46 lines
3.3 KiB
TypeScript
46 lines
3.3 KiB
TypeScript
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>
|
||
);
|
||
}
|