From d75df5280f9c9fff1c0febe455ac36d39d2517f1 Mon Sep 17 00:00:00 2001 From: hypercross Date: Thu, 3 Sep 2026 10:09:55 +0800 Subject: [PATCH] feat(markdown): support yaml role=tag info string Accept yaml role=tag alongside legacy yaml/tag so the block body gets yaml syntax highlighting, matching the CLI role= attribute convention. Copy button now emits the role=tag form. --- content/deck-test.md | 23 +++++++++++++++ docs/markdown.md | 21 ++++++++++---- src/components/md-deck/hooks/deckStore.ts | 7 +++-- src/doc-entries/md-deck.md | 10 +++---- src/markdown/code-block-yaml-tag.test.ts | 35 +++++++++++++++++++---- src/markdown/code-block-yaml-tag.ts | 9 ++++-- 6 files changed, 83 insertions(+), 22 deletions(-) diff --git a/content/deck-test.md b/content/deck-test.md index 1e03fe5..b6aa5d5 100644 --- a/content/deck-test.md +++ b/content/deck-test.md @@ -15,3 +15,26 @@ bleed: 1 padding: 2 layers: name1:1,1-2,2f12 name2:4,7-5,8f12s num1:1,3-2,4f12 num2:4,5-5,6f12s ``` + +## 使用 yaml role=tag 语法创建 md-deck(推荐,可语法高亮) + +```yaml role=tag +tag: md-deck +body: ./names.csv +data-config: + size: 54x86 + grid: 5x8 + layers: + - prop: name1 + pos: 1,1-2,2 + font: 12 + - prop: num2 + pos: 4,5-5,6 + font: 12 + orientation: s + - template: | + **{{name1}}** / {{num1}} + pos: 1,3-5,4 + font: 6 + align: l +``` diff --git a/docs/markdown.md b/docs/markdown.md index 5afa1f6..446644d 100644 --- a/docs/markdown.md +++ b/docs/markdown.md @@ -423,12 +423,12 @@ layers="字段:起始行,起始列 - 结束列,字体大小" 示例:`title:1,1-5,1f8` 表示 title 字段从第 1 行开始,占据 1-5 列,8mm字体。 -**结构化配置(yaml/tag 代码块):** +**结构化配置(yaml 代码块):** -可以使用 ```yaml/tag 代码块,通过 `body` 指定 CSV 路径,`data-config` 提供结构化配置(图层可以是字段或模板): +推荐使用 ```yaml role=tag 代码块(`yaml` 语言可被语法高亮),通过 `body` 指定 CSV 路径,`data-config` 提供结构化配置(图层可以是字段或模板): ````markdown -```yaml/tag +```yaml role=tag tag: md-deck body: ./cards.csv data-config: @@ -489,13 +489,22 @@ track npc#john.dwarf.warrior[hp=4/4 ac=15 name="John"] ## YAML 标签 -使用 ```yaml/tag 代码块创建自定义标签: +使用 ```yaml role=tag 代码块创建自定义标签(推荐,`yaml` 语言可被语法高亮): + +````markdown +```yaml role=tag +tag: tag-name +class: custom-class +id: my-id +body: 标签内容 +``` +```` + +也支持旧写法 ```yaml/tag(等价,但无语法高亮): ````markdown ```yaml/tag tag: tag-name -class: custom-class -id: my-id body: 标签内容 ``` ```` diff --git a/src/components/md-deck/hooks/deckStore.ts b/src/components/md-deck/hooks/deckStore.ts index ad46a74..5456e8e 100644 --- a/src/components/md-deck/hooks/deckStore.ts +++ b/src/components/md-deck/hooks/deckStore.ts @@ -521,7 +521,7 @@ export function createDeckStore(initialSrc: string = ""): DeckStore { return parts.join(""); }; - /** Serialize the deck back to a ```yaml/tag codeblock (round-trips templates). */ + /** Serialize the deck back to a yaml codeblock (round-trips templates). */ const generateYamlCode = () => { const toYamlLayer = (l: LayerConfig) => { const out: Record = {}; @@ -560,8 +560,9 @@ export function createDeckStore(initialSrc: string = ""): DeckStore { lineWidth: -1, noRefs: true, }); - const fence = "```"; - return `${fence}yaml/tag\n${yamlStr}${fence}`; + const fence = "```yaml role=tag"; + const backtick = "`"; + return `${fence}\n${yamlStr}${backtick.repeat(3)}`; }; const copyCode = async (fallback?: (code: string) => void) => { diff --git a/src/doc-entries/md-deck.md b/src/doc-entries/md-deck.md index 80a2a47..1541d9b 100644 --- a/src/doc-entries/md-deck.md +++ b/src/doc-entries/md-deck.md @@ -6,7 +6,7 @@ title: 卡牌组件 将 CSV 数据渲染为可打印的卡牌布局,支持自定义尺寸、网格、图层和双面排版。 -**语法:** `:md-deck[./cards.csv]{选项}` 或 ```yaml/tag 代码块 +**语法:** `:md-deck[./cards.csv]{选项}` 或 yaml 代码块 **基础卡牌:** :md-deck[./spells.csv]{grid="3x3"} @@ -16,12 +16,12 @@ title: 卡牌组件 CSV 包含数据字段列,通过图层定义控制各字段的位置、大小和对齐。 -## 结构化配置 (yaml/tag) +## 结构化配置 (yaml 代码块) -```yaml/tag 代码块可以用 `body` 指定 CSV 路径,`data-config` 提供结构化配置,图层可以是字段(`prop`)或 markdown 模板(`template`,支持 `{{变量}}` 替换): +推荐 ```yaml role=tag 代码块(`yaml` 语言可被语法高亮),用 `body` 指定 CSV 路径,`data-config` 提供结构化配置,图层可以是字段(`prop`)或 markdown 模板(`template`,支持 `{{变量}}` 替换): ````markdown -```yaml/tag +```yaml role=tag tag: md-deck body: ./cards.csv data-config: @@ -40,7 +40,7 @@ data-config: ``` ```` -`pos` 为网格位置 `x1,y1-x2,y2`(1-based,与紧凑 layers 字符串同格式)。 +`pos` 为网格位置 `x1,y1-x2,y2`(1-based,与紧凑 layers 字符串同格式)。也支持旧写法 ```yaml/tag。 ## 图层格式 diff --git a/src/markdown/code-block-yaml-tag.test.ts b/src/markdown/code-block-yaml-tag.test.ts index ea8fa75..07078bb 100644 --- a/src/markdown/code-block-yaml-tag.test.ts +++ b/src/markdown/code-block-yaml-tag.test.ts @@ -28,10 +28,7 @@ describe("code-block-yaml-tag", () => { " size: 63x88", " layers:", " - prop: title", - " x1: 1", - " y1: 1", - " x2: 5", - " y2: 1", + " pos: 1,1-5,1", " font: 12", "```", ].join("\n"), @@ -47,12 +44,38 @@ describe("code-block-yaml-tag", () => { expect(config.layers).toHaveLength(1); expect(config.layers[0]).toMatchObject({ prop: "title", - x1: 1, - y1: 1, + pos: "1,1-5,1", font: 12, }); }); + test("supports yaml role=tag info string (highlight-friendly)", () => { + const html = render( + [ + "```yaml role=tag", + "tag: md-deck", + "body: ./cards.csv", + "data-config:", + " grid: 5x5", + " layers:", + " - template: |", + " **{{name}}**", + " pos: 1,1-5,8", + "```", + ].join("\n"), + ); + expect(html).toContain(""); + const m = html.match(/data-config="([^"]*)"/); + const config = JSON.parse((m![1] || "").replace(/"/g, '"')); + expect(config.layers[0].template).toBe("**{{name}}**\n"); + }); + + test("does not swallow plain yaml code blocks", () => { + const token = ext.tokenizer("```yaml\nsize: 54x86\n```"); + expect(token).toBeUndefined(); + }); + test("handles missing tag and body", () => { const html = render("```yaml/tag\nclass: foo\n```"); expect(html).toContain(""); diff --git a/src/markdown/code-block-yaml-tag.ts b/src/markdown/code-block-yaml-tag.ts index fe2be91..403fc46 100644 --- a/src/markdown/code-block-yaml-tag.ts +++ b/src/markdown/code-block-yaml-tag.ts @@ -8,10 +8,15 @@ export default function markedCodeBlockYamlTag(): MarkedExtension { name: "code-block-yaml-tag", level: "block", start(src: string) { - return src.match(/^```yaml\/tag\s*\n/m)?.index; + return ( + src.match(/^```yaml\/tag\s*\n/m)?.index ?? + src.match(/^```yaml\s+role=tag(?:\s|\n)/m)?.index + ); }, tokenizer(src: string) { - const rule = /^```yaml\/tag\s*\n([\s\S]*?)\n```/; + // Both `yaml/tag` (legacy) and `yaml role=tag` (highlight-friendly) + // info strings identify a yaml-defined tag block. + const rule = /^```yaml(?:\/tag|\s+role=tag[^\n]*)\n([\s\S]*?)\n```/; const match = rule.exec(src); if (match) { const yamlContent = match[1]?.trim() || "";