┌─ TS ───────────────────────────────────────────────────────────────────────┐
│ // Wikilink resolution. The map is built by scanning content frontmatter │
│ // directly (remark plugins run outside astro:content), keyed by every │
│ // name an author might reach for: file id and title. │
│ import { readdirSync, readFileSync } from "node:fs"; │
│ import { join } from "node:path"; │
│ import { readManifests, registerOf, registerLabel } from "./registers"; │
│ │
│ export type WikiMap = Map<string, string>; │
│ │
│ const FM_RE = /^---\n([\s\S]*?)\n---/; │
│ │
│ function fmField(fm: string, key: string): string | null { │
│ const m = fm.match(new RegExp(`^${key}:\\s*["']?(.+?)["']?\\s*$`, "m")); │
│ return m ? m[1] : null; │
│ } │
│ │
│ function mdFiles(dir: string, prefix = ""): string[] { │
│ let out: string[] = []; │
│ for (const entry of readdirSync(dir, { withFileTypes: true })) { │
│ const rel = prefix ? `${prefix}/${entry.name}` : entry.name; │
│ if (entry.isDirectory()) out = out.concat(mdFiles(join(dir, entry.name), rel)) │
│ ; │
│ else if (entry.name.endsWith(".md")) out.push(rel); │
│ } │
│ return out; │
│ } │
│ │
│ function scan(map: WikiMap, dir: string, urlFor: (id: string) => string) { │
│ let files: string[]; │
│ try { │
│ files = mdFiles(dir); │
│ } catch { │
│ return; // collection dir may not exist yet │
│ } │
│ for (const file of files) { │
│ const id = file.replace(/\.md$/, ""); │
│ const url = urlFor(id); │
│ const fm = readFileSync(join(dir, file), "utf8").match(FM_RE)?.[1] ?? ""; │
│ if (fmField(fm, "draft") === "true") continue; │
│ map.set(id.toLowerCase(), url); │
│ const title = fmField(fm, "title"); │
│ if (title) map.set(title.toLowerCase(), url); │
│ } │
│ } │
│ │
│ export function buildWikiMap(contentDir = "src/content"): WikiMap { │
│ const map: WikiMap = new Map(); │
│ scan(map, join(contentDir, "kb"), (id) => `/kb/${id}/`); │
│ return map; │
│ } │
│ │
│ export function resolveWikilink(target: string, map: WikiMap): string | null { │
│ return map.get(target.trim().toLowerCase()) ?? null; │
│ } │
│ │
│ // ── Backlinks ──────────────────────────────────────────────────────── │
│ // The same graph, inverted: for each page, which pages link to it. │
│ │
│ export interface Backlink { │
│ url: string; │
│ label: string; │
│ } │
│ │
│ const WIKI_RE = /\[\[([^\]|]+)(?:\|[^\]]+)?\]\]/g; │
│ │
│ /** Wikilink targets in a markdown body. Code is stripped first so │
│ * literal `[[syntax]]` in docs doesn't count as a reference - mirrors │
│ * the remark plugin, which never sees code as text nodes. */ │
│ export function extractWikiTargets(body: string): string[] { │
│ const stripped = body │
│ .replace(/^(?:```|~~~)[\s\S]*?^(?:```|~~~)/gm, "") │
│ .replace(/`[^`\n]+`/g, ""); │
│ return [...stripped.matchAll(WIKI_RE)].map((m) => m[1].trim()); │
│ } │
│ │
│ function scanSources( │
│ out: Map<string, Backlink[]>, │
│ map: WikiMap, │
│ dir: string, │
│ urlFor: (id: string) => string, │
│ labelFor: (id: string, fm: string) => string, │
│ ) { │
│ let files: string[]; │
│ try { │
│ files = mdFiles(dir); │
│ } catch { │
│ return; │
│ } │
│ for (const file of files) { │
│ const id = file.replace(/\.md$/, ""); │
│ const raw = readFileSync(join(dir, file), "utf8"); │
│ const fmMatch = raw.match(FM_RE); │
│ const fm = fmMatch?.[1] ?? ""; │
│ if (fmField(fm, "draft") === "true") continue; │
│ const srcUrl = urlFor(id); │
│ const targets = new Set<string>(); │
│ for (const t of extractWikiTargets(raw.slice(fmMatch?.[0].length ?? 0))) { │
│ const url = resolveWikilink(t, map); │
│ if (url && url !== srcUrl) targets.add(url); │
│ } │
│ for (const target of targets) { │
│ if (!out.has(target)) out.set(target, []); │
│ out.get(target)!.push({ url: srcUrl, label: labelFor(id, fm) }); │
│ } │
│ } │
│ } │
│ │
│ /** Target url ? pages whose body links to it. Dead links and │
│ * self-references are dropped; sources carry their register label. */ │
│ export function buildBacklinks(contentDir = "src/content"): Map<string, Backlink[] │
│ > { │
│ const map = buildWikiMap(contentDir); │
│ const manifests = readManifests(join(contentDir, "kb")); │
│ const out = new Map<string, Backlink[]>(); │
│ scanSources(out, map, join(contentDir, "kb"), (id) => `/kb/${id}/`, (id, fm) => │
│ { │
│ const title = fmField(fm, "title") ?? id; │
│ return `${registerLabel(registerOf(id, manifests).register)} - ${title.toUpper │
│ Case()}`; │
│ }); │
│ for (const list of out.values()) { │
│ list.sort((a, b) => a.label.localeCompare(b.label, "en", { numeric: true })); │
│ } │
│ return out; │
│ } │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ TS ─────────────────────────────────┐
│ // Wikilink resolution. The map is built by │
│ scanning content frontmatter │
│ // directly (remark plugins run outside astr │
│ o:content), keyed by every │
│ // name an author might reach for: file id a │
│ nd title. │
│ import { readdirSync, readFileSync } from "n │
│ ode:fs"; │
│ import { join } from "node:path"; │
│ import { readManifests, registerOf, register │
│ Label } from "./registers"; │
│ │
│ export type WikiMap = Map<string, string>; │
│ │
│ const FM_RE = /^---\n([\s\S]*?)\n---/; │
│ │
│ function fmField(fm: string, key: string): s │
│ tring | null { │
│ const m = fm.match(new RegExp(`^${key}:\\s │
│ *["']?(.+?)["']?\\s*$`, "m")); │
│ return m ? m[1] : null; │
│ } │
│ │
│ function mdFiles(dir: string, prefix = ""): │
│ string[] { │
│ let out: string[] = []; │
│ for (const entry of readdirSync(dir, { wit │
│ hFileTypes: true })) { │
│ const rel = prefix ? `${prefix}/${entry. │
│ name}` : entry.name; │
│ if (entry.isDirectory()) out = out.conca │
│ t(mdFiles(join(dir, entry.name), rel)); │
│ else if (entry.name.endsWith(".md")) out │
│ .push(rel); │
│ } │
│ return out; │
│ } │
│ │
│ function scan(map: WikiMap, dir: string, url │
│ For: (id: string) => string) { │
│ let files: string[]; │
│ try { │
│ files = mdFiles(dir); │
│ } catch { │
│ return; // collection dir may not exist │
│ yet │
│ } │
│ for (const file of files) { │
│ const id = file.replace(/\.md$/, ""); │
│ const url = urlFor(id); │
│ const fm = readFileSync(join(dir, file), │
│ "utf8").match(FM_RE)?.[1] ?? ""; │
│ if (fmField(fm, "draft") === "true") con │
│ tinue; │
│ map.set(id.toLowerCase(), url); │
│ const title = fmField(fm, "title"); │
│ if (title) map.set(title.toLowerCase(), │
│ url); │
│ } │
│ } │
│ │
│ export function buildWikiMap(contentDir = "s │
│ rc/content"): WikiMap { │
│ const map: WikiMap = new Map(); │
│ scan(map, join(contentDir, "kb"), (id) => │
│ `/kb/${id}/`); │
│ return map; │
│ } │
│ │
│ export function resolveWikilink(target: stri │
│ ng, map: WikiMap): string | null { │
│ return map.get(target.trim().toLowerCase() │
│ ) ?? null; │
│ } │
│ │
│ // ── Backlinks ──────────────────────────── │
│ ──────────────────────────── │
│ // The same graph, inverted: for each page, │
│ which pages link to it. │
│ │
│ export interface Backlink { │
│ url: string; │
│ label: string; │
│ } │
│ │
│ const WIKI_RE = /\[\[([^\]|]+)(?:\|[^\]]+)?\ │
│ ]\]/g; │
│ │
│ /** Wikilink targets in a markdown body. Cod │
│ e is stripped first so │
│ * literal `[[syntax]]` in docs doesn't cou │
│ nt as a reference - mirrors │
│ * the remark plugin, which never sees code │
│ as text nodes. */ │
│ export function extractWikiTargets(body: str │
│ ing): string[] { │
│ const stripped = body │
│ .replace(/^(?:```|~~~)[\s\S]*?^(?:```|~~ │
│ ~)/gm, "") │
│ .replace(/`[^`\n]+`/g, ""); │
│ return [...stripped.matchAll(WIKI_RE)].map │
│ ((m) => m[1].trim()); │
│ } │
│ │
│ function scanSources( │
│ out: Map<string, Backlink[]>, │
│ map: WikiMap, │
│ dir: string, │
│ urlFor: (id: string) => string, │
│ labelFor: (id: string, fm: string) => stri │
│ ng, │
│ ) { │
│ let files: string[]; │
│ try { │
│ files = mdFiles(dir); │
│ } catch { │
│ return; │
│ } │
│ for (const file of files) { │
│ const id = file.replace(/\.md$/, ""); │
│ const raw = readFileSync(join(dir, file) │
│ , "utf8"); │
│ const fmMatch = raw.match(FM_RE); │
│ const fm = fmMatch?.[1] ?? ""; │
│ if (fmField(fm, "draft") === "true") con │
│ tinue; │
│ const srcUrl = urlFor(id); │
│ const targets = new Set<string>(); │
│ for (const t of extractWikiTargets(raw.s │
│ lice(fmMatch?.[0].length ?? 0))) { │
│ const url = resolveWikilink(t, map); │
│ if (url && url !== srcUrl) targets.add │
│ (url); │
│ } │
│ for (const target of targets) { │
│ if (!out.has(target)) out.set(target, │
│ []); │
│ out.get(target)!.push({ url: srcUrl, l │
│ abel: labelFor(id, fm) }); │
│ } │
│ } │
│ } │
│ │
│ /** Target url ? pages whose body links to i │
│ t. Dead links and │
│ * self-references are dropped; sources car │
│ ry their register label. */ │
│ export function buildBacklinks(contentDir = │
│ "src/content"): Map<string, Backlink[]> { │
│ const map = buildWikiMap(contentDir); │
│ const manifests = readManifests(join(conte │
│ ntDir, "kb")); │
│ const out = new Map<string, Backlink[]>(); │
│ scanSources(out, map, join(contentDir, "kb │
│ "), (id) => `/kb/${id}/`, (id, fm) => { │
│ const title = fmField(fm, "title") ?? id │
│ ; │
│ return `${registerLabel(registerOf(id, m │
│ anifests).register)} - ${title.toUpperCase() │
│ }`; │
│ }); │
│ for (const list of out.values()) { │
│ list.sort((a, b) => a.label.localeCompar │
│ e(b.label, "en", { numeric: true })); │
│ } │
│ return out; │
│ } │
└──────────────────────────────────────────────┘