Swap emoji and unicode glyphs for Tabler components so icons inherit currentColor and font-size and render consistently across platforms. - add src/components/doc-icons.ts to resolve doc-entry frontmatter icon keys to components; frontmatter now stores semantic keys - replace the align-*.png assets with TbOutlineAlign* icons - render RevealManager action SVGs as components instead of injected innerHTML strings - carry completion icons via an optional CompletionItem.icon field
76 lines
2.5 KiB
TypeScript
76 lines
2.5 KiB
TypeScript
/**
|
|
* Command parser — parse a raw input string into a typed ParsedInput.
|
|
*
|
|
* Pure function with no component dependencies, so it can live in its own
|
|
* module and keep JournalInput small.
|
|
*/
|
|
|
|
import type { IconTypes } from "solid-icons";
|
|
|
|
export interface CompletionItem {
|
|
label: string;
|
|
kind: "command" | "value" | "no-results";
|
|
insertText: string;
|
|
/** Optional icon rendered before the label in the completions dropdown. */
|
|
icon?: IconTypes;
|
|
}
|
|
|
|
export interface ParsedInput {
|
|
type: "chat" | "roll" | "link" | "set" | "rolltag";
|
|
payload: Record<string, unknown>;
|
|
error?: string;
|
|
}
|
|
|
|
export function parseInput(raw: string): ParsedInput {
|
|
if (raw.startsWith("/roll ")) {
|
|
const arg = raw.slice("/roll ".length).trim();
|
|
if (!arg)
|
|
return { type: "roll", payload: {}, error: "需要骰子表达式或种子表键名" };
|
|
return { type: "roll", payload: { arg } };
|
|
}
|
|
|
|
if (raw.startsWith("/link ")) {
|
|
const arg = raw.slice("/link ".length).trim();
|
|
if (!arg) return { type: "link", payload: {}, error: "需要路径" };
|
|
const hashIdx = arg.indexOf("#");
|
|
const path = hashIdx === -1 ? arg : arg.slice(0, hashIdx);
|
|
const section =
|
|
hashIdx === -1 ? undefined : arg.slice(hashIdx + 1) || undefined;
|
|
return { type: "link", payload: { path, section } };
|
|
}
|
|
|
|
if (raw.startsWith("/set ")) {
|
|
const rest = raw.slice("/set ".length).trim();
|
|
if (!rest)
|
|
return { type: "set", payload: {}, error: "格式: /set $key expression" };
|
|
|
|
// Split on first space to get key and expression
|
|
const spaceIdx = rest.indexOf(" ");
|
|
if (spaceIdx === -1)
|
|
return { type: "set", payload: {}, error: "格式: /set $key expression" };
|
|
|
|
const key = rest.slice(0, spaceIdx).trim();
|
|
const expr = rest.slice(spaceIdx + 1).trim();
|
|
|
|
if (!key || !expr)
|
|
return { type: "set", payload: {}, error: "key 和 expression 不能为空" };
|
|
|
|
if (!key.startsWith("$"))
|
|
return { type: "set", payload: {}, error: "key 必须以 $ 开头" };
|
|
|
|
// Check if expr is a rolltag (e.g. #w|#d|#s)
|
|
if (expr.includes("|") && expr.split("|").every((t) => t.trim().startsWith("#"))) {
|
|
const tags = expr.split("|").map((t) => t.trim());
|
|
return { type: "rolltag", payload: { key, tags } };
|
|
}
|
|
|
|
return { type: "set", payload: { key, expr } };
|
|
}
|
|
|
|
if (raw === "/roll" || raw === "/link" || raw === "/set") {
|
|
return { type: "chat", payload: {}, error: "请补全命令" };
|
|
}
|
|
|
|
return { type: "chat", payload: { text: raw } };
|
|
}
|