99 lines
3.3 KiB
JavaScript
99 lines
3.3 KiB
JavaScript
/*
|
|
* Copyright (C) 2026 Mateusz Gruszczyński @linuxiarz.pl
|
|
* Source-Available Code / Dual-Licensed.
|
|
*
|
|
* Free for non-commercial and evaluation use under terms of BSL/GPLv3.
|
|
* Commercial or production use requires a valid paid license.
|
|
* See LICENSE file in repository root for details.
|
|
*/
|
|
const STORAGE_KEY = "rustpad:theme";
|
|
const GUEST_STORAGE_KEY = "rustpad:guest-theme";
|
|
const AUTH_STATE_KEY = "rustpad:auth-state";
|
|
const THEMES = new Set(["dark", "light"]);
|
|
const systemThemeQuery = window.matchMedia("(prefers-color-scheme: light)");
|
|
|
|
function systemTheme() {
|
|
return systemThemeQuery.matches ? "light" : "dark";
|
|
}
|
|
|
|
function normalizeTheme(value) {
|
|
return THEMES.has(value) ? value : systemTheme();
|
|
}
|
|
|
|
function updateBrowserChrome(theme) {
|
|
document.documentElement.dataset.theme = theme;
|
|
document.documentElement.style.colorScheme = theme;
|
|
document.querySelector('meta[name="color-scheme"]')?.setAttribute("content", theme);
|
|
}
|
|
|
|
export function getTheme() {
|
|
return normalizeTheme(document.documentElement.dataset.theme);
|
|
}
|
|
|
|
function hasAccountSession() {
|
|
try { return localStorage.getItem(AUTH_STATE_KEY) === "1"; } catch { return false; }
|
|
}
|
|
|
|
function persistTheme(theme, scope = "auto") {
|
|
try {
|
|
localStorage.setItem(STORAGE_KEY, theme);
|
|
const guestScope = scope === "guest" || (scope === "auto" && !hasAccountSession());
|
|
if (guestScope) localStorage.setItem(GUEST_STORAGE_KEY, theme);
|
|
} catch { }
|
|
}
|
|
|
|
export function applyTheme(value, { persist = true, scope = "auto" } = {}) {
|
|
const theme = normalizeTheme(value);
|
|
const previousTheme = getTheme();
|
|
updateBrowserChrome(theme);
|
|
if (persist) persistTheme(theme, scope);
|
|
if (theme !== previousTheme) {
|
|
window.dispatchEvent(new CustomEvent("rustpad:theme-change", { detail: { theme } }));
|
|
}
|
|
return theme;
|
|
}
|
|
|
|
export function rememberGuestTheme(value = getTheme()) {
|
|
try {
|
|
if (THEMES.has(localStorage.getItem(GUEST_STORAGE_KEY))) return;
|
|
localStorage.setItem(GUEST_STORAGE_KEY, normalizeTheme(value));
|
|
} catch { }
|
|
}
|
|
|
|
export function restoreGuestTheme() {
|
|
let saved = null;
|
|
try { saved = localStorage.getItem(GUEST_STORAGE_KEY); } catch { }
|
|
if (THEMES.has(saved)) {
|
|
try { localStorage.setItem(STORAGE_KEY, saved); } catch { }
|
|
return applyTheme(saved, { persist: false });
|
|
}
|
|
try { localStorage.removeItem(STORAGE_KEY); } catch { }
|
|
return applyTheme(systemTheme(), { persist: false });
|
|
}
|
|
|
|
export function applySessionTheme(session) {
|
|
if (session?.theme) applyTheme(session.theme, { scope: "account" });
|
|
}
|
|
|
|
window.addEventListener("storage", event => {
|
|
if (event.key !== STORAGE_KEY && event.key !== GUEST_STORAGE_KEY && event.key !== AUTH_STATE_KEY) return;
|
|
if (event.key === GUEST_STORAGE_KEY && hasAccountSession()) return;
|
|
let saved = null;
|
|
try {
|
|
saved = hasAccountSession()
|
|
? localStorage.getItem(STORAGE_KEY)
|
|
: (localStorage.getItem(GUEST_STORAGE_KEY) || localStorage.getItem(STORAGE_KEY));
|
|
} catch { }
|
|
applyTheme(THEMES.has(saved) ? saved : systemTheme(), { persist: false });
|
|
});
|
|
|
|
systemThemeQuery.addEventListener("change", () => {
|
|
try {
|
|
const saved = hasAccountSession()
|
|
? localStorage.getItem(STORAGE_KEY)
|
|
: (localStorage.getItem(GUEST_STORAGE_KEY) || localStorage.getItem(STORAGE_KEY));
|
|
if (THEMES.has(saved)) return;
|
|
} catch { }
|
|
applyTheme(systemTheme(), { persist: false });
|
|
});
|