Changeset f7d142f in Klonkt
- Timestamp:
- 06/28/2026 03:36:22 PM (2 months ago)
- Branches:
- main
- Children:
- 81bb9c5
- Parents:
- 127f87e
- Location:
- src
- Files:
-
- 16 edited
-
server.js (modified) (2 diffs)
-
views/pages/admin-audio.ejs (modified) (2 diffs)
-
views/pages/admin-newsletter.ejs (modified) (2 diffs)
-
views/pages/admin-shows.ejs (modified) (2 diffs)
-
views/pages/admin-sites.ejs (modified) (2 diffs)
-
views/pages/admin-updates.ejs (modified) (1 diff)
-
views/pages/admin-users.ejs (modified) (2 diffs)
-
views/pages/authorize-interaction.ejs (modified) (1 diff)
-
views/pages/following.ejs (modified) (1 diff)
-
views/pages/news.ejs (modified) (1 diff)
-
views/pages/post.ejs (modified) (1 diff)
-
views/pages/viewer-blocked.ejs (modified) (1 diff)
-
views/partials/fedi-node.ejs (modified) (1 diff)
-
views/partials/footer.ejs (modified) (1 diff)
-
views/partials/playlist-editor.ejs (modified) (1 diff)
-
views/shell.ejs (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
src/server.js
r127f87e rf7d142f 109 109 (req, res) => `'nonce-${res.locals.cspNonce}'`, 110 110 ], 111 // Helmet's default sets script-src-attr to 'none', which blocks ALL inline 112 // event handlers (onchange/onclick/onsubmit) — causing e.g. the avatar 113 // upload (<input onchange="this.form.submit()">) and the role dropdown to 114 // silently do nothing. We explicitly allow inline handlers, consistent with 115 // the already-allowed inline <script> above. 116 scriptSrcAttr: ["'unsafe-inline'"], 111 // No inline event handlers anywhere: every on* attribute was moved to a 112 // delegated data-* handler (the shared script in shell.ejs), so inline 113 // handlers are blocked entirely — this closes the last 'unsafe-inline' in 114 // the script directives. 115 scriptSrcAttr: ["'none'"], 117 116 styleSrc: ["'self'", "'unsafe-inline'"], 118 117 // blob: required for the image editor (Cropper) — it displays the chosen … … 144 143 referrerPolicy: { policy: 'strict-origin-when-cross-origin' }, 145 144 })); 145 146 // Permissions-Policy: disable powerful features Klonkt never uses (camera, microphone, 147 // geolocation) and opt out of the Topics API. Features that embeds legitimately need 148 // (autoplay, fullscreen, encrypted-media, picture-in-picture) are left at their default 149 // allowlist, so YouTube/Spotify/SoundCloud players keep working. 150 app.use((req, res, next) => { 151 res.setHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=(), browsing-topics=()'); 152 next(); 153 }); 146 154 147 155 app.set('view engine', 'ejs'); -
src/views/pages/admin-audio.ejs
r127f87e rf7d142f 121 121 style="<%= t.downloadable ? 'color:var(--accent,#6b8f71)' : 'opacity:.5' %>">⬇</button> 122 122 <% } %> 123 <form action="/admin/audio/<%= t.id %>/delete" method="post" onsubmit="return confirm('<%= _aaudDelConfirm %>')" class="ax-track-delete">123 <form action="/admin/audio/<%= t.id %>/delete" method="post" data-confirm="<%= _aaudDelConfirm %>" class="ax-track-delete"> 124 124 <button type="submit" class="ax-icon-btn ax-icon-btn-danger" aria-label="<%= _aaudDelete %>" title="<%= _aaudDelete %>">🗑</button> 125 125 </form> … … 135 135 <h2 style="margin:0 0 .4rem">🔊 <%= t('aaud.embed_player') %></h2> 136 136 <p style="opacity:.75;font-size:.9rem;margin:0 0 .6rem"><%= t('aaud.embed_hint') %></p> 137 <textarea readonly rows="3" onclick="this.select()"style="width:100%;font-family:monospace;font-size:12px;padding:10px;border-radius:8px;border:1px solid var(--rule);background:var(--paper-2);color:inherit;"><iframe src="<%= embedUrl %>" width="100%" height="420" frameborder="0" loading="lazy" style="border-radius:12px"></iframe></textarea>137 <textarea readonly rows="3" data-selectall style="width:100%;font-family:monospace;font-size:12px;padding:10px;border-radius:8px;border:1px solid var(--rule);background:var(--paper-2);color:inherit;"><iframe src="<%= embedUrl %>" width="100%" height="420" frameborder="0" loading="lazy" style="border-radius:12px"></iframe></textarea> 138 138 <p style="margin:.6rem 0 0"><a class="btn" href="<%= embedUrl %>" target="_blank">▶ <%= t('aaud.preview_player') %></a></p> 139 139 </section> -
src/views/pages/admin-newsletter.ejs
r127f87e rf7d142f 24 24 25 25 <% var _sendConfirm = t('anews.send_confirm', { n: c.confirmed }); %> 26 <form method="POST" action="<%= siteUrlBase %>/admin/newsletter/send" class="anl-form" >26 <form method="POST" action="<%= siteUrlBase %>/admin/newsletter/send" class="anl-form" data-confirm="<%= _sendConfirm %>"> 27 27 <label><%= t('anews.subject') %> 28 28 <input type="text" name="subject" required maxlength="200" value="<%= (typeof nlSubject!=='undefined')?nlSubject:'' %>" placeholder="<%= t('anews.subject_ph') %>"> … … 31 31 <textarea name="body" rows="10" required placeholder="<%= t('anews.body_ph') %>"><%= (typeof nlBody!=='undefined')?nlBody:'' %></textarea> 32 32 </label> 33 <button type="submit" class="anl-send" <%= smtp ? '' : 'disabled' %> onclick="return confirm('<%= _sendConfirm %>')"><%= t('anews.send_btn', { n: c.confirmed }) %></button>33 <button type="submit" class="anl-send" <%= smtp ? '' : 'disabled' %>><%= t('anews.send_btn', { n: c.confirmed }) %></button> 34 34 </form> 35 35 -
src/views/pages/admin-shows.ejs
r127f87e rf7d142f 14 14 <form method="POST" action="<%= siteUrlBase %>/admin/shows/toggle" style="margin:0 0 14px"> 15 15 <label style="display:flex;align-items:center;gap:10px;cursor:pointer;font-weight:600"> 16 <input type="checkbox" name="enabled" value="1" <%= agOn ? 'checked' : '' %> onchange="this.form.submit()"style="width:18px;height:18px;accent-color:var(--accent)">16 <input type="checkbox" name="enabled" value="1" <%= agOn ? 'checked' : '' %> data-autosubmit style="width:18px;height:18px;accent-color:var(--accent)"> 17 17 <span><%= t('ashow.show_toggle') %> <small style="font-weight:400;opacity:.7"><%= t('ashow.show_toggle_hint') %></small></span> 18 18 </label> … … 45 45 <span class="ash-when"><%= s.date %><% if (s.time) { %> <%= s.time %><% } %></span> 46 46 <span class="ash-where"><%= s.city %><% if (s.venue) { %> — <%= s.venue %><% } %></span> 47 <form method="POST" action="<%= siteUrlBase %>/admin/shows/<%= s.id %>/delete" onsubmit="return confirm('<%= _delConfirm %>')" style="margin:0">47 <form method="POST" action="<%= siteUrlBase %>/admin/shows/<%= s.id %>/delete" data-confirm="<%= _delConfirm %>" style="margin:0"> 48 48 <button type="submit" class="ash-del">🗑</button> 49 49 </form> -
src/views/pages/admin-sites.ejs
r127f87e rf7d142f 93 93 <% if (!s.is_primary && (typeof canMutate === 'undefined' || canMutate)) { %> 94 94 <form method="post" action="/admin/sites/<%= s.slug %>/make-primary" style="margin:0;display:inline" 95 onsubmit="return confirm('<%= _s_makeprim_c %>')">95 data-confirm="<%= _s_makeprim_c %>"> 96 96 <button type="submit" class="ax-icon-btn" aria-label="<%= _s_makeprim %>" title="<%= _s_makeprim_t %>">★</button> 97 97 </form> … … 99 99 <a href="/admin/sites/<%= s.slug %>/edit" class="ax-icon-btn" aria-label="<%= _s_edit %>" title="<%= _s_edit %>">✎</a> 100 100 <form method="post" action="/admin/sites/<%= s.slug %>/delete" class="ax-delete-form" 101 onsubmit="return confirm('<%= _s_del_c %>')">101 data-confirm="<%= _s_del_c %>"> 102 102 <button type="submit" class="ax-icon-btn ax-icon-btn-danger" aria-label="<%= _s_del %>" title="<%= _s_del %>">🗑</button> 103 103 </form> -
src/views/pages/admin-updates.ejs
r127f87e rf7d142f 40 40 <% if (canCheck && canSelfUpdate) { %> 41 41 <form method="post" action="/admin/updates/run" class="set-form" 42 onsubmit="return confirm('<%= t('aupd.run_confirm') %>');">42 data-confirm="<%= t('aupd.run_confirm') %>"> 43 43 <button type="submit" class="btn btn-primary"> 44 44 <%= upToDate ? '↻ ' + t('aupd.redeploy') : '⬆ ' + t('aupd.update_now') %> -
src/views/pages/admin-users.ejs
r127f87e rf7d142f 80 80 <label class="ax-role"> 81 81 <span><%= _u_rol %></span> 82 <select name="role" onchange="this.form.submit()"<% if (u.id === user.id || !canMutate) { %>disabled<% } %>>82 <select name="role" data-autosubmit <% if (u.id === user.id || !canMutate) { %>disabled<% } %>> 83 83 <option value="kijker" <%= u.role === 'kijker' ? 'selected' : '' %>><%= _u_r_kijker %></option> 84 84 <option value="member" <%= u.role === 'member' ? 'selected' : '' %>><%= _u_r_member %></option> … … 94 94 : ''; %> 95 95 <form method="post" action="/admin/users/<%= u.id %>/delete" class="ax-delete-form" 96 onsubmit="return confirm('<%= t('ausr.del_confirm', { name: u.username }) %> <%= _warn %><%= t('ausr.del_undo') %>')">96 data-confirm="<%= t('ausr.del_confirm', { name: u.username }) %> <%= _warn %><%= t('ausr.del_undo') %>"> 97 97 <button type="submit" class="ax-icon-btn ax-icon-btn-danger" aria-label="<%= _u_del %>" title="<%= _u_del %>">🗑</button> 98 98 </form> -
src/views/pages/authorize-interaction.ejs
r127f87e rf7d142f 37 37 <a class="fedi-goto-btn" href="<%= m.in_reply_to %>" target="_blank" rel="nofollow noopener"><svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M7 17 17 7"/><path d="M7 7h10v10"/></svg> <%= t('fedi.goto_post') %></a> 38 38 <% } %> 39 <form method="post" action="/fediverse/<%= m.id %>/delete" class="fedi-del-form" onsubmit="return confirm('<%= t('fedi.delete_confirm') %>')">39 <form method="post" action="/fediverse/<%= m.id %>/delete" class="fedi-del-form" data-confirm="<%= t('fedi.delete_confirm') %>"> 40 40 <button type="submit" class="fedi-del-btn"><svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg> <%= t('comments.delete') %></button> 41 41 </form> -
src/views/pages/following.ejs
r127f87e rf7d142f 28 28 <form method="post" action="/news/autoboost" class="tl-foll-ab" title="<%= t('tl.autoboost_hint') %>"> 29 29 <input type="hidden" name="actor_uri" value="<%= f.actor_uri %>"> 30 <label><input type="checkbox" name="auto_boost" value="1" <%= f.auto_boost ? 'checked' : '' %> onchange="this.form.submit()"> ✨ <%= t('tl.autoboost') %></label>30 <label><input type="checkbox" name="auto_boost" value="1" <%= f.auto_boost ? 'checked' : '' %> data-autosubmit> ✨ <%= t('tl.autoboost') %></label> 31 31 </form> 32 32 <form method="post" action="/news/unfollow"><input type="hidden" name="actor_uri" value="<%= f.actor_uri %>"><button type="submit" class="tl-unfollow"><%= t('tl.unfollow') %></button></form> -
src/views/pages/news.ejs
r127f87e rf7d142f 135 135 <form method="post" action="/news/boost" class="tl-act-form tl-react-form"><input type="hidden" name="note" value="<%= p.id %>"><input type="hidden" name="author" value="<%= p.author_uri %>"><button type="submit" class="tl-act tl-act-boost<%= p.boosted ? ' is-on' : '' %>" title="<%= p.boosted ? t('tl.unboost') : t('fedi.boosts') %>" aria-label="<%= p.boosted ? t('tl.unboost') : t('fedi.boosts') %>"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="17 1 21 5 17 9"/><path d="M3 11V9a4 4 0 0 1 4-4h14"/><polyline points="7 23 3 19 7 15"/><path d="M21 13v2a4 4 0 0 1-4 4H3"/></svg></button></form> 136 136 <button type="button" class="tl-act tl-act-reply fedi-remote-reply-btn" data-fedi-uri="<%= p.id %>" data-fedi-ph="<%= t('fedi.remote_ph') %>" title="<%= t('fedi.remote_reply') %>" aria-label="<%= t('fedi.remote_reply') %>"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="9 17 4 12 9 7"/><path d="M20 18v-2a4 4 0 0 0-4-4H4"/></svg></button> 137 <form method="post" action="/blocking/add" class="tl-act-form" onsubmit="return confirm('<%= t('tl.block') %>?');"><input type="hidden" name="target" value="<%= p.author_uri %>"><button type="submit" class="tl-act tl-act-block" title="<%= t('tl.block') %>" aria-label="<%= t('tl.block') %>"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><line x1="4.9" y1="4.9" x2="19.1" y2="19.1"/></svg></button></form>137 <form method="post" action="/blocking/add" class="tl-act-form" data-confirm="<%= t('tl.block') %>?"><input type="hidden" name="target" value="<%= p.author_uri %>"><button type="submit" class="tl-act tl-act-block" title="<%= t('tl.block') %>" aria-label="<%= t('tl.block') %>"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><line x1="4.9" y1="4.9" x2="19.1" y2="19.1"/></svg></button></form> 138 138 </div> 139 139 </li> -
src/views/pages/post.ejs
r127f87e rf7d142f 68 68 <% } %> 69 69 <% if (permissions.canDeletePost(user, post, site)) { %> 70 <form method="post" action="<%= _base %>/posts/<%= post.slug %>/delete" style="display:inline" onsubmit="return confirm('Delete this post?')">70 <form method="post" action="<%= _base %>/posts/<%= post.slug %>/delete" style="display:inline" data-confirm="Delete this post?"> 71 71 <button type="submit" class="btn btn-danger">🗑 Delete</button> 72 72 </form> -
src/views/pages/viewer-blocked.ejs
r127f87e rf7d142f 8 8 </p> 9 9 <div class="vb-actions"> 10 <button type="button" class="vb-btn vb-btn-primary" onclick="history.back()">← <%= t('vblk.back') %></button>10 <button type="button" class="vb-btn vb-btn-primary" data-back>← <%= t('vblk.back') %></button> 11 11 <a class="vb-btn" href="/"><%= t('vblk.to_home') %></a> 12 12 </div> -
src/views/partials/fedi-node.ejs
r127f87e rf7d142f 14 14 <div class="comment-actions"> 15 15 <% if (n.mine && typeof canManageSite !== 'undefined' && canManageSite && n.outboxId) { %> 16 <form method="post" action="<%= _base %>/fediverse/<%= n.outboxId %>/delete" onsubmit="return confirm('<%= t('fedi.delete_confirm') %>')">16 <form method="post" action="<%= _base %>/fediverse/<%= n.outboxId %>/delete" data-confirm="<%= t('fedi.delete_confirm') %>"> 17 17 <button type="submit" class="comment-delete-btn"><%= t('comments.delete') %></button> 18 18 </form> -
src/views/partials/footer.ejs
r127f87e rf7d142f 22 22 <% if (typeof langs !== 'undefined' && langs && langs.length > 1) { %> 23 23 <select class="footer-lang" aria-label="<%= t('nav.language') %>" 24 onchange="location.href='/lang/'+this.value+'?r='+encodeURIComponent(location.pathname+location.search)">24 data-lang-switch> 25 25 <% langs.forEach(function(l){ %><option value="<%= l.code %>"<%= l.active ? ' selected' : '' %>><%= l.name %></option><% }); %> 26 26 </select> -
src/views/partials/playlist-editor.ejs
r127f87e rf7d142f 442 442 const u = coverEl.value.trim(); 443 443 coverThumb.innerHTML = u 444 ? `<img src="${esc(u)}" alt="" onerror="this.parentElement.innerHTML='<span class=\\'pl-cover-empty\\'>⚠️</span>'">`444 ? `<img src="${esc(u)}" alt="" data-fallback>` 445 445 : `<span class="pl-cover-empty">🎨</span>`; 446 446 }); -
src/views/shell.ejs
r127f87e rf7d142f 747 747 </script> 748 748 749 <script> 750 // Delegated replacements for inline on* handlers, so the CSP needs no 751 // script-src-attr 'unsafe-inline'. Document-level → also covers htmx-swapped content. 752 (function () { 753 if (window.__pcmsHandlersWired) return; window.__pcmsHandlersWired = true; 754 // Confirm before submitting a form that carries data-confirm. 755 document.addEventListener('submit', function (e) { 756 var f = e.target; 757 if (f && f.dataset && f.dataset.confirm && !window.confirm(f.dataset.confirm)) e.preventDefault(); 758 }); 759 // Auto-submit a form / switch language when a <select> changes. 760 document.addEventListener('change', function (e) { 761 var el = e.target; 762 if (!el || !el.dataset) return; 763 if (el.dataset.autosubmit !== undefined && el.form) el.form.submit(); 764 else if (el.dataset.langSwitch !== undefined) { 765 location.href = '/lang/' + encodeURIComponent(el.value) + '?r=' + encodeURIComponent(location.pathname + location.search); 766 } 767 }); 768 // Misc click helpers (select-all in a field, history-back button). 769 document.addEventListener('click', function (e) { 770 var el = e.target.closest && e.target.closest('[data-selectall],[data-back]'); 771 if (!el) return; 772 if (el.dataset.selectall !== undefined && el.select) el.select(); 773 if (el.dataset.back !== undefined) { e.preventDefault(); history.back(); } 774 }); 775 // Image fallback (the error event doesn't bubble → capture phase). 776 document.addEventListener('error', function (e) { 777 var el = e.target; 778 if (el && el.tagName === 'IMG' && el.dataset && el.dataset.fallback !== undefined && el.parentElement) { 779 el.parentElement.innerHTML = '<span class="pl-cover-empty">⚠️</span>'; 780 } 781 }, true); 782 })(); 783 </script> 784 749 785 <!-- Per-site custom footer HTML --> 750 786 <% if (safeSite.custom_foot_html) { %>
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)