diff --git a/package-lock.json b/package-lock.json index 8a1b466..8b24614 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "folio", - "version": "0.1.6", + "version": "0.1.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "folio", - "version": "0.1.6", + "version": "0.1.5", "license": "MIT", "dependencies": { "@tiptap/core": "^2.27.2", diff --git a/package.json b/package.json index 33314bc..347d9c7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "folio", - "version": "0.1.6", + "version": "0.1.5", "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 727e998..fd42374 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -340,70 +340,6 @@ handleIpc("folio:pickBackupFolder", async () => { handleIpc("folio:getRecents", (): Recent[] => listRecents()); -// ---- recents right-click menu ---- -// The renderer claims a right-click on a recents row synchronously -// (ipcRenderer.sendSync) before the main-process context-menu event fires, -// so the generic spelling/edit popup is skipped for that target and a -// dedicated book menu is shown instead. Menu actions are sent back to the -// renderer (folio:recents-menu) and executed by the same handlers the row -// buttons use — one code path, consistent behavior. -interface RecentsMenuEntry { - path: string; - title: string; - hidden?: boolean; - missing?: boolean; -} -let recentsMenuClaimedAt = 0; - -ipcMain.on( - "folio:recents-contextmenu-sync", - (event, entry: RecentsMenuEntry, vx: number, vy: number) => { - if (!entry || typeof entry.path !== "string") { - event.returnValue = false; - return; - } - const win = getWindow(); - if (!win) { - event.returnValue = false; - return; - } - recentsMenuClaimedAt = Date.now(); - // Convert viewport coords to screen coords for the popup. - const [ox, oy] = win.getContentBounds - ? [win.getContentBounds().x, win.getContentBounds().y] - : [0, 0]; - const x = Number.isFinite(vx) ? ox + Math.round(vx) : undefined; - const y = Number.isFinite(vy) ? oy + Math.round(vy) : undefined; - const exists = hasBook(entry.path); - const send = (action: string) => - win.webContents.send("folio:recents-menu", { action, entry }); - const items: Electron.MenuItemConstructorOptions[] = []; - if (exists) { - items.push({ label: "Open", click: () => send("open") }); - items.push({ label: "Rename…", click: () => send("rename") }); - items.push({ label: "Show in Folder", click: () => send("reveal") }); - items.push({ type: "separator" }); - } - items.push( - entry.hidden - ? { label: "Unhide in Recents", click: () => send("unhide") } - : { - label: "Hide from Recents", - toolTip: - "Removes the entry from the list only — the book on disk is untouched", - click: () => send("hide"), - } - ); - items.push({ type: "separator" }); - items.push({ - label: exists ? "Delete Book (to Trash)…" : "Remove from List", - click: () => send(exists ? "delete" : "remove"), - }); - Menu.buildFromTemplate(items).popup({ window: win, x, y }); - event.returnValue = true; - } -); - // Rename a book's display title (folio.json + matching recents entry). The // folder on disk keeps its name: renaming the directory would invalidate // every other recents/menu path that points at it, so the title is the only @@ -859,12 +795,6 @@ function wireContextMenu(win: BrowserWindow) { win.webContents.on("context-menu", (_e, params) => { const target = getWindow(); if (!target) return; - // A recents row claimed this right-click (see the - // folio:recents-contextmenu-sync handler): its dedicated book menu was - // already popped up, so skip the generic spelling/edit popup. The claim - // is only honored within a short window so it can never suppress later - // right-clicks elsewhere in the app. - if (Date.now() - recentsMenuClaimedAt < 250) return; const items: Electron.MenuItemConstructorOptions[] = []; if (params.misspelledWord) { const sugs = (params.dictionarySuggestions || []).slice(0, 5); diff --git a/src/main/preload.ts b/src/main/preload.ts index 1eba3e0..e926b6b 100644 --- a/src/main/preload.ts +++ b/src/main/preload.ts @@ -7,20 +7,6 @@ contextBridge.exposeInMainWorld("folio", { renameBook: (p: string, title: string) => ipcRenderer.invoke("folio:renameBook", p, title), hideRecent: (p: string) => ipcRenderer.invoke("folio:hideRecent", p), unhideRecent: (p: string) => ipcRenderer.invoke("folio:unhideRecent", p), - // Synchronously claim a right-click on a recents row so the main process - // pops the dedicated book menu instead of the generic edit menu. - claimRecentsContext: ( - p: string, - title: string, - hidden?: boolean, - missing?: boolean, - x?: number, - y?: number - ): boolean => - ipcRenderer.sendSync("folio:recents-contextmenu-sync", { path: p, title, hidden, missing }, x, y) === true, - onRecentsMenu: ( - cb: (msg: { action: string; entry: { path: string; title: string; hidden?: boolean; missing?: boolean } }) => void - ) => ipcRenderer.on("folio:recents-menu", (_e, msg) => cb(msg)), restoreBook: (p: string) => ipcRenderer.invoke("folio:restoreBook", p), openBook: () => ipcRenderer.invoke("folio:openBook"), openBookPath: (p: string) => ipcRenderer.invoke("folio:openPath", p), diff --git a/src/renderer/book/types.ts b/src/renderer/book/types.ts index 29eb99a..95d1c00 100644 --- a/src/renderer/book/types.ts +++ b/src/renderer/book/types.ts @@ -43,17 +43,6 @@ export type FolioAPI = { renameBook: (p: string, title: string) => Promise; hideRecent: (p: string) => Promise; unhideRecent: (p: string) => Promise; - claimRecentsContext: ( - p: string, - title: string, - hidden?: boolean, - missing?: boolean, - x?: number, - y?: number - ) => boolean; - onRecentsMenu: ( - cb: (msg: { action: string; entry: { path: string; title: string; hidden?: boolean; missing?: boolean } }) => void - ) => void; closeBook: () => Promise; getSpellcheck: () => Promise; setSpellcheck: (on: boolean) => Promise; diff --git a/src/renderer/book/welcome.ts b/src/renderer/book/welcome.ts index a5baa0c..4939609 100644 --- a/src/renderer/book/welcome.ts +++ b/src/renderer/book/welcome.ts @@ -9,7 +9,7 @@ import type { BookCtx } from "./types.js"; import type { ChapterEntry, Recent } from "./types.js"; import type { BookMeta } from "../../main/project.js"; -import { alertMessage, confirmMessage, promptInput } from "../dialog.js"; +import { alertMessage, promptInput } from "../dialog.js"; import { $, showUndoToast, showView } from "./ui.js"; export interface WelcomeFeature { @@ -221,28 +221,6 @@ export function createWelcomeFeature(ctx: BookCtx): WelcomeFeature { // double-click or Enter opens the book. Replaces the old always-on // "×" button, which deleted on a mis-click. label.addEventListener("click", () => selectRecent(li)); - // Right-click (two-finger tap) on a recents row opens a dedicated - // context menu (Open / Rename / Hide / Delete / Show in Folder). The - // renderer claims the event synchronously (sendSync) so the main - // process skips the generic spelling/edit menu for this target; if the - // claim ever fails, fall back to selecting the row inline. - li.addEventListener("contextmenu", (e) => { - e.preventDefault(); - try { - const claimed = api.claimRecentsContext( - r.path, - r.title || r.path, - !!r.hidden, - !!r.missing, - e.clientX, - e.clientY - ); - if (claimed) return; - } catch { - /* fall through to inline selection */ - } - selectRecent(li); - }); li.addEventListener("dblclick", () => void openPath(r.path)); li.addEventListener("keydown", (e) => { if (e.key === "Enter") void openPath(r.path); @@ -385,61 +363,6 @@ export function createWelcomeFeature(ctx: BookCtx): WelcomeFeature { if (state.currentBook) void api.revealInFolder(state.currentBook.path); }); - // Right-click menu actions (popped by the main process; see - // folio:recents-contextmenu-sync). Runs the same operations as the row - // buttons so behavior is identical from either surface. - api.onRecentsMenu(async ({ action, entry }) => { - switch (action) { - case "open": - await openPath(entry.path); - return; - case "rename": { - const name = await promptInput("Rename book", entry.title || ""); - if (name === null) return; - const trimmed = name.trim(); - if (!trimmed || trimmed === entry.title) return; - const res = await api.renameBook(entry.path, trimmed); - if (res && "error" in res) { - await alertMessage("Could not rename", (res as { error: string }).error); - return; - } - await renderRecents(); - return; - } - case "reveal": - await api.revealInFolder(entry.path); - return; - case "hide": - await api.hideRecent(entry.path); - await renderRecents(); - return; - case "unhide": - await api.unhideRecent(entry.path); - await renderRecents(); - return; - case "remove": - // Stale entry (book gone): no confirmation needed, nothing to lose. - await api.deleteBook(entry.path); - await renderRecents(); - return; - case "delete": { - const yes = await confirmMessage( - `Delete "${entry.title}"?`, - "The whole book folder moves to the system trash. Hide from Recents removes it from the list without touching the disk.", - "Move to Trash" - ); - if (!yes) return; - const res = await api.deleteBook(entry.path); - if (res && "error" in res) { - await alertMessage("Could not delete", (res as { error: string }).error); - return; - } - await renderRecents(); - return; - } - } - }); - return { showWelcome, showPlaceholder,