Copy a message to the clipboard from the chat

Bot and user messages get a quiet copy button under them: the copy icon becomes a check for a moment after it copies the message's text as stored, markdown and refs included. A message still on its way to the server can be copied too, though reply and reactions wait for its id. The async clipboard API is used where it exists and a hidden textarea where a plain HTTP install has none.
This commit is contained in:
2026-10-22 11:35:07 +01:00
parent 080f8e9abc
commit 5a9f6d938a
5 changed files with 139 additions and 13 deletions
+49
View File
@@ -0,0 +1,49 @@
type Params = {
text: string;
};
/**
* Copies text to the clipboard and says whether it worked. The async
* clipboard API needs a secure context, so a plain HTTP address (a VPN or
* LAN install) falls back to copying from a temporary textarea.
*/
export default async function copyText({ text }: Params): Promise<boolean> {
if (!text) {
return false;
}
if (navigator.clipboard?.writeText) {
try {
await navigator.clipboard.writeText(text);
return true;
} catch {
// Fall through to the textarea, which can still work.
}
}
return copyWithTextarea({ text });
}
/**
* The pre-clipboard-API way: select a hidden textarea and copy it. The
* element is off screen rather than hidden, since `display: none` is not
* selectable, and it is removed again either way.
*/
function copyWithTextarea({ text }: Params): boolean {
const textarea = document.createElement("textarea");
textarea.value = text;
textarea.setAttribute("readonly", "");
textarea.className = "sr-only";
document.body.appendChild(textarea);
textarea.select();
try {
return document.execCommand("copy");
} catch {
return false;
} finally {
textarea.remove();
}
}