Changeset 156baa3 in Klonkt for src/views/pages
- Timestamp:
- 08/07/2026 01:38:51 PM (5 weeks ago)
- Branches:
- main
- Children:
- 6ee289a
- Parents:
- 52fc278
- git-author:
- Robin <roboburr@…> (08/07/2026 01:26:06 PM)
- git-committer:
- roboburr <roboburr@…> (08/07/2026 01:38:51 PM)
- Location:
- src/views/pages
- Files:
-
- 5 edited
-
admin-media.ejs (modified) (1 diff)
-
admin-playlists.ejs (modified) (1 diff)
-
admin-videos.ejs (modified) (1 diff)
-
paid-gate.ejs (modified) (2 diffs)
-
paid-passkey.ejs (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/views/pages/admin-media.ejs
r52fc278 r156baa3 41 41 </div> 42 42 43 <script> 44 (function () { 45 if (window.__mediaWired) return; window.__mediaWired = true; 46 var T = <%- JSON.stringify({ copy: t('admin.media_copy'), delC: t('admin.media_del_confirm'), cleanC: t('admin.media_cleanup_confirm') }) %>; 47 document.addEventListener('click', function (e) { 48 var c = e.target.closest('[data-copy]'); 49 if (c) { 50 var u = location.origin + c.getAttribute('data-copy'); 51 var done = function () { var o = c.textContent; c.textContent = '✓'; setTimeout(function () { c.textContent = o === '✓' ? T.copy : o; }, 1200); }; 52 if (navigator.clipboard) navigator.clipboard.writeText(u).then(done).catch(function () { window.prompt('URL', u); }); 53 else window.prompt('URL', u); 54 return; 55 } 56 var d = e.target.closest('[data-del]'); 57 if (d) { 58 if (!window.confirm(T.delC)) return; 59 fetch('/admin/media/delete', { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ file: d.getAttribute('data-del') }) }) 60 .then(function (r) { return r.json(); }) 61 .then(function (j) { if (j && j.ok) { var card = d.closest('.media-card'); if (card) card.remove(); } else window.alert((j && j.error) || 'Error'); }) 62 .catch(function () { window.alert('Error'); }); 63 return; 64 } 65 if (e.target.closest('#media-cleanup')) { 66 if (!window.confirm(T.cleanC)) return; 67 fetch('/admin/media/cleanup', { method: 'POST', credentials: 'same-origin' }) 68 .then(function (r) { return r.json(); }) 69 .then(function (j) { location.href = '/admin/media?success=' + encodeURIComponent(((j && j.removed) || 0) + ' file(s) removed'); }) 70 .catch(function () { window.alert('Error'); }); 71 } 72 }); 73 })(); 74 </script> 43 <%# Het script staat in assets/js/mod/admin-media.js; de gegevens via partials/page-data.ejs (shaer-bqr). %> 44 <%- include('../partials/page-data', { pageData: { copy: t('admin.media_copy'), delC: t('admin.media_del_confirm'), cleanC: t('admin.media_cleanup_confirm') } }) %> -
src/views/pages/admin-playlists.ejs
r52fc278 r156baa3 222 222 </style> 223 223 224 <script> 225 (function() { 226 const csrf = '<%= _csrf %>'; 227 228 document.getElementById('pl-new-btn')?.addEventListener('click', () => { 229 if (typeof window.openPlaylistEditor === 'function') { 230 window.openPlaylistEditor({ mode: 'create', onSaved: () => location.reload() }); 231 } 232 }); 233 234 // Click-to-copy on shortcodes 235 document.querySelectorAll('[data-copy]').forEach(el => { 236 el.addEventListener('click', async () => { 237 try { 238 await navigator.clipboard.writeText(el.dataset.copy); 239 el.classList.add('is-copied'); 240 const original = el.textContent; 241 el.textContent = '✓ <%= t('apl.copied') %>'; 242 setTimeout(() => { el.classList.remove('is-copied'); el.textContent = original; }, 1200); 243 } catch (_) { /* fall back to selection */ } 244 }); 245 }); 246 247 document.querySelectorAll('[data-pl-edit]').forEach(btn => { 248 btn.addEventListener('click', () => { 249 if (typeof window.openPlaylistEditor === 'function') { 250 window.openPlaylistEditor({ mode: 'edit', id: btn.dataset.id, onSaved: () => location.reload() }); 251 } 252 }); 253 }); 254 255 document.querySelectorAll('[data-pl-delete]').forEach(btn => { 256 btn.addEventListener('click', async () => { 257 const id = btn.dataset.id; 258 const title = btn.dataset.title || id; 259 if (!confirm('<%= t('apl.delete_confirm') %>'.replace('{title}', title))) return; 260 try { 261 const r = await fetch(`/admin/playlists/api/${encodeURIComponent(id)}/delete`, { 262 method: 'POST', 263 headers: { 'X-CSRF-Token': csrf }, 264 credentials: 'same-origin', 265 }); 266 const j = await r.json(); 267 if (j.ok) location.reload(); 268 else alert('<%= t('apl.delete_failed') %>: ' + (j.error || '')); 269 } catch (err) { 270 alert('<%= t('apl.delete_failed') %>: ' + err.message); 271 } 272 }); 273 }); 274 275 // P52 — deep-link from playlist embed (?edit=<id>) auto-opens the editor. 276 // openPlaylistEditor is defined synchronously by the included partial, so 277 // it's available by the time this IIFE runs. 278 (function deepLinkEdit() { 279 const params = new URLSearchParams(location.search); 280 const editId = params.get('edit'); 281 if (!editId) return; 282 if (typeof window.openPlaylistEditor !== 'function') return; 283 // Strip the query param immediately so reload after save doesn't re-open. 284 history.replaceState({}, '', location.pathname); 285 window.openPlaylistEditor({ 286 mode: 'edit', 287 id: editId, 288 onSaved: () => location.reload(), 289 }); 290 })(); 291 })(); 292 </script> 224 <%# Het script staat in assets/js/mod/admin-playlists.js; de gegevens via partials/page-data.ejs (shaer-bqr). %> 225 <%- include('../partials/page-data', { pageData: { csrf: _csrf, copied: t('apl.copied'), delConfirm: t('apl.delete_confirm'), delFailed: t('apl.delete_failed') } }) %> -
src/views/pages/admin-videos.ejs
r52fc278 r156baa3 40 40 </div> 41 41 42 <script> 43 (function () { 44 if (window.__videosWired) return; window.__videosWired = true; 45 var T = <%- JSON.stringify({ copy: t('admin.media_copy'), delC: t('admin.videos_del_confirm') }) %>; 46 document.addEventListener('click', function (e) { 47 var c = e.target.closest('[data-copy]'); 48 if (c) { 49 var u = location.origin + c.getAttribute('data-copy'); 50 var done = function () { var o = c.textContent; c.textContent = '✓'; setTimeout(function () { c.textContent = o === '✓' ? T.copy : o; }, 1200); }; 51 if (navigator.clipboard) navigator.clipboard.writeText(u).then(done).catch(function () { window.prompt('URL', u); }); 52 else window.prompt('URL', u); 53 return; 54 } 55 var d = e.target.closest('[data-del]'); 56 if (d) { 57 if (!window.confirm(T.delC)) return; 58 fetch('/admin/media/videos/delete', { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ file: d.getAttribute('data-del') }) }) 59 .then(function (r) { return r.json(); }) 60 .then(function (j) { if (j && j.ok) { var card = d.closest('.media-card'); if (card) card.remove(); } }) 61 .catch(function () {}); 62 } 63 }); 64 })(); 65 </script> 42 <%# Het script staat in assets/js/mod/admin-videos.js; de gegevens via partials/page-data.ejs (shaer-bqr). %> 43 <%- include('../partials/page-data', { pageData: { copy: t('admin.media_copy'), delC: t('admin.videos_del_confirm') } }) %> -
src/views/pages/paid-gate.ejs
r52fc278 r156baa3 31 31 32 32 <script src="/assets/vendor/simplewebauthn-browser.umd.min.js"></script> 33 <script> 34 (function () { 35 var base = "<%= (typeof siteUrlBase !== 'undefined' && siteUrlBase ? siteUrlBase : '') %>"; 36 var slug = "<%= pgSlug %>"; 37 var hasPatron = <%= _hasPatron ? 'true' : 'false' %>; 38 var I = { join: "<%= t('pgate.join_short') %>", confirm: "<%= t('pgate.confirm') %>", failed: "<%= t('pgate.failed') %>", error: "<%= t('pgate.error') %>" }; 39 var btn = document.getElementById('pg-unlock'); 40 var status = document.getElementById('pg-status'); 41 function say(msg, err) { status.hidden = false; status.textContent = msg; status.classList.toggle('is-err', !!err); } 42 function toLink() { location.href = base + '/paid/link?post=' + encodeURIComponent(slug); } 43 44 // No WebAuthn here: an assertion is impossible. With a Patreon page there is 45 // already a "Word supporter" button, so hide the (dead) unlock button rather 46 // than turn it into a second "Word supporter". Without one, this IS the button. 47 if (!window.SimpleWebAuthnBrowser || !window.PublicKeyCredential) { 48 if (hasPatron) { btn.style.display = 'none'; } 49 else { btn.textContent = I.join; btn.addEventListener('click', toLink); } 50 return; 51 } 52 53 btn.addEventListener('click', function () { 54 btn.disabled = true; 55 say(I.confirm); 56 fetch(base + '/paid/challenge?post=' + encodeURIComponent(slug)) 57 .then(function (r) { if (!r.ok) throw { link: true }; return r.json(); }) 58 .then(function (data) { 59 return window.SimpleWebAuthnBrowser.startAuthentication({ optionsJSON: data.options }) 60 .then(function (response) { 61 return fetch(base + '/paid/unlock', { 62 method: 'POST', headers: { 'Content-Type': 'application/json' }, 63 body: JSON.stringify({ response: response, blob: data.blob }), 64 }); 65 }); 66 }) 67 .then(function (r) { return r.json().then(function (j) { return { status: r.status, j: j }; }); }) 68 .then(function (res) { 69 if (res.j && res.j.ok && res.j.redirect) { 70 // Reload the real post page via the one-shot unlock capability, so it 71 // renders through its normal template (layout, styles, audio). 72 location.href = res.j.redirect; 73 } else if (res.status === 403) { 74 toLink(); // no valid passkey yet (or lapsed tier): link via Patreon 75 } else { 76 btn.disabled = false; say(I.failed, true); 77 } 78 }) 79 .catch(function (e) { 80 if (e && e.link) { toLink(); return; } 81 if (e && e.name === 'NotAllowedError') { toLink(); return; } // cancelled / no passkey -> link 82 btn.disabled = false; say(I.error, true); 83 }); 84 }); 85 })(); 86 </script> 33 <%# Het script staat in assets/js/mod/paid-gate.js; de gegevens via partials/page-data.ejs (shaer-bqr). %> 87 34 88 35 <style> … … 105 52 .pg-status.is-err { color: #c0392b; } 106 53 </style> 54 <%- include('../partials/page-data', { pageData: { base: (typeof siteUrlBase !== 'undefined' && siteUrlBase ? siteUrlBase : ''), slug: pgSlug, hasPatron: !!_hasPatron, i18n: { join: t('pgate.join_short'), confirm: t('pgate.confirm'), failed: t('pgate.failed'), error: t('pgate.error') } } }) %> -
src/views/pages/paid-passkey.ejs
r52fc278 r156baa3 12 12 13 13 <script src="/assets/vendor/simplewebauthn-browser.umd.min.js"></script> 14 <script> 15 (function () { 16 var options = <%- optionsJson %>; 17 var blob = "<%= regBlob %>"; 18 var I = { unsupported: "<%= t('ppk.unsupported') %>", follow: "<%= t('ppk.follow') %>", done: "<%= t('ppk.done') %>", failed: "<%= t('ppk.failed') %>", cancelled: "<%= t('ppk.cancelled') %>", error: "<%= t('pgate.error') %>" }; 19 var postUrl = "<%= (typeof siteUrlBase !== 'undefined' && siteUrlBase ? siteUrlBase : '') %>/<%= postSlug %>"; 20 var btn = document.getElementById('pk-go'); 21 var status = document.getElementById('pk-status'); 22 function say(msg, err) { status.hidden = false; status.textContent = msg; status.classList.toggle('is-err', !!err); } 23 24 if (!window.SimpleWebAuthnBrowser || !window.PublicKeyCredential) { 25 btn.disabled = true; 26 say(I.unsupported, true); 27 return; 28 } 29 btn.addEventListener('click', function () { 30 btn.disabled = true; 31 say(I.follow); 32 window.SimpleWebAuthnBrowser.startRegistration({ optionsJSON: options }) 33 .then(function (response) { 34 return fetch('/paid/register', { 35 method: 'POST', headers: { 'Content-Type': 'application/json' }, 36 body: JSON.stringify({ response: response, blob: blob }), 37 }); 38 }) 39 .then(function (r) { return r.json(); }) 40 .then(function (j) { 41 if (j && j.ok) { say(I.done); setTimeout(function () { location.href = postUrl; }, 900); } 42 else { btn.disabled = false; say(I.failed.replace('{err}', (j && j.error) || '?'), true); } 43 }) 44 .catch(function (e) { btn.disabled = false; say(e && e.name === 'NotAllowedError' ? I.cancelled : I.error, true); }); 45 }); 46 })(); 47 </script> 14 <%# Het script staat in assets/js/mod/paid-passkey.js; de gegevens via partials/page-data.ejs (shaer-bqr). %> 48 15 49 16 <style> … … 58 25 .pk-status.is-err { color: #c0392b; } 59 26 </style> 27 <%- include('../partials/page-data', { pageData: { options: JSON.parse(optionsJson), blob: regBlob, i18n: { unsupported: t('ppk.unsupported'), follow: t('ppk.follow'), done: t('ppk.done'), failed: t('ppk.failed'), cancelled: t('ppk.cancelled'), error: t('pgate.error') }, postUrl: (typeof siteUrlBase !== 'undefined' && siteUrlBase ? siteUrlBase : '') + '/' + postSlug } }) %>
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)