Add wikilink support as a markdown-it plugin with unit tests

This commit is contained in:
avi 2026-08-17 11:15:06 -05:00
commit 1d705ac607
3 changed files with 190 additions and 1 deletions

82
src/renderer/wikilinks.ts Normal file
View file

@ -0,0 +1,82 @@
import type { MarkdownIt, StateInline } from "markdown-it";
export interface WikilinkTarget {
id: string;
title: string;
}
export interface WikilinkOpts {
chapters: WikilinkTarget[];
}
export function wikilinkPlugin(md: MarkdownIt, opts: WikilinkOpts): void {
const chapters = opts?.chapters ?? [];
const byTitle = new Map(
chapters.map((c) => [String(c.title).toLowerCase(), c.id])
);
const byId = new Set(chapters.map((c) => String(c.id)));
const resolveId = (target: string): string | null => {
const key = target.toLowerCase();
if (byTitle.has(key)) return byTitle.get(key)!;
if (byId.has(target)) return target;
return null;
};
const parse = (state: StateInline, silent: boolean): boolean => {
const src = state.src;
const max = state.posMax;
const start = state.pos;
if (start + 2 > max) return false;
if (src.charCodeAt(start) !== 0x5b /* [ */ || src.charCodeAt(start + 1) !== 0x5b /* [ */)
return false;
let end = -1;
for (let i = start + 2; i < max - 1; i++) {
const ch = src.charCodeAt(i);
if (ch === 0x5b /* [ */) return false;
if (ch === 0x0a /* newline */) return false;
if (ch === 0x5d /* ] */ && src.charCodeAt(i + 1) === 0x5d) {
end = i;
break;
}
}
if (end === -1) return false;
const raw = src.slice(start + 2, end).trim();
if (!raw) return false;
let target = raw;
let label = raw;
const pipe = raw.indexOf("|");
if (pipe >= 0) {
target = raw.slice(0, pipe).trim();
const alias = raw.slice(pipe + 1).trim();
label = alias.length ? alias : target;
}
if (!target) return false;
if (silent) {
state.pos = end + 2;
return true;
}
const id = resolveId(target);
const open = state.push("link_open", "a", 1);
if (id !== null) {
open.attrs = [
["href", `folio:chapter/${encodeURIComponent(id)}`],
["class", "wikilink internal"],
];
} else {
open.attrs = [["href", "#"], ["class", "wikilink missing"]];
}
const text = state.push("text", "", 0);
text.content = label;
state.push("link_close", "a", -1);
state.pos = end + 2;
return true;
};
md.inline.ruler.before("link", "wikilink", parse);
}