Changeset 746d98a in Klonkt


Ignore:
Timestamp:
09/30/2026 09:08:48 AM (80 minutes ago)
Author:
Robin <roboburr@…>
Branches:
main
Parents:
31680c3
Message:

Wardmodus: een besloten Klonkt met alleen het profiel openbaar

Robin, 30-9: "Ward Profile mode gaat actief wanneer ward gekoppeld is, dus
wanneer de persoon guardian(s) heeft. Een besloten Klonkt met enkel het profiel
van de ward publiek. Wij denken dat dit het beste valt bij emancipation om de
functie weer naar normaal Klonkt te brengen."

AFGELEID, NIET OPGESLAGEN. Heeft de site geaccepteerde guardians, dan staat de
modus aan; is de set leeg, dan uit. listGuardians telt alleen geaccepteerde
guardianships, dus een voorstel zet niets dicht, en de set leegmaken is
emancipatie (FEP-633c 3.4) -- precies waar het ontwerp de weg terug legt. Een
schakelaar zou een tweede waarheid naast de guardianship zijn.

STANDAARD DICHT (middleware/ward-profile.js, in server.js direct na de
siteresolver en de schrijfpoort). Een bezoeker ziet op / het profiel en verder
niets: een pagina gaat terug naar het profiel, al het andere is 404, een POST
bereikt zijn handler niet, en een htmx-navigatie krijgt HX-Redirect in plaats
van een brokstuk. Wat niet op de lijst staat komt er niet langs, zodat een
nieuwe route niet vanzelf een kind openzet.

Open blijft: inloggen, account, beheer, het guardianpaneel, /lang, de bestanden
van de pagina zelf, en /audio/stream -- die bewaakt elk bestand zelf, en een
track die de ward op de fediverse opende moet daar speelbaar blijven. De
federatie (ActivityPub, WebFinger, OAuth voor Shaer, /media, /assets, /og) hangt
al VOOR de poort; de paden staan toch op de lijst, voor als de volgorde ooit
omgaat, want dan zou een ward stil van de fediverse verdwijnen.

Wie alles ziet: de ward zelf, en god, kijker en sitebeheer. Een andere
ingelogde gebruiker is gewoon een bezoeker.

DE PROFIELPAGINA (pages/ward-profile.ejs) mag verzorgd, want dit is alles wat
een bezoeker ziet: foto met een ring in de accentkleur, de naam groot, de
handle op een pil, de bio, de links als brede knoppen in hun merkkleur, een
volgknop die de bestaande fediverse-dialoog opent (een volgverzoek aan een ward
gaat eerst langs de guardians), en een neutrale regel dat berichten alleen
voor volgers zijn. Nergens "kind" of "ward": dat de account guardians heeft
staat al openbaar op de fediverse. Dezelfde bronnen als de profielkop, zodat
het profiel op geen twee plekken iets anders kan zeggen. De chrome biedt een
bezoeker niets aan wat dicht is: geen zoekknop, geen archief, geen
Lezen/Grid-schakelaar.

