source: Klonkt/src/views/pages/messages.ejs@ 2c0c338

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

Berichten: binnen een gesprek nieuwste bovenaan, en de zwaai in de kop

Twee dingen die bij elkaar horen (Robins besluit, 25-8).

Binnen een DRAAD leest het nu van nieuw naar oud: wat er net bij kwam
staat er meteen, in plaats van onderaan een lange draad. De lijst van
gesprekken en de meldingen blijven staan zoals ze stonden -- alleen
binnen een gesprek draait de volgorde om.

Op een kopie van de array, want groupConversations sorteert oplopend en
de rest van de pagina rekent daarop (_tnew en _tto lezen dezelfde
array). En in de markup, niet met column-reverse: dat draait alleen het
beeld om terwijl een schermlezer de oude volgorde houdt.

Een ONTVANGEN zwaai staat nu ook in de kop van de draad. Ingeklapt zijn
de bubbels weg, en een zwaai is het bericht dat om een antwoord vraagt
zonder iets te zeggen -- die mocht niet achter een dichtgeklapt gesprek
verdwijnen. Blijft staan als de draad open is, net als het aantal. Een
zwaai die je zelf stuurde zet geen teken: dat is geen uitnodiging aan
jezelf.

Vijf toetsen die de echte partial renderen (test/messages-thread-order):
EJS-fouten bestaan alleen tijdens het renderen, dus die vielen tot nu
toe nergens in de suite om. Tegenbewijs gedraaid tegen de oude partial:
de volgorde-toets faalt daar, zoals het hoort.

  • Property mode set to 100644
