feat: notification 'Öffnen' routes to album for track/album downloads
- Single track and album downloads now navigate to the album page - Artist downloads continue to navigate to the artist page - Button label changes to 'Zum Album →' or 'Zum Künstler →' accordingly - album name stored in job metadata so it survives until poll completes - Search uses exact album+artist match, falls back to album-name-only Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
ba382aa6d9
commit
1b7c05f66d
1 file changed
+32
-6
@@ -298,7 +298,7 @@ export async function executeDownload() {
|
|||||||
discTotal: S.downloadTarget.discCount || undefined,
|
discTotal: S.downloadTarget.discCount || undefined,
|
||||||
durationMs: S.downloadTarget.trackTimeMillis || undefined,
|
durationMs: S.downloadTarget.trackTimeMillis || undefined,
|
||||||
};
|
};
|
||||||
addJob(jobId, payload.title, payload.artist, payload.type);
|
addJob(jobId, payload.title, payload.artist, payload.type, payload.album);
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${S.cfg.backendUrl}/api/download`, {
|
const res = await fetch(`${S.cfg.backendUrl}/api/download`, {
|
||||||
method: 'POST', headers: {'Content-Type':'application/json'},
|
method: 'POST', headers: {'Content-Type':'application/json'},
|
||||||
@@ -489,9 +489,12 @@ function _renderNotifList() {
|
|||||||
const missingHtml = n.missing?.length
|
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>`
|
? `<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);
|
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
|
const navBtn = hasNav
|
||||||
? `<button class="notif-nav-btn" onclick="_notifNavigate('${esc(n.id)}')">Öffnen →</button>`
|
? `<button class="notif-nav-btn" onclick="_notifNavigate('${esc(n.id)}')">${navLabel}</button>`
|
||||||
: '';
|
: '';
|
||||||
return `<div class="notif-card${n.read ? '' : ' unread'}">
|
return `<div class="notif-card${n.read ? '' : ' unread'}">
|
||||||
<div class="notif-card-top">
|
<div class="notif-card-top">
|
||||||
@@ -525,9 +528,31 @@ export async function _notifNavigate(notifId) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const norm = s => s?.toLowerCase().trim() || '';
|
||||||
|
|
||||||
|
// Track or album download → find album in Navidrome and navigate there.
|
||||||
|
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 || [];
|
||||||
|
// Prefer exact album + artist match, fall back to album-name-only match.
|
||||||
|
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) {
|
if (n.navArtist) {
|
||||||
try {
|
try {
|
||||||
const norm = s => s?.toLowerCase().trim() || '';
|
|
||||||
const r = await api('search3', `&query=${encodeURIComponent(n.navArtist)}&artistCount=5&albumCount=0&songCount=0`);
|
const r = await api('search3', `&query=${encodeURIComponent(n.navArtist)}&artistCount=5&albumCount=0&songCount=0`);
|
||||||
const artists = r.searchResult3?.artist || [];
|
const artists = r.searchResult3?.artist || [];
|
||||||
const match = artists.find(a => norm(a.name) === norm(n.navArtist)) || artists[0];
|
const match = artists.find(a => norm(a.name) === norm(n.navArtist)) || artists[0];
|
||||||
@@ -543,8 +568,8 @@ function _parseFailedTracks(log) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ─── JOBS ───
|
// ─── JOBS ───
|
||||||
function addJob(id, title, artist, type) {
|
function addJob(id, title, artist, type, album) {
|
||||||
S.jobs[id] = {title, artist, type: type || '', progress:0, status:'pending'};
|
S.jobs[id] = {title, artist, type: type || '', album: album || '', progress:0, status:'pending'};
|
||||||
renderJobs();
|
renderJobs();
|
||||||
}
|
}
|
||||||
function updateJob(id, status, msg, progress) {
|
function updateJob(id, status, msg, progress) {
|
||||||
@@ -603,6 +628,7 @@ async function pollJob(jobId) {
|
|||||||
read: false,
|
read: false,
|
||||||
navType: job?.type || '',
|
navType: job?.type || '',
|
||||||
navArtist: job?.artist || '',
|
navArtist: job?.artist || '',
|
||||||
|
navAlbum: job?.album || '',
|
||||||
navTitle: title,
|
navTitle: title,
|
||||||
});
|
});
|
||||||
if (d.status === 'done') {
|
if (d.status === 'done') {
|
||||||
|
|||||||
Reference in new issue
Block a user