Changeset 0475b13 in Klonkt


Ignore:
Timestamp:
08/07/2026 01:38:51 PM (5 weeks ago)
Author:
roboburr <roboburr@…>
Branches:
main
Children:
54ee51c
Parents:
792af53
git-author:
Robin <roboburr@…> (08/07/2026 01:04:55 PM)
git-committer:
roboburr <roboburr@…> (08/07/2026 01:38:51 PM)
Message:

Acht schone pagina's uit inline script naar modules (shaer-bqr, stap 3b)

Alle acht zonder EJS-interpolatie, dus ongewijzigd te verplaatsen: admin-help,
admin-epk, admin-paid, admin-settings, auth-register, admin-push, admin-site-edit
(2 blokken) en authorize-interaction (3 blokken).

Elke route zegt nu welke module hij wil via pageJs; de bootstrap in de shell
haalt hem op. Meerdere blokken uit een pagina belanden in EEN module, gescheiden
door een streep -- ze deelden toch al een pagina en een levensduur.

Templates compileren, modules syntactisch ok, suite 565/565. Het echte bewijs
per pagina is er via een LINK naartoe gaan; na een herlading werkt alles toch al.

Commit met expliciete paden, niet met -A: eerder vandaag veegde ik daarmee
andermans werk uit dezelfde werkmap mee.

Location:
src
Files:
8 added
16 edited

Legend:

