Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 20 additions & 11 deletions src/components/shell/hooks/useShellTerminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,21 +216,30 @@ export function useShellTerminal({
(event.ctrlKey || event.metaKey) &&
event.key?.toLowerCase() === 'v'
) {
// Without the async clipboard API (common on self-hosted plain-http
// deployments, where navigator.clipboard doesn't exist) there is
// nothing to read here — let the event through so the browser's native
// paste event can still reach xterm's hidden textarea, instead of
// swallowing the keystroke and pasting nothing.
if (typeof navigator === 'undefined' || !navigator.clipboard?.readText) {
return true;
}

// Block native paste so data is only injected after clipboard-read resolves.
event.preventDefault();
event.stopPropagation();

if (typeof navigator !== 'undefined' && navigator.clipboard?.readText) {
navigator.clipboard
.readText()
.then((text) => {
sendSocketMessage(wsRef.current, {
type: 'input',
data: text,
});
})
.catch(() => {});
}
navigator.clipboard
.readText()
.then((text) => {
// terminal.paste() applies bracketed-paste wrapping (DECSET 2004)
// and \n -> \r normalization, then emits onData, which flows to
// the pty through the existing socket input subscription. Sending
// the raw text as an input message bypassed bracketed paste, so
// multi-line pastes executed line-by-line in shells and CLIs.
nextTerminal.paste(text);
})
.catch(() => {});

return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,16 @@ export default function TerminalShortcutsPanel({
try {
const text = await navigator.clipboard.readText();
if (text.length > 0) {
sendInput(text);
// terminal.paste() applies bracketed-paste wrapping and \n -> \r
// normalization before the text reaches the pty via onData; sending
// raw text as an input message made multi-line pastes execute
// line-by-line.
terminalRef.current?.paste(text);
}
} catch {
// Ignore clipboard permission errors.
}
}, [sendInput]);
}, [terminalRef]);

const handleKeyPress = useCallback(
(seq: string) => {
Expand Down