Файли
openidea/src/workbench/browserGuards.ts
T
l0ng-ai de84a643aa fix(ui): Make read-only content selectable with a Copy context menu (#48)
Root cause: body is user-select: none and each feature had to opt back in; the Markdown preview (and other read-only views) never did, and the app-wide contextmenu guard left no Copy item.
Fix: shared selectableText class (src/ui) for read-only content, and a fallback Copy context menu from browserGuards when right-clicking a selection or a selectable area (component menus still win).
Siblings covered: Quick Documentation / hover / completion docs, hover errors, Quick Definition, debugger value tooltip, inspection messages, message dialogs (no drag on body text), notification titles, About dialog; existing selectable consoles now get Copy too.
Verified in-app on macOS: select in Markdown preview, right-click Copy puts the text on the clipboard; tab context menu unchanged.

Claude-Session: https://claude.ai/code/session_01VUYsdXA2nkF5v6zsC93dpn
2026-10-27 08:56:13 +08:00

71 рядки
2.4 KiB
TypeScript

/**
* Makes the webview behave like a desktop app: never the browser context menu
* (its Reload/AutoFill items would act on the webview), no reload/print/
* save-page/history shortcuts, no navigation on file drop. Copy/paste/undo
* shortcuts stay untouched. A right-click no component handled on selected or
* selectable read-only text gets IDEA's Copy menu instead of nothing.
*/
import { copyMenuFor, showContextMenu, useContextMenuStore } from "@/ui";
const BLOCKED = new Set([
"Meta+P",
"Ctrl+P",
"Meta+S",
"Meta+Shift+S",
"Meta+O",
"Meta+G",
"Meta+Shift+G",
"Meta+[",
"Meta+]",
"Meta+Alt+I",
"Meta+U",
]);
const RELOAD = new Set(["Meta+R", "Meta+Shift+R", "Ctrl+R", "Ctrl+Shift+R", "F5", "Shift+F5"]);
function keyId(e: KeyboardEvent) {
const key = e.code.startsWith("Key")
? e.code.slice(3)
: e.code === "BracketLeft"
? "["
: e.code === "BracketRight"
? "]"
: e.key;
return [e.ctrlKey && "Ctrl", e.altKey && "Alt", e.shiftKey && "Shift", e.metaKey && "Meta", key]
.filter(Boolean)
.join("+");
}
export function installBrowserGuards(opts: { blockReload?: boolean } = {}): () => void {
const blockReload = opts.blockReload ?? import.meta.env.PROD;
let menuBefore: unknown = null;
const beforeContextMenu = () => {
menuBefore = useContextMenuStore.getState().menu;
};
const onContextMenu = (e: MouseEvent) => {
const handled = e.defaultPrevented || useContextMenuStore.getState().menu !== menuBefore;
e.preventDefault();
if (handled) return;
const items = copyMenuFor(e.target);
if (items) showContextMenu(e, items);
};
const onKeyDown = (e: KeyboardEvent) => {
const id = keyId(e);
if (BLOCKED.has(id) || (blockReload && RELOAD.has(id))) e.preventDefault();
};
const onDrag = (e: DragEvent) => {
if (!(e.target as Element | null)?.closest?.("[data-drop-target]")) e.preventDefault();
};
window.addEventListener("contextmenu", beforeContextMenu, true);
window.addEventListener("contextmenu", onContextMenu);
window.addEventListener("keydown", onKeyDown);
window.addEventListener("dragover", onDrag);
window.addEventListener("drop", onDrag);
return () => {
window.removeEventListener("contextmenu", beforeContextMenu, true);
window.removeEventListener("contextmenu", onContextMenu);
window.removeEventListener("keydown", onKeyDown);
window.removeEventListener("dragover", onDrag);
window.removeEventListener("drop", onDrag);
};
}