严重 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 检查零错误,构建成功。
140 lines
3.9 KiB
TypeScript
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()
|
|
}
|
|
})
|
|
}
|