The theme was kept on the account, so every browser signed in as the same person got the same one: no Glass on the phone with Dracula on the desktop. It is now the ipx_theme cookie (<theme>.<mode>), written by theme.ts, and read by the server to draw the page in it from the first frame as before. /api/me no longer reports or takes a theme, and set_theme is gone. A browser with no cookie yet is sent the theme the account kept, and takes it as its cookie on that first load, so nobody loses their choice in the move. users.theme and theme_mode are only read now, for that. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
64 lines
3.5 KiB
TypeScript
64 lines
3.5 KiB
TypeScript
/* ---------------- theme ---------------- */
|
|
// A theme, and for those that come in both, light, dark or Auto, chosen in Settings and kept in a
|
|
// cookie, so each device has its own: Glass on a phone, Dracula on a desktop (issue #69). The
|
|
// server reads it and writes it onto the page's <html> tag (data-theme, data-choice) so the page
|
|
// is drawn in it from the start. The
|
|
// page gets data-mode, light or dark, which is all the CSS reads: Auto is worked out here, from
|
|
// the system, so no palette is written twice.
|
|
const THEMES: Record<string, {name: string, modes: boolean}> = {
|
|
adwaita: {name: 'Adwaita', modes: true},
|
|
catppuccin: {name: 'Catppuccin', modes: true},
|
|
classic: {name: 'Classic', modes: false},
|
|
dracula: {name: 'Dracula', modes: true},
|
|
flatremix: {name: 'Flat Remix', modes: true},
|
|
glass: {name: 'Glass', modes: true},
|
|
gruvbox: {name: 'Gruvbox', modes: true},
|
|
contrast: {name: 'High contrast', modes: true},
|
|
material: {name: 'Material', modes: true},
|
|
modern: {name: 'Modern', modes: true},
|
|
nordic: {name: 'Nordic', modes: true},
|
|
paper: {name: 'Paper', modes: false},
|
|
solarized: {name: 'Solarized', modes: true},
|
|
};
|
|
const MODES: Record<string, string> = {auto: 'Auto (matches your system)', light: 'Light', dark: 'Dark'};
|
|
// Before themes came in light and dark, ipx.theme in localStorage held one of these.
|
|
const OLD_THEMES: Record<string, [string, string]> = {dark: ['modern', 'dark'], light: ['modern', 'light'], auto: ['modern', 'auto']};
|
|
const systemDark = window.matchMedia?.('(prefers-color-scheme: dark)');
|
|
const theme = {name: 'modern', mode: 'dark'};
|
|
|
|
/// `save` for a choice made in Settings, which goes to this browser's cookie; not for applying one.
|
|
function setTheme(name = theme.name, mode = theme.mode, save = false){
|
|
theme.name = THEMES[name] ? name : 'modern';
|
|
theme.mode = MODES[mode] ? mode : 'dark';
|
|
const both = THEMES[theme.name].modes;
|
|
const root = document.documentElement;
|
|
root.dataset.theme = theme.name;
|
|
// A theme with one palette has it whatever the mode; both of those are light.
|
|
root.dataset.mode = !both ? 'light'
|
|
: theme.mode === 'auto' ? (systemDark && !systemDark.matches ? 'light' : 'dark') : theme.mode;
|
|
// The tab's icon in the same variant as the logo on the page.
|
|
const fav = $('#favicon'); if(fav) fav.href = fav.dataset[root.dataset.mode];
|
|
const sel = $('#stheme'); if(sel) sel.value = theme.name;
|
|
const ms = $('#smode'); if(ms) ms.value = theme.mode;
|
|
const mf = $('#smodefield'); if(mf) mf.hidden = !both;
|
|
if(save) saveTheme();
|
|
}
|
|
|
|
/// Kept a year, for the whole site: the admin page is drawn in it too.
|
|
function saveTheme(){
|
|
document.cookie = `ipx_theme=${theme.name}.${theme.mode}; Path=/; Max-Age=31536000; SameSite=Lax`;
|
|
}
|
|
systemDark?.addEventListener?.('change', () => { if(theme.mode === 'auto') setTheme(); });
|
|
(() => {
|
|
const root = document.documentElement;
|
|
// Without a cookie, the server sent the theme the account kept from before; this browser takes
|
|
// it as its own, once, so nobody has to choose again.
|
|
if(root.dataset.choice) return setTheme(root.dataset.theme, root.dataset.choice, !/(^|; )ipx_theme=/.test(document.cookie));
|
|
// Nothing on the account either. A theme this browser kept in localStorage, from before that,
|
|
// becomes its cookie, once.
|
|
let name: string | null = null, mode: string | null = null;
|
|
try{ name = localStorage.getItem('ipx.theme'); mode = localStorage.getItem('ipx.mode'); }catch{}
|
|
if(OLD_THEMES[name]) [name, mode] = OLD_THEMES[name];
|
|
setTheme(name ?? undefined, mode ?? undefined, !!name);
|
|
})();
|