289 lines
6 KiB
JavaScript
289 lines
6 KiB
JavaScript
import { fromMarkdown } from "mdast-util-from-markdown";
|
|
|
|
const INLINE_FOOTNOTE_PREFIX = "inline-footnote";
|
|
|
|
const findInlineFootnote = (value, fromIndex = 0) => {
|
|
const start = value.indexOf("^[", fromIndex);
|
|
|
|
if (start === -1) {
|
|
return null;
|
|
}
|
|
|
|
let depth = 1;
|
|
|
|
for (let index = start + 2; index < value.length; index += 1) {
|
|
const character = value[index];
|
|
|
|
if (character === "\\" && index + 1 < value.length) {
|
|
index += 1;
|
|
continue;
|
|
}
|
|
|
|
if (character === "[") {
|
|
depth += 1;
|
|
continue;
|
|
}
|
|
|
|
if (character === "]") {
|
|
depth -= 1;
|
|
|
|
if (depth === 0) {
|
|
return {
|
|
start,
|
|
end: index + 1,
|
|
value: value.slice(start + 2, index).trim(),
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
const createFootnoteChildren = (value) => {
|
|
const tree = fromMarkdown(value);
|
|
|
|
if (tree.children.length > 0) {
|
|
return tree.children;
|
|
}
|
|
|
|
return [
|
|
{
|
|
type: "paragraph",
|
|
children: [
|
|
{
|
|
type: "text",
|
|
value,
|
|
},
|
|
],
|
|
},
|
|
];
|
|
};
|
|
|
|
const createFootnoteParagraph = (children) => ({
|
|
type: "paragraph",
|
|
children:
|
|
children.length > 0
|
|
? children
|
|
: [
|
|
{
|
|
type: "text",
|
|
value: "",
|
|
},
|
|
],
|
|
});
|
|
|
|
const createFootnoteDefinition = (identifier, label, value) => ({
|
|
type: "footnoteDefinition",
|
|
identifier,
|
|
label,
|
|
children: createFootnoteChildren(value),
|
|
});
|
|
|
|
const createFootnoteDefinitionFromChildren = (identifier, label, children) => ({
|
|
type: "footnoteDefinition",
|
|
identifier,
|
|
label,
|
|
children: [createFootnoteParagraph(children)],
|
|
});
|
|
|
|
const createFootnoteReference = (context) => {
|
|
context.count += 1;
|
|
|
|
const label = String(context.count);
|
|
const identifier = `${INLINE_FOOTNOTE_PREFIX}-${label}`;
|
|
|
|
return {
|
|
identifier,
|
|
label,
|
|
node: {
|
|
type: "footnoteReference",
|
|
identifier,
|
|
label,
|
|
},
|
|
};
|
|
};
|
|
|
|
const findClosingBracket = (value) => {
|
|
for (let index = 0; index < value.length; index += 1) {
|
|
const character = value[index];
|
|
|
|
if (character === "\\" && index + 1 < value.length) {
|
|
index += 1;
|
|
continue;
|
|
}
|
|
|
|
if (character === "]") {
|
|
return index;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
};
|
|
|
|
const cloneNode = (node) => JSON.parse(JSON.stringify(node));
|
|
|
|
const splitTextNode = (node, context) => {
|
|
const replacements = [];
|
|
let cursor = 0;
|
|
let match = findInlineFootnote(node.value, cursor);
|
|
|
|
while (match) {
|
|
if (match.start > cursor) {
|
|
replacements.push({
|
|
type: "text",
|
|
value: node.value.slice(cursor, match.start),
|
|
});
|
|
}
|
|
|
|
if (match.value) {
|
|
const reference = createFootnoteReference(context);
|
|
replacements.push(reference.node);
|
|
|
|
context.definitions.push(
|
|
createFootnoteDefinition(reference.identifier, reference.label, match.value),
|
|
);
|
|
} else {
|
|
replacements.push({
|
|
type: "text",
|
|
value: node.value.slice(match.start, match.end),
|
|
});
|
|
}
|
|
|
|
cursor = match.end;
|
|
match = findInlineFootnote(node.value, cursor);
|
|
}
|
|
|
|
if (cursor < node.value.length) {
|
|
replacements.push({
|
|
type: "text",
|
|
value: node.value.slice(cursor),
|
|
});
|
|
}
|
|
|
|
return replacements;
|
|
};
|
|
|
|
const splitAcrossInlineNodes = (children, startIndex, context) => {
|
|
const startNode = children[startIndex];
|
|
const start = startNode.value.indexOf("^[");
|
|
|
|
if (start === -1) {
|
|
return null;
|
|
}
|
|
|
|
if (findInlineFootnote(startNode.value, start)) {
|
|
return null;
|
|
}
|
|
|
|
const footnoteChildren = [];
|
|
const replacements = [];
|
|
const before = startNode.value.slice(0, start);
|
|
const initialText = startNode.value.slice(start + 2);
|
|
|
|
if (before) {
|
|
replacements.push({
|
|
type: "text",
|
|
value: before,
|
|
});
|
|
}
|
|
|
|
if (initialText) {
|
|
footnoteChildren.push({
|
|
type: "text",
|
|
value: initialText,
|
|
});
|
|
}
|
|
|
|
for (let index = startIndex + 1; index < children.length; index += 1) {
|
|
const child = children[index];
|
|
|
|
if (child.type === "text") {
|
|
const closingIndex = findClosingBracket(child.value);
|
|
|
|
if (closingIndex !== -1) {
|
|
const footnoteText = child.value.slice(0, closingIndex);
|
|
const after = child.value.slice(closingIndex + 1);
|
|
|
|
if (footnoteText) {
|
|
footnoteChildren.push({
|
|
type: "text",
|
|
value: footnoteText,
|
|
});
|
|
}
|
|
|
|
const reference = createFootnoteReference(context);
|
|
replacements.push(reference.node);
|
|
|
|
if (after) {
|
|
replacements.push({
|
|
type: "text",
|
|
value: after,
|
|
});
|
|
}
|
|
|
|
context.definitions.push(
|
|
createFootnoteDefinitionFromChildren(
|
|
reference.identifier,
|
|
reference.label,
|
|
footnoteChildren,
|
|
),
|
|
);
|
|
|
|
return {
|
|
consumed: index - startIndex + 1,
|
|
replacements,
|
|
};
|
|
}
|
|
|
|
footnoteChildren.push(cloneNode(child));
|
|
continue;
|
|
}
|
|
|
|
footnoteChildren.push(cloneNode(child));
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
const visitChildren = (node, context) => {
|
|
if (!Array.isArray(node.children)) {
|
|
return;
|
|
}
|
|
|
|
for (let index = 0; index < node.children.length; index += 1) {
|
|
const child = node.children[index];
|
|
|
|
if (child.type === "text" && child.value.includes("^[")) {
|
|
const acrossNodes = splitAcrossInlineNodes(node.children, index, context);
|
|
|
|
if (acrossNodes) {
|
|
node.children.splice(index, acrossNodes.consumed, ...acrossNodes.replacements);
|
|
index += acrossNodes.replacements.length - 1;
|
|
continue;
|
|
}
|
|
|
|
const replacements = splitTextNode(child, context);
|
|
node.children.splice(index, 1, ...replacements);
|
|
index += replacements.length - 1;
|
|
continue;
|
|
}
|
|
|
|
visitChildren(child, context);
|
|
}
|
|
};
|
|
|
|
const remarkInlineFootnotes = () => (tree) => {
|
|
const context = {
|
|
count: 0,
|
|
definitions: [],
|
|
};
|
|
|
|
visitChildren(tree, context);
|
|
|
|
if (context.definitions.length > 0) {
|
|
tree.children.push(...context.definitions);
|
|
}
|
|
};
|
|
|
|
export default remarkInlineFootnotes;
|