source: Klonkt/src/views/partials/remote-object.ejs@ 19e28ce

main
Last change on this file since 19e28ce was 9eb6e1b, checked in by Robin <roboburr@…>, 5 hours ago

Een fediverse-post ziet eruit als een post, ook in de zoekbalk

Plak je een link in de zoekbalk, dan laadt de uitklaplijst nu de post zelf
in plaats van te zoeken naar de tekst van de link. Dezelfde kaart staat op de
zoekpagina en op /authorize_interaction, dus één render op drie plekken.

DE KAART. Wat eromheen stond is teruggebracht tot een kop met gezicht, de
inhoud, en één rij knoppen: Reageer, Like, Boost en het origineel. Weg zijn de
zin "je reageert op", de aparte knop naar het origineel, de twee grote pillen,
"of reageer:" en het antwoordvenster dat altijd openstond. Antwoorden klapt
open als je erom vraagt; melden staat onderaan en niet in de weg.

Foto's staan in een raster met vaste rijhoogtes. Met verhoudingen groeide het
mee met de breedte, en werd op een desktop de eerste van drie foto's zo hoog
dat de knoppen onder de vouw van de uitklaplijst verdwenen.

EEN INHOUDSWAARSCHUWING WORDT GERESPECTEERD. De oude render toonde alles open,
ook wat de schrijver achter een waarschuwing had gezet.

MOBIEL. Elke knop minstens 44 punten. Op een smal scherm vallen de woorden weg
en blijven de tekens staan, gelijk verdeeld; de woorden blijven bestaan voor
wie het scherm voorgelezen krijgt. Nagemeten op 375 pixels: geen horizontale
overloop, kleinste tikdoel 44, de knoppen in beeld zonder te scrollen.

DE PREVIEW IS COMPACT. Waarderen en boosten kan ter plekke; antwoorden en
melden gaan naar de volle pagina, met ?reply=1 of ?report=1 zodat dat deel
daar al openstaat. Een editor in een uitklaplijst is op een telefoon geen plek
om te schrijven. Zonder JS doet dezelfde link hetzelfde.

/search/remote levert het fragment, achter dezelfde grens als de volle
pagina: alleen wie de site beheert laat ophalen. Die grens weegt hier zwaarder,
want deze route wordt bij elke toetsaanslag geraakt; zonder hem is hij een
haalservice die iedereen deze server op adressen naar keuze laat afsturen.
Voor een bezoeker 204, en dan valt de zoekbalk terug op de gewone suggesties.
Elke toetsaanslag krijgt een volgnummer, zodat een traag antwoord op een oude
vraag niet overschrijft wat je inmiddels hebt getypt.

Waarderen en boosten ter plekke verhuisde van authorize-interaction.js naar
chrome.js: de kaart staat nu op elke pagina, via de zoekbalk. De zoekpagina
stuurde je bij een like eerder weg naar /authorize_interaction.

Onderweg: /authorize_interaction haalt nu ook ondertekend op, net als de
zoekbalk. Anders zegt die pagina "niet gevonden" over een post voor volgers die
de preview wel liet zien.

Getoetst: de preview met de post, ondertekend; wat er in de compacte kaart wel
en niet staat; dat een bezoeker via de preview niets laat ophalen; dat een
zoekterm niets ophaalt; de waarschuwing; het venster op verzoek. Tegenproef:
rechtencheck eruit, waarschuwing genegeerd, compact genegeerd en anoniem
ophalen laten elk hun eigen toets omvallen. Volledige suite 1283 groen.

Het gedrag in chrome.js is in de browser nagelopen met een nagebootste
server: herkennen, laden, invoegen, de volgorde bij een traag antwoord, de
terugval bij 204, waarderen en boosten heen en terug, en het antwoordvenster
open en dicht met de focus op de goede plek.

shaer-utpi

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

  • Property mode set to 100644
