docs: usage

This commit is contained in:
hypercross 2026-03-16 13:52:56 +08:00
parent 342cf97a50
commit 1f8f1b0c7d
4 changed files with 952 additions and 77 deletions

117
README.md
View File

@ -1,89 +1,52 @@
# TTRPG Tools
一个基于 `solid.js``rsbuild``ttrpg` 工具箱
一个基于 `solid.js``rsbuild`TTRPG 工具箱,支持 Markdown 解析、自定义组件和 CLI 工具
## 功能
- **CLI**: 提供一个 cli 工具,用于将目录内的各种 ttrpg 文档编译为 html。
- **Markdown**: 解析以各种格式编写的 ttrpg 内容,并支持扩展的语法。
- **TTRPG 组件**: 用于 ttrpg 内容的各种 UI 组件,且可以在 markdown 中通过扩展语法插入。
## CLI 命令
```bash
# 克隆仓库后npm install & npm link安装
# 在项目文件夹内运行
ttrpg serve [dir] -p 3000
```
## 组件语法
### csv
所有csv使用英文逗号分割。可使用空行。以#为注释。所有包含#或者英文逗号的字段,都应该使用双引号包裹。
### 骰子组件
```markdown
:dice[2d6+d8]
:dice[1d20+5]{key="attack"}
```
### 表格组件
```markdown
:table[./sparks.csv]
:table[./sparks.csv]{roll=true}
:table[./sparks.csv]{roll=true remix=true}
```
### 卡牌组件
假设准备有`./cards.csv`内容如下:
```csv
label,title,body
1,卡牌1,这张卡牌的**效果**的markdown文本
2,卡牌2,更多卡牌效果
```
```markdown
:md-deck[./cards.csv]{grid="5x8" layers="title:1,1-5,1f8 body:1,5-5,8f3"}
```
上述卡牌将生成5x8的卡牌牌面排版其中title层占第1行1-5列8mm字体。
## 开发
## 快速开始
```bash
# 安装依赖
npm install
# 开发模式
npm run dev
# 全局安装 CLI
npm link
# 构建
npm run build
# 预览
npm run preview
# 预览内容
ttrpg serve ./content
```
## 项目结构
## 文档导航
```
ttrpg-tools/
├── src/
│ ├── cli/ # CLI 工具源码
│ ├── components/ # TTRPG 组件
│ ├── markdown/ # Markdown 解析器
│ ├── App.tsx # 主应用组件
│ ├── main.tsx # 入口文件
│ └── styles.css # 样式文件
├── content/ # 示例内容
├── package.json
├── tsconfig.json
└── rsbuild.config.ts
```
| 文档 | 说明 |
|------|------|
| [📖 CLI 使用说明](./docs/cli.md) | CLI 安装、命令和用法 |
| [🛠️ 开发指南](./docs/development.md) | 项目结构、开发规范和构建 |
| [📝 Markdown 编写说明](./docs/markdown.md) | Markdown 语法和组件用法 |
## 功能概览
- **CLI 工具**: `serve` 预览模式 和 `compile` 编译模式
- **Markdown 解析**: 支持指令语法、YAML 标签、mermaid 图表
- **TTRPG 组件**: 骰子、表格、卡牌、标记、命令追踪器等
## 核心组件
| 组件 | 语法 | 说明 |
|------|------|------|
| 🎲 骰子 | `:dice[2d6+d8]` | 掷骰并记录结果 |
| 📊 表格 | `:table[./data.csv]` | 标签页式表格 |
| 🃏 卡牌 | `:md-deck[./cards.csv]` | 卡牌布局 |
| 📍 标记 | `:md-pin[A]{x=40 y=40}` | 图片标记 |
| 📋 追踪器 | `:md-commander` | 命令历史和状态追踪 |
## 技术栈
- **前端**: Solid.js 1.9+
- **构建**: Rsbuild
- **样式**: Tailwind CSS 4
- **Markdown**: marked + marked-directive
- **测试**: Jest
## 许可证
MIT

