## 删除分屏功能 - 删除 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 字段
80 lines
2.0 KiB
TypeScript
80 lines
2.0 KiB
TypeScript
import { create } from 'zustand'
|
|
import type { FileNode } from '../types/file'
|
|
import { settingsRepository } from '../db/settingsRepository'
|
|
|
|
interface SidebarState {
|
|
isVisible: boolean
|
|
rootPath: string | null
|
|
tree: FileNode[]
|
|
expandedDirs: Set<string>
|
|
sidebarWidth: number
|
|
_loaded: boolean
|
|
|
|
setVisible: (visible: boolean) => void
|
|
setRootPath: (path: string | null) => void
|
|
setTree: (tree: FileNode[]) => void
|
|
toggleDir: (path: string) => void
|
|
expandDirs: (paths: string[]) => void
|
|
setSidebarWidth: (width: number) => void
|
|
loadFromDB: () => Promise<void>
|
|
}
|
|
|
|
export const useSidebarStore = create<SidebarState>((set, get) => ({
|
|
isVisible: true,
|
|
rootPath: null,
|
|
tree: [],
|
|
expandedDirs: new Set<string>(),
|
|
sidebarWidth: 240,
|
|
_loaded: false,
|
|
|
|
// 从 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) =>
|
|
set(state => {
|
|
const newSet = new Set(state.expandedDirs)
|
|
if (newSet.has(path)) {
|
|
newSet.delete(path)
|
|
} else {
|
|
newSet.add(path)
|
|
}
|
|
return { expandedDirs: newSet }
|
|
}),
|
|
expandDirs: (paths) =>
|
|
set(state => {
|
|
const newSet = new Set(state.expandedDirs)
|
|
let changed = false
|
|
for (const p of paths) {
|
|
if (!newSet.has(p)) {
|
|
newSet.add(p)
|
|
changed = true
|
|
}
|
|
}
|
|
return changed ? { expandedDirs: newSet } : state
|
|
}),
|
|
setSidebarWidth: (width) => {
|
|
set({ sidebarWidth: width })
|
|
// 异步保存到 DB
|
|
settingsRepository.save({ sidebarWidth: width })
|
|
}
|
|
}))
|