256 lines
12 KiB
JavaScript
256 lines
12 KiB
JavaScript
const { contextBridge, ipcRenderer } = require('electron');
|
|
|
|
let downloadSeq = 0;
|
|
function runDownload(requestId, url, suggestName, entryId, extraHeaders, meta, onProgress) {
|
|
const listener = (_event, data) => {
|
|
if (!data || data.requestId !== requestId || typeof onProgress !== 'function') return;
|
|
try { onProgress(data); } catch (e) { /* 渲染层进度回调异常不影响下载 */ }
|
|
};
|
|
if (typeof onProgress === 'function') ipcRenderer.on('download:progress', listener);
|
|
return ipcRenderer
|
|
.invoke('download:file', url, suggestName, entryId, extraHeaders, meta, requestId)
|
|
.finally(() => ipcRenderer.removeListener('download:progress', listener));
|
|
}
|
|
|
|
function downloadFile(url, suggestName, entryId, extraHeaders, meta, onProgress) {
|
|
const requestId = `dl_${Date.now().toString(36)}_${(++downloadSeq).toString(36)}`;
|
|
return runDownload(requestId, url, suggestName, entryId, extraHeaders, meta, onProgress);
|
|
}
|
|
|
|
function captureReaderRect(rect) {
|
|
const value = rect && typeof rect === 'object' ? rect : {};
|
|
const area = {
|
|
x: Number(value.x),
|
|
y: Number(value.y),
|
|
width: Number(value.width),
|
|
height: Number(value.height)
|
|
};
|
|
const documentArea = document.getElementById('docArea');
|
|
const bounds = documentArea && documentArea.getBoundingClientRect();
|
|
if (
|
|
!bounds
|
|
|| !Object.values(area).every(Number.isFinite)
|
|
|| area.x < bounds.left - 1
|
|
|| area.y < bounds.top - 1
|
|
|| area.x + area.width > bounds.right + 1
|
|
|| area.y + area.height > bounds.bottom + 1
|
|
) {
|
|
return Promise.resolve({ ok: false, error: '只能截取阅读正文区域' });
|
|
}
|
|
return ipcRenderer.invoke('reader:captureRect', area);
|
|
}
|
|
|
|
contextBridge.exposeInMainWorld('api', {
|
|
sources: {
|
|
list: () => ipcRenderer.invoke('sources:list'),
|
|
browse: (sourceId, page) => ipcRenderer.invoke('source:list', sourceId, page),
|
|
search: (sourceId, keyword, page) => ipcRenderer.invoke('source:search', sourceId, keyword, page),
|
|
detail: (sourceId, postId) => ipcRenderer.invoke('source:detail', sourceId, postId),
|
|
download: (sourceId, postId) => ipcRenderer.invoke('source:download', sourceId, postId)
|
|
},
|
|
library: {
|
|
list: () => ipcRenderer.invoke('library:list'),
|
|
get: (id) => ipcRenderer.invoke('library:get', id),
|
|
listShelves: () => ipcRenderer.invoke('library:listShelves'),
|
|
listTags: () => ipcRenderer.invoke('library:listTags'),
|
|
addShelf: (input) => ipcRenderer.invoke('library:addShelf', input),
|
|
updateShelf: (id, patch) => ipcRenderer.invoke('library:updateShelf', id, patch),
|
|
removeShelf: (id) => ipcRenderer.invoke('library:removeShelf', id),
|
|
addTag: (input) => ipcRenderer.invoke('library:addTag', input),
|
|
updateTag: (id, patch) => ipcRenderer.invoke('library:updateTag', id, patch),
|
|
removeTag: (id) => ipcRenderer.invoke('library:removeTag', id),
|
|
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),
|
|
pickLocal: (kind) => ipcRenderer.invoke('dialog:pickLocal', kind),
|
|
importLocal: (selectionId, options) => (
|
|
ipcRenderer.invoke('library:importLocal', selectionId, options)
|
|
),
|
|
scan: () => ipcRenderer.invoke('library:scan'),
|
|
onChanged: (cb) => {
|
|
const h = () => cb();
|
|
ipcRenderer.on('library:changed', h);
|
|
return () => ipcRenderer.removeListener('library:changed', h);
|
|
}
|
|
},
|
|
settings: {
|
|
get: (key, def) => ipcRenderer.invoke('settings:get', key, def),
|
|
set: (key, value) => ipcRenderer.invoke('settings:set', key, value)
|
|
},
|
|
ui: {
|
|
getTheme: () => ipcRenderer.invoke('ui:getTheme'),
|
|
setTheme: (theme) => ipcRenderer.invoke('ui:setTheme', theme),
|
|
onThemeChanged: (cb) => {
|
|
const h = (_event, theme) => cb(theme);
|
|
ipcRenderer.on('ui:themeChanged', h);
|
|
return () => ipcRenderer.removeListener('ui:themeChanged', h);
|
|
}
|
|
},
|
|
downloadFile,
|
|
downloads: {
|
|
run: runDownload,
|
|
pause: (requestId) => ipcRenderer.invoke('download:pause', requestId),
|
|
delete: (requestId) => ipcRenderer.invoke('download:delete', requestId)
|
|
},
|
|
zlib: {
|
|
hasCreds: () => ipcRenderer.invoke('zlib:hasCreds'),
|
|
login: (email, password) => ipcRenderer.invoke('zlib:login', email, password),
|
|
logout: () => ipcRenderer.invoke('zlib:logout')
|
|
},
|
|
semanticScholar: {
|
|
keyStatus: () => ipcRenderer.invoke('semanticScholar:keyStatus'),
|
|
setKey: (key) => ipcRenderer.invoke('semanticScholar:setKey', key),
|
|
clearKey: () => ipcRenderer.invoke('semanticScholar:clearKey')
|
|
},
|
|
proxy: {
|
|
get: () => ipcRenderer.invoke('proxy:get'),
|
|
set: (url) => ipcRenderer.invoke('proxy:set', url)
|
|
},
|
|
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)
|
|
),
|
|
meta: (entryId, fileIndex) => ipcRenderer.invoke('reader:meta', entryId, fileIndex),
|
|
bytes: (entryId, fileIndex) => ipcRenderer.invoke('reader:bytes', entryId, fileIndex),
|
|
rangeOpen: (entryId, fileIndex) => ipcRenderer.invoke('reader:rangeOpen', entryId, fileIndex),
|
|
rangeRead: (sessionId, begin, end) => (
|
|
ipcRenderer.invoke('reader:rangeRead', sessionId, begin, end)
|
|
),
|
|
rangeClose: (sessionId) => ipcRenderer.invoke('reader:rangeClose', sessionId),
|
|
captureRect: (rect) => captureReaderRect(rect),
|
|
openExternal: (entryId, fileIndex) => (
|
|
ipcRenderer.invoke('reader:openExternal', entryId, fileIndex)
|
|
),
|
|
getState: (entryId, documentKey) => ipcRenderer.invoke('reader:getState', entryId, documentKey),
|
|
setProgress: (entryId, documentKey, locator, percent) => (
|
|
ipcRenderer.invoke('reader:setProgress', entryId, documentKey, locator, percent)
|
|
),
|
|
addBookmark: (entryId, mark) => ipcRenderer.invoke('reader:addBookmark', entryId, mark),
|
|
removeBookmark: (entryId, markId) => ipcRenderer.invoke('reader:removeBookmark', entryId, markId),
|
|
addNote: (entryId, note) => ipcRenderer.invoke('reader:addNote', entryId, note),
|
|
addStandaloneNote: (note) => ipcRenderer.invoke('reader:addStandaloneNote', note),
|
|
updateNote: (entryId, noteId, patch) => ipcRenderer.invoke('reader:updateNote', entryId, noteId, patch),
|
|
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),
|
|
removeCollection: (id) => ipcRenderer.invoke('reader:removeCollection', id),
|
|
pickNotePdf: () => ipcRenderer.invoke('reader:pickNotePdf'),
|
|
notePdfBytes: (ref) => ipcRenderer.invoke('reader:notePdfBytes', ref),
|
|
saveNotePdf: (bytes, suggestedName) => (
|
|
ipcRenderer.invoke('reader:saveNotePdf', bytes, suggestedName)
|
|
),
|
|
getAnnotations: (entryId, fileIndex) => ipcRenderer.invoke('reader:getAnnotations', entryId, fileIndex),
|
|
setAnnotationPage: (entryId, fileIndex, page, data) => ipcRenderer.invoke('reader:setAnnotationPage', entryId, fileIndex, page, data),
|
|
onOpenEntry: (cb) => {
|
|
const h = (_e, data) => cb(data);
|
|
ipcRenderer.on('reader:openEntry', h);
|
|
return () => ipcRenderer.removeListener('reader:openEntry', h);
|
|
},
|
|
onCloseEntry: (cb) => {
|
|
const h = (_e, entryId) => cb(entryId);
|
|
ipcRenderer.on('reader:closeEntry', h);
|
|
return () => ipcRenderer.removeListener('reader:closeEntry', h);
|
|
},
|
|
onPurgeEntry: (cb) => {
|
|
const h = async (_e, data) => {
|
|
try { await cb(data); } finally {
|
|
if (data && data.requestId) ipcRenderer.send('reader:purgeReady', data.requestId);
|
|
}
|
|
};
|
|
ipcRenderer.on('reader:purgeEntry', h);
|
|
return () => ipcRenderer.removeListener('reader:purgeEntry', h);
|
|
},
|
|
onPrepareClose: (cb) => {
|
|
const h = async () => {
|
|
try { await cb(); } finally { ipcRenderer.send('reader:shutdownReady'); }
|
|
};
|
|
ipcRenderer.on('reader:prepareClose', h);
|
|
return () => ipcRenderer.removeListener('reader:prepareClose', h);
|
|
},
|
|
onNotesChanged: (cb) => {
|
|
const h = (_e, data) => cb(data);
|
|
ipcRenderer.on('reader:notesChanged', h);
|
|
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);
|
|
return () => ipcRenderer.removeListener('ai:changed', h);
|
|
},
|
|
// 返回取消订阅函数:阅读器窗口关闭时要能解绑,否则监听器会越积越多
|
|
onDelta: (cb) => {
|
|
const h = (_e, data) => cb(data);
|
|
ipcRenderer.on('ai:delta', h);
|
|
return () => ipcRenderer.removeListener('ai:delta', h);
|
|
}
|
|
},
|
|
openPath: (p) => ipcRenderer.invoke('shell:openPath', p),
|
|
showItem: (p) => ipcRenderer.invoke('shell:showItem', p),
|
|
openExternal: (url) => ipcRenderer.invoke('shell:openExternal', url),
|
|
copy: (text) => ipcRenderer.invoke('copy', text),
|
|
getVersion: () => ipcRenderer.invoke('app:version'),
|
|
checkUpdate: () => ipcRenderer.invoke('app:checkUpdate'),
|
|
minimize: () => ipcRenderer.send('win:minimize'),
|
|
maximize: () => ipcRenderer.send('win:maximize'),
|
|
close: () => ipcRenderer.send('win:close')
|
|
});
|