File size: 16.7 KB
RevLine 
[f1a23b8]1<div class="msg-wrap">
2 <%- include('../partials/fedi-tabs', { active: 'berichten' }) %>
3 <h1 class="msg-title"><%= t('msg.title') %></h1>
[189e335]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><% } %>
[e84ce32]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;">
[780a7c6]14 <strong><%= o['shaer:candidateHandle'] || o['shaer:candidate'] %></strong><br>
[e84ce32]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;">
[780a7c6]18 <input type="hidden" name="offer" value="<%= o.id %>">
[e84ce32]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 <% } %>
[f1a23b8]25
26 <div class="msg-filters" role="tablist" aria-label="<%= t('msg.title') %>">
[7875a80]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. %>
[f1a23b8]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>
[8ed65a6]33 <button type="button" class="msg-chip" data-show="mod"><%= t('msg.filter_mod') %></button>
[f1a23b8]34 </div>
[8ed65a6]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>
[f1a23b8]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 { %>
[1485933]44 <ul class="msg-list" data-show="all" id="msg-list">
45 <% items.forEach(function(n){ %><%- include('../partials/msg-item', { n: n, seen: _seen }) %><% }); %>
[f1a23b8]46 </ul>
[8ed65a6]47 <p class="msg-nomatch" hidden><%= t('msg.no_match') %></p>
[1485933]48 <%- include('../partials/load-more', { hasMore: hasMore, nextOffset: nextOffset, moreBase: moreBase, moreTarget: '#msg-list', morePath: '/messages' }) %>
[f1a23b8]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
[e64a49b]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). %>
[f1a23b8]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
[8ed65a6]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
[f1a23b8]86 .msg-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: .55rem; }
87
[50489c7]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. */
[7875a80]103 .msg-thread { display: block; }
[50489c7]104 .msg-thread-head { display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; margin-bottom: .4rem; }
[a78a0ef]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. */
[ea138c0]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. */
[50489c7]111 .msg-thread-who { display: inline-flex; align-items: center; gap: .3rem; min-width: 0;
[ea138c0]112 font: inherit; font-size: .78em; line-height: 1.5; font-weight: 500;
113 padding: .05rem .5rem; border-radius: 999px; border: 0; cursor: pointer;
[a78a0ef]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);
[ea138c0]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-foot { 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-foot { 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; } }
[2c0c338]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; }
[50489c7]140 .msg-thread-count { font-variant-numeric: tabular-nums; opacity: .75; flex: 0 0 auto;
[a78a0ef]141 padding-inline-start: .3rem; border-inline-start: 1px solid color-mix(in srgb, var(--ink, #000) 14%, transparent); }
[7875a80]142 .msg-thread-post { display: inline-flex; align-items: center; gap: .3rem; font-size: .85em;
[50489c7]143 margin-inline-start: auto; min-width: 0; max-width: 100%;
144 overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
[7875a80]145 .msg-thread-post svg { width: 13px; height: 13px; flex: none; }
[50489c7]146 .msg-thread-msgs { list-style: none; margin: 0; padding: 0 0 0 .7rem; display: flex; flex-direction: column; gap: .5rem;
[7875a80]147 border-inline-start: 2px solid color-mix(in srgb, var(--ink, #000) 10%, transparent); }
[50489c7]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; }
[7875a80]157
[189e335]158 /* De voet van een gesprek: antwoorden en zwaaien naast elkaar, ingeklapt.
159 Een open editor onder elk gesprek maakt de lijst weer onleesbaar -- precies
160 wat deze weergave moest oplossen. */
161 .msg-thread-foot { display: flex; align-items: flex-start; gap: .5rem; margin-top: .5rem; flex-wrap: wrap; }
162 .msg-reply { flex: 1 1 12rem; min-width: 0; }
163 .msg-reply > summary { cursor: pointer; font-size: .82rem; color: var(--ink-soft, #888); }
164 .msg-reply[open] > summary { margin-bottom: .4rem; }
165 .msg-reply-form { min-width: 0; }
166 .msg-wave button { line-height: 1.2; }
167
[2bd31d6]168 /* Zwaaien gebeurt ter plekke (mod/messages.js). Twee toestanden na de klik:
169 gelukt, dan vervangt de bevestiging de knoppen -- en mislukt, dan komen de
170 knoppen terug met een schud, zodat een tweede poging voor de hand ligt.
171 De bestaande klasse `nudge` kon hier niet voor dienen: die heeft alleen
172 CSS op authorize-interaction, dus op deze pagina zou hij niets doen. */
173 .msg-wave-done { font-size: .82rem; color: var(--ink-soft, #888); white-space: nowrap; }
174 .msg-wave.is-failed, .msg-quickreply.is-failed { animation: msg-wave-shake .4s ease; }
175 @keyframes msg-wave-shake {
176 0%, 100% { transform: translateX(0); }
177 25% { transform: translateX(-4px); }
178 75% { transform: translateX(4px); }
179 }
180 @media (prefers-reduced-motion: reduce) {
181 .msg-wave.is-failed, .msg-quickreply.is-failed { animation: none; outline: 2px solid var(--accent); }
182 }
183
[50489c7]184 @media (max-width: 560px) {
185 /* Op een telefoon is 40px avatar per bubbel puur verlies: de naam staat er
186 al naast, en de inhoud heeft de breedte harder nodig. */
187 .msg-sub > .msg-av { display: none; }
188 .msg-thread-msgs { padding-inline-start: .55rem; }
189 .msg-thread-post { margin-inline-start: 0; }
190 .msg-item { padding: .7rem .6rem; gap: .6rem; }
191 }
[f1a23b8]192
193 .msg-av { position: relative; flex: 0 0 40px; width: 40px; height: 40px; border-radius: 50%;
194 display: inline-flex; align-items: center; justify-content: center; font-weight: 700;
195 background: color-mix(in srgb, var(--accent, #888) 16%, transparent); color: var(--accent, #06c); overflow: visible; }
196 .msg-av img { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; background: #fff; }
197 .msg-av-sent svg { width: 17px; height: 17px; }
198 .msg-dot { position: absolute; right: -3px; bottom: -3px; width: 18px; height: 18px; border-radius: 50%;
199 display: inline-flex; align-items: center; justify-content: center;
200 border: 2px solid var(--paper, #fff); background: var(--accent, #06c); color: #fff; }
201 .msg-dot svg { width: 10px; height: 10px; }
202 .msg-dot-like { background: #e8b04b; } .msg-dot-boost { background: #2fa85a; }
203 .msg-dot-report { background: #c0392b; } .msg-dot-mention { background: #8e6cf0; }
204
205 .msg-body { flex: 1; min-width: 0; }
206 .msg-line { display: flex; align-items: baseline; gap: .4rem; }
207 .msg-who { font-weight: 700; color: var(--ink, inherit); text-decoration: none; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
208 a.msg-who:hover { text-decoration: underline; }
209 .msg-handle { color: var(--ink-soft, #888); font-size: .82rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
210 .msg-new { width: 8px; height: 8px; border-radius: 50%; background: var(--accent, #06c); flex: 0 0 auto; align-self: center; }
211 .msg-time { color: var(--ink-soft, #999); font-size: .76rem; margin-left: auto; flex: 0 0 auto; white-space: nowrap; }
212 .msg-what { color: var(--ink-soft, #aaa); margin-top: .12rem; line-height: 1.4; }
213 .msg-what .msg-post { color: var(--accent, #06c); font-weight: 600; margin-left: .25rem; }
214 .msg-priv { display: inline-block; margin-left: .3rem; padding: .05rem .5rem; border-radius: 999px; font-size: .72rem; font-weight: 700;
215 background: color-mix(in srgb, #8e6cf0 14%, transparent); color: #8e6cf0; }
216 .msg-content { margin: .45rem 0 0; padding: .55rem .75rem; border-radius: 10px; line-height: 1.5;
217 background: color-mix(in srgb, var(--ink, #000) 4%, transparent); color: var(--ink, inherit); overflow-wrap: anywhere; }
218 .msg-content p { margin: .2rem 0; } .msg-content p:first-child { margin-top: 0; } .msg-content p:last-child { margin-bottom: 0; }
219
[544e9c2]220 .msg-poll { margin: .5rem 0 0; display: flex; flex-direction: column; gap: .4rem; }
221 .msg-poll-opt { display: flex; flex-direction: column; gap: .2rem; }
222 .msg-poll-top { display: flex; justify-content: space-between; gap: .5rem; font-size: .85rem; }
223 .msg-poll-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
224 .msg-poll-pct { font-variant-numeric: tabular-nums; color: var(--ink-soft, #888); flex-shrink: 0; }
225 .msg-poll-bar { height: 7px; border-radius: 999px; overflow: hidden; background: color-mix(in srgb, var(--ink, #000) 10%, transparent); }
226 .msg-poll-bar span { display: block; height: 100%; border-radius: 999px; background: var(--accent, #06c); }
227 .msg-poll-total { font-size: .78rem; color: var(--ink-soft, #888); margin-top: .1rem; }
228
[f1a23b8]229 .msg-actions { display: flex; gap: .7rem; align-items: center; margin-top: .45rem; }
230 .msg-edit summary { cursor: pointer; font-size: .8rem; color: var(--ink-soft, #888); font-weight: 600; }
231 .msg-edit-form { margin-top: .4rem; display: flex; flex-direction: column; gap: .4rem; }
232 .msg-edit-form textarea { width: 100%; font: inherit; padding: .5rem .65rem; border-radius: 8px;
233 border: 1px solid color-mix(in srgb, var(--ink, #000) 16%, transparent); background: transparent; color: var(--ink, inherit); }
234 .msg-del { background: none; border: none; padding: 0; font: inherit; font-size: .8rem; font-weight: 600;
235 color: var(--ink-soft, #888); cursor: pointer; }
236 .msg-del:hover { color: #c0392b; }
237
238 .msg-bm { margin: 1.4rem 0 0; border-top: 1px solid color-mix(in srgb, var(--ink, #000) 10%, transparent); padding-top: .8rem; }
239 .msg-bm summary { cursor: pointer; color: var(--ink-soft, #888); font-weight: 600; font-size: .88rem; }
240 .msg-bm-help { color: var(--ink-soft, #888); font-size: .82rem; line-height: 1.5; margin: .5rem 0 .6rem; }
241</style>
Note: See TracBrowser for help on using the repository browser.