Changes in src/assets/js/mod/chrome.js [156baa3:1e172f3] in Klonkt
- File:
-
- 1 edited
-
src/assets/js/mod/chrome.js (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
src/assets/js/mod/chrome.js
r156baa3 r1e172f3 41 41 }); 42 42 })(); 43 44 // ── de profielkop ──────────────────────────────────────────────45 // Zat inline in partials/profile-header.ejs, en die partial zit IN de46 // 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 })();102 103 // ── de topnav ──────────────────────────────────────────────────104 // Zat inline in partials/topnav.ejs. De zoekteksten komen nu van het105 // overlay-element (data-i18n) in plaats van uit interpolatie.106 (function() {107 // Wired ONCE. This chrome (topnav) is re-inserted out-of-band during htmx navigation108 // → without this guard the script would stack EXTRA listeners on every navigation,109 // causing the theme toggle to fire 2× (or more) = no net change ("toggle stops working").110 // Everything below uses event delegation on body/document, so it also works for111 // buttons that appear after this run (OOB).112 if (window.__pcmsChromeWired) return;113 window.__pcmsChromeWired = true;114 115 function toggleTheme() {116 var cur = document.documentElement.getAttribute('data-theme') || 'dark';117 var next = cur === 'dark' ? 'light' : 'dark';118 document.documentElement.setAttribute('data-theme', next);119 try { localStorage.setItem('pcms-theme', next); } catch (e) {}120 }121 function overlay() { return document.getElementById('search-overlay'); }122 function openSearch() { var o = overlay(); if (o) { o.hidden = false; var i = o.querySelector('input'); if (i) i.focus(); } }123 function closeSearch() { var o = overlay(); if (o) { o.hidden = true; var b = document.getElementById('search-suggest'); if (b) b.innerHTML = ''; } }124 125 document.body.addEventListener('click', function(e) {126 if (e.target.closest('#theme-toggle, #theme-toggle-mobile, #theme-toggle-footer')) { toggleTheme(); return; }127 if (e.target.closest('#search-toggle')) { openSearch(); return; }128 if (e.target.closest('#search-close')) { closeSearch(); return; }129 // Close open dropdowns (user menu + language picker) on click outside.130 document.querySelectorAll('.user-menu[open], .lang-menu[open]').forEach(function(d) {131 if (!d.contains(e.target)) d.removeAttribute('open');132 });133 });134 135 document.addEventListener('keydown', function(e) {136 if (e.key === 'Escape') { var o = overlay(); if (o && !o.hidden) closeSearch(); }137 });138 139 // ── Live results while typing ───────────────────────────────────140 // De teksten komen van de overlay zelf, elke keer opnieuw: bij een141 // htmx-navigatie wordt die vervangen en kan de taal gewisseld zijn.142 function _st() {143 var o = document.getElementById('search-overlay');144 try { return JSON.parse((o && o.getAttribute('data-i18n')) || '{}'); } catch (e) { return {}; }145 }146 function esc(s) { return String(s == null ? '' : s).replace(/[&<>"]/g, function(c){ return {'&':'&','<':'<','>':'>','"':'"'}[c]; }); }147 function renderSuggest(box, d, q, ov) {148 var html = '';149 function grp(title, items, fmt) {150 if (!items || !items.length) return;151 html += '<div class="ss-group"><div class="ss-title">' + esc(title) + '</div>' + items.map(fmt).join('') + '</div>';152 }153 grp(_st().posts, d.posts, function(p){ return '<a class="ss-item" href="' + esc(p.url) + '">' + esc(p.title) + '</a>'; });154 grp(_st().tracks, d.tracks, function(tk){ var sub = tk.artist ? ' <span class="ss-sub">' + esc(tk.artist) + '</span>' : ''; return tk.url ? '<a class="ss-item" href="' + esc(tk.url) + '">' + esc(tk.title) + sub + '</a>' : '<span class="ss-item ss-noclick">' + esc(tk.title) + sub + '</span>'; });155 grp(_st().events, d.events, function(ev){ return '<a class="ss-item" href="' + esc(ev.url) + '">' + esc(ev.where || ev.when) + ' <span class="ss-sub">' + esc(ev.when) + '</span></a>'; });156 grp(_st().pages, d.pages, function(pg){ return '<a class="ss-item" href="' + esc(pg.url) + '">' + esc(pg.label) + '</a>'; });157 var hasAny = (d.posts && d.posts.length) || (d.tracks && d.tracks.length) || (d.events && d.events.length) || (d.pages && d.pages.length);158 if (!hasAny) { box.innerHTML = '<div class="ss-empty">' + esc(_st().none) + '</div>'; return; }159 var action = (ov && ov.getAttribute('data-action')) || '/search';160 html += '<a class="ss-all" href="' + esc(action) + '?q=' + encodeURIComponent(q) + '">' + esc(_st().all) + '</a>';161 box.innerHTML = html;162 }163 var _sTimer;164 document.addEventListener('input', function(e) {165 var inp = e.target.closest && e.target.closest('#search-overlay input[name="q"]');166 if (!inp) return;167 var box = document.getElementById('search-suggest');168 if (!box) return;169 var q = inp.value.trim();170 clearTimeout(_sTimer);171 if (q.length < 2) { box.innerHTML = ''; return; }172 _sTimer = setTimeout(function() {173 var ov = document.getElementById('search-overlay');174 var url = (ov && ov.getAttribute('data-suggest')) || '/search/suggest';175 fetch(url + '?q=' + encodeURIComponent(q))176 .then(function(r){ return r.ok ? r.json() : null; })177 .then(function(d){ if (d) renderSuggest(box, d, q, ov); })178 .catch(function(){});179 }, 200);180 });181 // Click on a suggestion → close the overlay (the link/boost handles navigation).182 document.addEventListener('click', function(e) {183 if (e.target.closest && e.target.closest('#search-suggest a')) { var o = overlay(); if (o) o.hidden = true; }184 });185 })();186 187 // ── het profielblad ────────────────────────────────────────────188 // Zat inline in partials/profile-sheet.ejs, dat de shell opneemt.189 (function() {190 const sheet = document.getElementById('profile-sheet');191 if (!sheet) return;192 193 const backdrop = document.getElementById('profile-sheet-backdrop');194 const panel = sheet.querySelector('.profile-sheet-panel');195 const closeBtn = document.getElementById('profile-sheet-close');196 const dragZone = document.getElementById('profile-sheet-drag-zone');197 const themeBtn = document.getElementById('profile-sheet-theme');198 const themeLbl = document.getElementById('profile-sheet-theme-state');199 200 function openSheet() {201 sheet.classList.add('is-open');202 sheet.setAttribute('aria-hidden', 'false');203 document.body.classList.add('profile-sheet-locked');204 syncTheme();205 }206 function closeSheet() {207 sheet.classList.remove('is-open');208 sheet.setAttribute('aria-hidden', 'true');209 document.body.classList.remove('profile-sheet-locked');210 panel.style.removeProperty('--pcms-drag-y');211 }212 213 // Open: any element with [data-profile-sheet-toggle]214 document.addEventListener('click', function(e) {215 const trigger = e.target.closest('[data-profile-sheet-toggle]');216 if (trigger) {217 e.preventDefault();218 openSheet();219 }220 });221 222 // Close: backdrop tap, handle tap, ESC, or any [data-close-sheet] item223 if (backdrop) backdrop.addEventListener('click', closeSheet);224 if (closeBtn) closeBtn.addEventListener('click', closeSheet);225 document.addEventListener('keydown', (e) => {226 if (e.key === 'Escape' && sheet.classList.contains('is-open')) closeSheet();227 });228 229 // Menu items that navigate: close synchronously before nav fires230 sheet.querySelectorAll('[data-close-sheet]').forEach((el) => {231 el.addEventListener('click', closeSheet);232 });233 234 // Drag-down-to-close on touch.235 // Uses --pcms-drag-y custom property rather than overwriting the panel's236 // transform string. This composes correctly with any horizontal centering237 // (currently none here, but the pattern matches audio-sheet for safety).238 let startY = 0, lastY = 0, dragging = false;239 function onDown(e) {240 if (e.pointerType !== 'touch') return;241 if (panel.scrollTop > 0) return;242 startY = lastY = e.clientY;243 dragging = true;244 panel.classList.add('is-dragging');245 }246 function onMove(e) {247 if (!dragging) return;248 lastY = e.clientY;249 const dy = Math.max(0, lastY - startY);250 panel.style.setProperty('--pcms-drag-y', dy + 'px');251 }252 function onUp() {253 if (!dragging) return;254 dragging = false;255 panel.classList.remove('is-dragging');256 const dy = lastY - startY;257 if (dy > 80) closeSheet();258 else panel.style.removeProperty('--pcms-drag-y');259 }260 if (window.PointerEvent && dragZone) {261 dragZone.addEventListener('pointerdown', onDown);262 document.addEventListener('pointermove', onMove);263 document.addEventListener('pointerup', onUp);264 document.addEventListener('pointercancel', onUp);265 }266 267 // Theme toggle inside sheet — syncs label268 function syncTheme() {269 if (!themeLbl) return;270 const t = document.documentElement.getAttribute('data-theme') || 'dark';271 // De twee labels staan op het element zelf: een module kan geen vertaling272 // interpoleren, en zo hoort de tekst bij het ding dat hem toont.273 themeLbl.textContent = themeLbl.getAttribute(t === 'dark' ? 'data-dark' : 'data-light') || themeLbl.textContent;274 }275 if (themeBtn) {276 themeBtn.addEventListener('click', function() {277 const cur = document.documentElement.getAttribute('data-theme') || 'dark';278 const next = cur === 'dark' ? 'light' : 'dark';279 document.documentElement.setAttribute('data-theme', next);280 try { localStorage.setItem('pcms-theme', next); } catch (e) {}281 syncTheme();282 });283 }284 })();
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)