diff --git a/index.html b/index.html index fc7c704..f755577 100644 --- a/index.html +++ b/index.html @@ -138,9 +138,9 @@ - Album + diff --git a/js/api.js b/js/api.js index 7b78883..aa1a2c0 100644 --- a/js/api.js +++ b/js/api.js @@ -324,6 +324,20 @@ export class LosslessAPI { } } + // If album exists but has no releaseDate, try to extract from tracks + if (album && !album.releaseDate && tracksSection?.items && tracksSection.items.length > 0) { + const firstTrack = tracksSection.items[0]; + const track = firstTrack.item || firstTrack; + + if (track) { + if (track.album && track.album.releaseDate) { + album = { ...album, releaseDate: track.album.releaseDate }; + } else if (track.streamStartDate) { + album = { ...album, releaseDate: track.streamStartDate.split('T')[0] }; + } + } + } + const tracks = (tracksSection?.items || []).map(i => this.prepareTrack(i.item || i)); const result = { album, tracks }; diff --git a/js/downloads.js b/js/downloads.js index 9774f85..ffea632 100644 --- a/js/downloads.js +++ b/js/downloads.js @@ -207,10 +207,13 @@ export async function downloadAlbumAsZip(album, tracks, api, quality, lyricsMana const zip = new JSZip(); const template = localStorage.getItem('zip-folder-template') || '{albumTitle} - {albumArtist} - monochrome.tf'; + const releaseDate = album.releaseDate ? new Date(album.releaseDate) : null; + const year = (releaseDate && !isNaN(releaseDate.getTime())) ? releaseDate.getFullYear() : ''; + const folderName = formatTemplate(template, { albumTitle: album.title, albumArtist: album.artist?.name, - year: new Date(album.releaseDate).getFullYear() + year: year }); const notification = createBulkDownloadNotification('album', album.title, tracks.length); @@ -380,10 +383,13 @@ export async function downloadDiscography(artist, api, quality, lyricsManager = try { const { album: fullAlbum, tracks } = await api.getAlbum(album.id); + const releaseDate = fullAlbum.releaseDate ? new Date(fullAlbum.releaseDate) : null; + const year = (releaseDate && !isNaN(releaseDate.getTime())) ? releaseDate.getFullYear() : ''; + const albumFolder = formatTemplate(template, { albumTitle: fullAlbum.title, albumArtist: fullAlbum.artist?.name, - year: new Date(fullAlbum.releaseDate).getFullYear() + year: year }); for (const track of tracks) { diff --git a/js/ui.js b/js/ui.js index 293ee6a..8cbbf7b 100644 --- a/js/ui.js +++ b/js/ui.js @@ -40,6 +40,15 @@ export class UIRenderer { const trackArtists = getTrackArtists(track); const trackTitle = getTrackTitle(track); + let yearDisplay = ''; + const releaseDate = track.album?.releaseDate || track.streamStartDate; + if (releaseDate) { + const date = new Date(releaseDate); + if (!isNaN(date.getTime())) { + yearDisplay = ` • ${date.getFullYear()}`; + } + } + return ` ${trackNumberHTML} @@ -49,7 +58,7 @@ export class UIRenderer { ${trackTitle} ${explicitBadge} - ${trackArtists} + ${trackArtists}${yearDisplay} ${formatTime(track.duration)} @@ -66,13 +75,21 @@ export class UIRenderer { createAlbumCardHTML(album) { const explicitBadge = hasExplicitContent(album) ? this.createExplicitBadge() : ''; + let yearDisplay = ''; + if (album.releaseDate) { + const date = new Date(album.releaseDate); + if (!isNaN(date.getTime())) { + yearDisplay = `${date.getFullYear()}`; + } + } return ` ${album.title} ${explicitBadge} - Album • ${album.artist?.name ?? ''} + ${album.artist?.name ?? ''} + ${yearDisplay} `; } @@ -84,7 +101,6 @@ export class UIRenderer { ${artist.name} - Artist `; } @@ -109,7 +125,7 @@ export class UIRenderer { - + ${!isArtist ? '' : ''} `; } @@ -257,12 +273,14 @@ export class UIRenderer { const imageEl = document.getElementById('album-detail-image'); const titleEl = document.getElementById('album-detail-title'); const metaEl = document.getElementById('album-detail-meta'); + const prodEl = document.getElementById('album-detail-producer'); const tracklistContainer = document.getElementById('album-detail-tracklist'); imageEl.src = ''; imageEl.style.backgroundColor = 'var(--muted)'; titleEl.innerHTML = ''; metaEl.innerHTML = ''; + prodEl.innerHTML = ''; tracklistContainer.innerHTML = ` # @@ -282,15 +300,26 @@ export class UIRenderer { titleEl.innerHTML = `${album.title} ${explicitBadge}`; const totalDuration = calculateTotalDuration(tracks); - const releaseDate = new Date(album.releaseDate); - const year = releaseDate.getFullYear(); + let dateDisplay = ''; + if (album.releaseDate) { + const releaseDate = new Date(album.releaseDate); + if (!isNaN(releaseDate.getTime())) { + const year = releaseDate.getFullYear(); + dateDisplay = window.innerWidth > 768 + ? releaseDate.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }) + : year; + } + } - const dateDisplay = window.innerWidth > 768 - ? releaseDate.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }) - : year; + const firstCopyright = tracks.find(track => track.copyright)?.copyright; metaEl.innerHTML = - `By ${album.artist.name} • ${dateDisplay} • ${tracks.length} tracks • ${formatDuration(totalDuration)}`; + (dateDisplay ? `${dateDisplay} • ` : '') + + `${tracks.length} tracks • ${formatDuration(totalDuration)}`; + + prodEl.innerHTML = + `By ${album.artist.name}` + + (firstCopyright ? ` • ${firstCopyright}` : ''); tracklistContainer.innerHTML = `
Album • ${album.artist?.name ?? ''}
${album.artist?.name ?? ''}
${yearDisplay}
Artist