From bda201ae81ff98117331a8abb12a1f20ba1ccec9 Mon Sep 17 00:00:00 2001 From: hypercross Date: Sun, 1 Mar 2026 13:54:48 +0800 Subject: [PATCH] refactor: impl --- .../md-commander/CommanderEntries.tsx | 87 ++++++++++--------- .../md-commander/hooks/useCommander.ts | 19 ++-- src/components/md-commander/index.tsx | 34 +++++--- .../md-commander/stores/commandsStore.ts | 57 +++++++++++- .../md-commander/stores/entriesStore.ts | 38 ++++++++ src/components/md-commander/stores/index.ts | 14 +++ 6 files changed, 187 insertions(+), 62 deletions(-) create mode 100644 src/components/md-commander/stores/entriesStore.ts diff --git a/src/components/md-commander/CommanderEntries.tsx b/src/components/md-commander/CommanderEntries.tsx index 10489af..cf623e4 100644 --- a/src/components/md-commander/CommanderEntries.tsx +++ b/src/components/md-commander/CommanderEntries.tsx @@ -1,27 +1,17 @@ -import { type Component, For, Show, createEffect, on } from "solid-js"; +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; - // 当 entries 变化时自动滚动到底部 - createEffect( - on( - () => props.entries().length, - () => { - if (containerRef) { - containerRef.scrollTop = containerRef.scrollHeight; - } - }, - ), - ); - const handleCommandClick = (command: string) => { if (props.onCommandClick) { props.onCommandClick(command); @@ -34,35 +24,54 @@ export const CommanderEntries: Component = (props) => { class="commander-entries flex-1 overflow-auto p-3 bg-white space-y-2" > 0} + when={props.loading} fallback={ -
暂无命令执行记录
+ 0} + fallback={ +
暂无命令执行记录
+ } + > + + {(entry) => ( +
+
+ handleCommandClick(entry.command)} + title="点击复制到输入框" + > + {entry.command} + + + {entry.timestamp.toLocaleTimeString()} + +
+
+ {!entry.result.isHtml && entry.result.message} +
+
+ )} +
+
+ } + > +
{props.error}
+
} > - - {(entry) => ( -
-
- handleCommandClick(entry.command)} - title="点击复制到输入框" - > - {entry.command} - - - {entry.timestamp.toLocaleTimeString()} - -
-
- {!entry.result.isHtml && entry.result.message} -
-
- )} -
+
+
+
+ 加载命令模板中... +
+
); diff --git a/src/components/md-commander/hooks/useCommander.ts b/src/components/md-commander/hooks/useCommander.ts index a730b29..e4f54ac 100644 --- a/src/components/md-commander/hooks/useCommander.ts +++ b/src/components/md-commander/hooks/useCommander.ts @@ -4,20 +4,22 @@ import type { CompletionItem, TrackerItem, TrackerAttribute, - TrackerCommand, TrackerViewMode, MdCommanderCommandMap, + TrackerCommand, } from "../types"; import { parseInput, getCompletions } from "./completions"; import { addTrackerItem, getTrackerHistory, getTrackerItems, - moveTrackerItem, removeTrackerClass, + moveTrackerItem, + removeTrackerClass, removeTrackerItem, updateTrackerAttribute, - updateTrackerClasses + updateTrackerClasses, } from "../stores"; +import { addEntry, getEntries, clearEntries } from "../stores/entriesStore"; // ==================== Commander Hook ==================== @@ -29,7 +31,6 @@ export interface UseCommanderReturn { selectedCompletion: () => number; isFocused: () => boolean; setInputValue: (v: string) => void; - setEntries: (updater: (prev: CommanderEntry[]) => CommanderEntry[]) => void; setShowCompletions: (v: boolean) => void; setSelectedCompletion: (v: number | ((prev: number) => number)) => void; setIsFocused: (v: boolean) => void; @@ -62,7 +63,6 @@ export interface UseCommanderReturn { export function useCommander(initialCommands?: MdCommanderCommandMap): UseCommanderReturn { const [inputValue, setInputValue] = createSignal(""); - const [entries, setEntries] = createSignal([]); const [showCompletions, setShowCompletions] = createSignal(false); const [completions, setCompletions] = createSignal([]); const [selectedCompletion, setSelectedCompletionState] = createSignal(0); @@ -72,6 +72,9 @@ export function useCommander(initialCommands?: MdCommanderCommandMap): UseComman const [viewMode, setViewMode] = createSignal("history"); const [commands, setCommands] = createSignal(initialCommands || {}); + // 从 store 获取 entries + const entries = getEntries; + // ==================== 命令执行 ==================== const handleCommand = () => { @@ -108,14 +111,15 @@ export function useCommander(initialCommands?: MdCommanderCommandMap): UseComman timestamp: new Date(), }; - setEntries((prev) => [...prev, newEntry]); + // 使用 store 添加记录 + addEntry(newEntry); setCommandHistory((prev) => [...prev, input]); setHistoryIndex(-1); setInputValue(""); setShowCompletions(false); if (commandName === "clear") { - setEntries([]); + clearEntries(); } }; @@ -246,7 +250,6 @@ export function useCommander(initialCommands?: MdCommanderCommandMap): UseComman selectedCompletion, isFocused, setInputValue, - setEntries, setShowCompletions, setSelectedCompletion, setIsFocused, diff --git a/src/components/md-commander/index.tsx b/src/components/md-commander/index.tsx index 10ceb13..c4c6e4e 100644 --- a/src/components/md-commander/index.tsx +++ b/src/components/md-commander/index.tsx @@ -1,5 +1,5 @@ import { customElement, noShadowDOM } from "solid-element"; -import { onMount, onCleanup, Show, createEffect, on } from "solid-js"; +import { onMount, onCleanup, Show, createResource } from "solid-js"; import { useCommander } from "./hooks"; import { CommanderInput } from "./CommanderInput"; import { CommanderEntries } from "./CommanderEntries"; @@ -7,7 +7,7 @@ import { TrackerView } from "./TrackerView"; import { TabBar } from "./TabBar"; import type { MdCommanderProps } from "./types"; import { loadElementSrc } from "../utils/path"; -import { initializeCommands, loadCommandTemplatesFromCSV } from "./stores"; +import { initializeCommands, loadCommandTemplatesFromCSV, getCommands, getCommandsLoading, getCommandsError } from "./stores"; customElement( "md-commander", @@ -16,18 +16,28 @@ customElement( noShadowDOM(); const { articlePath, rawSrc } = loadElementSrc(element as any); - // 初始化命令并注册到 store + // 初始化命令 const commands = initializeCommands(props.commands); const commander = useCommander(commands); - // 加载 CSV 模板 - createEffect( async () => { - if (!rawSrc || !rawSrc) return; - await loadCommandTemplatesFromCSV(rawSrc, articlePath); - // 更新 commander 中的命令(从 store 获取) - const {getCommands} = await import("./stores/commandsStore"); - commander.setCommands(getCommands()); - }); + // 使用 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) { @@ -114,6 +124,8 @@ customElement( > commander.setInputValue(cmd)} /> diff --git a/src/components/md-commander/stores/commandsStore.ts b/src/components/md-commander/stores/commandsStore.ts index fd00df7..45de973 100644 --- a/src/components/md-commander/stores/commandsStore.ts +++ b/src/components/md-commander/stores/commandsStore.ts @@ -13,12 +13,17 @@ const defaultCommands: MdCommanderCommandMap = { list: listTrackCommand, }; -const [commandsStore, setCommandsStore] = createStore<{ +export interface CommandsStoreState { commands: MdCommanderCommandMap; initialized: boolean; -}>({ + loading: boolean; + error?: string; +} + +const [commandsStore, setCommandsStore] = createStore({ commands: { ...defaultCommands }, initialized: false, + loading: false, }); /** @@ -36,7 +41,7 @@ export function initializeCommands(customCommands?: MdCommanderCommandMap): MdCo */ export function registerCommands(customCommands?: MdCommanderCommandMap): void { const commands = initializeCommands(customCommands); - setCommandsStore({ commands, initialized: true }); + setCommandsStore({ commands, initialized: true, loading: false }); } /** @@ -60,6 +65,41 @@ 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 文件加载命令模板并更新命令定义 */ @@ -67,6 +107,9 @@ export async function loadCommandTemplatesFromCSV( path: string, articlePath: string ): Promise { + setCommandsLoading(true); + setCommandsError(undefined); + try { const csv = await loadCSV(resolvePath(articlePath, path)); @@ -82,7 +125,7 @@ export async function loadCommandTemplatesFromCSV( } // 为每个命令添加模板 - setCommandsStore("commands", (prev) => { + updateCommands((prev) => { const updated = { ...prev }; for (const [commandName, rows] of templatesByCommand.entries()) { const cmd = updated[commandName]; @@ -105,8 +148,14 @@ export async function loadCommandTemplatesFromCSV( } 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); } } diff --git a/src/components/md-commander/stores/entriesStore.ts b/src/components/md-commander/stores/entriesStore.ts new file mode 100644 index 0000000..d46fbc5 --- /dev/null +++ b/src/components/md-commander/stores/entriesStore.ts @@ -0,0 +1,38 @@ +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 index 2f38b9e..eb9f714 100644 --- a/src/components/md-commander/stores/index.ts +++ b/src/components/md-commander/stores/index.ts @@ -20,4 +20,18 @@ export { 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";