diff --git a/emain/emain.ts b/emain/emain.ts index be4fc6972..3e8c5c4bb 100644 --- a/emain/emain.ts +++ b/emain/emain.ts @@ -411,25 +411,6 @@ async function createNewWaveWindow() { electron.ipcMain.on("openNewWindow", createNewWaveWindow); -electron.ipcMain.on("context-editmenu", (_, { x, y }, opts) => { - if (opts == null) { - opts = {}; - } - console.log("context-editmenu"); - const menu = new electron.Menu(); - if (!opts.onlyPaste) { - if (opts.showCut) { - const menuItem = new electron.MenuItem({ label: "Cut", role: "cut" }); - menu.append(menuItem); - } - const menuItem = new electron.MenuItem({ label: "Copy", role: "copy" }); - menu.append(menuItem); - } - const menuItem = new electron.MenuItem({ label: "Paste", role: "paste" }); - menu.append(menuItem); - menu.popup({ x, y }); -}); - electron.ipcMain.on("contextmenu-show", (event, menuDefArr: ElectronContextMenuItem[], { x, y }) => { if (menuDefArr == null || menuDefArr.length == 0) { return; diff --git a/emain/preload.ts b/emain/preload.ts index 5e1bf2b3b..3f91a2df1 100644 --- a/emain/preload.ts +++ b/emain/preload.ts @@ -9,7 +9,6 @@ contextBridge.exposeInMainWorld("api", { getPlatform: () => ipcRenderer.sendSync("getPlatform"), getCursorPoint: () => ipcRenderer.sendSync("getCursorPoint"), openNewWindow: () => ipcRenderer.send("openNewWindow"), - contextEditMenu: (position, opts) => ipcRenderer.send("context-editmenu", position, opts), showContextMenu: (menu, position) => ipcRenderer.send("contextmenu-show", menu, position), onContextMenuClick: (callback) => ipcRenderer.on("contextmenu-click", callback), }); diff --git a/frontend/app/app.tsx b/frontend/app/app.tsx index 5906eec17..e19f6b4d9 100644 --- a/frontend/app/app.tsx +++ b/frontend/app/app.tsx @@ -4,7 +4,8 @@ import { Workspace } from "@/app/workspace/workspace"; import { getLayoutStateAtomForTab, globalLayoutTransformsMap } from "@/faraday/lib/layoutAtom"; import type { LayoutTreeState } from "@/faraday/lib/model"; -import { WOS, atoms, getApi, globalStore, setBlockFocus } from "@/store/global"; +import { ContextMenuModel } from "@/store/contextmenu"; +import { WOS, atoms, globalStore, setBlockFocus } from "@/store/global"; import * as services from "@/store/services"; import * as keyutil from "@/util/keyutil"; import * as util from "@/util/util"; @@ -27,27 +28,52 @@ const App = () => { ); }; -function handleContextMenu(e: React.MouseEvent) { - let isInNonTermInput = false; - const activeElem = document.activeElement; - if (activeElem != null && activeElem.nodeName == "TEXTAREA") { - if (!activeElem.classList.contains("xterm-helper-textarea")) { - isInNonTermInput = true; - } - } - if (activeElem != null && activeElem.nodeName == "INPUT" && activeElem.getAttribute("type") == "text") { - isInNonTermInput = true; - } - const opts: ContextMenuOpts = {}; - if (isInNonTermInput) { - opts.showCut = true; - } +function isContentEditableBeingEdited() { + const activeElement = document.activeElement; + return ( + activeElement && + activeElement.getAttribute("contenteditable") !== null && + activeElement.getAttribute("contenteditable") !== "false" + ); +} + +function canEnablePaste() { + const activeElement = document.activeElement; + return activeElement.tagName === "INPUT" || activeElement.tagName === "TEXTAREA" || isContentEditableBeingEdited(); +} + +function canEnableCopy() { const sel = window.getSelection(); - if (!util.isBlank(sel?.toString()) || isInNonTermInput) { - getApi().contextEditMenu({ x: e.clientX, y: e.clientY }, opts); - } else { - getApi().contextEditMenu({ x: e.clientX, y: e.clientY }, { onlyPaste: true }); + return !util.isBlank(sel?.toString()); +} + +function canEnableCut() { + const sel = window.getSelection(); + if (document.activeElement?.classList.contains("xterm-helper-textarea")) { + return false; } + return !util.isBlank(sel?.toString()) && canEnablePaste(); +} + +function handleContextMenu(e: React.MouseEvent) { + e.preventDefault(); + const canPaste = canEnablePaste(); + const canCopy = canEnableCopy(); + const canCut = canEnableCut(); + if (!canPaste && !canCopy && !canCut) { + return; + } + let menu: ContextMenuItem[] = []; + if (canCut) { + menu.push({ label: "Cut", role: "cut" }); + } + if (canCopy) { + menu.push({ label: "Copy", role: "copy" }); + } + if (canPaste) { + menu.push({ label: "Paste", role: "paste" }); + } + ContextMenuModel.showContextMenu(menu, e); } function switchTab(offset: number) { diff --git a/frontend/app/block/block.tsx b/frontend/app/block/block.tsx index ff5c68cc4..7e9e4bbcb 100644 --- a/frontend/app/block/block.tsx +++ b/frontend/app/block/block.tsx @@ -131,7 +131,7 @@ function getBlockHeaderText(blockIcon: string, blockData: Block, settings: Setti return [blockIconElem, viewString + blockIdStr]; } -function handleHeaderContextMenu(e: React.MouseEvent, onClose: () => void) { +function handleHeaderContextMenu(e: React.MouseEvent, blockData: Block, onClose: () => void) { e.preventDefault(); e.stopPropagation(); let menu: ContextMenuItem[] = []; @@ -148,6 +148,13 @@ function handleHeaderContextMenu(e: React.MouseEvent, onClose: ( }, }); menu.push({ type: "separator" }); + menu.push({ + label: "Copy BlockId", + click: () => { + navigator.clipboard.writeText(blockData.oid); + }, + }); + menu.push({ type: "separator" }); menu.push({ label: "Close Block", click: onClose, @@ -236,7 +243,7 @@ const BlockFrame_Tech = ({
handleHeaderContextMenu(e, onClose)} + onContextMenu={(e) => handleHeaderContextMenu(e, blockData, onClose)} > {getBlockHeaderText(blockIcon, blockData, settingsConfig)}
diff --git a/frontend/types/custom.d.ts b/frontend/types/custom.d.ts index f3bd797c5..0130794dd 100644 --- a/frontend/types/custom.d.ts +++ b/frontend/types/custom.d.ts @@ -6,11 +6,6 @@ declare global { blockId: string; }; - type ContextMenuOpts = { - showCut?: boolean; - onlyPaste?: boolean; - }; - type Bounds = { x: number; y: number; @@ -37,7 +32,6 @@ declare global { getPlatform: () => NodeJS.Platform; - contextEditMenu: (position: { x: number; y: number }, opts: ContextMenuOpts) => void; showContextMenu: (menu: ElectronContextMenuItem[], position: { x: number; y: number }) => void; onContextMenuClick: (callback: (id: string) => void) => void; };