Files
ttrpg-tools/src/markdown/code-block-yaml-tag.ts
T
hypercross f33761e24f refactor(markdown): Make module CLI-importable
Drop the mermaid import from the markdown module. The extension only emits <pre class="mermaid">, so initialize mermaid in the app entry instead. Use explicit .js specifiers plus a bundler extensionAlias so both the NodeNext CLI build and the web build resolve the sources.
2026-09-12 16:38:03 +08:00

88 lines
3.0 KiB
TypeScript

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<string, unknown> = {};
try {
yamlProps =
(yaml.load(yamlContent) as Record<string, unknown>) || {};
} 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<string, unknown> = {
...(attrs.id ? { id: attrs.id } : {}),
...attrs.extra,
};
const props: Record<string, unknown> = { ...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<string, unknown>).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, "&quot;")}"`;
}
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 || ""}</${token.tagName}>\n`;
},
},
],
};
}