Changeset 9eb6e1b in Klonkt for src/views/partials/remote-object.ejs
- Timestamp:
- 09/30/2026 06:41:45 AM (2 hours ago)
- Branches:
- main
- Children:
- 86cfa1a
- Parents:
- 3ceaef6
- File:
-
- 1 edited
-
src/views/partials/remote-object.ejs (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
src/views/partials/remote-object.ejs
r3ceaef6 r9eb6e1b 1 1 <%# 2 Een object van een andere server: hoe heteruit ziet, en wat je ermee kunt.2 Een post van een andere server: hoe hij eruit ziet, en wat je ermee kunt. 3 3 4 EEN PLEK, TWEE INGANGEN (de zoekbalk en /authorize_interaction). Dit stond 5 alleen op die tweede pagina, en toen de zoekfunctie een URL leerde herkennen 6 zou het een tweede render worden -- en daarmee een tweede plek waar een poort, 7 een sanitizer of een knop vergeten kan worden. De formulieren posten dus ook 8 vanaf de zoekpagina naar /authorize_interaction/..., want daar zit het werk en 9 daar zit de rechtencontrole. 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. 10 15 11 16 Verwacht: target (uit resolveRemoteNote), uri, reacted {liked,boosted}. 12 Optioneel: mayReply (false = alleen lezen en reageren, geen antwoordvenster), 13 cancelUrl (waar Annuleren heen gaat, standaard het origineel), siteTitle. 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 14 26 %> 15 <% const _cancel = (typeof cancelUrl !== 'undefined' && cancelUrl) ? cancelUrl : uri; %> 16 <p class="auth-interact-meta"> 17 <%= t('fedi.remote_replying_to') %> 18 <a href="<%= target.actor_url %>" rel="nofollow noopener" target="_blank"><strong><%= target.actor_name %></strong></a> 19 <span class="fedi-handle"><%= target.actor_handle %></span> 20 </p> 21 <blockquote class="auth-interact-preview"> 22 <% if (target.content) { %><div class="auth-interact-content"><%- target.content %></div> 23 <% } else if (target.preview) { %><%= target.preview %><% } %> 24 <% (target.images || []).forEach(function(im){ %> 25 <img class="auth-interact-img" src="<%= im %>" alt="" loading="lazy"> 26 <% }); %> 27 </blockquote> 28 <% if (target.url || uri) { %> 29 <a class="auth-view-src" href="<%= target.url || uri %>" target="_blank" rel="nofollow noopener"> 30 <svg viewBox="0 0 24 24" width="17" height="17" 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> 31 <span><%= t('fedi.remote_view_original') %></span> 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 %>&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> 32 104 </a> 33 105 <% } %> 34 <% 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); %> 35 <% if (!_pl.closed) { %> 36 <form method="post" action="/authorize_interaction/vote" class="auth-poll"> 37 <input type="hidden" name="uri" value="<%= uri %>"> 38 <% _pl.options.forEach(function(o){ %> 39 <label class="auth-poll-choice"><input type="<%= _pl.multiple ? 'checkbox' : 'radio' %>" name="choice" value="<%= o.name %>"><span><%= o.name %></span></label> 40 <% }); %> 41 <div class="auth-interact-actions"> 42 <button type="submit" class="btn btn-primary"><%= t('poll.vote') %></button> 43 </div> 44 <p class="auth-poll-foot"><%= _vn %> <%= _vn === 1 ? t('poll.voter_one') : t('poll.voter_many') %> · <%= t('poll.open') %><% if (_pl.endTime) { %> · <%= t('poll.closes') %> <%= new Date(_pl.endTime).toLocaleString() %><% } %><% if (_pl.multiple) { %> · <%= t('poll.multiple') %><% } %></p> 45 </form> 46 <% } else { %> 47 <div class="auth-poll"> 48 <% _pl.options.forEach(function(o){ var _pct = _tot ? Math.round((o.count||0)*100/_tot) : 0; %> 49 <div class="auth-poll-res"><span class="auth-poll-fill" style="width:<%= _pct %>%"></span><span class="auth-poll-name"><%= o.name %></span><span class="auth-poll-pct"><%= _pct %>%</span></div> 50 <% }); %> 51 <p class="auth-poll-foot"><%= _vn %> <%= _vn === 1 ? t('poll.voter_one') : t('poll.voter_many') %> · <%= t('poll.closed') %><% if (_pl.multiple) { %> · <%= t('poll.multiple') %><% } %></p> 52 </div> 53 <% } %> 54 <% } %> 55 <% var _liked = (typeof reacted !== 'undefined' && reacted.liked); var _boosted = (typeof reacted !== 'undefined' && reacted.boosted); %> 56 <div class="auth-interact-react"> 57 <form method="post" action="/authorize_interaction/like" class="fedi-react-form"> 58 <input type="hidden" name="uri" value="<%= uri %>"> 59 <button type="submit" class="fedi-bigact fedi-bigact-like<%= _liked ? ' is-on' : '' %>" data-on="<%= t('fedi.unlike_short') %>" data-off="<%= t('fedi.like_short') %>"> 60 <svg viewBox="0 0 24 24" fill="currentColor" 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> 61 <span class="fedi-bigact-label"><%= _liked ? t('fedi.unlike_short') : t('fedi.like_short') %></span> 62 </button> 63 </form> 64 <form method="post" action="/authorize_interaction/boost" class="fedi-react-form"> 65 <input type="hidden" name="uri" value="<%= uri %>"> 66 <button type="submit" class="fedi-bigact fedi-bigact-boost<%= _boosted ? ' is-on' : '' %>" data-on="<%= t('tl.unboost') %>" data-off="<%= t('fedi.boost_short') %>"> 67 <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> 68 <span class="fedi-bigact-label"><%= _boosted ? t('tl.unboost') : t('fedi.boost_short') %></span> 69 </button> 70 </form> 71 </div> 72 <% if (locals.mayReply !== false) { %> 73 <p class="auth-interact-or"><%= t('fedi.or_reply') %></p> 74 <p class="auth-interact-where">💬 <%= t('fedi.reply_where') %></p> 75 <div class="auth-interact-form"> 76 <%- include('./reply-editor', { 77 action: '/authorize_interaction', 78 hiddenFields: [{ name: 'uri', value: uri }], 79 placeholder: t('fedi.reply_ph'), 80 submitLabel: t('fedi.send'), 81 rows: 4, 82 participants: (target && target.actor_uri) ? [{ 83 uri: target.actor_uri, 84 url: target.actor_url || target.actor_uri, 85 handle: target.actor_handle || '', 86 }] : [], 87 }) %> 88 <div class="auth-interact-actions"><a href="<%= _cancel %>" class="btn"><%= t('comments.cancel') %></a></div> 89 </div> 90 <% } %> 91 <p class="auth-interact-note"><%= t('fedi.remote_as', { site: siteTitle }) %></p> 92 <details class="auth-report"> 93 <summary>🚩 <%= t('fedi.report_open') %></summary> 94 <form method="post" action="/authorize_interaction/report" class="auth-interact-form"> 95 <input type="hidden" name="uri" value="<%= uri %>"> 96 <input type="hidden" name="actor_uri" value="<%= target.actor_uri %>"> 97 <p class="auth-report-note"><%= t('fedi.report_where') %></p> 98 <textarea name="reason" rows="3" maxlength="3000" placeholder="<%= t('fedi.report_ph') %>"></textarea> 99 <div class="auth-interact-actions"> 100 <button type="submit" class="btn btn-danger"><%= t('fedi.report_send') %></button> 101 </div> 102 </form> 103 </details> 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 TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)