source: Klonkt/src/views/partials/msg-item.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: 18.3 KB
Line 
1<% if (n.type === 'thread') { %>
2<%
3 var _tseen = (typeof seen !== 'undefined' && seen) ? seen : 0;
4 // Een draad is nieuw zodra EEN bericht erin nieuw is.
5 var _tnew = !!_tseen && (n.messages || []).some(function (m) {
6 return m.created_at && Date.parse(m.created_at) > _tseen;
7 });
8 var _tpeople = (n.people || []).map(function (p) { return p.name || p.handle || '?'; });
9 // Alleen jij aan het woord: dan is de tegenpartij het adres waaraan
10 // je schreef, niet een lege naam.
11 var _tto = ((n.messages || []).find(function (m) { return m.to_handle; }) || {}).to_handle;
12 var _tlabel = _tpeople.length ? _tpeople.join(', ') : (_tto || t('msg.you'));
13 // Zwaaide er iemand in dit gesprek? Dan hoort dat in de KOP
14 // (Robins aanwijzing, 25-8). Ingeklapt zijn de bubbels weg, en juist
15 // een zwaai is het bericht dat om een antwoord vraagt zonder iets te
16 // zeggen -- die mag niet achter een dichtgeklapt gesprek verdwijnen.
17 // Alleen een ONTVANGEN zwaai: je eigen zwaai staat er als 'sent' en
18 // is geen uitnodiging aan jezelf.
19 var _twave = (n.messages || []).some(function (m) { return m.type === 'mention' && m.wave; });
20 %>
21 <li class="msg-item msg-thread<%= _tnew ? ' is-new' : '' %>" data-kind="conv" data-who="<%= (_tpeople.join(' ') + ' ' + (_tto || '')).toLowerCase() %>">
22 <div class="msg-thread-head">
23 <%# Tegenpartij en aantal in EEN uitsparing: het is een label bij het
24 gesprek, geen kop erboven. Zo houdt de inhoud de ruimte.
25
26 Deze uitsparing is ook de knop die het gesprek in- en uitklapt
27 (Barts aanwijzing): tikken op de tegenpartij is waar je van
28 nature naar wijst als je "dit gesprek" bedoelt. Een BUTTON en
29 geen span met een klikhandler, want dan werkt hij ook met het
30 toetsenbord en leest een schermlezer de stand voor.
31
32 Zonder JavaScript doet hij niets, en dan staat het gesprek
33 gewoon open -- dat is ook de stand waar we mee beginnen. %>
34 <button type="button" class="msg-thread-who" aria-expanded="true">
35 <span class="msg-thread-name"><%= _tlabel %></span>
36 <% if (n.count > 1) { %><span class="msg-thread-count"><%= n.count %></span><% } %>
37 <svg class="msg-thread-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="6 9 12 15 18 9"/></svg>
38 </button>
39 <%# Blijft staan als het gesprek dicht is, net als het aantal in de
40 uitsparing hierboven -- daar is hij juist het meest waard. %>
41 <% if (_twave) { %><span class="msg-thread-wave" role="img" aria-label="<%= t('msg.waved_at_you') %>" title="<%= t('msg.waved_at_you') %>">&#128075;</span><% } %>
42 <% if (_tnew) { %><span class="msg-new" title="<%= t('msg.new') %>"></span><% } %>
43 <%# De context bij de draad: waar gaat dit gesprek over? Zonder deze
44 link is een antwoord in een lijst niet te plaatsen. %>
45 <% if (n.post) { %>
46 <a class="msg-thread-post" href="/<%= n.post.slug %>">
47 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>
48 <%= n.post.title || n.post.slug %>
49 </a>
50 <% } %>
51 </div>
52 <%# Antwoorden vanuit de draad zelf, BOVEN de berichten (Robins
53 besluit, 25-8). Sinds het nieuwste bovenaan staat, is de bovenkant
54 waar het gesprek nu is: antwoorden hoort bij het laatste bericht,
55 en dat staat er direct onder. Onderaan zou de knop bij het OUDSTE
56 bericht komen te staan, en dat is precies waar je niet op
57 antwoordt.
58
59 Nog steeds ingeklapt, want een open editor onder elk gesprek maakt
60 de lijst onleesbaar -- juist het probleem dat deze weergave moest
61 oplossen. De route kiest het pad: een draad aan een post krijgt
62 een reply, een draad aan een persoon een direct bericht. %>
63 <%# shaer-r4c: precies de tak die de server ook kiest. /messages/reply
64 gaat naar deliverReply als er een interaction+post is (dat is een
65 ANTWOORD en valt onder de poort), en anders naar deliverDirectNote
66 (dat is een bericht, andere poort). Alleen de eerste verbergen. %>
67 <% var _rt = n.replyTo || {}; var _thread = _rt.interactionId && _rt.postSlug; %>
68 <% var _kan = canMutate && ((_thread && locals.mayReply !== false) || (!_thread && _rt.actorUri)); %>
69 <% if (_kan) { %>
70 <%# Heette msg-thread-foot toen hij onderaan stond. Een naam die
71 zegt waar iets staat is een leugen zodra het verhuist, dus nu
72 zegt hij wat het IS. %>
73 <div class="msg-thread-actions">
74 <details class="msg-reply">
75 <summary><%= t('fedi.reply') %></summary>
76 <div class="msg-reply-form">
77 <%- include('../partials/reply-editor', {
78 action: (typeof siteUrlBase !== 'undefined' ? siteUrlBase : '') + '/messages/reply',
79 hiddenFields: [
80 { name: 'interaction_id', value: _rt.interactionId || '' },
81 { name: 'post_slug', value: _rt.postSlug || '' },
82 { name: 'to', value: _rt.actorUri || '' },
83 ],
84 placeholder: t('fedi.reply_ph'),
85 submitLabel: t('fedi.reply'),
86 rows: 2,
87 }) %>
88 </div>
89 </details>
90 <%# De zwaai blijft een eigen knop: het is geen antwoord maar een
91 seintje (FEP-633c), en die twee horen niet op een hoop. %>
92 <% if (_rt.actorUri) { %>
93 <form method="post" action="<%= (typeof siteUrlBase !== 'undefined' ? siteUrlBase : '') %>/messages/quick-reply" class="msg-wave" data-sent="<%= t('msg.wave_sent') %>">
94 <input type="hidden" name="to" value="<%= _rt.actorUri %>">
95 <button class="btn small" name="text" value="&#128075;" title="<%= t('msg.wave_back') %>">&#128075;</button>
96 </form>
97 <% } %>
98 </div>
99 <% } %>
100 <%# NIEUWSTE BOVENAAN binnen een gesprek (Robins besluit, 25-8). De
101 LIJST van gesprekken en de meldingen blijven staan zoals ze
102 stonden; alleen binnen een draad leest het van nieuw naar oud, dus
103 wat er net bij kwam staat er meteen en niet onderaan een lange
104 draad.
105
106 Op een KOPIE, want groupConversations sorteert `messages`
107 oplopend en de rest van deze pagina rekent daarop -- `_tnew` en
108 `_tto` hierboven lezen dezelfde array. Een reverse() zonder
109 slice() draait het origineel om en dat werkt door naar alles wat
110 er daarna nog naar kijkt.
111
112 In de MARKUP en niet met column-reverse: die draait alleen het
113 beeld om, terwijl een schermlezer en het toetsenbord de oude
114 volgorde houden. %>
115 <ul class="msg-thread-msgs">
116 <% (n.messages || []).slice().reverse().forEach(function (m) { %><%- include('msg-item', { n: m, seen: _tseen, inThread: true }) %><% }); %>
117 </ul>
118 </li>
119<% } else { %>
120<%
121 var _seen = (typeof seen !== 'undefined' && seen) ? seen : 0;
122 var _t = n.type === 'announce' ? 'boost' : n.type; // reply|mention|report|like|boost|follow|sent
123 var _new = _seen && n.created_at ? (Date.parse(n.created_at) > _seen) : false;
124 var _who = n.name || n.handle || '';
125 var _priv0 = (n.visibility === 'followers' || n.visibility === 'direct');
126 // Emmers: Activiteit = likes/boosts/follows/polls, Moderatie =
127 // reports, en al het overige is gesprek. De oude emmers 'msgs' en
128 // 'sent' bestaan niet meer sinds Berichten, Gesprekken en Verzonden
129 // samenvielen -- en dat is geen cosmetische hernoeming: een
130 // gespreksregel die om wat voor reden dan ook geen draad werd (geen
131 // post, geen herkenbare tegenpartij) hield anders een data-kind
132 // waarvoor geen chip meer bestond, en was daarmee onder ELKE chip
133 // behalve Alles onzichtbaar.
134 var _kind = (_t === 'like' || _t === 'boost' || _t === 'follow' || _t === 'poll_done') ? 'act'
135 : _t === 'report' ? 'mod'
136 : 'conv';
137 // Binnen een draad is dit geen zelfstandige regel: de chip filtert op
138 // de draad, niet op de losse zinnen erin.
139 var _sub = (typeof inThread !== 'undefined' && inThread);
140 var _init = String(_who || '?').replace(/^@/, '').charAt(0).toUpperCase();
141 var _priv = (n.visibility === 'followers' || n.visibility === 'direct');
142 %>
143 <li class="<%= _sub ? 'msg-sub' : 'msg-item' %> msg-<%= _t %><%= _new ? ' is-new' : '' %>"<% if (!_sub) { %> data-kind="<%= _kind %>"<% } %> data-who="<%= String(_who).toLowerCase() %>">
144 <span class="msg-av<%= _t === 'sent' ? ' msg-av-sent' : '' %>" aria-hidden="true">
145 <% if (_t === 'sent') { %><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>
146 <% } else if (_t === 'poll_done') { %><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/></svg>
147 <% } else if (n.icon) { %><img src="<%= avatar(n.icon, 96) %>" alt="" loading="lazy">
148 <% } else { %><%= _init %><% } %>
149 <span class="msg-dot msg-dot-<%= _t %>">
150 <% if (_t === 'like') { %><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2.6l2.9 5.88 6.49.95-4.7 4.58 1.11 6.46L12 17.96l-5.8 3.06 1.1-6.46-4.69-4.58 6.49-.95z"/></svg>
151 <% } else if (_t === 'boost') { %><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><polyline points="17 1 21 5 17 9"/><path d="M3 11V9a4 4 0 0 1 4-4h14"/><polyline points="7 23 3 19 7 15"/><path d="M21 13v2a4 4 0 0 1-4 4H3"/></svg>
152 <% } else if (_t === 'follow') { %><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><line x1="19" y1="8" x2="19" y2="14"/><line x1="22" y1="11" x2="16" y2="11"/></svg>
153 <% } else if (_t === 'report') { %><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"/><line x1="4" y1="22" x2="4" y2="15"/></svg>
154 <% } else if (_t === 'mention') { %><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-3.92 7.94"/></svg>
155 <% } else if (_t === 'sent') { %><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 17 4 12 9 7"/><path d="M20 18v-2a4 4 0 0 0-4-4H4"/></svg>
156 <% } else if (_t === 'poll_done') { %><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/></svg>
157 <% } else { %><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg><% } %>
158 </span>
159 </span>
160
161 <div class="msg-body">
162 <div class="msg-line">
163 <% if (_t === 'sent') { %>
164 <span class="msg-who"><%= t('msg.you') %></span>
165 <% if (n.to_handle && !_sub) { %><span class="msg-handle">→ <%= n.to_handle %></span><% } %>
166 <% } else { %>
167 <a class="msg-who" href="<%= n.url %>" target="_blank" rel="nofollow noopener"><%- emojiName(_who, n.actor_emoji_json) %></a>
168 <% /* De naam blijft: binnen een draad moet je zien WIE wat zei.
169 De handle erachter niet -- die staat in de kop. */ %>
170 <% if (n.handle && n.name && !_sub) { %><span class="msg-handle"><%= n.handle %></span><% } %>
171 <% } %>
172 <% if (_new) { %><span class="msg-new" title="<%= t('msg.new') %>"></span><% } %>
173 <% /* When the post was written, like de Krant and de Guardian PWA
174 show it. created_at (when it reached us) stays the sort key
175 and drives the "new since your last visit" dot above. */ %>
176 <% if (n.published || n.created_at) { %><span class="msg-time"><%= formatDateTime(n.published || n.created_at) %></span><% } %>
177 </div>
178
179 <% var _hasWhat = !_sub || _priv
180 || (_t === 'mention' && (n.help_request || n.wave || n.note_url))
181 || (_t === 'sent' && n.in_reply_to); %>
182 <% if (_hasWhat) { %>
183 <div class="msg-what">
184 <% if (n.count && n.count > 1) { %>
185 <strong><%= t('msg.and_more', { n: n.count - 1 }) %></strong>
186 <%= _t === 'like' ? t('msg.liked_many') : t('msg.boosted_many') %>
187 <% } else if (_t === 'follow') { %><%= t('notif.followed') %>
188 <% } else if (_t === 'like') { %><%= t('notif.liked') %>
189 <% } else if (_t === 'boost') { %><%= t('notif.boosted') %>
190 <% } else if (_t === 'report') { %><%= t('notif.reported') %>
191 <% } else if (_t === 'mention' && n.help_request) { %>🛟 <%= t('msg.help_request') %>
192 <% } else if (_t === 'mention' && n.wave) { %>👋 <%= t('msg.waved_at_you') %>
193 <% } else if (_sub) { %><%# binnen een draad zegt de kop al dat dit een gesprek is %>
194 <% } else if (_t === 'mention') { %><%= t('notif.mentioned') %>
195 <% } else if (_t === 'sent') { %><%= t('msg.sent_reply') %>
196 <% } else if (_t === 'poll_done') { %><strong><%= t('msg.poll_done') %></strong>
197 <% } else { %><%= t('notif.replied') %><% } %>
198 <% if (_priv) { %><span class="msg-priv" title="<%= t('msg.private_hint') %>">🔒 <%= t('msg.private') %></span><% } %>
199 <% if (n.post_slug && !_sub) { %><a class="msg-post" href="/<%= n.post_slug %>"><%= n.post_title || n.post_slug %></a><% } %>
200 <% if (_t === 'mention' && n.note_url) { %><a class="msg-post" href="<%= n.note_url %>" target="_blank" rel="nofollow noopener"><%= t('tl.view_original') %></a><% } %>
201 <% if (_t === 'sent' && n.in_reply_to) { %><a class="msg-post" href="<%= n.in_reply_to %>" target="_blank" rel="nofollow noopener"><%= t('tl.view_original') %></a><% } %>
202 </div>
203 <% } %>
204
205 <% if (_t === 'report' && n.content) { %><div class="msg-content"><%= n.content %></div>
206 <% } else if ((_t === 'reply' || _t === 'mention' || _t === 'sent') && n.content) { %><div class="msg-content msg-note"><%- include('../partials/note-body', { nb: n }) %></div>
207 <% if (_t === 'mention' && n.wave && n.actorUri && canMutate) { %>
208 <form class="msg-quickreply" method="post" action="<%= (typeof siteUrlBase !== 'undefined' ? siteUrlBase : '') %>/messages/quick-reply" data-sent="<%= t('msg.wave_sent') %>" style="display:flex;gap:6px;flex-wrap:wrap;margin-top:6px">
209 <input type="hidden" name="to" value="<%= n.actorUri %>">
210 <button class="btn small" name="text" value="<%= t('msg.wave_r1') %>"><%= t('msg.wave_r1') %></button>
211 <button class="btn small" name="text" value="<%= t('msg.wave_r2') %>"><%= t('msg.wave_r2') %></button>
212 <button class="btn small" name="text" value="👋"><%= t('msg.wave_back') %></button>
213 </form>
214 <% } %>
215 <% } else if (_t === 'poll_done' && n.poll) { %>
216 <div class="msg-poll" role="group">
217 <% n.poll.options.forEach(function (o) { %>
218 <div class="msg-poll-opt">
219 <div class="msg-poll-top"><span class="msg-poll-name"><%= o.name %></span><span class="msg-poll-pct"><%= o.pct %>%</span></div>
220 <div class="msg-poll-bar"><span style="width:<%= o.pct %>%"></span></div>
221 </div>
222 <% }); %>
223 <div class="msg-poll-total"><%= t('msg.poll_total', { n: n.poll.voters }) %></div>
224 </div>
225 <% } %>
226
227 <% if (_t === 'sent' && n.outboxId) { %>
228 <div class="msg-actions">
229 <details class="msg-edit">
230 <summary><%= t('fedi.edit') %></summary>
231 <div class="msg-edit-form">
232 <%- include('../partials/reply-editor', {
233 action: '/fediverse/' + n.outboxId + '/edit',
234 placeholder: t('fedi.reply_ph'),
235 submitLabel: t('fedi.save_edit'),
236 rows: 2,
237 initialHtml: n.content,
238 initialText: n.editable,
239 defaultLang: n.language,
240 noAttach: true,
241 }) %>
242 </div>
243 </details>
244 <form method="post" action="/fediverse/<%= n.outboxId %>/delete" data-confirm="<%= t('fedi.delete_confirm') %>">
245 <button type="submit" class="msg-del"><%= t('comments.delete') %></button>
246 </form>
247 </div>
248 <% } %>
249 </div>
250 </li>
251<% } %>
Note: See TracBrowser for help on using the repository browser.