Files

120 lines
3.9 KiB
JavaScript

(() => {
const THEME_KEY = 'grok2api_theme_preference';
const THEME_LIGHT = 'light';
const THEME_DARK = 'dark';
const THEME_AUTO = 'auto';
const metaThemeColor = document.querySelector('meta[name="theme-color"]');
function safeGetPreference() {
try {
const stored = localStorage.getItem(THEME_KEY);
if (stored === THEME_LIGHT || stored === THEME_DARK || stored === THEME_AUTO) {
return stored;
}
} catch (e) {
// 忽略 localStorage 访问异常
}
return THEME_AUTO;
}
function safeSetPreference(preference) {
try {
localStorage.setItem(THEME_KEY, preference);
} catch (e) {
// 忽略 localStorage 写入异常
}
}
function resolvePreference(preference) {
if (preference === THEME_LIGHT || preference === THEME_DARK) {
return preference;
}
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
return THEME_DARK;
}
return THEME_LIGHT;
}
function syncThemeMeta(resolvedTheme) {
if (!metaThemeColor) return;
metaThemeColor.setAttribute('content', resolvedTheme === THEME_DARK ? '#0b0f14' : '#fafafa');
}
function syncThemeButtons(preference, resolvedTheme) {
const buttons = document.querySelectorAll('[data-theme-toggle-btn]');
buttons.forEach((btn) => {
const nextLabel = resolvedTheme === THEME_DARK ? '日间模式' : '深夜模式';
const nextTitle = resolvedTheme === THEME_DARK ? '切换到日间模式' : '切换到深夜模式';
btn.textContent = nextLabel;
btn.setAttribute('title', nextTitle);
btn.classList.toggle('theme-active', resolvedTheme === THEME_DARK);
btn.setAttribute('aria-pressed', resolvedTheme === THEME_DARK ? 'true' : 'false');
btn.setAttribute('data-theme-preference', preference);
btn.setAttribute('data-theme-resolved', resolvedTheme);
});
}
function applyTheme(preference, persist = false) {
const normalized = (preference === THEME_LIGHT || preference === THEME_DARK || preference === THEME_AUTO)
? preference
: THEME_AUTO;
const resolved = resolvePreference(normalized);
const root = document.documentElement;
root.setAttribute('data-theme-preference', normalized);
root.setAttribute('data-theme', resolved);
syncThemeMeta(resolved);
syncThemeButtons(normalized, resolved);
if (persist) {
safeSetPreference(normalized);
}
}
function toggleTheme() {
const current = document.documentElement.getAttribute('data-theme') === THEME_DARK ? THEME_DARK : THEME_LIGHT;
const next = current === THEME_DARK ? THEME_LIGHT : THEME_DARK;
applyTheme(next, true);
}
function handleThemeButtonClick(event) {
const target = event.target;
if (!(target instanceof Element)) return;
const button = target.closest('[data-theme-toggle-btn]');
if (!button) return;
event.preventDefault();
toggleTheme();
}
function init() {
applyTheme(safeGetPreference(), false);
document.addEventListener('click', handleThemeButtonClick);
if (window.matchMedia) {
const media = window.matchMedia('(prefers-color-scheme: dark)');
const onSystemThemeChange = () => {
if (safeGetPreference() === THEME_AUTO) {
applyTheme(THEME_AUTO, false);
}
};
if (typeof media.addEventListener === 'function') {
media.addEventListener('change', onSystemThemeChange);
} else if (typeof media.addListener === 'function') {
media.addListener(onSystemThemeChange);
}
}
}
window.themeController = {
applyTheme,
refreshButtons() {
const preference = document.documentElement.getAttribute('data-theme-preference') || safeGetPreference();
const resolved = document.documentElement.getAttribute('data-theme') || resolvePreference(preference);
syncThemeButtons(preference, resolved);
syncThemeMeta(resolved);
},
getPreference: safeGetPreference,
};
init();
})();