Fix "Invalid Date" display in album details

- Backfill album release date from tracks if missing in API response.
- Gracefully handle invalid or missing dates in UI rendering.
- Fix potential NaN in download folder naming due to invalid dates.
This commit is contained in:
google-labs-jules[bot] 2025-12-22 22:57:02 +00:00 committed by Julien Maille
parent 0aee58b823
commit 43f04e7454
4 changed files with 62 additions and 13 deletions

View file

@ -138,9 +138,9 @@
<header class="detail-header">
<img id="album-detail-image" src="" alt="" class="detail-header-image">
<div class="detail-header-info">
<div class="type">Album</div>
<h1 class="title" id="album-detail-title"></h1>
<div class="meta" id="album-detail-meta"></div>
<div class="meta" id="album-detail-producer"></div>
<div class="detail-header-actions">
<button id="play-album-btn" class="btn-primary">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="currentColor">

View file

@ -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 };

View file

@ -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) {

View file

@ -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 `
<div class="track-item" data-track-id="${track.id}">
${trackNumberHTML}
@ -49,7 +58,7 @@ export class UIRenderer {
${trackTitle}
${explicitBadge}
</div>
<div class="artist">${trackArtists}</div>
<div class="artist">${trackArtists}${yearDisplay}</div>
</div>
</div>
<div class="track-item-duration">${formatTime(track.duration)}</div>
@ -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 `
<a href="#album/${album.id}" class="card">
<div class="card-image-wrapper">
<img src="${this.api.getCoverUrl(album.cover, '320')}" alt="${album.title}" class="card-image" loading="lazy">
</div>
<h3 class="card-title">${album.title} ${explicitBadge}</h3>
<p class="card-subtitle">Album ${album.artist?.name ?? ''}</p>
<p class="card-subtitle">${album.artist?.name ?? ''}</p>
<p class="card-subtitle">${yearDisplay}</p>
</a>
`;
}
@ -84,7 +101,6 @@ export class UIRenderer {
<img src="${this.api.getArtistPictureUrl(artist.picture, '320')}" alt="${artist.name}" class="card-image" loading="lazy">
</div>
<h3 class="card-title">${artist.name}</h3>
<p class="card-subtitle">Artist</p>
</a>
`;
}
@ -109,7 +125,7 @@ export class UIRenderer {
<div class="skeleton-card ${isArtist ? 'artist' : ''}">
<div class="skeleton skeleton-card-image"></div>
<div class="skeleton skeleton-card-title"></div>
<div class="skeleton skeleton-card-subtitle"></div>
${!isArtist ? '<div class="skeleton skeleton-card-subtitle"></div>' : ''}
</div>
`;
}
@ -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 = '<div class="skeleton" style="height: 48px; width: 300px; max-width: 90%;"></div>';
metaEl.innerHTML = '<div class="skeleton" style="height: 16px; width: 200px; max-width: 80%;"></div>';
prodEl.innerHTML = '<div class="skeleton" style="height: 16px; width: 200px; max-width: 80%;"></div>';
tracklistContainer.innerHTML = `
<div class="track-list-header">
<span style="width: 40px; text-align: center;">#</span>
@ -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 <a href="#artist/${album.artist.id}">${album.artist.name}</a> • ${dateDisplay}${tracks.length} tracks • ${formatDuration(totalDuration)}`;
(dateDisplay ? `${dateDisplay}` : '') +
`${tracks.length} tracks • ${formatDuration(totalDuration)}`;
prodEl.innerHTML =
`By <a href="#artist/${album.artist.id}">${album.artist.name}</a>` +
(firstCopyright ? `${firstCopyright}` : '');
tracklistContainer.innerHTML = `
<div class="track-list-header">