- Move all inline <script> blocks (layout head/theme, topbar dark toggle, foot, paste, new, history, mine, settings, admin, unlock) to external files under internal/web/static/. Page data reaches scripts via data-* attributes (data-paste-id, data-default-dark) instead of template vars. - Replace inline onclick handlers (copy, delete, stats toggle) with addEventListener wiring. - Convert inline style="" attributes to CSS utility classes; swatch colors are now set via CSSOM/DOM APIs instead of innerHTML strings. - script-src/style-src are now plain 'self'; img-src data: stays for the SVG data-URI backgrounds. Verified with headless chromium: zero CSP violations on all pages in dark and light presets, theme swatches, admin lock, tables and paste view render correctly.
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
// #112/#127/#139: theme switcher. Swatch colors are derived from computed CSS vars per preset.
|
||||
(function () {
|
||||
// #112: derive each preset's swatches from the real CSS variables in
|
||||
// app.css by temporarily applying data-preset, so they can never drift.
|
||||
// #127: 5 theme pairs, light swatches top row, dark bottom row.
|
||||
var pairs = [
|
||||
{ id: 'midnight', name: 'Midnight' },
|
||||
{ id: 'smooth', name: 'Smooth' },
|
||||
{ id: 'pastel-lavender', name: 'Pastel Lavender' },
|
||||
{ id: 'pastel-peach', name: 'Pastel Peach' },
|
||||
{ id: 'pastel-cloud', name: 'Pastel Cloud' }
|
||||
];
|
||||
var SWATCH_VARS = ['--bg', '--surface', '--surface-2', '--muted', '--accent'];
|
||||
|
||||
function presetColors(id) {
|
||||
var root = document.documentElement;
|
||||
var prev = root.getAttribute('data-preset');
|
||||
root.setAttribute('data-preset', id);
|
||||
var cs = getComputedStyle(root);
|
||||
var colors = SWATCH_VARS.map(function (v) { return cs.getPropertyValue(v).trim(); });
|
||||
if (prev === null) root.removeAttribute('data-preset'); else root.setAttribute('data-preset', prev);
|
||||
return colors;
|
||||
}
|
||||
|
||||
// current base pair + dark flag from the resolved data-preset.
|
||||
// "midnight" is itself the dark variant, so check dark ids first.
|
||||
function state() {
|
||||
var p = document.documentElement.dataset.preset || 'midnight';
|
||||
if (p === 'midnight' || /-dark$/.test(p)) {
|
||||
return { base: p === 'midnight' ? 'midnight' : p.replace(/-dark$/, ''), dark: true };
|
||||
}
|
||||
return { base: p === 'midnight-light' ? 'midnight' : p, dark: false };
|
||||
}
|
||||
|
||||
var grid = document.getElementById('theme-grid');
|
||||
var cards = {};
|
||||
// #132: midnight is dark-first (root preset = midnight = dark; its light
|
||||
// variant is midnight-light), the others are light-first. Resolve the
|
||||
// LIGHT and DARK preset ids generically so the light swatches always
|
||||
// render in the top row of every card.
|
||||
function lightPreset(id) {
|
||||
if (id === 'midnight') return 'midnight-light';
|
||||
return id; // light-first bases use themselves as the light variant
|
||||
}
|
||||
function darkPreset(id) {
|
||||
if (id === 'midnight') return 'midnight'; // root preset is midnight's dark
|
||||
return id + '-dark';
|
||||
}
|
||||
pairs.forEach(function (t) {
|
||||
var light = presetColors(lightPreset(t.id));
|
||||
var dark = presetColors(darkPreset(t.id));
|
||||
var btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = 'theme-card';
|
||||
btn.setAttribute('aria-pressed', 'false');
|
||||
btn.setAttribute('data-pair', t.id);
|
||||
// #139: build the swatches via DOM APIs (CSSOM styles) instead of
|
||||
// innerHTML strings: re-parsing serialized inline styles would trip
|
||||
// style-src 'self'.
|
||||
var label = document.createElement('strong');
|
||||
label.textContent = t.name;
|
||||
btn.appendChild(label);
|
||||
[light, dark].forEach(function (colors) {
|
||||
var row = document.createElement('span');
|
||||
row.className = 'swatches';
|
||||
colors.forEach(function (c) {
|
||||
var sw = document.createElement('span');
|
||||
sw.className = 'swatch';
|
||||
sw.style.backgroundColor = c;
|
||||
row.appendChild(sw);
|
||||
});
|
||||
btn.appendChild(row);
|
||||
});
|
||||
btn.addEventListener('click', function () {
|
||||
var dark = state().dark;
|
||||
document.documentElement.dataset.preset = dark ? t.id + '-dark' : t.id;
|
||||
try { localStorage.setItem('palette-theme', t.id); } catch (e) {}
|
||||
Object.keys(cards).forEach(function (k) { cards[k].setAttribute('aria-pressed', 'false'); });
|
||||
btn.setAttribute('aria-pressed', 'true');
|
||||
});
|
||||
cards[t.id] = btn;
|
||||
grid.appendChild(btn);
|
||||
});
|
||||
|
||||
function sync() {
|
||||
var s = state();
|
||||
Object.keys(cards).forEach(function (k) {
|
||||
cards[k].setAttribute('aria-pressed', k === s.base ? 'true' : 'false');
|
||||
});
|
||||
var dt = document.getElementById('settings-dark-toggle');
|
||||
if (dt) dt.setAttribute('aria-pressed', s.dark ? 'true' : 'false');
|
||||
}
|
||||
sync();
|
||||
// the topbar script runs before this button exists, so wire it here
|
||||
var dt = document.getElementById('settings-dark-toggle');
|
||||
dt.addEventListener('click', function () {
|
||||
var btns = document.querySelectorAll('.topbar .dark-toggle');
|
||||
if (btns.length) btns[0].click(); else document.dispatchEvent(new CustomEvent('palette-darkchange'));
|
||||
});
|
||||
document.addEventListener('palette-darkchange', sync);
|
||||
})();
|
||||
Reference in New Issue
Block a user