import { INVALID_TEMPLATE_NAME_MESSAGE, validateTemplateName } from "../services/templateName.js"; import { TemplateStore, TemplateStoreError, TEMPLATE_NOT_FOUND_MESSAGE, } from "../services/templateStore.js"; export const PARAMETER_ERROR_MESSAGE = "参数错误"; export const NO_TEMPLATES_MESSAGE = "没有模板"; export interface OrderAutocompleteItem { value: string; label: string; description?: string; } export type NotifyType = "info" | "warning" | "error"; export interface OrderUi { editor(title: string, prefill?: string): Promise; input?(title: string, placeholder?: string): Promise; select?(title: string, items: string[]): Promise; setEditorText(text: string): void | Promise; notify(message: string, type?: NotifyType): void | Promise; } export interface OrderCommandContext { hasUI?: boolean; ui: OrderUi; } export interface OrderCommandOptions { store?: TemplateStore; } type Subcommand = "create" | "edit" | "send" | "remove" | "list"; const SUBCOMMAND_COMPLETIONS: Array = [ { value: "create", label: "create", description: "创建新模板:/order create " }, { value: "edit", label: "edit", description: "编辑已有模板:/order edit (可加 --clear 清空正文)" }, { value: "send", label: "send", description: "填入模板内容:/order send " }, { value: "remove", label: "remove", description: "删除模板:/order remove " }, { value: "list", label: "list", description: "列出模板:/order list" }, ]; export async function getOrderArgumentCompletions( argumentPrefix: string, store = new TemplateStore(), ): Promise { const normalized = argumentPrefix.trimStart(); const firstWhitespace = normalized.search(/\s/); if (firstWhitespace === -1) { const query = normalized.toLowerCase(); const subcommands = SUBCOMMAND_COMPLETIONS.filter((item) => item.value.startsWith(query)); return subcommands.length > 0 ? subcommands : null; } const subcommand = normalized.slice(0, firstWhitespace); const rest = normalized.slice(firstWhitespace + 1); const editClearSuffix = " --clear"; const hasEditClearSuffix = subcommand === "edit" && rest.endsWith(editClearSuffix); const namePrefix = hasEditClearSuffix ? rest.slice(0, -editClearSuffix.length) : rest; if ((subcommand !== "edit" && subcommand !== "send" && subcommand !== "remove") || /\s/.test(namePrefix)) { return null; } let names: string[]; let descriptions: Record; try { [names, descriptions] = await Promise.all([store.listTemplates(), store.readDescriptions()]); } catch { return null; } const query = namePrefix.toLowerCase(); const matches = names.filter((name) => name.toLowerCase().includes(query)); if (matches.length === 0) { return null; } return matches.map((name) => ({ value: hasEditClearSuffix ? `edit ${name} --clear` : `${subcommand} ${name}`, label: name, description: hasEditClearSuffix ? "一键清空模板正文" : descriptions[name] ?? templateCompletionFallback(subcommand), })); } export async function handleOrderCommand( args: string, ctx: OrderCommandContext, options: OrderCommandOptions = {}, ): Promise { const store = options.store ?? new TemplateStore(); if (ctx.hasUI === false) { await notify(ctx, "/order 需要交互式 UI", "error"); return; } const parsed = parseArgs(args); if (!parsed.ok) { await notify(ctx, PARAMETER_ERROR_MESSAGE, "error"); return; } try { switch (parsed.subcommand) { case "create": await createTemplate(parsed.name, ctx, store); return; case "edit": await editTemplate(parsed.name, ctx, store, parsed.clear ?? false); return; case "send": await sendTemplate(parsed.name, ctx, store); return; case "remove": await removeTemplate(parsed.name, ctx, store); return; case "list": await listTemplates(ctx, store); return; } } catch (error) { await notify(ctx, errorToMessage(error), "error"); } } async function createTemplate(name: string, ctx: OrderCommandContext, store: TemplateStore): Promise { validateTemplateName(name); await store.createTemplate(name); const description = await editDescription(ctx, name, ""); if (description !== undefined) { await store.setDescription(name, description); } const edited = await ctx.ui.editor(`编辑模板 ${name}`, ""); if (edited === undefined) { await notify(ctx, "已保留空模板", "info"); return; } await store.writeTemplate(name, edited); } async function editTemplate( name: string, ctx: OrderCommandContext, store: TemplateStore, clearText = false, ): Promise { validateTemplateName(name); await store.requireExists(name); const currentDescription = await store.getDescription(name); const description = await editDescription(ctx, name, currentDescription); if (description !== undefined) { await store.setDescription(name, description); } if (clearText) { await store.writeTemplate(name, ""); await notify(ctx, `已清空模板 ${name}`, "info"); return; } const action = await chooseEditTextAction(ctx, name); if (action === "clear") { await store.writeTemplate(name, ""); await notify(ctx, `已清空模板 ${name}`, "info"); return; } if (action === "cancel") { return; } const current = await store.readTemplate(name); const edited = await ctx.ui.editor(`编辑模板 ${name}`, current); if (edited !== undefined) { await store.writeTemplate(name, edited); } } async function chooseEditTextAction(ctx: OrderCommandContext, name: string): Promise<"edit" | "clear" | "cancel"> { if (!ctx.ui.select) { return "edit"; } const edit = "编辑正文"; const clear = "清空正文"; const choice = await ctx.ui.select(`编辑模板 ${name} 的正文`, [edit, clear]); if (choice === clear) { return "clear"; } if (choice === undefined) { return "cancel"; } return "edit"; } async function editDescription( ctx: OrderCommandContext, name: string, currentDescription: string, ): Promise { const title = currentDescription ? `描述模板 ${name}\n当前描述:${currentDescription}` : `描述模板 ${name}\n当前描述:(无)`; if (ctx.ui.input) { return ctx.ui.input(title, currentDescription || "可留空"); } return ctx.ui.editor(title, currentDescription); } async function sendTemplate(name: string, ctx: OrderCommandContext, store: TemplateStore): Promise { validateTemplateName(name); const content = await store.readTemplate(name); await ctx.ui.setEditorText(content); } async function removeTemplate(name: string, ctx: OrderCommandContext, store: TemplateStore): Promise { validateTemplateName(name); await store.removeTemplate(name); await notify(ctx, `已删除模板 ${name}`, "info"); } async function listTemplates(ctx: OrderCommandContext, store: TemplateStore): Promise { const names = await store.listTemplates(); if (names.length === 0) { await notify(ctx, NO_TEMPLATES_MESSAGE, "info"); return; } await notify(ctx, names.join("\n"), "info"); } type ParseResult = | { ok: true; subcommand: "list" } | { ok: true; subcommand: "edit"; name: string; clear?: boolean } | { ok: true; subcommand: Exclude; name: string } | { ok: false }; function parseArgs(args: string): ParseResult { const parts = args.trim().split(/\s+/).filter(Boolean); const [subcommand, name, ...extra] = parts; if (subcommand === "list") { return name === undefined && extra.length === 0 ? { ok: true, subcommand } : { ok: false }; } if (subcommand === "edit") { if (name === undefined) { return { ok: false }; } if (extra.length === 0) { return { ok: true, subcommand, name }; } return extra.length === 1 && extra[0] === "--clear" ? { ok: true, subcommand, name, clear: true } : { ok: false }; } if (subcommand === "create" || subcommand === "send" || subcommand === "remove") { return name !== undefined && extra.length === 0 ? { ok: true, subcommand, name } : { ok: false }; } return { ok: false }; } function templateCompletionFallback(subcommand: string): string { switch (subcommand) { case "edit": return "编辑模板"; case "remove": return "删除模板"; default: return "填入模板"; } } function errorToMessage(error: unknown): string { if (error instanceof TemplateStoreError) { return error.message; } if (error instanceof Error && error.message === INVALID_TEMPLATE_NAME_MESSAGE) { return error.message; } if (error instanceof Error && error.message === TEMPLATE_NOT_FOUND_MESSAGE) { return error.message; } return error instanceof Error ? error.message : String(error); } async function notify(ctx: OrderCommandContext, message: string, type: NotifyType): Promise { await ctx.ui.notify(message, type); }