- 集成 MetonaToast v2.0.0 替换自研 Toast 组件(右上角、进度条、自动关闭、主题联动) - 修复 sidebar resize 未持久化到 IndexedDB - 修复 Ctrl+Tab MRU 切换逻辑与文档不一致 - 完善 WYSIWYG 模式标签切换选区恢复 - 清理死代码(useActiveHeading observerRef、旧 Toast 样式、重复滚动条样式、SearchReplace 重复检查) - 同步 DESIGN.md / docs 文档(CodeMirror→Milkdown、stores 计数、editorStore 字段) - 新增 .npmrc.example 模板 - 更新关于对话框与文档远程地址为 Gitea - 版本号 0.3.12 → 0.3.13
55 lines
2.2 KiB
TypeScript
55 lines
2.2 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 }
|
|
if (isCtrl && e.key === '3') { e.preventDefault(); setViewMode('source'); 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 — MRU 顺序切换
|
|
if (isCtrl && e.key === 'Tab') {
|
|
e.preventDefault()
|
|
const { tabs, activeTabId, mruStack } = tabState
|
|
if (tabs.length > 1) {
|
|
// mruStack[0] 是最近一个被切走的标签(即上一个活动标签)
|
|
if (mruStack.length > 0) {
|
|
const targetId = mruStack[0]
|
|
if (tabs.find(t => t.id === targetId)) {
|
|
tabState.switchToTab(targetId)
|
|
return
|
|
}
|
|
}
|
|
// MRU 栈为空或不合法时回退到顺序切换
|
|
const idx = tabs.findIndex(t => t.id === activeTabId)
|
|
const next = e.shiftKey
|
|
? (idx - 1 + tabs.length) % tabs.length
|
|
: (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])
|
|
}
|