From 14b92b33a44b42b71ae35ca7caf1d9c5e092d351 Mon Sep 17 00:00:00 2001 From: Jokin Date: Mon, 13 Jul 2026 23:30:23 +0200 Subject: [PATCH] Suppress the webview's default right-click menu Reload/Inspect Element no longer appear on uncontrolled right-clicks in any window; text fields keep the system text menu. Co-Authored-By: Claude Fable 5 --- ui/src/app/connection-manager/connection-manager.ts | 11 +++++++++-- ui/src/app/editor-window/editor-window.ts | 12 ++++++++++-- ui/src/app/platform.ts | 11 +++++++++++ 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/ui/src/app/connection-manager/connection-manager.ts b/ui/src/app/connection-manager/connection-manager.ts index 28a9ca0..8e5423f 100644 --- a/ui/src/app/connection-manager/connection-manager.ts +++ b/ui/src/app/connection-manager/connection-manager.ts @@ -5,7 +5,7 @@ import { ConnectionsService } from '../connections.service'; import { ConfirmDialog } from '../dialogs/confirm-dialog'; import { PromptDialog } from '../dialogs/prompt-dialog'; import { DialogsService } from '../dialogs.service'; -import { IS_MAC_CHROME } from '../platform'; +import { blockNativeContextMenu, IS_MAC_CHROME } from '../platform'; import { RpcService } from '../rpc.service'; import { SettingsService } from '../settings.service'; import { Toasts } from '../toasts/toasts'; @@ -73,7 +73,10 @@ function fromDraft(d: Draft): SavedConnection { selector: 'app-root', imports: [NgIcon, ConfirmDialog, PromptDialog, Toasts], templateUrl: './connection-manager.html', - host: { '(window:keydown.escape)': 'onEscape()' }, + host: { + '(window:keydown.escape)': 'onEscape()', + '(window:contextmenu)': 'blockContextMenu($event)', + }, }) export class ConnectionManager { readonly store = inject(ConnectionsService); @@ -138,6 +141,10 @@ export class ConnectionManager { if (!this.dialogs.anyOpen) this.close(); } + blockContextMenu(event: Event): void { + blockNativeContextMenu(event); + } + async save(): Promise { const d = this.draft(); if (!d || !this.valid()) return null; diff --git a/ui/src/app/editor-window/editor-window.ts b/ui/src/app/editor-window/editor-window.ts index 4d40203..48ae671 100644 --- a/ui/src/app/editor-window/editor-window.ts +++ b/ui/src/app/editor-window/editor-window.ts @@ -10,7 +10,7 @@ import { viewChild, } from '@angular/core'; import { NgIcon } from '@ng-icons/core'; -import { EDITOR_CONTEXT, IS_MAC_CHROME } from '../platform'; +import { blockNativeContextMenu, EDITOR_CONTEXT, IS_MAC_CHROME } from '../platform'; import { RpcService } from '../rpc.service'; import { SettingsService } from '../settings.service'; import { ToastService } from '../toast.service'; @@ -31,7 +31,11 @@ const DRAFT_DEBOUNCE_MS = 400; selector: 'app-root', imports: [NgIcon, Toasts], templateUrl: './editor-window.html', - host: { '(window:keydown)': 'onKeydown($event)', '(window:blur)': 'flushDraft()' }, + host: { + '(window:keydown)': 'onKeydown($event)', + '(window:blur)': 'flushDraft()', + '(window:contextmenu)': 'blockContextMenu($event)', + }, }) export class EditorWindow { readonly ctx = EDITOR_CONTEXT; @@ -126,6 +130,10 @@ export class EditorWindow { queueMicrotask(() => this.box()?.nativeElement.focus()); } + blockContextMenu(event: Event): void { + blockNativeContextMenu(event); + } + onInput(value: string): void { this.text.set(value); this.scheduleDraft(); diff --git a/ui/src/app/platform.ts b/ui/src/app/platform.ts index 3a1722f..4a81697 100644 --- a/ui/src/app/platform.ts +++ b/ui/src/app/platform.ts @@ -23,3 +23,14 @@ export const APP_MODE: 'main' | 'editor' | 'manager' = export const EDITOR_CONTEXT = typeof window !== 'undefined' ? (window.__APP_CTX as EditorOpenContext | undefined) : undefined; + +/** + * Kill the webview's default right-click menu (Reload, Inspect Element…) + * everywhere the app doesn't show its own native menu. Text fields keep the + * system text menu (cut/copy/paste, spelling). + */ +export function blockNativeContextMenu(event: Event): void { + const target = event.target as HTMLElement | null; + if (target?.closest('input, textarea, [contenteditable="true"]')) return; + event.preventDefault(); +}