import type { MarkedExtension } from "marked"; import yaml from "js-yaml"; import { parseBlockAttrs } from "./block-attrs.js"; /** * YAML-defined tag blocks: * * ```yaml role=tag tag=md-deck id=my-deck * body: ./cards.csv * data-config: ... * ``` * * `role=tag` on the info string marks the block (and lets the content * scanner keep it intact). `tag=` / `id=` / any other `key=value` fence * attributes override the same keys in the YAML body, so simple cases * stay on one line. */ export default function markedCodeBlockYamlTag(): MarkedExtension { return { extensions: [ { name: "code-block-yaml-tag", level: "block", start(src: string) { return src.match(/^```yaml\s+role=tag(?:\s|\n)/m)?.index; }, tokenizer(src: string) { const rule = /^```yaml\s+role=tag([^\n]*)\n([\s\S]*?)\n```/; const match = rule.exec(src); if (match) { const yamlContent = match[2]?.trim() || ""; let yamlProps: Record = {}; try { yamlProps = (yaml.load(yamlContent) as Record) || {}; } catch (e) { console.error("YAML Parse Error in code-block-yaml-tag:", e); yamlProps = { error: "Invalid YAML content" }; } // Fence attributes override YAML body values. const attrs = parseBlockAttrs(match[1] || ""); const fenceProps: Record = { ...(attrs.id ? { id: attrs.id } : {}), ...attrs.extra, }; const props: Record = { ...yamlProps, ...fenceProps }; const tagName = (props.tag as string) || "tag-unknown"; const { tag, ...rest } = props; let content = ""; if ("body" in rest) { content = String(rest.body || ""); delete (rest as Record).body; } // `data-*` props may hold structured YAML values, so they are // serialized to JSON strings (e.g. md-deck layers/templates). const propsStr = Object.entries(rest) .map(([key, value]) => { const strValue = key.startsWith("data-") ? JSON.stringify(value) : String(value); if (strValue.includes(" ") || strValue.includes('"')) { return `${key}="${strValue.replace(/"/g, """)}"`; } return `${key}="${strValue}"`; }) .join(" "); return { type: "code-block-yaml-tag", raw: match[0], tagName, props: propsStr, content, }; } }, renderer(token: any) { const propsAttr = token.props ? ` ${token.props}` : ""; return `<${token.tagName}${propsAttr}>${token.content || ""}\n`; }, }, ], }; }