This commit is contained in:
2026-09-12 13:55:57 +08:00
commit 30f1cedd39
95 changed files with 42057 additions and 0 deletions

105
preload.js Normal file
View File

@@ -0,0 +1,105 @@
// 安全的 IPC 桥
// 通过 contextBridge 暴露最小化的 API 给渲染进程
const { contextBridge, ipcRenderer } = require('electron');
// 渲染端的 dev 模式开关 —— preload 跑在 Node 上下文,可以同步读 process.env / process.argv。
// 与 main.js:416 的 dev 检测同款条件NODE_ENV=development 或 --dev 参数),保持单一事实来源。
// renderer 用它开关 [sync] 控制台日志dev 模式打印、生产静默),不影响功能。
const IS_DEV = process.env.NODE_ENV === 'development' || process.argv.includes('--dev');
contextBridge.exposeInMainWorld('api', {
// 同步字段:渲染端在打 [sync] 日志前用 window.api.isDev 判断,避免生产环境控制台噪音
isDev: IS_DEV,
// 文件操作
// 注:不暴露「打开文件对话框」—— UI 上已去掉新建/打开文件的入口,
// 文件位置只通过设置里的数据目录决定
saveFileDialog: () => ipcRenderer.invoke('file:save-dialog'),
readFile: (filePath) => ipcRenderer.invoke('file:read', filePath),
// options.force = true 跳过主进程的"防覆盖外部修改"mtime 检查,
// 用于用户已明确同意覆盖外部编辑的场景("保留本地" 按钮 / 手动 Ctrl+S
// 自动保存默认 force=false被主进程拒绝时返回 EXTERNAL_CHANGE_DETECTED
// 由渲染端走冲突解决流程。
writeFile: (filePath, content, options = {}) =>
ipcRenderer.invoke('file:write', { filePath, content, options }),
fileExists: (filePath) => ipcRenderer.invoke('file:exists', filePath),
createFileIfMissing: (filePath, content) => ipcRenderer.invoke('file:create-if-missing', { filePath, content }),
createSnapshot: (filePath, label) => ipcRenderer.invoke('file:create-snapshot', { filePath, label }),
// 应用信息
getDataDir: () => ipcRenderer.invoke('app:get-data-dir'),
getDefaultDataDir: () => ipcRenderer.invoke('app:get-default-data-dir'),
// 用户主目录 —— renderer 用来把绝对路径转成 `~/...` 显示形式。
// 跟 getDataDir / getDefaultDataDir 配套(都是路径信息,权限边界一致)。
getHomeDir: () => ipcRenderer.invoke('app:get-home-dir'),
getVersion: () => ipcRenderer.invoke('app:get-version'),
getElectronVersion: () => ipcRenderer.invoke('app:get-electron-version'),
openDataDir: () => ipcRenderer.invoke('app:open-data-dir'),
// 打开外部链接(设置面板「关于」区的开发者主页链接用)
// 主进程端做协议白名单(仅 http / https防 file: / javascript: 等危险协议
openExternal: (url) => ipcRenderer.invoke('app:open-external', url),
// 设置
getSettings: () => ipcRenderer.invoke('app:get-settings'),
saveSettings: (partial) => ipcRenderer.invoke('app:save-settings', partial),
chooseDataDir: () => ipcRenderer.invoke('app:choose-data-dir'),
// 退出前保存:通知主进程 dirty 状态变化、接收刷写指令
notifyDirtyChanged: (isDirty) => ipcRenderer.send('app:dirty-changed', isDirty),
notifyFlushDone: () => ipcRenderer.send('app:flush-done'),
onFlushPendingSave: (callback) => {
const handler = () => callback();
ipcRenderer.on('app:flush-pending-save', handler);
return () => ipcRenderer.removeListener('app:flush-pending-save', handler);
},
// 事件监听(来自主进程)
// 路由表:主进程菜单 / 快捷键 → 渲染端处理器。新增菜单项时**必须**同步加这里,
// 否则 main.js 发了 IPC 渲染端却没人监听菜单点击会静默无效v4 调试记录)。
onMenuCommand: (callback) => {
const events = ['menu:save', 'menu:reload', 'menu:toggle-theme', 'menu:settings'];
const handlers = events.map(name => {
const handler = () => callback(name);
ipcRenderer.on(name, handler);
return { name, handler };
});
return () => handlers.forEach(({ name, handler }) => ipcRenderer.removeListener(name, handler));
},
onFileExternalChange: (callback) => {
// 载荷从 stringfilePath升级为 {filePath, diskContent}v3.4+ 增强)
const handler = (_event, payload) => callback(payload);
ipcRenderer.on('file:external-change', handler);
return () => ipcRenderer.removeListener('file:external-change', handler);
},
// 窗口控制
// setAlwaysOnTop立即应用置顶状态不落盘由调用方走 saveSettings 持久化)。
// 返回 boolean 表示是否应用成功——主进程在窗口不存在/被销毁时返回 false。
setAlwaysOnTop: (enabled) => ipcRenderer.invoke('window:set-always-on-top', enabled),
// 始终置顶状态变更通知(来自主进程,比如托盘菜单点击),
// toolbar.js 订阅这个事件以同步按钮的激活态。
onAlwaysOnTopChanged: (callback) => {
const handler = (_event, enabled) => callback(enabled);
ipcRenderer.on('always-on-top:changed', handler);
return () => ipcRenderer.removeListener('always-on-top:changed', handler);
},
// 自定义窗口控制frame: false 后接管系统标题栏)
// minimizeWindow最小化到任务栏
minimizeWindow: () => ipcRenderer.invoke('window:minimize'),
// toggleMaximizeWindow在最大化/还原之间切换,返回新的 isMaximized 状态,
// renderer 拿到返回值后只负责把按钮图标刷成对应样式,避免在 renderer 端维护状态副本。
toggleMaximizeWindow: () => ipcRenderer.invoke('window:toggle-maximize'),
// closeWindow与窗口右上角 X 同义 —— 默认隐藏到托盘(除非用户主动退出)
closeWindow: () => ipcRenderer.invoke('window:close'),
// 窗口最大化状态变化通知(来自主进程 maximize / unmaximize / ready-to-show
// toolbar.js 订阅这个事件以同步按钮图标(最大化态 ↔ 还原态)。
onMaximizeStateChanged: (callback) => {
const handler = (_event, isMaximized) => callback(isMaximized);
ipcRenderer.on('window:maximize-state', handler);
return () => ipcRenderer.removeListener('window:maximize-state', handler);
}
});