Skip to content

Commit fe143df

Browse files
authored
fix(ui): fallback to execCommand for clipboard copy when navigator.clipboard fails (anomalyco#27993)
Co-authored-by: SpiritChen51 <spiritchen51@users.noreply.github.com>
1 parent e94aeca commit fe143df

1 file changed

Lines changed: 33 additions & 9 deletions

File tree

packages/ui/src/components/message-part.tsx

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,27 @@ import { animate } from "motion"
5858
import { useLocation } from "@solidjs/router"
5959
import { attached, inline, kind } from "./message-file"
6060

61+
async function writeClipboard(text: string): Promise<boolean> {
62+
const body = typeof document === "undefined" ? undefined : document.body
63+
if (body) {
64+
const textarea = document.createElement("textarea")
65+
textarea.value = text
66+
textarea.setAttribute("readonly", "")
67+
textarea.style.position = "fixed"
68+
textarea.style.opacity = "0"
69+
textarea.style.pointerEvents = "none"
70+
body.appendChild(textarea)
71+
textarea.select()
72+
const copied = document.execCommand("copy")
73+
body.removeChild(textarea)
74+
if (copied) return true
75+
}
76+
77+
const clipboard = typeof navigator === "undefined" ? undefined : navigator.clipboard
78+
if (!clipboard?.writeText) return false
79+
return clipboard.writeText(text).then(() => true, () => false)
80+
}
81+
6182
function ShellSubmessage(props: { text: string; animate?: boolean }) {
6283
let widthRef: HTMLSpanElement | undefined
6384
let valueRef: HTMLSpanElement | undefined
@@ -1064,9 +1085,10 @@ export function UserMessageDisplay(props: { message: UserMessage; parts: PartTyp
10641085
const handleCopy = async () => {
10651086
const content = text()
10661087
if (!content) return
1067-
await navigator.clipboard.writeText(content)
1068-
setState("copied", true)
1069-
setTimeout(() => setState("copied", false), 2000)
1088+
if (await writeClipboard(content)) {
1089+
setState("copied", true)
1090+
setTimeout(() => setState("copied", false), 2000)
1091+
}
10701092
}
10711093

10721094
const revert = () => {
@@ -1490,9 +1512,10 @@ PART_MAPPING["text"] = function TextPartDisplay(props) {
14901512
const handleCopy = async () => {
14911513
const content = text()
14921514
if (!content) return
1493-
await navigator.clipboard.writeText(content)
1494-
setCopied(true)
1495-
setTimeout(() => setCopied(false), 2000)
1515+
if (await writeClipboard(content)) {
1516+
setCopied(true)
1517+
setTimeout(() => setCopied(false), 2000)
1518+
}
14961519
}
14971520

14981521
return (
@@ -1834,9 +1857,10 @@ ToolRegistry.register({
18341857
const handleCopy = async () => {
18351858
const content = text()
18361859
if (!content) return
1837-
await navigator.clipboard.writeText(content)
1838-
setCopied(true)
1839-
setTimeout(() => setCopied(false), 2000)
1860+
if (await writeClipboard(content)) {
1861+
setCopied(true)
1862+
setTimeout(() => setCopied(false), 2000)
1863+
}
18401864
}
18411865

18421866
return (

0 commit comments

Comments
 (0)