File size: 9.2 KB
Line 
1<%#
2 Een post van een andere server: hoe hij eruit ziet, en wat je ermee kunt.
3
4 EEN PLEK, DRIE INGANGEN: de live-preview onder de zoekbalk, de zoekpagina en
5 /authorize_interaction. Eén render, zodat een poort, een sanitizer of een knop
6 niet op één plek vergeten kan worden. De formulieren posten overal naar
7 /authorize_interaction/..., want daar zit het werk en de rechtencontrole.
8
9 Hij ziet eruit als een POST, niet als een formulier over een post: kop met
10 gezicht, de inhoud, en één rij knoppen eronder. Wat daar vroeger omheen stond
11 (een zin "je reageert op", een aparte knop naar het origineel, twee grote
12 pillen, "of reageer:" en een altijd open antwoordvenster) is teruggebracht tot
13 die ene rij. Antwoorden klapt pas open als je erom vraagt, melden staat
14 onderaan en niet in de weg.
15
16 Verwacht: target (uit resolveRemoteNote), uri, reacted {liked,boosted}.
17 Optioneel:
18 compact de live-preview: geen antwoordvenster en geen meldformulier; die
19 twee staan een tik verder op de volle pagina. Een editor in een
20 uitklaplijst is op een telefoon geen plek om te schrijven.
21 mayReply false = alleen lezen en reageren, geen antwoordknop
22 replyOpen het antwoordvenster staat al open (?reply=1, de terugval zonder JS)
23 reportOpen idem voor melden (?report=1)
24 cancelUrl waar Annuleren heen gaat zonder JS, standaard de volle pagina
25 siteTitle
26%>
27<%
28 const _compact = !!locals.compact;
29 const _mayReply = locals.mayReply !== false;
30 const _full = '/authorize_interaction?uri=' + encodeURIComponent(uri);
31 const _cancel = locals.cancelUrl || _full;
32 const _liked = !!(locals.reacted && reacted.liked);
33 const _boosted = !!(locals.reacted && reacted.boosted);
34 const _src = target.url || uri;
35 // Een inhoudswaarschuwing wordt gerespecteerd. De oude render toonde alles
36 // gewoon open, ook wat de schrijver achter een waarschuwing had gezet.
37 const _warn = target.cw || (target.sensitive ? t('fedi.sensitive') : '');
38 const _imgs = (target.images || []).slice(0, 4);
39 const _initial = String(target.actor_name || target.actor_handle || '?').replace(/^@/, '').slice(0, 1).toUpperCase();
40%>
41<article class="rp-card<%= _compact ? ' rp-compact' : '' %>">
42 <header class="rp-head">
43 <% if (target.actor_icon) { %>
44 <img class="rp-avatar" src="<%= target.actor_icon %>" alt="" width="40" height="40" loading="lazy">
45 <% } else { %>
46 <span class="rp-avatar rp-avatar-empty" aria-hidden="true"><%= _initial %></span>
47 <% } %>
48 <div class="rp-who">
49 <a class="rp-name" href="<%= target.actor_url || target.actor_uri %>" rel="nofollow noopener" target="_blank"><%= target.actor_name || target.actor_handle %></a>
50 <span class="rp-handle"><%= target.actor_handle %></span>
51 </div>
52 </header>
53
54 <% if (_warn) { %>
55 <details class="rp-cw">
56 <summary><span class="rp-cw-text"><%= _warn %></span><span class="rp-cw-open"><%= t('fedi.cw_open') %></span></summary>
57 <% } %>
58 <% if (target.content) { %>
59 <div class="rp-body"><%- target.content %></div>
60 <% } else if (target.preview) { %>
61 <div class="rp-body"><p><%= target.preview %></p></div>
62 <% } %>
63 <% if (_imgs.length) { %>
64 <div class="rp-media rp-media-<%= _imgs.length %>">
65 <% _imgs.forEach(function (im) { %>
66 <a href="<%= im %>" target="_blank" rel="nofollow noopener"><img src="<%= im %>" alt="" loading="lazy"></a>
67 <% }); %>
68 </div>
69 <% } %>
70 <% if (_warn) { %>
71 </details>
72 <% } %>
73
74 <% if (target.poll) { var _pl = target.poll; var _tot = _pl.options.reduce(function(s,o){return s+(o.count||0);},0); var _vn = (_pl.voters!=null?_pl.voters:_tot); %>
75 <% if (!_pl.closed) { %>
76 <form method="post" action="/authorize_interaction/vote" class="rp-poll">
77 <input type="hidden" name="uri" value="<%= uri %>">
78 <% _pl.options.forEach(function(o){ %>
79 <label class="rp-poll-choice"><input type="<%= _pl.multiple ? 'checkbox' : 'radio' %>" name="choice" value="<%= o.name %>"><span><%= o.name %></span></label>
80 <% }); %>
81 <div class="rp-poll-foot">
82 <span><%= _vn %> <%= _vn === 1 ? t('poll.voter_one') : t('poll.voter_many') %><% if (_pl.endTime) { %> · <%= t('poll.closes') %> <%= new Date(_pl.endTime).toLocaleString() %><% } %><% if (_pl.multiple) { %> · <%= t('poll.multiple') %><% } %></span>
83 <button type="submit" class="rp-act rp-primary"><%= t('poll.vote') %></button>
84 </div>
85 </form>
86 <% } else { %>
87 <div class="rp-poll">
88 <% _pl.options.forEach(function(o){ var _pct = _tot ? Math.round((o.count||0)*100/_tot) : 0; %>
89 <div class="rp-poll-res"><span class="rp-poll-fill" style="width:<%= _pct %>%"></span><span class="rp-poll-name"><%= o.name %></span><span class="rp-poll-pct"><%= _pct %>%</span></div>
90 <% }); %>
91 <div class="rp-poll-foot"><span><%= _vn %> <%= _vn === 1 ? t('poll.voter_one') : t('poll.voter_many') %> · <%= t('poll.closed') %></span></div>
92 </div>
93 <% } %>
94 <% } %>
95
96 <%# Eén rij. Op een smalle telefoon vallen de woorden weg en blijven de
97 tekens staan, elk minstens 44 punten groot: een duim is geen muis. De
98 woorden blijven voor een schermlezer wel gewoon bestaan. %>
99 <footer class="rp-actions">
100 <% if (_mayReply) { %>
101 <a class="rp-act rp-reply-btn" href="<%= _full %>&amp;reply=1#rp-reply" data-rp-reply<% if (!_compact) { %> aria-controls="rp-reply" aria-expanded="<%= locals.replyOpen ? 'true' : 'false' %>"<% } %>>
102 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><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>
103 <span class="rp-act-label"><%= t('fedi.reply_short') %></span>
104 </a>
105 <% } %>
106 <form method="post" action="/authorize_interaction/like" class="fedi-react-form">
107 <input type="hidden" name="uri" value="<%= uri %>">
108 <button type="submit" class="rp-act rp-like<%= _liked ? ' is-on' : '' %>" aria-pressed="<%= _liked %>" data-on="<%= t('fedi.unlike_short') %>" data-off="<%= t('fedi.like_short') %>">
109 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round" aria-hidden="true"><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>
110 <span class="rp-act-label"><%= _liked ? t('fedi.unlike_short') : t('fedi.like_short') %></span>
111 </button>
112 </form>
113 <form method="post" action="/authorize_interaction/boost" class="fedi-react-form">
114 <input type="hidden" name="uri" value="<%= uri %>">
115 <button type="submit" class="rp-act rp-boost<%= _boosted ? ' is-on' : '' %>" aria-pressed="<%= _boosted %>" data-on="<%= t('tl.unboost') %>" data-off="<%= t('fedi.boost_short') %>">
116 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><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>
117 <span class="rp-act-label"><%= _boosted ? t('tl.unboost') : t('fedi.boost_short') %></span>
118 </button>
119 </form>
120 <a class="rp-act rp-src" href="<%= _src %>" target="_blank" rel="nofollow noopener" title="<%= t('fedi.remote_view_original') %>">
121 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M7 17 17 7"/><path d="M7 7h10v10"/></svg>
122 <span class="rp-act-label"><%= t('fedi.src_short') %></span>
123 </a>
124 </footer>
125</article>
126
127<% if (_compact) { %>
128 <a class="rp-open" href="<%= _full %>"><%= t('fedi.open_full') %> →</a>
129<% } else { %>
130 <% if (_mayReply) { %>
131 <%# Dicht tot je op Reageer tikt. Zonder JS opent ?reply=1 hem, zodat de
132 knop ook dan ergens heen gaat. %>
133 <section class="rp-reply" id="rp-reply"<%= locals.replyOpen ? '' : ' hidden' %>>
134 <p class="rp-reply-where"><%= t('fedi.reply_where') %></p>
135 <%- include('./reply-editor', {
136 action: '/authorize_interaction',
137 hiddenFields: [{ name: 'uri', value: uri }],
138 placeholder: t('fedi.reply_ph'),
139 submitLabel: t('fedi.send'),
140 rows: 4,
141 participants: (target && target.actor_uri) ? [{
142 uri: target.actor_uri,
143 url: target.actor_url || target.actor_uri,
144 handle: target.actor_handle || '',
145 }] : [],
146 }) %>
147 <div class="rp-reply-foot"><a href="<%= _cancel %>" class="rp-act" data-rp-reply-close><%= t('comments.cancel') %></a></div>
148 </section>
149 <% } %>
150 <p class="rp-as"><%= t('fedi.remote_as', { site: siteTitle }) %></p>
151 <details class="rp-report" id="rp-report"<%= locals.reportOpen ? ' open' : '' %>>
152 <summary><%= t('fedi.report_open') %></summary>
153 <form method="post" action="/authorize_interaction/report" class="rp-report-form">
154 <input type="hidden" name="uri" value="<%= uri %>">
155 <input type="hidden" name="actor_uri" value="<%= target.actor_uri %>">
156 <p class="rp-report-note"><%= t('fedi.report_where') %></p>
157 <textarea name="reason" rows="3" maxlength="3000" placeholder="<%= t('fedi.report_ph') %>"></textarea>
158 <div><button type="submit" class="btn btn-danger"><%= t('fedi.report_send') %></button></div>
159 </form>
160 </details>
161<% } %>
Note: See TracBrowser for help on using the repository browser.