feat(menu): add Save As to File menu with Ctrl+Shift+S

- Add 'Save As...' item to native File menu, right under Save
- New IPC event folio:save-as-requested routes through preload to renderer
- Wired in index.ts to call saveChapterAs with current chapter ID
This commit is contained in:
avi 2026-09-05 13:37:24 -05:00
commit 38da218e33
4 changed files with 24 additions and 6 deletions

View file

@ -39,12 +39,18 @@ export function buildMenu(
{ label: "New", accelerator: "CmdOrCtrl+N", click: onRequestNew }, { label: "New", accelerator: "CmdOrCtrl+N", click: onRequestNew },
{ label: "Open", accelerator: "CmdOrCtrl+O", click: onRequestOpen }, { label: "Open", accelerator: "CmdOrCtrl+O", click: onRequestOpen },
{ type: "separator" }, { type: "separator" },
{ {
label: "Save", label: "Save",
accelerator: "CmdOrCtrl+S", accelerator: "CmdOrCtrl+S",
enabled: !!getState().bookPath, enabled: !!getState().bookPath,
click: () => getWindow()?.webContents.send("folio:save-requested"), click: () => getWindow()?.webContents.send("folio:save-requested"),
}, },
{
label: "Save As…",
accelerator: "CmdOrCtrl+Shift+S",
enabled: !!getState().bookPath,
click: () => getWindow()?.webContents.send("folio:save-as-requested"),
},
{ type: "separator" }, { type: "separator" },
{ label: "Open Recent", submenu: recentItems }, { label: "Open Recent", submenu: recentItems },
{ type: "separator" }, { type: "separator" },

View file

@ -102,6 +102,8 @@ contextBridge.exposeInMainWorld("folio", {
ipcRenderer.on("folio:selectAll-requested", () => cb()), ipcRenderer.on("folio:selectAll-requested", () => cb()),
onSaveRequested: (cb: () => void) => onSaveRequested: (cb: () => void) =>
ipcRenderer.on("folio:save-requested", () => cb()), ipcRenderer.on("folio:save-requested", () => cb()),
onSaveAsRequested: (cb: () => void) =>
ipcRenderer.on("folio:save-as-requested", () => cb()),
onZoomInRequested: (cb: () => void) => onZoomInRequested: (cb: () => void) =>
ipcRenderer.on("folio:zoom-in-requested", () => cb()), ipcRenderer.on("folio:zoom-in-requested", () => cb()),
onZoomOutRequested: (cb: () => void) => onZoomOutRequested: (cb: () => void) =>

View file

@ -170,6 +170,15 @@ export function initBook(api: FolioAPI) {
} }
}); });
api.onSaveRequested(() => void hooks.saveAndNotify()); api.onSaveRequested(() => void hooks.saveAndNotify());
api.onSaveAsRequested(async () => {
if (!state.selectedId) return;
const res = await api.saveChapterAs(state.selectedId);
if (res && "error" in res) {
showToast("Save As failed");
} else if (res && "filePath" in res) {
showToast(`Saved to ${basename(res.filePath as string)}`);
}
});
// ---- comments / annotations wiring ---- // ---- comments / annotations wiring ----
// Already wired through the comments feature's internal listeners; the // Already wired through the comments feature's internal listeners; the

View file

@ -87,6 +87,7 @@ export type FolioAPI = {
onRedoRequested: (cb: () => void) => void; onRedoRequested: (cb: () => void) => void;
onSelectAllRequested: (cb: () => void) => void; onSelectAllRequested: (cb: () => void) => void;
onSaveRequested: (cb: () => void) => void; onSaveRequested: (cb: () => void) => void;
onSaveAsRequested: (cb: () => void) => void;
onZoomInRequested: (cb: () => void) => void; onZoomInRequested: (cb: () => void) => void;
onZoomOutRequested: (cb: () => void) => void; onZoomOutRequested: (cb: () => void) => void;
onZoomResetRequested: (cb: () => void) => void; onZoomResetRequested: (cb: () => void) => void;