Add document/editor zoom controls
- Persist an app-wide zoom percentage (not per book) and apply it only to the writing view (.editor-panes) so the sidebar/navigation stay fixed size - Add a [-] [100%] [+] control to the chapter and Full Book headers, with a click-to-reset level button - Route Ctrl/Cmd+=, Ctrl/Cmd+-, and Ctrl/Cmd+0 to zoom in/out/reset, intercepting the browser's native zoom
This commit is contained in:
parent
946a2967db
commit
7bdddf32b2
5 changed files with 110 additions and 0 deletions
|
|
@ -143,6 +143,16 @@ function forwardEditShortcuts(win: BrowserWindow) {
|
|||
} else if ((key === "z" && input.shift) || (key === "y" && !input.shift)) {
|
||||
event.preventDefault();
|
||||
win.webContents.send("folio:redo-requested");
|
||||
} else if (key === "=" || key === "+") {
|
||||
// Ctrl/Cmd+= or Ctrl/Cmd++ → zoom in (intercept the browser's native zoom).
|
||||
event.preventDefault();
|
||||
win.webContents.send("folio:zoom-in-requested");
|
||||
} else if (key === "-") {
|
||||
event.preventDefault();
|
||||
win.webContents.send("folio:zoom-out-requested");
|
||||
} else if (key === "0") {
|
||||
event.preventDefault();
|
||||
win.webContents.send("folio:zoom-reset-requested");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -548,6 +558,18 @@ ipcMain.handle("folio:setTheme", (_e, id: unknown) => {
|
|||
return { ok: true };
|
||||
});
|
||||
|
||||
// Editor zoom preference (percentage). Persisted app-wide, not per book, so the
|
||||
// writing view stays at the user's chosen size across books and restarts.
|
||||
ipcMain.handle("folio:getZoom", () => {
|
||||
const z = getSetting<number>("zoom", 100);
|
||||
return Number.isFinite(z) && z > 0 ? Math.round(z) : 100;
|
||||
});
|
||||
ipcMain.handle("folio:setZoom", (_e, pct: unknown) => {
|
||||
const z = typeof pct === "number" && Number.isFinite(pct) ? Math.round(pct) : 100;
|
||||
setSetting("zoom", z);
|
||||
return { ok: true };
|
||||
});
|
||||
|
||||
ipcMain.handle("folio:importAttachment", async () => {
|
||||
const bp = requireBook();
|
||||
const { canceled, filePaths } = await showOpenDialog({
|
||||
|
|
|
|||
|
|
@ -46,6 +46,8 @@ contextBridge.exposeInMainWorld("folio", {
|
|||
copyText: (text: string) => ipcRenderer.invoke("folio:copyText", text),
|
||||
getClipboardText: () => ipcRenderer.invoke("folio:getClipboardText"),
|
||||
toggleFullscreen: () => ipcRenderer.invoke("folio:toggleFullscreen"),
|
||||
getZoom: () => ipcRenderer.invoke("folio:getZoom"),
|
||||
setZoom: (pct: number) => ipcRenderer.invoke("folio:setZoom", pct),
|
||||
|
||||
// theme preference
|
||||
getTheme: () => ipcRenderer.invoke("folio:getTheme"),
|
||||
|
|
@ -84,4 +86,10 @@ contextBridge.exposeInMainWorld("folio", {
|
|||
ipcRenderer.on("folio:selectAll-requested", () => cb()),
|
||||
onSaveRequested: (cb: () => void) =>
|
||||
ipcRenderer.on("folio:save-requested", () => cb()),
|
||||
onZoomInRequested: (cb: () => void) =>
|
||||
ipcRenderer.on("folio:zoom-in-requested", () => cb()),
|
||||
onZoomOutRequested: (cb: () => void) =>
|
||||
ipcRenderer.on("folio:zoom-out-requested", () => cb()),
|
||||
onZoomResetRequested: (cb: () => void) =>
|
||||
ipcRenderer.on("folio:zoom-reset-requested", () => cb()),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -51,6 +51,8 @@ export type FolioAPI = {
|
|||
toggleFullscreen: () => Promise<boolean>;
|
||||
getTheme: () => Promise<string>;
|
||||
setTheme: (id: string) => Promise<any>;
|
||||
getZoom: () => Promise<number>;
|
||||
setZoom: (pct: number) => Promise<any>;
|
||||
onBookOpened: (cb: (data: { path: string; title: string }) => void) => void;
|
||||
onBookClosed: (cb: () => void) => void;
|
||||
onChaptersChanged: (cb: () => void) => void;
|
||||
|
|
@ -67,6 +69,9 @@ export type FolioAPI = {
|
|||
onRedoRequested: (cb: () => void) => void;
|
||||
onSelectAllRequested: (cb: () => void) => void;
|
||||
onSaveRequested: (cb: () => void) => void;
|
||||
onZoomInRequested: (cb: () => void) => void;
|
||||
onZoomOutRequested: (cb: () => void) => void;
|
||||
onZoomResetRequested: (cb: () => void) => void;
|
||||
};
|
||||
|
||||
const $ = (id: string) => document.getElementById(id) as HTMLElement;
|
||||
|
|
@ -1334,6 +1339,45 @@ export function initBook(api: FolioAPI) {
|
|||
});
|
||||
api.onSaveRequested(() => void saveAndNotify());
|
||||
|
||||
// ---- editor zoom (writing view only) ----
|
||||
// Zoom scales the document content area (the .editor-panes containers) so the
|
||||
// sidebar/navigation keep their normal size. The chosen level is persisted
|
||||
// app-wide and re-applied on launch.
|
||||
let currentZoom = 100;
|
||||
function applyZoom(pct: number, persist = true) {
|
||||
const clamped = Math.max(50, Math.min(300, Math.round(pct)));
|
||||
currentZoom = clamped;
|
||||
document.querySelectorAll<HTMLElement>(".editor-panes").forEach((el) => {
|
||||
el.style.zoom = String(clamped / 100);
|
||||
});
|
||||
document.querySelectorAll<HTMLElement>(".zoom-level").forEach((el) => {
|
||||
el.textContent = clamped + "%";
|
||||
});
|
||||
if (persist) void api.setZoom(clamped);
|
||||
}
|
||||
document.querySelectorAll<HTMLElement>(".zoom-control").forEach((ctrl) => {
|
||||
ctrl
|
||||
.querySelector('[data-zoom="out"]')
|
||||
?.addEventListener("click", () => applyZoom(currentZoom - 10));
|
||||
ctrl
|
||||
.querySelector('[data-zoom="in"]')
|
||||
?.addEventListener("click", () => applyZoom(currentZoom + 10));
|
||||
ctrl
|
||||
.querySelector('[data-zoom="reset"]')
|
||||
?.addEventListener("click", () => applyZoom(100));
|
||||
});
|
||||
api.onZoomInRequested(() => applyZoom(currentZoom + 10));
|
||||
api.onZoomOutRequested(() => applyZoom(currentZoom - 10));
|
||||
api.onZoomResetRequested(() => applyZoom(100));
|
||||
void (async () => {
|
||||
try {
|
||||
const z = await api.getZoom();
|
||||
if (typeof z === "number" && z > 0) applyZoom(z, false);
|
||||
} catch {
|
||||
/* keep default 100% */
|
||||
}
|
||||
})();
|
||||
|
||||
// Ctrl/Cmd+S saves the current chapter (in addition to the menu item).
|
||||
window.addEventListener("keydown", (e) => {
|
||||
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "s") {
|
||||
|
|
|
|||
|
|
@ -526,6 +526,32 @@ body.dragging-active * {
|
|||
border-color: var(--color-on-accent-border);
|
||||
}
|
||||
|
||||
/* Editor zoom controls (writing view only; sidebar/nav stay fixed size). */
|
||||
.zoom-control {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
}
|
||||
.zoom-control .icon-btn {
|
||||
font-size: 1rem;
|
||||
line-height: 1;
|
||||
}
|
||||
.zoom-control .zoom-level {
|
||||
min-width: 50px;
|
||||
border: 1px solid var(--color-border);
|
||||
background: transparent;
|
||||
color: var(--color-text);
|
||||
border-radius: calc(var(--radius) - 4px);
|
||||
font-size: 0.78rem;
|
||||
padding: 2px 4px;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
}
|
||||
.zoom-control .zoom-level:hover {
|
||||
background: var(--color-on-accent-soft);
|
||||
border-color: var(--color-on-accent-border);
|
||||
}
|
||||
|
||||
/* Small accent dot shown next to a chapter's title when it has a color. */
|
||||
.row-color-dot {
|
||||
flex: 0 0 auto;
|
||||
|
|
|
|||
|
|
@ -62,6 +62,11 @@
|
|||
<span class="word-count" id="wordCount">words: 0</span>
|
||||
<button class="icon-button" id="exportBtn" title="Export book (Markdown)">⬇</button>
|
||||
<button class="icon-button" id="exportZipBtn" title="Export bundle (ZIP)">📦</button>
|
||||
<div class="zoom-control" role="group" aria-label="Zoom">
|
||||
<button class="icon-btn" type="button" data-zoom="out" title="Zoom out (Ctrl+-)">−</button>
|
||||
<button class="zoom-level" type="button" data-zoom="reset" title="Reset zoom to 100% (Ctrl+0)">100%</button>
|
||||
<button class="icon-btn" type="button" data-zoom="in" title="Zoom in (Ctrl+=)">+</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="editor-toolbar" id="editorToolbar">
|
||||
|
|
@ -230,6 +235,11 @@
|
|||
</div>
|
||||
</div>
|
||||
<button class="button button-primary" id="copyAllBtn" title="Copy the entire assembled manuscript as Markdown">Copy All</button>
|
||||
<div class="zoom-control" role="group" aria-label="Zoom">
|
||||
<button class="icon-btn" type="button" data-zoom="out" title="Zoom out (Ctrl+-)">−</button>
|
||||
<button class="zoom-level" type="button" data-zoom="reset" title="Reset zoom to 100% (Ctrl+0)">100%</button>
|
||||
<button class="icon-btn" type="button" data-zoom="in" title="Zoom in (Ctrl+=)">+</button>
|
||||
</div>
|
||||
<span class="save-state" id="fullBookStatus"></span>
|
||||
</div>
|
||||
</header>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue