refactor: v2.0 全量重构 — TypeScript + React + Zustand + IndexedDB

技术栈升级:
- JavaScript → TypeScript 5.6(全量类型安全)
- 原生 DOM → React 18(函数组件 + Hooks)
- 全局变量 → Zustand 5(轻量状态管理)
- localStorage → IndexedDB / Dexie.js 4(大容量、异步、索引)
- marked.js → unified / remark / rehype(插件化渲染管线)
- 无打包 → electron-vite 3(HMR 热更新)
- 纯 CSS → CSS Variables + CSS Modules

新增功能:
- 标签页状态 IndexedDB 持久化(关闭后可恢复)
- 最近打开文件列表
- 大文件虚拟化行号(>2000 行)
- 搜索高亮二分查找优化 O(log N)
- rehype-sanitize HTML 安全过滤

文件结构:
- 7 个源文件 → 51 个模块化文件
- src/main/     主进程(6 文件)
- src/preload/  预加载(1 文件)
- src/renderer/ 渲染进程(42 文件:组件/hooks/stores/lib/db/types/styles)
- src/shared/   共享类型(2 文件)

构建验证:
- TypeScript 检查零错误
- electron-vite build 成功
- 产物:main 15kB + preload 2kB + renderer 1.5MB
This commit is contained in:
thzxx
2026-05-27 20:29:23 +08:00
parent c2cdba546b
commit 5e1c89d280
59 changed files with 4674 additions and 314 deletions
+129
View File
@@ -0,0 +1,129 @@
import { app, BrowserWindow } from 'electron'
import { join } from 'path'
import { createWindow, setupSingleInstanceLock, getFilePathFromArgs } from './window-manager'
import { FileWatcher, SidebarWatcher } from './file-watcher'
import { registerIpcHandlers } from './ipc-handlers'
import { readFileContent } from './file-system'
let mainWindow: BrowserWindow | null = null
const state = {
activeFilePath: null as string | null,
pendingFilePath: null as string | null,
isClosing: false,
closeTimeout: null as NodeJS.Timeout | null
}
const fileWatcher = new FileWatcher(() => mainWindow)
const sidebarWatcher = new SidebarWatcher(() => mainWindow)
function openFileInTab(filePath: string): void {
if (!mainWindow || mainWindow.isDestroyed()) return
readFileContent(filePath).then(result => {
if (result.success) {
state.activeFilePath = filePath
fileWatcher.start(filePath)
mainWindow!.setTitle(`MarkLite - ${filePath.split(/[/\\]/).pop()}`)
mainWindow!.webContents.send('file:openInTab', { filePath, content: result.content })
}
})
}
// 单实例锁
const lockOk = setupSingleInstanceLock((filePath) => {
if (mainWindow && !mainWindow.isDestroyed()) {
if (mainWindow.isMinimized()) mainWindow.restore()
mainWindow.focus()
if (filePath) openFileInTab(filePath)
}
})
if (!lockOk) {
app.quit()
} else {
function setupCloseHandler(): void {
if (!mainWindow) return
mainWindow.on('close', (e) => {
if (state.isClosing) return
state.isClosing = true
e.preventDefault()
try {
if (mainWindow && !mainWindow.webContents.isDestroyed()) {
mainWindow.webContents.send('window:confirmClose')
} else {
mainWindow?.removeAllListeners('close')
mainWindow?.close()
return
}
} catch {
mainWindow?.removeAllListeners('close')
mainWindow?.close()
return
}
state.closeTimeout = setTimeout(() => {
state.closeTimeout = null
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.removeAllListeners('close')
mainWindow.close()
}
}, 5000)
})
}
app.whenReady().then(() => {
app.on('open-file', (event, filePath) => {
event.preventDefault()
if (mainWindow && !mainWindow.isDestroyed() && mainWindow.webContents.isLoading()) {
state.pendingFilePath = filePath
} else if (mainWindow && !mainWindow.isDestroyed()) {
openFileInTab(filePath)
} else {
state.pendingFilePath = filePath
}
})
mainWindow = createWindow()
// 注册 IPC 处理器
registerIpcHandlers(() => mainWindow, fileWatcher, sidebarWatcher, state)
// 加载渲染进程页面
if (process.env.ELECTRON_RENDERER_URL) {
mainWindow.loadURL(process.env.ELECTRON_RENDERER_URL)
} else {
mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
}
mainWindow.webContents.on('did-finish-load', () => {
if (state.pendingFilePath) {
openFileInTab(state.pendingFilePath)
state.pendingFilePath = null
}
})
setupCloseHandler()
mainWindow.on('closed', () => {
fileWatcher.stop()
sidebarWatcher.stop()
mainWindow = null
})
// 命令行文件
const cmdFile = getFilePathFromArgs(process.argv)
if (cmdFile) {
state.pendingFilePath = cmdFile
}
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
mainWindow = createWindow()
setupCloseHandler()
}
})
}