Files
MyNote/apps/desktop/src/renderer/features/keymap/CheatSheet.tsx
T

46 lines
3.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useEffect } from 'react';
import { useKeymap, formatCombo } from './keymap-service';
// 按 Typora 官方菜单分组的快捷键速查
const GROUPS: { label: string; ids: string[] }[] = [
{ label: '📁 文件', ids: ['newDoc', 'newWindow', 'openFile', 'quickOpen', 'save', 'saveAs', 'toggleSettings', 'closeTab', 'reopenTab'] },
{ label: '✏️ 基础编辑', ids: ['find', 'replace', 'copyMd', 'pastePlain', 'selectLine', 'selectWord', 'selectSameFormat', 'deleteWord', 'docStart', 'docEnd'] },
{ label: '📝 Markdown 格式', ids: ['bold', 'italic', 'underline', 'strike', 'inlineCode', 'link', 'insertImage', 'clearFormat'] },
{ label: '📑 段落、标题、块', ids: ['heading1', 'heading2', 'heading3', 'heading4', 'heading5', 'heading6', 'paragraph', 'headingUp', 'headingDown', 'blockquote', 'bulletList', 'orderedList', 'codeBlock', 'mathBlock', 'table', 'indent', 'outdent', 'moveUp', 'moveDown'] },
{ label: '👁️ 视图', ids: ['toggleSidebar', 'toggleOutline', 'toggleInfo', 'fileTree', 'focusMode', 'typewriter', 'fullscreen', 'focusSearch', 'toggleCheat'] },
];
export function CheatSheet({ onClose }: { onClose: () => void }) {
const bindings = useKeymap(s => s.bindings);
// FR-B6:Esc 关闭速查面板
useEffect(() => {
const onEsc = (e: KeyboardEvent) => { if (e.key === 'Escape') { e.preventDefault(); onClose(); } };
window.addEventListener('keydown', onEsc, true);
return () => window.removeEventListener('keydown', onEsc, true);
}, [onClose]);
const byId = new Map(bindings.map(b => [b.id, b] as const));
return (
<div onClick={e => { if (e.target === e.currentTarget) onClose(); }} style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.5)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 1500 }}>
<div className="panel-slide-in" style={{ width: 520, maxHeight: '80vh', overflow: 'auto', background: 'var(--bg-secondary)', border: '1px solid var(--control-border)', borderRadius: 10, padding: 20, boxShadow: '0 8px 32px rgba(0,0,0,0.5)' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 16 }}><strong style={{ fontSize: 15 }}>快捷键速查(Typora 标准)</strong><button onClick={onClose} style={{ background: 'none', border: 'none', color: 'var(--text-tertiary)', cursor: 'pointer', fontSize: 16 }}>✕</button></div>
{GROUPS.map(g => {
const items = g.ids.map(id => byId.get(id)).filter((b): b is NonNullable<typeof b> => !!b);
if (items.length === 0) return null;
return (
<div key={g.label} style={{ marginBottom: 14 }}>
<div style={{ opacity: 0.5, fontSize: 12, marginBottom: 6 }}>{g.label}</div>
{items.map(b => (
<div key={b.id} style={{ display: 'flex', justifyContent: 'space-between', padding: '3px 0', fontSize: 13 }}>
<span style={{ color: 'var(--text-primary)' }}>{b.label}</span>
<code style={{ background: 'var(--bg-secondary)', padding: '1px 6px', borderRadius: 3, color: 'var(--accent-text)' }}>{formatCombo(b.combo)}</code>
</div>
))}
</div>
);
})}
<div style={{ marginTop: 12, opacity: 0.4, fontSize: 11 }}>? 打开此面板 · Esc 关闭弹层</div>
</div>
</div>
);
}