Files
blog.pyaqa.ru/static/js/theme.js
Sergey Vanyushkin e2802d83f2 feat(ui): add web UI with Jinja2 templates and Gitea themes
- Add Jinja2 templates with data-testid attributes for testing
- Create light/dark themes based on Gitea color scheme
- Add theme switching with localStorage persistence
- Create base CSS, components, and layout styles
- Add mock web routes for UI demonstration
- Register web router and static files in main.py
- Add data-testid requirements to AGENTS.md
- Install jinja2 dependency
2026-05-02 14:45:51 +03:00

164 lines
4.3 KiB
JavaScript

/**
* Theme switching functionality for blog application.
*
* Handles theme persistence in localStorage and applies
* the selected theme to the document root element.
* Supports system preference detection and manual theme switching.
*/
(function() {
'use strict';
const STORAGE_KEY = 'blog-theme';
const THEME_ATTRIBUTE = 'data-theme';
const THEME_LIGHT = 'light';
const THEME_DARK = 'dark';
/**
* Get the currently stored theme preference.
* Falls back to system preference if no stored value.
*
* @returns {string} The theme name ('light' or 'dark')
*/
function getStoredTheme() {
try {
const stored = localStorage.getItem(STORAGE_KEY);
if (stored === THEME_LIGHT || stored === THEME_DARK) {
return stored;
}
} catch (e) {
console.warn('Failed to access localStorage:', e);
}
return getSystemPreference();
}
/**
* Detect system color scheme preference.
*
* @returns {string} 'dark' if system prefers dark mode, 'light' otherwise
*/
function getSystemPreference() {
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
return THEME_DARK;
}
return THEME_LIGHT;
}
/**
* Apply the specified theme to the document.
* Updates the data-theme attribute on the html element.
*
* @param {string} theme - The theme to apply ('light' or 'dark')
*/
function applyTheme(theme) {
const html = document.documentElement;
if (html) {
html.setAttribute(THEME_ATTRIBUTE, theme);
}
}
/**
* Save the theme preference to localStorage.
*
* @param {string} theme - The theme to save ('light' or 'dark')
*/
function saveTheme(theme) {
try {
localStorage.setItem(STORAGE_KEY, theme);
} catch (e) {
console.warn('Failed to save theme to localStorage:', e);
}
}
/**
* Set and apply the specified theme.
* Updates both the DOM and localStorage.
*
* @param {string} theme - The theme to set ('light' or 'dark')
*/
function setTheme(theme) {
if (theme !== THEME_LIGHT && theme !== THEME_DARK) {
console.warn('Invalid theme:', theme);
return;
}
applyTheme(theme);
saveTheme(theme);
updateThemeIcons(theme);
}
/**
* Toggle between light and dark themes.
*/
function toggleTheme() {
const currentTheme = document.documentElement.getAttribute(THEME_ATTRIBUTE);
const newTheme = currentTheme === THEME_DARK ? THEME_LIGHT : THEME_DARK;
setTheme(newTheme);
}
/**
* Update theme toggle icons based on current theme.
* Shows/hides sun/moon icons appropriately.
*
* @param {string} theme - The current theme
*/
function updateThemeIcons(theme) {
const lightIcons = document.querySelectorAll('[data-testid="theme-light-icon"]');
const darkIcons = document.querySelectorAll('[data-testid="theme-dark-icon"]');
lightIcons.forEach(icon => {
icon.style.display = theme === THEME_LIGHT ? 'none' : 'block';
});
darkIcons.forEach(icon => {
icon.style.display = theme === THEME_DARK ? 'none' : 'block';
});
}
/**
* Initialize theme on page load.
* Applies stored theme and sets up event listeners.
*/
function init() {
const theme = getStoredTheme();
applyTheme(theme);
document.addEventListener('DOMContentLoaded', function() {
updateThemeIcons(theme);
const toggleBtn = document.querySelector('[data-testid="theme-toggle"]');
if (toggleBtn) {
toggleBtn.addEventListener('click', toggleTheme);
}
});
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function(e) {
try {
const hasUserPreference = localStorage.getItem(STORAGE_KEY);
if (!hasUserPreference) {
const newTheme = e.matches ? THEME_DARK : THEME_LIGHT;
applyTheme(newTheme);
updateThemeIcons(newTheme);
}
} catch (err) {
console.warn('Failed to handle system theme change:', err);
}
});
}
const BlogTheme = {
setTheme: setTheme,
toggleTheme: toggleTheme,
getStoredTheme: getStoredTheme,
getSystemPreference: getSystemPreference,
THEME_LIGHT: THEME_LIGHT,
THEME_DARK: THEME_DARK
};
if (typeof window !== 'undefined') {
window.BlogTheme = BlogTheme;
}
init();
})();