Changeset 1e172f3 in Klonkt for src/views


Ignore:
Timestamp:
08/07/2026 12:00:22 PM (5 weeks ago)
Author:
roboburr <roboburr@…>
Branches:
main
Children:
4c1e327
Parents:
ea138c0
git-author:
Robin <roboburr@…> (08/07/2026 12:00:20 PM)
git-committer:
roboburr <roboburr@…> (08/07/2026 12:00:22 PM)
Message:

Modules laden vanuit de shell in plaats van inline script (shaer-bqr, stap 1)

Het mechanisme uit optie C, met de bottom-tab als eerste geval zodat het ook
te bewijzen is.

WAAROM. De CSP-nonce rouleert per verzoek (shaer-0i6). Een script dat via htmx
binnenkomt draagt dus een nonce die het document niet kent en wordt geweigerd.
De chrome komt bij ELKE navigatie out-of-band opnieuw binnen, dus daar valt de
JS bij de eerste klik binnen de site al weg.

HOE. Een bootstrap in shell.ejs -- die komt alleen bij een volledige laadbeurt
binnen en heeft dus wel de goede nonce. Hij leest body[data-js], een lijst
modulenamen, en importeert ze uit /assets/js/mod/. Een dynamische import vanuit
een vertrouwd script is precies waar strict-dynamic voor bedoeld is, dus de
module zelf heeft geen nonce nodig.

Bij een htmx-navigatie zet de pcmsNav-trigger data-js opnieuw en haalt de
bootstrap op wat er nieuw bij staat. 'chrome' staat er altijd bij.

De naam wordt een PAD, dus hij moet door /[a-z0-9-]+$/ -- geen punt, geen
schuine streep.

EERSTE GEVAL: de zoekknop van de bottom-tab. Geen servergegevens erin, al
gedelegeerd, al voorzien van een slot -- dus de verhuizing verandert niets aan de
logica en het mechanisme is er echt mee te toetsen.

WAT DIT BLOOTLEGT VOOR DE VOLGENDE STAP: het topnav-script interpoleert
vertalingen (<%= t('search.section_posts') %>) en kan dus niet zomaar een
statisch bestand worden. Servergegevens horen via een data-attribuut naar een
module, niet via interpolatie in de code. Dat is een eigen stap en staat als
zodanig in mod/chrome.js opgeschreven.

Templates compileren, suite 551/551. Het echte bewijs is een klik BINNEN de site:
na een herlading werkt alles toch al.

Location:
src/views
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • src/views/partials/bottom-tab.ejs

    rea138c0 r1e172f3  
    302302</style>
    303303
    304 <script>
    305 // Bottom-tab search button → opens #search-overlay. Using event delegation + looking
    306 // up the overlay on EVERY click: the overlay lives in the chrome that is OOB-replaced
    307 // during htmx navigation, so a once-captured reference goes stale
    308 // (button did nothing afterwards). Guard against double-wiring.
    309 (function() {
    310   if (window.__btSearchWired) return;
    311   window.__btSearchWired = true;
    312   document.addEventListener('click', function(e) {
    313     if (!e.target.closest || !e.target.closest('#bottom-tab-search-toggle')) return;
    314     var o = document.getElementById('search-overlay');
    315     if (o) { o.hidden = false; var i = o.querySelector('input'); if (i) i.focus(); }
    316   });
    317 })();
    318 </script>
     304<%# De zoekknop zit nu in de chrome-module (assets/js/mod/chrome.js).
     305    Inline script hier wordt door de CSP geweigerd zodra deze partial via htmx
     306    binnenkomt, en dat is bij elke navigatie -- zie shaer-0i6. %>
  • src/views/shell.ejs

    rea138c0 r1e172f3  
    339339</head>
    340340
    341 <body class="<%= bodyClass || 'on-home' %> has-bottom-tab" data-feed-view="<%- _e(safeSite.feed_view_default || 'timeline') %>" data-grid-cols="3" data-site-base="<%- _e((typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '') %>">
     341<body class="<%= bodyClass || 'on-home' %> has-bottom-tab" data-js="chrome<%= (typeof pageJs !== 'undefined' && pageJs) ? ' ' + pageJs : '' %>" data-feed-view="<%- _e(safeSite.feed_view_default || 'timeline') %>" data-grid-cols="3" data-site-base="<%- _e((typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '') %>">
    342342
    343343<% if (typeof isViewer !== 'undefined' && isViewer) { %>
     
    449449    }
    450450
     451    // Welke modules deze pagina wil (shaer-bqr). De bootstrap hieronder leest
     452    // dit zodra deze handler klaar is.
     453    if (typeof d.js === 'string' && /^[a-z0-9 -]*$/.test(d.js)) {
     454      document.body.setAttribute('data-js', d.js);
     455    }
     456
    451457    var next = d.bodyClass;
    452458    if (!next) return;
     
    467473  //  de pcmsNav-trigger (accent/palette/bodyClass) correct toepast. De vroegere
    468474  //  htmx:historyRestore-handler is vervallen nu htmx-history uitstaat.)
     475})();
     476</script>
     477
     478<!-- Modules laden (shaer-bqr). Inline script in gewisselde inhoud wordt door de
     479     CSP geweigerd: de nonce rouleert per verzoek, dus een script dat via htmx
     480     binnenkomt draagt er een die dit document niet kent (shaer-0i6). Alles wat
     481     bij een pagina hoort komt daarom uit een module, en die wordt HIER geladen —
     482     vanuit de shell, die alleen bij een volledige laadbeurt binnenkomt en dus
     483     wél de goede nonce heeft.
     484
     485     Een dynamische import vanuit een vertrouwd (genonced) script is precies waar
     486     'strict-dynamic' voor bedoeld is, dus de module zelf heeft geen nonce nodig. -->
     487<script>
     488(function () {
     489  if (window.__modBoot) return;
     490  window.__modBoot = true;
     491
     492  var loaded = {};
     493  function load() {
     494    var names = (document.body.getAttribute('data-js') || '').trim().split(/\s+/);
     495    names.forEach(function (name) {
     496      // Streng: deze waarde komt uit een template en wordt een PAD. Alleen
     497      // kleine letters, cijfers en streepjes; nooit een punt of een schuine
     498      // streep.
     499      if (!name || loaded[name] || !/^[a-z0-9-]+$/.test(name)) return;
     500      loaded[name] = 1;
     501      import('/assets/js/mod/' + name + '.js').catch(function (e) {
     502        console.warn('[mod] ' + name + ' laadde niet:', e && e.message);
     503      });
     504    });
     505  }
     506  load();
     507  // Bij een htmx-navigatie wisselt de INHOUD, niet de body. De nav-trigger
     508  // hieronder zet data-js opnieuw; daarna halen we op wat er nieuw bij staat.
     509  // Een module die er al is wordt niet opnieuw geimporteerd -- vandaar dat elke
     510  // module gedelegeerd moet werken en tegen een tweede aanroep moet kunnen.
     511  document.body.addEventListener('pcmsNav', load);
    469512})();
    470513</script>
Note: See TracChangeset for help on using the changeset viewer.