Compare commits
3
Commits
f1ddc8fb8e
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80e2b5b209 | ||
|
|
5fc2bc5262 | ||
|
|
d75df5280f |
@@ -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
|
||||
```
|
||||
|
||||
+16
-7
@@ -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:
|
||||
@@ -447,7 +447,7 @@ data-config:
|
||||
```
|
||||
````
|
||||
|
||||
每个图层通过 `prop`(读取 CSV 字段)或 `template`(渲染带 `{{变量}}` 替换的 markdown 模板)定义,`pos` 为网格位置 `x1,y1-x2,y2`(1-based,与紧凑 layers 字符串同格式),支持 `font`、`orientation`、`align`。`back_layers` 用于背面图层。
|
||||
每个图层通过 `prop`(读取 CSV 字段)或 `template`(渲染带 `{{变量}}` 替换的 markdown 模板)定义,`pos` 为网格位置 `x1,y1-x2,y2`(1-based,与紧凑 layers 字符串同格式),支持 `font`、`orientation`、`align`(水平 `l`/`c`/`r`,可加垂直前缀 `t`/`b` 组成 `tl`/`tc`/`tr`/`bl`/`bc`/`br`,如 `align: tl` 表示左上对齐)。`back_layers` 用于背面图层。
|
||||
|
||||
### 🧶 叙事线组件 (md-yarn-spinner)
|
||||
|
||||
@@ -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: 标签内容
|
||||
```
|
||||
````
|
||||
|
||||
@@ -56,12 +56,16 @@ export function parseBlockAttrs(info: string): BlockAttrs {
|
||||
* Defaults:
|
||||
* - role is set, no explicit as → "none" (strip — it's metadata)
|
||||
* - role=spark-table → "md-table" (render as md-table directive)
|
||||
* - role=tag → "codeblock" (kept for the marked yaml-tag extension)
|
||||
* - no role, no as → "codeblock" (keep as visible code block)
|
||||
*/
|
||||
export function resolveBlockAs(role: string | undefined, as: string | undefined): string {
|
||||
if (as) return as;
|
||||
// spark-table blocks render as md-table by default
|
||||
if (role === "spark-table") return "md-table";
|
||||
// yaml-defined tag blocks must survive stripping so the
|
||||
// code-block-yaml-tag marked extension can render them
|
||||
if (role === "tag") return "codeblock";
|
||||
if (role) return "none";
|
||||
return "codeblock";
|
||||
}
|
||||
@@ -269,6 +269,10 @@ describe("resolveBlockAs", () => {
|
||||
expect(resolveBlockAs("spark-table", undefined)).toBe("md-table");
|
||||
});
|
||||
|
||||
test("defaults to codeblock for tag role", () => {
|
||||
expect(resolveBlockAs("tag", undefined)).toBe("codeblock");
|
||||
});
|
||||
|
||||
test("defaults to codeblock when no role and no as", () => {
|
||||
expect(resolveBlockAs(undefined, undefined)).toBe("codeblock");
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createMemo, For, Show } from "solid-js";
|
||||
import { createMemo, For, Show, type JSX } from "solid-js";
|
||||
import { parseMarkdown } from "../../markdown";
|
||||
import { getLayerStyle } from "./hooks/dimensions";
|
||||
import type { CardData, CardSide, LayerConfig } from "./types";
|
||||
import type { Align, CardData, CardSide, LayerConfig } from "./types";
|
||||
import { DeckStore } from "./hooks/deckStore";
|
||||
import { processVariables } from "../utils/csv-loader";
|
||||
import type { LayerInteractionHandlers } from "./hooks/useLayerInteraction";
|
||||
@@ -31,10 +31,21 @@ export function CardLayer(props: CardLayerProps) {
|
||||
) as string;
|
||||
}
|
||||
|
||||
const getAlignStyle = (align?: "l" | "c" | "r") => {
|
||||
if (align === "l") return "left";
|
||||
if (align === "r") return "right";
|
||||
return "center";
|
||||
const getAlignStyle = (align?: Align) => {
|
||||
const horizontal = align?.includes("l")
|
||||
? "left"
|
||||
: align?.includes("r")
|
||||
? "right"
|
||||
: "center";
|
||||
const vertical = align?.includes("t")
|
||||
? "flex-start"
|
||||
: align?.includes("b")
|
||||
? "flex-end"
|
||||
: "center";
|
||||
return {
|
||||
"text-align": horizontal,
|
||||
"justify-content": vertical,
|
||||
} as JSX.CSSProperties;
|
||||
};
|
||||
|
||||
const isLayerSelected = (layerIndex: number) =>
|
||||
@@ -87,7 +98,7 @@ export function CardLayer(props: CardLayerProps) {
|
||||
style={{
|
||||
...getLayerStyle(layer, dimensions()),
|
||||
"font-size": `${layer.fontSize || 3}mm`,
|
||||
"text-align": getAlignStyle(layer.align),
|
||||
...getAlignStyle(layer.align),
|
||||
}}
|
||||
innerHTML={renderLayerContent(
|
||||
layer.template ?? props.cardData[layer.prop ?? ""],
|
||||
|
||||
@@ -50,6 +50,25 @@ describe("layersToConfigs", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("parses combined vertical+horizontal align", () => {
|
||||
const layers = layersToConfigs([
|
||||
{ prop: "a", pos: "1,1-2,2", align: "tl" },
|
||||
{ prop: "b", pos: "1,1-2,2", align: "br" },
|
||||
{ prop: "c", pos: "1,1-2,2", align: "tc" },
|
||||
{ prop: "d", pos: "1,1-2,2", align: "bc" },
|
||||
]);
|
||||
expect(layers[0].align).toBe("tl");
|
||||
expect(layers[1].align).toBe("br");
|
||||
expect(layers[2].align).toBe("tc");
|
||||
expect(layers[3].align).toBe("bc");
|
||||
});
|
||||
|
||||
test("parses vertical+horizontal align in compact layers string", () => {
|
||||
const layers = layersToConfigs("title:1,1-4,1tl body:1,2-5,8bc");
|
||||
expect(layers[0].align).toBe("tl");
|
||||
expect(layers[1].align).toBe("bc");
|
||||
});
|
||||
|
||||
test("defaults placement when pos is missing or invalid", () => {
|
||||
const layers = layersToConfigs([{ prop: "x" }, { prop: "y", pos: "bogus" }]);
|
||||
expect(layers[0]).toMatchObject({ x1: 1, y1: 1, x2: 2, y2: 2 });
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { CardShape, LayerConfig } from "./types";
|
||||
import type { Align, CardShape, LayerConfig } from "./types";
|
||||
import { parseLayers } from "./hooks/layer-parser";
|
||||
|
||||
/**
|
||||
@@ -27,7 +27,7 @@ export interface DeckLayerYaml {
|
||||
font?: number;
|
||||
fontSize?: number;
|
||||
orientation?: "n" | "s" | "e" | "w";
|
||||
align?: "l" | "c" | "r";
|
||||
align?: Align;
|
||||
visible?: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { For, createSignal, onCleanup, onMount } from "solid-js";
|
||||
import { createSortable, useDragDropContext } from "@thisbeyond/solid-dnd";
|
||||
import type { DeckStore } from "../hooks/deckStore";
|
||||
import type { LayerConfig } from "../types";
|
||||
import type { Align, LayerConfig } from "../types";
|
||||
import alignLeftIcon from "./icons/align-left.png";
|
||||
import alignCenterIcon from "./icons/align-center.png";
|
||||
import alignRightIcon from "./icons/align-right.png";
|
||||
@@ -17,7 +17,7 @@ export interface LayerRowProps {
|
||||
setOpenDropdown: (val: string | null) => void;
|
||||
onUpdateOrientation: (o: "n" | "s" | "e" | "w") => void;
|
||||
onUpdateFontSize: (fs?: number) => void;
|
||||
onUpdateAlign: (a?: "l" | "c" | "r") => void;
|
||||
onUpdateAlign: (a?: Align) => void;
|
||||
onSelect: () => void;
|
||||
onRemove: () => void;
|
||||
}
|
||||
@@ -29,11 +29,17 @@ const ORIENTATIONS = [
|
||||
{ value: "w" as const, label: "← 西" },
|
||||
];
|
||||
|
||||
const ALIGNS = [
|
||||
{ value: "" as const, icon: alignCenterIcon },
|
||||
{ value: "l" as const, icon: alignLeftIcon },
|
||||
{ value: "c" as const, icon: alignCenterIcon },
|
||||
{ value: "r" as const, icon: alignRightIcon },
|
||||
const ALIGNS: { value: Align | ""; icon: string }[] = [
|
||||
{ value: "", icon: alignCenterIcon },
|
||||
{ value: "l", icon: alignLeftIcon },
|
||||
{ value: "c", icon: alignCenterIcon },
|
||||
{ value: "r", icon: alignRightIcon },
|
||||
{ value: "tl", icon: "↖" },
|
||||
{ value: "tc", icon: "↑" },
|
||||
{ value: "tr", icon: "↗" },
|
||||
{ value: "bl", icon: "↙" },
|
||||
{ value: "bc", icon: "↓" },
|
||||
{ value: "br", icon: "↘" },
|
||||
];
|
||||
|
||||
const FONT_PRESETS = [3, 5, 8, 12] as const;
|
||||
@@ -53,14 +59,28 @@ function orientChar(v: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function alignSrc(v: string) {
|
||||
function alignIcon(v: string): import("solid-js").JSX.Element {
|
||||
switch (v) {
|
||||
case "tl":
|
||||
return "↖";
|
||||
case "tc":
|
||||
return "↑";
|
||||
case "tr":
|
||||
return "↗";
|
||||
case "bl":
|
||||
return "↙";
|
||||
case "bc":
|
||||
return "↓";
|
||||
case "br":
|
||||
return "↘";
|
||||
case "l":
|
||||
return alignLeftIcon;
|
||||
return <img src={alignLeftIcon} alt="align" class="w-5 h-5 not-prose" />;
|
||||
case "r":
|
||||
return alignRightIcon;
|
||||
return <img src={alignRightIcon} alt="align" class="w-5 h-5 not-prose" />;
|
||||
default:
|
||||
return alignCenterIcon;
|
||||
return (
|
||||
<img src={alignCenterIcon} alt="align" class="w-5 h-5 not-prose" />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,13 +169,7 @@ export function LayerRow(props: LayerRowProps) {
|
||||
</DropdownButton>
|
||||
|
||||
<DropdownButton
|
||||
icon={
|
||||
<img
|
||||
src={alignSrc(props.layer.align || "")}
|
||||
alt="align"
|
||||
class="w-5 h-5 not-prose"
|
||||
/>
|
||||
}
|
||||
icon={alignIcon(props.layer.align || "")}
|
||||
visible={props.layer.visible}
|
||||
open={props.openDropdown === `align-${props.index}`}
|
||||
onToggle={() =>
|
||||
@@ -173,7 +187,13 @@ export function LayerRow(props: LayerRowProps) {
|
||||
onClick={() => props.onUpdateAlign(o.value || undefined)}
|
||||
class="flex items-center gap-2 w-full px-3 py-1.5 text-sm hover:bg-gray-100 cursor-pointer whitespace-nowrap"
|
||||
>
|
||||
<img src={o.icon} alt="" class="w-4 h-4 not-prose max-w-none" />
|
||||
{o.icon.endsWith(".png") ? (
|
||||
<img src={o.icon} alt="" class="w-4 h-4 not-prose max-w-none" />
|
||||
) : (
|
||||
<span class="w-4 h-4 flex items-center justify-center not-prose">
|
||||
{o.icon}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</For>
|
||||
|
||||
@@ -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<string, unknown> = {};
|
||||
@@ -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) => {
|
||||
|
||||
@@ -4,15 +4,16 @@ import { CSV } from "../../utils/csv-loader";
|
||||
/**
|
||||
* 解析 layers 字符串
|
||||
* 格式:body:1,7-5,8 title:1,1-4,1f6.6sl
|
||||
* f[fontSize] 表示字体大小(可选),方向字母(可选),对齐字母 l/c/r(可选)
|
||||
* f[fontSize] 表示字体大小(可选),方向字母(可选),
|
||||
* 对齐字母(可选):水平 l/c/r,可加垂直前缀 t/b 组成 tl/tr/bl/br
|
||||
*/
|
||||
export function parseLayers(layersStr: string): Layer[] {
|
||||
if (!layersStr) return [];
|
||||
|
||||
const layers: Layer[] = [];
|
||||
// 匹配:prop:x1,y1-x2,y2[ffontSize][direction][align]
|
||||
// 匹配:prop:x1,y1-x2,y2[ffontSize][direction][[t|b]align]
|
||||
const regex =
|
||||
/([^: ]+):(\d+),(\d+)-(\d+),(\d+)(?:f([\d.]+))?([nsew])?([lcr])?/g;
|
||||
/([^: ]+):(\d+),(\d+)-(\d+),(\d+)(?:f([\d.]+))?([nsew])?([tb])?([lcr])?/g;
|
||||
let match;
|
||||
|
||||
while ((match = regex.exec(layersStr)) !== null) {
|
||||
@@ -24,7 +25,7 @@ export function parseLayers(layersStr: string): Layer[] {
|
||||
y2: parseInt(match[5]),
|
||||
fontSize: match[6] ? parseFloat(match[6]) : undefined,
|
||||
orientation: match[7] as "n" | "s" | "e" | "w" | undefined,
|
||||
align: match[8] as "l" | "c" | "r" | undefined,
|
||||
align: [match[8], match[9]].filter(Boolean).join("") as Layer["align"],
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,22 @@ export interface CardData {
|
||||
|
||||
export type CardSide = "front" | "back";
|
||||
|
||||
/**
|
||||
* Layer alignment: horizontal (l/c/r) optionally combined with vertical
|
||||
* (t/b). "tl" = top-left, "br" = bottom-right, "tc" = top-center,
|
||||
* "bc" = bottom-center, etc. Plain "l"/"c"/"r" means vertically centered.
|
||||
*/
|
||||
export type Align =
|
||||
| "l"
|
||||
| "c"
|
||||
| "r"
|
||||
| "tl"
|
||||
| "tc"
|
||||
| "tr"
|
||||
| "bl"
|
||||
| "bc"
|
||||
| "br";
|
||||
|
||||
export type { CardShape } from "../../plotcutter/contour";
|
||||
|
||||
export interface Layer {
|
||||
@@ -17,7 +33,7 @@ export interface Layer {
|
||||
y2: number;
|
||||
orientation?: "n" | "s" | "e" | "w";
|
||||
fontSize?: number;
|
||||
align?: "l" | "c" | "r";
|
||||
align?: Align;
|
||||
}
|
||||
|
||||
export interface LayerConfig {
|
||||
@@ -32,7 +48,7 @@ export interface LayerConfig {
|
||||
y2: number;
|
||||
orientation?: "n" | "s" | "e" | "w";
|
||||
fontSize?: number;
|
||||
align?: "l" | "c" | "r";
|
||||
align?: Align;
|
||||
_key?: number;
|
||||
}
|
||||
|
||||
|
||||
@@ -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。
|
||||
|
||||
## 图层格式
|
||||
|
||||
|
||||
@@ -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("<md-deck data-config=");
|
||||
expect(html).toContain("./cards.csv</md-deck>");
|
||||
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("<tag-unknown class=\"foo\"></tag-unknown>");
|
||||
|
||||
@@ -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() || "";
|
||||
@@ -33,21 +38,13 @@ export default function markedCodeBlockYamlTag(): MarkedExtension {
|
||||
delete (rest as Record<string, unknown>).body;
|
||||
}
|
||||
|
||||
// A structured `data-config` object is serialized to JSON and
|
||||
// passed as a single attribute (e.g. md-deck layers/templates).
|
||||
let configAttr = "";
|
||||
if ("data-config" in rest) {
|
||||
const rawConfig = rest["data-config"];
|
||||
delete (rest as Record<string, unknown>)["data-config"];
|
||||
if (rawConfig !== undefined && rawConfig !== null) {
|
||||
const json = JSON.stringify(rawConfig).replace(/"/g, """);
|
||||
configAttr = ` data-config="${json}"`;
|
||||
}
|
||||
}
|
||||
|
||||
// `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 = String(value);
|
||||
const strValue = key.startsWith("data-")
|
||||
? JSON.stringify(value)
|
||||
: String(value);
|
||||
if (strValue.includes(" ") || strValue.includes('"')) {
|
||||
return `${key}="${strValue.replace(/"/g, """)}"`;
|
||||
}
|
||||
@@ -60,15 +57,13 @@ export default function markedCodeBlockYamlTag(): MarkedExtension {
|
||||
raw: match[0],
|
||||
tagName,
|
||||
props: propsStr,
|
||||
config: configAttr,
|
||||
content,
|
||||
};
|
||||
}
|
||||
},
|
||||
renderer(token: any) {
|
||||
const propsAttr = token.props ? ` ${token.props}` : "";
|
||||
const configAttr = token.config || "";
|
||||
return `<${token.tagName}${propsAttr}${configAttr}>${token.content || ""}</${token.tagName}>\n`;
|
||||
return `<${token.tagName}${propsAttr}>${token.content || ""}</${token.tagName}>\n`;
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user