import type MarkdownIt from "markdown-it"; const CALLOUT_RE = /^\[!(\w+)\]\s*(.*)?$/; const TYPE_ALIASES: Record = { info: "note", hint: "tip", caution: "warning", bug: "danger", cite: "quote", summary: "abstract", tldr: "abstract", check: "success", done: "success", help: "question", faq: "question", fail: "failure", missing: "failure", }; function normalizeType(raw: string): string { const lower = raw.toLowerCase(); return TYPE_ALIASES[lower] ?? lower; } export function calloutPlugin(mdi: MarkdownIt): void { mdi.core.ruler.after("block", "callout", (state) => { const tokens = state.tokens; for (let i = 0; i < tokens.length; i++) { const open = tokens[i]; if (open?.type !== "blockquote_open") continue; const paraOpen = tokens[i + 1]; const inline = tokens[i + 2]; if (paraOpen?.type !== "paragraph_open") continue; if (inline?.type !== "inline") continue; const lines = inline.content.split("\n"); const firstLine = lines[0] ?? ""; const match = CALLOUT_RE.exec(firstLine); if (!match) continue; const type = normalizeType(match[1] ?? ""); const customTitle = match[2]?.trim() || null; const title = customTitle || type.charAt(0).toUpperCase() + type.slice(1); open.type = "callout_open"; open.tag = "div"; open.attrSet("class", `callout callout-${type}`); const titleOpen = new state.Token("callout_title_open", "p", 1); titleOpen.attrSet("class", "callout-title"); const titleInline = new state.Token("inline", "", 0); titleInline.content = title; titleInline.children = []; const titleClose = new state.Token("callout_title_close", "p", -1); lines.shift(); const remaining = lines.join("\n").trim(); if (remaining) { inline.content = remaining; tokens.splice(i + 1, 0, titleOpen, titleInline, titleClose); } else { const paraClose = tokens[i + 3]; if (paraClose?.type === "paragraph_close") { tokens.splice(i + 1, 3, titleOpen, titleInline, titleClose); } else { inline.content = remaining; tokens.splice(i + 1, 0, titleOpen, titleInline, titleClose); } } let depth = 1; for (let j = i + 1; j < tokens.length; j++) { if (tokens[j]?.type === "blockquote_open") depth++; if (tokens[j]?.type === "callout_open") depth++; if ( tokens[j]?.type === "blockquote_close" || tokens[j]?.type === "callout_close" ) { depth--; if (depth === 0) { const closeToken = tokens[j]; if (closeToken) { closeToken.type = "callout_close"; closeToken.tag = "div"; } break; } } } } }); mdi.renderer.rules["callout_open"] = (tokens, idx) => { const cls = tokens[idx]?.attrGet("class") ?? "callout"; return `
\n`; }; mdi.renderer.rules["callout_close"] = () => "
\n"; mdi.renderer.rules["callout_title_open"] = (tokens, idx) => { const cls = tokens[idx]?.attrGet("class") ?? "callout-title"; return `

`; }; mdi.renderer.rules["callout_title_close"] = () => "

\n"; }