feat(message): add "Copy Text" context-menu action
The message menu had Copy Link (permalink) but no way to copy the message text itself. Add a Copy Text item that copies the plain-text body with the reply fallback stripped (trimReplyFromBody). It renders nothing when there is no usable text body (e.g. media without a caption), so the caller can list it unconditionally next to Copy Link. Uses Icons.Text. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -683,6 +683,10 @@ The indicator is hidden once the server confirms the event (when the internal st
|
||||
|
||||
Context menu → **Forward** allows forwarding a message to any room the user is a member of.
|
||||
|
||||
### Copy Message Text
|
||||
|
||||
Context menu → **Copy Text** copies a message's plain-text body to the clipboard (reply fallback stripped via `trimReplyFromBody`), complementing the existing **Copy Link** (permalink) action. It renders only when the event has a usable text body, so media without a caption doesn't show an empty action.
|
||||
|
||||
### Draft Persistence
|
||||
|
||||
- Composer drafts are stored in `localStorage` keyed by `roomId`
|
||||
|
||||
@@ -56,6 +56,7 @@ import {
|
||||
getMemberAvatarMxc,
|
||||
getMemberName,
|
||||
sendStateEvent,
|
||||
trimReplyFromBody,
|
||||
} from '../../../utils/room';
|
||||
import { mxcUrlToHttp } from '../../../utils/matrix';
|
||||
import { messageAriaLabel } from '../../../utils/a11y';
|
||||
@@ -411,6 +412,43 @@ export const MessageCopyLinkItem = as<
|
||||
);
|
||||
});
|
||||
|
||||
// Copies the message's plain-text body (reply fallback stripped) to the
|
||||
// clipboard. Renders nothing for events without a usable text body (e.g. media
|
||||
// without a caption), so the caller can list it unconditionally.
|
||||
export const MessageCopyTextItem = as<
|
||||
'button',
|
||||
{
|
||||
mEvent: MatrixEvent;
|
||||
onClose?: () => void;
|
||||
}
|
||||
>(({ mEvent, onClose, ...props }, ref) => {
|
||||
const content = mEvent.getContent();
|
||||
const rawBody = typeof content.body === 'string' ? content.body : '';
|
||||
const body = trimReplyFromBody(rawBody).trim();
|
||||
|
||||
if (!body) return null;
|
||||
|
||||
const handleCopy = () => {
|
||||
copyToClipboard(body);
|
||||
onClose?.();
|
||||
};
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
size="300"
|
||||
after={<Icon size="100" src={Icons.Text} />}
|
||||
radii="300"
|
||||
onClick={handleCopy}
|
||||
{...props}
|
||||
ref={ref}
|
||||
>
|
||||
<Text className={css.MessageMenuItemText} as="span" size="T300" truncate>
|
||||
Copy Text
|
||||
</Text>
|
||||
</MenuItem>
|
||||
);
|
||||
});
|
||||
|
||||
export const MessagePinItem = as<
|
||||
'button',
|
||||
{
|
||||
@@ -1284,6 +1322,7 @@ export const Message = React.memo(
|
||||
onClose={closeMenu}
|
||||
/>
|
||||
)}
|
||||
<MessageCopyTextItem mEvent={mEvent} onClose={closeMenu} />
|
||||
<MessageCopyLinkItem room={room} mEvent={mEvent} onClose={closeMenu} />
|
||||
{canPinEvent && (
|
||||
<MessagePinItem room={room} mEvent={mEvent} onClose={closeMenu} />
|
||||
@@ -1513,6 +1552,7 @@ export const Event = React.memo(
|
||||
onClose={closeMenu}
|
||||
/>
|
||||
)}
|
||||
<MessageCopyTextItem mEvent={mEvent} onClose={closeMenu} />
|
||||
<MessageCopyLinkItem room={room} mEvent={mEvent} onClose={closeMenu} />
|
||||
</Box>
|
||||
{((!mEvent.isRedacted() && canDelete && !stateEvent) ||
|
||||
|
||||
Reference in New Issue
Block a user