blogs/sites/research/src/remark/callouts.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

101 lines
2.4 KiB
JavaScript

const CALLOUT_LABELS = new Map([
["aside", "Aside"],
["caution", "Caution"],
["note", "Note"],
["tip", "Tip"],
["warning", "Warning"],
]);
function textFromInline(node) {
if (!node) return "";
if (typeof node.value === "string") return node.value;
if (!Array.isArray(node.children)) return "";
return node.children.map(textFromInline).join("");
}
function normalizeLabel(value) {
return value
.trim()
.replace(/^["“”]+|["“”]+$/g, "")
.replace(/:$/, "")
.toLowerCase();
}
function paragraphCalloutLabel(paragraph) {
if (paragraph?.type !== "paragraph") return null;
const first = paragraph.children?.[0];
if (first?.type === "text") {
const match = first.value.match(/^\s*\[!(aside|caution|note|tip|warning)\]\s*/i);
if (match) {
return CALLOUT_LABELS.get(normalizeLabel(match[1]));
}
}
if (first?.type !== "strong") return null;
const label = CALLOUT_LABELS.get(normalizeLabel(textFromInline(first)));
if (!label) return null;
return label;
}
function removeLeadingLabel(paragraph, label) {
if (paragraph?.type !== "paragraph") return;
const first = paragraph.children?.[0];
if (first?.type === "text") {
first.value = first.value.replace(/^\s*\[!(aside|caution|note|tip|warning)\]\s*/i, "");
if (!first.value) paragraph.children.shift();
return;
}
const firstText = normalizeLabel(textFromInline(first));
if (!first || first.type !== "strong" || firstText !== label.toLowerCase()) {
return;
}
paragraph.children.shift();
const next = paragraph.children[0];
if (next?.type === "text") {
next.value = next.value.replace(/^:\s*/, "").replace(/^\s+/, "");
if (!next.value) paragraph.children.shift();
}
}
function transformBlockquote(node) {
if (node.type !== "blockquote") return;
const first = node.children?.[0];
const label = paragraphCalloutLabel(first);
if (!label) return;
removeLeadingLabel(first, label);
if (first.children?.length === 0) {
node.children.shift();
}
node.data = {
hName: "aside",
hProperties: {
className: ["callout", `callout-${label.toLowerCase()}`],
dataCalloutLabel: label,
},
};
}
function visit(node) {
transformBlockquote(node);
if (!Array.isArray(node.children)) return;
for (const child of node.children) {
visit(child);
}
}
export default function remarkCallouts() {
return function transformer(tree) {
visit(tree);
};
}