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:
1 parent
d096f0da70
commit
919575f3e7
7 files changed
+201
-73
No files matched your search
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user