fix(dashboard): paste on Ctrl+V and word-delete on Ctrl+Backspace/Delete
The dashboard chat is an xterm.js terminal in a browser tab, so several editing keys never reached the input: - Bare Ctrl+V fell through to the TUI, whose server-side clipboard read can't see the browser/OS clipboard, so it reported "No image found in clipboard". Route Ctrl+V through the same navigator.clipboard path as Ctrl+Shift+V (image-or-text). Fixes #24860. - Ctrl+Backspace / Ctrl+Delete never sent a word-delete: xterm.js emits bare DEL regardless of modifier. Send ^W (0x17) and Alt+d (ESC d) to the PTY so readline / prompt_toolkit delete the previous / next word. Ctrl+W itself stays unavailable in a browser tab: it is a reserved shortcut (close tab) that preventDefault cannot suppress. Ctrl+Backspace covers word-delete there; the Electron desktop app can bind Ctrl+W.
This commit is contained in:
@@ -670,7 +670,12 @@ export default function ChatPage({ isActive = true }: { isActive?: boolean }) {
|
||||
// react to the keypress.
|
||||
// Paste: Cmd+Shift+V on macOS, Ctrl+Shift+V on others.
|
||||
const copyModifier = isMac ? ev.metaKey : ev.ctrlKey && ev.shiftKey;
|
||||
const pasteModifier = isMac ? ev.metaKey : ev.ctrlKey && ev.shiftKey;
|
||||
// Paste on BARE Ctrl+V too (not only Ctrl+Shift+V). Bare Ctrl+V otherwise
|
||||
// falls through to the TUI, whose server-side clipboard read can't see the
|
||||
// browser/OS clipboard → "No image found in clipboard". Routing Ctrl+V
|
||||
// through the same navigator.clipboard path below makes it paste
|
||||
// image-or-text correctly, like Ctrl+Shift+V.
|
||||
const pasteModifier = isMac ? ev.metaKey : ev.ctrlKey;
|
||||
|
||||
if (copyModifier && ev.key.toLowerCase() === "c") {
|
||||
const sel = term.getSelection();
|
||||
@@ -690,6 +695,40 @@ export default function ChatPage({ isActive = true }: { isActive?: boolean }) {
|
||||
// (or the bare ev if the user used a different modifier).
|
||||
}
|
||||
|
||||
// Ctrl+Backspace → delete previous word. xterm.js sends bare DEL
|
||||
// regardless of modifier, so word-delete never reaches the TUI on its
|
||||
// own. Send ^W (0x17), which readline / prompt_toolkit treat as
|
||||
// delete-word-backward. (Ctrl+W can't be used in a browser tab — it's a
|
||||
// reserved shortcut that closes the tab and preventDefault has no effect;
|
||||
// for Ctrl+W muscle memory use the Electron desktop app.)
|
||||
if (
|
||||
ev.ctrlKey &&
|
||||
!ev.shiftKey &&
|
||||
!ev.altKey &&
|
||||
!ev.metaKey &&
|
||||
ev.key === "Backspace"
|
||||
) {
|
||||
ev.preventDefault();
|
||||
const ws = wsRef.current;
|
||||
if (ws && ws.readyState === WebSocket.OPEN) ws.send("\x17");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ctrl+Delete → delete next word. Mirror of Ctrl+Backspace; sends Alt+d
|
||||
// (ESC d), the readline / prompt_toolkit kill-word-forward binding.
|
||||
if (
|
||||
ev.ctrlKey &&
|
||||
!ev.shiftKey &&
|
||||
!ev.altKey &&
|
||||
!ev.metaKey &&
|
||||
ev.key === "Delete"
|
||||
) {
|
||||
ev.preventDefault();
|
||||
const ws = wsRef.current;
|
||||
if (ws && ws.readyState === WebSocket.OPEN) ws.send("\x1bd");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pasteModifier && ev.key.toLowerCase() === "v") {
|
||||
// preventDefault suppresses the DOM paste event, so image paste must
|
||||
// be handled here via clipboard.read() — readText() alone misses
|
||||
|
||||
Reference in New Issue
Block a user