feat: 笔记独立窗口改为多标签,AI 多轮会话与 TXT/MD 阅读
笔记独立窗口从「一窗一条」改为单窗口多标签,与阅读器一致: 标签集在主进程侧为权威,notes:tabsChanged 只能收窄不能新增, 否则渲染层可以谎报持有某条笔记来越权读取。存活编辑器上限 3 并 LRU 回收,回收前序列化未保存内容。笔记没有自动保存,关标签与 关窗都做二次确认,取消关闭必须回报主进程复位 closePending, 否则窗口再也关不掉而看门狗仍会销毁未保存内容。 AI 助手支持多轮会话:会话独立落盘,先取历史再写提问, 历史只发文本不重发图像,失败与取消都保留已流出的残片。 新增 TXT/MD 内置阅读(转内存 EPUB 复用 epub 渲染管线), 补上渲染层遗漏的可阅读格式白名单:主进程本就放行 txt/md, 但渲染层另有两份白名单漏了,表现为卡片上没有「阅读」按钮。 书库卡片封面改用 contain 完整显示,留白由同图模糊层垫底, 修正不同比例封面被裁切程度不一导致的观感不一致;多选复选框 去掉衬底色块,恢复原生外观。 其余:PDF 画质档位与画布尺寸钳制、原子写入、笔记资源托管、 GitHub Pages 站点。
This commit is contained in:
+43
@@ -58,7 +58,9 @@ contextBridge.exposeInMainWorld('api', {
|
||||
findBySource: (sourceId, postId) => ipcRenderer.invoke('library:findBySource', sourceId, postId),
|
||||
add: (item) => ipcRenderer.invoke('library:add', item),
|
||||
update: (id, patch) => ipcRenderer.invoke('library:update', id, patch),
|
||||
updateMany: (patches) => ipcRenderer.invoke('library:updateMany', patches),
|
||||
remove: (id, options) => ipcRenderer.invoke('library:remove', id, options),
|
||||
removeMany: (ids, options) => ipcRenderer.invoke('library:removeMany', ids, options),
|
||||
getDir: () => ipcRenderer.invoke('library:getDir'),
|
||||
pickDir: () => ipcRenderer.invoke('library:pickDir'),
|
||||
setDir: (dir, migrate) => ipcRenderer.invoke('library:setDir', dir, migrate),
|
||||
@@ -103,6 +105,7 @@ contextBridge.exposeInMainWorld('api', {
|
||||
},
|
||||
reader: {
|
||||
ready: () => ipcRenderer.invoke('reader:ready'),
|
||||
entryClosed: () => ipcRenderer.invoke('reader:entryClosed'),
|
||||
open: (entryId, fileIndex) => ipcRenderer.invoke('reader:open', entryId, fileIndex),
|
||||
openAt: (entryId, fileIndex, documentKey, locator) => (
|
||||
ipcRenderer.invoke('reader:openAt', entryId, fileIndex, documentKey, locator)
|
||||
@@ -130,6 +133,9 @@ contextBridge.exposeInMainWorld('api', {
|
||||
removeNote: (entryId, noteId) => ipcRenderer.invoke('reader:removeNote', entryId, noteId),
|
||||
listNotes: (filters) => ipcRenderer.invoke('reader:listNotes', filters),
|
||||
getNoteCounts: () => ipcRenderer.invoke('reader:getNoteCounts'),
|
||||
getAnnotationCounts: () => ipcRenderer.invoke('reader:getAnnotationCounts'),
|
||||
orphanReport: () => ipcRenderer.invoke('reader:orphanReport'),
|
||||
purgeOrphans: (scope) => ipcRenderer.invoke('reader:purgeOrphans', scope),
|
||||
listCollections: () => ipcRenderer.invoke('reader:listCollections'),
|
||||
addCollection: (input) => ipcRenderer.invoke('reader:addCollection', input),
|
||||
updateCollection: (id, patch) => ipcRenderer.invoke('reader:updateCollection', id, patch),
|
||||
@@ -173,12 +179,49 @@ contextBridge.exposeInMainWorld('api', {
|
||||
return () => ipcRenderer.removeListener('reader:notesChanged', h);
|
||||
}
|
||||
},
|
||||
notes: {
|
||||
openWindow: (entryId, noteId) => ipcRenderer.invoke('notes:openWindow', entryId, noteId),
|
||||
getOne: (entryId, noteId) => ipcRenderer.invoke('notes:getOne', entryId, noteId),
|
||||
openWindows: () => ipcRenderer.invoke('notes:openWindows'),
|
||||
tabsChanged: (tabs) => ipcRenderer.invoke('notes:tabsChanged', tabs),
|
||||
shutdownReady: () => ipcRenderer.invoke('notes:shutdownReady'),
|
||||
cancelClose: () => ipcRenderer.invoke('notes:cancelClose'),
|
||||
onWindowsChanged: (cb) => {
|
||||
const h = (_e, data) => cb(data);
|
||||
ipcRenderer.on('notes:windowsChanged', h);
|
||||
return () => ipcRenderer.removeListener('notes:windowsChanged', h);
|
||||
},
|
||||
onOpenTab: (cb) => {
|
||||
const h = (_e, data) => cb(data);
|
||||
ipcRenderer.on('notes:openTab', h);
|
||||
return () => ipcRenderer.removeListener('notes:openTab', h);
|
||||
},
|
||||
onCloseTab: (cb) => {
|
||||
const h = (_e, data) => cb(data);
|
||||
ipcRenderer.on('notes:closeTab', h);
|
||||
return () => ipcRenderer.removeListener('notes:closeTab', h);
|
||||
},
|
||||
onPrepareClose: (cb) => {
|
||||
const h = () => cb();
|
||||
ipcRenderer.on('notes:prepareClose', h);
|
||||
return () => ipcRenderer.removeListener('notes:prepareClose', h);
|
||||
}
|
||||
},
|
||||
ai: {
|
||||
status: () => ipcRenderer.invoke('ai:status'),
|
||||
save: (cfg) => ipcRenderer.invoke('ai:save', cfg),
|
||||
clear: () => ipcRenderer.invoke('ai:clear'),
|
||||
run: (payload) => ipcRenderer.invoke('ai:run', payload),
|
||||
cancel: (runId) => ipcRenderer.invoke('ai:cancel', runId),
|
||||
sessions: {
|
||||
list: (filters) => ipcRenderer.invoke('ai:sessionList', filters),
|
||||
create: (input) => ipcRenderer.invoke('ai:sessionCreate', input),
|
||||
rename: (sessionId, title) => ipcRenderer.invoke('ai:sessionRename', sessionId, title),
|
||||
setPinned: (sessionId, pinned) => ipcRenderer.invoke('ai:sessionPin', sessionId, pinned),
|
||||
remove: (sessionId) => ipcRenderer.invoke('ai:sessionRemove', sessionId),
|
||||
clear: (sessionId) => ipcRenderer.invoke('ai:sessionClear', sessionId),
|
||||
messages: (sessionId, options) => ipcRenderer.invoke('ai:sessionMessages', sessionId, options)
|
||||
},
|
||||
onChanged: (cb) => {
|
||||
const h = (_e, data) => cb(data);
|
||||
ipcRenderer.on('ai:changed', h);
|
||||
|
||||
Reference in New Issue
Block a user