155 lines
4.7 KiB
TypeScript
155 lines
4.7 KiB
TypeScript
import { tags as t } from "@lezer/highlight";
|
|
import type { Line, MarkdownConfig } from "@lezer/markdown";
|
|
import * as ct from "./customtags.ts";
|
|
|
|
const footnoteRefRegex = /^\[\^([^\]\s]+)\]/;
|
|
|
|
export const FootnoteRef: MarkdownConfig = {
|
|
defineNodes: [
|
|
{ name: "FootnoteRef" },
|
|
{ name: "FootnoteRefMark", style: t.processingInstruction },
|
|
{ name: "FootnoteRefLabel", style: ct.FootnoteRefTag },
|
|
],
|
|
parseInline: [
|
|
{
|
|
name: "FootnoteRef",
|
|
parse(cx, next, pos) {
|
|
if (next !== 91 /* '[' */) {
|
|
return -1;
|
|
}
|
|
const match = footnoteRefRegex.exec(cx.slice(pos, cx.end));
|
|
if (!match) {
|
|
return -1;
|
|
}
|
|
// If followed by ':', this is a definition, not a reference
|
|
const afterMatch = pos + match[0].length;
|
|
if (
|
|
afterMatch < cx.end &&
|
|
cx.slice(afterMatch, afterMatch + 1) === ":"
|
|
) {
|
|
return -1;
|
|
}
|
|
const endPos = pos + match[0].length;
|
|
return cx.addElement(
|
|
cx.elt("FootnoteRef", pos, endPos, [
|
|
cx.elt("FootnoteRefMark", pos, pos + 2), // [^
|
|
cx.elt("FootnoteRefLabel", pos + 2, endPos - 1), // label
|
|
cx.elt("FootnoteRefMark", endPos - 1, endPos), // ]
|
|
]),
|
|
);
|
|
},
|
|
after: "Emphasis",
|
|
},
|
|
],
|
|
};
|
|
|
|
const footnoteDefRegex = /^\[\^([^\]\s]+)\]:\s?/;
|
|
|
|
export const FootnoteDefinition: MarkdownConfig = {
|
|
defineNodes: [
|
|
{ name: "FootnoteDefinition", block: true },
|
|
{ name: "FootnoteDefMark", style: t.processingInstruction },
|
|
{ name: "FootnoteDefLabel", style: ct.FootnoteDefTag },
|
|
{ name: "FootnoteDefBody" },
|
|
],
|
|
parseBlock: [
|
|
{
|
|
name: "FootnoteDefinition",
|
|
parse: (cx, line: Line) => {
|
|
const match = footnoteDefRegex.exec(line.text);
|
|
if (!match) {
|
|
return false;
|
|
}
|
|
const label = match[1];
|
|
const markLen = match[0].length; // [^label]:
|
|
const startPos = cx.parsedPos;
|
|
let bodyText = line.text.slice(markLen);
|
|
let endPos = cx.parsedPos + line.text.length + 1;
|
|
|
|
// Consume continuation lines (indented by 4+ spaces or tab)
|
|
// Blank lines are allowed between continuation paragraphs
|
|
while (cx.nextLine()) {
|
|
if (/^(?: {4}|\t)/.test(line.text)) {
|
|
bodyText += `\n${line.text}`;
|
|
endPos = cx.parsedPos + line.text.length + 1;
|
|
} else if (line.text.trim() === "") {
|
|
// Blank line: peek ahead to see if next line is indented
|
|
bodyText += "\n";
|
|
endPos = cx.parsedPos + line.text.length + 1;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
// Trim trailing blank lines from the body
|
|
while (bodyText.endsWith("\n")) {
|
|
const trimmed = bodyText.slice(0, -1);
|
|
if (trimmed.endsWith("\n") || trimmed.length === 0) {
|
|
bodyText = trimmed;
|
|
endPos--;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
const labelStart = startPos + 2; // after [^
|
|
const labelEnd = labelStart + label.length;
|
|
const markEnd = startPos + markLen; // after [^label]:
|
|
// endPos - 1 to exclude trailing newline
|
|
const bodyEnd = endPos - 1;
|
|
|
|
const elts = [
|
|
cx.elt("FootnoteDefMark", startPos, startPos + 2), // [^
|
|
cx.elt("FootnoteDefLabel", labelStart, labelEnd),
|
|
cx.elt("FootnoteDefMark", labelEnd, markEnd), // ]:
|
|
];
|
|
if (bodyEnd > markEnd) {
|
|
elts.push(
|
|
cx.elt(
|
|
"FootnoteDefBody",
|
|
markEnd,
|
|
bodyEnd,
|
|
cx.parser.parseInline(bodyText, markEnd),
|
|
),
|
|
);
|
|
}
|
|
cx.addElement(cx.elt("FootnoteDefinition", startPos, bodyEnd, elts));
|
|
return true;
|
|
},
|
|
before: "LinkReference",
|
|
},
|
|
],
|
|
};
|
|
|
|
const inlineFootnoteRegex = /^\^\[([^\]]+)\]/;
|
|
|
|
export const InlineFootnote: MarkdownConfig = {
|
|
defineNodes: [
|
|
{ name: "InlineFootnote" },
|
|
{ name: "InlineFootnoteMark", style: t.processingInstruction },
|
|
{ name: "InlineFootnoteContent", style: ct.InlineFootnoteTag },
|
|
],
|
|
parseInline: [
|
|
{
|
|
name: "InlineFootnote",
|
|
parse(cx, next, pos) {
|
|
if (next !== 94 /* '^' */) {
|
|
return -1;
|
|
}
|
|
const match = inlineFootnoteRegex.exec(cx.slice(pos, cx.end));
|
|
if (!match) {
|
|
return -1;
|
|
}
|
|
const endPos = pos + match[0].length;
|
|
return cx.addElement(
|
|
cx.elt("InlineFootnote", pos, endPos, [
|
|
cx.elt("InlineFootnoteMark", pos, pos + 2), // ^[
|
|
cx.elt("InlineFootnoteContent", pos + 2, endPos - 1), // content
|
|
cx.elt("InlineFootnoteMark", endPos - 1, endPos), // ]
|
|
]),
|
|
);
|
|
},
|
|
after: "Emphasis",
|
|
},
|
|
],
|
|
};
|