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
@@ -3,6 +3,7 @@ import Row from "@moduletrace/twui/layout/Row";
import { Reply } from "lucide-react";
import type { BotMessageWithAttachments } from "@/src/types";
import { BotMessageActionsContext } from "../(data)/message-actions-context";
import CopyMessageButton from "./copy-message-button";
import MessageActionButton from "./message-action-button";
import MessageReactionPicker from "./message-reaction-picker";
@@ -11,23 +12,32 @@ type Props = {
};
/**
* A message's reply and reaction actions, shown under it.
* A message's copy, reply and reaction actions, shown under it. A message
* still on its way to the server has no id yet, so it can only be copied.
*/
export default function MessageActions({ message }: Props) {
const { onReply, onReact } = useContext(BotMessageActionsContext);
if (!message.id) return null;
if (!message.id && !message.text?.trim()) return null;
return (
<Row className="gap-3! items-center">
<MessageActionButton
title="Reply"
aria_label="Reply to this message"
onClick={() => onReply({ message })}
>
<Reply size={14} aria-hidden />
</MessageActionButton>
<MessageReactionPicker message_id={message.id} onReact={onReact} />
<CopyMessageButton text={message.text || ""} />
{message.id ? (
<>
<MessageActionButton
title="Reply"
aria_label="Reply to this message"
onClick={() => onReply({ message })}
>
<Reply size={14} aria-hidden />
</MessageActionButton>
<MessageReactionPicker
message_id={message.id}
onReact={onReact}
/>
</>
) : null}
</Row>
);
}