DE PROFIELLINKS verhuizen van Appearance naar het account (/account#links),
bij naam, bio en foto. Twee vallen onderweg, allebei getoetst:

  • Appearance schreef profile_links altijd, uit de formuliervelden. Met de velden weg zou elke keer opslaan daar de links wissen. Die pagina schrijft ze niet meer.
  • ownedSite haalde profile_links niet op, dus de accountpagina toonde nul links en opslaan wiste ze. Gevonden bij het bekijken, niet door een toets; nu wel.

buildProfileLinks staat in PlatformIcons.js, en de rijen-knoppen in een eigen
module (mod/profile-links.js).

Getoetst: gewone Klonkt zonder guardians, een voorstel zet niets dicht,
gekoppeld alleen het profiel, pagina's terug, 404 voor wat geen pagina is, geen
handler voor een POST, HX-Redirect, wat open moet blijft open, ward en beheer
zien alles, een andere ingelogde is een bezoeker, emancipatie maakt het weer
gewoon, de links bij het account en Appearance wist ze niet. Tegenproef per
zaak. Volledige suite 1325 groen.

Nagekeken in de browser op 375 en 1280 pixels: het profiel past op een
telefoonscherm, knoppen van 55 pixels, geen overloop, de volgknop opent de
dialoog; de accountpagina toont de bestaande links en voegt er een toe.

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

Files:
4 added
14 edited

Legend:

Unmodified
Added
Removed
  • src/assets/js/mod/admin-site-edit.js

    r31680c3 r746d98a  
    4242  if (sh) sh.textContent = location.host + (sh.dataset.prefix || '/');
    4343})();
    44 (function() {
    45   // Profile-links repeater: add row from <template>, remove on click.
    46   var rows = document.getElementById('profile-links-rows');
    47   var tpl  = document.getElementById('profile-link-template');
    48   var add  = document.getElementById('profile-link-add');
    49   if (!rows || !tpl || !add) return;
    50 
    51   add.addEventListener('click', function() {
    52     var clone = tpl.content.cloneNode(true);
    53     rows.appendChild(clone);
    54   });
    55   rows.addEventListener('click', function(e) {
    56     if (e.target && e.target.classList.contains('pl-remove')) {
    57       var row = e.target.closest('.profile-link-row');
    58       if (row) row.remove();
    59     }
    60   });
    61 })();
     44// De profiellinks staan sinds 30-9 bij je profiel: zie mod/profile-links.js.
    6245
    6346// P63 — Profile photo picker: upload via /admin/sites/upload-photo,
  • src/middleware/render.js

    r31680c3 r746d98a  
    213213    audioTracks: data.audioTracks || res.locals.audioTracks || [],
    214214    siteUrlBase: res.locals.siteUrlBase || '',
     215    // De wardmodus (middleware/ward-profile.js): een bezoeker van een ward-site ziet
     216    // alleen het profiel, en de chrome biedt dan niets aan wat toch dicht is.
     217    wardProfile: !!res.locals.wardProfile,
    215218    footerNewsletter: getSetting('footer_newsletter') === '1', // newsletter sign-up in footer (premium)
    216219    agendaEnabled: getSetting('agenda_enabled') === '1', // show agenda/events in the pill (premium, opt-in)
  • src/routes/account.js

    r31680c3 r746d98a  
    1414
    1515import express from 'express';
     16import { listPlatforms, buildProfileLinks, parseProfileLinks } from '../services/PlatformIcons.js';
    1617import path from 'path';
    1718import fs from 'fs';
    … …  
    6970    pageTitle: 'Account',
    7071    bodyClass: 'on-special',
     72    // De profiellinks staan sinds 30-9 hier, bij je profiel, en niet meer op de
     73    // Appearance-pagina (Robins verzoek).
     74    pageJs: 'profile-links',
     75    platforms: listPlatforms(),
     76    profileLinks: editableSite ? parseProfileLinks(editableSite.profile_links) : [],
    7177    account,
    7278    hasPassword,
    … …  
    106112function ownedSite(user) {
    107113  if (!user) return null;
    108   let site = db.prepare('SELECT id, title, tagline, slug, owner_id, profile_photo FROM sites WHERE owner_id = ? ORDER BY created_at LIMIT 1').get(user.id);
     114  // profile_links hoort erbij sinds de links hier staan (30-9). Zonder die kolom
     115  // toonde de pagina nul links, en wiste opslaan ze dus.
     116  let site = db.prepare('SELECT id, title, tagline, slug, owner_id, profile_photo, profile_links FROM sites WHERE owner_id = ? ORDER BY created_at LIMIT 1').get(user.id);
    109117  if (!site && user.role === 'god') {
    110118    site = getPrimarySite(); // primary/main site as fallback
    … …  
    128136});
    129137
     138// ==================== PROFIELLINKS ====================
     139// Sinds 30-9 hier en niet meer op Appearance: ze horen bij je profiel, naast je
     140// naam, bio en foto. Dezelfde rechten als de site-naam hierboven: de eigenaar
     141// van de site, of god.
     142router.post('/links', requireAuth, (req, res) => {
     143  const site = ownedSite(req.session.user);
     144  if (!site) return res.redirect('/account?error=' + encodeURIComponent('Geen site om te bewerken.'));
     145  if (site.owner_id !== req.session.user.id && req.session.user.role !== 'god') {
     146    return res.redirect('/account?error=' + encodeURIComponent('Geen rechten om deze site te bewerken.'));
     147  }
     148  db.prepare('UPDATE sites SET profile_links = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?')
     149    .run(buildProfileLinks(req.body), site.id);
     150  res.redirect('/account?success=' + encodeURIComponent('Links bijgewerkt') + '#links');
     151});
     152
    130153// ==================== UPDATE BIO ====================
    131154const RESERVED_USERNAMES = new Set(['admin', 'account', 'auth', 'login', 'register', 'logout', 'user', 'users', 'api', 'fediverse', 'posts', 'media', 'audio', 'assets', 'cirkel', 'authorize_interaction']);
  • src/routes/admin-sites.js

    r31680c3 r746d98a  
    5555});
    5656
    57 /** Coerce req.body fields into the JSON profile_links array. */
    58 function buildProfileLinks(body) {
    59   const platforms = body.profile_link_platform || [];
    60   const urls = body.profile_link_url || [];
    61   const arr = [];
    62   const platformsArr = Array.isArray(platforms) ? platforms : [platforms];
    63   const urlsArr      = Array.isArray(urls)      ? urls      : [urls];
    64   for (let i = 0; i < platformsArr.length; i++) {
    65     const p = (platformsArr[i] || '').toString().trim();
    66     const u = (urlsArr[i] || '').toString().trim();
    67     if (!p || !u) continue;
    68     if (!PLATFORMS[p]) continue;
    69     if (!/^https?:\/\//i.test(u) && p !== 'email') continue;
    70     if (p === 'email' && !/^mailto:|^[^\s@]+@[^\s@]+$/i.test(u)) continue;
    71     arr.push({ platform: p, url: u });
    72   }
    73   return arr.length ? JSON.stringify(arr) : null;
    74 }
    7557
    7658/**
    … …  
    211193    accents: ThemeService.listAccents(),
    212194    platforms: listPlatforms(),
    213     parsedLinks: [],
    214195    apAliases: '',
    215196    error: null,
    … …  
    275256  if (!site) return res.redirect('/admin/sites?error=Not+found');
    276257
    277   let parsedLinks = [];
    278   if (site.profile_links) {
    279     try { parsedLinks = JSON.parse(site.profile_links) || []; } catch {}
    280   }
    281258
    282259  let apAliases = '';
    … …  
    293270    accents: ThemeService.listAccents(),
    294271    platforms: listPlatforms(),
    295     parsedLinks,
    296272    apAliases,
    297273    success: req.query.success || null,
    … …  
    333309  const feedAlt = ['timeline', 'auto'].includes(f.feed_alt_view) ? f.feed_alt_view : 'reader';
    334310  const feedViewDef = f.feed_view_default === 'grid' ? 'grid' : feedAlt;
    335   const profileLinksJson = buildProfileLinks(f);
     311  // De profiellinks staan sinds 30-9 bij je profiel (/account). Deze pagina
     312  // schrijft ze dus NIET meer: met de velden weg zou elke keer opslaan hier ze
     313  // wissen. De kolom blijft van /account/links.
    336314
    337315  // FEP-7628 aliases — validated/resolved before anything is written.
    … …  
    366344      palette = ?, accent = ?, theme_override = ?, profile_photo = ?,
    367345      profile_enabled = ?,
    368       profile_links = ?,
    369346      ap_aliases = ?,
    370347      is_public = ?, robots_index = ?, require_login_to_comment = ?,
    … …  
    386363    f.profile_photo || null,
    387364    f.profile_enabled ? 1 : 0,
    388     profileLinksJson,
    389365    apAliasesJson,
    390366    f.is_public ? 1 : 0,
  • src/server.js

    r31680c3 r746d98a  
    2222import { getThumbnail, getRemoteThumbnail, verifyImg, THUMB_SIZES } from './services/ThumbnailService.js';
    2323import { resolveSite, loadAudioTracks, loadTheme } from './middleware/site.js';
     24import { wardProfileGate } from './middleware/ward-profile.js';
    2425import { isViewer } from './middleware/auth.js';
    2526import { renderPage } from './middleware/render.js';
    … …  
    396397});
    397398
     399// De wardmodus (30-9): heeft deze Klonkt guardians, dan ziet een bezoeker alleen
     400// het profiel. Hier en niet eerder: de federatie, OAuth, /media en /assets hangen
     401// hierboven en moeten gewoon blijven werken. Zie middleware/ward-profile.js.
     402app.use(wardProfileGate);
    398403app.use('/auth', authRoutes);
    399404app.use('/account', accountRoutes);
  • src/services/PlatformIcons.js

    r31680c3 r746d98a  
    127127}
    128128
    129 export default { PLATFORMS, PLATFORM_ORDER, listPlatforms, CONTACT_PLATFORMS, linkHref };
     129/**
     130 * De profiellinks uit een formulier (profile_link_platform[] + profile_link_url[])
     131 * als de JSON die in sites.profile_links staat, of null als er niets over is.
     132 *
     133 * Stond in routes/admin-sites.js, toen de links op de Appearance-pagina stonden.
     134 * Sinds 30-9 bewerk je ze bij je profiel (/account), en een tweede kopie van
     135 * deze keuring loopt gegarandeerd uit de pas -- dus hij staat hier.
     136 */
     137export function buildProfileLinks(body) {
     138  const platforms = (body && body.profile_link_platform) || [];
     139  const urls = (body && body.profile_link_url) || [];
     140  const arr = [];
     141  const platformsArr = Array.isArray(platforms) ? platforms : [platforms];
     142  const urlsArr      = Array.isArray(urls)      ? urls      : [urls];
     143  for (let i = 0; i < platformsArr.length; i++) {
     144    const p = (platformsArr[i] || '').toString().trim();
     145    const u = (urlsArr[i] || '').toString().trim();
     146    if (!p || !u) continue;
     147    if (!PLATFORMS[p]) continue;
     148    if (!/^https?:\/\//i.test(u) && p !== 'email') continue;
     149    if (p === 'email' && !/^mailto:|^[^\s@]+@[^\s@]+$/i.test(u)) continue;
     150    arr.push({ platform: p, url: u });
     151  }
     152  return arr.length ? JSON.stringify(arr) : null;
     153}
     154
     155/** sites.profile_links terug als lijst; een kapotte waarde is een lege lijst. */
     156export function parseProfileLinks(json) {
     157  if (!json) return [];
     158  try { const a = JSON.parse(json); return Array.isArray(a) ? a : []; } catch { return []; }
     159}
     160
     161export default { PLATFORMS, PLATFORM_ORDER, listPlatforms, CONTACT_PLATFORMS, linkHref, buildProfileLinks, parseProfileLinks };
  • src/services/i18n.js

    r31680c3 r746d98a  
    843843    'acct.tagline': 'Tagline',
    844844    'acct.tagline_hint': '— korte oneliner (optioneel)',
    845     'acct.site_save': 'Site opslaan',
     845    'ward.followers_only': 'Berichten van {name} zijn alleen te zien voor volgers.', 'asite.links_moved': 'Je profiellinks staan nu bij je profiel.', 'asite.links_moved_link': 'Naar je profiel', 'acct.links_save': 'Links opslaan', 'acct.link_platform': 'Platform', 'acct.link_url': 'Adres', 'acct.link_remove': 'Link weghalen', 'acct.site_save': 'Site opslaan',
    846846    'acct.password_change': 'Wachtwoord wijzigen',
    847847    'acct.password_current': 'Huidig wachtwoord',
    … …  
    18521852    'acct.tagline': 'Tagline',
    18531853    'acct.tagline_hint': '— short one-liner (optional)',
    1854     'acct.site_save': 'Save site',
     1854    'ward.followers_only': 'Posts by {name} are only visible to followers.', 'asite.links_moved': 'Your profile links now live with your profile.', 'asite.links_moved_link': 'Go to your profile', 'acct.links_save': 'Save links', 'acct.link_platform': 'Platform', 'acct.link_url': 'Address', 'acct.link_remove': 'Remove link', 'acct.site_save': 'Save site',
    18551855    'acct.password_change': 'Change password',
    18561856    'acct.password_current': 'Current password',
    … …  
    28612861    'acct.tagline': 'Slogan',
    28622862    'acct.tagline_hint': '— kurzer Einzeiler (optional)',
    2863     'acct.site_save': 'Seite speichern',
     2863    'ward.followers_only': 'Beiträge von {name} sind nur für Follower sichtbar.', 'asite.links_moved': 'Deine Profil-Links findest du jetzt bei deinem Profil.', 'asite.links_moved_link': 'Zum Profil', 'acct.links_save': 'Links speichern', 'acct.link_platform': 'Plattform', 'acct.link_url': 'Adresse', 'acct.link_remove': 'Link entfernen', 'acct.site_save': 'Seite speichern',
    28642864    'acct.password_change': 'Passwort ändern',
    28652865    'acct.password_current': 'Aktuelles Passwort',
  • src/views/pages/account.ejs

    r31680c3 r746d98a  
    113113    </form>
    114114  </section>
     115  <% } %>
     116
     117  <%# ── PROFIELLINKS ───────────────────────────────────────────
     118      Sinds 30-9 hier, bij je profiel, en niet meer op de Appearance-pagina: ze
     119      horen bij je naam, bio en foto. In de wardmodus is dat profiel het enige
     120      dat een bezoeker van de site ziet. %>
     121  <% if (typeof editableSite !== 'undefined' && editableSite && canMutate) { %>
     122  <section class="ax-card" id="links">
     123    <div class="ax-card-title"><%= t('asite.links') %></div>
     124    <p class="form-hint"><%= t('asite.links_hint') %></p>
     125    <form action="/account/links" method="post" class="ax-form">
     126      <div id="profile-links-rows">
     127        <% (profileLinks || []).forEach(function (l) { %>
     128          <div class="profile-link-row">
     129            <select name="profile_link_platform[]" aria-label="<%= t('acct.link_platform') %>">
     130              <% platforms.forEach(function (p) { %>
     131                <option value="<%= p.key %>" <%= l.platform === p.key ? 'selected' : '' %>><%= p.label %></option>
     132              <% }); %>
     133            </select>
     134            <input type="text" name="profile_link_url[]" value="<%= l.url %>" placeholder="https://…" required aria-label="<%= t('acct.link_url') %>">
     135            <button type="button" class="ax-btn pl-remove" aria-label="<%= t('acct.link_remove') %>">×</button>
     136          </div>
     137        <% }); %>
     138      </div>
     139      <template id="profile-link-template">
     140        <div class="profile-link-row">
     141          <select name="profile_link_platform[]" aria-label="<%= t('acct.link_platform') %>">
     142            <% platforms.forEach(function (p) { %>
     143              <option value="<%= p.key %>"><%= p.label %></option>
     144            <% }); %>
     145          </select>
     146          <input type="text" name="profile_link_url[]" placeholder="https://…" required aria-label="<%= t('acct.link_url') %>">
     147          <button type="button" class="ax-btn pl-remove" aria-label="<%= t('acct.link_remove') %>">×</button>
     148        </div>
     149      </template>
     150      <div class="ax-form-actions">
     151        <button type="button" id="profile-link-add" class="ax-btn"><%= t('asite.link_add') %></button>
     152        <button type="submit" class="ax-btn ax-btn-primary"><%= t('acct.links_save') %></button>
     153      </div>
     154    </form>
     155  </section>
     156  <style>
     157    .profile-link-row { display: grid; grid-template-columns: 1fr auto; gap: .5rem; margin-bottom: .5rem; }
     158    .profile-link-row select { padding: .45rem .6rem; grid-column: 1 / -1; }
     159    .profile-link-row input { min-width: 0; }
     160    @media (min-width: 520px) {
     161      .profile-link-row { grid-template-columns: 160px 1fr auto; }
     162      .profile-link-row select { grid-column: auto; }
     163    }
     164  </style>
    115165  <% } %>
    116166
  • src/views/pages/admin-site-edit.ejs

    r31680c3 r746d98a  
    150150    </fieldset>
    151151
    152     <fieldset class="profile-links-fieldset">
    153       <legend><%= t('asite.links') %></legend>
    154       <p class="form-hint"><%= t('asite.links_hint') %></p>
    155 
    156       <div id="profile-links-rows">
    157         <% (parsedLinks || []).forEach(function(l) { %>
    158           <div class="profile-link-row">
    159             <select name="profile_link_platform[]">
    160               <% platforms.forEach(function(p) { %>
    161                 <option value="<%= p.key %>" <%= l.platform === p.key ? 'selected' : '' %>><%= p.label %></option>
    162               <% }); %>
    163             </select>
    164             <input type="text" name="profile_link_url[]" value="<%= l.url %>" placeholder="https://…" required>
    165             <button type="button" class="btn btn-danger pl-remove">×</button>
    166           </div>
    167         <% }); %>
    168       </div>
    169 
    170       <template id="profile-link-template">
    171         <div class="profile-link-row">
    172           <select name="profile_link_platform[]">
    173             <% platforms.forEach(function(p) { %>
    174               <option value="<%= p.key %>"><%= p.label %></option>
    175             <% }); %>
    176           </select>
    177           <input type="text" name="profile_link_url[]" placeholder="https://…" required>
    178           <button type="button" class="btn btn-danger pl-remove">×</button>
    179         </div>
    180       </template>
    181 
    182       <button type="button" id="profile-link-add" class="btn"><%= t('asite.link_add') %></button>
    183     </fieldset>
     152    <%# De profiellinks staan sinds 30-9 bij je profiel, naast je naam, bio en foto:
     153        dat is waar ze bij horen, en in de wardmodus is dat profiel het enige dat
     154        een bezoeker ziet. %>
     155    <p class="form-hint profile-links-moved"><%= t('asite.links_moved') %> <a href="/account#links"><%= t('asite.links_moved_link') %></a></p>
    184156
    185157    <%# Aliassen en verhuizen staan sinds 14-8 op /admin/migrate, bij elkaar met
    … …  
    477449   button below. Prevents horizontal overflow on phones (fixed 160px column +
    478450   non-shrinkable input together ~447px > screen width). */
    479 .profile-link-row {
    480   display: grid; grid-template-columns: 1fr auto; gap: 0.5rem; margin-bottom: 0.5rem;
    481 }
    482 .profile-link-row select { padding: 0.45rem 0.6rem; grid-column: 1 / -1; }
    483 .profile-link-row input { min-width: 0; }
    484 @media (min-width: 520px) {
    485   .profile-link-row { grid-template-columns: 160px 1fr auto; }
    486   .profile-link-row select { grid-column: auto; }
    487 }
    488 .profile-links-fieldset details > label { margin-top: 0.4rem; }
    489451</style>
  • src/views/partials/bottom-tab.ejs

    r31680c3 r746d98a  
    4848  </a>
    4949
    50   <!-- Zoek -->
     50  <!-- Zoek (niet in de wardmodus: daar is zoeken dicht voor een bezoeker) -->
     51  <% if (!(typeof wardProfile !== 'undefined' && wardProfile)) { %>
    5152  <button type="button" class="bottom-tab-item<%= _active === 'search' ? ' is-active' : '' %>"
    5253          id="bottom-tab-search-toggle" aria-label="<%= t('nav.search') %>">
    … …  
    5758    <span class="bottom-tab-label"><%= t('tab.search') %></span>
    5859  </button>
     60  <% } %>
    5961
    6062  <!-- Plus (FAB-style, only when user can post) -->
  • src/views/partials/chrome.ejs

    r31680c3 r746d98a  
    3434  <% if (!_headerless) { %>
    3535    <%- include('profile-header') %>
    36     <% if (!_isAdmin && (typeof site !== 'undefined' && site)) { %><%- include('view-switcher') %><% } %>
     36    <%# Niet in de wardmodus: een bezoeker ziet daar alleen het profiel, en er
     37        is geen stroom om tussen Lezen en Grid te wisselen. %>
     38    <% if (!_isAdmin && (typeof site !== 'undefined' && site) && !(typeof wardProfile !== 'undefined' && wardProfile)) { %><%- include('view-switcher') %><% } %>
    3739  <% } %>
    3840<% } %>
  • src/views/partials/profile-header.ejs

    r31680c3 r746d98a  
    1010// Show profile-header unless we're on an admin page or the site explicitly
    1111// disabled it via profile_enabled=0.
    12 const _showProfile = site
    13   && (site.profile_enabled === undefined || site.profile_enabled !== 0)
    14   && !(typeof bodyClass !== 'undefined' && bodyClass.indexOf('on-admin') >= 0);
     12// In de wardmodus IS de pagina het profiel (pages/ward-profile.ejs). Dan valt de
     13// kop hier weg, maar de volgdialoog hieronder blijft: de volgknop op die pagina
     14// opent hem.
     15const _wardPage = typeof wardProfile !== 'undefined' && wardProfile;
     16const _showProfile = site && (_wardPage || (
     17  (site.profile_enabled === undefined || site.profile_enabled !== 0)
     18  && !(typeof bodyClass !== 'undefined' && bodyClass.indexOf('on-admin') >= 0)));
    1519
    1620// profile_name and profile_bio used to be optional overrides exposed in the
    … …  
    2024const _displayName = (site && (site.profile_name || site.title)) || '';
    2125const _bioText     = (site && (site.profile_bio || site.description || site.tagline)) || '';
     26// Volgen via de fediverse. Hier boven en niet in de kop: op de wardpagina valt
     27// de kop weg, maar de volgdialoog onderaan hoort er dan nog te staan.
     28const _showFollow = (typeof canManageFedi === 'undefined' || !canManageFedi) && site && site.slug && (typeof apEnabled === 'undefined' || apEnabled);
    2229
    2330// Parse profile_links JSON column once.
    … …  
    3845
    3946<% if (_showProfile) { %>
     47<% if (!_wardPage) { %>
    4048<aside class="profile-header" aria-label="Site profile">
    4149  <div class="container profile-header-inner">
    … …  
    7482      <%# Follow-via-fediverse: a compact icon in the links row (keeps the header
    7583          short), and hidden for the owner — you don't follow yourself. %>
    76       <% var _showFollow = (typeof canManageFedi === 'undefined' || !canManageFedi) && site && site.slug && (typeof apEnabled === 'undefined' || apEnabled); %>
    7784      <% if (_profileLinks.length || _showFollow) { %>
    7885        <% const _platforms = (typeof platforms_catalog !== 'undefined' && platforms_catalog) || {}; %>
    … …  
    144151  </div>
    145152</div>
     153<% } /* !_wardPage */ %>
    146154<% if (_showFollow) { %>
    147155<%# Follow-via-fediverse modal — replaces the native prompt() (which looked ugly). %>
  • src/views/partials/topnav.ejs

    r31680c3 r746d98a  
    1616// Fediverse pages (on-special) get the same top-nav "back to site" treatment as admin.
    1717const _isSpecial   = typeof bodyClass !== 'undefined' && bodyClass.indexOf('on-special') >= 0;
    18 const _hasSearch   = !site || site.show_search === undefined || site.show_search;
     18// In de wardmodus is zoeken dicht voor een bezoeker: geen knop naar een deur op slot.
     19const _hasSearch   = (!site || site.show_search === undefined || site.show_search)
     20                     && !(typeof wardProfile !== 'undefined' && wardProfile);
    1921const _canPost     = user && (typeof canMutate === 'undefined' || canMutate)
    2022                     && permissions && permissions.canCreatePost && permissions.canCreatePost(user, site);
    … …  
    4749    <nav class="top-nav top-nav-desktop" aria-label="Hoofdnavigatie">
    4850
    49       <% if (!_isAdmin && !_isSpecial) { %>
     51      <%# Niet in de wardmodus: daar is het archief dicht voor een bezoeker. %>
     52      <% if (!_isAdmin && !_isSpecial && !(typeof wardProfile !== 'undefined' && wardProfile)) { %>
    5053        <a class="nav-link" href="<%= _siteUrlBase %>/archive"
    5154           hx-get="<%= _siteUrlBase %>/archive?partial=1" hx-target="#pcms-main"
  • src/views/shell.ejs

    r31680c3 r746d98a  
    130130// ontwikkeling een jaar gecachet, dus zonder bump bereikt een reparatie
    131131// iedereen behalve wie de fout al heeft.
    132 const MOD_V = 67;
     132const MOD_V = 68;
    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.