From 310391726f25018feb41707480e75528501ea8b5 Mon Sep 17 00:00:00 2001 From: Silberengel Date: Thu, 9 Apr 2026 09:43:13 +0200 Subject: [PATCH] correct highlights --- electron/main.cjs | 34 ++++++++++++++++++- .../Note/SelectionHighlightTrigger.tsx | 16 +++++++-- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/electron/main.cjs b/electron/main.cjs index 49836995..aafbc408 100644 --- a/electron/main.cjs +++ b/electron/main.cjs @@ -1,6 +1,6 @@ 'use strict' -const { app, BrowserWindow, ipcMain, shell } = require('electron') +const { app, BrowserWindow, ipcMain, shell, Menu } = require('electron') const fs = require('fs') const path = require('path') @@ -59,6 +59,38 @@ function createWindow() { } return { action: 'deny' } }) + + // Electron does not show Chromium’s full-page context menu; without this, users only get in-app UI + // (e.g. selection highlight) and no Copy / Paste / Select all for text fields and content. + win.webContents.on('context-menu', (_event, params) => { + const template = [] + + if (params.linkURL && /^https?:\/\//i.test(params.linkURL)) { + template.push({ + label: 'Open link in browser', + click: () => void shell.openExternal(params.linkURL) + }) + template.push({ type: 'separator' }) + } + + template.push( + { role: 'cut', enabled: params.editFlags.canCut }, + { role: 'copy', enabled: params.editFlags.canCopy }, + { role: 'paste', enabled: params.editFlags.canPaste }, + { type: 'separator' }, + { role: 'selectAll', enabled: params.editFlags.canSelectAll } + ) + + if (isDev) { + template.push({ type: 'separator' }) + template.push({ + label: 'Inspect element', + click: () => win.webContents.inspectElement(params.x, params.y) + }) + } + + Menu.buildFromTemplate(template).popup({ window: win }) + }) } app.whenReady().then(() => { diff --git a/src/components/Note/SelectionHighlightTrigger.tsx b/src/components/Note/SelectionHighlightTrigger.tsx index 4641e097..d210ceda 100644 --- a/src/components/Note/SelectionHighlightTrigger.tsx +++ b/src/components/Note/SelectionHighlightTrigger.tsx @@ -1,4 +1,5 @@ import { buildHighlightDataFromEvent } from '@/lib/build-highlight-data' +import { isMobileBrowserProfile } from '@/lib/client-platform' import { useCreateHighlight } from './CreateHighlightContext' import { Event } from 'nostr-tools' import { Highlighter } from 'lucide-react' @@ -90,10 +91,10 @@ export default function SelectionHighlightTrigger({ } // Mobile: finger lifts — wait for the browser to settle the selection, then evaluate. - // The 600 ms matches the delay used in RssFeedItem for the same reason. + // Shorter delay on coarse pointers; contextmenu (below) is the reliable path when the OS shows the callout. const onTouchEnd = () => { isTouchActiveRef.current = false - schedule(600) + schedule(isMobileBrowserProfile() ? 280 : 600) } // Both: covers keyboard selection (Shift+Arrow) on desktop and selection-handle @@ -103,14 +104,25 @@ export default function SelectionHighlightTrigger({ schedule(80) } + // When the system opens the text callout / context menu, selection is still valid here; delayed + // touchend/selectionchange often misses on iOS/Android because the selection is cleared before we run. + const onContextMenu = (e: MouseEvent) => { + if (!containerRef.current) return + const t = e.target + if (!(t instanceof Node) || !containerRef.current.contains(t)) return + queueMicrotask(() => evaluateSelection()) + } + document.addEventListener('touchstart', onTouchStart, { passive: true }) document.addEventListener('touchend', onTouchEnd, { passive: true }) document.addEventListener('selectionchange', onSelectionChange) + document.addEventListener('contextmenu', onContextMenu) return () => { document.removeEventListener('touchstart', onTouchStart) document.removeEventListener('touchend', onTouchEnd) document.removeEventListener('selectionchange', onSelectionChange) + document.removeEventListener('contextmenu', onContextMenu) if (debounceRef.current) clearTimeout(debounceRef.current) } }, [openHighlight, evaluateSelection])