diff --git a/ui/src/components/Message.tsx b/ui/src/components/Message.tsx index 5f9d64c..0988860 100644 --- a/ui/src/components/Message.tsx +++ b/ui/src/components/Message.tsx @@ -1,4 +1,5 @@ import React, { useState, useRef, useEffect } from "react"; +import { copyToClipboard } from "../utils/clipboard"; import { linkifyText } from "../utils/linkify"; import { Message as MessageType, @@ -124,7 +125,7 @@ function GitInfoMessage({ const handleCopyHash = (e: React.MouseEvent) => { e.preventDefault(); if (commitHash) { - navigator.clipboard.writeText(commitHash).then(() => { + copyToClipboard(commitHash).then(() => { setCopied(true); setTimeout(() => setCopied(false), 1500); }); @@ -416,7 +417,7 @@ function Message({ message, onOpenDiffViewer, onCommentTextChange }: MessageProp const handleCopy = () => { const text = getMessageText(); if (text) { - navigator.clipboard.writeText(text).catch((err) => { + copyToClipboard(text).catch((err) => { console.error("Failed to copy text:", err); }); } diff --git a/ui/src/components/TerminalPanel.tsx b/ui/src/components/TerminalPanel.tsx index 295842b..e56593f 100644 --- a/ui/src/components/TerminalPanel.tsx +++ b/ui/src/components/TerminalPanel.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useRef, useState, useCallback } from "react"; import { Terminal } from "@xterm/xterm"; import { FitAddon } from "@xterm/addon-fit"; import { WebLinksAddon } from "@xterm/addon-web-links"; +import { copyToClipboard } from "../utils/clipboard"; import { isDarkModeActive } from "../services/theme"; import "@xterm/xterm/css/xterm.css"; @@ -441,12 +442,12 @@ export default function TerminalPanel({ ); const copyScreen = useCallback(() => { - navigator.clipboard.writeText(getBufferText("screen")); + copyToClipboard(getBufferText("screen")); showFeedback("copyScreen"); }, [getBufferText, showFeedback]); const copyAll = useCallback(() => { - navigator.clipboard.writeText(getBufferText("all")); + copyToClipboard(getBufferText("all")); showFeedback("copyAll"); }, [getBufferText, showFeedback]); diff --git a/ui/src/utils/clipboard.ts b/ui/src/utils/clipboard.ts new file mode 100644 index 0000000..21c96d2 --- /dev/null +++ b/ui/src/utils/clipboard.ts @@ -0,0 +1,21 @@ +// Copy text to clipboard. +// Uses the modern Clipboard API when available (secure contexts), +// otherwise falls back to execCommand for non-secure contexts (e.g. HTTP on non-localhost). +export async function copyToClipboard(text: string): Promise { + if (navigator.clipboard) { + return navigator.clipboard.writeText(text); + } + + // Non-secure context: use textarea + execCommand + const textarea = document.createElement("textarea"); + textarea.value = text; + textarea.style.position = "fixed"; + textarea.style.opacity = "0"; + document.body.appendChild(textarea); + textarea.select(); + try { + document.execCommand("copy"); + } finally { + document.body.removeChild(textarea); + } +}