blogs/sites/research/src/remark/inline-footnotes.mjs
Neeldhara Misra 8d391a2e5e
Some checks are pending
Build / build (push) Waiting to run
Fix isolated site builds for Dokploy
2026-06-25 04:22:41 +05:30

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;