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
@@ -1,6 +1,8 @@
import { memo } from "react";
import Row from "@moduletrace/twui/layout/Row";
import Stack from "@moduletrace/twui/layout/Stack";
import type { BotMessageWithAttachments } from "@/src/types";
import CopyMessageButton from "./copy-message-button";
import MessageAttachments from "./message-attachments";
import MessageReactions from "./message-reactions";
import MessageReplyQuote from "./message-reply-quote";
@@ -14,8 +16,8 @@ type Props = {
/**
* One user message: the quoted message it answers, its files, then its
* text (a file-only message has no bubble) and the bot's reactions to it.
* The user doesn't reply to or react to their own messages. Memoized,
* like `BotMessageLine`.
* The user doesn't reply to or react to their own messages, but they can
* copy one. Memoized, like `BotMessageLine`.
*/
function UserMessage({ message, bot_name }: Props) {
return (
@@ -35,6 +37,11 @@ function UserMessage({ message, bot_name }: Props) {
reactions={message.reactions}
/>
) : null}
{message.text ? (
<Row className="gap-3! items-center">
<CopyMessageButton text={message.text} />
</Row>
) : null}
</Stack>
);
}