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:
@@ -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()
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user