- ESLint flat config + Prettier + EditorConfig - Markdown处理器LRU缓存 - Zustand选择器优化减少重渲染 - CodeMirror Compartment主题热切换 - Preview防抖(150ms) + IndexedDB去抖(500ms) - 组件拆分: App.tsx 310→104行, Sidebar.tsx 244→90行 - 统一错误处理 errorHandler.ts - ConfirmDialog替代原生confirm - LoadingSpinner加载状态 - Toast多条堆叠+类型区分 - 可访问性增强(ARIA属性、键盘导航) - Vitest测试框架(78个测试用例) - Git hooks(husky + lint-staged) - 项目文档(README.md, CONTRIBUTING.md)
52 lines
2.0 KiB
TypeScript
52 lines
2.0 KiB
TypeScript
import { useEffect, useCallback } from 'react'
|
|
import { useTabStore } from '../stores/tabStore'
|
|
import { useEditorStore } from '../stores/editorStore'
|
|
|
|
export function useKeyboard(handleOpenFile: () => void, handleSave: () => void, handleSaveAs: () => void) {
|
|
const setViewMode = useEditorStore(s => s.setViewMode)
|
|
|
|
const handleKeydown = useCallback((e: KeyboardEvent) => {
|
|
const isCtrl = e.ctrlKey || e.metaKey
|
|
|
|
if (isCtrl && e.key === 'o') { e.preventDefault(); handleOpenFile(); return }
|
|
if (isCtrl && e.key === 's' && !e.shiftKey) { e.preventDefault(); handleSave(); return }
|
|
if (isCtrl && e.shiftKey && e.key === 'S') { e.preventDefault(); handleSaveAs(); return }
|
|
if (isCtrl && e.key === '1') { e.preventDefault(); setViewMode('editor'); return }
|
|
if (isCtrl && e.key === '2') { e.preventDefault(); setViewMode('preview'); return }
|
|
|
|
const tabState = useTabStore.getState()
|
|
if (isCtrl && e.key === 't') { e.preventDefault(); tabState.createTab(null, ''); return }
|
|
if (isCtrl && e.key === 'w') {
|
|
e.preventDefault()
|
|
if (tabState.activeTabId) tabState.closeTab(tabState.activeTabId)
|
|
return
|
|
}
|
|
|
|
// Ctrl+Tab / Ctrl+Shift+Tab
|
|
if (isCtrl && e.key === 'Tab') {
|
|
e.preventDefault()
|
|
const { tabs, activeTabId, mruStack } = tabState
|
|
if (tabs.length > 1) {
|
|
if (e.shiftKey) {
|
|
if (mruStack.length > 0) {
|
|
const targetId = mruStack[0]
|
|
if (tabs.find(t => t.id === targetId)) {
|
|
tabState.switchToTab(targetId)
|
|
}
|
|
}
|
|
} else {
|
|
const idx = tabs.findIndex(t => t.id === activeTabId)
|
|
const next = (idx + 1) % tabs.length
|
|
tabState.switchToTab(tabs[next].id)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
}, [handleOpenFile, handleSave, handleSaveAs, setViewMode])
|
|
|
|
useEffect(() => {
|
|
document.addEventListener('keydown', handleKeydown)
|
|
return () => document.removeEventListener('keydown', handleKeydown)
|
|
}, [handleKeydown])
|
|
}
|