Ordner-Chips einklappbar: „Ordner ▾/▸“ nach den Status-Chips, eingeklappt bleibt nur der gewählte Ordner sichtbar; je Bereich auf dem Gerät gemerkt (sw CACHE v159)
Some checks failed
Build and push Docker image / build (push) Successful in 16s
Test / test (push) Successful in 11m1s
Test / e2e (push) Failing after 1m28s

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mrwnslz 2026-10-26 20:40:10 +01:00
parent e8b525a3de
commit d702a27d16
2 changed files with 29 additions and 5 deletions

View file

@ -22,22 +22,45 @@
// lead: chips before the folders (the library's Angefangen/Ungelesen …): [{id, label, count, on}],
// picked with onPick({lead: id}). active: null = all, '' = without a folder, else a name.
// The folders fold away (a "Ordner" chip), per area (the list's element id), remembered on
// this device; folded, only the folder chosen stays in sight.
function areaKey(el) {
const owner = el.id ? el : el.parentElement && el.parentElement.closest('[id]');
return 'diora_folders_folded_' + (owner ? owner.id : 'x');
}
function folded(el) {
try { return localStorage.getItem(areaKey(el)) === '1'; } catch (e) { return false; }
}
function chips(el, o) {
const lead = o.lead || [];
const leadOn = lead.some(c => c.on);
const folders = o.folders || [];
const fold = folders.length > 0 && folded(el);
let h = '<div class="dfo-row" role="toolbar">';
if (!lead.length) h += chip('dfo-all', 'Alle', null, o.active == null && !leadOn, 'data-dfo-all="1"');
lead.forEach(c => { h += chip('', c.label, c.count, !!c.on, 'data-dfo-lead="' + esc(c.id) + '"'); });
if (lead.length && (o.folders || []).length) h += '<span class="dfo-sep" aria-hidden="true"></span>';
(o.folders || []).forEach(f => {
if (lead.length && folders.length) h += '<span class="dfo-sep" aria-hidden="true"></span>';
if (folders.length) {
h += '<button type="button" class="dfo-chip dfo-fold" data-dfo-fold="1" aria-expanded="' + !fold + '" title="' +
(fold ? 'Ordner zeigen' : 'Ordner einklappen') + '">Ordner ' + (fold ? '▸ <span class="dfo-count">' + folders.length + '</span>' : '▾') + '</button>';
}
folders.forEach(f => {
if (fold && o.active !== f.name) return;
h += chip('', f.name, f.count, o.active === f.name, 'data-dfo-folder="' + esc(f.name) + '"');
});
if (o.none) h += chip('dfo-none', 'Ohne Ordner', o.none, o.active === '', 'data-dfo-folder=""');
if (o.onManage) h += '<button type="button" class="dfo-chip dfo-manage" data-dfo-manage="1" title="Ordner verwalten" aria-label="Ordner verwalten">⋯</button>';
if (o.none && (!fold || o.active === '')) h += chip('dfo-none', 'Ohne Ordner', o.none, o.active === '', 'data-dfo-folder=""');
if (o.onManage && !fold) h += '<button type="button" class="dfo-chip dfo-manage" data-dfo-manage="1" title="Ordner verwalten" aria-label="Ordner verwalten">⋯</button>';
el.innerHTML = h + '</div>';
el.onclick = e => {
const b = e.target.closest('button');
if (!b) return;
if (b.dataset.dfoFold) {
try { localStorage.setItem(areaKey(el), fold ? '0' : '1'); } catch (err) {}
chips(el, o);
return;
}
if (b.dataset.dfoManage) { o.onManage(); return; }
if (b.dataset.dfoAll) { o.onPick(null); return; }
if (b.dataset.dfoLead != null) { o.onPick({lead: b.dataset.dfoLead}); return; }
@ -188,6 +211,7 @@
'.dfo-count { opacity: .7; font-size: 12px; }',
'.dfo-none { font-style: italic; }',
'.dfo-manage { min-width: 34px; padding: 0 8px; }',
'.dfo-fold { border-style: dashed; }',
'.dfo-sep { flex: none; width: 1px; height: 20px; background: var(--border-strong, #444); margin: 0 2px; }',
// Above the book view (900), below the page's dialogs (990): a name is asked for on top.
'.dfo-scrim { position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 950; background: var(--scrim, rgba(0,0,0,.5)); display: flex; align-items: flex-end; justify-content: center; }',

View file

@ -2,7 +2,7 @@
* diora service worker — caches the app shell for offline use.
*/
const CACHE = 'diora-v158';
const CACHE = 'diora-v159';
const SHELL = [
'/static/css/app.css',
'/static/js/app/01-core.js',