source: Klonkt/src/assets/css/reply-editor.css@ feced2c

main
Last change on this file since feced2c was feced2c, checked in by Robin <roboburr@…>, 7 weeks ago

Feature: media in replies — rich replies phase 2 (klonkt-demo-c7f)

Drop, paste or pick images/audio/video in the reply editor; they upload, show
as removable chips, travel as AS2 attachments on the federated Note, and render
in the thread.

  • POST /posts/upload-reply-media (requireSiteManager): image/audio/video by extension AND mimetype, stored as-is under /media/reply-media/ (no transcode; a reply attachment is not a track), 32MB cap, returns {url, mediaType, name}.
  • Editor: paperclip button + hidden file input (the mobile path), paste-files and drag/drop handlers, busy/error chips, image thumbnails, max 4, hidden attachments JSON field. Media-only submit allowed (text no longer required when something is attached).
  • deliverReply({attachments}): re-validates server-side — own /media/ paths only (the upload route is the sole producer, remote URLs rejected), image|audio|video mimetypes, capped at 4; stored as JSON on ap_outbox (additive column). Dedup guard now includes attachments so two media-only replies to the same parent are distinct from each other but double-submits still dedup.
  • buildNote reply branch: attachment array with Image/Audio/Video types and absolute URLs. getInteractions passes media through; fedi-node renders it (img/audio/video) for visitors too, loading the stylesheet when the owner-only editor is not on the page.

3 new tests (foreign-URL and type rejection, typed absolute Note attachments,
media-only allowed, only-invalid rejected); 91 green. Browser-verified end to
end: real upload via the endpoint, paste-event -> chip with thumbnail ->
submit -> ap_outbox row with content+language+attachments -> media rendered in
the thread -> /ap/notes/<id> serves the typed absolute attachment.

Co-Authored-By: Claude Opus 4.8 <noreply@…>

  • Property mode set to 100644
File size: 3.7 KB
Line 
1/* Rich reply editor (partials/reply-editor.ejs + js/reply-editor.js). */
2/* !important: site CSS gives forms inside comment threads `display: contents`,
3 which collapses the form to a 0x0 non-box — flex layout AND the fixed
4 full-screen overlay silently break. This component must own its box. */
5.re-form[data-re] { display: flex !important; flex-direction: column; gap: .45rem; }
6
7.re-toolbar { display: flex; gap: .25rem; flex-wrap: wrap; }
8.re-toolbar button {
9 border: 1px solid color-mix(in srgb, var(--ink, #000) 14%, transparent);
10 background: none; color: inherit; border-radius: 8px;
11 min-width: 2rem; height: 1.9rem; padding: 0 .45rem;
12 font-size: .85rem; line-height: 1; cursor: pointer;
13}
14.re-toolbar button:hover { background: color-mix(in srgb, var(--ink, #000) 8%, transparent); }
15
16.re-editor {
17 min-height: 4.2rem; padding: .55rem .7rem; border-radius: 10px;
18 border: 1px solid color-mix(in srgb, var(--ink, #000) 16%, transparent);
19 background: transparent; overflow-wrap: anywhere; outline: none;
20}
21.re-editor:focus { border-color: var(--accent, #06c); }
22.re-editor:empty::before { content: attr(data-ph); color: color-mix(in srgb, var(--ink, #000) 40%, transparent); pointer-events: none; }
23.re-editor blockquote { margin: .4rem 0; padding-left: .7rem; border-left: 3px solid color-mix(in srgb, var(--ink, #000) 20%, transparent); }
24
25.re-foot { display: flex; align-items: center; gap: .6rem; justify-content: flex-end; }
26.re-lang {
27 font: inherit; font-size: .82rem; padding: .3rem .45rem; border-radius: 8px;
28 border: 1px solid color-mix(in srgb, var(--ink, #000) 16%, transparent);
29 background: transparent; color: inherit; max-width: 10rem;
30}
31
32/* Media attachments (chips under the editor). */
33.re-attachments { display: flex; flex-wrap: wrap; gap: .4rem; }
34.re-att {
35 display: inline-flex; align-items: center; gap: .35rem;
36 padding: .25rem .4rem; border-radius: 8px; font-size: .8rem;
37 background: color-mix(in srgb, var(--ink, #000) 7%, transparent);
38 max-width: 100%; overflow: hidden;
39}
40.re-att img { width: 44px; height: 44px; object-fit: cover; border-radius: 6px; display: block; }
41.re-att-del { border: none; background: none; color: inherit; font-size: 1rem; line-height: 1; cursor: pointer; padding: 0 .15rem; }
42.re-att-err { background: color-mix(in srgb, #c00 18%, transparent); }
43.re-editor.re-drop { border-color: var(--accent, #06c); border-style: dashed; }
44
45/* Media on a sent reply in the thread (fedi-node). */
46.re-reply-media { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: .4rem; }
47.re-reply-media img { max-width: min(260px, 100%); max-height: 260px; border-radius: 10px; display: block; }
48.re-reply-media audio { max-width: 100%; }
49.re-reply-media video { max-width: min(320px, 100%); border-radius: 10px; }
50
51/* Full-screen compose (mobile). JS toggles .re-full on the form. */
52.re-head { display: flex; align-items: center; gap: .6rem; }
53.re-head-title { flex: 1; text-align: center; font-size: .95rem; }
54.re-cancel {
55 border: none; background: none; color: inherit; font-size: 1.5rem;
56 line-height: 1; padding: .25rem .5rem; cursor: pointer;
57}
58.re-form.re-full {
59 /* Above the bottom tab bar (1050) and the profile/audio sheets (1100). */
60 position: fixed; inset: 0; z-index: 1200;
61 background: var(--paper, #fff); padding: .8rem .9rem;
62 padding-top: calc(.8rem + env(safe-area-inset-top));
63 padding-bottom: calc(.8rem + env(safe-area-inset-bottom));
64 gap: .6rem;
65}
66.re-form.re-full .re-editor { flex: 1; min-height: 0; overflow-y: auto; border: none; padding: .4rem .1rem; }
67.re-form.re-full .re-editor:focus { border: none; }
68.re-form.re-full .re-send { display: none; } /* send lives in the top bar */
69.re-form.re-full .re-foot { justify-content: flex-start; }
70html.re-lock, html.re-lock body { overflow: hidden; }
Note: See TracBrowser for help on using the repository browser.