notify.js statically imported navigate from views/views.js, creating a new
circular chain: navigation.js → ui/ui.js → settings.js → downloads.js →
notify.js → views/views.js → navigation.js. This chain did not exist in the
old monolithic ui.js and caused module initialisation failures in some browsers.
Fix: make navigate a lazy dynamic import inside _notifNavigate() body, which
is the only call site. Also replace unnecessary dynamic import('../utils.js')
calls with the already-statically-imported toast function.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
147 lines
5.5 KiB
JavaScript
147 lines
5.5 KiB
JavaScript
import { esc, toast } from '../utils.js';
|
|
import { api } from '../api.js';
|
|
|
|
const _NOTIF_KEY = 'ns_notifications';
|
|
|
|
function _loadNotifs() {
|
|
try { return JSON.parse(localStorage.getItem(_NOTIF_KEY) || '[]'); } catch { return []; }
|
|
}
|
|
function _saveNotifs(n) { localStorage.setItem(_NOTIF_KEY, JSON.stringify(n.slice(0, 100))); }
|
|
|
|
export function _addNotif(notif) {
|
|
const list = _loadNotifs();
|
|
list.unshift(notif);
|
|
_saveNotifs(list);
|
|
_renderNotifBell();
|
|
_renderNotifList();
|
|
}
|
|
|
|
export function deleteNotif(id) {
|
|
_saveNotifs(_loadNotifs().filter(n => n.id !== id));
|
|
_renderNotifBell();
|
|
_renderNotifList();
|
|
}
|
|
|
|
export function clearNotifs() {
|
|
_saveNotifs([]);
|
|
_renderNotifBell();
|
|
_renderNotifList();
|
|
}
|
|
|
|
export function toggleNotifPanel() {
|
|
const panel = document.getElementById('notif-panel');
|
|
const overlay = document.getElementById('notif-overlay');
|
|
if (!panel) return;
|
|
const isOpen = panel.classList.contains('open');
|
|
if (isOpen) {
|
|
panel.classList.remove('open');
|
|
overlay?.classList.remove('active');
|
|
} else {
|
|
_saveNotifs(_loadNotifs().map(n => ({...n, read: true})));
|
|
_renderNotifList();
|
|
_renderNotifBell();
|
|
panel.classList.add('open');
|
|
overlay?.classList.add('active');
|
|
}
|
|
}
|
|
|
|
export function initNotifBell() { _renderNotifBell(); }
|
|
|
|
function _renderNotifBell() {
|
|
const unread = _loadNotifs().filter(n => !n.read).length;
|
|
document.querySelectorAll('.notif-badge').forEach(b => {
|
|
b.textContent = unread > 9 ? '9+' : unread;
|
|
b.style.display = unread > 0 ? '' : 'none';
|
|
});
|
|
}
|
|
|
|
function _renderNotifList() {
|
|
const list = document.getElementById('notif-list');
|
|
if (!list) return;
|
|
const notifs = _loadNotifs();
|
|
if (!notifs.length) {
|
|
list.innerHTML = '<div class="notif-empty">Keine Benachrichtigungen</div>';
|
|
return;
|
|
}
|
|
list.innerHTML = notifs.map(n => {
|
|
const d = new Date(n.timestamp);
|
|
const time = d.toLocaleDateString('de-DE', {day:'2-digit', month:'2-digit'}) + ' ' + d.toLocaleTimeString('de-DE', {hour:'2-digit', minute:'2-digit'});
|
|
const icon = n.status === 'done' ? '✅' : '❌';
|
|
const failedHtml = n.failed?.length
|
|
? `<details class="notif-failed"><summary>${n.failed.length} Track${n.failed.length > 1 ? 's' : ''} fehlgeschlagen</summary><ul>${n.failed.map(f => `<li>${esc(f)}</li>`).join('')}</ul></details>`
|
|
: '';
|
|
const missingHtml = n.missing?.length
|
|
? `<details class="notif-failed"><summary>${n.missing.length} Track${n.missing.length > 1 ? 's' : ''} nicht in Navidrome gefunden</summary><ul>${n.missing.map(f => `<li>${esc(f)}</li>`).join('')}</ul></details>`
|
|
: '';
|
|
const hasNav = n.status === 'done' && ((n.navType === 'playlist' && n.navId) || n.navArtist || n.navAlbum);
|
|
const navLabel = n.navType === 'artist' ? 'Zum Künstler →'
|
|
: (n.navType === 'track' || n.navType === 'album') ? 'Zum Album →'
|
|
: 'Öffnen →';
|
|
const navBtn = hasNav
|
|
? `<button class="notif-nav-btn" onclick="_notifNavigate('${esc(n.id)}')">${navLabel}</button>`
|
|
: '';
|
|
return `<div class="notif-card${n.read ? '' : ' unread'}">
|
|
<div class="notif-card-top">
|
|
<span class="notif-icon">${icon}</span>
|
|
<div class="notif-info">
|
|
<div class="notif-title">${esc(n.title)}</div>
|
|
<div class="notif-sub">${n.artist ? esc(n.artist) + ' · ' : ''}${time}</div>
|
|
</div>
|
|
<button class="notif-x" onclick="deleteNotif('${esc(n.id)}')">✕</button>
|
|
</div>
|
|
${n.message ? `<div class="notif-msg">${esc(n.message)}</div>` : ''}
|
|
${failedHtml}
|
|
${missingHtml}
|
|
${navBtn}
|
|
</div>`;
|
|
}).join('');
|
|
}
|
|
|
|
export async function _notifNavigate(notifId) {
|
|
const notifs = _loadNotifs();
|
|
const n = notifs.find(x => x.id === notifId);
|
|
if (!n) return;
|
|
const panel = document.getElementById('notif-panel');
|
|
const overlay = document.getElementById('notif-overlay');
|
|
panel?.classList.remove('open');
|
|
overlay?.classList.remove('active');
|
|
|
|
const { navigate } = await import('../views/views.js');
|
|
|
|
if (n.navType === 'playlist' && n.navId) {
|
|
navigate('playlist-detail', { id: n.navId, name: n.navName || n.title.replace(/^Playlist:\s*/, '') });
|
|
return;
|
|
}
|
|
|
|
const norm = s => s?.toLowerCase().trim() || '';
|
|
|
|
if (n.navType === 'track' || n.navType === 'album') {
|
|
const albumQuery = n.navAlbum || n.navTitle;
|
|
if (albumQuery) {
|
|
try {
|
|
const r = await api('search3', `&query=${encodeURIComponent(albumQuery)}&artistCount=0&albumCount=10&songCount=0`);
|
|
const albums = r.searchResult3?.album || [];
|
|
const match = albums.find(a => norm(a.name) === norm(albumQuery) && norm(a.artist) === norm(n.navArtist))
|
|
|| albums.find(a => norm(a.name) === norm(albumQuery))
|
|
|| albums[0];
|
|
if (match) {
|
|
navigate('album-detail', { id: match.id, name: match.name, artist: match.artist || '', artistId: match.artistId || '', coverArt: match.coverArt || '' });
|
|
return;
|
|
}
|
|
} catch {}
|
|
}
|
|
toast('Album nicht gefunden — Library-Scan nötig?');
|
|
return;
|
|
}
|
|
|
|
if (n.navArtist) {
|
|
try {
|
|
const r = await api('search3', `&query=${encodeURIComponent(n.navArtist)}&artistCount=5&albumCount=0&songCount=0`);
|
|
const artists = r.searchResult3?.artist || [];
|
|
const match = artists.find(a => norm(a.name) === norm(n.navArtist)) || artists[0];
|
|
if (match) { navigate('artist-detail', { id: match.id, name: match.name }); return; }
|
|
} catch {}
|
|
toast('Künstler nicht gefunden — Library-Scan nötig?');
|
|
}
|
|
}
|