Fix desktop terminal link and context menu behavior
This commit is contained in:
@@ -565,6 +565,18 @@ export default function TerminalEmulator({
|
|||||||
const handleInsetTop = Math.max(0, (thumbRegionHeight - scrollbarGeometry.handleSize) / 2);
|
const handleInsetTop = Math.max(0, (thumbRegionHeight - scrollbarGeometry.handleSize) / 2);
|
||||||
const handleTravelDurationMs =
|
const handleTravelDurationMs =
|
||||||
isDraggingScrollbar || isScrollActive ? 0 : SCROLLBAR_HANDLE_TRAVEL_DURATION_MS;
|
isDraggingScrollbar || isScrollActive ? 0 : SCROLLBAR_HANDLE_TRAVEL_DURATION_MS;
|
||||||
|
const handleContextMenu = () => {
|
||||||
|
const showContextMenu = window.paseoDesktop?.menu?.showContextMenu;
|
||||||
|
if (typeof showContextMenu !== "function") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasSelection = Boolean(window.getSelection()?.toString());
|
||||||
|
void showContextMenu({
|
||||||
|
kind: "terminal",
|
||||||
|
hasSelection,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -586,6 +598,10 @@ export default function TerminalEmulator({
|
|||||||
onPointerDown={() => {
|
onPointerDown={() => {
|
||||||
runtimeRef.current?.focus();
|
runtimeRef.current?.focus();
|
||||||
}}
|
}}
|
||||||
|
onContextMenu={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
handleContextMenu();
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
ref={hostRef}
|
ref={hostRef}
|
||||||
|
|||||||
@@ -37,6 +37,13 @@ export interface DesktopOpenerBridge {
|
|||||||
openUrl?: (url: string) => Promise<void>;
|
openUrl?: (url: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DesktopMenuBridge {
|
||||||
|
showContextMenu?: (input?: {
|
||||||
|
kind?: "terminal";
|
||||||
|
hasSelection?: boolean;
|
||||||
|
}) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
export interface DesktopWindowBridge {
|
export interface DesktopWindowBridge {
|
||||||
label?: string;
|
label?: string;
|
||||||
startMove?: (screenX: number, screenY: number) => void;
|
startMove?: (screenX: number, screenY: number) => void;
|
||||||
@@ -73,6 +80,7 @@ export interface DesktopHostBridge {
|
|||||||
dialog?: DesktopDialogBridge;
|
dialog?: DesktopDialogBridge;
|
||||||
notification?: DesktopNotificationBridge;
|
notification?: DesktopNotificationBridge;
|
||||||
opener?: DesktopOpenerBridge;
|
opener?: DesktopOpenerBridge;
|
||||||
|
menu?: DesktopMenuBridge;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { WebLinksAddon } from "@xterm/addon-web-links";
|
|||||||
import { WebglAddon } from "@xterm/addon-webgl";
|
import { WebglAddon } from "@xterm/addon-webgl";
|
||||||
import { Terminal, type ITheme } from "@xterm/xterm";
|
import { Terminal, type ITheme } from "@xterm/xterm";
|
||||||
import type { TerminalState } from "@server/shared/messages";
|
import type { TerminalState } from "@server/shared/messages";
|
||||||
|
import { openExternalUrl } from "@/utils/open-external-url";
|
||||||
import {
|
import {
|
||||||
type PendingTerminalModifiers,
|
type PendingTerminalModifiers,
|
||||||
isTerminalModifierDomKey,
|
isTerminalModifierDomKey,
|
||||||
@@ -168,7 +169,12 @@ export class TerminalEmulatorRuntime {
|
|||||||
let webglAddon: WebglAddon | null = null;
|
let webglAddon: WebglAddon | null = null;
|
||||||
terminal.loadAddon(fitAddon);
|
terminal.loadAddon(fitAddon);
|
||||||
terminal.loadAddon(unicode11Addon);
|
terminal.loadAddon(unicode11Addon);
|
||||||
terminal.loadAddon(new WebLinksAddon());
|
terminal.loadAddon(
|
||||||
|
new WebLinksAddon((event, uri) => {
|
||||||
|
event.preventDefault();
|
||||||
|
void openExternalUrl(uri);
|
||||||
|
}),
|
||||||
|
);
|
||||||
terminal.loadAddon(new SearchAddon({ highlightLimit: 20_000 }));
|
terminal.loadAddon(new SearchAddon({ highlightLimit: 20_000 }));
|
||||||
terminal.loadAddon(new ClipboardAddon());
|
terminal.loadAddon(new ClipboardAddon());
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
import { app, Menu, BrowserWindow } from "electron";
|
import { app, Menu, BrowserWindow, ipcMain } from "electron";
|
||||||
|
|
||||||
|
type ShowContextMenuInput = {
|
||||||
|
kind?: "terminal";
|
||||||
|
hasSelection?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
function withBrowserWindow(
|
function withBrowserWindow(
|
||||||
callback: (win: BrowserWindow) => void,
|
callback: (win: BrowserWindow) => void,
|
||||||
@@ -89,4 +94,36 @@ export function setupApplicationMenu(): void {
|
|||||||
|
|
||||||
const menu = Menu.buildFromTemplate(template);
|
const menu = Menu.buildFromTemplate(template);
|
||||||
Menu.setApplicationMenu(menu);
|
Menu.setApplicationMenu(menu);
|
||||||
|
|
||||||
|
ipcMain.handle("paseo:menu:showContextMenu", (event, input?: ShowContextMenuInput) => {
|
||||||
|
const win = BrowserWindow.fromWebContents(event.sender);
|
||||||
|
if (!win) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input?.kind !== "terminal") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const menu = Menu.buildFromTemplate([
|
||||||
|
{
|
||||||
|
label: "Copy",
|
||||||
|
role: "copy",
|
||||||
|
enabled: input.hasSelection === true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Paste",
|
||||||
|
role: "paste",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "separator",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Select All",
|
||||||
|
role: "selectAll",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
menu.popup({ window: win });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,4 +51,8 @@ contextBridge.exposeInMainWorld("paseoDesktop", {
|
|||||||
opener: {
|
opener: {
|
||||||
openUrl: (url: string) => ipcRenderer.invoke("paseo:opener:openUrl", url),
|
openUrl: (url: string) => ipcRenderer.invoke("paseo:opener:openUrl", url),
|
||||||
},
|
},
|
||||||
|
menu: {
|
||||||
|
showContextMenu: (input?: Record<string, unknown>) =>
|
||||||
|
ipcRenderer.invoke("paseo:menu:showContextMenu", input),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user