source: Klonkt/src/assets/js/mod/admin-push.js@ a8364ee

main
Last change on this file since a8364ee was a8364ee, checked in by Robin <roboburr@…>, 5 weeks ago

Alle element-bedrade modules herstarten per paginawissel (shaer-5s1, vervolg)

Het recept van 4b5db37, toegepast op de rest van de familie. Twaalf modules
exporteren nu init(): download (de auto-start liep alleen bij import, dus na
een htmx-bezoek startte er niets), auth-register, admin-settings, admin-epk,
admin-paid, admin-help, admin-push, admin-site-edit, authorize-interaction,
paid-gate, paid-passkey en news.

Twee verdienen een woord:

authorize-interaction blok 1 bedraadde een element achter een WINDOW-vlag:

na een swap is de knop een nieuw element en
blokkeerde de vlag het herbedraden voorgoed. De vlag
zit nu op het element zelf.

news de gif- en lees-meer-scanners claimden in hun

commentaar per render te draaien, maar deden dat als
module niet meer. Ze zijn nu benoemde functies:
init() draait ze per navigatie, en een gedelegeerde
htmx:afterSettle draait ze ook na feed-paginering --
binnengehaalde items kregen anders geen lees-meer en
geen gif-gedrag.

Bewust ONaangeraakt, met reden: chrome (gedelegeerd, en de profile-sheet
staat BUITEN de geswapte #pcms-chrome dus zijn element-referenties blijven
geldig), post en messages (gedelegeerd; messages herstart zichzelf al via
pcmsNav), track-editor en playlist-editor (gedelegeerd / globale functie).

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

  • Property mode set to 100644
