/** * preload.js — contextBridge 暴露给渲染进程的安全 IPC API */ 'use strict'; const { contextBridge, ipcRenderer } = require('electron'); contextBridge.exposeInMainWorld('api', { // 数据查询(列表不含图片数据,图片走 getImage 按需拉)。 // limit 留空 → 主进程用配置里的 displayLimit(默认 300),改设置后下次拉就生效。 list: (query, limit) => ipcRenderer.invoke('clips:list', { query: query || '', limit: limit || 0 }), getImage: (id) => ipcRenderer.invoke('clips:get-image', { id }), count: () => ipcRenderer.invoke('clips:count'), // 操作 movetop: (id) => ipcRenderer.invoke('clips:movetop', { id }), remove: (id) => ipcRenderer.invoke('clips:delete', { id }), hide: () => ipcRenderer.invoke('window:hide'), openSettings: () => ipcRenderer.invoke('settings:open'), // 窗口置顶 toggleAlwaysOnTop: (on) => ipcRenderer.invoke('window:toggle-always-on-top', { on }), getAlwaysOnTop: () => ipcRenderer.invoke('window:get-always-on-top'), // 应用图标(标题栏左上角小图标):返回 PNG data URL,喂给 。 // 主进程会缓存 .ico→PNG 的转换结果,每个窗口首次调用一次即可。 getAppIcon: () => ipcRenderer.invoke('app:get-icon'), // 订阅 subscribe: () => ipcRenderer.invoke('clips:subscribe'), onAdded: (cb) => { const handler = (_e, clip) => cb(clip); ipcRenderer.on('clips:added', handler); return () => ipcRenderer.removeListener('clips:added', handler); }, onRefresh: (cb) => { const handler = () => cb(); ipcRenderer.on('clips:refresh', handler); return () => ipcRenderer.removeListener('clips:refresh', handler); }, }); contextBridge.exposeInMainWorld('favoritesApi', { add: (clipId) => ipcRenderer.invoke('favorites:add', { clipId }), remove: (favId) => ipcRenderer.invoke('favorites:remove', { favId }), // 收藏 Tab 的复制:写收藏自身存的内容(favId 与历史 id 不同库,不能走 movetop) copy: (favId) => ipcRenderer.invoke('favorites:copy', { favId }), list: (query, limit) => ipcRenderer.invoke('favorites:list', { query: query || '', limit: limit || 0 }), getImage: (favId) => ipcRenderer.invoke('favorites:get-image', { favId }), count: () => ipcRenderer.invoke('favorites:count'), subscribe: () => ipcRenderer.invoke('favorites:subscribe'), onChanged: (cb) => { const handler = () => cb(); ipcRenderer.on('favorites:changed', handler); return () => ipcRenderer.removeListener('favorites:changed', handler); }, }); contextBridge.exposeInMainWorld('settingsApi', { get: () => ipcRenderer.invoke('settings:get'), set: (key, value) => ipcRenderer.invoke('settings:set', { key, value }), clearHistory: () => ipcRenderer.invoke('settings:clear-history'), openDir: () => ipcRenderer.invoke('settings:open-dir'), chooseDir: () => ipcRenderer.invoke('settings:choose-dir'), // 外链交给系统浏览器(关于页)。scheme 白名单在主进程卡,渲染进程只管递 URL openExternal: (url) => ipcRenderer.invoke('settings:open-external', { url }), // 设置窗口订阅 displayLimit 变更,主进程设置被改后给所有窗口推一条刷新通知 onDisplayLimitChanged: (cb) => { const handler = (_e, payload) => cb(payload); ipcRenderer.on('settings:display-limit-changed', handler); return () => ipcRenderer.removeListener('settings:display-limit-changed', handler); }, }); contextBridge.exposeInMainWorld('themeApi', { get: () => ipcRenderer.invoke('theme:get'), set: (theme) => ipcRenderer.invoke('theme:set', { theme }), setAccent: (accent) => ipcRenderer.invoke('theme:set-accent', { accent }), setFollowSystem: (followSystem)=> ipcRenderer.invoke('theme:set-follow-system', { followSystem }), // 必须先订阅,主进程才会把 theme:system-changed 发过来 subscribe: () => ipcRenderer.invoke('theme:subscribe'), onSystemChanged: (cb) => { const handler = (_e, payload) => cb(payload); ipcRenderer.on('theme:system-changed', handler); return () => ipcRenderer.removeListener('theme:system-changed', handler); }, });