Changeset 7cfe35a in Klonkt for src/assets/js/guardian.js


Ignore:
Timestamp:
08/07/2026 01:56:58 PM (5 weeks ago)
Author:
Robin <roboburr@…>
Branches:
main
Children:
ee78e03
Parents:
fb9a8ad (diff), a29c8c8 (diff)
Note: this is a merge changeset, the changes displayed below correspond to the merge itself.
Use the (diff) links above to see all the changes relative to each parent.
git-author:
proef <proef@…> (08/07/2026 01:54:50 PM)
git-committer:
Robin <roboburr@…> (08/07/2026 01:56:58 PM)
Message:

Geen inline scripts meer in de pagina's (shaer-bqr)

De CSP staat op default-src 'none' met strict-dynamic en een nonce per
verzoek. Bij een htmx-navigatie komt er nieuwe inhoud binnen zonder nieuwe
nonce, dus een inline script in geswapte inhoud werd geweigerd -- zichtbaar
als een geblokkeerde reply-editor op /messages.

Alle 24 pagina's en partials gaan nu naar een module onder assets/js/mod/.
Een bootstrap in de shell leest data-js van de body en importeert wat daar
staat, ook opnieuw na elke pagInawissel. Servergegevens gaan mee via
partials/page-data.ejs, een script met type application/json: dat wordt niet
uitgevoerd en niet opgehaald, dus script-src heeft er niets over te zeggen.

lib.js heeft esc(), omdat de modules op plekken HTML met stringplakwerk
bouwen waar de escapende EJS-tag vroeger het werk deed.

embed-player.ejs houdt zijn inline script: eigen render, eigen CSP, altijd
een volledige laadbeurt.

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

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/assets/js/guardian.js

    rfb9a8ad r7cfe35a  
    7272      card.appendChild(a);
    7373    }
     74    card.appendChild(helpState(h));
    7475    return card;
     76  }
     77
     78  /** Hoe lang geleden, grof. Een oppik vervalt niet maar hoort wel te verouderen. */
     79  function ago(ms) {
     80    var u = Math.floor(ms / 3600000);
     81    if (u < 1) return T.help_just_now || 'just now';
     82    if (u < 24) return (T.help_hours || '{n}h ago').replace('{n}', u);
     83    return (T.help_days || '{n}d ago').replace('{n}', Math.floor(u / 24));
     84  }
     85
     86  /**
     87   * De gedeelde staat van een hulpvraag (shaer-lgo): wie er al op af is, of het
     88   * is afgesloten, en de twee knoppen.
     89   *
     90   * De faalstand hier is NIET veilig: 'iedereen denkt dat het geregeld is' is
     91   * gevaarlijker dan geen markering. Dus bij twijfel toont dit OPEN, en een oude
     92   * oppik verkleurt in plaats van te verdwijnen -- anders ziet een hulpvraag er
     93   * onaangeroerd uit terwijl er iemand mee bezig is.
     94   */
     95  function helpState(h) {
     96    var st = h.state || { open: true, pickedUpBy: [], handled: null, ageMs: null };
     97    var box = el('div', 'g-help-state');
     98
     99    if (st.handled) {
     100      var wie = st.handled.handle || handleOf(st.handled.uri);
     101      box.appendChild(el('div', 'g-help-done', (T.help_handled_by || 'Handled by {who}').replace('{who}', wie)));
     102      // Geen terugdraaiknop: leeft de vraag nog, dan wordt hij opnieuw gesteld.
     103      box.appendChild(el('p', 'small g-empty', T.help_handled_note || ''));
     104      return box;
     105    }
     106
     107    (st.pickedUpBy || []).forEach(function (p) {
     108      var line = (T.help_picked_by || '{who} is looking into this').replace('{who}', p.handle || handleOf(p.uri));
     109      box.appendChild(el('div', 'g-help-pick', line));
     110    });
     111    // Oud, maar niet weg. Dit is het verschil tussen 'er is iemand mee bezig' en
     112    // 'er was ooit iemand mee bezig'.
     113    if (st.ageMs != null && st.ageMs > 3600000) {
     114      box.appendChild(el('div', 'g-help-age small', ago(st.ageMs)));
     115    }
     116
     117    var row = el('div', 'row');
     118    var mij = (st.pickedUpBy || []).some(function (p) { return p.uri === S.me; });
     119    if (!mij) {
     120      var pick = el('button', 'small', T.help_pick || 'I am on it');
     121      pick.addEventListener('click', function () { markHelp(h, 'pickup', pick); });
     122      row.appendChild(pick);
     123    }
     124    var done = el('button', 'quiet small', T.help_close || 'Mark handled');
     125    // Een stevige bevestiging, en nooit een window.confirm: dat verstopt een
     126    // uitleg achter een OK die mensen wegklikken. Zelfde lijn als het loslaten
     127    // van een ward.
     128    done.addEventListener('click', function () {
     129      done.hidden = true;
     130      var ask = el('div', 'g-confirm');
     131      ask.appendChild(el('p', 'small', T.help_close_ask || ''));
     132      var ja = el('button', 'small', T.help_close_yes || 'Yes, handled');
     133      ja.addEventListener('click', function () { markHelp(h, 'handled', ja); });
     134      var nee = el('button', 'quiet small', T.release_no || 'No');
     135      nee.addEventListener('click', function () { ask.remove(); done.hidden = false; });
     136      ask.appendChild(ja); ask.appendChild(nee);
     137      box.appendChild(ask);
     138    });
     139    row.appendChild(done);
     140    box.appendChild(row);
     141    return box;
     142  }
     143
     144  function markHelp(h, kind, btn) {
     145    if (btn) btn.disabled = true;
     146    fetch('/guardian/api/help/' + kind, {
     147      method: 'POST', headers: { 'Content-Type': 'application/json' },
     148      body: JSON.stringify({ note: h.object_uri, ward: h.actor_uri, site: S.site }),
     149    }).then(refresh).catch(function () { if (btn) btn.disabled = false; });
    75150  }
    76151
Note: See TracChangeset for help on using the changeset viewer.