refactor: 删除分屏功能 + 代码审计修复 + 安全加固

## 删除分屏功能
- 删除 ViewMode 'split' 类型,仅保留 editor/preview
- 删除 splitRatio 状态和 Resizer 组件
- 删除滚动同步模块 (scrollSync.ts, rehypeSourceLine.ts)
- 更新快捷键: Ctrl+1 编辑, Ctrl+2 预览

## Bug 修复
- 修复 Toast setTimeout 内存泄漏 (App.tsx)
- 修复 ModifiedBanner reload 更新错误标签 (改用 modifiedFilePath 匹配)
- 修复 FileWatcher error 未重置 isSelfWriting (file-watcher.ts)
- 修复另存为时未 stop watcher (ipc-handlers.ts)

## 死代码清理 (10 项)
- 删除 main/ipc-channels.ts (与 shared/ 重复)
- 删除 main/file-system.ts 未使用的 formatBytes
- 删除 constants.ts 6 个未使用常量
- 删除 fileUtils.ts 未使用的 formatBytes
- 删除 Icons.tsx 5 个未使用图标 (SplitView/ChevronUp/ChevronDown/X/ArrowUp/ArrowDown)
- 删除 useCodeMirror 未使用的 getContent/scrollTo
- 删除 TabBar 未使用的 menuRef
- 删除 Tab.scrollLeft/previewScrollTop 字段
- 删除 tabStore 未使用的 getTabIndex
- 删除 Toast/ModifiedBanner 多余 React import

## 安全加固
- ipc-handlers: 添加路径遍历防护 (validatePath 函数)
- preload: openExternal 仅允许 http/https 协议
- window-manager: 启用 sandbox: true
- preload: removeAllListeners 改为精确取消订阅 (返回 Unsubscribe 函数)

## 状态持久化
- activeTabId 持久化到 IndexedDB (刷新后恢复正确标签)
- sidebar 状态持久化到 IndexedDB (isVisible/sidebarWidth)

## 代码优化
- preload 使用 IPC_CHANNELS 常量替代硬编码字符串
- ipc-handlers _event 类型改为 IpcMainInvokeEvent
- settingsRepository 删除 splitRatio 字段
This commit is contained in:
thzxx
2026-05-28 13:42:11 +08:00
parent 3c6e4ac5ce
commit 9c92dcfa9d
37 changed files with 242 additions and 655 deletions
+30 -3
View File
@@ -1,5 +1,6 @@
import { create } from 'zustand'
import type { FileNode } from '../types/file'
import { settingsRepository } from '../db/settingsRepository'
interface SidebarState {
isVisible: boolean
@@ -7,6 +8,7 @@ interface SidebarState {
tree: FileNode[]
expandedDirs: Set<string>
sidebarWidth: number
_loaded: boolean
setVisible: (visible: boolean) => void
setRootPath: (path: string | null) => void
@@ -14,16 +16,37 @@ interface SidebarState {
toggleDir: (path: string) => void
expandDirs: (paths: string[]) => void
setSidebarWidth: (width: number) => void
loadFromDB: () => Promise<void>
}
export const useSidebarStore = create<SidebarState>((set) => ({
export const useSidebarStore = create<SidebarState>((set, get) => ({
isVisible: true,
rootPath: null,
tree: [],
expandedDirs: new Set<string>(),
sidebarWidth: 240,
_loaded: false,
setVisible: (visible) => set({ isVisible: visible }),
// 从 IndexedDB 加载 sidebar 设置
loadFromDB: async () => {
if (get()._loaded) return
try {
const settings = await settingsRepository.load()
set({
isVisible: !settings.sidebarCollapsed,
sidebarWidth: settings.sidebarWidth,
_loaded: true
})
} catch {
set({ _loaded: true })
}
},
setVisible: (visible) => {
set({ isVisible: visible })
// 异步保存到 DB
settingsRepository.save({ sidebarCollapsed: !visible })
},
setRootPath: (path) => set({ rootPath: path }),
setTree: (tree) => set({ tree }),
toggleDir: (path) =>
@@ -48,5 +71,9 @@ export const useSidebarStore = create<SidebarState>((set) => ({
}
return changed ? { expandedDirs: newSet } : state
}),
setSidebarWidth: (width) => set({ sidebarWidth: width })
setSidebarWidth: (width) => {
set({ sidebarWidth: width })
// 异步保存到 DB
settingsRepository.save({ sidebarWidth: width })
}
}))