161 lines
5.5 KiB
JavaScript
161 lines
5.5 KiB
JavaScript
// 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>`;
|
|
}
|