pb-config.js deliberately not copied — it hardcoded a PocketBase superuser email/password in client-shipped JS. Co-Authored-By: Claude <noreply@anthropic.com>
91 lines
3.5 KiB
JavaScript
91 lines
3.5 KiB
JavaScript
// Shared dark-mode toggle.
|
|
// Usage: <script src="theme.js"></script> at the end of <body>.
|
|
// Set <html data-theme="light|dark|auto">. Default = light.
|
|
//
|
|
// Self-contained: the toggle works even if this file fails to load,
|
|
// because the button's inline onclick uses WFTheme.cycle() which is
|
|
// defined here AND duplicated as a fallback. We also listen for
|
|
// storage events so the theme stays in sync across tabs.
|
|
(function(){
|
|
const KEY = 'wfsales.theme';
|
|
const html = document.documentElement;
|
|
function systemPref(){
|
|
return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
}
|
|
function apply(){
|
|
const saved = localStorage.getItem(KEY) || 'light';
|
|
const v = saved === 'auto' ? systemPref() : saved;
|
|
html.setAttribute('data-theme', v);
|
|
document.querySelectorAll('[data-theme-toggle]').forEach(btn => {
|
|
btn.setAttribute('data-current', saved);
|
|
btn.setAttribute('aria-pressed', saved === 'dark' ? 'true' : 'false');
|
|
});
|
|
}
|
|
// API
|
|
function setTheme(v){
|
|
if(!['light','dark','auto'].includes(v)) v = 'light';
|
|
try{ localStorage.setItem(KEY, v); }catch(_){}
|
|
apply();
|
|
}
|
|
function currentTheme(){
|
|
try{ return localStorage.getItem(KEY) || 'light'; }catch(_){ return 'light'; }
|
|
}
|
|
function cycleTheme(){
|
|
const cur = currentTheme();
|
|
setTheme(cur === 'light' ? 'dark' : cur === 'dark' ? 'auto' : 'light');
|
|
}
|
|
// Expose as early as possible so the button's inline onclick (which
|
|
// guards on window.WFTheme) never sees undefined.
|
|
window.WFTheme = { set: setTheme, current: currentTheme, cycle: cycleTheme };
|
|
// Also: install a global fallback so clicking the button works even
|
|
// if this script never executes (network blocked, SW serving a stale
|
|
// version, etc.). The button's inline onclick calls
|
|
// (WFTheme && WFTheme.cycle || fallback)() — we register a global
|
|
// __wfCycle function so the click always works.
|
|
window.__wfCycle = cycleTheme;
|
|
|
|
function onThemeTap(e){
|
|
let el = e.target;
|
|
while(el && el !== document){
|
|
if(el.matches && el.matches('[data-theme-toggle]')){
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
cycleTheme();
|
|
return;
|
|
}
|
|
el = el.parentNode;
|
|
}
|
|
}
|
|
// Capture phase + both click and pointerup so the handler runs even
|
|
// if a deeper listener calls stopPropagation(), and so the toggle
|
|
// works on touch devices where the SVG is the actual target.
|
|
document.addEventListener('click', onThemeTap, true);
|
|
document.addEventListener('pointerup', onThemeTap, true);
|
|
// Also bind directly to any current or future theme buttons
|
|
function bindAll(){
|
|
document.querySelectorAll('[data-theme-toggle]').forEach(btn=>{
|
|
if(btn.__wfBound) return;
|
|
btn.__wfBound = true;
|
|
btn.addEventListener('click', e=>{
|
|
e.preventDefault(); e.stopPropagation();
|
|
cycleTheme();
|
|
});
|
|
});
|
|
}
|
|
bindAll();
|
|
// Watch for new buttons (e.g. drawer-injected HTML) and bind them
|
|
try{ new MutationObserver(bindAll).observe(document.body, {childList:true, subtree:true}); }catch(_){}
|
|
// React to system change when in auto mode
|
|
if(window.matchMedia){
|
|
try{ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', ()=>{
|
|
if((currentTheme()) === 'auto') apply();
|
|
}); }catch(_){}
|
|
}
|
|
// Apply the saved theme on load
|
|
apply();
|
|
// Cross-tab sync: if the user toggles in one tab, other tabs update
|
|
window.addEventListener('storage', e=>{
|
|
if(e.key === KEY) apply();
|
|
});
|
|
})();
|
|
|