- 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.
79 lines
3.5 KiB
JavaScript
79 lines
3.5 KiB
JavaScript
// #16/#100/#127: theme preset resolution. Runs in <head> (blocking) so the
|
|
// correct data-preset is set before first paint. The server default dark flag
|
|
// arrives via the script tag's data-default-dark attribute so no inline script is needed.
|
|
// #130: shared line wrap preference (localStorage 'palette-wrap', default off).
|
|
// preset hook (#16): ?theme= wins; else persisted choice (#100).
|
|
// #127: theme pairs - each base theme has a light and dark variant;
|
|
// palette-theme stores the base id, palette-dark the dark flag.
|
|
(function () {
|
|
var PAIRS = {
|
|
'midnight': { light: 'midnight-light', dark: 'midnight' },
|
|
'smooth': { light: 'smooth', dark: 'smooth-dark' },
|
|
'pastel-lavender': { light: 'pastel-lavender', dark: 'pastel-lavender-dark' },
|
|
'pastel-peach': { light: 'pastel-peach', dark: 'pastel-peach-dark' },
|
|
'pastel-cloud': { light: 'pastel-cloud', dark: 'pastel-cloud-dark' }
|
|
};
|
|
function resolve(t, dark) {
|
|
for (var base in PAIRS) {
|
|
if (t === PAIRS[base].light || t === PAIRS[base].dark) return t;
|
|
if (t === base) return dark ? PAIRS[base].dark : PAIRS[base].light;
|
|
}
|
|
return null;
|
|
}
|
|
var t = new URLSearchParams(location.search).get('theme');
|
|
// precedence: URL ?theme= > stored pair+dark prefs > server default dark > dark on
|
|
var dark = (document.currentScript && document.currentScript.getAttribute('data-default-dark')) === '1';
|
|
try {
|
|
var stored = localStorage.getItem('palette-dark');
|
|
if (stored !== null) dark = stored !== 'false';
|
|
} catch (e) {}
|
|
if (t) {
|
|
// URL ?theme= wins and accepts both base and variant ids; a variant id
|
|
// also sets the dark flag and normalizes t back to its base pair id.
|
|
var found = false;
|
|
for (var base in PAIRS) {
|
|
if (t === PAIRS[base].dark) { dark = true; t = base; found = true; break; }
|
|
if (t === PAIRS[base].light) { dark = false; t = base; found = true; break; }
|
|
if (t === base) { found = true; break; }
|
|
}
|
|
if (!found) t = null;
|
|
}
|
|
if (!t) {
|
|
try { t = localStorage.getItem('palette-theme'); } catch (e) {}
|
|
}
|
|
if (!PAIRS[t]) t = 'midnight';
|
|
// do not persist anything here: URL theme is a one-off override
|
|
document.documentElement.dataset.preset = dark ? PAIRS[t].dark : PAIRS[t].light;
|
|
})();
|
|
// #130: shared line wrap preference (localStorage 'palette-wrap', default off)
|
|
(function () {
|
|
function wrapOn() {
|
|
try { return localStorage.getItem('palette-wrap') === '1'; } catch (e) { return false; }
|
|
}
|
|
function wrapApply(on) {
|
|
if (on) document.documentElement.setAttribute('data-wrap', '1');
|
|
else document.documentElement.removeAttribute('data-wrap');
|
|
document.querySelectorAll('.wrap-toggle').forEach(function (b) {
|
|
b.setAttribute('aria-pressed', on ? 'true' : 'false');
|
|
});
|
|
var s = document.getElementById('wrap-setting');
|
|
if (s) s.checked = on;
|
|
}
|
|
window.paletteWrapToggle = function () {
|
|
var on = !wrapOn();
|
|
try { localStorage.setItem('palette-wrap', on ? '1' : '0'); } catch (e) {}
|
|
wrapApply(on);
|
|
return on;
|
|
};
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
wrapApply(wrapOn());
|
|
document.querySelectorAll('.wrap-toggle').forEach(function (b) {
|
|
b.addEventListener('click', function () { window.paletteWrapToggle(); });
|
|
});
|
|
var s = document.getElementById('wrap-setting');
|
|
if (s) s.addEventListener('change', function () {
|
|
if (s.checked !== wrapOn()) window.paletteWrapToggle();
|
|
});
|
|
});
|
|
})();
|