From a82a225d23613416d2411adbbf26256d302879dc Mon Sep 17 00:00:00 2001 From: Haileyesus Date: Thu, 12 Feb 2026 21:47:59 +0300 Subject: [PATCH] refactor(OneLineDisplay): improve clipboard functionality with fallback for unsupported environments --- .../chat/tools/components/OneLineDisplay.tsx | 55 ++++++++++++++++++- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/src/components/chat/tools/components/OneLineDisplay.tsx b/src/components/chat/tools/components/OneLineDisplay.tsx index c9e3355..52fc0f1 100644 --- a/src/components/chat/tools/components/OneLineDisplay.tsx +++ b/src/components/chat/tools/components/OneLineDisplay.tsx @@ -25,6 +25,52 @@ interface OneLineDisplayProps { toolId?: string; } +// Fallback for environments where the async Clipboard API is unavailable or blocked. +const copyWithLegacyExecCommand = (text: string): boolean => { + if (typeof document === 'undefined' || !document.body) { + return false; + } + + const textarea = document.createElement('textarea'); + textarea.value = text; + textarea.setAttribute('readonly', ''); + textarea.style.position = 'fixed'; + textarea.style.opacity = '0'; + textarea.style.left = '-9999px'; + document.body.appendChild(textarea); + textarea.select(); + textarea.setSelectionRange(0, text.length); + + let copied = false; + try { + copied = document.execCommand('copy'); + } catch { + copied = false; + } finally { + document.body.removeChild(textarea); + } + + return copied; +}; + +const copyTextToClipboard = async (text: string): Promise => { + if ( + typeof navigator !== 'undefined' && + typeof window !== 'undefined' && + window.isSecureContext && + navigator.clipboard?.writeText + ) { + try { + await navigator.clipboard.writeText(text); + return true; + } catch { + // Fall back below when writeText is rejected (permissions/insecure contexts/browser limits). + } + } + + return copyWithLegacyExecCommand(text); +}; + /** * Unified one-line display for simple tool inputs and results * Used by: Bash, Read, Grep/Glob (minimized), TodoRead, etc. @@ -53,9 +99,12 @@ export const OneLineDisplay: React.FC = ({ const [copied, setCopied] = useState(false); const isTerminal = style === 'terminal'; - const handleAction = () => { + const handleAction = async () => { if (action === 'copy' && value) { - navigator.clipboard.writeText(value); + const didCopy = await copyTextToClipboard(value); + if (!didCopy) { + return; + } setCopied(true); setTimeout(() => setCopied(false), 2000); } else if (onAction) { @@ -181,4 +230,4 @@ export const OneLineDisplay: React.FC = ({ {action === 'copy' && renderCopyButton()} ); -}; \ No newline at end of file +};