| 1 | // De chrome: alles wat om elke pagina heen staat (shaer-bqr).
|
|---|
| 2 | //
|
|---|
| 3 | // Dit stond als inline <script> in de partials. Dat kan niet blijven: de
|
|---|
| 4 | // CSP-nonce rouleert per verzoek, en de chrome wordt bij ELKE htmx-navigatie
|
|---|
| 5 | // out-of-band opnieuw ingevoegd. Zo'n script draagt dan een nonce die het
|
|---|
| 6 | // document niet kent en wordt geweigerd -- dus viel de chrome-JS bij de eerste
|
|---|
| 7 | // klik binnen de site al weg (shaer-0i6).
|
|---|
| 8 | //
|
|---|
| 9 | // Nu een module, geladen door de bootstrap in shell.ejs. Die zit in het
|
|---|
| 10 | // document zelf, heeft dus wel de goede nonce, en een dynamische import vanuit
|
|---|
| 11 | // een vertrouwd script is precies waar 'strict-dynamic' voor is.
|
|---|
| 12 | //
|
|---|
| 13 | // TWEE REGELS voor alles wat hier bij komt:
|
|---|
| 14 | //
|
|---|
| 15 | // GEDELEGEERD luister op document, nooit op een element dat er nu staat. De
|
|---|
| 16 | // chrome wordt vervangen, dus een vastgehouden verwijzing is na
|
|---|
| 17 | // een navigatie een verwijzing naar iets dat weg is.
|
|---|
| 18 | // IDEMPOTENT de module kan een tweede keer geladen worden. Een slot op
|
|---|
| 19 | // window voorkomt dat er een tweede stel luisteraars bij komt --
|
|---|
| 20 | // dat is hoe de themaknop ooit twee keer vuurde en dus niets deed.
|
|---|
| 21 | //
|
|---|
| 22 | // En: GEEN servergegevens in deze code. Interpolatie hoort niet in een statisch
|
|---|
| 23 | // bestand; wat de server wil meegeven komt via een data-attribuut op een element.
|
|---|
| 24 |
|
|---|
| 25 | (function () {
|
|---|
| 26 | 'use strict';
|
|---|
| 27 | if (window.__chromeMod) return;
|
|---|
| 28 | window.__chromeMod = true;
|
|---|
| 29 |
|
|---|
| 30 | // De zoekknop in de bottom-tab opent de overlay. De overlay zelf wordt bij een
|
|---|
| 31 | // htmx-navigatie vervangen, dus hem hier vasthouden zou na een klik binnen de
|
|---|
| 32 | // site niets meer opleveren: elke keer opnieuw opzoeken.
|
|---|
| 33 | document.addEventListener('click', function (e) {
|
|---|
| 34 | if (!e.target.closest || !e.target.closest('#bottom-tab-search-toggle')) return;
|
|---|
| 35 | var o = document.getElementById('search-overlay');
|
|---|
| 36 | if (o) {
|
|---|
| 37 | o.hidden = false;
|
|---|
| 38 | var i = o.querySelector('input');
|
|---|
| 39 | if (i) i.focus();
|
|---|
| 40 | }
|
|---|
| 41 | });
|
|---|
| 42 | })();
|
|---|
| 43 |
|
|---|
| 44 | // ── de profielkop ──────────────────────────────────────────────
|
|---|
| 45 | // Zat inline in partials/profile-header.ejs, en die partial zit IN de
|
|---|
| 46 | // OOB-chrome -- dus hij kwam bij elke navigatie opnieuw binnen.
|
|---|
| 47 | (function () {
|
|---|
| 48 | if (window.__pcmsFediFollowWired) return;
|
|---|
| 49 | window.__pcmsFediFollowWired = true;
|
|---|
| 50 | function modal() { return document.querySelector('.pf-follow'); }
|
|---|
| 51 | function closeM() { var m = modal(); if (m) m.classList.remove('is-open'); }
|
|---|
| 52 | function go() {
|
|---|
| 53 | var m = modal(); if (!m) return;
|
|---|
| 54 | var inp = m.querySelector('.pf-follow-input'), raw = inp ? inp.value : '';
|
|---|
| 55 | // Strip a full @user@host handle (and any scheme/path) down to the server host.
|
|---|
| 56 | var server = String(raw || '').trim().replace(/^@?[^@\s]*@/, '').replace(/^https?:\/\//i, '').replace(/\/.*$/, '').trim();
|
|---|
| 57 | if (!server) { if (inp) inp.focus(); return; }
|
|---|
| 58 | try { localStorage.setItem('pcmsFediServer', server); } catch (e) {}
|
|---|
| 59 | var actor = location.origin + '/ap/users/' + encodeURIComponent(m.getAttribute('data-actor-slug') || '');
|
|---|
| 60 | location.href = 'https://' + server + '/authorize_interaction?uri=' + encodeURIComponent(actor);
|
|---|
| 61 | }
|
|---|
| 62 | document.addEventListener('click', function (e) {
|
|---|
| 63 | var b = e.target.closest && e.target.closest('.profile-fedi-link');
|
|---|
| 64 | if (b) { e.preventDefault();
|
|---|
| 65 | var m = modal(); if (!m) return;
|
|---|
| 66 | m.setAttribute('data-actor-slug', b.getAttribute('data-fedi-actor-slug') || '');
|
|---|
| 67 | var inp = m.querySelector('.pf-follow-input');
|
|---|
| 68 | try { if (inp && !inp.value) inp.value = localStorage.getItem('pcmsFediServer') || ''; } catch (e2) {}
|
|---|
| 69 | m.classList.add('is-open');
|
|---|
| 70 | setTimeout(function () { if (inp) inp.focus(); }, 30);
|
|---|
| 71 | return;
|
|---|
| 72 | }
|
|---|
| 73 | if (e.target.closest && e.target.closest('.pf-follow-go')) { e.preventDefault(); go(); return; }
|
|---|
| 74 | if (e.target.closest && e.target.closest('.pf-follow-cancel')) { e.preventDefault(); closeM(); return; }
|
|---|
| 75 | var open = document.querySelector('.pf-follow.is-open');
|
|---|
| 76 | if (open && e.target === open) closeM(); /* click on backdrop */
|
|---|
| 77 | });
|
|---|
| 78 | document.addEventListener('keydown', function (e) {
|
|---|
| 79 | var m = document.querySelector('.pf-follow.is-open'); if (!m) return;
|
|---|
| 80 | if (e.key === 'Escape') { closeM(); }
|
|---|
| 81 | else if (e.key === 'Enter' && e.target.closest && e.target.closest('.pf-follow')) { e.preventDefault(); go(); }
|
|---|
| 82 | });
|
|---|
| 83 | })();
|
|---|
| 84 |
|
|---|
| 85 | /* Profile summary: click the avatar to open a modal with photo + details. */
|
|---|
| 86 | (function () {
|
|---|
| 87 | if (window.__pcmsLightboxWired) return; window.__pcmsLightboxWired = true;
|
|---|
| 88 | function close() { var m = document.querySelector('.pf-summary.is-open'); if (m) m.classList.remove('is-open'); }
|
|---|
| 89 | document.addEventListener('click', function (e) {
|
|---|
| 90 | if (e.target.closest && e.target.closest('.pf-summary-close')) { e.preventDefault(); close(); return; }
|
|---|
| 91 | var open = document.querySelector('.pf-summary.is-open');
|
|---|
| 92 | if (open && e.target === open) { close(); return; } /* click on backdrop */
|
|---|
| 93 | var t = e.target.closest && e.target.closest('.profile-photo[data-pf-summary]');
|
|---|
| 94 | if (!t) return;
|
|---|
| 95 | e.preventDefault();
|
|---|
| 96 | var root = (t.closest && t.closest('#pcms-chrome')) || document;
|
|---|
| 97 | var modal = root.querySelector('.pf-summary') || document.querySelector('.pf-summary');
|
|---|
| 98 | if (modal) modal.classList.add('is-open');
|
|---|
| 99 | });
|
|---|
| 100 | document.addEventListener('keydown', function (e) { if (e.key === 'Escape') close(); });
|
|---|
| 101 | })();
|
|---|