Files

160 lines
5.5 KiB
JavaScript
Raw Permalink Normal View History

2026-10-29 11:57:29 +01:00
// Generated, do not edit. sha256:08b414b8e046583bdfdf9c3302134a9e220a7c451265f464b9ed56dca2d3d460
import { getPref, setPref } from '../data/preferences.js';
import { CONFIG, targetHash } from '../../shared/config.js';
import { esc } from './escape.js';
const producers = [];
let cache = [];
let cacheKey = '';
const PREF_SCOPE = 'attention';
const PREF_ACT = 'activated';
const PREF_SEEN = 'seen';
const PREF_DAY = 'day';
const activated = new Map();
const seen = new Set();
let loaded = false;
let dayN = 0;
const stateKey = a => `${a.id}|${a.severity || 'attention'}`;
function todayStr() {
const d = new Date();
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
}
async function ensureLoaded() {
if (loaded) return;
loaded = true;
try {
const act = await getPref(PREF_SCOPE, PREF_ACT, {});
if (act && typeof act === 'object' && !Array.isArray(act)) {
for (const [id, s] of Object.entries(act)) activated.set(String(id), Number(s) || 0);
}
} catch { }
try {
const s = await getPref(PREF_SCOPE, PREF_SEEN, []);
if (Array.isArray(s)) for (const k of s) seen.add(String(k));
} catch { }
let day = { date: '', n: 0 };
try { const d = await getPref(PREF_SCOPE, PREF_DAY, null); if (d && typeof d === 'object') day = d; } catch { }
const today = todayStr();
if (day.date !== today) { day = { date: today, n: (Number(day.n) || 0) + 1 }; saveDay(day); }
dayN = Number(day.n) || 1;
}
async function saveDay(day) { try { await setPref(PREF_SCOPE, PREF_DAY, day); } catch { } }
async function saveActivated() {
try { await setPref(PREF_SCOPE, PREF_ACT, Object.fromEntries(activated)); } catch { }
}
async function saveSeen() {
try { await setPref(PREF_SCOPE, PREF_SEEN, [...seen]); } catch { }
}
function fadeFor(a) {
const k = stateKey(a);
if (!activated.has(k)) return 'hidden';
const age = dayN - activated.get(k);
if (age >= CONFIG.ATTENTION.HIDE_AFTER_DAYS) return 'hidden';
if (age >= CONFIG.ATTENTION.FADE_AFTER_DAYS || seen.has(k)) return 'dim';
return 'full';
}
function currentRouteKey() {
const h = (typeof location !== 'undefined' ? location.hash : '') || '';
return h.replace(/^#\/?/, '') || 'home';
}
function opDezePlek(a, here) {
const hereHead = here.split('/')[0];
if (!a.tab) return a.route === here || a.route === hereHead;
const plek = `${a.route}/${a.tab}`;
return here === plek || here.startsWith(`${plek}/`);
}
export function registerAttention(producer) {
if (typeof producer === 'function') producers.push(producer);
return () => {
const i = producers.indexOf(producer);
if (i >= 0) producers.splice(i, 1);
};
}
export async function refreshAttention() {
const collected = [];
for (const p of producers) {
try {
const items = await p();
if (Array.isArray(items)) collected.push(...items.filter(Boolean));
} catch { }
}
const norm = collected.map(a => ({
id: String(a.id),
severity: a.severity || 'attention',
route: a.route ?? null,
tab: a.tab ?? null,
hash: a.hash || (a.route ? targetHash({ route: a.route }) : null),
label: a.label || '',
count: Number.isFinite(a.count) ? a.count : null,
})).sort((x, y) => (x.route || '').localeCompare(y.route || '') || x.id.localeCompare(y.id));
cache = norm;
await ensureLoaded();
const here = currentRouteKey();
const liveKeys = new Set(norm.map(stateKey));
let activatedChanged = false, seenChanged = false;
for (const a of norm) {
const k = stateKey(a);
if (activated.has(k)) {
if (a.route !== null && opDezePlek(a, here) && !seen.has(k)) { seen.add(k); seenChanged = true; }
continue;
}
if (a.route === null || !opDezePlek(a, here)) {
activated.set(k, dayN); activatedChanged = true;
}
}
for (const k of [...activated.keys()]) {
if (!liveKeys.has(k)) { activated.delete(k); activatedChanged = true; }
}
for (const k of [...seen]) {
if (!liveKeys.has(k)) { seen.delete(k); seenChanged = true; }
}
if (activatedChanged) saveActivated();
if (seenChanged) saveSeen();
const actKey = norm.filter(a => activated.has(stateKey(a))).map(a => `${stateKey(a)}:${fadeFor(a)}`).sort().join(',');
const key = JSON.stringify(norm) + '|' + actKey;
const changed = key !== cacheKey;
cacheKey = key;
if (changed && typeof document !== 'undefined' && typeof document.dispatchEvent === 'function') {
document.dispatchEvent(new CustomEvent('attention-changed'));
}
return cache;
}
export function attentionItems() { return cache; }
export function generalAttention() {
return cache.filter(a => a.route === null).map(a => ({ ...a, fade: fadeFor(a) })).filter(a => a.fade !== 'hidden');
}
export function attentionForRoute(route) {
return cache.filter(a => a.route === route)
.map(a => ({ ...a, fade: fadeFor(a) })).filter(a => a.fade !== 'hidden');
}
export function hasAttentionForRoute(route) { return attentionForRoute(route).length > 0; }
export function attentionHasProblem(items) {
return (items || []).some(a => a.severity === 'problem');
}
export function attentionDimmed(items) {
return !!(items && items.length) && items.every(a => a.fade === 'dim');
}
export function attentionDotHtml(items) {
if (!items || !items.length) return '';
const hint = items.map(a => a.label).filter(Boolean).join(' \u00b7 ');
const dim = attentionDimmed(items);
const stuk = attentionHasProblem(items);
return ` <span class="nav-attention${stuk ? ' is-problem' : ''}${dim ? ' is-dim' : ''}" title="${esc(hint)}" aria-label="${esc(hint)}"></span>`;
}