diff --git a/.agents/skills/tttk-authoring/SKILL.md b/.agents/skills/tttk-authoring/SKILL.md index c2a7376..9661ada 100644 --- a/.agents/skills/tttk-authoring/SKILL.md +++ b/.agents/skills/tttk-authoring/SKILL.md @@ -1,6 +1,6 @@ --- name: tttk-authoring -description: Author and edit TTRPG game design documents for the tttk (Tabletop Toolkit) CLI. Use when writing or modifying markdown content that uses tttk custom elements (:md-dice, :md-table, :md-deck, :md-pins, :md-commander, etc.), custom fenced code blocks (role=spark-table / declare / file / tag), companion CSV data files, or when previewing content with `tttk serve` and validating it with `tttk check`. +description: Author and edit TTRPG game design documents for the tttk (Tabletop Toolkit) CLI. Use when writing or modifying markdown content that uses tttk custom elements (:md-dice, :md-table, :md-deck, :md-pins, etc.), custom fenced code blocks (role=spark-table / declare / file / tag), companion CSV data files, or when previewing content with `tttk serve` and validating it with `tttk check`. --- # Authoring tttk game design documents diff --git a/.agents/skills/tttk-authoring/reference.md b/.agents/skills/tttk-authoring/reference.md index db2946f..899bd76 100644 --- a/.agents/skills/tttk-authoring/reference.md +++ b/.agents/skills/tttk-authoring/reference.md @@ -54,7 +54,6 @@ Nest with more colons (`::::`, `:::::`). | `:md-border` | color or border image | `width`, `slice` | | `:md-font` | font family | `source`, `weight`, `color` | | `:md-pins` | image path | `pins`, `fixed`, `labelStart` | -| `:md-commander` | command template CSV | `placeholder`, `class`, `height` | | `:md-yarn-spinner` | Yarn Spinner file | `start` | ### `:md-dice` @@ -205,19 +204,6 @@ Numbered markers on an image. :md-pins[./images/dungeon.png]{labelStart="1"} ``` -### `:md-commander` - -Interactive command history / tracker panel. - -The body is a CSV of command templates (`command,parameter,label,description,insertedText`). - -- Body: command template CSV -- Attributes: `placeholder`, `class`, `height` - -```markdown -:md-commander[./commands.csv]{placeholder="输入命令…" height="400px"} -``` - ### `:md-yarn-spinner` Runs a Yarn Spinner dialogue file. diff --git a/README.md b/README.md index 20fc0bb..5087361 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,6 @@ npx tttk check ./content | 📊 表格 | `:md-table[./data.csv]` | 标签页式表格 | | 🃏 卡牌 | `:md-deck[./cards.csv]` | 卡牌布局(推荐 `yaml role=tag` 写法) | | 📍 标记 | `:md-pins[./map.png]{pins="A:25,50"}` | 图片标记 | -| 📋 追踪器 | `:md-commander[./commands.csv]` | 命令历史和状态追踪 | ## 技术栈 diff --git a/content/commander-test.md b/content/commander-test.md deleted file mode 100644 index 2545e81..0000000 --- a/content/commander-test.md +++ /dev/null @@ -1,15 +0,0 @@ -# Commander Test - -:md-commander[./commands.csv] - -## Lonelog - -`我` 走进酒吧。 - -`oracle` 酒吧里人多吗? - -`roll` d20 = 18 `prompt` 是的 - -`fact` 酒吧里有很多人。 - -:[blah] \ No newline at end of file diff --git a/content/commands.csv b/content/commands.csv deleted file mode 100644 index 6c1db79..0000000 --- a/content/commands.csv +++ /dev/null @@ -1,6 +0,0 @@ -command,parameter,label,description,insertedText -roll,emmet,2d6,2D6,2d6 -roll,emmet,d20,D20,d20 -track,emmet,monk,破戒佛爷,"破戒佛爷.bandit.monk[gd=4/4,str=14/14,cla=10,spi=8]" -track,emmet,bandit,龙虎寨山贼,"龙虎寨山贼.bandit[gd=2/2,str=10/10,cla=10,spi=10]" -track,emmet,khitan,契丹护卫,"契丹护卫.khitan.guard[gd=4/4,ap=2,str=14/14,cla=14,spi=14]" \ No newline at end of file diff --git a/docs/development.md b/docs/development.md index 80d26fc..c151846 100644 --- a/docs/development.md +++ b/docs/development.md @@ -46,7 +46,6 @@ tttk/ │ │ ├── md-table.tsx # 表格组件 │ │ ├── md-deck/ # 卡牌组件 │ │ ├── md-pins.tsx # 标记组件 -│ │ ├── md-commander/ # 命令追踪器 │ │ ├── md-yarn-spinner.tsx # 叙事线组件 │ │ ├── Article.tsx # 文章组件 diff --git a/docs/markdown.md b/docs/markdown.md index 50948c0..babddaf 100644 --- a/docs/markdown.md +++ b/docs/markdown.md @@ -461,36 +461,6 @@ data-config: :md-yarn-spinner[./story.yarn] ``` - - -### 📋 命令追踪器 (md-commander) - -支持命令历史和状态追踪。 - -```markdown -:md-commander -``` - -**追踪器命令:** - -``` -track npc#john.dwarf.warrior[hp=4/4 ac=15 name="John"] -``` - -**Emmet 简写语法:** - -- `#id` - 设置 ID -- `.class` - 添加类别 -- `[attr=value]` - 设置属性 - -**属性类型:** - -| 类型 | 格式 | 显示 | -|------|------|------| -| progress | `x/y` | 进度条 | -| count | 整数 | 数字计数器 | -| string | 文本 | 文本字段 | - ## YAML 标签 使用 ```yaml role=tag 代码块创建自定义标签(`yaml` 语言可被语法高亮): diff --git a/docs/mcp.md b/docs/mcp.md index 6346552..394adca 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -335,8 +335,6 @@ MCP 服务器提供以下 Resources,包含 TTRPG Tools 的文档和参考材 - `:md-table` - 表格组件 - `:md-deck` - 卡牌组件 - `:md-yarn-spinner` - 叙事线组件 - - - `:md-commander` - 命令追踪器 - YAML 标签 - Mermaid 图表 - 警告/提示块 diff --git a/docs/reference.md b/docs/reference.md index db2946f..899bd76 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -54,7 +54,6 @@ Nest with more colons (`::::`, `:::::`). | `:md-border` | color or border image | `width`, `slice` | | `:md-font` | font family | `source`, `weight`, `color` | | `:md-pins` | image path | `pins`, `fixed`, `labelStart` | -| `:md-commander` | command template CSV | `placeholder`, `class`, `height` | | `:md-yarn-spinner` | Yarn Spinner file | `start` | ### `:md-dice` @@ -205,19 +204,6 @@ Numbered markers on an image. :md-pins[./images/dungeon.png]{labelStart="1"} ``` -### `:md-commander` - -Interactive command history / tracker panel. - -The body is a CSV of command templates (`command,parameter,label,description,insertedText`). - -- Body: command template CSV -- Attributes: `placeholder`, `class`, `height` - -```markdown -:md-commander[./commands.csv]{placeholder="输入命令…" height="400px"} -``` - ### `:md-yarn-spinner` Runs a Yarn Spinner dialogue file. diff --git a/src/cli/validate/known.ts b/src/cli/validate/known.ts index 96b0b0e..b2b2e59 100644 --- a/src/cli/validate/known.ts +++ b/src/cli/validate/known.ts @@ -131,16 +131,6 @@ export const KNOWN_ELEMENTS: readonly ElementSpec[] = [ ':md-pins[./images/dungeon.png]{labelStart="1"}', ], }, - { - name: "md-commander", - body: "command template CSV", - attrs: ["placeholder", "class", "height"], - summary: "Interactive command history / tracker panel.", - details: - "The body is a CSV of command templates (`command,parameter,label,description,insertedText`).", - refKind: "csv", - examples: [':md-commander[./commands.csv]{placeholder="输入命令…" height="400px"}'], - }, { name: "md-yarn-spinner", body: "Yarn Spinner file", diff --git a/src/components/doc-data.ts b/src/components/doc-data.ts index 45d75e1..4b0fbe7 100644 --- a/src/components/doc-data.ts +++ b/src/components/doc-data.ts @@ -50,8 +50,6 @@ import mdEmbedRaw from "../doc-entries/md-embed.md"; import mdDeckRaw from "../doc-entries/md-deck.md"; import mdYarnRaw from "../doc-entries/md-yarn-spinner.md"; -import mdCommanderRaw from "../doc-entries/md-commander.md"; - const rawDocuments: string[] = [ mdDiceRaw, mdTableRaw, @@ -63,8 +61,6 @@ const rawDocuments: string[] = [ mdEmbedRaw, mdDeckRaw, mdYarnRaw, - - mdCommanderRaw, ]; let _entries: DocEntry[] | null = null; diff --git a/src/components/index.ts b/src/components/index.ts index 41d8395..2c70043 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -8,7 +8,6 @@ import "./md-border"; import "./md-font"; import "./md-embed"; import "./md-deck"; -import "./md-commander/index"; import "./md-yarn-spinner"; // 导出组件 @@ -33,29 +32,3 @@ export type { FontProps } from "./md-font"; export type { BorderProps } from "./md-border"; export type { EmbedProps } from "./md-embed"; export type { YarnSpinnerProps } from "./md-yarn-spinner"; - -// 导出 md-commander 相关 -export type { - MdCommanderProps, - MdCommanderCommand, - MdCommanderCommandMap, - MdCommanderParameter, - MdCommanderOption, - MdCommanderOptionType, - CommanderEntry, - CompletionItem, - TrackerItem, - TrackerAttribute, - TrackerAttributeType, - TrackerCommand, - TrackerViewMode, -} from "./md-commander/types"; -export { TabBar } from "./md-commander/TabBar"; -export { TrackerView } from "./md-commander/TrackerView"; -export { CommanderEntries } from "./md-commander/CommanderEntries"; -export { CommanderInput } from "./md-commander/CommanderInput"; -export { useCommander } from "./md-commander/hooks"; -export { - initializeCommands, - getCommands, -} from "./md-commander/stores/commandsStore"; diff --git a/src/components/md-commander/AttributeTooltip.tsx b/src/components/md-commander/AttributeTooltip.tsx deleted file mode 100644 index b2091e2..0000000 --- a/src/components/md-commander/AttributeTooltip.tsx +++ /dev/null @@ -1,229 +0,0 @@ -import { type Component, Show, For, createSignal, createEffect, onCleanup } from "solid-js"; -import type { TrackerAttribute } from "./types"; - -export interface AttributeTooltipProps { - position: { x: number; y: number }; - attributes: Record; - classes: string[]; - onUpdate: (attrName: string, attr: TrackerAttribute) => void; - onClassesChange: (classes: string[]) => void; - onClose: () => void; -} - -export const AttributeTooltip: Component = (props) => { - const [position, setPosition] = createSignal(props.position); - const [newClass, setNewClass] = createSignal(""); - - // 点击外部关闭 - createEffect(() => { - const handleClickOutside = (e: MouseEvent) => { - const target = e.target as HTMLElement; - if (target.closest('body') && !target.closest(".attribute-tooltip")) { - props.onClose(); - } - }; - document.addEventListener("click", handleClickOutside); - onCleanup(() => document.removeEventListener("click", handleClickOutside)); - }); - - // 处理属性更新 - const handleUpdate = (attrName: string, value: any) => { - const attr = props.attributes[attrName]; - props.onUpdate(attrName, { ...attr, value }); - }; - - // 处理滚轮增减(progress 和 count 类型) - const handleWheel = (e: WheelEvent, attrName: string, attr: TrackerAttribute) => { - e.preventDefault(); - e.stopPropagation(); - - if (attr.type === "count") { - const delta = e.deltaY > 0 ? -1 : 1; - const newValue = Math.max(0, (attr.value as number) + delta); - handleUpdate(attrName, newValue); - } else if (attr.type === "progress") { - const val = attr.value as { x: number; y: number }; - const delta = e.deltaY > 0 ? -1 : 1; - // 滚轮向上/下调整当前值 x - const newX = Math.max(0, Math.min(val.y, val.x + delta)); - handleUpdate(attrName, { x: newX, y: val.y }); - } - }; - - // 添加 class - const handleAddClass = () => { - const className = newClass().trim(); - if (className && !props.classes.includes(className)) { - props.onClassesChange([...props.classes, className]); - setNewClass(""); - } - }; - - // 移除 class - const handleRemoveClass = (e: MouseEvent, className: string) => { - e.preventDefault(); - e.stopPropagation(); - props.onClassesChange(props.classes.filter((c) => c !== className)); - }; - - // 渲染不同类型的属性编辑器 - const renderEditor = (attrName: string, attr: TrackerAttribute) => { - if (attr.type === "progress") { - const val = attr.value as { x: number; y: number }; - const percentage = val.y > 0 ? (val.x / val.y) * 100 : 0; - return ( -
- {/* 进度条 */} -
handleWheel(e, attrName, attr)} - > -
-
- {/* 输入框 */} -
- handleUpdate(attrName, { x: parseInt(e.target.value) || 0, y: val.y })} - onWheel={(e) => handleWheel(e, attrName, attr)} - class="w-14 px-1 py-0.5 border border-gray-300 rounded text-center text-sm" - min="0" - /> - / - handleUpdate(attrName, { x: val.x, y: parseInt(e.target.value) || 0 })} - class="w-14 px-1 py-0.5 border border-gray-300 rounded text-center text-sm" - min="0" - /> -
-
- ); - } - - if (attr.type === "count") { - const value = attr.value as number; - return ( -
handleWheel(e, attrName, attr)} - title="滚动鼠标滚轮增减" - > - - handleUpdate(attrName, parseInt(e.target.value) || 0)} - onWheel={(e) => handleWheel(e, attrName, attr)} - class="w-12 px-1 py-0.5 border border-gray-300 rounded text-center text-sm" - min="0" - /> - -
- ); - } - - if (attr.type === "string") { - return ( - handleUpdate(attrName, e.target.value)} - class="w-full px-2 py-1 border border-gray-300 rounded text-sm" - /> - ); - } - - return 未知类型; - }; - - return ( -
e.stopPropagation()} - > -
- 编辑属性 - -
-
- {/* Class 列表编辑器 */} -
- -
- - {(className) => ( - - .{className} - - - )} - -
-
- setNewClass((e.target as HTMLInputElement).value)} - onKeyDown={(e) => { - if (e.key === "Enter") { - e.preventDefault(); - e.stopPropagation(); - handleAddClass(); - } - }} - placeholder="添加 class..." - class="flex-1 px-2 py-1 border border-gray-300 rounded text-sm" - /> - -
-
- - {/* 属性编辑器 */} - - {([name, attr]) => ( -
- - {renderEditor(name, attr)} -
- )} -
-
-
- ); -}; diff --git a/src/components/md-commander/CommanderEntries.tsx b/src/components/md-commander/CommanderEntries.tsx deleted file mode 100644 index c45a5d7..0000000 --- a/src/components/md-commander/CommanderEntries.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { type Component, For, Show } from "solid-js"; -import type { CommanderEntry } from "./types"; -import { getResultClass } from "./hooks"; - -export interface CommanderEntriesProps { - entries: () => CommanderEntry[]; - onCommandClick?: (command: string) => void; - loading?: boolean; - error?: string; -} - -export const CommanderEntries: Component = (props) => { - let containerRef: HTMLDivElement | undefined; - - const handleCommandClick = (command: string) => { - if (props.onCommandClick) { - props.onCommandClick(command); - } - }; - - return ( -
- 0} - fallback={ -
暂无命令执行记录
- } - > - - {(entry) => ( -
-
- handleCommandClick(entry.command)} - title="点击复制到输入框" - > - {entry.command} - - - {entry.timestamp.toLocaleTimeString()} - -
-
- {!entry.result.isHtml && entry.result.message} -
-
- )} -
-
- } - > -
{props.error}
- - } - > -
-
-
- 加载命令模板中... -
-
- -
- ); -}; diff --git a/src/components/md-commander/CommanderInput.tsx b/src/components/md-commander/CommanderInput.tsx deleted file mode 100644 index 8659e64..0000000 --- a/src/components/md-commander/CommanderInput.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import {type Component, For, Show, createEffect, on} from "solid-js"; -import type { CompletionItem } from "./types"; - -export interface CommanderInputProps { - placeholder?: string; - inputValue: () => string; - onInput: (e: Event) => void; - onKeyDown: (e: KeyboardEvent) => void; - onFocus: () => void; - onBlur: () => void; - onSubmit: () => void; - showCompletions: () => boolean; - completions: () => CompletionItem[]; - selectedCompletion: () => number; - onSelectCompletion: (idx: number) => void; - onAcceptCompletion: () => void; -} - -export const CommanderInput: Component = (props) => { - let containerRef: HTMLDivElement | undefined; - - // 当选中项变化时自动滚动到可见区域 - createEffect( - on( - () => props.selectedCompletion(), - () => { - if (containerRef) { - const selectedItem = containerRef.querySelector('[data-selected="true"]'); - if (selectedItem) { - selectedItem.scrollIntoView({ block: "nearest" }); - } - } - }, - ), - ); - - const maxVisible = 3; - - return ( -
- ❯ - - - - {/* 自动补全下拉框 - 向上弹出 */} - 0}> -
- {(comp, idx) => ( -
{ - props.onSelectCompletion(idx()); - props.onAcceptCompletion(); - }} - > -
- - {comp.type} - - {comp.label} -
- - {comp.description} - -
- )}
-
-
-
- ); -}; diff --git a/src/components/md-commander/TabBar.tsx b/src/components/md-commander/TabBar.tsx deleted file mode 100644 index 451cc0a..0000000 --- a/src/components/md-commander/TabBar.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { type Component } from "solid-js"; -import type { TrackerViewMode } from "./types"; - -export interface TabBarProps { - mode: () => TrackerViewMode; - onModeChange: (mode: TrackerViewMode) => void; -} - -export const TabBar: Component = (props) => { - return ( -
- - -
- ); -}; diff --git a/src/components/md-commander/TrackerView.tsx b/src/components/md-commander/TrackerView.tsx deleted file mode 100644 index 5666c41..0000000 --- a/src/components/md-commander/TrackerView.tsx +++ /dev/null @@ -1,169 +0,0 @@ -import { type Component, For, Show, createSignal } from "solid-js"; -import type { TrackerItem, TrackerAttribute } from "./types"; -import { AttributeTooltip } from "./AttributeTooltip"; - -export interface TrackerViewProps { - items: () => TrackerItem[]; - onEditAttribute?: (index: number, attrName: string, attr: TrackerAttribute) => void; - onClassesChange?: (index: number, classes: string[]) => void; - onRemoveClass?: (index: number, className: string) => void; - onMoveUp?: (index: number) => void; - onMoveDown?: (index: number) => void; - onRemove?: (index: number) => void; -} - -export const TrackerView: Component = (props) => { - const [editingItem, setEditingItem] = createSignal<{ - index: number; - position: { x: number; y: number }; - } | null>(null); - - const formatAttributeValue = (attr: TrackerAttribute): string => { - if (attr.type === "progress") { - const val = attr.value as { x: number; y: number }; - return `${val.x}/${val.y}`; - } - if (attr.type === "count") { - return String(attr.value); - } - return String(attr.value); - }; - - const formatAttributeShort = (name: string, attr: TrackerAttribute): string => { - const val = formatAttributeValue(attr); - return `${name}=${val}`; - }; - - // 处理滚轮增减(progress 和 count 类型) - const handleWheel = (e: WheelEvent, index: number, attrName: string, attr: TrackerAttribute) => { - e.preventDefault(); - e.stopPropagation(); - - if (attr.type === "count") { - const delta = e.deltaY > 0 ? -1 : 1; - const newValue = Math.max(0, (attr.value as number) + delta); - props.onEditAttribute?.(index, attrName, { ...attr, value: newValue }); - } else if (attr.type === "progress") { - const val = attr.value as { x: number; y: number }; - const delta = e.deltaY > 0 ? -1 : 1; - const newX = Math.max(0, Math.min(val.y, val.x + delta)); - props.onEditAttribute?.(index, attrName, { ...attr, value: { x: newX, y: val.y } }); - } - }; - - const handleTagClick = (e: MouseEvent, index: number) => { - e.preventDefault(); - e.stopPropagation(); - const rect = (e.currentTarget as HTMLElement).getBoundingClientRect(); - setEditingItem({ - index, - position: { - x: rect.left, - y: rect.bottom + 5, - }, - }); - }; - - const handleUpdateAttribute = (attrName: string, attr: TrackerAttribute) => { - const data = editingItem(); - if (data) { - props.onEditAttribute?.(data.index, attrName, attr); - } - }; - - const handleClassesChange = (classes: string[]) => { - const data = editingItem(); - if (data) { - props.onClassesChange?.(data.index, classes); - } - }; - - return ( -
- 0} - fallback={ -
暂无追踪项目
- } - > -
- - {(item, index) => ( -
- {/* 第一行:tag#id.class + 操作按钮 */} -
-
- - - #{item.id} - - 0}> - .{item.classes.join(".")} - - {/* 属性简写显示 */} - 0}> - - - {([name, attr]) => ( - handleWheel(e, index(), name, attr)} - > - {formatAttributeShort(name, attr)}{" "} - - )} - - - -
-
- - - -
-
-
- )} -
-
-
- - {/* 属性编辑工具提示 */} - - setEditingItem(null)} - /> - -
- ); -}; diff --git a/src/components/md-commander/commands/clear.ts b/src/components/md-commander/commands/clear.ts deleted file mode 100644 index 2ceb2a1..0000000 --- a/src/components/md-commander/commands/clear.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { MdCommanderCommand } from "../types"; - -export const clearCommand: MdCommanderCommand = { - command: "clear", - description: "清空命令历史", - handler: () => ({ - message: "命令历史已清空", - type: "success", - }), -}; diff --git a/src/components/md-commander/commands/help.ts b/src/components/md-commander/commands/help.ts deleted file mode 100644 index 7daa66b..0000000 --- a/src/components/md-commander/commands/help.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { MdCommanderCommand, MdCommanderCommandMap } from "../types"; - -export function setupHelpCommand(commands: MdCommanderCommandMap): MdCommanderCommand { - return { - command: "help", - description: "显示帮助信息或特定命令的帮助", - parameters: [ - { - name: "cmd", - description: "要查询的命令名", - type: "enum", - values: Object.keys(commands).filter((k) => k !== "help"), - required: false, - }, - ], - handler: (args, cmds) => { - const cmdName = args.params.cmd; - if (cmdName && cmds?.[cmdName]) { - return { - message: `命令:${cmdName}\n描述:${cmds[cmdName]?.description || "无描述"}`, - type: "info", - }; - } - const cmdList = Object.keys(cmds || {}).filter((k) => k !== "help").join(", "); - return { - message: `可用命令:${cmdList}`, - type: "info", - }; - }, - }; -} diff --git a/src/components/md-commander/commands/index.ts b/src/components/md-commander/commands/index.ts deleted file mode 100644 index 0f9391c..0000000 --- a/src/components/md-commander/commands/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { setupHelpCommand } from "./help"; -export { clearCommand } from "./clear"; -export { rollCommand } from "./roll"; -export { trackCommand, untrackCommand, listTrackCommand } from "./tracker"; diff --git a/src/components/md-commander/commands/roll.ts b/src/components/md-commander/commands/roll.ts deleted file mode 100644 index 5f7afac..0000000 --- a/src/components/md-commander/commands/roll.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { MdCommanderCommand } from "../types"; -import { rollFormula } from "../../dice"; - -export const rollCommand: MdCommanderCommand = { - command: "roll", - description: "掷骰子 - 支持骰池、修饰符和组合", - parameters: [ - { - name: "formula", - description: "骰子公式,如 3d6+{d4,d8}kh2-5", - type: "string", - required: true, - }, - ], - handler: (args) => { - const formula = args.params.formula || "1d6"; - const result = rollFormula(formula); - return { - message: result.success ? result.result.detail : `错误:${result.error ?? formula}`, - isHtml: result.success, - type: result.success ? "success" : "error", - }; - }, -}; diff --git a/src/components/md-commander/commands/tracker.ts b/src/components/md-commander/commands/tracker.ts deleted file mode 100644 index 403935c..0000000 --- a/src/components/md-commander/commands/tracker.ts +++ /dev/null @@ -1,86 +0,0 @@ -import type { MdCommanderCommand } from "../types"; -import { parseEmmet } from "../utils"; -import { addTrackerItem, removeTrackerItem, getTrackerItems, findTrackerItem } from "../stores"; - -export const trackCommand: MdCommanderCommand = { - command: "track", - description: "添加一个新的追踪项目 - 支持 Emmet 语法:track npc#john.dwarf.warrior[hp=4/4 ac=15]", - parameters: [ - { - name: "emmet", - description: "Emmet 格式的追踪项目:tag#class1.class2[attr=value]", - type: "string", - required: true, - }, - ], - handler: (args, commands) => { - const emmet = args.params.emmet; - if (!emmet) { - return { message: "错误:缺少 Emmet 参数", type: "error" }; - } - - const parsed = parseEmmet(emmet); - - if (!parsed.tag) { - return { message: "错误:缺少 tag", type: "error" }; - } - - // 直接添加追踪项目 - addTrackerItem(parsed); - - const idStr = parsed.id ? ` #${parsed.id}` : ""; - const classStr = parsed.classes.length > 0 ? `.${parsed.classes.join(".")}` : ""; - const attrCount = Object.keys(parsed.attributes).length; - const attrStr = attrCount > 0 ? `[${attrCount} 个属性]` : ""; - - return { - message: `已添加追踪项目:${parsed.tag}${idStr}${classStr}${attrStr}`, - type: "success", - }; - }, -}; - -export const untrackCommand: MdCommanderCommand = { - command: "untrack", - description: "移除一个追踪项目 - 支持 Emmet 语法:untrack #g2 或 untrack goblin.warrior", - parameters: [ - { - name: "emmet", - description: "Emmet 格式:#id 或 tag.class", - type: "string", - required: true, - }, - ], - handler: (args) => { - const emmet = args.params.emmet; - if (!emmet) { - return { message: "错误:缺少 Emmet 参数", type: "error" }; - } - - const found = findTrackerItem(emmet); - if (!found) { - return { message: `未找到匹配的追踪项目:${emmet}`, type: "error" }; - } - - const success = removeTrackerItem(emmet); - if (!success) { - return { message: `移除失败:${emmet}`, type: "error" }; - } - - return { message: `已移除追踪项目:${found.tag}${found.id ? '#' + found.id : ''}`, type: "success" }; - }, -}; - -export const listTrackCommand: MdCommanderCommand = { - command: "list", - description: "列出所有追踪项目", - handler: (args, commands) => { - const items = getTrackerItems(); - if (items.length === 0) { - return { message: "暂无追踪项目", type: "info" }; - } - - const list = items.map((i) => `${i.tag}${i.id ? "#" + i.id : ""}${i.classes.length > 0 ? "." + i.classes.join(".") : ""}`).join("\n"); - return { message: `追踪项目:\n${list}`, type: "info" }; - }, -}; diff --git a/src/components/md-commander/hooks/completions.ts b/src/components/md-commander/hooks/completions.ts deleted file mode 100644 index 899cbbf..0000000 --- a/src/components/md-commander/hooks/completions.ts +++ /dev/null @@ -1,182 +0,0 @@ -import type { MdCommanderCommandMap, CompletionItem } from "../types"; - -export interface ParsedCommand { - command?: string; - params: Record; - options: Record; - incompleteParam?: { index: number; value: string }; -} - -/** - * 解析命令行输入 - */ -export function parseInput(input: string, commands?: MdCommanderCommandMap): ParsedCommand { - const result: ParsedCommand = { params: {}, options: {} }; - const trimmed = input.trim(); - if (!trimmed) return result; - - const parts = trimmed.split(/\s+/); - let i = 0; - - // 获取命令 - if (parts[0] && !parts[0].startsWith("-")) { - result.command = parts[0]; - i = 1; - } - - // 获取命令的参数定义 - const cmd = result.command ? commands?.[result.command] : undefined; - const paramDefs = cmd?.parameters || []; - let paramIndex = 0; - - // 解析参数和选项 - while (i < parts.length) { - const part = parts[i]; - - if (part.startsWith("--")) { - const eqIndex = part.indexOf("="); - if (eqIndex !== -1) { - result.options[part.slice(2, eqIndex)] = part.slice(eqIndex + 1); - } else { - const key = part.slice(2); - if (i + 1 < parts.length && !parts[i + 1].startsWith("-")) { - result.options[key] = parts[i + 1]; - i++; - } - } - } else if (part.startsWith("-") && part.length === 2) { - const key = part.slice(1); - if (i + 1 < parts.length && !parts[i + 1].startsWith("-")) { - result.options[key] = parts[i + 1]; - i++; - } - } else if (paramIndex < paramDefs.length) { - result.params[paramDefs[paramIndex].name] = part; - paramIndex++; - } - i++; - } - - // 检查是否有未完成的位置参数 - if (paramIndex < paramDefs.length) { - const lastPart = parts[parts.length - 1]; - if (lastPart && !lastPart.startsWith("-") && parts.length > 1) { - result.incompleteParam = { index: paramIndex, value: lastPart }; - } - } - - return result; -} - -/** - * 获取自动补全建议 - */ -export function getCompletions(input: string, commands: MdCommanderCommandMap): CompletionItem[] { - const trimmed = input.trim(); - - // 空输入时返回所有命令 - if (!trimmed || /^\s*$/.test(trimmed)) { - return Object.values(commands).map((cmd) => ({ - label: cmd.command, - type: "command", - description: cmd.description, - insertText: cmd.command, - })); - } - - const parsed = parseInput(trimmed, commands); - - // 命令补全 - if (!parsed.command || !commands[parsed.command]) { - return Object.values(commands) - .filter((cmd) => cmd.command.startsWith(parsed.command || "")) - .map((cmd) => ({ - label: cmd.command, - type: "command", - description: cmd.description, - insertText: cmd.command, - })); - } - - const cmd = commands[parsed.command]; - if (!cmd) return []; - - const paramDefs = cmd.parameters || []; - const usedParams = Object.keys(parsed.params); - - // 判断是否正在输入最后一个参数 - const isTypingLastParam = paramDefs.length === usedParams.length && - trimmed.length > 0 && - !trimmed.endsWith(" ") && - !trimmed.split(/\s+/)[trimmed.split(/\s+/).length - 1].startsWith("-"); - - // 参数补全 - if (paramDefs.length > usedParams.length || parsed.incompleteParam || isTypingLastParam) { - let paramIndex = parsed.incompleteParam?.index ?? (isTypingLastParam ? paramDefs.length - 1 : usedParams.length); - const paramDef = paramDefs[paramIndex]; - if (!paramDef) return []; - - let currentValue = parsed.incompleteParam?.value || ""; - if (isTypingLastParam && !parsed.incompleteParam) { - const parts = trimmed.split(/\s+/); - currentValue = parts[parts.length - 1] || ""; - } - - // 模板补全 - if (paramDef.templates) { - return paramDef.templates - .filter((t) => t.label.toLowerCase().includes(currentValue.toLowerCase())) - .map((t) => ({ - label: t.label, - type: "value", - description: t.description, - insertText: t.insertText, - })); - } - - // 枚举补全 - if (paramDef.type === "enum" && paramDef.values) { - return paramDef.values - .filter((v) => v.startsWith(currentValue)) - .map((v) => ({ - label: v, - type: "value", - description: paramDef.description, - insertText: v, - })); - } - - // 其他类型显示提示 - return [{ - label: `<${paramDef.name}>`, - type: "value", - description: `${paramDef.type}${paramDef.required !== false ? " (必填)" : ""}: ${paramDef.description || ""}`, - insertText: "", - }]; - } - - // 选项补全 - if (!cmd.options) return []; - - const usedOptions = Object.keys(parsed.options); - return Object.values(cmd.options) - .filter((opt) => !usedOptions.includes(opt.option)) - .map((opt) => ({ - label: `--${opt.option}`, - type: "option", - description: opt.description, - insertText: `--${opt.option}=${opt.type === "boolean" ? "" : ""}`, - })); -} - -/** - * 根据结果类型获取 CSS 类名 - */ -export function getResultClass(type?: "success" | "error" | "warning" | "info"): string { - switch (type) { - case "success": return "text-green-600"; - case "error": return "text-red-600"; - case "warning": return "text-yellow-600"; - default: return "text-blue-600"; - } -} diff --git a/src/components/md-commander/hooks/index.ts b/src/components/md-commander/hooks/index.ts deleted file mode 100644 index 8ec8352..0000000 --- a/src/components/md-commander/hooks/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export { useCommander } from "./useCommander"; -export type { UseCommanderReturn } from "./useCommander"; -export { rollFormula } from "../../dice"; -export type { DiceRollerResult } from "../../dice"; -export { parseInput, getCompletions, getResultClass } from "./completions"; -export type { ParsedCommand } from "./completions"; diff --git a/src/components/md-commander/hooks/useCommander.ts b/src/components/md-commander/hooks/useCommander.ts deleted file mode 100644 index e4f54ac..0000000 --- a/src/components/md-commander/hooks/useCommander.ts +++ /dev/null @@ -1,280 +0,0 @@ -import { createSignal } from "solid-js"; -import type { - CommanderEntry, - CompletionItem, - TrackerItem, - TrackerAttribute, - TrackerViewMode, - MdCommanderCommandMap, - TrackerCommand, -} from "../types"; -import { parseInput, getCompletions } from "./completions"; -import { - addTrackerItem, - getTrackerHistory, - getTrackerItems, - moveTrackerItem, - removeTrackerClass, - removeTrackerItem, - updateTrackerAttribute, - updateTrackerClasses, -} from "../stores"; -import { addEntry, getEntries, clearEntries } from "../stores/entriesStore"; - -// ==================== Commander Hook ==================== - -export interface UseCommanderReturn { - inputValue: () => string; - entries: () => CommanderEntry[]; - showCompletions: () => boolean; - completions: () => CompletionItem[]; - selectedCompletion: () => number; - isFocused: () => boolean; - setInputValue: (v: string) => void; - setShowCompletions: (v: boolean) => void; - setSelectedCompletion: (v: number | ((prev: number) => number)) => void; - setIsFocused: (v: boolean) => void; - handleCommand: () => void; - updateCompletions: () => void; - acceptCompletion: () => void; - commands: () => MdCommanderCommandMap; - setCommands: (cmds: MdCommanderCommandMap) => void; - historyIndex: () => number; - setHistoryIndex: (v: number) => void; - commandHistory: () => string[]; - navigateHistory: (direction: "up" | "down") => void; - - // Tracker 相关 - viewMode: () => TrackerViewMode; - setViewMode: (mode: TrackerViewMode) => void; - trackerItems: () => TrackerItem[]; - trackerHistory: () => TrackerCommand[]; - addTrackerItem: (item: Omit) => TrackerItem; - removeTrackerItem: (emmet: string) => boolean; - removeTrackerItemByIndex: (index: number) => void; - updateTrackerAttribute: (emmet: string, attrName: string, attr: TrackerAttribute) => boolean; - updateTrackerAttributeByIndex: (index: number, attrName: string, attr: TrackerAttribute) => void; - updateTrackerClassesByIndex: (index: number, classes: string[]) => void; - moveTrackerItem: (emmet: string, direction: "up" | "down") => boolean; - moveTrackerItemByIndex: (index: number, direction: "up" | "down") => void; - removeTrackerItemClass: (emmet: string, className: string) => boolean; - removeTrackerItemClassByIndex: (index: number, className: string) => void; -} - -export function useCommander(initialCommands?: MdCommanderCommandMap): UseCommanderReturn { - const [inputValue, setInputValue] = createSignal(""); - const [showCompletions, setShowCompletions] = createSignal(false); - const [completions, setCompletions] = createSignal([]); - const [selectedCompletion, setSelectedCompletionState] = createSignal(0); - const [isFocused, setIsFocused] = createSignal(false); - const [commandHistory, setCommandHistory] = createSignal([]); - const [historyIndex, setHistoryIndex] = createSignal(-1); - const [viewMode, setViewMode] = createSignal("history"); - const [commands, setCommands] = createSignal(initialCommands || {}); - - // 从 store 获取 entries - const entries = getEntries; - - // ==================== 命令执行 ==================== - - const handleCommand = () => { - const input = inputValue().trim(); - if (!input) return; - - const cmds = commands(); - const parsed = parseInput(input, cmds); - const commandName = parsed.command; - const cmd = cmds[commandName!]; - - let result: { message: string; type?: "success" | "error" | "warning" | "info"; isHtml?: boolean }; - - if (!cmd) { - result = { message: `未知命令:${commandName}`, type: "error" }; - } else if (cmd.handler) { - try { - result = cmd.handler({ params: parsed.params, options: parsed.options }, cmds); - } catch (e) { - result = { - message: `执行错误:${e instanceof Error ? e.message : String(e)}`, - type: "error", - }; - } - } else { - result = { message: `命令 ${commandName} 已执行(无处理器)`, type: "info" }; - } - - const newEntry: CommanderEntry = { - id: Date.now().toString() + Math.random().toString(36).slice(2), - command: input, - args: parsed.options, - result, - timestamp: new Date(), - }; - - // 使用 store 添加记录 - addEntry(newEntry); - setCommandHistory((prev) => [...prev, input]); - setHistoryIndex(-1); - setInputValue(""); - setShowCompletions(false); - - if (commandName === "clear") { - clearEntries(); - } - }; - - // ==================== 自动补全 ==================== - - const updateCompletions = () => { - const comps = getCompletions(inputValue(), commands()); - setCompletions(comps); - setShowCompletions(comps.length > 0 && isFocused()); - setSelectedCompletionState(0); - }; - - const setSelectedCompletion = (v: number | ((prev: number) => number)) => { - const maxIdx = completions().length - 1; - if (typeof v === "function") { - setSelectedCompletionState((prev) => { - const next = v(prev); - return Math.max(0, Math.min(next, maxIdx)); - }); - } else { - setSelectedCompletionState(Math.max(0, Math.min(v, maxIdx))); - } - }; - - const acceptCompletion = () => { - const idx = selectedCompletion(); - const comps = completions(); - const comp = comps[Math.min(idx, comps.length - 1)]; - if (!comp) return; - - const input = inputValue(); - const parsed = parseInput(input, commands()); - let newValue: string; - - if (comp.type === "command") { - newValue = comp.insertText + " "; - } else if (comp.type === "option") { - const existingOptions = Object.entries(parsed.options) - .map(([k, v]) => `--${k}=${v}`) - .join(" "); - newValue = `${parsed.command || ""} ${existingOptions}${existingOptions ? " " : ""}${comp.insertText}`; - } else if (comp.type === "value") { - const cmd = parsed.command ? commands()[parsed.command] : null; - const paramDefs = cmd?.parameters || []; - const usedParams = Object.keys(parsed.params); - const isTypingLastParam = paramDefs.length === usedParams.length && !input.endsWith(" "); - - if (isTypingLastParam) { - const parts = input.trim().split(/\s+/); - parts.pop(); - newValue = `${parsed.command || ""}${parts.length > 1 ? " " + parts.slice(1).join(" ") : ""} ${comp.insertText}`; - } else if (paramDefs.length > usedParams.length) { - const existingParams = Object.values(parsed.params).join(" "); - newValue = `${parsed.command || ""} ${existingParams}${existingParams ? " " : ""}${comp.insertText}`; - } else { - newValue = input; - } - } else { - newValue = input; - } - - setInputValue(newValue.trim()); - setShowCompletions(false); - }; - - // ==================== 命令历史 ==================== - - const navigateHistory = (direction: "up" | "down") => { - const history = commandHistory(); - if (history.length === 0) return; - - let newIndex = historyIndex(); - if (direction === "up") { - if (newIndex < history.length - 1) newIndex++; - } else { - if (newIndex > 0) { - newIndex--; - } else { - setInputValue(""); - setHistoryIndex(-1); - return; - } - } - - setHistoryIndex(newIndex); - setInputValue(history[history.length - 1 - newIndex]); - }; - - // ==================== Tracker 操作 ==================== - - const getEmmetFromIndex = (index: number): string | null => { - const items = getTrackerItems(); - if (index < 0 || index >= items.length) return null; - const item = items[index]; - return `${item.tag}${item.id ? "#" + item.id : ""}${item.classes.length > 0 ? "." + item.classes.join(".") : ""}`; - }; - - const removeTrackerItemByIndex = (index: number) => { - const emmet = getEmmetFromIndex(index); - if (emmet) removeTrackerItem(emmet); - }; - - const updateTrackerAttributeByIndex = (index: number, attrName: string, attr: TrackerAttribute) => { - const emmet = getEmmetFromIndex(index); - if (emmet) updateTrackerAttribute(emmet, attrName, attr); - }; - - const updateTrackerClassesByIndex = (index: number, classes: string[]) => { - const emmet = getEmmetFromIndex(index); - if (emmet) updateTrackerClasses(emmet, classes); - }; - - const moveTrackerItemByIndex = (index: number, direction: "up" | "down") => { - const emmet = getEmmetFromIndex(index); - if (emmet) moveTrackerItem(emmet, direction); - }; - - const removeTrackerItemClassByIndex = (index: number, className: string) => { - const emmet = getEmmetFromIndex(index); - if (emmet) removeTrackerClass(emmet, className); - }; - - return { - inputValue, - entries, - showCompletions, - completions, - selectedCompletion, - isFocused, - setInputValue, - setShowCompletions, - setSelectedCompletion, - setIsFocused, - handleCommand, - updateCompletions, - acceptCompletion, - commands, - setCommands, - historyIndex, - setHistoryIndex, - commandHistory, - navigateHistory, - viewMode, - setViewMode, - trackerItems: getTrackerItems, - trackerHistory: getTrackerHistory, - addTrackerItem, - removeTrackerItem, - removeTrackerItemByIndex, - updateTrackerAttribute, - updateTrackerAttributeByIndex, - updateTrackerClassesByIndex, - moveTrackerItem, - moveTrackerItemByIndex, - removeTrackerItemClass: removeTrackerClass, - removeTrackerItemClassByIndex, - }; -} diff --git a/src/components/md-commander/index.tsx b/src/components/md-commander/index.tsx deleted file mode 100644 index 9a70789..0000000 --- a/src/components/md-commander/index.tsx +++ /dev/null @@ -1,161 +0,0 @@ -import { customElement, noShadowDOM } from "solid-element"; -import { onMount, onCleanup, createResource } from "solid-js"; -import { useCommander } from "./hooks"; -import { CommanderInput } from "./CommanderInput"; -import { CommanderEntries } from "./CommanderEntries"; -import { TrackerView } from "./TrackerView"; -import { TabBar } from "./TabBar"; -import type { MdCommanderProps } from "./types"; -import { loadElementSrc } from "../utils/path"; -import { initializeCommands, loadCommandTemplatesFromCSV, getCommands, getCommandsLoading, getCommandsError } from "./stores"; - -customElement( - "md-commander", - { placeholder: "", class: "", height: "", commandTemplates: "" }, - (props, { element }) => { - noShadowDOM(); - const { articlePath, rawSrc } = loadElementSrc(element); - - // 初始化命令 - const commands = initializeCommands(props.commands); - const commander = useCommander(commands); - - // 使用 createResource 加载 CSV 模板 - const [templateData] = createResource( - () => (rawSrc ? { path: rawSrc, articlePath } : null), - async (paths) => { - await loadCommandTemplatesFromCSV(paths.path, paths.articlePath); - return getCommands(); - } - ); - - // 当模板加载完成后更新 commander 中的命令 - createResource( - () => templateData(), - (loadedCommands) => { - if (loadedCommands) { - commander.setCommands(loadedCommands); - } - } - ); - - const handleKeyDown = (e: KeyboardEvent) => { - if (commander.showCompletions() && commander.completions().length > 0) { - if (e.key === "ArrowDown") { - e.preventDefault(); - commander.setSelectedCompletion( - (prev) => (prev + 1) % commander.completions().length, - ); - return; - } - if (e.key === "ArrowUp") { - e.preventDefault(); - commander.setSelectedCompletion( - (prev) => (prev - 1 + commander.completions().length) % commander.completions().length, - ); - return; - } - if (e.key === "Tab") { - e.preventDefault(); - commander.acceptCompletion(); - return; - } - if (e.key === "Escape") { - commander.setShowCompletions(false); - return; - } - } - - if (e.key === "ArrowUp" && !commander.showCompletions()) { - e.preventDefault(); - commander.navigateHistory("up"); - return; - } - if (e.key === "ArrowDown" && !commander.showCompletions()) { - e.preventDefault(); - commander.navigateHistory("down"); - return; - } - - if (e.key === "Enter" && !e.shiftKey) { - e.preventDefault(); - commander.handleCommand(); - } - }; - - const heightStyle = () => props.height || "400px"; - - onMount(() => { - const handleClickOutside = (e: MouseEvent) => { - if (!element?.contains(e.target as Node)) { - commander.setShowCompletions(false); - } - }; - document.addEventListener("click", handleClickOutside); - onCleanup(() => document.removeEventListener("click", handleClickOutside)); - }); - - return ( -
- - -
- {/* 历史视图 */} -
- commander.setInputValue(cmd)} - /> -
- - {/* 追踪视图 */} -
- - commander.updateTrackerAttributeByIndex(index, attrName, attr) - } - onClassesChange={(index, classes) => - commander.updateTrackerClassesByIndex(index, classes) - } - onRemoveClass={(index, className) => - commander.removeTrackerItemClassByIndex(index, className) - } - onMoveUp={(index) => commander.moveTrackerItemByIndex(index, "up")} - onMoveDown={(index) => commander.moveTrackerItemByIndex(index, "down")} - onRemove={(index) => commander.removeTrackerItemByIndex(index)} - /> -
-
- -
- { - commander.setInputValue((e.target as HTMLInputElement).value); - commander.updateCompletions(); - }} - onKeyDown={handleKeyDown} - onFocus={() => { - commander.setIsFocused(true); - commander.updateCompletions(); - }} - onBlur={() => commander.setIsFocused(false)} - onSubmit={commander.handleCommand} - showCompletions={commander.showCompletions} - completions={commander.completions} - selectedCompletion={commander.selectedCompletion} - onSelectCompletion={commander.setSelectedCompletion} - onAcceptCompletion={commander.acceptCompletion} - /> -
-
- ); - }, -); diff --git a/src/components/md-commander/stores/commandsStore.ts b/src/components/md-commander/stores/commandsStore.ts deleted file mode 100644 index 01dc134..0000000 --- a/src/components/md-commander/stores/commandsStore.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { createStore } from "solid-js/store"; -import type { MdCommanderCommand, MdCommanderCommandMap } from "../types"; -import { setupHelpCommand, clearCommand, rollCommand, trackCommand, untrackCommand, listTrackCommand } from "../commands"; -import {resolvePath} from "../../utils/path"; -import {loadCSVFromPath} from "../../utils/csv-loader"; - -const defaultCommands: MdCommanderCommandMap = { - help: setupHelpCommand({}), - clear: clearCommand, - roll: rollCommand, - track: trackCommand, - untrack: untrackCommand, - list: listTrackCommand, -}; - -export interface CommandsStoreState { - commands: MdCommanderCommandMap; - initialized: boolean; - loading: boolean; - error?: string; -} - -const [commandsStore, setCommandsStore] = createStore({ - commands: { ...defaultCommands }, - initialized: false, - loading: false, -}); - -/** - * 初始化命令(包括 help 命令的动态更新) - */ -export function initializeCommands(customCommands?: MdCommanderCommandMap): MdCommanderCommandMap { - const commands = { ...defaultCommands, ...customCommands }; - // 更新 help 命令的命令列表 - commands.help = setupHelpCommand(commands); - return commands; -} - -/** - * 注册命令到 store - */ -export function registerCommands(customCommands?: MdCommanderCommandMap): void { - const commands = initializeCommands(customCommands); - setCommandsStore({ commands, initialized: true, loading: false }); -} - -/** - * 从 store 获取命令 - */ -export function getCommands(): MdCommanderCommandMap { - return commandsStore.commands; -} - -/** - * 检查命令是否已初始化 - */ -export function isCommandsInitialized(): boolean { - return commandsStore.initialized; -} - -/** - * 获取单个命令 - */ -export function getCommand(name: string): MdCommanderCommand | undefined { - return commandsStore.commands[name]; -} - -/** - * 获取加载状态 - */ -export function getCommandsLoading(): boolean { - return commandsStore.loading; -} - -/** - * 获取错误信息 - */ -export function getCommandsError(): string | undefined { - return commandsStore.error; -} - -/** - * 设置加载状态 - */ -export function setCommandsLoading(loading: boolean): void { - setCommandsStore("loading", loading); -} - -/** - * 设置错误信息 - */ -export function setCommandsError(error?: string): void { - setCommandsStore("error", error); -} - -/** - * 更新命令 - */ -export function updateCommands(updater: (prev: MdCommanderCommandMap) => MdCommanderCommandMap): void { - setCommandsStore("commands", (prev) => updater(prev)); -} - -/** - * 从 CSV 文件加载命令模板并更新命令定义 - */ -export async function loadCommandTemplatesFromCSV( - path: string, - articlePath: string -): Promise { - setCommandsLoading(true); - setCommandsError(undefined); - - try { - const csv = await loadCSVFromPath(resolvePath(articlePath, path)); - - // 按命令分组模板 - const templatesByCommand = new Map(); - for (const row of csv) { - if (!row.command || !row.label || !row.insertedText) continue; - - if (!templatesByCommand.has(row.command)) { - templatesByCommand.set(row.command, []); - } - templatesByCommand.get(row.command)!.push(row); - } - - // 为每个命令添加模板 - updateCommands((prev) => { - const updated = { ...prev }; - for (const [commandName, rows] of templatesByCommand.entries()) { - const cmd = updated[commandName]; - if (!cmd || !cmd.parameters) continue; - - const templates = rows.map((row) => ({ - label: row.label, - description: row.description || "", - insertText: row.insertedText, - })); - - // 为每个参数添加模板 - updated[commandName] = { - ...cmd, - parameters: cmd.parameters.map((param) => ({ - ...param, - templates: param.templates ? [...param.templates, ...templates] : templates, - })), - }; - } - return updated; - }); - - setCommandsStore("initialized", true); - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - setCommandsError(`加载命令模板失败:${errorMessage}`); - console.warn(`Error loading command templates from ${path}:`, error); - } finally { - setCommandsLoading(false); - } -} - -interface CommandTemplateRow { - command: string; - parameter: string; - label: string; - description: string; - insertedText: string; -} diff --git a/src/components/md-commander/stores/entriesStore.ts b/src/components/md-commander/stores/entriesStore.ts deleted file mode 100644 index d46fbc5..0000000 --- a/src/components/md-commander/stores/entriesStore.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { createStore } from "solid-js/store"; -import type { CommanderEntry } from "../types"; - -export interface EntriesStore { - entries: CommanderEntry[]; -} - -const [entriesStore, setEntriesStore] = createStore({ - entries: [], -}); - -/** - * 添加命令执行记录 - */ -export function addEntry(entry: CommanderEntry): void { - setEntriesStore("entries", (prev) => [...prev, entry]); -} - -/** - * 清空所有记录 - */ -export function clearEntries(): void { - setEntriesStore("entries", []); -} - -/** - * 获取所有记录 - */ -export function getEntries(): CommanderEntry[] { - return entriesStore.entries; -} - -/** - * 批量设置记录 - */ -export function setEntries(entries: CommanderEntry[]): void { - setEntriesStore("entries", entries); -} diff --git a/src/components/md-commander/stores/index.ts b/src/components/md-commander/stores/index.ts deleted file mode 100644 index eb9f714..0000000 --- a/src/components/md-commander/stores/index.ts +++ /dev/null @@ -1,37 +0,0 @@ -export { - addTrackerItem, - removeTrackerItem, - updateTrackerAttribute, - updateTrackerClasses, - moveTrackerItem, - removeTrackerClass, - getTrackerItems, - getTrackerHistory, - clearTrackerHistory, - findTrackerIndex, - findTrackerItem, -} from "./trackerStore"; -export type { TrackerStore } from "./trackerStore"; - -export { - initializeCommands, - registerCommands, - getCommands, - isCommandsInitialized, - getCommand, - loadCommandTemplatesFromCSV, - getCommandsLoading, - getCommandsError, - setCommandsLoading, - setCommandsError, - updateCommands, -} from "./commandsStore"; -export type { CommandsStoreState } from "./commandsStore"; - -export { - addEntry, - clearEntries, - getEntries, - setEntries, -} from "./entriesStore"; -export type { EntriesStore } from "./entriesStore"; diff --git a/src/components/md-commander/stores/trackerStore.ts b/src/components/md-commander/stores/trackerStore.ts deleted file mode 100644 index ca4d534..0000000 --- a/src/components/md-commander/stores/trackerStore.ts +++ /dev/null @@ -1,197 +0,0 @@ -import { createStore } from "solid-js/store"; -import type { TrackerItem, TrackerAttribute, TrackerCommand } from "../types"; -import {parseEmmet} from "../utils"; - -export interface TrackerStore { - items: TrackerItem[]; - history: TrackerCommand[]; -} - -const [tracker, setTracker] = createStore({ - items: [], - history: [], -}); - -/** - * 根据 Emmet 语法查找匹配的项目 - * 支持: - * - #id - 匹配 id - * - tag - 匹配 tag - * - tag.class - 匹配 tag 和 class - * - .class - 匹配 class - * - [attr=value] - 匹配属性 - */ -export function findTrackerIndex(emmet: string): number { - const {tag, id, classes, attributes} = parseEmmet(emmet); - - return tracker.items.findIndex(item => { - // 匹配 tag - if (tag && item.tag !== tag) return false; - // 匹配 id - if (id && item.id !== id) return false; - // 匹配所有 classes - if (classes.length > 0) { - for (const cls of classes) { - if (!item.classes.includes(cls)) return false; - } - } - // 匹配属性 - for(const attrName in attributes){ - const attr = attributes[attrName]; - if (item.attributes[attrName] !== attr) return false; - } - return true; - }); -} - -export function findTrackerItem(emmet: string): TrackerItem | undefined { - const index = findTrackerIndex(emmet); - return index >= 0 ? tracker.items[index] : undefined; -} - -export function addTrackerItem(item: Omit): TrackerItem { - const newItem: TrackerItem = { - ...item, - }; - - setTracker("items", (prev) => [...prev, newItem]); - setTracker("history", (prev) => [ - ...prev, - { - type: "add", - itemId: newItem.id, - data: newItem, - timestamp: new Date(), - }, - ]); - - return newItem; -} - -export function removeTrackerItem(emmet: string): boolean { - const index = findTrackerIndex(emmet); - if (index === -1) return false; - - const item = tracker.items[index]; - - setTracker("items", (prev) => prev.filter((_, i) => i !== index)); - setTracker("history", (prev) => [ - ...prev, - { - type: "remove", - itemId: `${item.tag}${item.id ? '#' + item.id : ''}`, - data: item, - timestamp: new Date(), - }, - ]); - - return true; -} - -export function updateTrackerAttribute( - emmet: string, - attrName: string, - attr: TrackerAttribute -): boolean { - const index = findTrackerIndex(emmet); - if (index === -1) return false; - - const item = tracker.items[index]; - setTracker("items", (prev) => - prev.map((i, idx) => - idx === index - ? { ...i, attributes: { ...i.attributes, [attrName]: attr } } - : i - ) - ); - setTracker("history", (prev) => [ - ...prev, - { - type: "update", - itemId: `${item.tag}${item.id ? '#' + item.id : ''}`, - attributeUpdates: { [attrName]: attr }, - timestamp: new Date(), - }, - ]); - return true; -} - -export function moveTrackerItem(emmet: string, direction: "up" | "down"): boolean { - const index = findTrackerIndex(emmet); - if (index === -1) return false; - - const newIndex = direction === "up" ? index - 1 : index + 1; - if (newIndex < 0 || newIndex >= tracker.items.length) return false; - - const newItems = [...tracker.items]; - [newItems[index], newItems[newIndex]] = [newItems[newIndex], newItems[index]]; - - setTracker("items", newItems); - setTracker("history", (prev) => [ - ...prev, - { - type: "reorder", - data: newItems, - timestamp: new Date(), - }, - ]); - return true; -} - -export function removeTrackerClass(emmet: string, className: string): boolean { - const index = findTrackerIndex(emmet); - if (index === -1) return false; - - const item = tracker.items[index]; - setTracker("items", (prev) => - prev.map((i, idx) => - idx === index - ? { ...i, classes: i.classes.filter((c) => c !== className) } - : i - ) - ); - setTracker("history", (prev) => [ - ...prev, - { - type: "update", - itemId: `${item.tag}${item.id ? '#' + item.id : ''}`, - timestamp: new Date(), - }, - ]); - return true; -} - -export function updateTrackerClasses(emmet: string, classes: string[]): boolean { - const index = findTrackerIndex(emmet); - if (index === -1) return false; - - const item = tracker.items[index]; - setTracker("items", (prev) => - prev.map((i, idx) => - idx === index - ? { ...i, classes: [...classes] } - : i - ) - ); - setTracker("history", (prev) => [ - ...prev, - { - type: "update", - itemId: `${item.tag}${item.id ? '#' + item.id : ''}`, - timestamp: new Date(), - }, - ]); - return true; -} - -export function getTrackerItems(): TrackerItem[] { - return tracker.items; -} - -export function getTrackerHistory(): TrackerCommand[] { - return tracker.history; -} - -export function clearTrackerHistory() { - setTracker("history", []); -} diff --git a/src/components/md-commander/types.ts b/src/components/md-commander/types.ts deleted file mode 100644 index 321f920..0000000 --- a/src/components/md-commander/types.ts +++ /dev/null @@ -1,117 +0,0 @@ -import type { Component } from "solid-js"; - -// ==================== 组件 Props ==================== - -export interface MdCommanderProps { - placeholder?: string; - class?: string; - height?: string; - commands?: Record; - commandTemplates?: string | string[]; // CSV 文件路径或路径数组 -} - -// ==================== 命令定义 ==================== - -export interface MdCommanderCommandMap { - [key: string]: MdCommanderCommand; -} - -export interface MdCommanderCommand { - command: string; - description?: string; - parameters?: MdCommanderParameter[]; - options?: Record; - handler?: (args: { - params: Record; - options: Record; - }, commands?: MdCommanderCommandMap) => { - message: string; - type?: "success" | "error" | "info" | "warning"; - isHtml?: boolean; - }; -} - -export interface MdCommanderParameter { - name: string; - description?: string; - type: MdCommanderOptionType; - required?: boolean; - default?: string; - min?: number; - max?: number; - values?: string[]; - templates?: MdCommanderTemplate[]; -} - -export interface MdCommanderTemplate { - label: string; - description?: string; - insertText: string; -} - -export type MdCommanderOptionType = - | "string" - | "number" - | "enum" - | "tag" - | "boolean"; - -export interface MdCommanderOption { - option: string; - description?: string; - type: MdCommanderOptionType; - required?: boolean; - default?: any; - min?: number; - max?: number; - values?: string[]; - EntryComponent?: Component; -} - -// ==================== 命令执行记录 ==================== - -export interface CommanderEntry { - id: string; - command: string; - args: Record; - result: { - message: string; - type?: "success" | "error" | "warning" | "info"; - isHtml?: boolean; - }; - timestamp: Date; -} - -export interface CompletionItem { - label: string; - type: "command" | "option" | "value"; - description?: string; - insertText: string; -} - -// ==================== Tracker ==================== - -export type TrackerViewMode = "history" | "tracker"; - -export type TrackerAttributeType = "progress" | "count" | "string"; - -export interface TrackerAttribute { - name: string; - type: TrackerAttributeType; - value: string | number | { x: number; y: number }; -} - -export interface TrackerItem { - tag: string; - id?: string; - classes: string[]; - attributes: Record; -} - -export interface TrackerCommand { - type: "add" | "remove" | "update" | "reorder"; - itemId?: string; - data?: Partial | TrackerItem[]; - attributeUpdates?: Record; - timestamp: Date; -} diff --git a/src/components/md-commander/utils/emmetParser.ts b/src/components/md-commander/utils/emmetParser.ts deleted file mode 100644 index cfb51dc..0000000 --- a/src/components/md-commander/utils/emmetParser.ts +++ /dev/null @@ -1,114 +0,0 @@ -import type { TrackerAttribute } from "../types"; - -export interface ParsedEmmet { - tag: string; - id?: string; - classes: string[]; - attributes: Record; -} - -/** - * 解析 Emmet 风格的 tracker 语法 - * 格式:tag#id.class1.class2[attr1=value1 attr2=value2] - * 示例:npc#john.dwarf.warrior[hp=4/4 ac=15 name="John the Dwarf"] - */ -export function parseEmmet(input: string): ParsedEmmet { - const result: ParsedEmmet = { - tag: "", - classes: [], - attributes: {}, - }; - - if (!input) return result; - - // 匹配属性部分 [...] - const attrMatch = input.match(/\[(.+)\]$/); - let attrString: string | undefined; - let mainPart: string; - - if (attrMatch) { - attrString = attrMatch[1]; - mainPart = input.slice(0, attrMatch.index); - } else { - mainPart = input; - } - - // 解析 tag、id 和 classes - // 格式:tag#id.class1.class2.class3 - const tagClassMatch = mainPart.match(/^([^#.\s]+)?(?:#([^.\s]+))?(?:\.([^.]+(?:\.[^.]+)*))?/); - - if (tagClassMatch) { - if (tagClassMatch[1]) { - result.tag = tagClassMatch[1]; - } - if (tagClassMatch[2]) { - // # 后面的是 ID,不是 class - result.id = tagClassMatch[2]; - } - if (tagClassMatch[3]) { - result.classes.push(...tagClassMatch[3].split(".")); - } - } - - // 解析属性 - if (attrString) { - result.attributes = parseAttributes(attrString); - } - - return result; -} - -/** - * 解析属性字符串 - * 支持格式: - * - hp=4/4 (progress 类型,自动检测) - * - count=5 (count 类型,纯数字) - * - name="John" (string 类型,带引号) - * - name=John (string 类型,不带引号) - */ -function parseAttributes(attrString: string): Record { - const attributes: Record = {}; - - // 匹配键值对:使用,分隔 - const regex = /(\w+)=(?:"([^"]*)"|'([^']*)'|([^,]+))/g; - let match: RegExpExecArray | null; - - while ((match = regex.exec(attrString)) !== null) { - const key = match[1]; - // 匹配的值可能是第 2、3 或 4 组(取决于引号类型) - const value = match[2] ?? match[3] ?? match[4] ?? ""; - - // 自动检测类型 - let attr: TrackerAttribute; - - // 检查是否是 progress 格式 (x/y) - const progressMatch = value.match(/^(\d+)\/(\d+)$/); - if (progressMatch) { - attr = { - name: key, - type: "progress", - value: { x: parseInt(progressMatch[1]), y: parseInt(progressMatch[2]) }, - }; - } - // 检查是否是纯数字(count 类型) - else if (/^\d+$/.test(value)) { - attr = { - name: key, - type: "count", - value: parseInt(value), - }; - } - // 默认 string 类型 - else { - attr = { - name: key, - type: "string", - value: value.replace(/^["']|["']$/g, ""), // 移除可能的引号 - }; - } - - attributes[key] = attr; - } - - return attributes; -} diff --git a/src/components/md-commander/utils/index.ts b/src/components/md-commander/utils/index.ts deleted file mode 100644 index 2c489f4..0000000 --- a/src/components/md-commander/utils/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { parseEmmet } from './emmetParser'; -export type { ParsedEmmet } from './emmetParser'; diff --git a/src/doc-entries/md-commander.md b/src/doc-entries/md-commander.md deleted file mode 100644 index 9d23688..0000000 --- a/src/doc-entries/md-commander.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -tag: md-commander -icon: 📋 -title: 命令追踪器 ---- - -支持命令历史和游戏状态追踪,使用类 Emmet 语法创建追踪项,支持 CSV 命令模板加载。 - -**语法:** `:md-commander[./templates.csv]{选项}` - -**追踪 NPC 血量和防御:** -``` -track npc#john.dwarf.warrior[hp=4/4 ac=15 name="John"] -``` - -## 语法规则 - -- `#id` 设置 ID -- `.class` 添加类别 -- `[attr=value]` 设置属性 - -## 属性类型 - -| 格式 | 显示 | -|---|---| -| `x/y` | 进度条 | -| 整数 | 计数器 | -| 文本 | 文本字段 | - -## 视图模式 - -组件有两个标签页,通过顶部标签栏切换: - -- **历史**:显示命令执行历史,点击可重新填入命令 -- **追踪**:显示当前追踪的所有项目,支持属性编辑、类管理、排序和删除 - -## 键盘快捷键 - -| 快捷键 | 说明 | -|---|---| -| `Enter` | 执行命令 | -| `Tab` | 接受自动补全 | -| `↑` / `↓` | 补全列表导航 / 命令历史导航 | -| `Escape` | 关闭补全菜单 | - -## 属性 - -| 属性 | 类型 | 默认值 | 说明 | -|---|---|---|---| -| `placeholder` | string | `"输入命令..."` | 输入框占位符 | -| `class` | string | — | 额外 CSS 类 | -| `height` | string | `"400px"` | 组件高度 | -| `commandTemplates` | string | — | CSV 模板文件路径 | - -## 使用场景 - -用于追踪战斗中的 NPC 血量、AC、状态等游戏信息。 \ No newline at end of file