From 5e51f5b356c543417aae140b0bce4ee53bf406cf Mon Sep 17 00:00:00 2001 From: Jiayuan Zhang Date: Fri, 24 Apr 2026 00:11:16 +0800 Subject: [PATCH] feat(desktop): add right-click context menu with clipboard actions (#1575) Co-authored-by: Lambda --- apps/desktop/src/main/context-menu.ts | 33 +++++++++++++++++++++++++++ apps/desktop/src/main/index.ts | 3 +++ 2 files changed, 36 insertions(+) create mode 100644 apps/desktop/src/main/context-menu.ts diff --git a/apps/desktop/src/main/context-menu.ts b/apps/desktop/src/main/context-menu.ts new file mode 100644 index 000000000..437b315b8 --- /dev/null +++ b/apps/desktop/src/main/context-menu.ts @@ -0,0 +1,33 @@ +import { BrowserWindow, Menu, MenuItem, type WebContents } from "electron"; + +// Electron ships with no default right-click menu, so a user selecting text +// in the renderer has no way to copy it. Mirror Chrome's minimal clipboard +// menu using `roles`, which keeps i18n + accelerator handling native. +export function installContextMenu(webContents: WebContents): void { + webContents.on("context-menu", (_event, params) => { + const { editFlags, selectionText, isEditable } = params; + const hasSelection = selectionText.trim().length > 0; + + const menu = new Menu(); + + if (isEditable && editFlags.canCut) { + menu.append(new MenuItem({ role: "cut" })); + } + if (hasSelection && editFlags.canCopy) { + menu.append(new MenuItem({ role: "copy" })); + } + if (isEditable && editFlags.canPaste) { + menu.append(new MenuItem({ role: "paste" })); + } + if (isEditable && editFlags.canSelectAll) { + if (menu.items.length > 0) { + menu.append(new MenuItem({ type: "separator" })); + } + menu.append(new MenuItem({ role: "selectAll" })); + } + + if (menu.items.length === 0) return; + const window = BrowserWindow.fromWebContents(webContents) ?? undefined; + menu.popup({ window }); + }); +} diff --git a/apps/desktop/src/main/index.ts b/apps/desktop/src/main/index.ts index 1b22f4f4d..970cafcde 100644 --- a/apps/desktop/src/main/index.ts +++ b/apps/desktop/src/main/index.ts @@ -6,6 +6,7 @@ import fixPath from "fix-path"; import { setupAutoUpdater } from "./updater"; import { setupDaemonManager } from "./daemon-manager"; import { openExternalSafely } from "./external-url"; +import { installContextMenu } from "./context-menu"; // Bundled icon used for dev-mode dock/taskbar branding. In production the // app bundle icon (from electron-builder) wins; this path is only consumed @@ -109,6 +110,8 @@ function createWindow(): void { return { action: "deny" }; }); + installContextMenu(mainWindow.webContents); + if (is.dev && process.env["ELECTRON_RENDERER_URL"]) { mainWindow.loadURL(process.env["ELECTRON_RENDERER_URL"]); } else {