Unmodified
Added
Removed
  • src/routes/admin-epk.js

    r792af53 r0475b13  
    3333  try { const r = JSON.parse(getSetting('epk_tracks_' + site.id, '') || '[]'); if (Array.isArray(r)) chosen = r; } catch (e) {}
    3434  renderPage(req, res, 'pages/admin-epk', {
     35    pageJs: 'admin-epk',
    3536    pageTitleKey: 'admin.t_epk',
    3637    bodyClass: 'on-admin',
  • src/routes/admin-paid.js

    r792af53 r0475b13  
    3636  if (!gate(req, res)) return;
    3737  renderPage(req, res, 'pages/admin-paid', {
     38    pageJs: 'admin-paid',
    3839    pageTitleKey: 'apaid.t',
    3940    bodyClass: 'on-admin',
  • src/routes/admin-push.js

    r792af53 r0475b13  
    1212router.get('/', requireSiteManager, async (req, res) => {
    1313  renderPage(req, res, 'pages/admin-push', {
     14    pageJs: 'admin-push',
    1415    pageTitleKey: 'push.t',
    1516    bodyClass: 'on-admin',
  • src/routes/admin-settings.js

    r792af53 r0475b13  
    6666router.get('/', requireGod, (req, res) => {
    6767  renderPage(req, res, 'pages/admin-settings', {
     68    pageJs: 'admin-settings',
    6869    pageTitleKey: 'admin.t_settings',
    6970    bodyClass: 'on-admin',
  • src/routes/admin-sites.js

    r792af53 r0475b13  
    200200router.get('/new', requireGod, (req, res) => {
    201201  renderPage(req, res, 'pages/admin-site-edit', {
     202    pageJs: 'admin-site-edit',
    202203    pageTitleKey: 'admin.t_newsite',
    203204    bodyClass: 'on-admin',
     
    283284
    284285  renderPage(req, res, 'pages/admin-site-edit', {
     286    pageJs: 'admin-site-edit',
    285287    pageTitleKey: 'admin.t_editsite', pageTitleVars: { title: site.title },
    286288    bodyClass: 'on-admin',
  • src/routes/admin.js

    r792af53 r0475b13  
    8787router.get('/handleiding', requireAuth, (req, res) => {
    8888  renderPage(req, res, 'pages/admin-help', {
     89    pageJs: 'admin-help',
    8990    pageTitleKey: 'admin.t_manual',
    9091    bodyClass: 'on-admin',
  • src/routes/auth.js

    r792af53 r0475b13  
    115115  if (!isSetupMode()) return res.redirect('/auth/login' + (next ? '?next=' + encodeURIComponent(next) : ''));
    116116  renderPage(req, res, 'pages/auth-register', {
     117    pageJs: 'auth-register',
    117118    pageTitle: t(resolveLang(req), 'setup.title'), bodyClass: 'on-special',
    118119    error: null, username: '', email: '', siteName: '', next,
     
    124125  const next = safeNext(req.body.next) || '';
    125126  const renderErr = (error) => renderPage(req, res, 'pages/auth-register', {
     127    pageJs: 'auth-register',
    126128    pageTitle: t(resolveLang(req), 'setup.title'), bodyClass: 'on-special',
    127129    error, username: username || '', email: email || '', siteName: siteName || '', next,
  • src/routes/posts.js

    r792af53 r0475b13  
    795795  }
    796796  renderPage(req, res, 'pages/authorize-interaction', {
     797    pageJs: 'authorize-interaction',
    797798    pageTitleKey: 'fedi.remote_interact', // i18n: was hardcoded Dutch on non-NL sites
    798799    bodyClass: 'on-special',
  • src/views/pages/admin-epk.ejs

    r792af53 r0475b13  
    9090  .admin-page .epk-pick-item input:disabled ~ .epk-pick-title { opacity: 0.45; }
    9191</style>
    92 <script>
    93 (function () {
    94   var box = document.querySelector('.epk-track-pick');
    95   if (!box) return;
    96   var max = parseInt(box.dataset.max, 10) || 5;
    97   function sync() {
    98     var checks = box.querySelectorAll('input[type=checkbox]');
    99     var n = box.querySelectorAll('input[type=checkbox]:checked').length;
    100     checks.forEach(function (c) { c.disabled = (!c.checked && n >= max); });
    101   }
    102   box.addEventListener('change', sync);
    103   sync();
    104 })();
    105 </script>
     92<%# Het script van deze pagina staat in assets/js/mod/admin-epk.js (shaer-bqr). %>
  • src/views/pages/admin-help.ejs

    r792af53 r0475b13  
    145145  .hb-empty { color: var(--ink-muted, var(--ink-soft)); padding: 1rem; text-align: center; }
    146146</style>
    147 <script>
    148 (function () {
    149   var q = document.getElementById('hb-q');
    150   var items = [].slice.call(document.querySelectorAll('.hb-item'));
    151   var empty = document.getElementById('hb-empty');
    152   var count = document.getElementById('hb-count');
    153   if (!q) return;
    154   // Bewaar de originele tekst per item (voor highlight-reset).
    155   items.forEach(function (it) { it._txt = it.textContent.toLowerCase(); });
    156   function run() {
    157     var term = q.value.trim().toLowerCase();
    158     var shown = 0;
    159     items.forEach(function (it) {
    160       var hit = !term || it._txt.indexOf(term) >= 0;
    161       it.classList.toggle('hb-hidden', !hit);
    162       if (hit) shown++;
    163     });
    164     empty.hidden = shown !== 0;
    165     count.textContent = term ? (shown + ' onderwerp' + (shown === 1 ? '' : 'en') + ' gevonden') : '';
    166   }
    167   q.addEventListener('input', run);
    168 })();
    169 </script>
     147<%# Het script van deze pagina staat in assets/js/mod/admin-help.js (shaer-bqr). %>
  • src/views/pages/admin-paid.ejs

    r792af53 r0475b13  
    7272</div>
    7373
    74 <script>
    75 (function () {
    76   var field = document.getElementById('pd-redirect');
    77   var btn = document.getElementById('pd-copy');
    78   if (!field || !btn) return;
    79   field.addEventListener('focus', function () { field.select(); });
    80   btn.addEventListener('click', function () {
    81     field.select();
    82     var done = function () { var t = btn.textContent; btn.textContent = document.getElementById('pd-copy').getAttribute('data-copied'); setTimeout(function () { btn.textContent = t; }, 1400); };
    83     if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(field.value).then(done, function () { try { document.execCommand('copy'); done(); } catch (e) {} }); }
    84     else { try { document.execCommand('copy'); done(); } catch (e) {} }
    85   });
    86 })();
    87 </script>
     74<%# Het script van deze pagina staat in assets/js/mod/admin-paid.js (shaer-bqr). %>
  • src/views/pages/admin-push.ejs

    r792af53 r0475b13  
    5858  i18n: { on: t('push.state_on'), off: t('push.state_off'), denied: t('push.state_denied'), unknown: t('push.state_unknown'), unsupported: t('push.state_unsupported'), failed: t('push.enable_failed') },
    5959}).replace(/</g, '\\u003c') %></script>
    60 <script>
    61 (function () {
    62   var cfg = JSON.parse(document.getElementById('np-data').textContent);
    63   var elState = document.getElementById('np-state');
    64   var btnOn = document.getElementById('np-on'), btnOff = document.getElementById('np-off'), btnTest = document.getElementById('np-test');
    65   var alertsBox = document.getElementById('np-alerts'), savedMsg = document.getElementById('np-saved');
    66   var currentEndpoint = null;
    67 
    68   var isIos = /iPad|iPhone|iPod/.test(navigator.userAgent);
    69   var standalone = window.matchMedia('(display-mode: standalone)').matches || window.navigator.standalone === true;
    70   if (isIos && !standalone) document.getElementById('np-ios-hint').hidden = false;
    71 
    72   if (!('serviceWorker' in navigator) || !('PushManager' in window) || !('Notification' in window)) {
    73     document.getElementById('np-unsupported').hidden = false;
    74     elState.textContent = cfg.i18n.unsupported;
    75     return;
    76   }
    77 
    78   function b64ToU8(s) {
    79     var pad = '='.repeat((4 - (s.length % 4)) % 4);
    80     var raw = atob((s + pad).replace(/-/g, '+').replace(/_/g, '/'));
    81     var out = new Uint8Array(raw.length);
    82     for (var i = 0; i < raw.length; i++) out[i] = raw.charCodeAt(i);
    83     return out;
    84   }
    85   function post(url, body) {
    86     return fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body || {}) });
    87   }
    88   function deviceLabel() {
    89     var ua = navigator.userAgent;
    90     var browser = /Firefox\//.test(ua) ? 'Firefox' : /Edg\//.test(ua) ? 'Edge' : /Chrome\//.test(ua) ? 'Chrome' : /Safari\//.test(ua) ? 'Safari' : 'Browser';
    91     var os = /Android/.test(ua) ? 'Android' : /iPad|iPhone|iPod/.test(ua) ? 'iOS' : /Mac/.test(ua) ? 'macOS' : /Win/.test(ua) ? 'Windows' : /Linux/.test(ua) ? 'Linux' : '';
    92     return (browser + (os ? ' op ' + os : ''));
    93   }
    94   function readAlertBoxes() {
    95     var out = {};
    96     alertsBox.querySelectorAll('input[data-alert]').forEach(function (cb) { out[cb.getAttribute('data-alert')] = cb.checked ? 1 : 0; });
    97     return out;
    98   }
    99   function setAlertBoxes(alerts) {
    100     alertsBox.querySelectorAll('input[data-alert]').forEach(function (cb) {
    101       cb.checked = !!alerts[cb.getAttribute('data-alert')];
    102     });
    103   }
    104 
    105   function render(sub) {
    106     currentEndpoint = sub ? sub.endpoint : null;
    107     elState.textContent = sub ? cfg.i18n.on : (Notification.permission === 'denied' ? cfg.i18n.denied : cfg.i18n.off);
    108     btnOn.hidden = !!sub || Notification.permission === 'denied';
    109     btnOff.hidden = !sub;
    110     btnTest.hidden = !sub;
    111     alertsBox.hidden = !sub;
    112   }
    113 
    114   navigator.serviceWorker.ready.then(function (reg) {
    115     return reg.pushManager.getSubscription();
    116   }).then(function (sub) {
    117     // Show this device's SAVED prefs when we know them, defaults otherwise.
    118     setAlertBoxes((sub && cfg.saved[sub.endpoint]) ? Object.assign({}, cfg.alerts, cfg.saved[sub.endpoint]) : cfg.alerts);
    119     render(sub);
    120   }).catch(function () { elState.textContent = cfg.i18n.unknown; });
    121 
    122   btnOn.addEventListener('click', function () {
    123     btnOn.disabled = true;
    124     Notification.requestPermission().then(function (perm) {
    125       if (perm !== 'granted') { btnOn.disabled = false; render(null); return; }
    126       navigator.serviceWorker.ready.then(function (reg) {
    127         return reg.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: b64ToU8(cfg.vapid) });
    128       }).then(function (sub) {
    129         return post('/push/subscribe', { subscription: sub.toJSON(), alerts: readAlertBoxes(), uaLabel: deviceLabel() })
    130           .then(function (r) { if (!r.ok) throw new Error('subscribe failed'); render(sub); location.reload(); });
    131       }).catch(function () { btnOn.disabled = false; elState.textContent = cfg.i18n.failed; });
    132     });
    133   });
    134 
    135   btnOff.addEventListener('click', function () {
    136     navigator.serviceWorker.ready.then(function (reg) { return reg.pushManager.getSubscription(); }).then(function (sub) {
    137       if (!sub) { render(null); return; }
    138       var ep = sub.endpoint;
    139       sub.unsubscribe().then(function () { return post('/push/unsubscribe', { endpoint: ep }); })
    140         .then(function () { location.reload(); });
    141     });
    142   });
    143 
    144   btnTest.addEventListener('click', function () {
    145     btnTest.disabled = true;
    146     post('/push/test').then(function () { setTimeout(function () { btnTest.disabled = false; }, 2000); });
    147   });
    148 
    149   alertsBox.addEventListener('change', function () {
    150     if (!currentEndpoint) return;
    151     post('/push/alerts', { endpoint: currentEndpoint, alerts: readAlertBoxes() }).then(function (r) {
    152       if (r.ok) { savedMsg.hidden = false; setTimeout(function () { savedMsg.hidden = true; }, 1500); }
    153     });
    154   });
    155 
    156   document.querySelectorAll('.np-remove').forEach(function (btn) {
    157     btn.addEventListener('click', function () {
    158       post('/push/unsubscribe', { endpoint: btn.getAttribute('data-endpoint') }).then(function () { location.reload(); });
    159     });
    160   });
    161 })();
    162 </script>
     60<%# Het script van deze pagina staat in assets/js/mod/admin-push.js (shaer-bqr). %>
    16361<% } %>
  • src/views/pages/admin-settings.ejs

    r792af53 r0475b13  
    2727      <div class="set-actions"><button type="submit" class="btn btn-primary"><%= t('aset.save') %></button></div>
    2828    </form>
    29     <script>
    30     (function () {
    31       var f = document.getElementById('mode-form');
    32       if (!f || f.__age18wired) return; f.__age18wired = true;
    33       f.addEventListener('submit', function (e) {
    34         var sel = f.querySelector('input[name=mode]:checked');
    35         if (sel && sel.value === 'cirkels' && f.getAttribute('data-was-cirkels') === '0') {
    36           if (!window.confirm(f.getAttribute('data-confirm'))) e.preventDefault();
    37         }
    38       });
    39     })();
    40     </script>
     29    <%# Het script van deze pagina staat in assets/js/mod/admin-settings.js (shaer-bqr). %>
    4130  </section>
    4231
  • src/views/pages/admin-site-edit.ejs

    r792af53 r0475b13  
    126126    <%# Live preview: selecting an accent / theme / palette re-themes this page
    127127        instantly (reverts on reload; persists on save). %>
    128     <script>
    129     (function(){
    130       if (window.__themePreviewWired) return; window.__themePreviewWired = true;
    131       var html = document.documentElement;
    132       function applyAccent(hex){
    133         if(!/^#[0-9a-fA-F]{6}$/.test(hex)) return;
    134         var sa = document.getElementById('pcms-site-accent');
    135         if(!sa){ sa = document.createElement('style'); sa.id = 'pcms-site-accent'; document.head.appendChild(sa); }
    136         sa.textContent = ':root,[data-palette]{--accent:'+hex+';--accent-soft:color-mix(in srgb,'+hex+' 80%,white);--accent-tint:color-mix(in srgb,'+hex+' 12%,transparent);}';
    137       }
    138       document.addEventListener('change', function(e){
    139         var t = e.target; if(!t || !t.name) return;
    140         if(t.name === 'palette'){ html.setAttribute('data-palette', t.value); }
    141         else if(t.name === 'accent'){ applyAccent(t.value); }
    142         else if(t.name === 'theme_override'){
    143           if(t.value === 'light' || t.value === 'dark'){ html.setAttribute('data-theme', t.value); }
    144           else { var dk = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; html.setAttribute('data-theme', dk ? 'dark' : 'light'); }
    145         }
    146       });
    147     })();
    148     </script>
     128    <%# Het script van deze pagina staat in assets/js/mod/admin-site-edit.js (shaer-bqr). %>
    149129
    150130    <fieldset>
     
    275255</div>
    276256
    277 <script>
    278 (function() {
    279   // Slug URL field: show the real host as a dimmed prefix (the slug itself is
    280   // coloured via CSS). Hub → host/user/<slug>, otherwise host/<slug>.
    281   var sh = document.getElementById('slug-host');
    282   if (sh) sh.textContent = location.host + (sh.dataset.prefix || '/');
    283 })();
    284 (function() {
    285   // Profile-links repeater: add row from <template>, remove on click.
    286   var rows = document.getElementById('profile-links-rows');
    287   var tpl  = document.getElementById('profile-link-template');
    288   var add  = document.getElementById('profile-link-add');
    289   if (!rows || !tpl || !add) return;
    290 
    291   add.addEventListener('click', function() {
    292     var clone = tpl.content.cloneNode(true);
    293     rows.appendChild(clone);
    294   });
    295   rows.addEventListener('click', function(e) {
    296     if (e.target && e.target.classList.contains('pl-remove')) {
    297       var row = e.target.closest('.profile-link-row');
    298       if (row) row.remove();
    299     }
    300   });
    301 })();
    302 
    303 // P63 — Profile photo picker: upload via /admin/sites/upload-photo,
    304 // then write the returned URL into the visible input. Live thumb preview.
    305 (function() {
    306   var picker  = document.getElementById('photo-picker');
    307   if (!picker) return;
    308   var thumb   = document.getElementById('photo-thumb');
    309   var preview = document.getElementById('photo-preview');
    310   var urlEl   = document.getElementById('photo-url');
    311   var trigger = document.getElementById('photo-upload-trigger');
    312   var clear   = document.getElementById('photo-clear');
    313   var field   = document.getElementById('photo-upload-field');
    314   var status  = document.getElementById('photo-status');
    315 
    316   function showPreview(url) {
    317     if (url) {
    318       preview.src = url;
    319       preview.hidden = false;
    320       thumb.removeAttribute('data-empty');
    321       clear.hidden = false;
    322     } else {
    323       preview.src = '';
    324       preview.hidden = true;
    325       thumb.setAttribute('data-empty', '');
    326       clear.hidden = true;
    327     }
    328   }
    329 
    330   if (urlEl) {
    331     urlEl.addEventListener('input', function() {
    332       showPreview(urlEl.value.trim());
    333     });
    334   }
    335 
    336   if (trigger && field) {
    337     trigger.addEventListener('click', function() { field.click(); });
    338     field.addEventListener('change', async function() {
    339       var file = field.files && field.files[0];
    340       if (!file) return;
    341       status.classList.remove('is-error');
    342       status.textContent = 'Uploaden…';
    343       try {
    344         var fd = new FormData();
    345         fd.append('photo', file);
    346         var r = await fetch('/admin/sites/upload-photo', {
    347           method: 'POST',
    348           body: fd,
    349           credentials: 'same-origin',
    350         });
    351         var j = await r.json();
    352         if (!r.ok || !j.ok) throw new Error(j.error || ('Upload mislukt (' + r.status + ')'));
    353         urlEl.value = j.url;
    354         showPreview(j.url);
    355         status.textContent = 'Geüpload ✓';
    356         setTimeout(function() { status.textContent = ''; }, 2000);
    357       } catch (e) {
    358         status.classList.add('is-error');
    359         status.textContent = 'Mislukt: ' + e.message;
    360       } finally {
    361         field.value = '';
    362       }
    363     });
    364   }
    365 
    366   if (clear) {
    367     clear.addEventListener('click', function() {
    368       urlEl.value = '';
    369       showPreview('');
    370       status.textContent = '';
    371       // Note: doesn't delete the file from disk — saving the form with empty
    372       // URL leaves the file orphaned on the server. Acceptable for now.
    373     });
    374   }
    375 })();
    376 </script>
     257
    377258
    378259<style>
  • src/views/pages/auth-register.ejs

    r792af53 r0475b13  
    4141  .setup-handle-hint code { color: var(--accent); font-weight: 600; background: color-mix(in srgb, var(--accent) 12%, transparent); padding: .05rem .35rem; border-radius: 6px; white-space: nowrap; }
    4242</style>
    43 <script>
    44 (function () {
    45   var u = document.getElementById('setup-username'), out = document.getElementById('setup-handle');
    46   if (!u || !out) return;
    47   function upd() {
    48     var v = (u.value || '').toLowerCase().replace(/[^a-z0-9_-]/g, '') || 'username';
    49     out.textContent = '@' + v + '@' + location.host;
    50   }
    51   u.addEventListener('input', upd);
    52   upd();
    53 })();
    54 </script>
     43<%# Het script van deze pagina staat in assets/js/mod/auth-register.js (shaer-bqr). %>
  • src/views/pages/authorize-interaction.ejs

    r792af53 r0475b13  
    1212      <p class="fedi-bm-help"><%= t('fedi.bm_help') %></p>
    1313    </div>
    14     <script>
    15     (function () {
    16       if (window.__fediBmWired) return; window.__fediBmWired = true;
    17       var a = document.getElementById('fedi-bm-btn'); if (!a) return;
    18       a.setAttribute('href', "javascript:void(window.open('" + location.origin + "/authorize_interaction?uri='+encodeURIComponent(window.location.href)))");
    19       a.addEventListener('click', function (e) { e.preventDefault(); a.classList.add('nudge'); setTimeout(function(){ a.classList.remove('nudge'); }, 600); });
    20     })();
    21     </script>
     14    <%# Het script van deze pagina staat in assets/js/mod/authorize-interaction.js (shaer-bqr). %>
    2215    <% if (!manage.length) { %>
    2316      <p class="auth-interact-note"><%= t('fedi.manage_empty') %></p>
     
    5649        <% }); %>
    5750      </ul>
    58       <script>
    59       (function () {
    60         if (window.__fediEditWired) return; window.__fediEditWired = true;
    61         document.addEventListener('click', function (e) {
    62           var b = e.target.closest && e.target.closest('.fedi-edit-btn');
    63           if (!b) return;
    64           var li = b.closest('.fedi-manage-item'); if (!li) return;
    65           var form = li.querySelector('.fedi-edit-form'); if (!form) return;
    66           var open = form.classList.toggle('is-open');
    67           b.classList.toggle('is-open', open);
    68           b.setAttribute('aria-expanded', open ? 'true' : 'false');
    69           if (open) { var ed = form.querySelector('.re-editor') || form.querySelector('textarea'); if (ed) ed.focus(); }
    70         });
    71       })();
    72       </script>
     51     
    7352    <% } %>
    7453    <p class="auth-interact-note"><a href="/"><%= t('fedi.remote_back') %></a></p>
     
    331310</style>
    332311
    333 <script>
    334 /* Like/Boost toggle in place — POST via fetch, flip the button, stay on the page. */
    335 (function(){
    336   if (window.__fediReactWired) return; window.__fediReactWired = true;
    337   document.addEventListener('submit', function(e){
    338     var f = e.target.closest && e.target.closest('.fedi-react-form');
    339     if (!f) return;
    340     e.preventDefault();
    341     var btn = f.querySelector('button'); if (!btn || btn.disabled) return;
    342     btn.disabled = true;
    343     var body = new URLSearchParams();
    344     new FormData(f).forEach(function(v, k){ body.append(k, v); });
    345     fetch(f.action, { method: 'POST', body: body, headers: { 'X-Requested-With': 'fetch' }, credentials: 'same-origin' })
    346       .then(function(r){ return r.ok ? r.json() : null; })
    347       .then(function(j){
    348         if (j) {
    349           var on = !!j.on;
    350           btn.classList.toggle('is-on', on);
    351           var lbl = btn.querySelector('.fedi-bigact-label');
    352           if (lbl) lbl.textContent = on ? (btn.getAttribute('data-on') || lbl.textContent) : (btn.getAttribute('data-off') || lbl.textContent);
    353         }
    354       })
    355       .catch(function(){})
    356       .then(function(){ btn.disabled = false; });
    357   });
    358 })();
    359 </script>
     312
Note: See TracChangeset for help on using the changeset viewer.