master @ 65 LINES
[ HISTORY ] [ UP ]
┌─ TS ───────────────────────────────────────────────────────────────────────┐
│ // [[target]] and [[target|alias]] ? links, resolved against the wiki │
│ // map at build time. Unresolved targets render as soft dead links, in │
│ // the wiki tradition, rather than failing the build mid-draft. │
│ import { buildWikiMap, resolveWikilink, type WikiMap } from "./wikilinks"; │
│ │
│ const WIKI_RE = /\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/g; │
│ │
│ interface MdNode { │
│ type: string; │
│ value?: string; │
│ children?: MdNode[]; │
│ [key: string]: unknown; │
│ } │
│ │
│ const esc = (s: string) => │
│ s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"); │
│ │
│ export function wikiNodes(text: string, map: WikiMap): MdNode[] | null { │
│ if (!text.includes("[[")) return null; │
│ const out: MdNode[] = []; │
│ let last = 0; │
│ for (const m of text.matchAll(WIKI_RE)) { │
│ if (m.index! > last) out.push({ type: "text", value: text.slice(last, m.index) │
│ }); │
│ const target = m[1].trim(); │
│ const label = (m[2] ?? m[1]).trim(); │
│ const url = resolveWikilink(target, map); │
│ out.push( │
│ url │
│ ? { │
│ type: "link", │
│ url, │
│ children: [{ type: "text", value: label }], │
│ data: { hProperties: { className: ["wikilink"] } }, │
│ } │
│ : { type: "html", value: `<span class="wikilink-dead" title="unresolved: $ │
│ {esc(target)}">${esc(label)}</span>` }, │
│ ); │
│ last = m.index! + m[0].length; │
│ } │
│ if (!out.length) return null; │
│ if (last < text.length) out.push({ type: "text", value: text.slice(last) }); │
│ return out; │
│ } │
│ │
│ function walk(node: MdNode, map: WikiMap) { │
│ if (!node.children) return; │
│ if (node.type === "link") return; // don't rewrite inside real links │
│ node.children = node.children.flatMap((child) => { │
│ if (child.type === "text" && child.value) { │
│ const replaced = wikiNodes(child.value, map); │
│ if (replaced) return replaced; │
│ } │
│ walk(child, map); │
│ return [child]; │
│ }); │
│ } │
│ │
│ let cache: WikiMap | null = null; │
│ │
│ export function remarkWikilinks() { │
│ return (tree: MdNode) => { │
│ cache ??= buildWikiMap(); │
│ walk(tree, cache); │
│ }; │
│ } │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ TS ─────────────────────────────────┐
│ // [[target]] and [[target|alias]] ? links, │
│ resolved against the wiki │
│ // map at build time. Unresolved targets ren │
│ der as soft dead links, in │
│ // the wiki tradition, rather than failing t │
│ he build mid-draft. │
│ import { buildWikiMap, resolveWikilink, type │
│ WikiMap } from "./wikilinks"; │
│ │
│ const WIKI_RE = /\[\[([^\]|]+)(?:\|([^\]]+)) │
│ ?\]\]/g; │
│ │
│ interface MdNode { │
│ type: string; │
│ value?: string; │
│ children?: MdNode[]; │
│ [key: string]: unknown; │
│ } │
│ │
│ const esc = (s: string) => │
│ s.replace(/&/g, "&").replace(/</g, "&l │
│ t;").replace(/>/g, ">"); │
│ │
│ export function wikiNodes(text: string, map: │
│ WikiMap): MdNode[] | null { │
│ if (!text.includes("[[")) return null; │
│ const out: MdNode[] = []; │
│ let last = 0; │
│ for (const m of text.matchAll(WIKI_RE)) { │
│ if (m.index! > last) out.push({ type: "t │
│ ext", value: text.slice(last, m.index) }); │
│ const target = m[1].trim(); │
│ const label = (m[2] ?? m[1]).trim(); │
│ const url = resolveWikilink(target, map) │
│ ; │
│ out.push( │
│ url │
│ ? { │
│ type: "link", │
│ url, │
│ children: [{ type: "text", value │
│ : label }], │
│ data: { hProperties: { className │
│ : ["wikilink"] } }, │
│ } │
│ : { type: "html", value: `<span clas │
│ s="wikilink-dead" title="unresolved: ${esc(t │
│ arget)}">${esc(label)}</span>` }, │
│ ); │
│ last = m.index! + m[0].length; │
│ } │
│ if (!out.length) return null; │
│ if (last < text.length) out.push({ type: " │
│ text", value: text.slice(last) }); │
│ return out; │
│ } │
│ │
│ function walk(node: MdNode, map: WikiMap) { │
│ if (!node.children) return; │
│ if (node.type === "link") return; // don't │
│ rewrite inside real links │
│ node.children = node.children.flatMap((chi │
│ ld) => { │
│ if (child.type === "text" && child.value │
│ ) { │
│ const replaced = wikiNodes(child.value │
│ , map); │
│ if (replaced) return replaced; │
│ } │
│ walk(child, map); │
│ return [child]; │
│ }); │
│ } │
│ │
│ let cache: WikiMap | null = null; │
│ │
│ export function remarkWikilinks() { │
│ return (tree: MdNode) => { │
│ cache ??= buildWikiMap(); │
│ walk(tree, cache); │
│ }; │
│ } │
└──────────────────────────────────────────────┘
──────────────────────────────────────────────────────────────────────────────────────
OOKNET
────────────────────────────────────────────────
OOKNET