source: Klonkt/src/views/partials/msg-item.ejs@ ea138c0

main
Last change on this file since ea138c0 was ea138c0, checked in by roboburr <roboburr@…>, 5 weeks ago

Gesprekken in- en uitklappen door op de tegenpartij te tikken

Barts aanwijzing: de uitsparing met de naam is ook de knop. Dat is waar je van
nature naar wijst als je "dit gesprek" bedoelt, en het scheelt een extra
bedieningselement in een lijst die juist rustiger moest worden.

Een BUTTON en geen span met een klikhandler: dan werkt hij met het toetsenbord en
leest een schermlezer de stand voor (aria-expanded). Zonder JavaScript doet hij
niets, en dan staat het gesprek open -- wat ook de beginstand is. Standaard
uitgeklapt, zoals gevraagd.

Ingeklapt blijven de naam en het AANTAL staan. Dat aantal is juist ingeklapt het
meest waard: het is dan het enige wat zegt hoe groot het gesprek is.

Twee dingen die eromheen moesten:

RAAKVLAK de uitsparing is 18px hoog en dat is op een telefoon niet te raken.

De knop krijgt een minimumhoogte; de uitsparing blijft klein, het
doel wordt groter.

ZOEKEN een treffer in een dichtgeklapt gesprek liet alleen de naam zien: je

zoekt iets, het staat er, en je ziet het niet. Zolang er gezocht
wordt gaat zo'n gesprek open en daarna keert hij terug naar de stand
die de lezer zelf koos.

De stand leeft alleen in de pagina en niet in opslag. Een gesprek dat je gisteren
dichtklapte stilhouden is iets anders dan het opruimen van je scherm van nu.

De nieuwe CSS staat NA .msg-item, om dezelfde reden als de rest van dat blok: bij
gelijke specificiteit wint de laatste regel.

Templates compileren, suite 551/551. De pagina zelf staat achter een login, dus
het gedrag in beeld is aan Bart.

  • Property mode set to 100644
