101 lines
2.4 KiB
JavaScript
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);
|
|
};
|
|
}
|