Browse Source
Implements [[term]], [[w:term]], and [[d:term]] wiki link syntax: - New wiki_links.ts utility for parsing and tag generation - Extract wiki links from content and generate w/d tags - CodeMirror syntax highlighting (violet/cyan/amber) - Preview displays extracted wiki tags separately from hashtags - Tutorial documentation for wiki link usage Per WIKI_TAG_SPEC.md: - [[term]] and [[w:term]] generate w-tags (references/mentions) - [[d:term]] generates d-tags (definitions) - Custom display text: [[term|display text]] 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>master
3 changed files with 310 additions and 20 deletions
@ -0,0 +1,145 @@ |
|||||||
|
/** |
||||||
|
* Wiki link parsing and tag generation utilities |
||||||
|
* Supports [[term]], [[w:term]], and [[d:term]] syntax |
||||||
|
*/ |
||||||
|
|
||||||
|
export interface WikiLink { |
||||||
|
fullMatch: string; |
||||||
|
type: 'w' | 'd' | 'auto'; // auto means [[term]] without explicit prefix
|
||||||
|
term: string; |
||||||
|
displayText: string; |
||||||
|
startIndex: number; |
||||||
|
endIndex: number; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Extracts all wiki links from AsciiDoc content. |
||||||
|
* Supports three formats: |
||||||
|
* - [[term]] - Auto (will query both w and d tags) |
||||||
|
* - [[w:term]] - Explicit reference/mention (backward link) |
||||||
|
* - [[d:term]] - Explicit definition (forward link) |
||||||
|
*/ |
||||||
|
export function extractWikiLinks(content: string): WikiLink[] { |
||||||
|
const wikiLinks: WikiLink[] = []; |
||||||
|
|
||||||
|
// Match [[prefix:term]] or [[term]]
|
||||||
|
// Captures: optional prefix (w: or d:), term, optional display text after |
|
||||||
|
const regex = /\[\[(?:(w|d):)?([^\]|]+)(?:\|([^\]]+))?\]\]/g; |
||||||
|
|
||||||
|
let match; |
||||||
|
while ((match = regex.exec(content)) !== null) { |
||||||
|
const prefix = match[1]; // 'w', 'd', or undefined
|
||||||
|
const term = match[2].trim(); |
||||||
|
const customDisplay = match[3]?.trim(); |
||||||
|
|
||||||
|
wikiLinks.push({ |
||||||
|
fullMatch: match[0], |
||||||
|
type: prefix ? (prefix as 'w' | 'd') : 'auto', |
||||||
|
term, |
||||||
|
displayText: customDisplay || term, |
||||||
|
startIndex: match.index, |
||||||
|
endIndex: match.index + match[0].length, |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
return wikiLinks; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Converts a term to a clean tag format (lowercase, hyphenated). |
||||||
|
* Example: "Knowledge Graphs" -> "knowledge-graphs" |
||||||
|
*/ |
||||||
|
export function termToTag(term: string): string { |
||||||
|
return term |
||||||
|
.toLowerCase() |
||||||
|
.trim() |
||||||
|
.replace(/\s+/g, '-') |
||||||
|
.replace(/[^a-z0-9-]/g, ''); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Generates Nostr event tags from wiki links. |
||||||
|
* Format: ['w', 'tag-slug', 'Display Text'] or ['d', 'tag-slug'] |
||||||
|
*/ |
||||||
|
export function wikiLinksToTags(wikiLinks: WikiLink[]): string[][] { |
||||||
|
const tags: string[][] = []; |
||||||
|
|
||||||
|
for (const link of wikiLinks) { |
||||||
|
const tagSlug = termToTag(link.term); |
||||||
|
|
||||||
|
if (link.type === 'w' || link.type === 'auto') { |
||||||
|
// Reference tag includes display text
|
||||||
|
tags.push(['w', tagSlug, link.displayText]); |
||||||
|
} |
||||||
|
|
||||||
|
if (link.type === 'd') { |
||||||
|
// Definition tag (no display text, it IS the thing)
|
||||||
|
tags.push(['d', tagSlug]); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return tags; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Replaces wiki link syntax with HTML for preview rendering. |
||||||
|
* Can be customized for different rendering styles. |
||||||
|
*/ |
||||||
|
export function renderWikiLinksToHtml( |
||||||
|
content: string, |
||||||
|
options: { |
||||||
|
linkClass?: string; |
||||||
|
wLinkClass?: string; |
||||||
|
dLinkClass?: string; |
||||||
|
onClickHandler?: (type: 'w' | 'd' | 'auto', term: string) => string; |
||||||
|
} = {}, |
||||||
|
): string { |
||||||
|
const { |
||||||
|
linkClass = 'wiki-link', |
||||||
|
wLinkClass = 'wiki-link-reference', |
||||||
|
dLinkClass = 'wiki-link-definition', |
||||||
|
onClickHandler, |
||||||
|
} = options; |
||||||
|
|
||||||
|
return content.replace( |
||||||
|
/\[\[(?:(w|d):)?([^\]|]+)(?:\|([^\]]+))?\]\]/g, |
||||||
|
(match, prefix, term, customDisplay) => { |
||||||
|
const displayText = customDisplay?.trim() || term.trim(); |
||||||
|
const type = prefix ? prefix : 'auto'; |
||||||
|
const tagSlug = termToTag(term); |
||||||
|
|
||||||
|
// Determine CSS classes
|
||||||
|
let classes = linkClass; |
||||||
|
if (type === 'w') classes += ` ${wLinkClass}`; |
||||||
|
else if (type === 'd') classes += ` ${dLinkClass}`; |
||||||
|
|
||||||
|
// Generate href or onclick
|
||||||
|
const action = onClickHandler |
||||||
|
? `onclick="${onClickHandler(type, tagSlug)}"` |
||||||
|
: `href="#wiki/${type}/${encodeURIComponent(tagSlug)}"`; |
||||||
|
|
||||||
|
// Add title attribute showing the type
|
||||||
|
const title = |
||||||
|
type === 'w' |
||||||
|
? 'Wiki reference (mentions this concept)' |
||||||
|
: type === 'd' |
||||||
|
? 'Wiki definition (defines this concept)' |
||||||
|
: 'Wiki link (searches both references and definitions)'; |
||||||
|
|
||||||
|
return `<a class="${classes}" ${action} title="${title}" data-wiki-type="${type}" data-wiki-term="${tagSlug}">${displayText}</a>`; |
||||||
|
}, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Converts wiki links to plain text (for content storage). |
||||||
|
* Preserves the display text if custom, otherwise uses the term. |
||||||
|
*/ |
||||||
|
export function wikiLinksToPlainText(content: string): string { |
||||||
|
return content.replace( |
||||||
|
/\[\[(?:w|d:)?([^\]|]+)(?:\|([^\]]+))?\]\]/g, |
||||||
|
(match, term, customDisplay) => { |
||||||
|
return customDisplay?.trim() || term.trim(); |
||||||
|
}, |
||||||
|
); |
||||||
|
} |
||||||
Loading…
Reference in new issue