diff --git a/src/App.tsx b/src/App.tsx index 799e08c..7ce3a79 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,11 +3,17 @@ import { useLocation } from "@solidjs/router"; // 导入组件以注册自定义元素 import "./components"; -import { Article, MobileSidebar, DesktopSidebar } from "./components"; +import { + Article, + MobileSidebar, + DesktopSidebar, + DocDialog, +} from "./components"; const App: Component = () => { const location = useLocation(); const [isSidebarOpen, setIsSidebarOpen] = createSignal(false); + const [isDocOpen, setIsDocOpen] = createSignal(false); const currentPath = createMemo(() => { // 根据路由加载对应的 markdown 文件 @@ -40,14 +46,23 @@ const App: Component = () => { ☰

TTRPG Tools

+
+
- {/* fill th rest of the space*/} -
-
-
-
-
+ {/* fill th rest of the space*/} +
+
+
+
+
+ setIsDocOpen(false)} /> ); }; diff --git a/src/components/DocDialog.tsx b/src/components/DocDialog.tsx new file mode 100644 index 0000000..c955c4b --- /dev/null +++ b/src/components/DocDialog.tsx @@ -0,0 +1,170 @@ +import { + Component, + createSignal, + For, + Show, + onCleanup, + onMount, +} from "solid-js"; +import { docEntries, type DocEntry } from "./doc-data"; + +export interface DocDialogProps { + isOpen: boolean; + onClose: () => void; +} + +const DocDialog: Component = (props) => { + const [selectedTag, setSelectedTag] = createSignal(docEntries[0]?.tag ?? ""); + + const selectedEntry = () => + docEntries.find((e) => e.tag === selectedTag()) ?? docEntries[0]; + + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === "Escape") props.onClose(); + }; + + onMount(() => { + document.addEventListener("keydown", handleKeyDown); + }); + + onCleanup(() => { + document.removeEventListener("keydown", handleKeyDown); + }); + + return ( + +
{ + if (e.target === e.currentTarget) props.onClose(); + }} + > +
+
+

指令组件文档

