/** * @file clipboard.ts * @description `copyText(text)` — the one copy path for every "Copy …" action. * Uses the async Clipboard API when it exists and falls back to a hidden * textarea + `execCommand("copy")`, because `navigator.clipboard` is undefined * on a non-secure origin such as `http://:4820`. Reports the outcome as * a toast, so a copy is never silent. * @author Nguyễn Ngọc Trí Vĩ */ import i18n from "../i18n"; import { toast } from "./toast"; function fallbackCopy(text: string): boolean { const ta = document.createElement("textarea"); ta.value = text; ta.setAttribute("readonly", ""); ta.style.position = "fixed"; ta.style.opacity = "0"; document.body.appendChild(ta); ta.select(); try { return document.execCommand("copy"); } catch { return false; } finally { ta.remove(); } } /** * Copy text to the clipboard and toast the result. * @param quiet Skip the success toast (the caller shows its own feedback). * @returns Whether the copy succeeded. */ export async function copyText(text: string, quiet = false): Promise { let ok = false; try { if (navigator.clipboard?.writeText) { await navigator.clipboard.writeText(text); ok = true; } } catch { /* fall through to the textarea path */ } if (!ok) ok = fallbackCopy(text); if (!ok) toast.error(i18n.t("common:copyFailed")); else if (!quiet) toast.success(i18n.t("common:copied")); return ok; }