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); }; }