主题与快捷键:透明主题真透明 + 完整 Typora 快捷键

This commit is contained in:
hunter-dev
2026-08-30 23:08:01 +08:00
parent ab46ec3cce
commit 3cc36c6450
18 changed files with 587 additions and 52 deletions
@@ -1,8 +1,14 @@
import { useEffect } from 'react';
import { useKeymap, formatCombo, type Scope } from './keymap-service';
import { useKeymap, formatCombo } from './keymap-service';
const SCOPE_LABELS: Record<Scope, string> = { global: '全局', app: '应用级', editor: '编辑器' };
const SCOPE_ORDER: Scope[] = ['global', 'app', 'editor'];
// 按 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);
@@ -12,16 +18,17 @@ export function CheatSheet({ onClose }: { onClose: () => void }) {
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: 480, 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 }}>快捷键速查</strong><button onClick={onClose} style={{ background: 'none', border: 'none', color: 'var(--text-tertiary)', cursor: 'pointer', fontSize: 16 }}>✕</button></div>
{SCOPE_ORDER.map(scope => {
const items = bindings.filter(b => b.scope === scope);
<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={scope} style={{ marginBottom: 14 }}>
<div style={{ opacity: 0.5, fontSize: 12, marginBottom: 6 }}>{SCOPE_LABELS[scope]}</div>
<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>