diff --git a/package-lock.json b/package-lock.json index 480956c..14e63dd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "folio", - "version": "0.1.2", + "version": "0.1.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "folio", - "version": "0.1.2", + "version": "0.1.3", "license": "MIT", "dependencies": { "@tiptap/core": "^2.27.2", diff --git a/package.json b/package.json index 5561b17..ac9f264 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "folio", - "version": "0.1.2", + "version": "0.1.3", "description": "A simple, local, open-source desktop writing app.", "productName": "Folio", "author": "Folio Contributors", diff --git a/src/main/index.ts b/src/main/index.ts index 169a3d4..cabf016 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -502,14 +502,36 @@ function currentBookTitle(): string { } } +// GTK save dialogs started with a bare filename (no directory) re-synthesize +// the name from the last-browsed folder when the user navigates, producing +// names like "Some Folder/.odt". Always hand them an absolute path. The last +// export folder is remembered so exports reopen where the user left them. +function exportDefaultPath(filename: string): string { + const last = getSetting("lastExportDir", ""); + const dir = + last && fs.existsSync(last) && fs.statSync(last).isDirectory() + ? last + : app.getPath("documents"); + return path.join(dir, filename); +} + +function rememberExportDir(filePath: string): void { + try { + setSetting("lastExportDir", path.dirname(filePath)); + } catch { + // best-effort only + } +} + handleIpc("folio:exportMarkdown", async () => { const bp = requireBook(); const { canceled, filePath } = await showSaveDialog({ title: "Export combined Markdown", - defaultPath: `${currentBookTitle()}.md`, + defaultPath: exportDefaultPath(`${currentBookTitle()}.md`), filters: [{ name: "Markdown", extensions: ["md"] }], }); if (canceled || !filePath) return { canceled: true } as const; + rememberExportDir(filePath); fs.writeFileSync(filePath, combineMarkdown(bp), "utf-8"); return { ok: true, filePath }; }); @@ -518,10 +540,11 @@ handleIpc("folio:exportZip", async () => { const bp = requireBook(); const { canceled, filePath } = await showSaveDialog({ title: "Export book bundle (ZIP)", - defaultPath: `${currentBookTitle()}.zip`, + defaultPath: exportDefaultPath(`${currentBookTitle()}.zip`), filters: [{ name: "ZIP Archive", extensions: ["zip"] }], }); if (canceled || !filePath) return { canceled: true } as const; + rememberExportDir(filePath); const buf = await buildZip(bp); fs.writeFileSync(filePath, buf); return { ok: true, filePath }; @@ -549,11 +572,12 @@ requireBook(); } else { const { canceled, filePath: chosen } = await showSaveDialog({ title: "Export as PDF", - defaultPath: `${base}${suffix}.pdf`, + defaultPath: exportDefaultPath(`${base}${suffix}.pdf`), filters: [{ name: "PDF Document", extensions: ["pdf"] }], }); if (canceled || !chosen) return { canceled: true } as const; filePath = chosen.endsWith(".pdf") ? chosen : `${chosen}.pdf`; + rememberExportDir(filePath); } const win = getWindow(); if (!win) return { error: "No window is open." }; @@ -590,7 +614,7 @@ async function exportOffice(format: "docx" | "odt", chapterIds?: string[] | null } else { const { canceled, filePath: chosen } = await showSaveDialog({ title: `Export${selected ? " Selected Chapters" : " Full Book"} as ${format.toUpperCase()}`, - defaultPath: `${base}${suffix}.${ext}`, + defaultPath: exportDefaultPath(`${base}${suffix}.${ext}`), filters: [ { name: format === "docx" ? "Word Document" : "OpenDocument Text", @@ -600,6 +624,7 @@ async function exportOffice(format: "docx" | "odt", chapterIds?: string[] | null }); if (canceled || !chosen) return { canceled: true } as const; filePath = chosen.endsWith(`.${ext}`) ? chosen : `${chosen}.${ext}`; + rememberExportDir(filePath); } const meta = loadBook(bp); const chapters = selected @@ -631,10 +656,11 @@ handleIpc("folio:saveChapterAs", async (_e, id: string) => { if (!entry) return { error: "Chapter not found" }; const { canceled, filePath } = await showSaveDialog({ title: "Save Chapter As", - defaultPath: `${entry.title}.md`, + defaultPath: exportDefaultPath(`${entry.title}.md`), filters: [{ name: "Markdown", extensions: ["md"] }], }); if (canceled || !filePath) return { canceled: true } as const; + rememberExportDir(filePath); const content = getChapterContent(bp, id); fs.writeFileSync(filePath, content, "utf-8"); return { ok: true, filePath };