File size: 5.4 KB
Line 
1// Pushmeldingen in beheer (pages/admin-push.ejs) -- verplaatst uit inline script, shaer-bqr.
2//
3// Inline script in een pagina wordt door de CSP geweigerd zodra je die pagina via
4// een link BINNEN de site opent: de nonce rouleert per verzoek (shaer-0i6). Dit
5// bestand wordt door de bootstrap in shell.ejs geladen en heeft dat probleem niet.
6//
7// Alles hier hoort GEDELEGEERD te luisteren (op document, niet op een element dat
8// er nu staat) en tegen een tweede aanroep te kunnen.
9
10// Element-bedrading leeft zo lang als de pagina; de bootstrap roept init()
11// aan bij elke paginawissel waarop deze module actief is (shaer-5s1).
12export function init() { run(); }
13
14function run() {
15(function () {
16 var cfg = JSON.parse(document.getElementById('np-data').textContent);
17 var elState = document.getElementById('np-state');
18 var btnOn = document.getElementById('np-on'), btnOff = document.getElementById('np-off'), btnTest = document.getElementById('np-test');
19 var alertsBox = document.getElementById('np-alerts'), savedMsg = document.getElementById('np-saved');
20 var currentEndpoint = null;
21
22 var isIos = /iPad|iPhone|iPod/.test(navigator.userAgent);
23 var standalone = window.matchMedia('(display-mode: standalone)').matches || window.navigator.standalone === true;
24 if (isIos && !standalone) document.getElementById('np-ios-hint').hidden = false;
25
26 if (!('serviceWorker' in navigator) || !('PushManager' in window) || !('Notification' in window)) {
27 document.getElementById('np-unsupported').hidden = false;
28 elState.textContent = cfg.i18n.unsupported;
29 return;
30 }
31
32 function b64ToU8(s) {
33 var pad = '='.repeat((4 - (s.length % 4)) % 4);
34 var raw = atob((s + pad).replace(/-/g, '+').replace(/_/g, '/'));
35 var out = new Uint8Array(raw.length);
36 for (var i = 0; i < raw.length; i++) out[i] = raw.charCodeAt(i);
37 return out;
38 }
39 function post(url, body) {
40 return fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body || {}) });
41 }
42 function deviceLabel() {
43 var ua = navigator.userAgent;
44 var browser = /Firefox\//.test(ua) ? 'Firefox' : /Edg\//.test(ua) ? 'Edge' : /Chrome\//.test(ua) ? 'Chrome' : /Safari\//.test(ua) ? 'Safari' : 'Browser';
45 var os = /Android/.test(ua) ? 'Android' : /iPad|iPhone|iPod/.test(ua) ? 'iOS' : /Mac/.test(ua) ? 'macOS' : /Win/.test(ua) ? 'Windows' : /Linux/.test(ua) ? 'Linux' : '';
46 return (browser + (os ? ' op ' + os : ''));
47 }
48 function readAlertBoxes() {
49 var out = {};
50 alertsBox.querySelectorAll('input[data-alert]').forEach(function (cb) { out[cb.getAttribute('data-alert')] = cb.checked ? 1 : 0; });
51 return out;
52 }
53 function setAlertBoxes(alerts) {
54 alertsBox.querySelectorAll('input[data-alert]').forEach(function (cb) {
55 cb.checked = !!alerts[cb.getAttribute('data-alert')];
56 });
57 }
58
59 function render(sub) {
60 currentEndpoint = sub ? sub.endpoint : null;
61 elState.textContent = sub ? cfg.i18n.on : (Notification.permission === 'denied' ? cfg.i18n.denied : cfg.i18n.off);
62 btnOn.hidden = !!sub || Notification.permission === 'denied';
63 btnOff.hidden = !sub;
64 btnTest.hidden = !sub;
65 alertsBox.hidden = !sub;
66 }
67
68 navigator.serviceWorker.ready.then(function (reg) {
69 return reg.pushManager.getSubscription();
70 }).then(function (sub) {
71 // Show this device's SAVED prefs when we know them, defaults otherwise.
72 setAlertBoxes((sub && cfg.saved[sub.endpoint]) ? Object.assign({}, cfg.alerts, cfg.saved[sub.endpoint]) : cfg.alerts);
73 render(sub);
74 }).catch(function () { elState.textContent = cfg.i18n.unknown; });
75
76 btnOn.addEventListener('click', function () {
77 btnOn.disabled = true;
78 Notification.requestPermission().then(function (perm) {
79 if (perm !== 'granted') { btnOn.disabled = false; render(null); return; }
80 navigator.serviceWorker.ready.then(function (reg) {
81 return reg.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: b64ToU8(cfg.vapid) });
82 }).then(function (sub) {
83 return post('/push/subscribe', { subscription: sub.toJSON(), alerts: readAlertBoxes(), uaLabel: deviceLabel() })
84 .then(function (r) { if (!r.ok) throw new Error('subscribe failed'); render(sub); location.reload(); });
85 }).catch(function () { btnOn.disabled = false; elState.textContent = cfg.i18n.failed; });
86 });
87 });
88
89 btnOff.addEventListener('click', function () {
90 navigator.serviceWorker.ready.then(function (reg) { return reg.pushManager.getSubscription(); }).then(function (sub) {
91 if (!sub) { render(null); return; }
92 var ep = sub.endpoint;
93 sub.unsubscribe().then(function () { return post('/push/unsubscribe', { endpoint: ep }); })
94 .then(function () { location.reload(); });
95 });
96 });
97
98 btnTest.addEventListener('click', function () {
99 btnTest.disabled = true;
100 post('/push/test').then(function () { setTimeout(function () { btnTest.disabled = false; }, 2000); });
101 });
102
103 alertsBox.addEventListener('change', function () {
104 if (!currentEndpoint) return;
105 post('/push/alerts', { endpoint: currentEndpoint, alerts: readAlertBoxes() }).then(function (r) {
106 if (r.ok) { savedMsg.hidden = false; setTimeout(function () { savedMsg.hidden = true; }, 1500); }
107 });
108 });
109
110 document.querySelectorAll('.np-remove').forEach(function (btn) {
111 btn.addEventListener('click', function () {
112 post('/push/unsubscribe', { endpoint: btn.getAttribute('data-endpoint') }).then(function () { location.reload(); });
113 });
114 });
115})();
116}
Note: See TracBrowser for help on using the repository browser.