+ +
+ +
+ + +
+ + {(entry) => } + +
+
+
+
+
+ ); +}; + +/** Single entry documentation content */ +const DocContent: Component<{ entry: DocEntry }> = (props) => { + const e = props.entry; + + return ( +
+

+ {e.icon} {e.title} + + :{e.tag} + +

+ +

{e.description}

+ +
+

+ 基本语法 +

+
+          {e.syntax}
+        
+
+ + 0}> +
+

+ 属性 +

+ + + + + + + + + + + + {(prop) => ( + + + + + + + )} + + +
+ 属性 + + 类型 + + 默认值 + 说明
+ + {prop.name} + + + {prop.type} + + {prop.default ?? "—"} + {prop.desc}
+
+
+ +
+

示例

+
+ {e.examples} +
+
+ +
+

+ 使用场景 +

+

{e.usage}

+
+
+ ); +}; + +export default DocDialog; diff --git a/src/components/doc-data.ts b/src/components/doc-data.ts new file mode 100644 index 0000000..ec1d5f8 --- /dev/null +++ b/src/components/doc-data.ts @@ -0,0 +1,234 @@ +export interface DocEntry { + tag: string; + icon: string; + title: string; + description: string; + syntax: string; + props: { name: string; type: string; default?: string; desc: string }[]; + examples: string; + usage: string; +} + +export const docEntries: DocEntry[] = [ + { + tag: "md-dice", + icon: "🎲", + title: "骰子组件", + description: "点击文字执行掷骰,可用于属性检定、伤害投掷等场景。", + syntax: ':md-dice[2d6+d8]{key="attack"}', + props: [ + { name: "key", type: "string", desc: "URL 参数标识,结果记录到 ?dice-key=15" }, + ], + examples: `**攻击检定:** :md-dice[1d20+5]{key="attack"} + +**伤害掷骰:** :md-dice[2d6+3]{key="damage"} + +**优势检定:** :md-dice[2d20k1+5]{key="advantage"} + +点击掷骰文字执行投掷,再次点击重置为公式。`, + usage: "用于技能检定、攻击命中、伤害计算等需要随机数的场景。", + }, + { + tag: "md-table", + icon: "📊", + title: "表格组件", + description: "将 CSV 数据转换为可切换标签页的表格,支持随机抽取和变量引用。", + syntax: ':md-table[./data.csv]{roll=true remix=true}', + props: [ + { name: "roll", type: "boolean", desc: "显示随机切换按钮" }, + { name: "remix", type: "boolean", desc: "支持 {{prop}} 引用同行其他列" }, + ], + examples: `**基础表格:** +\`\`\`markdown +:md-table[./npcs.csv] +\`\`\` + +**带随机抽取:** +\`\`\`markdown +:md-table[./encounters.csv]{roll=true} +\`\`\` + +**随机 + 变量引用:** +\`\`\`markdown +:md-table[./quests.csv]{roll=true remix=true} +\`\`\` + +CSV 要求包含 label, body 列,可选 group 列分组。支持 YAML front matter 继承行属性。`, + usage: "用于 NPC 列表、遭遇表、宝物生成、随机事件等结构化数据。", + }, + { + tag: "md-link", + icon: "🔗", + title: "链接组件", + description: "点击链接在当前页面内展开显示目标文章内容,支持章节定位。", + syntax: ':md-link[./rules.md#combat]', + props: [], + examples: `**展开完整文档:** +:md-link[./rules.md] + +**展开特定章节:** +:md-link[./rules.md#combat] + +点击链接即可内联展开目标内容,再次点击折叠。`, + usage: "用于引用规则书章节、怪物数据、快速预览等场景。", + }, + { + tag: "md-pins", + icon: "📍", + title: "标记组件", + description: "在地图或图片上添加可编辑或固定的位置标记,支持字母或数字标签。", + syntax: ':md-pins[./images/map.png]{pins="A:30,40 B:10,30" fixed}', + props: [ + { name: "pins", type: "string", default: '""', desc: '标记列表,格式 "A:x,y B:x,y"' }, + { name: "fixed", type: "boolean", default: "false", desc: "固定模式(只读不可编辑)" }, + { name: "labelStart", type: "string", default: '"A"', desc: "标签起始值,支持字母或数字" }, + ], + examples: `**固定标记(只读):** +:md-pins[./images/battle-map.png]{pins="A:25,50 B:75,30" fixed} + +**可编辑标记:** +:md-pins[./images/blank-map.png] + +**数字标签:** +:md-pins[./images/dungeon.png]{labelStart="1"} + +非 fixed 模式下点击图片添加标记,点击标记删除。`, + usage: "用于战斗地图标注、地牢房间标记、任务地点指示等。", + }, + { + tag: "md-bg", + icon: "🖼️", + title: "背景组件", + description: "将图片设置为当前文章卡片的背景,支持多种适配方式。", + syntax: ':md-bg[./images/dungeon-bg.jpg]{fit="cover"}', + props: [ + { name: "fit", type: "cover | contain | fill | none | scale-down", default: "cover", desc: "背景适配方式" }, + ], + examples: `**设置背景图:** +:md-bg[./images/dungeon-bg.jpg]{fit="cover"} + +背景图自动覆盖整个卡片区域,配合文章内容营造沉浸氛围。`, + usage: "用于营造场景氛围,如地牢、森林、城镇等不同环境的背景。", + }, + { + tag: "md-font", + icon: "🔤", + title: "字体组件", + description: "设置整个文档的字体,支持 Google Fonts、emfont 和系统本地字体三种来源。", + syntax: ':md-font[Noto Sans SC]{source="google" weight="400"}', + props: [ + { name: "source", type: "google | emfont | local", default: "local", desc: "字体来源" }, + { name: "weight", type: "string", default: '"400"', desc: "字体粗细" }, + ], + examples: `**Google 字体:** +:md-font[Noto Sans SC]{source="google"} + +**Emfont 字体:** +:md-font[Source Han Serif]{source="emfont" weight="700"} + +**本地字体(默认):** +:md-font[STSong] + +字体将应用到整个文档卡片。`, + usage: "用于切换文档的显示字体,适配不同风格需求。", + }, + { + tag: "md-embed", + icon: "📄", + title: "嵌入组件", + description: "将另一个 Markdown 文件的内容内联嵌入到当前文档中。", + syntax: ':md-embed[./rules.md#combat]', + props: [], + examples: `**嵌入完整文档:** +:md-embed[./rules.md] + +**嵌入指定章节:** +:md-embed[./rules.md#combat] + +嵌入后内容直接在当前位置显示,包括其中的表格、骰子等组件也会正常渲染。`, + usage: "用于在文档中引用通用规则、重复使用的数据表格等。", + }, + { + tag: "md-deck", + icon: "🃏", + title: "卡牌组件", + description: "将 CSV 数据渲染为卡牌布局,支持自定义网格和图层的排版。", + syntax: ':md-deck[./cards.csv]{grid="5x8" layers="title:1,1-5,1f8 body:1,5-8,8f3"}', + props: [ + { name: "grid", type: "string", desc: "卡牌布局,格式 行x列" }, + { name: "layers", type: "string", desc: "图层定义,格式 字段:行,列-列,字号" }, + ], + examples: `**基础卡牌:** +:md-deck[./spells.csv]{grid="3x3"} + +**多层卡牌:** +:md-deck[./cards.csv]{grid="5x8" layers="title:1,1-5,1f8 body:1,5-8,8f3"} + +CSV 包含 label 和显示字段列,通过图层定义控制各字段的位置和大小。`, + usage: "用于法术卡、物品卡、NPC 卡等可打印的卡牌排版。", + }, + { + tag: "md-yarn-spinner", + icon: "🧶", + title: "叙事线组件", + description: "展示 Yarn Spinner 格式的分支叙事结构,支持对话选择和分支。", + syntax: ':md-yarn-spinner[./story.yarn]', + props: [], + examples: `**加载叙事文件:** +:md-yarn-spinner[./story.yarn] + +Yarn Spinner 是用于游戏对话系统的格式,支持选项、条件分支和变量。`, + usage: "用于互动故事、分支对话、冒险剧本等。", + }, + { + tag: "md-token", + icon: "🪙", + title: "代币组件", + description: "展示游戏代币或棋子的图片。", + syntax: ':md-token[./token.png]', + props: [], + examples: `**展示代币:** +:md-token[./goblin.png] + +代币图片自动渲染为圆形,适合战棋或卡牌中的棋子展示。`, + usage: "用于展示怪物代币、角色棋子、道具图标等。", + }, + { + tag: "md-token-viewer", + icon: "🎨", + title: "代币预览组件", + description: "使用 Three.js 在浏览器中 3D 渲染 3MF 格式的代币模型,支持旋转查看。", + syntax: ':md-token-viewer[./token.3mf]', + props: [], + examples: `**3D 预览模型:** +:md-token-viewer[./dragon.3mf] + +支持鼠标拖拽旋转和自动旋转,从多角度查看模型细节。`, + usage: "用于 3D 打印前的代币模型预览。", + }, + { + tag: "md-commander", + icon: "📋", + title: "命令追踪器", + description: "支持命令历史和游戏状态追踪,使用类 Emmet 语法创建追踪项。", + syntax: ':md-commander', + props: [], + examples: `**追踪 NPC 血量和防御:** +\`\`\` +track npc#john.dwarf.warrior[hp=4/4 ac=15 name="John"] +\`\`\` + +**语法规则:** +- \`#id\` 设置 ID +- \`.class\` 添加类别 +- \`[attr=value]\` 设置属性 + +**属性类型:** +| 格式 | 显示 | +|---|---| +| x/y | 进度条 | +| 整数 | 计数器 | +| 文本 | 文本字段 |`, + usage: "用于追踪战斗中的 NPC 血量、AC、状态等游戏信息。", + }, +]; diff --git a/src/components/index.ts b/src/components/index.ts index 765db39..1a7000b 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -18,6 +18,8 @@ export type { ArticleProps } from "./Article"; export { MobileSidebar, DesktopSidebar } from "./Sidebar"; export type { SidebarProps } from "./Sidebar"; export { FileTreeNode, HeadingNode } from "./FileTree"; +export { default as DocDialog } from "./DocDialog"; +export type { DocDialogProps } from "./DocDialog"; // 导出数据类型 export type { DiceProps } from "./md-dice";