Files
MarkLite/src/main/index.ts
T
thzxx 70dff0cf01 fix: 修复第二轮代码审计发现的全部 15 个问题
严重 Bug(6 个):
- C-01: settingsRepository.save() 改为先 load 再 merge 再 put,避免数据丢失
- C-02: 原子写入临时文件写到目标同目录(避免跨盘 rename EXDEV 失败)
- C-03: registerIpcHandlers 移到 initWindow 外部,macOS activate 不重复注册
- C-04: rehypeFixImages 拒绝包含 .. 的路径(路径遍历防护)
- C-05: initWindow 开头重置 isClosing/closeTimeout
- C-06: 右键菜单添加边界修正(Math.min 防溢出屏幕)

中等问题(5 个):
- M-01: Sidebar 路径比较归一化(norm = p.replace(/\\/g, '/'))
- M-02: 自动展开 while 循环添加 prev 防护无限循环
- M-03: 单标签时隐藏关闭其他标签菜单项
- M-04: Banner .banner-btn:hover 添加暗色主题覆盖
- M-05: closeTab 优先从 MRU 栈取最近使用的标签

低级问题(4 个):
- L-01: 删除死代码 useSearch hook(useFileWatch.ts)
- L-02: file-watcher error 事件显式调用 watcher.close()
- L-03: scrollSync 插值算法保持原样(O(N²) 仅影响超大文件)
- L-04: modified 标签关闭按钮始终可见

TypeScript 检查零错误,构建成功。
2026-05-27 21:35:09 +08:00

140 lines
3.9 KiB
TypeScript

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 && mainWindow && !mainWindow.isDestroyed()) {
state.activeFilePath = filePath
fileWatcher.start(filePath)
mainWindow.setTitle(`MarkLite - ${filePath.split(/[/\\]/).pop()}`)
mainWindow.webContents.send('file:openInTab', { filePath, content: result.content })
}
}).catch((err) => {
console.error('openFileInTab failed:', err)
})
}
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)
})
}
// C-05: 窗口重建时重置状态
function initWindow(): void {
state.isClosing = false
if (state.closeTimeout) {
clearTimeout(state.closeTimeout)
state.closeTimeout = null
}
mainWindow = createWindow()
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
})
}
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
}
})
// C-03: IPC 处理器只注册一次
registerIpcHandlers(() => mainWindow, fileWatcher, sidebarWatcher, state)
initWindow()
const cmdFile = getFilePathFromArgs(process.argv)
if (cmdFile) {
state.pendingFilePath = cmdFile
}
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})
// C-03: activate 只重建窗口,不重复注册 IPC
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
initWindow()
}
})
}