177
docs/cli.md Normal file
View File

@ -0,0 +1,177 @@
# CLI 使用说明
TTRPG Tools 提供一个 CLI 工具,用于将目录内的各种 TTRPG 文档编译为 HTML。
## 安装
```bash
# 克隆仓库后安装依赖
npm install
# 全局链接 CLI 工具
npm link
```
安装完成后,可在任意目录使用 `ttrpg` 命令。
## 命令
CLI 使用子命令组织:
### serve - 预览模式
运行一个 Web 服务器预览目录中的内容,并实时监听文件更新。
```bash
ttrpg serve [dir] -p 3000
```
**参数:**
| 参数 | 说明 | 默认值 |
|------|------|--------|
| `[dir]` | 要预览的目录 | `.` (当前目录) |
**选项:**
| 选项 | 说明 | 默认值 |
|------|------|--------|
| `-p, --port <port>` | 端口号 | `3000` |
**功能:**
- 扫描目录下的所有 `.md`、`.csv`、`.yarn` 文件
- 为每个文件创建路由
- 提供实时文件监听,修改后自动刷新
- 通过 `/__CONTENT_INDEX.json` 提供文件索引
**示例:**
```bash
# 预览当前目录
ttrpg serve
# 预览指定目录
ttrpg serve ./my-ttrpg-content
# 指定端口
ttrpg serve ./docs -p 8080
```
### compile - 编译模式
将目录中的内容输出为带 hash 路由、单个 HTML 入口的 Web 应用。
```bash
ttrpg compile [dir] -o ./dist/output
```
**参数:**
| 参数 | 说明 | 默认值 |
|------|------|--------|
| `[dir]` | 要编译的目录 | `.` (当前目录) |
**选项:**
| 选项 | 说明 | 默认值 |
|------|------|--------|
| `-o, --output <dir>` | 输出目录 | `./dist/output` |
**功能:**
- 扫描目录下的所有 `.md` 文件
- 解析 Markdown 并生成路由
- 打包为带 hash 路由的单个 HTML 入口
- 复制引用的资源文件图片、CSV 等)
**示例:**
```bash
# 编译当前目录
ttrpg compile
# 编译指定目录并输出到指定位置
ttrpg compile ./content -o ./build
# 编译并部署
ttrpg compile ./docs -o ./public && npm run preview
```
## 输入文件
CLI 会搜索目录下的以下文件:
- `.md` - Markdown 文档
- `.csv` - 表格数据
- `.yarn` - Yarn Spinner 叙事文件
### 文件组织建议
```
my-ttrpg-content/
├── index.md # 首页
├── rules/
│ ├── index.md # 规则首页
│ ├── combat.md # 战斗规则
│ └── magic.md # 魔法系统
├── characters/
│ ├── index.md
│ └── npc-list.csv # NPC 列表
└── assets/
├── images/ # 图片资源
└── icons/ # 图标资源
```
### 相对路径引用
若 Markdown 文件通过相对路径引用了其他文件如图片、CSVCLI 会在打包时自动处理这些引用:
```markdown
<!-- 引用同目录下的 CSV -->
:table[./sparks.csv]
<!-- 引用子目录的图片 -->
![地图](./maps/city-map.png)
<!-- 引用上级目录的文件 -->
:deck[../data/cards.csv]
```
## 开发服务器特性
### 实时索引
访问 `http://localhost:3000/__CONTENT_INDEX.json` 可获取当前内容目录的文件索引。
### 自动刷新
文件变化时,服务器会自动重新加载内容,无需手动刷新页面。
### SPA 路由
使用 hash 路由支持单页应用导航:
- `/content/rules/combat.md``#/content/rules/combat.md`
- 支持浏览器前进/后退
## 常见问题
### 端口被占用
```bash
# 使用其他端口
ttrpg serve -p 8080
```
### 编译输出为空
检查输入目录是否包含 `.md` 文件:
```bash
# 查看目录内容
ls -R ./content
# 重新编译
ttrpg compile ./content -o ./dist/output
```

