Add asset pipeline and professionalize theme
CI / build (push) Failing after 6m2s

Introduce a Node build system for minified CSS/JS with content hashes,
add PWA support, i18n helpers, accessibility improvements, and more
translatable strings. Move theme configuration from bookmarks to
localStorage, add Playwright tests, and set up CI workflows.
This commit is contained in:
2026-08-09 09:25:03 -04:00
parent 10af62dd6c
commit f753b27542
68 changed files with 4743 additions and 1989 deletions
+122
View File
@@ -0,0 +1,122 @@
// =====================================================================
// Theme configuration and dark/light toggle for Shaarli Pro.
// Keeps theme/mode preferences client-side without creating bookmarks.
// Used by the dark/light switch AND the theme picker (tools.html).
// =====================================================================
(function () {
'use strict';
const STORAGE_KEY = 'shaarit_themes_config';
function readConfig() {
try {
const raw = localStorage.getItem(STORAGE_KEY);
if (!raw) return null;
const parsed = JSON.parse(raw);
return (parsed && typeof parsed === 'object') ? parsed : null;
} catch (e) {
return null;
}
}
function writeConfig(config) {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(config));
return true;
} catch (e) {
return false;
}
}
// Merge partial updates into the persisted config and save to localStorage.
function save(partial) {
const base = readConfig() || {};
const config = Object.assign(
{ version: 2, themes: [], default: 'DEFAULT', mode: 'dark' },
base,
partial || {}
);
const ok = writeConfig(config);
return Promise.resolve({ ok, config });
}
// Reads the current persisted config from localStorage (no write).
function load() {
return Promise.resolve(readConfig());
}
// Kept for backward compatibility with older callers.
function findCandidates() {
return Promise.resolve([]);
}
window.ShaaritThemeConfig = { save, load, findCandidates };
})();
document.addEventListener('DOMContentLoaded', () => {
'use strict';
const themeCheckbox = document.getElementById('theme-toggle-checkbox');
const themeIconLight = document.getElementById('theme-icon-light');
const themeLabelSpan = document.querySelector('.theme-toggle-label span');
const i18n = window.ShaarliProI18n || { t: function (k) { return k; } };
function checkThemeRestrictions() {
const themeId = localStorage.getItem('shaarit_theme_id') || 'DEFAULT';
const darkOnlyThemes = ['LINEAR', 'SPOTIFY', 'NOTION', 'DISCORD', 'DRACULA', 'ONE_DARK_PRO', 'TOKYO_NIGHT', 'NORD', 'NIGHT_OWL', 'ANTHRACITE', 'CYBERPUNK', 'NAVY_ELEGANCE', 'EARTHY'];
return darkOnlyThemes.indexOf(themeId) !== -1;
}
function updateTheme(theme) {
// Enforce dark mode if the theme only supports dark
if (checkThemeRestrictions()) {
theme = 'dark';
}
document.documentElement.setAttribute('data-theme', theme);
localStorage.setItem('theme', theme);
if (themeCheckbox) {
themeCheckbox.checked = theme === 'dark';
// Disable the checkbox if restricted to dark mode
themeCheckbox.disabled = checkThemeRestrictions();
themeCheckbox.closest('.theme-switch').style.opacity = themeCheckbox.disabled ? '0.5' : '1';
}
if (themeIconLight) {
themeIconLight.className = theme === 'dark' ? 'mdi mdi-weather-night' : 'mdi mdi-weather-sunny';
}
if (themeLabelSpan) {
themeLabelSpan.textContent = theme === 'dark' ? i18n.t('Dark Mode') : i18n.t('Light Mode');
}
}
async function syncModeToBookmark(mode) {
try {
if (!window.ShaaritThemeConfig) return;
await window.ShaaritThemeConfig.save({ mode });
} catch (e) {
// eslint-disable-next-line no-console
console.error('[shaarit] Failed to sync mode to bookmark', e);
}
}
// Handle themeChanged event fired by tools.html
window.addEventListener('themeChanged', () => {
updateTheme(localStorage.getItem('theme') || 'dark');
});
// Init Theme
const savedTheme = localStorage.getItem('theme') ||
(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
updateTheme(savedTheme);
if (themeCheckbox) {
themeCheckbox.addEventListener('change', () => {
if (checkThemeRestrictions()) return; // Extra safety
const next = themeCheckbox.checked ? 'dark' : 'light';
updateTheme(next);
// Sync mode to bookmark in background
syncModeToBookmark(next);
});
}
});