Add command palette and command registry

- Commands (with optional shortcuts and availability checks) are registered in one place;
  global shortcuts are dispatched from it, and plugins can use registerCommand later
- The quick switcher lists commands when the input starts with '>' (VS Code style),
  opened with Ctrl+Shift+P or F1 (Firefox reserves Ctrl+Shift+P), plus buttons in the
  sidebar footer and the mobile top bar; recently used commands come first
- Built-in commands: quick open, search, new note/folder, save, rename, delete, close tab,
  trash, log out
- Mount the trash dialog at the workspace level so it is not positioned inside the
  transformed mobile drawer
This commit is contained in:
eezd committed 2026-10-17 12:14:11 +08:00
1 parent d096f0da70
commit 919575f3e7
7 files changed
+201 -73

No files matched your search

+62
View File
@@ -0,0 +1,62 @@
// 命令注册表:命令面板列出的操作与全局快捷键都来自这里;插件也通过 registerCommand 添加命令
import { shallowReactive } from 'vue'
export interface Command {
id: string
title: string
/** 快捷键,如 'Mod+Shift+F'、'F1';Mod 在 Mac 上为 Cmd,其他平台为 Ctrl。第一个用于显示 */
keys?: string[]
/** 返回 false 时命令不可用:不在面板中列出,快捷键也不触发 */
when?: () => boolean
run: () => void
}
const commands = shallowReactive(new Map<string, Command>())
/** 注册命令,返回注销函数;id 重复时后注册的覆盖先注册的 */
export function registerCommand(cmd: Command): () => void {
commands.set(cmd.id, cmd)
return () => {
if (commands.get(cmd.id) === cmd) commands.delete(cmd.id)
}
}
export function availableCommands(): Command[] {
return [...commands.values()].filter((c) => c.when?.() ?? true)
}
export function runCommand(id: string) {
const cmd = commands.get(id)
if (cmd && (cmd.when?.() ?? true)) cmd.run()
}
const isMac = /Mac|iPhone|iPad/.test(navigator.platform)
export function matchKeys(e: KeyboardEvent, keys: string): boolean {
const parts = keys.split('+')
const key = parts.pop()!.toLowerCase()
return (
e.key.toLowerCase() === key &&
(isMac ? e.metaKey : e.ctrlKey) === parts.includes('Mod') &&
e.shiftKey === parts.includes('Shift') &&
e.altKey === parts.includes('Alt')
)
}
export function formatKeys(keys: string): string {
const parts = keys.split('+')
if (isMac) return parts.map((p) => ({ Mod: '⌘', Shift: '⇧', Alt: '⌥' })[p] ?? p).join('')
return parts.map((p) => (p === 'Mod' ? 'Ctrl' : p)).join('+')
}
/** 全局快捷键分发;编辑器等已处理的按键(defaultPrevented)和输入法组字中的按键不处理 */
export function handleShortcut(e: KeyboardEvent) {
if (e.defaultPrevented || e.isComposing) return
for (const cmd of availableCommands()) {
if (cmd.keys?.some((k) => matchKeys(e, k))) {
e.preventDefault()
cmd.run()
return
}
}
}