274
docs/development.md Normal file
View File

@ -0,0 +1,274 @@
# 开发和项目说明
## 快速开始
```bash
# 安装依赖
npm install
# 开发模式Web
npm run dev
# 构建Web
npm run build
# 预览构建结果
npm run preview
# CLI 开发模式
npm run cli:dev
# CLI 构建
npm run cli:build
# 运行测试
npm test
```
## 项目结构
```
ttrpg-tools/
├── src/
│ ├── cli/ # CLI 工具源码
│ │ ├── commands/ # 命令实现 (serve, compile)
│ │ ├── index.ts # CLI 入口
│ │ └── types.ts # 类型定义
│ ├── components/ # TTRPG 组件
│ │ ├── md-dice.tsx # 骰子组件
│ │ ├── md-table.tsx # 表格组件
│ │ ├── md-deck/ # 卡牌组件
│ │ ├── md-pins.tsx # 标记组件
│ │ ├── md-commander/ # 命令追踪器
│ │ ├── md-yarn-spinner.tsx # 叙事线组件
│ │ ├── md-token.tsx # 代币组件
│ │ ├── Article.tsx # 文章组件
│ │ ├── Sidebar.tsx # 侧边栏
│ │ ├── FileTree.tsx # 文件树
│ │ └── utils/ # 工具函数
│ ├── markdown/ # Markdown 解析器
│ │ ├── index.ts # marked 配置
│ │ └── mermaid.ts # mermaid 支持
│ ├── data-loader/ # 数据加载器
│ ├── plotcutter/ # 剧情切割工具
│ ├── yarn-spinner/ # 叙事线解析
│ ├── App.tsx # 主应用
│ ├── main.tsx # 入口文件
│ ├── styles.css # 样式
│ ├── index.html # HTML 模板
│ └── global.d.ts # 类型声明
├── bin/
│ └── cli/ # CLI 二进制文件
├── content/ # 示例内容
├── docs/ # 文档
├── package.json
├── tsconfig.json
├── tsconfig.cli.json
├── rsbuild.config.ts
└── jest.config.js
```
## 技术栈
| 类别 | 技术 |
|------|------|
| 前端框架 | Solid.js 1.9+ |
| 构建工具 | Rsbuild |
| 样式 | Tailwind CSS 4 + @tailwindcss/typography |
| Markdown | marked + marked-directive + marked-alert + marked-gfm-heading-id |
| 图表 | mermaid |
| 3D | three.js |
| 测试 | Jest |
| CLI | commander + chokidar |
## 开发规范
### Solid.js 最佳实践
- **优先使用 `createEffect` 和 `createMemo`** 实现响应式
- 在 JSX 中直接引用 `props` 保持响应式
- 避免滥用 `onMount` + `createSignal`
```tsx
// ✅ 推荐
const doubled = createMemo(() => count() * 2);
createEffect(() => console.log(count()));
// ❌ 避免
onMount(() => {
const [value, setValue] = createSignal(0);
});
```
### 组件开发
- **不使用 Shadow DOM**:使用 `noShadowDOM()`
- **继承 Tailwind CSS 样式系统**
- 使用 `customElement` 注册自定义元素
```tsx
import { customElement, noShadowDOM } from "solid-element";
customElement("md-dice", { key: "" }, (props, { element }) => {
noShadowDOM();
// 组件逻辑
});
```
### 类型检查
开发完成后检查类型错误:
```bash
npx tsc --noEmit
```
### 代码风格
- 使用 TypeScript 严格模式
- 遵循 ESLint 配置(如有)
- 使用 2 空格缩进
## 添加新组件
### 1. 创建组件文件
```tsx
// src/components/md-new-component.tsx
import { customElement, noShadowDOM } from "solid-element";
import { createSignal } from "solid-js";
export interface NewComponentProps {
prop1?: string;
}
customElement("md-new-component", { prop1: "" }, (props, { element }) => {
noShadowDOM();
const [state, setState] = createSignal("");
return (
<div class="new-component">
{/* 组件内容 */}
</div>
);
});
```
### 2. 注册组件
```tsx
// src/components/index.ts
import './md-new-component';
```
### 3. 导出类型(可选)
```tsx
// src/components/index.ts
export type { NewComponentProps } from './md-new-component';
```
## 构建配置
### Rsbuild 配置
```ts
// rsbuild.config.ts
import { defineConfig } from '@rsbuild/core';
import { pluginBabel } from '@rsbuild/plugin-babel';
import { pluginSolid } from '@rsbuild/plugin-solid';
import tailwindcss from '@tailwindcss/postcss';
export default defineConfig({
plugins: [pluginBabel(), pluginSolid()],
tools: {
postcss: {
postcssOptions: {
plugins: [tailwindcss()],
},
},
},
html: {
template: './src/index.html',
},
});
```
### TypeScript 配置
- `tsconfig.json` - 主项目配置Web
- `tsconfig.cli.json` - CLI 工具配置Node.js
## 测试
```bash
# 运行所有测试
npm test
# 运行特定测试文件
npm test -- src/components/md-dice.test.ts
# 监听模式
npm test -- --watch
```
## 调试
### Web 开发
```bash
npm run dev
# 访问 http://localhost:3000
```
### CLI 调试
```bash
# 使用 ts-node 直接运行
npm run ttrpg serve ./content
# 或构建后运行
npm run cli:build
node dist/cli/index.js serve ./content
```
## 发布流程
1. 更新版本号
2. 运行测试和类型检查
3. 构建 Web 和 CLI
4. 发布到 npm
```bash
npm run build
npm run cli:build
npm test
npx tsc --noEmit
npm publish
```
## 常见问题
### Windows 换行符
开发环境主要在 Windows 上,注意使用 CRLF 换行符。
### 依赖安装失败
```bash
# 清理缓存
npm cache clean --force
rm -rf node_modules package-lock.json
npm install
```
### 构建错误
```bash
# 检查类型
npx tsc --noEmit
# 重新构建
npm run build
```

