feat: implement clipboard utility for copying text and update components to use it

This commit is contained in:
Anders Böttcher
2026-05-13 17:19:43 +02:00
parent f7c52d0b1c
commit ffbbf1b6eb
3 changed files with 41 additions and 16 deletions
+5 -4
View File
@@ -2,6 +2,7 @@
import { useEffect, useState } from "react";
import { LINKS } from "@/lib/links";
import { copyText } from "@/lib/clipboard";
export interface TerminalLinks {
dockerImage?: string;
@@ -254,10 +255,10 @@ export default function TerminalTyper({ mode = "docker-run", links = {}, casaosM
.join("\n");
}
navigator.clipboard.writeText(textToCopy).then(() => {
copyText(textToCopy).then(() => {
setCopied(true);
setTimeout(() => setCopied(false), 2000);
});
}).catch(() => {});
};
const copyCasaOs = () => {
@@ -265,10 +266,10 @@ export default function TerminalTyper({ mode = "docker-run", links = {}, casaosM
const text = CASAOS_LINES.filter(line => line.type === "output")
.map(line => line.text)
.join("\n");
navigator.clipboard.writeText(text).then(() => {
copyText(text).then(() => {
setCopiedCasaOs(true);
setTimeout(() => setCopiedCasaOs(false), 2000);
});
}).catch(() => {});
};
useEffect(() => {
+3 -12
View File
@@ -1,6 +1,7 @@
"use client";
import { useState } from "react";
import { copyText } from "@/lib/clipboard";
export default function CopyLinkButton({ token }: { token: string }) {
const [copied, setCopied] = useState(false);
@@ -8,21 +9,11 @@ export default function CopyLinkButton({ token }: { token: string }) {
async function copy() {
const url = `${window.location.origin}/files/${token}`;
try {
await navigator.clipboard.writeText(url);
await copyText(url);
setCopied(true);
setTimeout(() => setCopied(false), 2500);
} catch {
// Fallback for browsers that deny clipboard access
const el = document.createElement("textarea");
el.value = url;
el.style.position = "fixed";
el.style.opacity = "0";
document.body.appendChild(el);
el.select();
document.execCommand("copy");
document.body.removeChild(el);
setCopied(true);
setTimeout(() => setCopied(false), 2500);
// copy completely unavailable (e.g. sandboxed iframe) — do nothing
}
}
+33
View File
@@ -0,0 +1,33 @@
/**
* Copies text to the clipboard.
*
* Tries the modern Clipboard API first (requires secure context / HTTPS).
* Falls back to the legacy textarea + execCommand technique, which works on
* plain HTTP and private-IP origins where navigator.clipboard is unavailable.
*/
export async function copyText(text: string): Promise<void> {
// Modern path — secure context (HTTPS or localhost)
if (typeof navigator !== 'undefined' && navigator.clipboard?.writeText) {
try {
await navigator.clipboard.writeText(text)
return
} catch {
// Fall through to legacy method
}
}
// Legacy fallback — works on HTTP / private IPs
const el = document.createElement('textarea')
el.value = text
// Keep it out of the viewport so it doesn't flash
el.style.cssText = 'position:fixed;top:0;left:0;width:1px;height:1px;opacity:0;pointer-events:none;'
document.body.appendChild(el)
el.focus()
el.select()
try {
// eslint-disable-next-line @typescript-eslint/no-deprecated
document.execCommand('copy')
} finally {
document.body.removeChild(el)
}
}