Files
MyNote/apps/desktop/src/renderer/features/keymap/use-hotkeys.ts
T

76 lines
2.9 KiB
TypeScript

import { useEffect, useRef } from 'react';
import hotkeys from 'hotkeys-js';
import { useKeymap, type Binding } from './keymap-service';
// FR-K1/K3/K7:从 keymap store 读绑定,多作用域裁决,Ctrl+F 拦截宿主查找
export function useGlobalHotkeys(opts: { onNewDoc: () => void; onSave: () => void; onFocusSearch: () => void; onToggleOutline: () => void; onCheat: () => void; onSettings: () => void; onFind: () => void }) {
const optsRef = useRef(opts);
optsRef.current = opts;
const bindings = useKeymap(s => s.bindings);
const sig = bindings.map(b => b.id + '@' + b.combo).join('|');
useEffect(() => {
const actions: Record<string, () => void> = {
newDoc: () => optsRef.current.onNewDoc(),
save: () => optsRef.current.onSave(),
focusSearch: () => optsRef.current.onFocusSearch(),
toggleOutline: () => optsRef.current.onToggleOutline(),
toggleCheat: () => optsRef.current.onCheat(),
toggleSettings: () => optsRef.current.onSettings(),
find: () => optsRef.current.onFind(),
};
// 将 combo 字符串解析为可比较的修饰键+键名
const parseCombo = (combo: string) => {
const parts = combo.toLowerCase().split('+');
const key = parts[parts.length - 1];
return {
key,
ctrl: parts.includes('ctrl'),
shift: parts.includes('shift'),
alt: parts.includes('alt'),
meta: parts.includes('meta'),
};
};
const matchCombo = (e: KeyboardEvent, combo: string) => {
const c = parseCombo(combo);
return e.key.toLowerCase() === c.key &&
e.ctrlKey === c.ctrl &&
e.shiftKey === c.shift &&
e.altKey === c.alt &&
e.metaKey === c.meta;
};
// 编辑器作用域绑定——用捕获阶段 keydown,先于 ProseMirror 拦截
const editorBindings = bindings.filter(b => b.scope === 'editor' && actions[b.id]);
const onCaptureKeydown = (e: KeyboardEvent) => {
for (const b of editorBindings) {
if (matchCombo(e, b.combo)) {
e.preventDefault();
e.stopPropagation();
actions[b.id]();
return;
}
}
};
window.addEventListener('keydown', onCaptureKeydown, true);
// 全局/应用级绑定——用 hotkeys-js
const bound: string[] = [];
for (const b of bindings) {
if (b.scope === 'editor') continue; // 编辑器键已在捕获阶段处理
const act = actions[b.id];
if (act) {
hotkeys(b.combo, (e) => { e.preventDefault(); act(); });
bound.push(b.combo);
}
}
hotkeys('?', (e) => { if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') { e.preventDefault(); optsRef.current.onCheat(); } });
bound.push('?');
return () => {
window.removeEventListener('keydown', onCaptureKeydown, true);
for (const c of bound) hotkeys.unbind(c);
};
}, [sig]);
}