Changeset 9eb6e1b in Klonkt for src


Ignore:
Timestamp:
09/30/2026 06:41:45 AM (4 hours ago)
Author:
Robin <roboburr@…>
Branches:
main
Children:
86cfa1a
Parents:
3ceaef6
Message:

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@…>

Location:
src
Files:
1 added
12 edited

Legend:

Unmodified
Added
Removed
  • src/assets/js/mod/authorize-interaction.js

    r3ceaef6 r9eb6e1b  
    4141// ── volgend blok ──
    4242
    43 /* Like/Boost toggle in place — POST via fetch, flip the button, stay on the page. */
    44 (function(){
    45   if (window.__fediReactWired) return; window.__fediReactWired = true;
    46   document.addEventListener('submit', function(e){
    47     var f = e.target.closest && e.target.closest('.fedi-react-form');
    48     if (!f) return;
    49     e.preventDefault();
    50     var btn = f.querySelector('button'); if (!btn || btn.disabled) return;
    51     btn.disabled = true;
    52     var body = new URLSearchParams();
    53     new FormData(f).forEach(function(v, k){ body.append(k, v); });
    54     fetch(f.action, { method: 'POST', body: body, headers: { 'X-Requested-With': 'fetch' }, credentials: 'same-origin' })
    55       .then(function(r){ return r.ok ? r.json() : null; })
    56       .then(function(j){
    57         if (j) {
    58           var on = !!j.on;
    59           btn.classList.toggle('is-on', on);
    60           var lbl = btn.querySelector('.fedi-bigact-label');
    61           if (lbl) lbl.textContent = on ? (btn.getAttribute('data-on') || lbl.textContent) : (btn.getAttribute('data-off') || lbl.textContent);
    62         }
    63       })
    64       .catch(function(){})
    65       .then(function(){ btn.disabled = false; });
    66   });
    67 })();
     43/* Waarderen en boosten ter plekke staat sinds de herbouw van de kaart in
     44   chrome.js: de kaart staat nu ook in de zoekbalk, op elke pagina. */
    6845}
  • src/assets/js/mod/chrome.js

    r3ceaef6 r9eb6e1b  
    161161    box.innerHTML = html;
    162162  }
    163   var _sTimer;
     163  // Een ADRES is geen zoekterm (shaer-utpi). Dezelfde regel als lookupUri in
     164  // routes/search.js: een schema is verplicht, anders wordt zoeken naar
     165  // "soundfabrics.nl" een netwerkverzoek. De server beslist daarna nog eens,
     166  // met de rechten erbij; dit scheelt alleen een vraag die toch nee oplevert.
     167  function lookupUri(q) {
     168    if (!/^https?:\/\//i.test(q) || /\s/.test(q)) return null;
     169    try { var u = new URL(q); return u.hostname.indexOf('.') < 0 ? null : u.href; } catch (e) { return null; }
     170  }
     171  function allLink(ov, q) {
     172    var action = (ov && ov.getAttribute('data-action')) || '/search';
     173    return '<a class="ss-all" href="' + esc(action) + '?q=' + encodeURIComponent(q) + '">' + esc(_st().all) + '</a>';
     174  }
     175  function fetchSuggest(box, q, ov, seq) {
     176    var url = (ov && ov.getAttribute('data-suggest')) || '/search/suggest';
     177    fetch(url + '?q=' + encodeURIComponent(q))
     178      .then(function(r){ return r.ok ? r.json() : null; })
     179      .then(function(d){ if (d && seq === _sSeq) renderSuggest(box, d, q, ov); })
     180      .catch(function(){});
     181  }
     182  // Elke toetsaanslag krijgt een volgnummer. Een ophaling van een andere
     183  // server duurt soms seconden; zonder nummer overschrijft een traag antwoord
     184  // op een oude vraag wat je inmiddels hebt getypt.
     185  var _sTimer, _sSeq = 0;
    164186  document.addEventListener('input', function(e) {
    165187    var inp = e.target.closest && e.target.closest('#search-overlay input[name="q"]');
    … …  
    168190    if (!box) return;
    169191    var q = inp.value.trim();
     192    var seq = ++_sSeq;
    170193    clearTimeout(_sTimer);
    171194    if (q.length < 2) { box.innerHTML = ''; return; }
     195    var ov = document.getElementById('search-overlay');
     196    var address = lookupUri(q);
     197    if (!address) { _sTimer = setTimeout(function() { fetchSuggest(box, q, ov, seq); }, 200); return; }
     198    // Een geplakt adres: de post zelf, niet de tekst van de link doorzocht.
     199    box.innerHTML = '<div class="ss-remote-loading">' + esc(_st().looking) + '</div>';
    172200    _sTimer = setTimeout(function() {
    173       var ov = document.getElementById('search-overlay');
    174       var url = (ov && ov.getAttribute('data-suggest')) || '/search/suggest';
    175       fetch(url + '?q=' + encodeURIComponent(q))
    176         .then(function(r){ return r.ok ? r.json() : null; })
    177         .then(function(d){ if (d) renderSuggest(box, d, q, ov); })
    178         .catch(function(){});
    179     }, 200);
    180   });
    181   // Click on a suggestion → close the overlay (the link/boost handles navigation).
     201      var url = (ov && ov.getAttribute('data-remote')) || '/search/remote';
     202      fetch(url + '?q=' + encodeURIComponent(address), { credentials: 'same-origin', headers: { 'X-Requested-With': 'fetch' } })
     203        .then(function(r) {
     204          if (seq !== _sSeq) return;
     205          // 204: geen recht om op te halen (of toch geen adres). Dan is het
     206          // gewoon een zoekterm en krijg je de gewone suggesties.
     207          if (r.status === 204 || !r.ok) { fetchSuggest(box, q, ov, seq); return; }
     208          return r.text().then(function(html) { if (seq === _sSeq) box.innerHTML = html + allLink(ov, q); });
     209        })
     210        .catch(function() { if (seq === _sSeq) fetchSuggest(box, q, ov, seq); });
     211    }, 300);
     212  });
     213  // Een klik op een suggestie sluit de overlay, want de link gaat ergens heen.
     214  // Behalve wat in een nieuw tabblad opent (het origineel, een profiel): dan
     215  // blijf je hier, en hoort de kaart er nog te staan als je terugkomt.
    182216  document.addEventListener('click', function(e) {
    183     if (e.target.closest && e.target.closest('#search-suggest a')) { var o = overlay(); if (o) o.hidden = true; }
     217    var a = e.target.closest && e.target.closest('#search-suggest a');
     218    if (a && a.getAttribute('target') !== '_blank') { var o = overlay(); if (o) o.hidden = true; }
     219  });
     220})();
     221
     222// ── De kaart van een post van een andere server ─────────────────────────
     223// Staat hier en niet in authorize-interaction.js, want de kaart staat nu op
     224// drie plekken: de live-preview (elke pagina, via de zoekbalk), de zoekpagina
     225// en /authorize_interaction. Alles luistert op document, dus een kaart die
     226// later binnenkomt (de preview, een htmx-wissel) doet vanzelf mee.
     227(function() {
     228  if (window.__remoteCardWired) return; window.__remoteCardWired = true;
     229
     230  // Waarderen en boosten ter plekke: POST via fetch, de knop omzetten, blijven
     231  // waar je bent. Zonder JS doet het formulier hetzelfde met een omweg.
     232  document.addEventListener('submit', function(e) {
     233    var f = e.target.closest && e.target.closest('.fedi-react-form');
     234    if (!f) return;
     235    e.preventDefault();
     236    var btn = f.querySelector('button'); if (!btn || btn.disabled) return;
     237    btn.disabled = true;
     238    var body = new URLSearchParams();
     239    new FormData(f).forEach(function(v, k) { body.append(k, v); });
     240    fetch(f.action, { method: 'POST', body: body, headers: { 'X-Requested-With': 'fetch' }, credentials: 'same-origin' })
     241      .then(function(r) { return r.ok ? r.json() : null; })
     242      .then(function(j) {
     243        if (!j) return;
     244        var on = !!j.on;
     245        btn.classList.toggle('is-on', on);
     246        btn.setAttribute('aria-pressed', on ? 'true' : 'false');
     247        var lbl = btn.querySelector('.rp-act-label');
     248        if (lbl) lbl.textContent = on ? (btn.getAttribute('data-on') || lbl.textContent) : (btn.getAttribute('data-off') || lbl.textContent);
     249      })
     250      .catch(function() {})
     251      .then(function() { btn.disabled = false; });
     252  });
     253
     254  // Antwoorden klapt open waar de kaart staat. Staat er geen venster (de
     255  // preview), dan volgt de knop gewoon zijn link naar de volle pagina, met het
     256  // venster daar al open.
     257  document.addEventListener('click', function(e) {
     258    var open = e.target.closest && e.target.closest('[data-rp-reply]');
     259    var close = !open && e.target.closest && e.target.closest('[data-rp-reply-close]');
     260    if (!open && !close) return;
     261    var panel = document.getElementById('rp-reply');
     262    if (!panel) return;
     263    e.preventDefault();
     264    var btn = document.querySelector('[data-rp-reply][aria-controls="rp-reply"]');
     265    var show = !!open && panel.hidden;
     266    panel.hidden = !show;
     267    if (btn) btn.setAttribute('aria-expanded', show ? 'true' : 'false');
     268    if (show) {
     269      var ed = panel.querySelector('.re-editor, textarea');
     270      if (ed) ed.focus();
     271      // Op een telefoon staat het venster vaak onder de vouw.
     272      if (panel.scrollIntoView) panel.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
     273    } else if (btn) {
     274      btn.focus();
     275    }
    184276  });
    185277})();
  • src/routes/posts.js

    r3ceaef6 r9eb6e1b  
    876876  let target = null, followTarget = null;
    877877  if (!sent && !followed && !voted && !reported && uri) {
    878     try { target = await ActivityPubService.resolveRemoteNote(uri); } catch { /* ignore */ }
     878    // Ondertekend als de site, net als de zoekbalk: een post die alleen voor
     879    // volgers zichtbaar is weigert een anonieme GET, en dan zegt deze pagina
     880    // "niet gevonden" over een post die de preview wel liet zien.
     881    try { target = await ActivityPubService.resolveRemoteNote(uri, site ? { asSlug: site.slug } : {}); } catch { /* ignore */ }
    879882    // Not a post? Maybe the URI is a profile/actor → offer Follow, not reply.
    880883    if (!target) { try { followTarget = await ActivityPubService.resolveRemoteActor(uri); } catch { /* ignore */ } }
    … …  
    895898    reacted: (site && uri) ? ActivityPubService.getReaction(site.slug, uri) : { liked: false, boosted: false },
    896899    siteTitle: site ? site.title : '',
     900    // De knoppen in de preview wijzen hierheen met ?reply=1 of ?report=1: dan
     901    // staat dat deel al open en hoef je niet twee keer te tikken.
     902    replyOpen: !!req.query.reply,
     903    reportOpen: !!req.query.report,
    897904  });
    898905});
  • src/routes/search.js

    r3ceaef6 r9eb6e1b  
    2727import { t as i18nT, resolveLang } from '../services/i18n.js';
    2828import ActivityPubService from '../services/ActivityPubService.js';
     29import ejs from 'ejs';
     30import path from 'path';
     31import { fileURLToPath } from 'url';
    2932import PermissionsService from '../services/PermissionsService.js';
    3033
    … …  
    99102  const site = res.locals.site;
    100103  return !!(site && PermissionsService.canAdminSite(u, site));
     104}
     105
     106/**
     107 * Haal het object achter een adres op: eerst als post, dan als profiel.
     108 *
     109 * Eén weg voor de zoekpagina en de live-preview. Staan ze los, dan verschilt er
     110 * vroeg of laat iets -- de ondertekening bijvoorbeeld, en dan toont de preview
     111 * een post als ontbrekend die de pagina wel vindt.
     112 *
     113 * ONDERTEKEND als de site: een post die alleen voor volgers zichtbaar is weigert
     114 * een anonieme GET, en dan lijkt een bestaande post te ontbreken.
     115 */
     116async function lookUpRemote(remoteUri, site) {
     117  let remote = null;
     118  let remoteKind = null;
     119  try { remote = await ActivityPubService.resolveRemoteNote(remoteUri, { asSlug: site.slug }); } catch { /* onbereikbaar */ }
     120  if (remote) remoteKind = 'note';
     121  if (!remote) {
     122    try { remote = await ActivityPubService.resolveRemoteActor(remoteUri); } catch { /* onbereikbaar */ }
     123    if (remote) remoteKind = 'actor';
     124  }
     125  const reacted = remoteKind === 'note'
     126    ? ActivityPubService.getReaction(site.slug, remote.object_uri || remoteUri)
     127    : { liked: false, boosted: false };
     128  return { remote, remoteKind, reacted };
    101129}
    102130
    … …  
    214242  const remoteUri = lookupUri(rawQ);
    215243  const mayLookup = remoteUri ? mayLookUp(req, res) : false;
    216   let remote = null;
    217   let remoteKind = null;
    218   if (remoteUri && mayLookup) {
    219     // Ondertekend als deze site: een post die alleen voor volgers zichtbaar is
    220     // weigert een anonieme GET, en dan lijkt een bestaande post te ontbreken.
    221     try { remote = await ActivityPubService.resolveRemoteNote(remoteUri, { asSlug: site.slug }); } catch { /* onbereikbaar */ }
    222     if (remote) remoteKind = 'note';
    223     if (!remote) {
    224       try { remote = await ActivityPubService.resolveRemoteActor(remoteUri); } catch { /* onbereikbaar */ }
    225       if (remote) remoteKind = 'actor';
    226     }
    227   }
     244  const { remote, remoteKind, reacted: remoteReacted } = (remoteUri && mayLookup)
     245    ? await lookUpRemote(remoteUri, site)
     246    : { remote: null, remoteKind: null, reacted: { liked: false, boosted: false } };
    228247
    229248  const r = searchSite(req, res, rawQ, { posts: 50, tracks: 25, events: 25, pages: 8 });
    … …  
    234253    total, queryError: r.queryError,
    235254    remoteUri, remote, remoteKind, mayLookup,
    236     remoteReacted: (remoteKind === 'note') ? ActivityPubService.getReaction(site.slug, remote.object_uri || remoteUri) : { liked: false, boosted: false },
     255    remoteReacted,
    237256    siteTitle: site.title || '',
    238257  });
     258});
     259
     260// ── De live-preview van een adres (HTML-fragment) ─────────────────────────────
     261//
     262// Plak je een link in de zoekbalk, dan haalt de uitklaplijst de post zelf op
     263// in plaats van te zoeken naar de tekst van de link (shaer-utpi).
     264//
     265// Dezelfde grens als de volle pagina, en hier weegt hij zwaarder: deze route
     266// wordt bij elke toetsaanslag geraakt, dus zonder die grens is hij een
     267// haalservice die iedereen deze server op adressen naar keuze laat afsturen.
     268// Mag je niet ophalen, of is het geen adres, dan 204: de zoekbalk valt dan
     269// terug op de gewone suggesties. Geen 403, want voor een bezoeker is een
     270// geplakte link gewoon een zoekterm en geen geweigerde handeling.
     271const VIEWS_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'views');
     272
     273router.get('/remote', async (req, res) => {
     274  const site = res.locals.site;
     275  const remoteUri = lookupUri(String(req.query.q || '').slice(0, 2048));
     276  if (!site || !remoteUri || !mayLookUp(req, res)) return res.status(204).end();
     277  const { remote, remoteKind, reacted } = await lookUpRemote(remoteUri, site);
     278  // Dezelfde taal als de pagina eromheen, anders staat er een Engelse kaart
     279  // in een Nederlandse zoekbalk.
     280  const lang = resolveLang(req, { userLang: req.session?.user?.lang, defaultLang: getSetting('default_lang') });
     281  const html = await ejs.renderFile(path.join(VIEWS_DIR, 'partials', 'remote-preview.ejs'), {
     282    t: (k, vars) => i18nT(lang, k, vars),
     283    remote, remoteKind, remoteUri, reacted,
     284    siteTitle: site.title || '',
     285  }, { async: false });
     286  // Wat deze bezoeker ziet hangt van zijn rechten en zijn reacties af: niet
     287  // bewaren, niet delen.
     288  res.set('Cache-Control', 'private, no-store');
     289  res.type('html').send(html);
    239290});
    240291
  • src/services/i18n.js

    r3ceaef6 r9eb6e1b  
    166166    'fedi.heading': 'Vanuit de fediverse', 'fedi.likes': 'sterren', 'fedi.boosts': 'boosts', 'fedi.replies': 'Reacties uit de fediverse',
    167167    'fedi.reply': 'Reageer', 'fedi.reply_ph': 'Je antwoord aan de fediverse…', 'fedi.send': 'Versturen', 'fedi.you': 'Jij',
    168     'fedi.remote_title': 'Reageer via de fediverse', 'fedi.follow_heading': 'Volgen via de fediverse', 'fedi.profile_follow': 'Volg via de fediverse', 'profile.since': 'Op Klonkt sinds', 'profile.free': 'Gratis', 'fedi.follow_intro': 'Je staat op het punt te volgen:', 'fedi.follow_btn': 'Volgen', 'fedi.cancel': 'Annuleren', 'fedi.followed_title': 'Volgverzoek verstuurd ✅', 'fedi.followed_done': 'Je volgverzoek is onderweg. Zodra de andere kant het accepteert, verschijnen hun berichten in je tijdlijn.', 'fedi.view_profile': 'Bekijk profiel →', 'fedi.remote_reply': 'Reageer via de fediverse', 'fedi.remote_prompt': 'Je fediverse-adres:', 'fedi.remote_notfound': 'Kon die post niet ophalen. Plak de volledige post-URL:', 'fedi.remote_load': 'Ophalen', 'fedi.remote_replying_to': 'Je reageert op', 'fedi.remote_as': 'Wordt verzonden als {site}.', 'fedi.remote_view_original': 'Bekijk de hele post + reacties op de bron →', 'fedi.remote_reply_short': 'via de fediverse', 'fedi.like_short': 'Like', 'fedi.unlike_short': 'Like intrekken', 'fedi.boost_short': 'Boost', 'fedi.remote_ph': 'jouw server', 'fedi.remote_sent_title': 'Verzonden ✅', 'fedi.remote_sent': 'Je reactie is verstuurd. Hij verschijnt zo bij de originele post op de fediverse, niet op deze pagina. Bekijk hem daar:', 'fedi.reply_where': 'Je reactie verschijnt bij de originele post op de fediverse, niet op deze pagina. Via de link hierboven zie je hem daar.', 'fedi.remote_back': '← Terug naar je site', 'fedi.like_btn': 'Like deze post', 'fedi.or_reply': 'of reageer:', 'fedi.liked_title': 'Geliket', 'fedi.liked_done': 'Je like is onderweg naar de fediverse.', 'fedi.boost_btn': 'Boost deze post', 'fedi.boosted_title': 'Geboost', 'fedi.boosted_done': 'Je boost is onderweg naar de fediverse.', 'fedi.remote_interact': 'Interacteer via de fediverse', 'fedi.report_open': 'Deze post rapporteren', 'fedi.report_where': 'De melding gaat naar de instance en hun moderator(s).', 'fedi.report_ph': 'Wat is er mis? (optioneel)', 'fedi.report_send': 'Rapporteren', 'fedi.reported_title': 'Gerapporteerd', 'fedi.reported_done': 'Je melding is naar de server van de gebruiker gestuurd. Hun moderators bekijken het.', 'fedi.delete_confirm': 'Deze reactie verwijderen?', 'fedi.mod_remove_confirm': 'Deze reactie uit je thread verwijderen? Hij komt niet terug, ook niet via thread-aanvulling.', 'fedi.mod_report_confirm': 'Deze reactie rapporteren bij de server van de auteur?', 'fedi.manage_title': 'Mijn fediverse-reacties', 'fedi.manage_empty': 'Je hebt nog geen reacties verstuurd.', 'fedi.goto_post': 'Naar de post', 'fedi.edit': 'Bewerken', 'fedi.save_edit': 'Opslaan', 'fedi.bm_label': 'Interacteer via mijn site', 'fedi.bm_help': 'Sleep deze knop naar je bladwijzerbalk. Klik ’m daarna op elke fediverse-post (Mastodon, een andere Klonkt…) om er via jouw site op te reageren, te liken of te boosten.', 'tl.title': 'Krant', 'tl.lead': 'Volg accounts in de fediverse en zie hun berichten hier.', 'tl.follow_btn': 'Volgen', 'tl.following': 'Je volgt', 'tl.unfollow': 'Ontvolgen', 'tl.autoboost': 'Uitgelicht', 'tl.autoboost_follow': 'uitlichten in cirkel', 'tl.autoboost_hint': 'Hun nieuwe posts verschijnen doorlopend in jouw Cirkel (lokaal, geen fediverse-boost).', 'tl.moved_title': 'Dit account is verhuisd', 'tl.moved_lead': 'Nieuwe berichten, volgen, liken en reageren doe je voortaan vanaf', 'tl.moved_hint': 'Lezen blijft hier gewoon werken, en reacties op je oude posts komen nog binnen. Wil je terug? Maak het verhuisadres leeg bij Uiterlijk.', 'tl.move_title': 'Je volglijst meenemen', 'tl.move_hint': 'Verhuis je naar een ander adres? Je volgers krijgen dat vanzelf te horen, maar wie JIJ volgt niet. Neem die lijst hiermee mee. Werkt ook van en naar Mastodon.', 'tl.move_export': 'Lijst downloaden (CSV)', 'tl.move_import_file': 'Kies je gedownloade CSV-bestand:', 'tl.move_import_lbl': 'Of plak de lijst hier:', 'tl.move_import': 'Iedereen volgen', 'tl.pending': 'in afwachting', 'tl.unboost': 'Boost intrekken', 'tl.feed': 'Berichten', 'tl.tab_feed': 'Krant', 'tl.tab_following': 'Volgend', 'tl.tab_replies': 'Reacties', 'tl.tab_followers': 'Volgers', 'tl.followers': 'Volgers', 'tl.followers_lead': 'Wie jou volgt in de fediverse, met de laatste geslaagde bezorging. Rood = nog nooit bezorgd of laatste poging mislukt — kandidaat om op te ruimen na een check.', 'tl.empty_followers': 'Nog geen volgers.', 'tl.last_delivery': 'Laatste bezorging', 'tl.never_delivered': 'Nog nooit bezorgd', 'tl.delivery_failed': 'laatste poging mislukt', 'tl.remove_follower': 'Verwijderen', 'tl.folreq_title': 'Volgverzoeken', 'tl.folreq_sub': 'Deze wachten op jouw ja of nee. Tot die tijd ziet de aanvrager niets van je posts.', 'tl.folreq_accept': 'Accepteer', 'tl.folreq_deny': 'Weiger', 'tl.approve_toggle': 'Volgers eerst goedkeuren', 'tl.approve_toggle_hint': 'Aan: volgverzoeken wachten hier op jouw ja. Uit: iedereen mag direct volgen.', 'tl.remove_confirm': 'Deze volger verwijderen? Een actief account moet je dan opnieuw volgen.', 'tl.tab_connect': 'Connect', 'tl.connect': 'Connect', 'tl.dir_following': 'jij volgt', 'tl.dir_follower': 'volgt jou', 'tl.dir_mutual': 'wederzijds', 'tl.connect_empty': 'Nog geen connecties. Volg iemand hierboven om te beginnen.', 'tl.unreachable': 'Niet bereikbaar', 'tl.unreachable_lead': 'Deze volgers konden we niet bereiken (nooit bezorgd of laatste poging mislukt). Ruim ze op na een handmatige check.', 'msg.tab': 'Berichten', 'msg.title': 'Berichten', 'msg.filter_all': 'Alles', 'msg.filter_msgs': 'Berichten', 'msg.filter_conv': 'Gesprekken', 'msg.filter_act': 'Activiteit', 'msg.filter_mod': 'Moderatie', 'msg.filter_sent': 'Verzonden', 'msg.search_ph': 'Zoeken in berichten…', 'msg.no_match': 'Niets gevonden.', 'msg.poll_done': 'Je peiling is afgelopen', 'msg.poll_total': '{n} stemmers', 'msg.you': 'Jij', 'msg.sent_reply': 'reageerde via de fediverse', 'msg.and_more': 'en {n} anderen', 'msg.liked_many': 'liketen je post', 'msg.boosted_many': 'boostten je post', 'msg.private': 'privé', 'msg.private_hint': 'Alleen aan jou gericht; staat niet op de publieke postpagina.', 'msg.new': 'Nieuw sinds je laatste bezoek', 'msg.empty': 'Nog geen berichten. Reacties, vermeldingen en activiteit verschijnen hier.', 'oauth.title': 'App toegang geven', 'oauth.wants_access': 'wil verbinding maken met je Klonkt-account.', 'oauth.post_as': 'Plaatsen als', 'oauth.scope_read': 'Je berichten, reacties en meldingen lezen', 'oauth.scope_write': 'Namens jou posten, reageren, liken en volgen', 'oauth.allow': 'Toestaan', 'oauth.deny': 'Weigeren', 'oauth.foot': 'Je kunt de toegang later intrekken. Geef alleen apps toegang die je vertrouwt.', 're.title': 'Reactie schrijven', 're.bold': 'Vet', 're.italic': 'Cursief', 're.link': 'Link invoegen', 're.list': 'Opsomming', 're.quote': 'Citaat', 're.lang': 'Taal van je reactie', 're.attach': 'Media toevoegen (afbeelding, audio, video)', 're.attach_err': 'Upload mislukt', 're.to': 'Aan:', 're.mention_del': 'Deze persoon niet meer adresseren', 'tl.empty_following': 'Je volgt nog niemand.', 'tl.empty': 'Nog niks — volg iemand om hun berichten hier te zien.', 'tl.view_original': 'Bekijk origineel →', 'tl.open_player': 'Open de speler', 'feed.load_more': 'Meer laden', 'tl.paste_ph': 'Plak een fediverse-post-URL', 'tl.paste_go': 'Openen', 'tl.boosted': 'boostte dit', 'tl.read_more': 'Meer lezen', 'tl.show_less': 'Minder', 'poll.vote': 'Stem', 'poll.votes': 'stemmen', 'poll.closed': 'gesloten', 'poll.open': 'open', 'poll.aria': 'Peiling', 'poll.voter_one': 'stemmer', 'poll.voter_many': 'stemmers', 'poll.closes': 'sluit op', 'poll.multiple': 'meerkeuze', 'poll.fedi_only': 'Stemmen kan vanuit de fediverse — volg deze site en stem in je eigen app.', 'poll.voted_title': 'Stem verstuurd', 'poll.voted_done': 'Je stem is verstuurd naar de poll. De uitslag werkt bij zodra de maker die doorstuurt.',
     168    'fedi.remote_title': 'Reageer via de fediverse', 'fedi.follow_heading': 'Volgen via de fediverse', 'fedi.profile_follow': 'Volg via de fediverse', 'profile.since': 'Op Klonkt sinds', 'profile.free': 'Gratis', 'fedi.follow_intro': 'Je staat op het punt te volgen:', 'fedi.follow_btn': 'Volgen', 'fedi.cancel': 'Annuleren', 'fedi.followed_title': 'Volgverzoek verstuurd ✅', 'fedi.followed_done': 'Je volgverzoek is onderweg. Zodra de andere kant het accepteert, verschijnen hun berichten in je tijdlijn.', 'fedi.view_profile': 'Bekijk profiel →', 'fedi.remote_reply': 'Reageer via de fediverse', 'fedi.remote_prompt': 'Je fediverse-adres:', 'fedi.remote_notfound': 'Kon die post niet ophalen. Plak de volledige post-URL:', 'fedi.remote_load': 'Ophalen', 'fedi.remote_replying_to': 'Je reageert op', 'fedi.cw_open': 'tonen', 'fedi.sensitive': 'Gevoelige inhoud', 'fedi.open_full': 'Openen', 'fedi.src_short': 'Origineel', 'fedi.reply_short': 'Reageer', 'fedi.profile_short': 'Profiel', 'search.fedi_looking': 'Opzoeken op het fediverse…', 'fedi.remote_as': 'Wordt verzonden als {site}.', 'fedi.remote_view_original': 'Bekijk de hele post + reacties op de bron →', 'fedi.remote_reply_short': 'via de fediverse', 'fedi.like_short': 'Like', 'fedi.unlike_short': 'Like intrekken', 'fedi.boost_short': 'Boost', 'fedi.remote_ph': 'jouw server', 'fedi.remote_sent_title': 'Verzonden ✅', 'fedi.remote_sent': 'Je reactie is verstuurd. Hij verschijnt zo bij de originele post op de fediverse, niet op deze pagina. Bekijk hem daar:', 'fedi.reply_where': 'Je reactie verschijnt bij de originele post op de fediverse, niet op deze pagina. Via de link hierboven zie je hem daar.', 'fedi.remote_back': '← Terug naar je site', 'fedi.like_btn': 'Like deze post', 'fedi.or_reply': 'of reageer:', 'fedi.liked_title': 'Geliket', 'fedi.liked_done': 'Je like is onderweg naar de fediverse.', 'fedi.boost_btn': 'Boost deze post', 'fedi.boosted_title': 'Geboost', 'fedi.boosted_done': 'Je boost is onderweg naar de fediverse.', 'fedi.remote_interact': 'Interacteer via de fediverse', 'fedi.report_open': 'Deze post rapporteren', 'fedi.report_where': 'De melding gaat naar de instance en hun moderator(s).', 'fedi.report_ph': 'Wat is er mis? (optioneel)', 'fedi.report_send': 'Rapporteren', 'fedi.reported_title': 'Gerapporteerd', 'fedi.reported_done': 'Je melding is naar de server van de gebruiker gestuurd. Hun moderators bekijken het.', 'fedi.delete_confirm': 'Deze reactie verwijderen?', 'fedi.mod_remove_confirm': 'Deze reactie uit je thread verwijderen? Hij komt niet terug, ook niet via thread-aanvulling.', 'fedi.mod_report_confirm': 'Deze reactie rapporteren bij de server van de auteur?', 'fedi.manage_title': 'Mijn fediverse-reacties', 'fedi.manage_empty': 'Je hebt nog geen reacties verstuurd.', 'fedi.goto_post': 'Naar de post', 'fedi.edit': 'Bewerken', 'fedi.save_edit': 'Opslaan', 'fedi.bm_label': 'Interacteer via mijn site', 'fedi.bm_help': 'Sleep deze knop naar je bladwijzerbalk. Klik ’m daarna op elke fediverse-post (Mastodon, een andere Klonkt…) om er via jouw site op te reageren, te liken of te boosten.', 'tl.title': 'Krant', 'tl.lead': 'Volg accounts in de fediverse en zie hun berichten hier.', 'tl.follow_btn': 'Volgen', 'tl.following': 'Je volgt', 'tl.unfollow': 'Ontvolgen', 'tl.autoboost': 'Uitgelicht', 'tl.autoboost_follow': 'uitlichten in cirkel', 'tl.autoboost_hint': 'Hun nieuwe posts verschijnen doorlopend in jouw Cirkel (lokaal, geen fediverse-boost).', 'tl.moved_title': 'Dit account is verhuisd', 'tl.moved_lead': 'Nieuwe berichten, volgen, liken en reageren doe je voortaan vanaf', 'tl.moved_hint': 'Lezen blijft hier gewoon werken, en reacties op je oude posts komen nog binnen. Wil je terug? Maak het verhuisadres leeg bij Uiterlijk.', 'tl.move_title': 'Je volglijst meenemen', 'tl.move_hint': 'Verhuis je naar een ander adres? Je volgers krijgen dat vanzelf te horen, maar wie JIJ volgt niet. Neem die lijst hiermee mee. Werkt ook van en naar Mastodon.', 'tl.move_export': 'Lijst downloaden (CSV)', 'tl.move_import_file': 'Kies je gedownloade CSV-bestand:', 'tl.move_import_lbl': 'Of plak de lijst hier:', 'tl.move_import': 'Iedereen volgen', 'tl.pending': 'in afwachting', 'tl.unboost': 'Boost intrekken', 'tl.feed': 'Berichten', 'tl.tab_feed': 'Krant', 'tl.tab_following': 'Volgend', 'tl.tab_replies': 'Reacties', 'tl.tab_followers': 'Volgers', 'tl.followers': 'Volgers', 'tl.followers_lead': 'Wie jou volgt in de fediverse, met de laatste geslaagde bezorging. Rood = nog nooit bezorgd of laatste poging mislukt — kandidaat om op te ruimen na een check.', 'tl.empty_followers': 'Nog geen volgers.', 'tl.last_delivery': 'Laatste bezorging', 'tl.never_delivered': 'Nog nooit bezorgd', 'tl.delivery_failed': 'laatste poging mislukt', 'tl.remove_follower': 'Verwijderen', 'tl.folreq_title': 'Volgverzoeken', 'tl.folreq_sub': 'Deze wachten op jouw ja of nee. Tot die tijd ziet de aanvrager niets van je posts.', 'tl.folreq_accept': 'Accepteer', 'tl.folreq_deny': 'Weiger', 'tl.approve_toggle': 'Volgers eerst goedkeuren', 'tl.approve_toggle_hint': 'Aan: volgverzoeken wachten hier op jouw ja. Uit: iedereen mag direct volgen.', 'tl.remove_confirm': 'Deze volger verwijderen? Een actief account moet je dan opnieuw volgen.', 'tl.tab_connect': 'Connect', 'tl.connect': 'Connect', 'tl.dir_following': 'jij volgt', 'tl.dir_follower': 'volgt jou', 'tl.dir_mutual': 'wederzijds', 'tl.connect_empty': 'Nog geen connecties. Volg iemand hierboven om te beginnen.', 'tl.unreachable': 'Niet bereikbaar', 'tl.unreachable_lead': 'Deze volgers konden we niet bereiken (nooit bezorgd of laatste poging mislukt). Ruim ze op na een handmatige check.', 'msg.tab': 'Berichten', 'msg.title': 'Berichten', 'msg.filter_all': 'Alles', 'msg.filter_msgs': 'Berichten', 'msg.filter_conv': 'Gesprekken', 'msg.filter_act': 'Activiteit', 'msg.filter_mod': 'Moderatie', 'msg.filter_sent': 'Verzonden', 'msg.search_ph': 'Zoeken in berichten…', 'msg.no_match': 'Niets gevonden.', 'msg.poll_done': 'Je peiling is afgelopen', 'msg.poll_total': '{n} stemmers', 'msg.you': 'Jij', 'msg.sent_reply': 'reageerde via de fediverse', 'msg.and_more': 'en {n} anderen', 'msg.liked_many': 'liketen je post', 'msg.boosted_many': 'boostten je post', 'msg.private': 'privé', 'msg.private_hint': 'Alleen aan jou gericht; staat niet op de publieke postpagina.', 'msg.new': 'Nieuw sinds je laatste bezoek', 'msg.empty': 'Nog geen berichten. Reacties, vermeldingen en activiteit verschijnen hier.', 'oauth.title': 'App toegang geven', 'oauth.wants_access': 'wil verbinding maken met je Klonkt-account.', 'oauth.post_as': 'Plaatsen als', 'oauth.scope_read': 'Je berichten, reacties en meldingen lezen', 'oauth.scope_write': 'Namens jou posten, reageren, liken en volgen', 'oauth.allow': 'Toestaan', 'oauth.deny': 'Weigeren', 'oauth.foot': 'Je kunt de toegang later intrekken. Geef alleen apps toegang die je vertrouwt.', 're.title': 'Reactie schrijven', 're.bold': 'Vet', 're.italic': 'Cursief', 're.link': 'Link invoegen', 're.list': 'Opsomming', 're.quote': 'Citaat', 're.lang': 'Taal van je reactie', 're.attach': 'Media toevoegen (afbeelding, audio, video)', 're.attach_err': 'Upload mislukt', 're.to': 'Aan:', 're.mention_del': 'Deze persoon niet meer adresseren', 'tl.empty_following': 'Je volgt nog niemand.', 'tl.empty': 'Nog niks — volg iemand om hun berichten hier te zien.', 'tl.view_original': 'Bekijk origineel →', 'tl.open_player': 'Open de speler', 'feed.load_more': 'Meer laden', 'tl.paste_ph': 'Plak een fediverse-post-URL', 'tl.paste_go': 'Openen', 'tl.boosted': 'boostte dit', 'tl.read_more': 'Meer lezen', 'tl.show_less': 'Minder', 'poll.vote': 'Stem', 'poll.votes': 'stemmen', 'poll.closed': 'gesloten', 'poll.open': 'open', 'poll.aria': 'Peiling', 'poll.voter_one': 'stemmer', 'poll.voter_many': 'stemmers', 'poll.closes': 'sluit op', 'poll.multiple': 'meerkeuze', 'poll.fedi_only': 'Stemmen kan vanuit de fediverse — volg deze site en stem in je eigen app.', 'poll.voted_title': 'Stem verstuurd', 'poll.voted_done': 'Je stem is verstuurd naar de poll. De uitslag werkt bij zodra de maker die doorstuurt.',
    169169    'comments.to_start': 'om de conversatie te starten.',
    170170    'comments.reply': 'Reageer', 'comments.delete': 'Verwijder', 'comments.cancel': 'Annuleren',
    … …  
    11771177    'fedi.heading': 'From the fediverse', 'fedi.likes': 'favourites', 'fedi.boosts': 'boosts', 'fedi.replies': 'Replies from the fediverse',
    11781178    'fedi.reply': 'Reply', 'fedi.reply_ph': 'Your reply to the fediverse…', 'fedi.send': 'Send', 'fedi.you': 'You',
    1179     'fedi.remote_title': 'Reply via the fediverse', 'fedi.follow_heading': 'Follow via the fediverse', 'fedi.profile_follow': 'Follow via the fediverse', 'profile.since': 'On Klonkt since', 'profile.free': 'Free', 'fedi.follow_intro': 'You are about to follow:', 'fedi.follow_btn': 'Follow', 'fedi.cancel': 'Cancel', 'fedi.followed_title': 'Follow request sent ✅', 'fedi.followed_done': 'Your follow request is on its way. Once accepted, their posts show up in your timeline.', 'fedi.view_profile': 'View profile →', 'fedi.remote_reply': 'Reply via the fediverse', 'fedi.remote_prompt': 'Your fediverse address:', 'fedi.remote_notfound': 'Could not fetch that post. Paste the full post URL:', 'fedi.remote_load': 'Fetch', 'fedi.remote_replying_to': 'Replying to', 'fedi.remote_as': 'Sent as {site}.', 'fedi.remote_view_original': 'View the full post + comments on the source →', 'fedi.remote_reply_short': 'via the fediverse', 'fedi.like_short': 'Like', 'fedi.unlike_short': 'Unlike', 'fedi.boost_short': 'Boost', 'fedi.remote_ph': 'your server', 'fedi.remote_sent_title': 'Sent ✅', 'fedi.remote_sent': 'Your reply has been sent. It will show up on the original post on the fediverse, not on this page. See it there:', 'fedi.reply_where': 'Your reply appears on the original post on the fediverse, not on this page. Use the link above to see it there.', 'fedi.remote_back': '← Back to your site', 'fedi.like_btn': 'Like this post', 'fedi.or_reply': 'or reply:', 'fedi.liked_title': 'Liked', 'fedi.liked_done': 'Your like is on its way to the fediverse.', 'fedi.boost_btn': 'Boost this post', 'fedi.boosted_title': 'Boosted', 'fedi.boosted_done': 'Your boost is on its way to the fediverse.', 'fedi.remote_interact': 'Interact via the fediverse', 'fedi.report_open': 'Report this post', 'fedi.report_where': 'The report goes to the instance and their moderator(s).', 'fedi.report_ph': 'What is wrong? (optional)', 'fedi.report_send': 'Report', 'fedi.reported_title': 'Reported', 'fedi.reported_done': 'Your report has been sent to their server. Their moderators will review it.', 'fedi.delete_confirm': 'Delete this reply?', 'fedi.mod_remove_confirm': 'Remove this reply from your thread? It will not come back, not even via thread-filling.', 'fedi.mod_report_confirm': 'Report this reply to its author’s server?', 'fedi.manage_title': 'My fediverse replies', 'fedi.manage_empty': 'You have not sent any replies yet.', 'fedi.goto_post': 'Go to post', 'fedi.edit': 'Edit', 'fedi.save_edit': 'Save', 'fedi.bm_label': 'Interact via my site', 'fedi.bm_help': 'Drag this button to your bookmarks bar. Then click it on any fediverse post (Mastodon, another Klonkt…) to reply, like or boost it via your own site.', 'tl.title': 'News', 'tl.lead': 'Follow accounts on the fediverse and see their posts here.', 'tl.follow_btn': 'Follow', 'tl.following': 'Following', 'tl.unfollow': 'Unfollow', 'tl.autoboost': 'Featured', 'tl.autoboost_follow': 'feature in circle', 'tl.autoboost_hint': 'Their new posts keep showing in your Circle (local, no fediverse boost).', 'tl.moved_title': 'This account has moved', 'tl.moved_lead': 'New posts, follows, likes and replies now happen at', 'tl.moved_hint': 'Reading keeps working here, and replies to your old posts still arrive. Want to come back? Clear the move target under Appearance.', 'tl.move_title': 'Take your following list with you', 'tl.move_hint': 'Moving to another address? Your followers are told automatically, but who YOU follow is not. Take that list with you here. Works to and from Mastodon too.', 'tl.move_export': 'Download list (CSV)', 'tl.move_import_file': 'Choose your downloaded CSV file:', 'tl.move_import_lbl': 'Or paste the list here:', 'tl.move_import': 'Follow everyone', 'tl.pending': 'pending', 'tl.unboost': 'Unboost', 'tl.feed': 'Posts', 'tl.tab_feed': 'News', 'tl.tab_following': 'Following', 'tl.tab_replies': 'Replies', 'tl.tab_followers': 'Followers', 'tl.followers': 'Followers', 'tl.followers_lead': 'Who follows you on the fediverse, with the last successful delivery. Red = never delivered or last attempt failed — a candidate to clean up after a check.', 'tl.empty_followers': 'No followers yet.', 'tl.last_delivery': 'Last delivery', 'tl.never_delivered': 'Never delivered', 'tl.delivery_failed': 'last attempt failed', 'tl.remove_follower': 'Remove', 'tl.folreq_title': 'Follow requests', 'tl.folreq_sub': 'These wait for your yes or no. Until then the requester sees none of your posts.', 'tl.folreq_accept': 'Accept', 'tl.folreq_deny': 'Deny', 'tl.approve_toggle': 'Approve followers first', 'tl.approve_toggle_hint': 'On: follow requests wait here for your yes. Off: anyone may follow right away.', 'tl.remove_confirm': 'Remove this follower? An active account would have to follow you again.', 'tl.tab_connect': 'Connect', 'tl.connect': 'Connect', 'tl.dir_following': 'you follow', 'tl.dir_follower': 'follows you', 'tl.dir_mutual': 'mutual', 'tl.connect_empty': 'No connections yet. Follow someone above to get started.', 'tl.unreachable': 'Unreachable', 'tl.unreachable_lead': 'We could not reach these followers (never delivered or last attempt failed). Clean them up after a manual check.', 'msg.tab': 'Messages', 'msg.title': 'Messages', 'msg.filter_all': 'All', 'msg.filter_msgs': 'Messages', 'msg.filter_conv': 'Conversations', 'msg.filter_act': 'Activity', 'msg.filter_mod': 'Moderation', 'msg.filter_sent': 'Sent', 'msg.search_ph': 'Search messages…', 'msg.no_match': 'Nothing found.', 'msg.poll_done': 'Your poll has ended', 'msg.poll_total': '{n} voters', 'msg.you': 'You', 'msg.sent_reply': 'replied via the fediverse', 'msg.and_more': 'and {n} others', 'msg.liked_many': 'liked your post', 'msg.boosted_many': 'boosted your post', 'msg.private': 'private', 'msg.private_hint': 'Addressed to you only; not shown on the public post page.', 'msg.new': 'New since your last visit', 'msg.empty': 'No messages yet. Replies, mentions and activity show up here.', 'oauth.title': 'Authorize app', 'oauth.wants_access': 'wants to connect to your Klonkt account.', 'oauth.post_as': 'Post as', 'oauth.scope_read': 'Read your posts, replies and notifications', 'oauth.scope_write': 'Post, reply, like and follow on your behalf', 'oauth.allow': 'Allow', 'oauth.deny': 'Deny', 'oauth.foot': 'You can revoke access later. Only authorize apps you trust.', 're.title': 'Write a reply', 're.bold': 'Bold', 're.italic': 'Italic', 're.link': 'Insert link', 're.list': 'Bullet list', 're.quote': 'Quote', 're.lang': 'Language of your reply', 're.attach': 'Add media (image, audio, video)', 're.attach_err': 'Upload failed', 're.to': 'To:', 're.mention_del': 'Stop addressing this person', 'tl.empty_following': 'You do not follow anyone yet.', 'tl.empty': 'Nothing yet — follow someone to see their posts here.', 'tl.view_original': 'View original →', 'tl.open_player': 'Open the player', 'feed.load_more': 'Load more', 'tl.paste_ph': 'Paste a fediverse post URL', 'tl.paste_go': 'Open', 'tl.boosted': 'boosted', 'tl.read_more': 'Read more', 'tl.show_less': 'Show less', 'poll.vote': 'Vote', 'poll.votes': 'votes', 'poll.closed': 'closed', 'poll.open': 'open', 'poll.aria': 'Poll', 'poll.voter_one': 'voter', 'poll.voter_many': 'voters', 'poll.closes': 'closes', 'poll.multiple': 'multiple choice', 'poll.fedi_only': 'Voting happens on the fediverse — follow this site and vote from your own app.', 'poll.voted_title': 'Vote sent', 'poll.voted_done': 'Your vote is on its way to the poll. The results refresh once the author sends the update.',
     1179    'fedi.remote_title': 'Reply via the fediverse', 'fedi.follow_heading': 'Follow via the fediverse', 'fedi.profile_follow': 'Follow via the fediverse', 'profile.since': 'On Klonkt since', 'profile.free': 'Free', 'fedi.follow_intro': 'You are about to follow:', 'fedi.follow_btn': 'Follow', 'fedi.cancel': 'Cancel', 'fedi.followed_title': 'Follow request sent ✅', 'fedi.followed_done': 'Your follow request is on its way. Once accepted, their posts show up in your timeline.', 'fedi.view_profile': 'View profile →', 'fedi.remote_reply': 'Reply via the fediverse', 'fedi.remote_prompt': 'Your fediverse address:', 'fedi.remote_notfound': 'Could not fetch that post. Paste the full post URL:', 'fedi.remote_load': 'Fetch', 'fedi.remote_replying_to': 'Replying to', 'fedi.cw_open': 'show', 'fedi.sensitive': 'Sensitive content', 'fedi.open_full': 'Open', 'fedi.src_short': 'Original', 'fedi.reply_short': 'Reply', 'fedi.profile_short': 'Profile', 'search.fedi_looking': 'Looking it up on the fediverse…', 'fedi.remote_as': 'Sent as {site}.', 'fedi.remote_view_original': 'View the full post + comments on the source →', 'fedi.remote_reply_short': 'via the fediverse', 'fedi.like_short': 'Like', 'fedi.unlike_short': 'Unlike', 'fedi.boost_short': 'Boost', 'fedi.remote_ph': 'your server', 'fedi.remote_sent_title': 'Sent ✅', 'fedi.remote_sent': 'Your reply has been sent. It will show up on the original post on the fediverse, not on this page. See it there:', 'fedi.reply_where': 'Your reply appears on the original post on the fediverse, not on this page. Use the link above to see it there.', 'fedi.remote_back': '← Back to your site', 'fedi.like_btn': 'Like this post', 'fedi.or_reply': 'or reply:', 'fedi.liked_title': 'Liked', 'fedi.liked_done': 'Your like is on its way to the fediverse.', 'fedi.boost_btn': 'Boost this post', 'fedi.boosted_title': 'Boosted', 'fedi.boosted_done': 'Your boost is on its way to the fediverse.', 'fedi.remote_interact': 'Interact via the fediverse', 'fedi.report_open': 'Report this post', 'fedi.report_where': 'The report goes to the instance and their moderator(s).', 'fedi.report_ph': 'What is wrong? (optional)', 'fedi.report_send': 'Report', 'fedi.reported_title': 'Reported', 'fedi.reported_done': 'Your report has been sent to their server. Their moderators will review it.', 'fedi.delete_confirm': 'Delete this reply?', 'fedi.mod_remove_confirm': 'Remove this reply from your thread? It will not come back, not even via thread-filling.', 'fedi.mod_report_confirm': 'Report this reply to its author’s server?', 'fedi.manage_title': 'My fediverse replies', 'fedi.manage_empty': 'You have not sent any replies yet.', 'fedi.goto_post': 'Go to post', 'fedi.edit': 'Edit', 'fedi.save_edit': 'Save', 'fedi.bm_label': 'Interact via my site', 'fedi.bm_help': 'Drag this button to your bookmarks bar. Then click it on any fediverse post (Mastodon, another Klonkt…) to reply, like or boost it via your own site.', 'tl.title': 'News', 'tl.lead': 'Follow accounts on the fediverse and see their posts here.', 'tl.follow_btn': 'Follow', 'tl.following': 'Following', 'tl.unfollow': 'Unfollow', 'tl.autoboost': 'Featured', 'tl.autoboost_follow': 'feature in circle', 'tl.autoboost_hint': 'Their new posts keep showing in your Circle (local, no fediverse boost).', 'tl.moved_title': 'This account has moved', 'tl.moved_lead': 'New posts, follows, likes and replies now happen at', 'tl.moved_hint': 'Reading keeps working here, and replies to your old posts still arrive. Want to come back? Clear the move target under Appearance.', 'tl.move_title': 'Take your following list with you', 'tl.move_hint': 'Moving to another address? Your followers are told automatically, but who YOU follow is not. Take that list with you here. Works to and from Mastodon too.', 'tl.move_export': 'Download list (CSV)', 'tl.move_import_file': 'Choose your downloaded CSV file:', 'tl.move_import_lbl': 'Or paste the list here:', 'tl.move_import': 'Follow everyone', 'tl.pending': 'pending', 'tl.unboost': 'Unboost', 'tl.feed': 'Posts', 'tl.tab_feed': 'News', 'tl.tab_following': 'Following', 'tl.tab_replies': 'Replies', 'tl.tab_followers': 'Followers', 'tl.followers': 'Followers', 'tl.followers_lead': 'Who follows you on the fediverse, with the last successful delivery. Red = never delivered or last attempt failed — a candidate to clean up after a check.', 'tl.empty_followers': 'No followers yet.', 'tl.last_delivery': 'Last delivery', 'tl.never_delivered': 'Never delivered', 'tl.delivery_failed': 'last attempt failed', 'tl.remove_follower': 'Remove', 'tl.folreq_title': 'Follow requests', 'tl.folreq_sub': 'These wait for your yes or no. Until then the requester sees none of your posts.', 'tl.folreq_accept': 'Accept', 'tl.folreq_deny': 'Deny', 'tl.approve_toggle': 'Approve followers first', 'tl.approve_toggle_hint': 'On: follow requests wait here for your yes. Off: anyone may follow right away.', 'tl.remove_confirm': 'Remove this follower? An active account would have to follow you again.', 'tl.tab_connect': 'Connect', 'tl.connect': 'Connect', 'tl.dir_following': 'you follow', 'tl.dir_follower': 'follows you', 'tl.dir_mutual': 'mutual', 'tl.connect_empty': 'No connections yet. Follow someone above to get started.', 'tl.unreachable': 'Unreachable', 'tl.unreachable_lead': 'We could not reach these followers (never delivered or last attempt failed). Clean them up after a manual check.', 'msg.tab': 'Messages', 'msg.title': 'Messages', 'msg.filter_all': 'All', 'msg.filter_msgs': 'Messages', 'msg.filter_conv': 'Conversations', 'msg.filter_act': 'Activity', 'msg.filter_mod': 'Moderation', 'msg.filter_sent': 'Sent', 'msg.search_ph': 'Search messages…', 'msg.no_match': 'Nothing found.', 'msg.poll_done': 'Your poll has ended', 'msg.poll_total': '{n} voters', 'msg.you': 'You', 'msg.sent_reply': 'replied via the fediverse', 'msg.and_more': 'and {n} others', 'msg.liked_many': 'liked your post', 'msg.boosted_many': 'boosted your post', 'msg.private': 'private', 'msg.private_hint': 'Addressed to you only; not shown on the public post page.', 'msg.new': 'New since your last visit', 'msg.empty': 'No messages yet. Replies, mentions and activity show up here.', 'oauth.title': 'Authorize app', 'oauth.wants_access': 'wants to connect to your Klonkt account.', 'oauth.post_as': 'Post as', 'oauth.scope_read': 'Read your posts, replies and notifications', 'oauth.scope_write': 'Post, reply, like and follow on your behalf', 'oauth.allow': 'Allow', 'oauth.deny': 'Deny', 'oauth.foot': 'You can revoke access later. Only authorize apps you trust.', 're.title': 'Write a reply', 're.bold': 'Bold', 're.italic': 'Italic', 're.link': 'Insert link', 're.list': 'Bullet list', 're.quote': 'Quote', 're.lang': 'Language of your reply', 're.attach': 'Add media (image, audio, video)', 're.attach_err': 'Upload failed', 're.to': 'To:', 're.mention_del': 'Stop addressing this person', 'tl.empty_following': 'You do not follow anyone yet.', 'tl.empty': 'Nothing yet — follow someone to see their posts here.', 'tl.view_original': 'View original →', 'tl.open_player': 'Open the player', 'feed.load_more': 'Load more', 'tl.paste_ph': 'Paste a fediverse post URL', 'tl.paste_go': 'Open', 'tl.boosted': 'boosted', 'tl.read_more': 'Read more', 'tl.show_less': 'Show less', 'poll.vote': 'Vote', 'poll.votes': 'votes', 'poll.closed': 'closed', 'poll.open': 'open', 'poll.aria': 'Poll', 'poll.voter_one': 'voter', 'poll.voter_many': 'voters', 'poll.closes': 'closes', 'poll.multiple': 'multiple choice', 'poll.fedi_only': 'Voting happens on the fediverse — follow this site and vote from your own app.', 'poll.voted_title': 'Vote sent', 'poll.voted_done': 'Your vote is on its way to the poll. The results refresh once the author sends the update.',
    11801180    'comments.to_start': 'to start the conversation.',
    11811181    'comments.reply': 'Reply', 'comments.delete': 'Delete', 'comments.cancel': 'Cancel',
    … …  
    21862186    'fedi.heading': 'Aus dem Fediverse', 'fedi.likes': 'Favoriten', 'fedi.boosts': 'Boosts', 'fedi.replies': 'Antworten aus dem Fediverse',
    21872187    'fedi.reply': 'Antworten', 'fedi.reply_ph': 'Deine Antwort an das Fediverse…', 'fedi.send': 'Senden', 'fedi.you': 'Du',
    2188     'fedi.remote_title': 'Über das Fediverse antworten', 'fedi.follow_heading': 'Über das Fediverse folgen', 'fedi.profile_follow': 'Über das Fediverse folgen', 'profile.since': 'Auf Klonkt seit', 'profile.free': 'Kostenlos', 'fedi.follow_intro': 'Du folgst gleich:', 'fedi.follow_btn': 'Folgen', 'fedi.cancel': 'Abbrechen', 'fedi.followed_title': 'Folge-Anfrage gesendet ✅', 'fedi.followed_done': 'Deine Folge-Anfrage ist unterwegs. Sobald sie akzeptiert wird, erscheinen ihre Beiträge in deiner Timeline.', 'fedi.view_profile': 'Profil ansehen →', 'fedi.remote_reply': 'Über das Fediverse antworten', 'fedi.remote_prompt': 'Deine Fediverse-Adresse:', 'fedi.remote_notfound': 'Beitrag konnte nicht geladen werden. Füge die vollständige Beitrags-URL ein:', 'fedi.remote_load': 'Laden', 'fedi.remote_replying_to': 'Antwort an', 'fedi.remote_as': 'Wird als {site} gesendet.', 'fedi.remote_view_original': 'Ganzen Beitrag + Kommentare an der Quelle ansehen →', 'fedi.remote_reply_short': 'übers Fediverse', 'fedi.like_short': 'Liken', 'fedi.unlike_short': 'Like zurücknehmen', 'fedi.boost_short': 'Boosten', 'fedi.remote_ph': 'dein Server', 'fedi.remote_sent_title': 'Gesendet ✅', 'fedi.remote_sent': 'Deine Antwort wurde gesendet. Sie erscheint gleich beim Originalbeitrag im Fediverse, nicht auf dieser Seite. Sieh sie dir dort an:', 'fedi.reply_where': 'Deine Antwort erscheint beim Originalbeitrag im Fediverse, nicht auf dieser Seite. Über den Link oben siehst du sie dort.', 'fedi.remote_back': '← Zurück zu deiner Seite', 'fedi.like_btn': 'Diesen Beitrag liken', 'fedi.or_reply': 'oder antworten:', 'fedi.liked_title': 'Geliked', 'fedi.liked_done': 'Dein Like ist unterwegs ins Fediverse.', 'fedi.boost_btn': 'Diesen Beitrag boosten', 'fedi.boosted_title': 'Geboostet', 'fedi.boosted_done': 'Dein Boost ist unterwegs ins Fediverse.', 'fedi.remote_interact': 'Übers Fediverse interagieren', 'fedi.report_open': 'Diesen Beitrag melden', 'fedi.report_where': 'Die Meldung geht an die Instanz und deren Moderator(en).', 'fedi.report_ph': 'Was ist das Problem? (optional)', 'fedi.report_send': 'Melden', 'fedi.reported_title': 'Gemeldet', 'fedi.reported_done': 'Deine Meldung wurde an den Server der Person gesendet. Deren Moderatoren prüfen sie.', 'fedi.delete_confirm': 'Diese Antwort löschen?', 'fedi.mod_remove_confirm': 'Diese Antwort aus deinem Thread entfernen? Sie kommt nicht zurück, auch nicht über Thread-Auffüllung.', 'fedi.mod_report_confirm': 'Diese Antwort beim Server des Autors melden?', 'fedi.manage_title': 'Meine Fediverse-Antworten', 'fedi.manage_empty': 'Du hast noch keine Antworten gesendet.', 'fedi.goto_post': 'Zur Post', 'fedi.edit': 'Bearbeiten', 'fedi.save_edit': 'Speichern', 'fedi.bm_label': 'Über meine Seite interagieren', 'fedi.bm_help': 'Zieh diesen Button in deine Lesezeichenleiste. Klick ihn dann auf einem beliebigen Fediverse-Beitrag (Mastodon, ein anderes Klonkt…), um über deine eigene Seite zu antworten, zu liken oder zu boosten.', 'tl.title': 'Zeitung', 'tl.lead': 'Folge Konten im Fediverse und sieh ihre Beiträge hier.', 'tl.follow_btn': 'Folgen', 'tl.following': 'Du folgst', 'tl.unfollow': 'Entfolgen', 'tl.autoboost': 'Hervorgehoben', 'tl.autoboost_follow': 'im Zirkel hervorheben', 'tl.autoboost_hint': 'Ihre neuen Beiträge erscheinen laufend in deinem Zirkel (lokal, kein Fediverse-Boost).', 'tl.moved_title': 'Dieses Konto ist umgezogen', 'tl.moved_lead': 'Neue Beiträge, Folgen, Likes und Antworten laufen jetzt über', 'tl.moved_hint': 'Lesen funktioniert hier weiterhin, und Antworten auf deine alten Beiträge kommen noch an. Zurück? Leere das Umzugsziel unter Aussehen.', 'tl.move_title': 'Deine Folgeliste mitnehmen', 'tl.move_hint': 'Ziehst du auf eine andere Adresse um? Deine Follower erfahren das automatisch, wem DU folgst aber nicht. Nimm diese Liste hier mit. Funktioniert auch von und zu Mastodon.', 'tl.move_export': 'Liste herunterladen (CSV)', 'tl.move_import_file': 'Wähle deine heruntergeladene CSV-Datei:', 'tl.move_import_lbl': 'Oder füge die Liste hier ein:', 'tl.move_import': 'Allen folgen', 'tl.pending': 'ausstehend', 'tl.unboost': 'Boost zurücknehmen', 'tl.feed': 'Beiträge', 'tl.tab_feed': 'Zeitung', 'tl.tab_following': 'Folge ich', 'tl.tab_replies': 'Antworten', 'tl.tab_followers': 'Follower', 'tl.followers': 'Follower', 'tl.followers_lead': 'Wer dir im Fediverse folgt, mit der letzten erfolgreichen Zustellung. Rot = nie zugestellt oder letzter Versuch fehlgeschlagen — nach einer Prüfung ein Kandidat zum Aufräumen.', 'tl.empty_followers': 'Noch keine Follower.', 'tl.last_delivery': 'Letzte Zustellung', 'tl.never_delivered': 'Nie zugestellt', 'tl.delivery_failed': 'letzter Versuch fehlgeschlagen', 'tl.remove_follower': 'Entfernen', 'tl.folreq_title': 'Folgeanfragen', 'tl.folreq_sub': 'Diese warten auf dein Ja oder Nein. Bis dahin sieht die anfragende Person nichts von deinen Beiträgen.', 'tl.folreq_accept': 'Annehmen', 'tl.folreq_deny': 'Ablehnen', 'tl.approve_toggle': 'Follower zuerst genehmigen', 'tl.approve_toggle_hint': 'An: Folgeanfragen warten hier auf dein Ja. Aus: alle dürfen sofort folgen.', 'tl.remove_confirm': 'Diesen Follower entfernen? Ein aktives Konto müsste dir erneut folgen.', 'tl.tab_connect': 'Connect', 'tl.connect': 'Connect', 'tl.dir_following': 'du folgst', 'tl.dir_follower': 'folgt dir', 'tl.dir_mutual': 'gegenseitig', 'tl.connect_empty': 'Noch keine Verbindungen. Folge oben jemandem, um zu starten.', 'tl.unreachable': 'Nicht erreichbar', 'tl.unreachable_lead': 'Diese Follower konnten wir nicht erreichen (nie zugestellt oder letzter Versuch fehlgeschlagen). Räume sie nach einer Prüfung auf.', 'msg.tab': 'Nachrichten', 'msg.title': 'Nachrichten', 'msg.filter_all': 'Alle', 'msg.filter_msgs': 'Nachrichten', 'msg.filter_conv': 'Gespräche', 'msg.filter_act': 'Aktivität', 'msg.filter_mod': 'Moderation', 'msg.filter_sent': 'Gesendet', 'msg.search_ph': 'Nachrichten durchsuchen…', 'msg.no_match': 'Nichts gefunden.', 'msg.poll_done': 'Deine Umfrage ist beendet', 'msg.poll_total': '{n} Teilnehmer', 'msg.you': 'Du', 'msg.sent_reply': 'hat über das Fediverse geantwortet', 'msg.and_more': 'und {n} andere', 'msg.liked_many': 'gefällt dein Beitrag', 'msg.boosted_many': 'teilten deinen Beitrag', 'msg.private': 'privat', 'msg.private_hint': 'Nur an dich gerichtet; erscheint nicht auf der öffentlichen Beitragsseite.', 'msg.new': 'Neu seit deinem letzten Besuch', 'msg.empty': 'Noch keine Nachrichten. Antworten, Erwähnungen und Aktivität erscheinen hier.', 'oauth.title': 'App autorisieren', 'oauth.wants_access': 'möchte sich mit deinem Klonkt-Konto verbinden.', 'oauth.post_as': 'Posten als', 'oauth.scope_read': 'Deine Beiträge, Antworten und Meldungen lesen', 'oauth.scope_write': 'In deinem Namen posten, antworten, liken und folgen', 'oauth.allow': 'Erlauben', 'oauth.deny': 'Ablehnen', 'oauth.foot': 'Du kannst den Zugriff später widerrufen. Autorisiere nur Apps, denen du vertraust.', 're.title': 'Antwort schreiben', 're.bold': 'Fett', 're.italic': 'Kursiv', 're.link': 'Link einfügen', 're.list': 'Aufzählung', 're.quote': 'Zitat', 're.lang': 'Sprache deiner Antwort', 're.attach': 'Medien hinzufügen (Bild, Audio, Video)', 're.attach_err': 'Upload fehlgeschlagen', 're.to': 'An:', 're.mention_del': 'Diese Person nicht mehr adressieren', 'tl.empty_following': 'Du folgst noch niemandem.', 'tl.empty': 'Noch nichts — folge jemandem, um Beiträge hier zu sehen.', 'tl.view_original': 'Original ansehen →', 'tl.open_player': 'Player öffnen', 'feed.load_more': 'Mehr laden', 'tl.paste_ph': 'URL eines Fediverse-Beitrags einfügen', 'tl.paste_go': 'Öffnen', 'tl.boosted': 'hat geteilt', 'tl.read_more': 'Mehr lesen', 'tl.show_less': 'Weniger', 'poll.vote': 'Abstimmen', 'poll.votes': 'Stimmen', 'poll.closed': 'geschlossen', 'poll.open': 'offen', 'poll.aria': 'Umfrage', 'poll.voter_one': 'Teilnehmer', 'poll.voter_many': 'Teilnehmer', 'poll.closes': 'endet am', 'poll.multiple': 'Mehrfachauswahl', 'poll.fedi_only': 'Abstimmen geht über das Fediverse — folge dieser Seite und stimme in deiner eigenen App ab.', 'poll.voted_title': 'Stimme gesendet', 'poll.voted_done': 'Deine Stimme ist unterwegs zur Umfrage. Die Ergebnisse aktualisieren sich, sobald die Autorin oder der Autor das Update sendet.',
     2188    'fedi.remote_title': 'Über das Fediverse antworten', 'fedi.follow_heading': 'Über das Fediverse folgen', 'fedi.profile_follow': 'Über das Fediverse folgen', 'profile.since': 'Auf Klonkt seit', 'profile.free': 'Kostenlos', 'fedi.follow_intro': 'Du folgst gleich:', 'fedi.follow_btn': 'Folgen', 'fedi.cancel': 'Abbrechen', 'fedi.followed_title': 'Folge-Anfrage gesendet ✅', 'fedi.followed_done': 'Deine Folge-Anfrage ist unterwegs. Sobald sie akzeptiert wird, erscheinen ihre Beiträge in deiner Timeline.', 'fedi.view_profile': 'Profil ansehen →', 'fedi.remote_reply': 'Über das Fediverse antworten', 'fedi.remote_prompt': 'Deine Fediverse-Adresse:', 'fedi.remote_notfound': 'Beitrag konnte nicht geladen werden. Füge die vollständige Beitrags-URL ein:', 'fedi.remote_load': 'Laden', 'fedi.remote_replying_to': 'Antwort an', 'fedi.cw_open': 'anzeigen', 'fedi.sensitive': 'Sensible Inhalte', 'fedi.open_full': 'Öffnen', 'fedi.src_short': 'Original', 'fedi.reply_short': 'Antworten', 'fedi.profile_short': 'Profil', 'search.fedi_looking': 'Wird im Fediverse gesucht…', 'fedi.remote_as': 'Wird als {site} gesendet.', 'fedi.remote_view_original': 'Ganzen Beitrag + Kommentare an der Quelle ansehen →', 'fedi.remote_reply_short': 'übers Fediverse', 'fedi.like_short': 'Liken', 'fedi.unlike_short': 'Like zurücknehmen', 'fedi.boost_short': 'Boosten', 'fedi.remote_ph': 'dein Server', 'fedi.remote_sent_title': 'Gesendet ✅', 'fedi.remote_sent': 'Deine Antwort wurde gesendet. Sie erscheint gleich beim Originalbeitrag im Fediverse, nicht auf dieser Seite. Sieh sie dir dort an:', 'fedi.reply_where': 'Deine Antwort erscheint beim Originalbeitrag im Fediverse, nicht auf dieser Seite. Über den Link oben siehst du sie dort.', 'fedi.remote_back': '← Zurück zu deiner Seite', 'fedi.like_btn': 'Diesen Beitrag liken', 'fedi.or_reply': 'oder antworten:', 'fedi.liked_title': 'Geliked', 'fedi.liked_done': 'Dein Like ist unterwegs ins Fediverse.', 'fedi.boost_btn': 'Diesen Beitrag boosten', 'fedi.boosted_title': 'Geboostet', 'fedi.boosted_done': 'Dein Boost ist unterwegs ins Fediverse.', 'fedi.remote_interact': 'Übers Fediverse interagieren', 'fedi.report_open': 'Diesen Beitrag melden', 'fedi.report_where': 'Die Meldung geht an die Instanz und deren Moderator(en).', 'fedi.report_ph': 'Was ist das Problem? (optional)', 'fedi.report_send': 'Melden', 'fedi.reported_title': 'Gemeldet', 'fedi.reported_done': 'Deine Meldung wurde an den Server der Person gesendet. Deren Moderatoren prüfen sie.', 'fedi.delete_confirm': 'Diese Antwort löschen?', 'fedi.mod_remove_confirm': 'Diese Antwort aus deinem Thread entfernen? Sie kommt nicht zurück, auch nicht über Thread-Auffüllung.', 'fedi.mod_report_confirm': 'Diese Antwort beim Server des Autors melden?', 'fedi.manage_title': 'Meine Fediverse-Antworten', 'fedi.manage_empty': 'Du hast noch keine Antworten gesendet.', 'fedi.goto_post': 'Zur Post', 'fedi.edit': 'Bearbeiten', 'fedi.save_edit': 'Speichern', 'fedi.bm_label': 'Über meine Seite interagieren', 'fedi.bm_help': 'Zieh diesen Button in deine Lesezeichenleiste. Klick ihn dann auf einem beliebigen Fediverse-Beitrag (Mastodon, ein anderes Klonkt…), um über deine eigene Seite zu antworten, zu liken oder zu boosten.', 'tl.title': 'Zeitung', 'tl.lead': 'Folge Konten im Fediverse und sieh ihre Beiträge hier.', 'tl.follow_btn': 'Folgen', 'tl.following': 'Du folgst', 'tl.unfollow': 'Entfolgen', 'tl.autoboost': 'Hervorgehoben', 'tl.autoboost_follow': 'im Zirkel hervorheben', 'tl.autoboost_hint': 'Ihre neuen Beiträge erscheinen laufend in deinem Zirkel (lokal, kein Fediverse-Boost).', 'tl.moved_title': 'Dieses Konto ist umgezogen', 'tl.moved_lead': 'Neue Beiträge, Folgen, Likes und Antworten laufen jetzt über', 'tl.moved_hint': 'Lesen funktioniert hier weiterhin, und Antworten auf deine alten Beiträge kommen noch an. Zurück? Leere das Umzugsziel unter Aussehen.', 'tl.move_title': 'Deine Folgeliste mitnehmen', 'tl.move_hint': 'Ziehst du auf eine andere Adresse um? Deine Follower erfahren das automatisch, wem DU folgst aber nicht. Nimm diese Liste hier mit. Funktioniert auch von und zu Mastodon.', 'tl.move_export': 'Liste herunterladen (CSV)', 'tl.move_import_file': 'Wähle deine heruntergeladene CSV-Datei:', 'tl.move_import_lbl': 'Oder füge die Liste hier ein:', 'tl.move_import': 'Allen folgen', 'tl.pending': 'ausstehend', 'tl.unboost': 'Boost zurücknehmen', 'tl.feed': 'Beiträge', 'tl.tab_feed': 'Zeitung', 'tl.tab_following': 'Folge ich', 'tl.tab_replies': 'Antworten', 'tl.tab_followers': 'Follower', 'tl.followers': 'Follower', 'tl.followers_lead': 'Wer dir im Fediverse folgt, mit der letzten erfolgreichen Zustellung. Rot = nie zugestellt oder letzter Versuch fehlgeschlagen — nach einer Prüfung ein Kandidat zum Aufräumen.', 'tl.empty_followers': 'Noch keine Follower.', 'tl.last_delivery': 'Letzte Zustellung', 'tl.never_delivered': 'Nie zugestellt', 'tl.delivery_failed': 'letzter Versuch fehlgeschlagen', 'tl.remove_follower': 'Entfernen', 'tl.folreq_title': 'Folgeanfragen', 'tl.folreq_sub': 'Diese warten auf dein Ja oder Nein. Bis dahin sieht die anfragende Person nichts von deinen Beiträgen.', 'tl.folreq_accept': 'Annehmen', 'tl.folreq_deny': 'Ablehnen', 'tl.approve_toggle': 'Follower zuerst genehmigen', 'tl.approve_toggle_hint': 'An: Folgeanfragen warten hier auf dein Ja. Aus: alle dürfen sofort folgen.', 'tl.remove_confirm': 'Diesen Follower entfernen? Ein aktives Konto müsste dir erneut folgen.', 'tl.tab_connect': 'Connect', 'tl.connect': 'Connect', 'tl.dir_following': 'du folgst', 'tl.dir_follower': 'folgt dir', 'tl.dir_mutual': 'gegenseitig', 'tl.connect_empty': 'Noch keine Verbindungen. Folge oben jemandem, um zu starten.', 'tl.unreachable': 'Nicht erreichbar', 'tl.unreachable_lead': 'Diese Follower konnten wir nicht erreichen (nie zugestellt oder letzter Versuch fehlgeschlagen). Räume sie nach einer Prüfung auf.', 'msg.tab': 'Nachrichten', 'msg.title': 'Nachrichten', 'msg.filter_all': 'Alle', 'msg.filter_msgs': 'Nachrichten', 'msg.filter_conv': 'Gespräche', 'msg.filter_act': 'Aktivität', 'msg.filter_mod': 'Moderation', 'msg.filter_sent': 'Gesendet', 'msg.search_ph': 'Nachrichten durchsuchen…', 'msg.no_match': 'Nichts gefunden.', 'msg.poll_done': 'Deine Umfrage ist beendet', 'msg.poll_total': '{n} Teilnehmer', 'msg.you': 'Du', 'msg.sent_reply': 'hat über das Fediverse geantwortet', 'msg.and_more': 'und {n} andere', 'msg.liked_many': 'gefällt dein Beitrag', 'msg.boosted_many': 'teilten deinen Beitrag', 'msg.private': 'privat', 'msg.private_hint': 'Nur an dich gerichtet; erscheint nicht auf der öffentlichen Beitragsseite.', 'msg.new': 'Neu seit deinem letzten Besuch', 'msg.empty': 'Noch keine Nachrichten. Antworten, Erwähnungen und Aktivität erscheinen hier.', 'oauth.title': 'App autorisieren', 'oauth.wants_access': 'möchte sich mit deinem Klonkt-Konto verbinden.', 'oauth.post_as': 'Posten als', 'oauth.scope_read': 'Deine Beiträge, Antworten und Meldungen lesen', 'oauth.scope_write': 'In deinem Namen posten, antworten, liken und folgen', 'oauth.allow': 'Erlauben', 'oauth.deny': 'Ablehnen', 'oauth.foot': 'Du kannst den Zugriff später widerrufen. Autorisiere nur Apps, denen du vertraust.', 're.title': 'Antwort schreiben', 're.bold': 'Fett', 're.italic': 'Kursiv', 're.link': 'Link einfügen', 're.list': 'Aufzählung', 're.quote': 'Zitat', 're.lang': 'Sprache deiner Antwort', 're.attach': 'Medien hinzufügen (Bild, Audio, Video)', 're.attach_err': 'Upload fehlgeschlagen', 're.to': 'An:', 're.mention_del': 'Diese Person nicht mehr adressieren', 'tl.empty_following': 'Du folgst noch niemandem.', 'tl.empty': 'Noch nichts — folge jemandem, um Beiträge hier zu sehen.', 'tl.view_original': 'Original ansehen →', 'tl.open_player': 'Player öffnen', 'feed.load_more': 'Mehr laden', 'tl.paste_ph': 'URL eines Fediverse-Beitrags einfügen', 'tl.paste_go': 'Öffnen', 'tl.boosted': 'hat geteilt', 'tl.read_more': 'Mehr lesen', 'tl.show_less': 'Weniger', 'poll.vote': 'Abstimmen', 'poll.votes': 'Stimmen', 'poll.closed': 'geschlossen', 'poll.open': 'offen', 'poll.aria': 'Umfrage', 'poll.voter_one': 'Teilnehmer', 'poll.voter_many': 'Teilnehmer', 'poll.closes': 'endet am', 'poll.multiple': 'Mehrfachauswahl', 'poll.fedi_only': 'Abstimmen geht über das Fediverse — folge dieser Seite und stimme in deiner eigenen App ab.', 'poll.voted_title': 'Stimme gesendet', 'poll.voted_done': 'Deine Stimme ist unterwegs zur Umfrage. Die Ergebnisse aktualisieren sich, sobald die Autorin oder der Autor das Update sendet.',
    21892189    'comments.to_start': 'um das Gespräch zu starten.',
    21902190    'comments.reply': 'Antworten', 'comments.delete': 'Löschen', 'comments.cancel': 'Abbrechen',
  • src/views/pages/authorize-interaction.ejs

    r3ceaef6 r9eb6e1b  
    106106    </form>
    107107  <% } else { %>
    108     <%- include('../partials/remote-object', { target: target, uri: uri, reacted: (typeof reacted !== 'undefined' ? reacted : { liked: false, boosted: false }), siteTitle: siteTitle, cancelUrl: uri }) %>
     108    <%- include('../partials/remote-object', { target: target, uri: uri, reacted: (typeof reacted !== 'undefined' ? reacted : { liked: false, boosted: false }), siteTitle: siteTitle, replyOpen: typeof replyOpen !== 'undefined' && replyOpen, reportOpen: typeof reportOpen !== 'undefined' && reportOpen }) %>
    109109  <% } %>
    110110  <% } %>
  • src/views/pages/search.ejs

    r3ceaef6 r9eb6e1b  
    4040        <p class="auth-interact-note"><%= t('search.fedi_signin') %></p>
    4141      <% } else if (remote && remoteKind === 'note') { %>
    42         <%- include('../partials/remote-object', { target: remote, uri: remoteUri, reacted: remoteReacted, siteTitle: siteTitle, cancelUrl: _base + '/search' }) %>
     42        <%- include('../partials/remote-object', { target: remote, uri: remoteUri, reacted: remoteReacted, siteTitle: siteTitle, cancelUrl: _base + '/search?q=' + encodeURIComponent(query) }) %>
    4343      <% } else if (remote && remoteKind === 'actor') { %>
    44         <%- include('../partials/remote-actor', { actor: remote, cancelUrl: _base + '/search', siteTitle: siteTitle }) %>
     44        <%- include('../partials/remote-actor', { actor: remote, siteTitle: siteTitle }) %>
    4545      <% } else { %>
    4646        <p class="auth-interact-note"><%= t('fedi.remote_notfound') %></p>
  • src/views/partials/fedi-interact-styles.ejs

    r3ceaef6 r9eb6e1b  
    77  .auth-interact { max-width: 640px; margin: 1rem auto; padding: 0 1rem; }
    88  .auth-interact-title { margin: 0 0 1rem; }
    9   .auth-interact-meta { color: var(--ink-soft, #888); margin: 0 0 .75rem; }
    10   .auth-interact-preview {
    11     margin: 0 0 1rem; padding: .75rem 1rem; border-left: 3px solid var(--accent, #888);
    12     background: color-mix(in srgb, var(--ink, #000) 4%, transparent); border-radius: 0 12px 12px 0;
    13     color: var(--ink, inherit);
    14   }
    159  .auth-interact-form { display: flex; flex-direction: column; gap: .75rem; }
    1610  .auth-interact-form textarea, .auth-interact-form input[type=url] {
    … …  
    2115  .auth-interact-actions { display: flex; gap: .5rem; align-items: center; }
    2216  .auth-interact-note { color: var(--ink-soft, #999); font-size: .85rem; margin-top: 1rem; }
    23   .auth-interact-where { margin: .25rem 0 .85rem; padding: .6rem .8rem; font-size: .85rem; line-height: 1.5; border-radius: 10px; background: color-mix(in srgb, var(--accent, #888) 12%, transparent); color: var(--ink, inherit); }
    24   .auth-interact-content { line-height: 1.55; }
    25   .auth-interact-content p { margin: .35rem 0; }
    26   .auth-interact-content p:first-child { margin-top: 0; }
    27   .auth-interact-img { max-width: 100%; height: auto; border-radius: 10px; margin: .5rem 0 0; display: block; }
    28   .auth-interact-orig { margin: .75rem 0 0; font-size: .85rem; }
    29   .auth-interact-orig a { color: var(--accent, #06c); }
    30   .auth-view-src { display: inline-flex; align-items: center; gap: .5rem; margin: 0 0 1.1rem; padding: .6rem 1rem;
    31     border: 1px solid color-mix(in srgb, var(--accent, #888) 45%, transparent); border-radius: 12px;
    32     background: color-mix(in srgb, var(--accent, #888) 10%, transparent); color: var(--accent, #06c);
    33     text-decoration: none; font-weight: 600; font-size: .92rem; transition: background .15s, border-color .15s; }
    34   .auth-view-src:hover { background: color-mix(in srgb, var(--accent, #888) 18%, transparent); border-color: var(--accent, #888); }
    35   .auth-interact-react { display: flex; gap: .6rem; flex-wrap: wrap; margin: 0 0 .25rem; }
    36   .auth-interact-react form { margin: 0; }
    37   .fedi-bigact { display: inline-flex; align-items: center; gap: .5rem; padding: .55rem 1.1rem; border-radius: 999px;
    38     border: 1px solid color-mix(in srgb, var(--ink, #000) 14%, transparent);
    39     background: color-mix(in srgb, var(--ink, #000) 4%, transparent);
    40     color: var(--ink, inherit); font: inherit; font-weight: 600; font-size: .92rem; cursor: pointer; white-space: nowrap;
    41     transition: background .12s, border-color .12s; }
    42   .fedi-bigact svg { width: 17px; height: 17px; flex: 0 0 17px; }
    43   .fedi-bigact-like svg { color: #e8b04b; }
    44   .fedi-bigact-like:hover, .fedi-bigact-like.is-on { background: color-mix(in srgb, #e8b04b 16%, transparent); border-color: color-mix(in srgb, #e8b04b 55%, transparent); }
    45   .fedi-bigact-boost svg { color: #2fa85a; }
    46   .fedi-bigact-boost:hover, .fedi-bigact-boost.is-on { background: color-mix(in srgb, #2fa85a 16%, transparent); border-color: color-mix(in srgb, #2fa85a 55%, transparent); }
    4717  .fedi-manage { list-style: none; padding: 0; margin: 1.25rem 0 1rem; display: flex; flex-direction: column; gap: .7rem; }
    4818  .fedi-manage-item { padding: .9rem 1rem; border-radius: 14px; background: color-mix(in srgb, var(--ink, #000) 3.5%, transparent); border: 1px solid color-mix(in srgb, var(--ink, #000) 9%, transparent); transition: border-color .15s ease, box-shadow .15s ease; }
    … …  
    8757  .fedi-edit-save { padding: .4rem 1rem; border: 0; border-radius: 999px; background: var(--accent); color: var(--paper, #fff);
    8858    font: inherit; font-weight: 600; font-size: .82rem; cursor: pointer; }
    89   /* Poll ballot / results on the interact page */
    90   .auth-poll { display: flex; flex-direction: column; gap: 8px; margin: 0 0 1rem; }
    91   .auth-poll-choice { display: flex; align-items: center; gap: 10px; padding: 10px 12px; border-radius: 10px; cursor: pointer;
     59
     60  /* ── De kaart van een post of profiel van een andere server ──────────────
     61     Eén kaart voor de live-preview, de zoekpagina en /authorize_interaction.
     62     De telefoon is het uitgangspunt: knoppen van minstens 44 punten, en op
     63     een smal scherm vallen de woorden weg en blijven de tekens staan. */
     64  .rp-card { position: relative; padding: 14px 16px 4px; border-radius: 16px;
     65    border: 1px solid color-mix(in srgb, var(--ink, #000) 10%, transparent);
     66    background: color-mix(in srgb, var(--ink, #000) 2.5%, var(--paper, transparent));
     67    color: var(--ink, inherit); overflow: hidden; }
     68  .rp-head { display: flex; align-items: center; gap: 10px; min-width: 0; margin: 0 0 10px; }
     69  .rp-avatar { flex: 0 0 40px; width: 40px; height: 40px; border-radius: 50%; object-fit: cover;
     70    background: color-mix(in srgb, var(--ink, #000) 8%, transparent); }
     71  .rp-profile .rp-avatar { flex-basis: 56px; width: 56px; height: 56px; }
     72  .rp-avatar-empty { display: inline-flex; align-items: center; justify-content: center;
     73    font-weight: 700; color: var(--ink-soft, #888); }
     74  .rp-who { display: flex; flex-direction: column; min-width: 0; line-height: 1.25; }
     75  .rp-name { font-weight: 700; color: var(--ink, inherit); text-decoration: none;
     76    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
     77  .rp-name:hover { text-decoration: underline; }
     78  .rp-handle { font-size: .82rem; color: var(--ink-soft, #888); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
     79
     80  .rp-body { line-height: 1.55; overflow-wrap: anywhere; }
     81  .rp-body p { margin: .45rem 0; }
     82  .rp-body > :first-child { margin-top: 0; }
     83  .rp-body > :last-child { margin-bottom: 0; }
     84  .rp-body a { color: var(--accent, #06c); }
     85  .rp-body img { max-width: 100%; height: auto; }
     86
     87  /* Beeld als raster: één foto op volle breedte, meer als tegels, zodat een
     88     lange reeks de knoppen niet van het scherm duwt. */
     89  /* Vaste rijhoogtes in plaats van verhoudingen: met verhoudingen groeit het
     90     raster mee met de breedte, en werd op een desktop de eerste van drie foto's
     91     zo hoog dat de knoppen onder de vouw van de uitklaplijst verdwenen. */
     92  .rp-media { display: grid; gap: 3px; margin: 10px 0 0; border-radius: 12px; overflow: hidden; }
     93  .rp-media a { display: block; min-width: 0; }
     94  .rp-media img { display: block; width: 100%; height: 100%; object-fit: cover; }
     95  .rp-media-1 img { height: auto; max-height: 480px; }
     96  .rp-media-2, .rp-media-3, .rp-media-4 { grid-template-columns: 1fr 1fr; grid-auto-rows: clamp(120px, 28vw, 220px); }
     97  .rp-media-3 a:first-child { grid-column: 1 / -1; }
     98  .rp-compact .rp-media-1 img { max-height: 240px; }
     99  .rp-compact .rp-media-2, .rp-compact .rp-media-3, .rp-compact .rp-media-4 { grid-auto-rows: 110px; }
     100
     101  /* Een inhoudswaarschuwing: dicht tot je ervoor kiest. */
     102  .rp-cw > summary { list-style: none; display: flex; align-items: center; justify-content: space-between; gap: 10px;
     103    min-height: 44px; padding: 8px 12px; border-radius: 10px; cursor: pointer; font-weight: 600;
     104    background: color-mix(in srgb, #e8b04b 12%, transparent); border: 1px solid color-mix(in srgb, #e8b04b 35%, transparent); }
     105  .rp-cw > summary::-webkit-details-marker { display: none; }
     106  .rp-cw-text { min-width: 0; overflow-wrap: anywhere; }
     107  .rp-cw-open { flex: 0 0 auto; font-size: .8rem; color: var(--accent, #06c); }
     108  .rp-cw[open] > summary { margin: 0 0 10px; }
     109  .rp-cw[open] .rp-cw-open { display: none; }
     110
     111  /* De rij knoppen: gelijke tekens, het origineel rechts. */
     112  .rp-actions { display: flex; align-items: center; gap: 2px; margin: 10px -8px 0; padding: 2px 0;
     113    border-top: 1px solid color-mix(in srgb, var(--ink, #000) 8%, transparent); }
     114  .rp-actions form { margin: 0; }
     115  .rp-act { display: inline-flex; align-items: center; justify-content: center; gap: 6px;
     116    min-width: 44px; min-height: 44px; padding: 0 12px; border: 0; border-radius: 999px;
     117    background: transparent; color: var(--ink-soft, #888); font: inherit; font-size: .88rem; font-weight: 600;
     118    text-decoration: none; cursor: pointer; white-space: nowrap; -webkit-tap-highlight-color: transparent;
     119    transition: background .12s, color .12s; }
     120  .rp-act svg { flex: 0 0 18px; width: 18px; height: 18px; }
     121  .rp-act:hover { background: color-mix(in srgb, var(--ink, #000) 6%, transparent); color: var(--ink, inherit); }
     122  .rp-act:focus-visible { outline: 2px solid var(--accent, #06c); outline-offset: 2px; }
     123  .rp-act[disabled] { opacity: .55; cursor: progress; }
     124  .rp-like.is-on { color: #d99a1e; }
     125  .rp-like.is-on svg { fill: currentColor; }
     126  .rp-boost.is-on { color: #2fa85a; }
     127  .rp-reply-btn[aria-expanded="true"] { color: var(--accent, #06c); }
     128  .rp-src { margin-left: auto; }
     129  .rp-primary { background: var(--accent, #06c); color: var(--paper, #fff); }
     130  .rp-primary:hover { background: var(--accent, #06c); color: var(--paper, #fff); filter: brightness(1.08); }
     131  .rp-profile .rp-actions { border-top: 0; margin-top: 4px; padding-bottom: 8px; }
     132  .rp-profile .rp-actions form { flex: 0 0 auto; }
     133
     134  /* Een peiling in de kaart. */
     135  .rp-poll { display: flex; flex-direction: column; gap: 6px; margin: 12px 0 0; }
     136  .rp-poll-choice { display: flex; align-items: center; gap: 10px; min-height: 44px; padding: 0 12px; border-radius: 10px; cursor: pointer;
    92137    border: 1px solid color-mix(in srgb, var(--ink, #000) 14%, transparent); }
    93   .auth-poll-choice:hover { border-color: var(--accent); }
    94   .auth-poll-choice input { accent-color: var(--accent); }
    95   .auth-poll-res { position: relative; padding: 9px 12px; border-radius: 10px; overflow: hidden; display: flex; align-items: center; gap: 8px;
     138  .rp-poll-choice:hover { border-color: var(--accent, #06c); }
     139  .rp-poll-choice input { accent-color: var(--accent, #06c); }
     140  .rp-poll-res { position: relative; display: flex; align-items: center; gap: 8px; padding: 9px 12px; border-radius: 10px; overflow: hidden;
    96141    background: color-mix(in srgb, var(--ink, #000) 6%, transparent); }
    97   .auth-poll-fill { position: absolute; inset: 0 auto 0 0; background: color-mix(in srgb, var(--accent) 22%, transparent); z-index: 0; }
    98   .auth-poll-name { position: relative; z-index: 1; flex: 1; font-size: .95rem; }
    99   .auth-poll-pct { position: relative; z-index: 1; font-variant-numeric: tabular-nums; font-weight: 500; }
    100   .auth-poll-foot { font-size: .82rem; color: var(--ink-soft, #888); margin: .1rem 0 0; }
    101   .auth-report { margin: .6rem 0 0; }
    102   .auth-report summary { cursor: pointer; font-size: .85rem; color: var(--ink-soft, #888); }
    103   .auth-report summary:hover, .auth-report[open] summary { color: #c0392b; }
    104   .auth-report[open] summary { margin-bottom: .5rem; }
    105   .auth-report-note { font-size: .82rem; color: var(--ink-muted, #999); margin: 0 0 .5rem; }
    106   /* Het gezicht van een profiel dat we net hebben opgehaald. */
    107   .remote-actor { display: flex; align-items: center; gap: .75rem; }
    108   .remote-actor .auth-interact-meta { margin: 0; }
    109   .remote-actor-icon { width: 48px; height: 48px; border-radius: 50%; object-fit: cover; flex: 0 0 48px;
    110     background: color-mix(in srgb, var(--ink, #000) 8%, transparent); }
     142  .rp-poll-fill { position: absolute; inset: 0 auto 0 0; background: color-mix(in srgb, var(--accent, #06c) 22%, transparent); }
     143  .rp-poll-name { position: relative; flex: 1; }
     144  .rp-poll-pct { position: relative; font-variant-numeric: tabular-nums; font-weight: 600; }
     145  .rp-poll-foot { display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap;
     146    font-size: .82rem; color: var(--ink-soft, #888); }
     147
     148  /* Wat er onder de kaart staat, op de volle pagina. */
     149  .rp-reply { margin: 12px 0 0; }
     150  .rp-reply-where { margin: 0 0 8px; font-size: .85rem; line-height: 1.5; color: var(--ink-soft, #888); }
     151  .rp-reply-foot { display: flex; justify-content: flex-end; margin-top: 4px; }
     152  .rp-as { margin: 12px 2px 0; font-size: .8rem; color: var(--ink-soft, #999); }
     153  .rp-report { margin: 6px 2px 0; }
     154  .rp-report > summary { display: inline-block; padding: 8px 0; cursor: pointer; font-size: .8rem; color: var(--ink-soft, #888); }
     155  .rp-report > summary:hover, .rp-report[open] > summary { color: #c0392b; }
     156  .rp-report-form { display: flex; flex-direction: column; gap: .6rem; }
     157  .rp-report-note { margin: 0; font-size: .82rem; color: var(--ink-soft, #999); }
     158  .rp-report-form textarea { width: 100%; box-sizing: border-box; padding: .6rem .8rem; font: inherit; resize: vertical; border-radius: 12px;
     159    border: 1px solid color-mix(in srgb, var(--ink, #000) 18%, transparent); background: var(--paper, #fff); color: var(--ink, #000); }
     160
     161  /* De live-preview: open-link onder de kaart, en de kaart zonder rand-marge. */
     162  .rp-open { display: block; padding: 10px 4px 2px; text-align: right; font-weight: 600; font-size: .88rem;
     163    color: var(--accent, #06c); text-decoration: none; }
     164  .rp-open:hover { text-decoration: underline; }
     165  .ss-remote { padding: 4px 0 6px; }
     166  .ss-remote-note { padding: .8rem .6rem; color: var(--ink-soft, var(--ink-muted)); font-size: .9rem; }
     167  .ss-remote-loading { display: flex; align-items: center; gap: 10px; padding: .9rem .6rem; color: var(--ink-soft, var(--ink-muted)); font-size: .9rem; }
     168  .ss-remote-loading::before { content: ''; width: 16px; height: 16px; border-radius: 50%; flex: 0 0 16px;
     169    border: 2px solid color-mix(in srgb, var(--accent, #06c) 30%, transparent); border-top-color: var(--accent, #06c);
     170    animation: ss-spin .8s linear infinite; }
     171  @keyframes ss-spin { to { transform: rotate(360deg); } }
     172  @media (prefers-reduced-motion: reduce) { .ss-remote-loading::before { animation: none; } }
     173
     174  /* Smal scherm: alleen de tekens, gelijk verdeeld over de breedte. De woorden
     175     blijven bestaan voor wie het scherm voorgelezen krijgt. */
     176  @media (max-width: 480px) {
     177    .rp-card { padding: 12px 12px 2px; border-radius: 14px; }
     178    .rp-actions { margin: 8px -4px 0; justify-content: space-between; }
     179    .rp-act .rp-act-label { position: absolute; width: 1px; height: 1px; overflow: hidden;
     180      clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }
     181    .rp-src { margin-left: 0; }
     182    .rp-media-1 img { max-height: 360px; }
     183  }
    111184</style>
  • src/views/partials/remote-actor.ejs

    r3ceaef6 r9eb6e1b  
    22  Een PERSOON van een andere server: wie het is, en de knop om te volgen.
    33
    4   Zelfde reden als remote-object: de zoekbalk en /authorize_interaction laten
    5   hetzelfde zien, dus staat het op één plek. Het volgen zelf gaat naar
    6   /authorize_interaction/follow, want daar zit de poort van de guardians
    7   (shaer-p729) en die hoort niet twee keer geschreven te worden.
     4  Dezelfde kaart als een post (remote-object), zodat een profiel en een post in
     5  de zoekbalk er als familie uitzien en niet als twee formulieren. Het volgen
     6  zelf gaat naar /authorize_interaction/follow, want daar zit de poort van de
     7  guardians (shaer-p729) en die hoort niet twee keer geschreven te worden.
    88
    9   Verwacht: actor (uit resolveRemoteActor). Optioneel: cancelUrl, siteTitle.
     9  Verwacht: actor (uit resolveRemoteActor). Optioneel: compact (de
     10  live-preview), siteTitle.
    1011%>
    11 <% const _annuleer = (typeof cancelUrl !== 'undefined' && cancelUrl) ? cancelUrl : '/'; %>
    12 <div class="remote-actor">
    13   <% if (actor.actor_icon) { %>
    14     <img class="remote-actor-icon" src="<%= actor.actor_icon %>" alt="" loading="lazy" width="48" height="48">
    15   <% } %>
    16   <p class="auth-interact-meta">
    17     <%= t('fedi.follow_intro') %>
    18     <a href="<%= actor.actor_url %>" rel="nofollow noopener" target="_blank"><strong><%= actor.actor_name %></strong></a>
    19     <span class="fedi-handle"><%= actor.actor_handle %></span>
    20   </p>
    21 </div>
    22 <form method="post" action="/authorize_interaction/follow" class="auth-interact-form">
    23   <input type="hidden" name="uri" value="<%= actor.actor_uri %>">
    24   <div class="auth-interact-actions">
    25     <button type="submit" class="btn btn-primary"><%= t('fedi.follow_btn') %></button>
    26     <a href="<%= _annuleer %>" class="btn"><%= t('comments.cancel') %></a>
    27   </div>
    28 </form>
    29 <p class="auth-interact-note"><%= t('fedi.remote_as', { site: typeof siteTitle !== 'undefined' ? siteTitle : '' }) %></p>
     12<%
     13  const _compact = !!locals.compact;
     14  const _initial = String(actor.actor_name || actor.actor_handle || '?').replace(/^@/, '').slice(0, 1).toUpperCase();
     15%>
     16<article class="rp-card rp-profile<%= _compact ? ' rp-compact' : '' %>">
     17  <header class="rp-head">
     18    <% if (actor.actor_icon) { %>
     19      <img class="rp-avatar" src="<%= actor.actor_icon %>" alt="" width="56" height="56" loading="lazy">
     20    <% } else { %>
     21      <span class="rp-avatar rp-avatar-empty" aria-hidden="true"><%= _initial %></span>
     22    <% } %>
     23    <div class="rp-who">
     24      <a class="rp-name" href="<%= actor.actor_url || actor.actor_uri %>" rel="nofollow noopener" target="_blank"><%= actor.actor_name || actor.actor_handle %></a>
     25      <span class="rp-handle"><%= actor.actor_handle %></span>
     26    </div>
     27  </header>
     28  <footer class="rp-actions">
     29    <form method="post" action="/authorize_interaction/follow">
     30      <input type="hidden" name="uri" value="<%= actor.actor_uri %>">
     31      <button type="submit" class="rp-act rp-primary">
     32        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><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>
     33        <span><%= t('fedi.follow_btn') %></span>
     34      </button>
     35    </form>
     36    <a class="rp-act rp-src" href="<%= actor.actor_url || actor.actor_uri %>" target="_blank" rel="nofollow noopener" title="<%= t('fedi.view_profile') %>">
     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="M7 17 17 7"/><path d="M7 7h10v10"/></svg>
     38      <span class="rp-act-label"><%= t('fedi.profile_short') %></span>
     39    </a>
     40  </footer>
     41</article>
     42<% if (!_compact) { %>
     43  <p class="rp-as"><%= t('fedi.remote_as', { site: typeof siteTitle !== 'undefined' ? siteTitle : '' }) %></p>
     44<% } %>
  • src/views/partials/remote-object.ejs

    r3ceaef6 r9eb6e1b  
    11<%#
    2   Een object van een andere server: hoe het eruit ziet, en wat je ermee kunt.
     2  Een post van een andere server: hoe hij eruit ziet, en wat je ermee kunt.
    33
    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.
    1015
    1116  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
    1426%>
    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 %>&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>
    32104      </a>
    33105    <% } %>
    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<% } %>
  • src/views/partials/topnav.ejs

    r3ceaef6 r9eb6e1b  
    178178<%# De teksten staan op het element dat ze gebruikt, niet in het script: een
    179179    module is een statisch bestand en kan geen vertaling interpoleren. %>
    180 <div id="search-overlay" class="search-overlay" hidden data-suggest="<%= _siteUrlBase %>/search/suggest" data-action="<%= _siteUrlBase %>/search"
    181      data-i18n="<%= JSON.stringify({ posts: t('search.section_posts'), tracks: t('search.section_tracks'), events: t('search.section_events'), pages: t('search.section_pages'), all: t('search.suggest_all'), none: t('search.suggest_empty') }) %>">
     180<div id="search-overlay" class="search-overlay" hidden data-suggest="<%= _siteUrlBase %>/search/suggest" data-remote="<%= _siteUrlBase %>/search/remote" data-action="<%= _siteUrlBase %>/search"
     181     data-i18n="<%= JSON.stringify({ posts: t('search.section_posts'), tracks: t('search.section_tracks'), events: t('search.section_events'), pages: t('search.section_pages'), all: t('search.suggest_all'), none: t('search.suggest_empty'), looking: t('search.fedi_looking') }) %>">
    182182  <div class="container">
    183183    <form class="search-overlay-form" method="get" action="<%= _siteUrlBase %>/search">
    … …  
    467467}
    468468/* Live-resultaten dropdown */
    469 .search-suggest { margin-top: .6rem; max-height: 60vh; overflow-y: auto; display: flex; flex-direction: column; gap: .25rem; }
     469.search-suggest { margin-top: .6rem; max-height: 60vh; max-height: 70dvh; overflow-y: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch; display: flex; flex-direction: column; gap: .25rem; }
    470470.search-suggest:empty { display: none; }
    471471.ss-group { padding: .25rem 0; }
  • src/views/shell.ejs

    r3ceaef6 r9eb6e1b  
    130130// ontwikkeling een jaar gecachet, dus zonder bump bereikt een reparatie
    131131// iedereen behalve wie de fout al heeft.
    132 const MOD_V = 64;
     132const MOD_V = 65;
    133133%><!DOCTYPE html>
    134134<html lang="<%- _e(lang) %>" data-palette="<%- _e((typeof palette !== 'undefined' && palette) ? palette : (safeSite.palette || 'klonkt')) %>">
Note: See TracChangeset for help on using the changeset viewer.