feat(ui): copy a message's text from the hover menu (LC-282) #319

Merged
longjacksonle merged 1 commit from feat/lc-282-copy-message-text into main 2026-06-14 18:21:33 +02:00

What

Adds a "Copy text" hover action that copies a message's raw markdown source to the clipboard (LC-282) - the smaller sibling of the LC-272 code-block copy. Shown only for messages that have text.

How

  • New GET /messages/{id}/raw (routes::room::get_message_raw) returns the stored body as text/plain, access-gated: 404 for a missing/soft-deleted message, 403 for a room the caller cannot see (returns only what they can already read).
  • message.html: a "Copy text" button (after Copy link, gated on !message.body.is_empty()) carrying only the message id. Its inline handler fetches /messages/{id}/raw and copies via navigator.clipboard.writeText (falling back to __lcCopyFallback), flipping to "Copied" for ~1.5s - mirroring the LC-246 copy-link handler.
  • Strings localized in en + es (the "Copied" flip reuses the existing key).

Fetching on click (rather than inlining the body into a data- attribute on every row) keeps large rooms - which load full history - from doubling their text payload. Copying rendered text was rejected (it strips the markdown). No new DB/env. Not operator-visible.

Tests

routes_reactions_authz: /messages/{id}/raw returns the body for an accessible message, 403 for a private room a non-member can't see, 404 for a missing message. The four LC-77 render fixtures are regenerated for the new button (diff is purely additive - exactly that button). just test and just test-saas pass.

QA note

JS-driven copy, so a quick manual pass: hover a text message, "Copy text" copies the exact markdown source (paste it back into the composer to confirm), flips to "Copied"; an image-only message shows no button; Ctrl/Cmd-click is irrelevant here (plain button). The endpoint is server-gated and test-covered.

## What Adds a "Copy text" hover action that copies a message's raw markdown source to the clipboard (LC-282) - the smaller sibling of the LC-272 code-block copy. Shown only for messages that have text. ## How - New `GET /messages/{id}/raw` (`routes::room::get_message_raw`) returns the stored `body` as `text/plain`, access-gated: 404 for a missing/soft-deleted message, 403 for a room the caller cannot see (returns only what they can already read). - `message.html`: a "Copy text" button (after Copy link, gated on `!message.body.is_empty()`) carrying only the message id. Its inline handler fetches `/messages/{id}/raw` and copies via `navigator.clipboard.writeText` (falling back to `__lcCopyFallback`), flipping to "Copied" for ~1.5s - mirroring the LC-246 copy-link handler. - Strings localized in en + es (the "Copied" flip reuses the existing key). Fetching on click (rather than inlining the body into a `data-` attribute on every row) keeps large rooms - which load full history - from doubling their text payload. Copying rendered text was rejected (it strips the markdown). No new DB/env. Not operator-visible. ## Tests `routes_reactions_authz`: `/messages/{id}/raw` returns the body for an accessible message, 403 for a private room a non-member can't see, 404 for a missing message. The four LC-77 render fixtures are regenerated for the new button (diff is purely additive - exactly that button). `just test` and `just test-saas` pass. ## QA note JS-driven copy, so a quick manual pass: hover a text message, "Copy text" copies the exact markdown source (paste it back into the composer to confirm), flips to "Copied"; an image-only message shows no button; Ctrl/Cmd-click is irrelevant here (plain button). The endpoint is server-gated and test-covered.
feat(ui): copy a message's text from the hover menu (LC-282)
All checks were successful
check-secrets / Nosey parker (push) Successful in 3s
check-secrets / TruffleHog (push) Successful in 5s
check-secrets / Kingfisher (push) Successful in 6s
check-secrets / Nosey parker (pull_request) Successful in 4s
check-secrets / Kingfisher (pull_request) Successful in 6s
check-secrets / TruffleHog (pull_request) Successful in 4s
Check / clippy + fmt + tests (pull_request) Successful in 2m12s
Create release / Create release from merged PR (pull_request) Has been skipped
ccd682550b
Adds a "Copy text" hover action that copies the message's raw markdown source to the clipboard (the smaller sibling of the LC-272 code-block copy), shown only for messages that have text.

A tiny GET /messages/{id}/raw returns the stored body as text/plain, access-gated (404 for a missing/soft-deleted message, 403 for a room the caller cannot see). The button carries only the message id and fetches the raw text on click, so large rooms that load full history are not bloated by inlining every body into a data attribute. The handler mirrors the LC-246 copy-link button (navigator.clipboard.writeText with the __lcCopyFallback legacy path, "Copied" flip). Image-only/system messages get no button.

Inlining the body per-row was rejected (doubles the text payload of every message); copying rendered text was rejected (strips markdown). Strings localized in en + es. The four LC-77 render fixtures are regenerated for the new button (diff is exactly that button, purely additive). Tests cover the raw endpoint (returns body; 403 inaccessible; 404 missing).

#LC-282
#LC-283

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
longjacksonle deleted branch feat/lc-282-copy-message-text 2026-06-14 18:21:33 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/lets-chat!319
No description provided.