P0 致命Bug修复: - A1: openFolderDialog 类型/运行时崩溃(文件夹打开功能完全失效) - A2: SourceEditor 受控textarea手动改DOM反模式 - A3: tabStore.updateTabContent 内容相同时误标isModified 死代码清理: - B1: 删除menu:*/save/as/viewMode 三个永不触发的IPC通道 健壮性修复: - E1: rehypeFixImages 路径规范化+防越界加固 - E2: getFileName 尾斜杠返回正确文件名 - E3: EditorToolbar 行内代码按钮改用toggleInlineCodeCommand - E4: ErrorBoundary 内联样式抽为CSS类 功能增强: - D1: 标签页拖拽排序(tabStore.moveTab + TabBar DnD + CSS) - D2: Milkdown自动配对括号/引号(ProseMirror插件) - D3: 状态栏自动保存开关可点击 - D4: 文档大纲活跃标题高亮(useActiveHeading hook) - D5: 关闭窗口前flush防抖数据(flushSaveToDB) - D6: 搜索替换支持正则表达式 类型/架构: - C2: preload类型集中定义(ElectronAPI契约) - __pycache__ typo修复 文档/版本: - README/DESIGN同步为Milkdown + v0.3.10 - 项目结构树/技术栈/快捷键表更新 验证: typecheck(仅7预存), lint✅, test 96/96, vite build✅
65 lines
2.9 KiB
TypeScript
65 lines
2.9 KiB
TypeScript
import { contextBridge, ipcRenderer, shell } from 'electron'
|
|
import { IPC_CHANNELS } from '../shared/ipc-channels'
|
|
import type { ElectronAPI } from '../renderer/types/ipc'
|
|
|
|
// C-02: 运行时实现受 ElectronAPI 类型约束,编译期保证 preload 与渲染进程契约一致
|
|
const api: ElectronAPI = {
|
|
// File operations
|
|
openFile: () => ipcRenderer.invoke(IPC_CHANNELS.DIALOG_OPEN_FILE),
|
|
readFile: (filePath: string) => ipcRenderer.invoke(IPC_CHANNELS.FILE_READ, filePath),
|
|
saveFile: (data) => ipcRenderer.invoke(IPC_CHANNELS.FILE_SAVE, data),
|
|
saveFileAs: (data) => ipcRenderer.invoke(IPC_CHANNELS.FILE_SAVE_AS, data),
|
|
getCurrentPath: () => ipcRenderer.invoke(IPC_CHANNELS.FILE_GET_CURRENT_PATH),
|
|
getFileStats: (filePath: string) => ipcRenderer.invoke(IPC_CHANNELS.FILE_STATS, filePath),
|
|
reloadFile: () => ipcRenderer.invoke(IPC_CHANNELS.FILE_RELOAD),
|
|
|
|
// Tab management
|
|
tabSwitched: (filePath: string | null) => ipcRenderer.invoke(IPC_CHANNELS.TAB_SWITCHED, filePath),
|
|
|
|
// Window control
|
|
forceClose: () => ipcRenderer.invoke(IPC_CHANNELS.WINDOW_FORCE_CLOSE),
|
|
cancelClose: () => ipcRenderer.invoke(IPC_CHANNELS.WINDOW_CANCEL_CLOSE),
|
|
|
|
// Shell — 仅允许 http/https 协议
|
|
openExternal: (url: string) => {
|
|
try {
|
|
const parsed = new URL(url)
|
|
if (parsed.protocol === 'http:' || parsed.protocol === 'https:') {
|
|
shell.openExternal(url)
|
|
}
|
|
} catch {
|
|
// 无效 URL,忽略
|
|
}
|
|
},
|
|
|
|
// File Tree (Sidebar)
|
|
readDirTree: (dirPath: string) => ipcRenderer.invoke(IPC_CHANNELS.DIR_READ_TREE, dirPath),
|
|
openFolderDialog: () => ipcRenderer.invoke(IPC_CHANNELS.DIR_OPEN_DIALOG),
|
|
watchDir: (dirPath: string) => ipcRenderer.invoke(IPC_CHANNELS.DIR_WATCH, dirPath),
|
|
unwatchDir: () => ipcRenderer.invoke(IPC_CHANNELS.DIR_UNWATCH),
|
|
|
|
// Events from main process — 返回取消订阅函数
|
|
onFileOpenInTab: (callback) => {
|
|
const handler = (_event: Electron.IpcRendererEvent, data: { filePath: string; content: string }) => callback(data)
|
|
ipcRenderer.on(IPC_CHANNELS.FILE_OPEN_IN_TAB, handler)
|
|
return () => { ipcRenderer.removeListener(IPC_CHANNELS.FILE_OPEN_IN_TAB, handler) }
|
|
},
|
|
onExternalModification: (callback) => {
|
|
const handler = (_event: Electron.IpcRendererEvent, filePath: string) => callback(filePath)
|
|
ipcRenderer.on(IPC_CHANNELS.FILE_EXTERNALLY_MODIFIED, handler)
|
|
return () => { ipcRenderer.removeListener(IPC_CHANNELS.FILE_EXTERNALLY_MODIFIED, handler) }
|
|
},
|
|
onDirChanged: (callback) => {
|
|
const handler = () => callback()
|
|
ipcRenderer.on(IPC_CHANNELS.SIDEBAR_DIR_CHANGED, handler)
|
|
return () => { ipcRenderer.removeListener(IPC_CHANNELS.SIDEBAR_DIR_CHANGED, handler) }
|
|
},
|
|
onConfirmClose: (callback) => {
|
|
const handler = () => callback()
|
|
ipcRenderer.on(IPC_CHANNELS.WINDOW_CONFIRM_CLOSE, handler)
|
|
return () => { ipcRenderer.removeListener(IPC_CHANNELS.WINDOW_CONFIRM_CLOSE, handler) }
|
|
}
|
|
}
|
|
|
|
contextBridge.exposeInMainWorld('electronAPI', api)
|