461
docs/markdown.md Normal file
View File

@ -0,0 +1,461 @@
# Markdown 编写说明
本文档介绍 TTRPG Tools 支持的 Markdown 扩展语法和组件用法。
## 基础语法
使用标准 Markdown 语法,支持以下扩展:
- [GFM](https://github.github.com/gfm/) - GitHub Flavored Markdown
- [marked-alert](https://github.com/Insidify/marked-alert) - 警告/提示块
- [marked-gfm-heading-id](https://github.com/markedjs/marked-gfm-heading-id) - 标题 ID
- [marked-directive](https://github.com/fengzilong/marked-directive) - 指令语法
## 指令语法
通过 `marked-directive` 支持自定义组件插入:
```markdown
:component-name[content]{key="value" another="test"}
```
### 语法说明
| 部分 | 说明 | 示例 |
|------|------|------|
| `:name` | 组件名称 | `:dice` |
| `[content]` | 内容参数 | `[2d6+d8]` |
| `{attrs}` | 属性对象 | `{key="attack"}` |
### 嵌套指令
```markdown
::: container
这里是容器内容
:dice[1d20]
:::
```
## 图标语法
使用简单的 `:icon-name` 语法插入图标:
```markdown
:attack :defense :potion :sword
```
图标会渲染为 `<icon class="icon-attack"></icon>` 形式。
### 配置图标前缀
可通过 `iconPath` 属性指定图标目录:
```markdown
<Article src="/content/page.md" iconPath="./assets/icons" />
```
或使用空字符串禁用图标前缀:
```markdown
<Article src="/content/page.md" iconPath="" />
```
## 组件库
### 🎲 骰子组件 (md-dice)
```markdown
:dice[2d6+d8]
:dice[1d20+5]{key="attack"}
```
**功能:**
- 点击骰子图标执行掷骰
- 点击文本重置为公式
- `key` 属性将结果记录到 URL 参数中 (`?dice-attack=15`)
**属性:**
| 属性 | 类型 | 说明 |
|------|------|------|
| `key` | string | 用于 URL 参数记录的标识 |
**示例:**
```markdown
攻击检定 :dice[1d20+5]{key="attack"}
伤害掷骰 :dice[2d6+3]{key="damage"}
```
### 🔗 链接组件 (md-link)
```markdown
:link[./other-page.md]
:link[./rules.md#combat-section]
```
**功能:**
- 点击链接在当前页面内展开显示目标文章内容
- 支持 `#section` 语法显示特定章节
- 支持相对路径引用
**属性:**
| 属性 | 类型 | 说明 |
|------|------|------|
| (无) | - | 内容即为链接目标路径 |
**示例:**
```markdown
查看完整规则 :link[./rules.md]
查看战斗章节 :link[./rules.md#combat]
```
### 🖼️ 背景组件 (md-bg)
```markdown
:bg[./images/background.jpg]
:bg[./images/pattern.png]{fit="contain"}
```
**功能:**
- 将图片设置为当前文章卡片的背景
- 自动覆盖整个卡片区域
**属性:**
| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `fit` | `cover` \| `contain` \| `fill` \| `none` \| `scale-down` | `cover` | 背景适配方式 |
**示例:**
```markdown
:bg[./images/dungeon-bg.jpg]{fit="cover"}
# 地牢探险
这里是地牢的描述内容...
```
### 🪙 代币组件 (md-token)
```markdown
:md-token[./token-image.png]
:md-token[./hero.png]{size=60 defaultThickness=2.5}
```
**功能:**
- 将 2D 图片转换为 3D 打印模型
- 自动进行图像矢量追踪分层
- 支持预览和导出 STL 文件
**属性:**
| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `size` | number | `50` | 模型整体尺寸 (mm) |
| `defaultThickness` | number | `2` | 默认图层厚度 (mm) |
**使用流程:**
1. 引用图片PNG/JPG 等)
2. 等待矢量追踪完成
3. 在设置面板调整图层厚度
4. 点击「生成 3D 模型」
5. 预览并下载 STL 文件
**示例:**
```markdown
<!-- 基础代币 -->
:md-token[./images/warrior-token.png]
<!-- 指定尺寸 -->
:md-token[./images/dragon.png]{size=75 defaultThickness=3}
```
### 🧶 叙事线组件 (md-yarn-spinner)
```markdown
:md-yarn-spinner[./story.yarn]{start="start"}
```
**功能:**
- 运行 Yarn Spinner 叙事脚本
- 显示对话历史和选项
- 支持命令和变量替换
**属性:**
| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `start` | string | `start` | 起始节点名称 |
**Yarn 文件格式:**
```yarn
title: start
tags:
colorID: 0
position: 0,0
---
NPC: 欢迎来到村庄!
玩家:谢谢![[继续]]
===
title: 继续
tags:
colorID: 0
position: 200,0
---
NPC: 有什么我可以帮你的吗?
-> 询问任务
最近有什么危险的任务吗?
-> 离开
再见!
[[结束]]
===
```
**示例:**
```markdown
:md-yarn-spinner[./dialogues/village-elder.yarn]
:md-yarn-spinner[./quest-branch.yarn]{start="intro"}
```
### 📋 命令追踪器 (md-commander)
```markdown
:md-commander
:md-commander[./commands.csv]{placeholder="输入命令..."}
```
**功能:**
- 命令历史视图
- 追踪器视图(角色状态、进度等)
- 支持 Emmet 简写语法
- 命令自动补全
**属性:**
| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `placeholder` | string | - | 输入框占位符 |
| `class` | string | - | 额外 CSS 类 |
| `height` | string | `400px` | 组件高度 |
| `commandTemplates` | string | - | CSV 模板文件路径 |
**追踪器命令语法:**
```
track npc#john.dwarf.warrior[hp=4/4 ac=15 name="John"]
```
**Emmet 简写语法:**
- `#id` - 设置 ID
- `.class` - 添加类别
- `[attr=value]` - 设置属性
**属性类型:**
| 类型 | 格式 | 显示 |
|------|------|------|
| `progress` | `x/y` | 进度条 (如 `hp=4/4`) |
| `count` | 整数 | 数字计数器 (如 `ac=15`) |
| `string` | 文本 | 文本字段 (如 `name="John"`) |
**内置命令:**
| 命令 | 说明 |
|------|------|
| `track` | 添加追踪项 |
| `remove` | 移除追踪项 |
| `update` | 更新属性 |
| `clear` | 清空追踪 |
**示例:**
```markdown
<!-- 基础追踪器 -->
:md-commander
<!-- 带占位符 -->
:md-commander{placeholder="输入 /help 查看命令"}
<!-- 从 CSV 加载命令模板 -->
:md-commander[./my-commands.csv]
<!-- 示例追踪命令 -->
track enemy#goblin.soldier[hp=7/7 ac=12]
track ally#cleric[hp=24/24 spells=4/4]
```
### 📁 文件树 (FileTree)
文件树组件自动在侧边栏显示,无需手动插入。
**功能:**
- 自动扫描内容目录
- 显示文件层级结构
- 支持文件夹展开/收起
- 显示当前文件的标题导航
**目录结构示例:**
```
content/
├── index.md # 首页
├── rules/
│ ├── index.md # 规则首页
│ ├── combat.md # 战斗规则
│ └── magic.md # 魔法系统
└── characters/
└── npc-list.md # NPC 列表
```
侧边栏会自动显示:
- 📁 rules/
- 📄 combat.md
- 📄 magic.md
## YAML 标签
使用 ```yaml/tag 代码块创建自定义标签:
````markdown
```yaml/tag
tag: tag-name
class: custom-class
id: my-id
body: 标签内容
```
````
渲染为:
```html
<tag-name id="my-id" class="custom-class">标签内容</tag-name>
```
## Mermaid 图表
支持 mermaid 流程图:
````markdown
```mermaid
graph TD
A[开始] --> B{选择}
B -->|选项 1| C[结果 1]
B -->|选项 2| D[结果 2]
```
````
支持的图表类型:
- flowchart - 流程图
- sequenceDiagram - 时序图
- classDiagram - 类图
- stateDiagram - 状态图
- erDiagram - ER 图
- pie - 饼图
## 警告/提示块
```markdown
> [!NOTE]
> 这是一条备注
> [!TIP]
> 这是一条提示
> [!IMPORTANT]
> 这是重要信息
> [!WARNING]
> 这是警告信息
> [!CAUTION]
> 这是危险警告
```
## 文件引用
### 相对路径
```markdown
<!-- 引用同目录文件 -->
:table[./data.csv]
<!-- 引用子目录文件 -->
:deck[./cards/deck.csv]
<!-- 引用上级目录文件 -->
:table[../shared/npcs.csv]
<!-- 引用图片 -->
![地图](./images/map.png)
```
### 路径解析规则
- 所有路径相对于当前 Markdown 文件
- 支持 `.``..` 导航
- 自动处理 URL 编码
## 样式定制
### 使用 Tailwind 类
组件支持 Tailwind CSS 类:
```markdown
:dice[1d20]{class="text-red-500"}
```
### 自定义 CSS
```css
/* 在 styles.css 中添加 */
.md-dice {
@apply text-blue-600 hover:text-blue-800;
}
```
## 最佳实践
### 内容组织
```markdown
# 章节标题
这里是章节内容。
## 子章节
### 规则说明
:dice[2d6] 掷骰决定结果。
:table[./options.csv]{roll=true}
> [!TIP]
> 这是一个有用的提示。
```
### 性能优化
- 大型 CSV 文件使用 `group` 列分组
- 图片使用适当分辨率
- 避免过多嵌套组件
### 可访问性
- 为图片添加 `alt` 文本
- 使用语义化标题层级
- 确保颜色对比度