diff --git a/electron/main.ts b/electron/main.ts index d932ec9..6f64cbd 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -351,9 +351,29 @@ ipcMain.on('myreader:allow-close', (event) => { win.close() }) +let pendingOpenPaths: string[] = [] + app.whenReady().then(() => { Menu.setApplicationMenu(buildMenu()) - createWindow() + + const args = process.argv.slice(1) + for (const arg of args) { + if (existsSync(arg) && isMarkdownFile(arg)) { + pendingOpenPaths.push(arg) + } + } + + const win = createWindow() + + win.webContents.on('dom-ready', () => { + if (pendingOpenPaths.length > 0) { + setTimeout(() => { + win.webContents.send('myreader:open-files', pendingOpenPaths) + pendingOpenPaths = [] + }, 500) + } + }) + app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) { createWindow() diff --git a/electron/preload.ts b/electron/preload.ts index d83bf19..4221576 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -25,6 +25,7 @@ export interface MyReaderApi { allowClose: () => void onRequestClose: (handler: () => void) => () => void onMenuAction: (handler: (action: string) => void) => () => void + onOpenFiles: (handler: (paths: string[]) => void) => () => void } const api: MyReaderApi = { @@ -64,6 +65,15 @@ const api: MyReaderApi = { return () => { ipcRenderer.removeListener('myreader:menu', listener) } + }, + onOpenFiles: (handler: (paths: string[]) => void) => { + const listener = (_e: unknown, paths: string[]): void => { + handler(paths) + } + ipcRenderer.on('myreader:open-files', listener) + return () => { + ipcRenderer.removeListener('myreader:open-files', listener) + } } } diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index fefa2d9..e36dc67 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -173,6 +173,10 @@ export default function App(): React.ReactElement { return } let cancelled = false + const timeoutPromise = new Promise((_, reject) => { + setTimeout(() => reject(new Error('timeout')), 5000) + }) + void (async () => { try { const raw = localStorage.getItem(SESSION_KEY) @@ -181,16 +185,17 @@ export default function App(): React.ReactElement { if (data.v !== 1 || !Array.isArray(data.tabs)) return const restored: Tab[] = [] for (const row of data.tabs) { + if (cancelled) break if (!row || typeof row !== 'object') continue const id = crypto.randomUUID() const content = typeof row.content === 'string' ? row.content : '' const saved = typeof row.saved === 'string' ? row.saved : content if (row.path) { try { - await api.readFile(row.path) + await Promise.race([api.readFile(row.path), timeoutPromise]) restored.push({ id, filePath: row.path, content, savedContent: saved }) } catch { - /* missing or unreadable */ + /* missing or unreadable or timeout */ } } else { restored.push({ id, filePath: null, content, savedContent: saved }) @@ -326,6 +331,20 @@ export default function App(): React.ReactElement { if (textPrompt) setTextPromptInput(textPrompt.defaultValue) }, [textPrompt]) + React.useEffect(() => { + if (!api || !sessionHydrated) return + + const handleOpenFiles = (paths: string[]) => { + paths.forEach(path => { + void openPathInTab(path) + }) + } + + const unsubscribe = api.onOpenFiles(handleOpenFiles) + + return unsubscribe + }, [api, sessionHydrated]) + React.useEffect(() => { if (imageInsert) { setImageUrlInput('') diff --git a/src/renderer/src/env.d.ts b/src/renderer/src/env.d.ts index cb27bb0..9b3d5fe 100644 --- a/src/renderer/src/env.d.ts +++ b/src/renderer/src/env.d.ts @@ -25,6 +25,7 @@ export interface MyReaderApi { allowClose: () => void onRequestClose: (handler: () => void) => () => void onMenuAction: (handler: (action: string) => void) => () => void + onOpenFiles: (handler: (paths: string[]) => void) => () => void } declare global {