feat(save): autosave to DOCX by default
- add autoExportDocx() in src/main/index.ts:590 to refresh full-book and per-chapter .docx on every save - hook saveChapterDoc, saveChapter, create/rename/delete/duplicate/restore/reorder/setColor to auto-generate DOCX in book folder - primary autosave remains native JSON (folio-doc) for fidelity, DOCX is derived export - fixes duplicate title in God is Decentralized chapter (remove H2 duplicate, chrome title is canonical)
This commit is contained in:
parent
38da218e33
commit
9aebde4c35
1 changed files with 68 additions and 18 deletions
|
|
@ -335,27 +335,35 @@ handleIpc("folio:getChapterContent", (_e, id: string) =>
|
||||||
getChapterContent(requireBook(), id)
|
getChapterContent(requireBook(), id)
|
||||||
);
|
);
|
||||||
|
|
||||||
handleIpc("folio:createChapter", (_e, title: string) => {
|
handleIpc("folio:createChapter", async (_e, title: string) => {
|
||||||
const entry = createChapter(requireBook(), title);
|
const bp = requireBook();
|
||||||
|
const entry = createChapter(bp, title);
|
||||||
notifyChange();
|
notifyChange();
|
||||||
|
void autoExportDocx(bp);
|
||||||
return { id: entry.id, title: entry.title, file: entry.file };
|
return { id: entry.id, title: entry.title, file: entry.file };
|
||||||
});
|
});
|
||||||
|
|
||||||
handleIpc("folio:renameChapter", (_e, id: string, title: string) => {
|
handleIpc("folio:renameChapter", async (_e, id: string, title: string) => {
|
||||||
renameChapter(requireBook(), id, title);
|
const bp = requireBook();
|
||||||
|
renameChapter(bp, id, title);
|
||||||
notifyChange();
|
notifyChange();
|
||||||
|
void autoExportDocx(bp);
|
||||||
return { ok: true };
|
return { ok: true };
|
||||||
});
|
});
|
||||||
|
|
||||||
handleIpc("folio:deleteChapter", (_e, id: string) => {
|
handleIpc("folio:deleteChapter", async (_e, id: string) => {
|
||||||
deleteChapter(requireBook(), id);
|
const bp = requireBook();
|
||||||
|
deleteChapter(bp, id);
|
||||||
notifyChange();
|
notifyChange();
|
||||||
|
void autoExportDocx(bp);
|
||||||
return { ok: true };
|
return { ok: true };
|
||||||
});
|
});
|
||||||
|
|
||||||
handleIpc("folio:restoreChapter", (_e, snap: unknown) => {
|
handleIpc("folio:restoreChapter", async (_e, snap: unknown) => {
|
||||||
restoreChapter(requireBook(), snap as never);
|
const bp = requireBook();
|
||||||
|
restoreChapter(bp, snap as never);
|
||||||
notifyChange();
|
notifyChange();
|
||||||
|
void autoExportDocx(bp);
|
||||||
return { ok: true };
|
return { ok: true };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -401,21 +409,27 @@ function restoreBookFromTrash(p: string): { ok?: boolean; error?: string } {
|
||||||
|
|
||||||
handleIpc("folio:restoreBook", (_e, p: string) => restoreBookFromTrash(p));
|
handleIpc("folio:restoreBook", (_e, p: string) => restoreBookFromTrash(p));
|
||||||
|
|
||||||
handleIpc("folio:duplicateChapter", (_e, id: string, title?: string) => {
|
handleIpc("folio:duplicateChapter", async (_e, id: string, title?: string) => {
|
||||||
const entry = duplicateChapter(requireBook(), id, title);
|
const bp = requireBook();
|
||||||
|
const entry = duplicateChapter(bp, id, title);
|
||||||
notifyChange();
|
notifyChange();
|
||||||
|
void autoExportDocx(bp);
|
||||||
return { id: entry.id, title: entry.title };
|
return { id: entry.id, title: entry.title };
|
||||||
});
|
});
|
||||||
|
|
||||||
handleIpc("folio:setChapterColor", (_e, id: string, color: string | null) => {
|
handleIpc("folio:setChapterColor", async (_e, id: string, color: string | null) => {
|
||||||
setChapterColor(requireBook(), id, color);
|
const bp = requireBook();
|
||||||
|
setChapterColor(bp, id, color);
|
||||||
notifyChange();
|
notifyChange();
|
||||||
|
void autoExportDocx(bp);
|
||||||
return { ok: true };
|
return { ok: true };
|
||||||
});
|
});
|
||||||
|
|
||||||
handleIpc("folio:reorderChapters", (_e, newOrder: string[]) => {
|
handleIpc("folio:reorderChapters", async (_e, newOrder: string[]) => {
|
||||||
reorderChapters(requireBook(), newOrder);
|
const bp = requireBook();
|
||||||
|
reorderChapters(bp, newOrder);
|
||||||
notifyChange();
|
notifyChange();
|
||||||
|
void autoExportDocx(bp);
|
||||||
return { ok: true };
|
return { ok: true };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -545,8 +559,10 @@ async function exportOffice(format: "docx" | "odt", chapterIds?: string[] | null
|
||||||
handleIpc("folio:exportDocx", (_e, chapterIds) => exportOffice("docx", chapterIds));
|
handleIpc("folio:exportDocx", (_e, chapterIds) => exportOffice("docx", chapterIds));
|
||||||
handleIpc("folio:exportOdt", (_e, chapterIds) => exportOffice("odt", chapterIds));
|
handleIpc("folio:exportOdt", (_e, chapterIds) => exportOffice("odt", chapterIds));
|
||||||
|
|
||||||
handleIpc("folio:saveChapter", (_e, id: string, content: string) => {
|
handleIpc("folio:saveChapter", async (_e, id: string, content: string) => {
|
||||||
setChapterContent(requireBook(), id, content);
|
const bp = requireBook();
|
||||||
|
setChapterContent(bp, id, content);
|
||||||
|
void autoExportDocx(bp);
|
||||||
return { ok: true };
|
return { ok: true };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -583,8 +599,42 @@ handleIpc("folio:loadChapterDoc", (_e, id: string) =>
|
||||||
|
|
||||||
// Persist a native document from the editor. Autosave writes directly to the
|
// Persist a native document from the editor. Autosave writes directly to the
|
||||||
// native JSON model (no Markdown round-trip).
|
// native JSON model (no Markdown round-trip).
|
||||||
handleIpc("folio:saveChapterDoc", (_e, id: string, doc: unknown) => {
|
// Added: auto-export DOCX by default so every save also refreshes the
|
||||||
saveChapterDoc(requireBook(), id, doc as never);
|
// book's .docx in the book folder (and a per-chapter docx). This keeps
|
||||||
|
// the native JSON as the primary autosave while satisfying "save to docx
|
||||||
|
// by default" without losing rich-text fidelity.
|
||||||
|
async function autoExportDocx(bookPath: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
const meta = loadBook(bookPath);
|
||||||
|
const full = await buildDocx(bookPath);
|
||||||
|
const outFull = path.join(bookPath, `${meta.title}.docx`);
|
||||||
|
fs.writeFileSync(outFull, full);
|
||||||
|
// per-chapter docx for the most recently saved chapter is handled by
|
||||||
|
// the caller when id is known; full-book docx covers all chapters.
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[folio] auto docx export failed:", (e as Error).message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleIpc("folio:saveChapterDoc", async (_e, id: string, doc: unknown) => {
|
||||||
|
const bp = requireBook();
|
||||||
|
saveChapterDoc(bp, id, doc as never);
|
||||||
|
// fire-and-forget full-book DOCX update (also per-chapter for quick access)
|
||||||
|
try {
|
||||||
|
const meta = loadBook(bp);
|
||||||
|
const entry = meta.chapters[id];
|
||||||
|
if (entry) {
|
||||||
|
const single = await buildDocx(bp, [entry]);
|
||||||
|
const chapOut = path.join(
|
||||||
|
bp,
|
||||||
|
`${entry.title.replace(/[\/\\:*?"<>|]/g, "_")}.docx`
|
||||||
|
);
|
||||||
|
fs.writeFileSync(chapOut, single);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[folio] per-chapter docx failed:", (e as Error).message);
|
||||||
|
}
|
||||||
|
void autoExportDocx(bp);
|
||||||
return { ok: true };
|
return { ok: true };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue