source: Klonkt/src/views/pages/messages.ejs@ 824d1d0

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

Berichten: antwoorden en zwaaien naar de bovenkant van het gesprek

Volgt op de omgekeerde volgorde van zojuist (Robins besluit, 25-8).
Stond de antwoordknop onderaan, dan stond hij sinds vanmiddag bij het
OUDSTE bericht -- en dat is precies waar je niet op antwoordt. Nu staat
de rij direct onder de kop, waar het nieuwste bericht begint.

De zwaai-hand reist mee: die zat al in dezelfde rij en hoort bij
hetzelfde gebaar. De zwaai-MARKERING in de kop blijft waar hij is; die
twee vullen elkaar aan, want de markering overleeft het inklappen en de
knoppen niet.

msg-thread-foot heet nu msg-thread-actions: een naam die zegt waar iets
staat is een leugen zodra het verhuist. Geen JavaScript verwees ernaar,
alleen de eigen CSS op deze pagina, dus geen MOD_V nodig. De marge ging
mee van top naar bottom, anders plakt de rij tegen de eerste bubbel.

Zesde toets erbij, met tegenbewijs tegen de vorige commit: daar faalt
precies die ene en blijven de andere vijf groen. Volle suite 1236.

  • Property mode set to 100644
File size: 16.9 KB
Line 
1<div class="msg-wrap">
2 <%- include('../partials/fedi-tabs', { active: 'berichten' }) %>
3 <h1 class="msg-title"><%= t('msg.title') %></h1>
4 <% if (typeof success !== 'undefined' && success) { %><div class="alert alert-success"><%= success === 'guardian_accepted' ? t('msg.guard_accepted') : (success === 'guardian_rejected' ? t('msg.guard_rejected') : (success === 'wave_sent' ? t('msg.wave_sent') : (success === 'reply_sent' ? t('msg.reply_sent') : success))) %></div><% } %>
5 <% if (typeof error !== 'undefined' && error) { %><div class="alert alert-error"><%= error === 'guardianship' ? t('msg.guard_failed') : (error === 'reply_empty' ? t('msg.reply_empty') : ((error === 'reply_failed' || error === 'reply_target') ? t('msg.reply_failed') : error)) %></div><% } %>
6
7 <%# FEP-633c: a pending guardianship offer is a special message: the kid
8 answers here (accept/reject travels the same C2S pipeline as the apps). %>
9 <% if (typeof guardianOffers !== 'undefined' && guardianOffers.length) { %>
10 <% guardianOffers.forEach(function(o){ %>
11 <div class="alert" style="display:flex;align-items:center;gap:12px;flex-wrap:wrap;border-left:3px solid #ff6b35;">
12 <span style="font-size:1.4em;">&#128735;</span>
13 <div style="flex:1;min-width:200px;">
14 <strong><%= o['shaer:candidateHandle'] || o['shaer:candidate'] %></strong><br>
15 <span><%= t('msg.guard_offer') %></span>
16 </div>
17 <form method="post" action="<%= (typeof moreBase !== 'undefined' ? moreBase : '') %>/messages/guardianship" style="display:flex;gap:8px;">
18 <input type="hidden" name="offer" value="<%= o.id %>">
19 <button class="btn" type="submit" name="answer" value="accept"><%= t('msg.guard_accept') %></button>
20 <button class="btn" type="submit" name="answer" value="reject" style="opacity:.7;"><%= t('msg.guard_reject') %></button>
21 </form>
22 </div>
23 <% }); %>
24 <% } %>
25
26 <div class="msg-filters" role="tablist" aria-label="<%= t('msg.title') %>">
27 <%# Vier chips, geen zes: Berichten, Gesprekken en Verzonden gingen op in
28 een enkele Gesprekken-view, waarin verzonden en ontvangen in dezelfde
29 draad staan. Activiteit en Moderatie blijven wat ze waren. %>
30 <button type="button" class="msg-chip is-on" data-show="all"><%= t('msg.filter_all') %></button>
31 <button type="button" class="msg-chip" data-show="conv"><%= t('msg.filter_conv') %></button>
32 <button type="button" class="msg-chip" data-show="act"><%= t('msg.filter_act') %></button>
33 <button type="button" class="msg-chip" data-show="mod"><%= t('msg.filter_mod') %></button>
34 </div>
35 <div class="msg-search">
36 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
37 <input type="search" id="msg-q" placeholder="<%= t('msg.search_ph') %>" autocomplete="off" spellcheck="false" aria-label="<%= t('msg.search_ph') %>">
38 </div>
39
40 <% var _seen = (typeof seenAt !== 'undefined' && seenAt) ? seenAt : 0; %>
41 <% if (!items || !items.length) { %>
42 <p class="msg-empty"><%= t('msg.empty') %></p>
43 <% } else { %>
44 <ul class="msg-list" data-show="all" id="msg-list">
45 <% items.forEach(function(n){ %><%- include('../partials/msg-item', { n: n, seen: _seen }) %><% }); %>
46 </ul>
47 <p class="msg-nomatch" hidden><%= t('msg.no_match') %></p>
48 <%- include('../partials/load-more', { hasMore: hasMore, nextOffset: nextOffset, moreBase: moreBase, moreTarget: '#msg-list', morePath: '/messages' }) %>
49 <% } %>
50
51 <%# Interact bookmarklet — moved here from the old Reacties page. %>
52 <details class="msg-bm">
53 <summary><%= t('fedi.bm_label') %></summary>
54 <p class="msg-bm-help"><%= t('fedi.bm_help') %></p>
55 <a class="fedi-bm-btn" id="fedi-bm-btn" href="#" draggable="true" title="<%= t('fedi.bm_help') %>">
56 <svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"/></svg>
57 <%= t('fedi.bm_label') %>
58 </a>
59 </details>
60</div>
61
62<%# Filteren, zoeken en het in-/uitklappen zitten in assets/js/mod/messages.js.
63 Inline script hier wordt door de CSP geweigerd zodra je deze pagina via een
64 link BINNEN de site opent -- zie shaer-0i6. De shell laadt de module op
65 body[data-js]; deze pagina vraagt erom met pageJs (routes/posts.js). %>
66
67<style>
68 .msg-wrap { max-width: 640px; margin: 1rem auto; padding: 0 1rem; }
69 .msg-title { margin: 0 0 .9rem; }
70 .msg-empty { color: var(--ink-soft, #888); }
71
72 .msg-filters { display: flex; gap: .4rem; margin: 0 0 1.1rem; flex-wrap: wrap; }
73 .msg-chip { border: 1px solid color-mix(in srgb, var(--ink, #000) 16%, transparent); background: none;
74 color: var(--ink-soft, #888); font: inherit; font-size: .84rem; font-weight: 600;
75 padding: .3rem .85rem; border-radius: 999px; cursor: pointer; }
76 .msg-chip.is-on { background: var(--accent, #06c); border-color: var(--accent, #06c); color: #fff; }
77
78 .msg-search { display: flex; align-items: center; gap: .5rem; margin: 0 0 1.1rem;
79 padding: .45rem .7rem; border-radius: 10px;
80 border: 1px solid color-mix(in srgb, var(--ink, #000) 16%, transparent); }
81 .msg-search:focus-within { border-color: var(--accent, #06c); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent, #06c) 18%, transparent); }
82 .msg-search svg { width: 16px; height: 16px; color: var(--ink-soft, #888); flex-shrink: 0; }
83 .msg-search input { flex: 1; border: none; background: none; color: inherit; font: inherit; outline: none; min-width: 0; }
84 .msg-nomatch { color: var(--ink-soft, #888); text-align: center; padding: 1.2rem 0; }
85
86 .msg-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: .55rem; }
87
88
89 .msg-item { display: flex; gap: .8rem; padding: .85rem .95rem; border-radius: 14px; align-items: flex-start;
90 background: color-mix(in srgb, var(--ink, #000) 3.5%, transparent);
91 border: 1px solid color-mix(in srgb, var(--ink, #000) 9%, transparent);
92 transition: border-color .15s ease, box-shadow .15s ease; }
93 .msg-item:hover { border-color: color-mix(in srgb, var(--accent, #888) 30%, transparent);
94 box-shadow: 0 2px 12px color-mix(in srgb, var(--ink, #000) 6%, transparent); }
95 .msg-item.is-new { border-color: color-mix(in srgb, var(--accent, #06c) 45%, transparent);
96 background: color-mix(in srgb, var(--accent, #06c) 4%, transparent); }
97 .msg-sent { border-left: 3px solid color-mix(in srgb, var(--accent, #06c) 55%, transparent); }
98
99 /* ── Gesprekken ──────────────────────────────────────────────────────
100 NA .msg-item, en dat is geen smaak: .msg-item zet display:flex, en bij
101 gelijke specificiteit wint de laatste regel. Stond dit ervoor, dan kwam de
102 draadkop NAAST de bubbels te staan in plaats van erboven. */
103 .msg-thread { display: block; }
104 .msg-thread-head { display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; margin-bottom: .4rem; }
105 /* De tegenpartij is een labeltje bij het gesprek, geen kop erboven: een
106 kleine verzonken uitsparing, zodat de bubbels eronder de ruimte krijgen.
107 Het aantal zit in dezelfde uitsparing in plaats van ernaast. */
108 /* Ook de knop die het gesprek in- en uitklapt. Een button erft geen font en
109 geen kleur, dus die staan hier expliciet; zonder dat wordt het ineens
110 systeemblauw in 13px. */
111 .msg-thread-who { display: inline-flex; align-items: center; gap: .3rem; min-width: 0;
112 font: inherit; font-size: .78em; line-height: 1.5; font-weight: 500;
113 padding: .05rem .5rem; border-radius: 999px; border: 0; cursor: pointer;
114 color: color-mix(in srgb, var(--ink, #000) 62%, transparent);
115 background: color-mix(in srgb, var(--ink, #000) 5%, transparent);
116 box-shadow: inset 0 1px 2px color-mix(in srgb, var(--ink, #000) 9%, transparent);
117 max-width: 100%; overflow: hidden; white-space: nowrap;
118 /* Ruimer aanraakvlak dan de tekst: een chip van 18px hoog is op een telefoon
119 niet te raken. De uitsparing blijft klein, het doel wordt groter. */
120 min-height: 32px; }
121 .msg-thread-who:hover { background: color-mix(in srgb, var(--ink, #000) 9%, transparent); }
122 .msg-thread-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
123 .msg-thread-chevron { width: 12px; height: 12px; flex: none; opacity: .55;
124 transition: transform .18s ease; }
125 /* Ingeklapt: de bubbels en het antwoordveld verdwijnen, de uitsparing met de
126 naam en het AANTAL blijft staan -- dat aantal is juist ingeklapt het meest
127 waard. */
128 .msg-thread.is-collapsed .msg-thread-msgs,
129 .msg-thread.is-collapsed .msg-thread-actions { display: none; }
130 /* Tijdens het zoeken wint de treffer van de dichtgeklapte stand. */
131 .msg-thread.is-collapsed.is-search-open .msg-thread-msgs { display: flex; }
132 .msg-thread.is-collapsed.is-search-open .msg-thread-actions { display: flex; }
133 .msg-thread.is-collapsed .msg-thread-chevron { transform: rotate(-90deg); }
134 .msg-thread.is-collapsed .msg-thread-head { margin-bottom: 0; }
135 @media (prefers-reduced-motion: reduce) { .msg-thread-chevron { transition: none; } }
136 /* De zwaai in de kop: klein, en met dezelfde rust als het aantal ernaast.
137 Hij staat er ook als het gesprek open is -- dan zegt de bubbel het al, maar
138 een kop die van stand verandert leest als iets dat gebeurde. */
139 .msg-thread-wave { flex: 0 0 auto; font-size: .95em; line-height: 1; }
140 .msg-thread-count { font-variant-numeric: tabular-nums; opacity: .75; flex: 0 0 auto;
141 padding-inline-start: .3rem; border-inline-start: 1px solid color-mix(in srgb, var(--ink, #000) 14%, transparent); }
142 .msg-thread-post { display: inline-flex; align-items: center; gap: .3rem; font-size: .85em;
143 margin-inline-start: auto; min-width: 0; max-width: 100%;
144 overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
145 .msg-thread-post svg { width: 13px; height: 13px; flex: none; }
146 .msg-thread-msgs { list-style: none; margin: 0; padding: 0 0 0 .7rem; display: flex; flex-direction: column; gap: .5rem;
147 border-inline-start: 2px solid color-mix(in srgb, var(--ink, #000) 10%, transparent); }
148 .msg-sub { display: flex; gap: .6rem; align-items: flex-start; min-width: 0; }
149 .msg-sub > .msg-body { min-width: 0; }
150 /* Jouw eigen bijdrage onderscheidt zich met een tint en de accentlijn die
151 .msg-sent al had -- niet met row-reverse: dat duwde de avatar buiten de
152 kaart zodra de regel smaller werd dan zijn inhoud. */
153 .msg-sub.msg-sent { border-radius: 10px; padding: .3rem .45rem;
154 background: color-mix(in srgb, var(--accent, #06c) 5%, transparent); }
155 /* Een lange naam of tijd mag de bubbel niet uit de kaart duwen. */
156 .msg-sub .msg-line { flex-wrap: wrap; }
157
158 /* Antwoorden en zwaaien naast elkaar, ingeklapt, en sinds 25-8 BOVEN de
159 berichten -- daar staat het nieuwste, en daar antwoord je dus op. Heette
160 msg-thread-foot toen het onderaan stond; de marge verhuisde mee van top
161 naar bottom, anders plakt de rij tegen de eerste bubbel.
162 Een open editor onder elk gesprek maakt de lijst weer onleesbaar -- precies
163 wat deze weergave moest oplossen. */
164 .msg-thread-actions { display: flex; align-items: flex-start; gap: .5rem; margin-bottom: .5rem; flex-wrap: wrap; }
165 .msg-reply { flex: 1 1 12rem; min-width: 0; }
166 .msg-reply > summary { cursor: pointer; font-size: .82rem; color: var(--ink-soft, #888); }
167 .msg-reply[open] > summary { margin-bottom: .4rem; }
168 .msg-reply-form { min-width: 0; }
169 .msg-wave button { line-height: 1.2; }
170
171 /* Zwaaien gebeurt ter plekke (mod/messages.js). Twee toestanden na de klik:
172 gelukt, dan vervangt de bevestiging de knoppen -- en mislukt, dan komen de
173 knoppen terug met een schud, zodat een tweede poging voor de hand ligt.
174 De bestaande klasse `nudge` kon hier niet voor dienen: die heeft alleen
175 CSS op authorize-interaction, dus op deze pagina zou hij niets doen. */
176 .msg-wave-done { font-size: .82rem; color: var(--ink-soft, #888); white-space: nowrap; }
177 .msg-wave.is-failed, .msg-quickreply.is-failed { animation: msg-wave-shake .4s ease; }
178 @keyframes msg-wave-shake {
179 0%, 100% { transform: translateX(0); }
180 25% { transform: translateX(-4px); }
181 75% { transform: translateX(4px); }
182 }
183 @media (prefers-reduced-motion: reduce) {
184 .msg-wave.is-failed, .msg-quickreply.is-failed { animation: none; outline: 2px solid var(--accent); }
185 }
186
187 @media (max-width: 560px) {
188 /* Op een telefoon is 40px avatar per bubbel puur verlies: de naam staat er
189 al naast, en de inhoud heeft de breedte harder nodig. */
190 .msg-sub > .msg-av { display: none; }
191 .msg-thread-msgs { padding-inline-start: .55rem; }
192 .msg-thread-post { margin-inline-start: 0; }
193 .msg-item { padding: .7rem .6rem; gap: .6rem; }
194 }
195
196 .msg-av { position: relative; flex: 0 0 40px; width: 40px; height: 40px; border-radius: 50%;
197 display: inline-flex; align-items: center; justify-content: center; font-weight: 700;
198 background: color-mix(in srgb, var(--accent, #888) 16%, transparent); color: var(--accent, #06c); overflow: visible; }
199 .msg-av img { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; background: #fff; }
200 .msg-av-sent svg { width: 17px; height: 17px; }
201 .msg-dot { position: absolute; right: -3px; bottom: -3px; width: 18px; height: 18px; border-radius: 50%;
202 display: inline-flex; align-items: center; justify-content: center;
203 border: 2px solid var(--paper, #fff); background: var(--accent, #06c); color: #fff; }
204 .msg-dot svg { width: 10px; height: 10px; }
205 .msg-dot-like { background: #e8b04b; } .msg-dot-boost { background: #2fa85a; }
206 .msg-dot-report { background: #c0392b; } .msg-dot-mention { background: #8e6cf0; }
207
208 .msg-body { flex: 1; min-width: 0; }
209 .msg-line { display: flex; align-items: baseline; gap: .4rem; }
210 .msg-who { font-weight: 700; color: var(--ink, inherit); text-decoration: none; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
211 a.msg-who:hover { text-decoration: underline; }
212 .msg-handle { color: var(--ink-soft, #888); font-size: .82rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
213 .msg-new { width: 8px; height: 8px; border-radius: 50%; background: var(--accent, #06c); flex: 0 0 auto; align-self: center; }
214 .msg-time { color: var(--ink-soft, #999); font-size: .76rem; margin-left: auto; flex: 0 0 auto; white-space: nowrap; }
215 .msg-what { color: var(--ink-soft, #aaa); margin-top: .12rem; line-height: 1.4; }
216 .msg-what .msg-post { color: var(--accent, #06c); font-weight: 600; margin-left: .25rem; }
217 .msg-priv { display: inline-block; margin-left: .3rem; padding: .05rem .5rem; border-radius: 999px; font-size: .72rem; font-weight: 700;
218 background: color-mix(in srgb, #8e6cf0 14%, transparent); color: #8e6cf0; }
219 .msg-content { margin: .45rem 0 0; padding: .55rem .75rem; border-radius: 10px; line-height: 1.5;
220 background: color-mix(in srgb, var(--ink, #000) 4%, transparent); color: var(--ink, inherit); overflow-wrap: anywhere; }
221 .msg-content p { margin: .2rem 0; } .msg-content p:first-child { margin-top: 0; } .msg-content p:last-child { margin-bottom: 0; }
222
223 .msg-poll { margin: .5rem 0 0; display: flex; flex-direction: column; gap: .4rem; }
224 .msg-poll-opt { display: flex; flex-direction: column; gap: .2rem; }
225 .msg-poll-top { display: flex; justify-content: space-between; gap: .5rem; font-size: .85rem; }
226 .msg-poll-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
227 .msg-poll-pct { font-variant-numeric: tabular-nums; color: var(--ink-soft, #888); flex-shrink: 0; }
228 .msg-poll-bar { height: 7px; border-radius: 999px; overflow: hidden; background: color-mix(in srgb, var(--ink, #000) 10%, transparent); }
229 .msg-poll-bar span { display: block; height: 100%; border-radius: 999px; background: var(--accent, #06c); }
230 .msg-poll-total { font-size: .78rem; color: var(--ink-soft, #888); margin-top: .1rem; }
231
232 .msg-actions { display: flex; gap: .7rem; align-items: center; margin-top: .45rem; }
233 .msg-edit summary { cursor: pointer; font-size: .8rem; color: var(--ink-soft, #888); font-weight: 600; }
234 .msg-edit-form { margin-top: .4rem; display: flex; flex-direction: column; gap: .4rem; }
235 .msg-edit-form textarea { width: 100%; font: inherit; padding: .5rem .65rem; border-radius: 8px;
236 border: 1px solid color-mix(in srgb, var(--ink, #000) 16%, transparent); background: transparent; color: var(--ink, inherit); }
237 .msg-del { background: none; border: none; padding: 0; font: inherit; font-size: .8rem; font-weight: 600;
238 color: var(--ink-soft, #888); cursor: pointer; }
239 .msg-del:hover { color: #c0392b; }
240
241 .msg-bm { margin: 1.4rem 0 0; border-top: 1px solid color-mix(in srgb, var(--ink, #000) 10%, transparent); padding-top: .8rem; }
242 .msg-bm summary { cursor: pointer; color: var(--ink-soft, #888); font-weight: 600; font-size: .88rem; }
243 .msg-bm-help { color: var(--ink-soft, #888); font-size: .82rem; line-height: 1.5; margin: .5rem 0 .6rem; }
244</style>
Note: See TracBrowser for help on using the repository browser.