2026-05-15 16:35:31 +08:00
|
|
|
import { contextBridge, ipcRenderer } from 'electron'
|
|
|
|
|
import type {
|
|
|
|
|
ListDirResult,
|
|
|
|
|
OpenFileResult,
|
|
|
|
|
OpenFolderResult,
|
|
|
|
|
OpenImageResult,
|
|
|
|
|
SaveDialogResult
|
|
|
|
|
} from './ipc-types.js'
|
|
|
|
|
|
2026-05-19 13:33:48 +08:00
|
|
|
const bufferedOpenFiles: string[][] = []
|
|
|
|
|
let openFilesHandler: ((paths: string[]) => void) | null = null
|
|
|
|
|
|
|
|
|
|
ipcRenderer.on('myreader:open-files', (_e, paths: string[]) => {
|
|
|
|
|
if (openFilesHandler) {
|
|
|
|
|
openFilesHandler(paths)
|
|
|
|
|
} else {
|
|
|
|
|
bufferedOpenFiles.push(paths)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
2026-05-15 16:35:31 +08:00
|
|
|
export interface MyReaderApi {
|
|
|
|
|
openFileDialog: () => Promise<OpenFileResult>
|
|
|
|
|
openFolderDialog: () => Promise<OpenFolderResult>
|
|
|
|
|
openImageDialog: () => Promise<OpenImageResult>
|
|
|
|
|
saveFileDialog: (defaultPath?: string) => Promise<SaveDialogResult>
|
|
|
|
|
readFile: (filePath: string) => Promise<string>
|
|
|
|
|
writeFile: (filePath: string, content: string) => Promise<void>
|
|
|
|
|
listDirectory: (dirPath: string) => Promise<ListDirResult>
|
|
|
|
|
parentDirectory: (dirPath: string) => Promise<string>
|
|
|
|
|
resolveRelativePath: (baseFilePath: string, href: string) => Promise<string>
|
|
|
|
|
openExternal: (url: string) => Promise<void>
|
|
|
|
|
defaultDirectory: () => Promise<string>
|
|
|
|
|
deleteFile: (filePath: string) => Promise<void>
|
|
|
|
|
renamePath: (oldPath: string, newName: string) => Promise<{ newPath: string }>
|
|
|
|
|
allowClose: () => void
|
2026-07-06 20:30:40 +08:00
|
|
|
setMenuVisible: (visible: boolean) => Promise<void>
|
2026-05-15 16:35:31 +08:00
|
|
|
onRequestClose: (handler: () => void) => () => void
|
|
|
|
|
onMenuAction: (handler: (action: string) => void) => () => void
|
2026-05-18 14:13:16 +08:00
|
|
|
onOpenFiles: (handler: (paths: string[]) => void) => () => void
|
2026-05-19 13:33:48 +08:00
|
|
|
getPendingFilesIPC: () => Promise<string[]>
|
2026-05-15 16:35:31 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const api: MyReaderApi = {
|
|
|
|
|
openFileDialog: () => ipcRenderer.invoke('myreader:open-file-dialog'),
|
|
|
|
|
openFolderDialog: () => ipcRenderer.invoke('myreader:open-folder-dialog'),
|
|
|
|
|
openImageDialog: () => ipcRenderer.invoke('myreader:open-image-dialog'),
|
|
|
|
|
saveFileDialog: (defaultPath?: string) => ipcRenderer.invoke('myreader:save-dialog', defaultPath),
|
|
|
|
|
readFile: (filePath: string) => ipcRenderer.invoke('myreader:read-file', filePath),
|
|
|
|
|
writeFile: (filePath: string, content: string) =>
|
|
|
|
|
ipcRenderer.invoke('myreader:write-file', filePath, content),
|
|
|
|
|
listDirectory: (dirPath: string) => ipcRenderer.invoke('myreader:list-directory', dirPath),
|
|
|
|
|
parentDirectory: (dirPath: string) => ipcRenderer.invoke('myreader:parent-directory', dirPath),
|
|
|
|
|
resolveRelativePath: (baseFilePath: string, href: string) =>
|
|
|
|
|
ipcRenderer.invoke('myreader:resolve-relative-path', baseFilePath, href),
|
|
|
|
|
openExternal: (url: string) => ipcRenderer.invoke('myreader:open-external', url),
|
|
|
|
|
defaultDirectory: () => ipcRenderer.invoke('myreader:default-directory'),
|
|
|
|
|
deleteFile: (filePath: string) => ipcRenderer.invoke('myreader:delete-file', filePath),
|
|
|
|
|
renamePath: (oldPath: string, newName: string) =>
|
|
|
|
|
ipcRenderer.invoke('myreader:rename-path', oldPath, newName),
|
|
|
|
|
allowClose: () => {
|
|
|
|
|
ipcRenderer.send('myreader:allow-close')
|
|
|
|
|
},
|
2026-07-06 20:30:40 +08:00
|
|
|
setMenuVisible: (visible: boolean) => ipcRenderer.invoke('myreader:set-menu-visible', visible),
|
2026-05-15 16:35:31 +08:00
|
|
|
onRequestClose: (handler: () => void) => {
|
|
|
|
|
const listener = (): void => {
|
|
|
|
|
handler()
|
|
|
|
|
}
|
|
|
|
|
ipcRenderer.on('myreader:request-close', listener)
|
|
|
|
|
return () => {
|
|
|
|
|
ipcRenderer.removeListener('myreader:request-close', listener)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
onMenuAction: (handler: (action: string) => void) => {
|
|
|
|
|
const listener = (_e: unknown, action: string): void => {
|
|
|
|
|
handler(action)
|
|
|
|
|
}
|
|
|
|
|
ipcRenderer.on('myreader:menu', listener)
|
|
|
|
|
return () => {
|
|
|
|
|
ipcRenderer.removeListener('myreader:menu', listener)
|
|
|
|
|
}
|
2026-05-18 14:13:16 +08:00
|
|
|
},
|
|
|
|
|
onOpenFiles: (handler: (paths: string[]) => void) => {
|
2026-05-19 13:33:48 +08:00
|
|
|
openFilesHandler = handler
|
2026-05-18 14:13:16 +08:00
|
|
|
return () => {
|
2026-05-19 13:33:48 +08:00
|
|
|
openFilesHandler = null
|
2026-05-18 14:13:16 +08:00
|
|
|
}
|
2026-05-19 13:33:48 +08:00
|
|
|
},
|
|
|
|
|
getPendingFilesIPC: async (): Promise<string[]> => {
|
|
|
|
|
const fromMain = await ipcRenderer.invoke('myreader:get-pending-files')
|
|
|
|
|
const fromBuffer = bufferedOpenFiles.flat()
|
|
|
|
|
bufferedOpenFiles.length = 0
|
|
|
|
|
const seen = new Set<string>()
|
|
|
|
|
const merged: string[] = []
|
|
|
|
|
for (const p of [...fromMain, ...fromBuffer]) {
|
|
|
|
|
const key = p.replace(/\\/g, '/').toLowerCase()
|
|
|
|
|
if (seen.has(key)) continue
|
|
|
|
|
seen.add(key)
|
|
|
|
|
merged.push(p)
|
|
|
|
|
}
|
|
|
|
|
return merged
|
2026-05-15 16:35:31 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
contextBridge.exposeInMainWorld('myreader', api)
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error('[MyReader] preload contextBridge failed', e)
|
2026-05-19 13:33:48 +08:00
|
|
|
}
|