File size: 15.6 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 %>
14 <li class="msg-item msg-thread<%= _tnew ? ' is-new' : '' %>" data-kind="conv" data-who="<%= (_tpeople.join(' ') + ' ' + (_tto || '')).toLowerCase() %>">
15 <div class="msg-thread-head">
16 <%# Tegenpartij en aantal in EEN uitsparing: het is een label bij het
17 gesprek, geen kop erboven. Zo houdt de inhoud de ruimte.
18
19 Deze uitsparing is ook de knop die het gesprek in- en uitklapt
20 (Barts aanwijzing): tikken op de tegenpartij is waar je van
21 nature naar wijst als je "dit gesprek" bedoelt. Een BUTTON en
22 geen span met een klikhandler, want dan werkt hij ook met het
23 toetsenbord en leest een schermlezer de stand voor.
24
25 Zonder JavaScript doet hij niets, en dan staat het gesprek
26 gewoon open -- dat is ook de stand waar we mee beginnen. %>
27 <button type="button" class="msg-thread-who" aria-expanded="true">
28 <span class="msg-thread-name"><%= _tlabel %></span>
29 <% if (n.count > 1) { %><span class="msg-thread-count"><%= n.count %></span><% } %>
30 <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>
31 </button>
32 <% if (_tnew) { %><span class="msg-new" title="<%= t('msg.new') %>"></span><% } %>
33 <%# De context bij de draad: waar gaat dit gesprek over? Zonder deze
34 link is een antwoord in een lijst niet te plaatsen. %>
35 <% if (n.post) { %>
36 <a class="msg-thread-post" href="/<%= n.post.slug %>">
37 <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>
38 <%= n.post.title || n.post.slug %>
39 </a>
40 <% } %>
41 </div>
42 <ul class="msg-thread-msgs">
43 <% (n.messages || []).forEach(function (m) { %><%- include('msg-item', { n: m, seen: _tseen, inThread: true }) %><% }); %>
44 </ul>
45 <%# Antwoorden vanuit de draad zelf. Ingeklapt, want een open editor
46 onder elk gesprek maakt de lijst onleesbaar -- juist het probleem
47 dat deze weergave moest oplossen. De route kiest het pad: een
48 draad aan een post krijgt een reply, een draad aan een persoon een
49 direct bericht. %>
50 <% var _rt = n.replyTo || {}; var _kan = canMutate && ((_rt.interactionId && _rt.postSlug) || _rt.actorUri); %>
51 <% if (_kan) { %>
52 <div class="msg-thread-foot">
53 <details class="msg-reply">
54 <summary><%= t('fedi.reply') %></summary>
55 <div class="msg-reply-form">
56 <%- include('../partials/reply-editor', {
57 action: (typeof siteUrlBase !== 'undefined' ? siteUrlBase : '') + '/messages/reply',
58 hiddenFields: [
59 { name: 'interaction_id', value: _rt.interactionId || '' },
60 { name: 'post_slug', value: _rt.postSlug || '' },
61 { name: 'to', value: _rt.actorUri || '' },
62 ],
63 placeholder: t('fedi.reply_ph'),
64 submitLabel: t('fedi.reply'),
65 rows: 2,
66 }) %>
67 </div>
68 </details>
69 <%# De zwaai blijft een eigen knop: het is geen antwoord maar een
70 seintje (FEP-633c), en die twee horen niet op een hoop. %>
71 <% if (_rt.actorUri) { %>
72 <form method="post" action="<%= (typeof siteUrlBase !== 'undefined' ? siteUrlBase : '') %>/messages/quick-reply" class="msg-wave">
73 <input type="hidden" name="to" value="<%= _rt.actorUri %>">
74 <button class="btn small" name="text" value="&#128075;" title="<%= t('msg.wave_back') %>">&#128075;</button>
75 </form>
76 <% } %>
77 </div>
78 <% } %>
79 </li>
80<% } else { %>
81<%
82 var _seen = (typeof seen !== 'undefined' && seen) ? seen : 0;
83 var _t = n.type === 'announce' ? 'boost' : n.type; // reply|mention|report|like|boost|follow|sent
84 var _new = _seen && n.created_at ? (Date.parse(n.created_at) > _seen) : false;
85 var _who = n.name || n.handle || '';
86 var _priv0 = (n.visibility === 'followers' || n.visibility === 'direct');
87 // Emmers: Activiteit = likes/boosts/follows/polls, Moderatie =
88 // reports, en al het overige is gesprek. De oude emmers 'msgs' en
89 // 'sent' bestaan niet meer sinds Berichten, Gesprekken en Verzonden
90 // samenvielen -- en dat is geen cosmetische hernoeming: een
91 // gespreksregel die om wat voor reden dan ook geen draad werd (geen
92 // post, geen herkenbare tegenpartij) hield anders een data-kind
93 // waarvoor geen chip meer bestond, en was daarmee onder ELKE chip
94 // behalve Alles onzichtbaar.
95 var _kind = (_t === 'like' || _t === 'boost' || _t === 'follow' || _t === 'poll_done') ? 'act'
96 : _t === 'report' ? 'mod'
97 : 'conv';
98 // Binnen een draad is dit geen zelfstandige regel: de chip filtert op
99 // de draad, niet op de losse zinnen erin.
100 var _sub = (typeof inThread !== 'undefined' && inThread);
101 var _init = String(_who || '?').replace(/^@/, '').charAt(0).toUpperCase();
102 var _priv = (n.visibility === 'followers' || n.visibility === 'direct');
103 %>
104 <li class="<%= _sub ? 'msg-sub' : 'msg-item' %> msg-<%= _t %><%= _new ? ' is-new' : '' %>"<% if (!_sub) { %> data-kind="<%= _kind %>"<% } %> data-who="<%= String(_who).toLowerCase() %>">
105 <span class="msg-av<%= _t === 'sent' ? ' msg-av-sent' : '' %>" aria-hidden="true">
106 <% 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>
107 <% } 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>
108 <% } else if (n.icon) { %><img src="<%= avatar(n.icon, 96) %>" alt="" loading="lazy">
109 <% } else { %><%= _init %><% } %>
110 <span class="msg-dot msg-dot-<%= _t %>">
111 <% 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>
112 <% } 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>
113 <% } 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>
114 <% } 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>
115 <% } 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>
116 <% } 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>
117 <% } 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>
118 <% } 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><% } %>
119 </span>
120 </span>
121
122 <div class="msg-body">
123 <div class="msg-line">
124 <% if (_t === 'sent') { %>
125 <span class="msg-who"><%= t('msg.you') %></span>
126 <% if (n.to_handle && !_sub) { %><span class="msg-handle">→ <%= n.to_handle %></span><% } %>
127 <% } else { %>
128 <a class="msg-who" href="<%= n.url %>" target="_blank" rel="nofollow noopener"><%- emojiName(_who, n.actor_emoji_json) %></a>
129 <% /* De naam blijft: binnen een draad moet je zien WIE wat zei.
130 De handle erachter niet -- die staat in de kop. */ %>
131 <% if (n.handle && n.name && !_sub) { %><span class="msg-handle"><%= n.handle %></span><% } %>
132 <% } %>
133 <% if (_new) { %><span class="msg-new" title="<%= t('msg.new') %>"></span><% } %>
134 <% /* When the post was written, like de Krant and de Guardian PWA
135 show it. created_at (when it reached us) stays the sort key
136 and drives the "new since your last visit" dot above. */ %>
137 <% if (n.published || n.created_at) { %><span class="msg-time"><%= formatDateTime(n.published || n.created_at) %></span><% } %>
138 </div>
139
140 <% var _hasWhat = !_sub || _priv
141 || (_t === 'mention' && (n.help_request || n.wave || n.note_url))
142 || (_t === 'sent' && n.in_reply_to); %>
143 <% if (_hasWhat) { %>
144 <div class="msg-what">
145 <% if (n.count && n.count > 1) { %>
146 <strong><%= t('msg.and_more', { n: n.count - 1 }) %></strong>
147 <%= _t === 'like' ? t('msg.liked_many') : t('msg.boosted_many') %>
148 <% } else if (_t === 'follow') { %><%= t('notif.followed') %>
149 <% } else if (_t === 'like') { %><%= t('notif.liked') %>
150 <% } else if (_t === 'boost') { %><%= t('notif.boosted') %>
151 <% } else if (_t === 'report') { %><%= t('notif.reported') %>
152 <% } else if (_t === 'mention' && n.help_request) { %>🛟 <%= t('msg.help_request') %>
153 <% } else if (_t === 'mention' && n.wave) { %>👋 <%= t('msg.waved_at_you') %>
154 <% } else if (_sub) { %><%# binnen een draad zegt de kop al dat dit een gesprek is %>
155 <% } else if (_t === 'mention') { %><%= t('notif.mentioned') %>
156 <% } else if (_t === 'sent') { %><%= t('msg.sent_reply') %>
157 <% } else if (_t === 'poll_done') { %><strong><%= t('msg.poll_done') %></strong>
158 <% } else { %><%= t('notif.replied') %><% } %>
159 <% if (_priv) { %><span class="msg-priv" title="<%= t('msg.private_hint') %>">🔒 <%= t('msg.private') %></span><% } %>
160 <% if (n.post_slug && !_sub) { %><a class="msg-post" href="/<%= n.post_slug %>"><%= n.post_title || n.post_slug %></a><% } %>
161 <% if (_t === 'mention' && n.note_url) { %><a class="msg-post" href="<%= n.note_url %>" target="_blank" rel="nofollow noopener"><%= t('tl.view_original') %></a><% } %>
162 <% 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><% } %>
163 </div>
164 <% } %>
165
166 <% if (_t === 'report' && n.content) { %><div class="msg-content"><%= n.content %></div>
167 <% } else if ((_t === 'reply' || _t === 'mention' || _t === 'sent') && n.content) { %><div class="msg-content msg-note"><%- include('../partials/note-body', { nb: n }) %></div>
168 <% if (_t === 'mention' && n.wave && n.actorUri && canMutate) { %>
169 <form class="msg-quickreply" method="post" action="<%= (typeof siteUrlBase !== 'undefined' ? siteUrlBase : '') %>/messages/quick-reply" style="display:flex;gap:6px;flex-wrap:wrap;margin-top:6px">
170 <input type="hidden" name="to" value="<%= n.actorUri %>">
171 <button class="btn small" name="text" value="<%= t('msg.wave_r1') %>"><%= t('msg.wave_r1') %></button>
172 <button class="btn small" name="text" value="<%= t('msg.wave_r2') %>"><%= t('msg.wave_r2') %></button>
173 <button class="btn small" name="text" value="👋"><%= t('msg.wave_back') %></button>
174 </form>
175 <% } %>
176 <% } else if (_t === 'poll_done' && n.poll) { %>
177 <div class="msg-poll" role="group">
178 <% n.poll.options.forEach(function (o) { %>
179 <div class="msg-poll-opt">
180 <div class="msg-poll-top"><span class="msg-poll-name"><%= o.name %></span><span class="msg-poll-pct"><%= o.pct %>%</span></div>
181 <div class="msg-poll-bar"><span style="width:<%= o.pct %>%"></span></div>
182 </div>
183 <% }); %>
184 <div class="msg-poll-total"><%= t('msg.poll_total', { n: n.poll.voters }) %></div>
185 </div>
186 <% } %>
187
188 <% if (_t === 'sent' && n.outboxId) { %>
189 <div class="msg-actions">
190 <details class="msg-edit">
191 <summary><%= t('fedi.edit') %></summary>
192 <div class="msg-edit-form">
193 <%- include('../partials/reply-editor', {
194 action: '/fediverse/' + n.outboxId + '/edit',
195 placeholder: t('fedi.reply_ph'),
196 submitLabel: t('fedi.save_edit'),
197 rows: 2,
198 initialHtml: n.content,
199 initialText: n.editable,
200 defaultLang: n.language,
201 noAttach: true,
202 }) %>
203 </div>
204 </details>
205 <form method="post" action="/fediverse/<%= n.outboxId %>/delete" data-confirm="<%= t('fedi.delete_confirm') %>">
206 <button type="submit" class="msg-del"><%= t('comments.delete') %></button>
207 </form>
208 </div>
209 <% } %>
210 </div>
211 </li>
212<% } %>
Note: See TracBrowser for help on using the repository browser.