diff --git a/src/components/md-commander/commands/help.ts b/src/components/md-commander/commands/help.ts index 272bc74..7daa66b 100644 --- a/src/components/md-commander/commands/help.ts +++ b/src/components/md-commander/commands/help.ts @@ -1,37 +1,31 @@ import type { MdCommanderCommand, MdCommanderCommandMap } from "../types"; -export const helpCommand: MdCommanderCommand = { - command: "help", - description: "显示帮助信息或特定命令的帮助", - parameters: [ - { - name: "cmd", - description: "要查询的命令名", - type: "enum", - values: [], // 运行时填充 - required: false, - }, - ], - handler: (args, commands) => { - const cmdName = args.params.cmd; - if (cmdName && commands?.[cmdName]) { +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: `命令:${cmdName}\n描述:${commands[cmdName]?.description || "无描述"}`, + message: `可用命令:${cmdList}`, type: "info", }; - } - const cmdList = Object.keys(commands || {}).filter(k => k !== "help").join(", "); - return { - message: `可用命令:${cmdList}`, - type: "info", - }; - }, -}; - -export function setupHelpCommand(commands: MdCommanderCommandMap): MdCommanderCommand { - const cmd = { ...helpCommand }; - if (cmd.parameters?.[0]) { - cmd.parameters[0].values = Object.keys(commands).filter(k => k !== "help"); - } - return cmd; + }, + }; } diff --git a/src/components/md-commander/commands/index.ts b/src/components/md-commander/commands/index.ts index 707a35d..0f9391c 100644 --- a/src/components/md-commander/commands/index.ts +++ b/src/components/md-commander/commands/index.ts @@ -1,4 +1,4 @@ -export { helpCommand, setupHelpCommand } from "./help"; +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/hooks/completions.ts b/src/components/md-commander/hooks/completions.ts new file mode 100644 index 0000000..899cbbf --- /dev/null +++ b/src/components/md-commander/hooks/completions.ts @@ -0,0 +1,182 @@ +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 index f2b1cd8..0a6bd6a 100644 --- a/src/components/md-commander/hooks/index.ts +++ b/src/components/md-commander/hooks/index.ts @@ -1,13 +1,6 @@ -export { useCommander, defaultCommands, parseInput, getCompletions, getResultClass } from './useCommander'; -export type { UseCommanderReturn } from './useCommander'; -export { rollFormula, rollSimple } from './useDiceRoller'; -export * from './dice-engine'; - -// Tracker 相关导出 -export type { - TrackerItem, - TrackerAttribute, - TrackerAttributeType, - TrackerCommand, - TrackerViewMode, -} from '../types'; +export { useCommander, initializeCommands, defaultCommands } from "./useCommander"; +export type { UseCommanderReturn } from "./useCommander"; +export { rollSimple, rollFormula } from "./useDiceRoller"; +export type { DiceRollerResult } from "./useDiceRoller"; +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 index feab661..8036463 100644 --- a/src/components/md-commander/hooks/useCommander.ts +++ b/src/components/md-commander/hooks/useCommander.ts @@ -1,5 +1,5 @@ import { createSignal } from "solid-js"; -import { +import type { MdCommanderCommand, MdCommanderCommandMap, CommanderEntry, @@ -9,8 +9,8 @@ import { TrackerCommand, TrackerViewMode, } from "../types"; -import { rollSimple } from "./useDiceRoller"; -import { helpCommand, setupHelpCommand, clearCommand, rollCommand, trackCommand, untrackCommand, listTrackCommand } from "../commands"; +import { parseInput, getCompletions } from "./completions"; +import { setupHelpCommand, clearCommand, rollCommand, trackCommand, untrackCommand, listTrackCommand } from "../commands"; import { addTrackerItem as addTracker, removeTrackerItem as removeTracker, @@ -26,7 +26,7 @@ import { // ==================== 默认命令 ==================== export const defaultCommands: MdCommanderCommandMap = { - help: setupHelpCommand({}), + help: { command: "help" }, // 占位,稍后初始化 clear: clearCommand, roll: rollCommand, track: trackCommand, @@ -34,228 +34,11 @@ export const defaultCommands: MdCommanderCommandMap = { list: listTrackCommand, }; -// 初始化默认命令 -Object.keys(defaultCommands).forEach(key => { - if (key === "help") { - const help = setupHelpCommand(defaultCommands); - defaultCommands.help = help; - } -}); - -// ==================== 工具函数 ==================== - -export function parseInput(input: string, commands?: MdCommanderCommandMap): { - command?: string; - params: Record; - options: Record; - incompleteParam?: { index: number; value: string }; -} { - const result: { - command?: string; - params: Record; - options: Record; - incompleteParam?: { index: number; value: string }; - } = { - 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("--")) { - // 选项 --key=value 或 --key value - const eqIndex = part.indexOf("="); - if (eqIndex !== -1) { - const key = part.slice(2, eqIndex); - const value = part.slice(eqIndex + 1); - result.options[key] = value; - } else { - const key = part.slice(2); - if (i + 1 < parts.length && !parts[i + 1].startsWith("-")) { - result.options[key] = parts[i + 1]; - i++; - } else { - // 未完成的选项 - } - } - } else if (part.startsWith("-") && part.length === 2) { - // 短选项 -k value - 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) { - const paramDef = paramDefs[paramIndex]; - result.params[paramDef.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]) { - const commandCompletions = Object.values(commands) - .filter((cmd) => cmd.command.startsWith(parsed.command || "")) - .map((cmd) => ({ - label: cmd.command, - type: "command" as "command", - description: cmd.description, - insertText: cmd.command, - })); - if (commandCompletions.length > 0) { - return commandCompletions; - } - } - - 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: number; - if (parsed.incompleteParam !== undefined) { - paramIndex = parsed.incompleteParam.index; - } else if (isTypingLastParam) { - // 正在编辑最后一个参数 - paramIndex = paramDefs.length - 1; - } else { - paramIndex = 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" as "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" as "value", - description: paramDef.description, - insertText: v, - })); - } - // 其他类型的参数,显示提示 - return [{ - label: `<${paramDef.name}>`, - type: "value" as "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" ? "" : ""}`, - })); -} - -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"; - case "info": - default: - return "text-blue-600"; - } +// 初始化默认命令(包括 help) +export function initializeCommands(customCommands?: MdCommanderCommandMap): MdCommanderCommandMap { + const commands = { ...defaultCommands, ...customCommands }; + commands.help = setupHelpCommand(commands); + return commands; } // ==================== Commander Hook ==================== @@ -280,51 +63,38 @@ export interface UseCommanderReturn { setHistoryIndex: (v: number) => void; commandHistory: () => string[]; navigateHistory: (direction: 'up' | 'down') => void; - - // Tracker 相关 + + // Tracker 相关 - 直接暴露 store API viewMode: () => TrackerViewMode; setViewMode: (mode: TrackerViewMode) => void; trackerItems: () => TrackerItem[]; - setTrackerItems: (updater: (prev: TrackerItem[]) => TrackerItem[]) => void; trackerHistory: () => TrackerCommand[]; - addTrackerItem: (item: Omit) => void; - removeTrackerItem: (emmet: string) => boolean; + addTrackerItem: typeof addTracker; + removeTrackerItem: typeof removeTracker; removeTrackerItemByIndex: (index: number) => void; - updateTrackerAttribute: (emmet: string, attrName: string, attr: TrackerAttribute) => boolean; + updateTrackerAttribute: typeof updateTrackerAttr; updateTrackerAttributeByIndex: (index: number, attrName: string, attr: TrackerAttribute) => void; updateTrackerClassesByIndex: (index: number, classes: string[]) => void; - moveTrackerItem: (emmet: string, direction: 'up' | 'down') => boolean; + moveTrackerItem: typeof moveTracker; moveTrackerItemByIndex: (index: number, direction: 'up' | 'down') => void; - removeTrackerItemClass: (emmet: string, className: string) => boolean; + removeTrackerItemClass: typeof removeClassFromTracker; removeTrackerItemClassByIndex: (index: number, className: string) => void; - recordTrackerCommand: (cmd: Omit) => void; } -export function useCommander( - customCommands?: MdCommanderCommandMap, -): UseCommanderReturn { +export function useCommander(customCommands?: MdCommanderCommandMap): UseCommanderReturn { const [inputValue, setInputValue] = createSignal(""); const [entries, setEntries] = 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); - - // Tracker 相关 const [viewMode, setViewMode] = createSignal("history"); - const commands = { ...defaultCommands, ...customCommands }; + const commands = initializeCommands(customCommands); - // 更新 help 命令的参数值 - if (commands.help?.parameters?.[0]) { - commands.help.parameters[0].values = Object.keys(commands).filter( - (k) => k !== "help", - ); - } + // ==================== 命令执行 ==================== const handleCommand = () => { const input = inputValue().trim(); @@ -360,11 +130,8 @@ export function useCommander( }; setEntries((prev) => [...prev, newEntry]); - - // 添加到命令历史 setCommandHistory((prev) => [...prev, input]); - setHistoryIndex(-1); // 重置历史索引 - + setHistoryIndex(-1); setInputValue(""); setShowCompletions(false); @@ -373,18 +140,17 @@ export function useCommander( } }; + // ==================== 自动补全 ==================== + const updateCompletions = () => { - const input = inputValue(); - const comps = getCompletions(input, commands); + const comps = getCompletions(inputValue(), commands); setCompletions(comps); setShowCompletions(comps.length > 0 && isFocused()); setSelectedCompletionState(0); }; const setSelectedCompletion = (v: number | ((prev: number) => number)) => { - const comps = completions(); - const maxIdx = comps.length - 1; - + const maxIdx = completions().length - 1; if (typeof v === 'function') { setSelectedCompletionState(prev => { const next = v(prev); @@ -398,49 +164,33 @@ export function useCommander( const acceptCompletion = () => { const idx = selectedCompletion(); const comps = completions(); - const validIdx = Math.min(idx, comps.length - 1); - const comp = comps[validIdx]; + const comp = comps[Math.min(idx, comps.length - 1)]; if (!comp) return; const input = inputValue(); - const trimmed = input.trim(); const parsed = parseInput(input, commands); - let newValue: string; + if (comp.type === "command") { newValue = comp.insertText + " "; } else if (comp.type === "option") { - const base = parsed.command || ""; const existingOptions = Object.entries(parsed.options) .map(([k, v]) => `--${k}=${v}`) .join(" "); - newValue = `${base} ${existingOptions}${existingOptions ? " " : ""}${comp.insertText}`; + 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 base = parsed.command || ""; - const existingOptions = Object.entries(parsed.options) - .map(([k, v]) => `--${k}=${v}`) - .join(" "); - - // 判断是否正在编辑最后一个参数(使用原始 input 检查尾随空格) - const isTypingLastParam = paramDefs.length === usedParams.length && - input.length > 0 && - !input.endsWith(" "); + const isTypingLastParam = paramDefs.length === usedParams.length && !input.endsWith(" "); if (isTypingLastParam) { - // 替换最后一个参数 - const parts = trimmed.split(/\s+/); - parts.pop(); // 移除正在输入的参数 - const existingParams = parts.slice(1).join(" "); // 跳过命令 - newValue = `${base}${existingParams ? " " + existingParams : ""} ${comp.insertText}${existingOptions ? " " + existingOptions : ""}`; + 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 = `${base} ${existingParams}${existingParams ? " " : ""}${comp.insertText}${existingOptions ? " " + existingOptions : ""}`; + newValue = `${parsed.command || ""} ${existingParams}${existingParams ? " " : ""}${comp.insertText}`; } else { newValue = input; } @@ -452,22 +202,19 @@ export function useCommander( 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++; - } + if (newIndex < history.length - 1) newIndex++; } else { - // 向下浏览历史(更新的命令) if (newIndex > 0) { newIndex--; } else { - // 回到当前输入 setInputValue(""); setHistoryIndex(-1); return; @@ -475,92 +222,41 @@ export function useCommander( } setHistoryIndex(newIndex); - // 从历史末尾获取命令(最新的在前) setInputValue(history[history.length - 1 - newIndex]); }; - // ==================== Tracker 方法 ==================== + // ==================== Tracker 操作 ==================== - const addTrackerItem = (item: Omit) => { - return addTracker(item); - }; - - const removeTrackerItem = (emmet: string) => { - return removeTracker(emmet); + 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 items = trackerItems(); - if (index >= 0 && index < items.length) { - const item = items[index]; - const emmet = `${item.tag}${item.id ? '#' + item.id : ''}${item.classes.length > 0 ? '.' + item.classes.join('.') : ''}`; - removeTracker(emmet); - } - }; - - const updateTrackerAttribute = (emmet: string, attrName: string, attr: TrackerAttribute) => { - return updateTrackerAttr(emmet, attrName, attr); + const emmet = getEmmetFromIndex(index); + if (emmet) removeTracker(emmet); }; const updateTrackerAttributeByIndex = (index: number, attrName: string, attr: TrackerAttribute) => { - const items = trackerItems(); - if (index >= 0 && index < items.length) { - const item = items[index]; - const emmet = `${item.tag}${item.id ? '#' + item.id : ''}${item.classes.length > 0 ? '.' + item.classes.join('.') : ''}`; - updateTrackerAttr(emmet, attrName, attr); - } + const emmet = getEmmetFromIndex(index); + if (emmet) updateTrackerAttr(emmet, attrName, attr); }; const updateTrackerClassesByIndex = (index: number, classes: string[]) => { - const items = trackerItems(); - if (index >= 0 && index < items.length) { - const item = items[index]; - const emmet = `${item.tag}${item.id ? '#' + item.id : ''}${item.classes.length > 0 ? '.' + item.classes.join('.') : ''}`; - updateTrackerClassesStore(emmet, classes); - } - }; - - const moveTrackerItem = (emmet: string, direction: 'up' | 'down') => { - return moveTracker(emmet, direction); + const emmet = getEmmetFromIndex(index); + if (emmet) updateTrackerClassesStore(emmet, classes); }; const moveTrackerItemByIndex = (index: number, direction: 'up' | 'down') => { - const items = trackerItems(); - if (index >= 0 && index < items.length) { - const item = items[index]; - const emmet = `${item.tag}${item.id ? '#' + item.id : ''}${item.classes.length > 0 ? '.' + item.classes.join('.') : ''}`; - moveTracker(emmet, direction); - } - }; - - const removeTrackerItemClass = (emmet: string, className: string) => { - return removeClassFromTracker(emmet, className); + const emmet = getEmmetFromIndex(index); + if (emmet) moveTracker(emmet, direction); }; const removeTrackerItemClassByIndex = (index: number, className: string) => { - const items = trackerItems(); - if (index >= 0 && index < items.length) { - const item = items[index]; - const emmet = `${item.tag}${item.id ? '#' + item.id : ''}${item.classes.length > 0 ? '.' + item.classes.join('.') : ''}`; - removeClassFromTracker(emmet, className); - } - }; - - const trackerItems = () => getTrackerItems(); - const trackerHistory = () => getTrackerHistory(); - - const setTrackerItems = (updater: (prev: TrackerItem[]) => TrackerItem[]) => { - // 直接操作 store - const current = getTrackerItems(); - const updated = updater(current); - // 计算差异并记录 - if (updated.length !== current.length) { - // 长度变化,可能是批量操作 - } - }; - - const recordTrackerCommand = (cmd: Omit) => { - // store 已经自动记录 + const emmet = getEmmetFromIndex(index); + if (emmet) removeClassFromTracker(emmet, className); }; return { @@ -585,19 +281,17 @@ export function useCommander( navigateHistory, viewMode, setViewMode, - trackerItems, - setTrackerItems, - trackerHistory, - addTrackerItem, - removeTrackerItem, + trackerItems: getTrackerItems, + trackerHistory: getTrackerHistory, + addTrackerItem: addTracker, + removeTrackerItem: removeTracker, removeTrackerItemByIndex, - updateTrackerAttribute, + updateTrackerAttribute: updateTrackerAttr, updateTrackerAttributeByIndex, updateTrackerClassesByIndex, - moveTrackerItem, + moveTrackerItem: moveTracker, moveTrackerItemByIndex, - removeTrackerItemClass, + removeTrackerItemClass: removeClassFromTracker, removeTrackerItemClassByIndex, - recordTrackerCommand, }; } diff --git a/src/components/md-commander/index.tsx b/src/components/md-commander/index.tsx index 41285ba..cac1bd6 100644 --- a/src/components/md-commander/index.tsx +++ b/src/components/md-commander/index.tsx @@ -1,24 +1,26 @@ import { customElement, noShadowDOM } from "solid-element"; import { onMount, onCleanup, Show, createEffect } from "solid-js"; -import { useCommander } from "./hooks"; +import { useCommander, initializeCommands } 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, resolvePath} from "../utils/path"; -import {loadCommandTemplates} from "./utils/commandTemplates"; +import { loadElementSrc, resolvePath } from "../utils/path"; +import { loadCommandTemplates } from "./utils/commandTemplates"; customElement( "md-commander", { placeholder: "", class: "", height: "", commandTemplates: "" }, (props, { element }) => { noShadowDOM(); - const {articlePath, rawSrc} = loadElementSrc(element as any); + const { articlePath, rawSrc } = loadElementSrc(element as any); const commander = useCommander(props.commands); + createEffect(async () => { - if(!rawSrc) return; - await loadCommandTemplates(commander.commands, resolvePath(articlePath, rawSrc)); + if (!rawSrc) return; + const commands = initializeCommands(props.commands); + await loadCommandTemplates(commands, resolvePath(articlePath, rawSrc)); }); const handleKeyDown = (e: KeyboardEvent) => { @@ -48,7 +50,6 @@ customElement( } } - // 补全未打开时,使用上下键浏览历史 if (e.key === "ArrowUp" && !commander.showCompletions()) { e.preventDefault(); commander.navigateHistory("up"); @@ -83,25 +84,22 @@ customElement( class={`md-commander flex flex-col border border-gray-300 rounded-lg overflow-hidden ${props.class || ""}`} style={{ height: heightStyle() }} > - {/* 标签页导航 */} - + - {/* 内容区域:历史或追踪 */} - commander.updateTrackerAttributeByIndex(index, attrName, attr) + commander.updateTrackerAttributeByIndex(index, attrName, attr) } onClassesChange={(index, classes) => commander.updateTrackerClassesByIndex(index, classes) } - onRemoveClass={commander.removeTrackerItemClassByIndex} + onRemoveClass={(index, className) => + commander.removeTrackerItemClassByIndex(index, className) + } onMoveUp={(index) => commander.moveTrackerItemByIndex(index, "up")} onMoveDown={(index) => commander.moveTrackerItemByIndex(index, "down")} onRemove={(index) => commander.removeTrackerItemByIndex(index)} @@ -114,7 +112,6 @@ customElement( /> - {/* 命令输入框 */}
; } +// ==================== 命令执行记录 ==================== + export interface CommanderEntry { id: string; command: string; @@ -83,7 +89,9 @@ export interface CompletionItem { insertText: string; } -// ==================== Tracker 类型 ==================== +// ==================== Tracker ==================== + +export type TrackerViewMode = "history" | "tracker"; export type TrackerAttributeType = "progress" | "count" | "string"; @@ -95,13 +103,11 @@ export interface TrackerAttribute { export interface TrackerItem { tag: string; - id?: string; // Emmet ID (#id) + id?: string; classes: string[]; attributes: Record; } -export type TrackerViewMode = "history" | "tracker"; - export interface TrackerCommand { type: "add" | "remove" | "update" | "reorder"; itemId?: string;