haufen sachen
This commit is contained in:
1 parent
416ca9e854
commit
e6243701a3
20 files changed
+1562
-116
No files matched your search
@@ -0,0 +1,159 @@
|
||||
// ── Notification banner system ──
|
||||
// Providers register via addProvider(fn). Each fn(settings) returns an array of items.
|
||||
|
||||
import { $, esc } from './util.js';
|
||||
|
||||
const banner = $('upcoming-banner');
|
||||
const providers = [];
|
||||
const DISMISS_KEY = 'hyprcrd-dismissed-notifs';
|
||||
const DISMISS_DATE_KEY = 'hyprcrd-dismissed-date';
|
||||
const SETTINGS_KEY = 'hyprcrd-notif-settings';
|
||||
|
||||
// Clear dismissed list daily
|
||||
if (localStorage.getItem(DISMISS_DATE_KEY) !== new Date().toISOString().substring(0, 10)) {
|
||||
localStorage.removeItem(DISMISS_KEY);
|
||||
localStorage.setItem(DISMISS_DATE_KEY, new Date().toISOString().substring(0, 10));
|
||||
}
|
||||
|
||||
function getDismissed() {
|
||||
try { return JSON.parse(localStorage.getItem(DISMISS_KEY) || '[]'); } catch { return []; }
|
||||
}
|
||||
|
||||
function dismiss(id) {
|
||||
const d = getDismissed();
|
||||
if (!d.includes(id)) d.push(id);
|
||||
localStorage.setItem(DISMISS_KEY, JSON.stringify(d));
|
||||
render();
|
||||
}
|
||||
|
||||
export function getSettings() {
|
||||
const defaults = { lookahead: 3, time: 0, birthdays: true, termine: true, overdue: true };
|
||||
try { return { ...defaults, ...JSON.parse(localStorage.getItem(SETTINGS_KEY) || '{}') }; } catch { return defaults; }
|
||||
}
|
||||
|
||||
export function saveSettings(s) {
|
||||
localStorage.setItem(SETTINGS_KEY, JSON.stringify(s));
|
||||
render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a notification provider.
|
||||
* @param {function} fn - Receives settings, returns array of { id, icon, iconColor, label, when, sort, onClick?, dismissable?, type? }
|
||||
*/
|
||||
export function addProvider(fn) {
|
||||
providers.push(fn);
|
||||
}
|
||||
|
||||
function getAllItems() {
|
||||
const settings = getSettings();
|
||||
const items = [];
|
||||
for (const fn of providers) {
|
||||
try {
|
||||
const result = fn(settings);
|
||||
if (Array.isArray(result)) items.push(...result);
|
||||
} catch {}
|
||||
}
|
||||
items.sort((a, b) => (a.sort || '').localeCompare(b.sort || ''));
|
||||
return items;
|
||||
}
|
||||
|
||||
export function render() {
|
||||
if (!banner) return;
|
||||
const dismissed = getDismissed();
|
||||
const visible = getAllItems().filter(i => !dismissed.includes(String(i.id)));
|
||||
|
||||
if (!visible.length) { banner.classList.add('hidden'); banner.innerHTML = ''; return; }
|
||||
|
||||
banner.classList.remove('hidden');
|
||||
banner.innerHTML = `<div class="flex items-center gap-2 px-6 py-2 bg-t-raised border-b border-t-border overflow-x-auto">
|
||||
<i class="ph ph-bell-ringing text-t-accent text-sm shrink-0"></i>
|
||||
${visible.map(i => `<div class="upcoming-item" data-notif-id="${i.id}" style="color:${i.iconColor || 'var(--t-text)'}">
|
||||
<i class="ph ph-${i.icon || 'info'}" style="font-size:11px;"></i>
|
||||
${i.when ? `<span class="font-pixel text-[10px]">${esc(i.when)}</span>` : ''}
|
||||
<span>${esc(i.label)}</span>
|
||||
${i.dismissable !== false ? `<button class="upcoming-dismiss" data-dismiss="${i.id}"><i class="ph ph-x text-xs"></i></button>` : ''}
|
||||
</div>`).join('')}
|
||||
</div>`;
|
||||
|
||||
banner.querySelectorAll('[data-notif-id]').forEach(el => {
|
||||
el.addEventListener('click', e => {
|
||||
if (e.target.closest('.upcoming-dismiss')) return;
|
||||
const item = visible.find(i => String(i.id) === el.dataset.notifId);
|
||||
if (item?.onClick) item.onClick();
|
||||
});
|
||||
});
|
||||
banner.querySelectorAll('.upcoming-dismiss').forEach(btn => {
|
||||
btn.addEventListener('click', e => {
|
||||
e.stopPropagation();
|
||||
dismiss(btn.dataset.dismiss);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ── Settings panel integration ──
|
||||
|
||||
export function renderSettingsPanel() {
|
||||
const s = getSettings();
|
||||
|
||||
// Populate controls
|
||||
const lookahead = $('notif-lookahead');
|
||||
if (lookahead) lookahead.value = String(s.lookahead);
|
||||
const time = $('notif-time');
|
||||
if (time) time.value = String(s.time);
|
||||
const cbBday = $('notif-birthdays');
|
||||
if (cbBday) cbBday.checked = s.birthdays;
|
||||
const cbTermin = $('notif-termine');
|
||||
if (cbTermin) cbTermin.checked = s.termine;
|
||||
const cbOverdue = $('notif-overdue');
|
||||
if (cbOverdue) cbOverdue.checked = s.overdue;
|
||||
|
||||
// Auto-save on change (use onchange to avoid stacking listeners)
|
||||
const save = () => saveSettings({
|
||||
lookahead: parseInt(lookahead?.value || '3'),
|
||||
time: parseInt(time?.value || '0'),
|
||||
birthdays: cbBday?.checked ?? true,
|
||||
termine: cbTermin?.checked ?? true,
|
||||
overdue: cbOverdue?.checked ?? true,
|
||||
});
|
||||
if (lookahead) lookahead.onchange = save;
|
||||
if (time) time.onchange = save;
|
||||
if (cbBday) cbBday.onchange = save;
|
||||
if (cbTermin) cbTermin.onchange = save;
|
||||
if (cbOverdue) cbOverdue.onchange = save;
|
||||
|
||||
// Restore dismissed — replace element to avoid duplicate listeners
|
||||
const restoreBtn = $('notif-clear-dismissed');
|
||||
if (restoreBtn) {
|
||||
const fresh = restoreBtn.cloneNode(true);
|
||||
restoreBtn.parentNode.replaceChild(fresh, restoreBtn);
|
||||
fresh.addEventListener('click', () => {
|
||||
localStorage.removeItem(DISMISS_KEY);
|
||||
render();
|
||||
renderStream();
|
||||
});
|
||||
}
|
||||
|
||||
renderStream();
|
||||
}
|
||||
|
||||
function renderStream() {
|
||||
const stream = $('notif-stream');
|
||||
if (!stream) return;
|
||||
const dismissed = getDismissed();
|
||||
const all = getAllItems();
|
||||
|
||||
if (!all.length) {
|
||||
stream.innerHTML = '<div class="text-xs text-t-dim font-pixel text-center py-4">no notifications</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
stream.innerHTML = all.map(i => {
|
||||
const isDismissed = dismissed.includes(String(i.id));
|
||||
return `<div class="flex items-center gap-2 px-2 py-1.5 rounded text-xs ${isDismissed ? 'opacity-40' : ''}" style="color:${i.iconColor || 'var(--t-text)'}">
|
||||
<i class="ph ph-${i.icon || 'info'}" style="font-size:11px;flex-shrink:0;"></i>
|
||||
${i.when ? `<span class="font-pixel text-[9px] text-t-dim" style="min-width:55px;">${esc(i.when)}</span>` : ''}
|
||||
<span class="flex-1 truncate">${esc(i.label)}</span>
|
||||
${isDismissed ? '<span class="font-pixel text-[8px] text-t-dim">dismissed</span>' : ''}
|
||||
</div>`;
|
||||
}).join('');
|
||||
}
|
||||
Reference in new issue
Block a user