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
+66
View File
@@ -0,0 +1,66 @@
// =====================================================================
// Accessibility helpers for Shaarli Pro modals and overlays.
// =====================================================================
(function (root) {
'use strict';
const FOCUSABLE_SELECTORS = [
'a[href]',
'button:not([disabled])',
'input:not([disabled])',
'select:not([disabled])',
'textarea:not([disabled])',
'[tabindex]:not([tabindex="-1"])'
].join(', ');
/**
* Return a focusable element inside a container.
*/
function getFocusable(container, first) {
if (!container) return null;
const elements = Array.prototype.slice.call(container.querySelectorAll(FOCUSABLE_SELECTORS));
return first ? elements[0] : elements[elements.length - 1];
}
/**
* Move focus to the first focusable element inside a container.
*/
function focusFirst(container) {
const el = getFocusable(container, true);
if (el) el.focus();
}
/**
* Keep keyboard focus inside a container while it is open.
* Returns a cleanup function that removes the listener.
*/
function trapFocus(container) {
if (!container) return function () {};
function handleKey(e) {
if (e.key !== 'Tab') return;
const first = getFocusable(container, true);
const last = getFocusable(container, false);
if (!first || !last) return;
if (e.shiftKey && document.activeElement === first) {
e.preventDefault();
last.focus();
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault();
first.focus();
}
}
container.addEventListener('keydown', handleKey);
return function () {
container.removeEventListener('keydown', handleKey);
};
}
root.ShaarliProA11y = {
focusFirst: focusFirst,
trapFocus: trapFocus,
getFocusable: getFocusable
};
}(window));