Fix Unicode support and improve navigation functionality

- Fix album cover path generation for Unicode artist names (Beyoncé, Björk, etc.)
- Improve Next button to respect current filter/sort state instead of just incrementing rank
- Add proper Unicode character handling with regex property escapes
- Update README to document new Unicode support and smart navigation features

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Johan Lundberg 2025-07-02 01:40:16 +02:00
parent 75bbf157e7
commit dc0c775eb4
2 changed files with 52 additions and 10 deletions

View file

@ -17,7 +17,8 @@ A beautiful, interactive web application showcasing Rolling Stone's greatest alb
- **Complete Metadata**: Full album information including artist, year, label, and descriptions - **Complete Metadata**: Full album information including artist, year, label, and descriptions
- **Wikipedia Integration**: Direct links to Wikipedia pages for each album - **Wikipedia Integration**: Direct links to Wikipedia pages for each album
- **Spotify Integration**: Listen to albums directly on Spotify with one click - **Spotify Integration**: Listen to albums directly on Spotify with one click
- **Sequential Navigation**: Next button on each album for easy browsing - **Smart Navigation**: Next button that respects current filter/sort state for intuitive browsing
- **Unicode Support**: Proper handling of international artist names (Beyoncé, Björk, etc.)
- **Search & Filter**: Easy navigation with search, status filters, and sorting options - **Search & Filter**: Easy navigation with search, status filters, and sorting options
### Modern UI/UX ### Modern UI/UX

View file

@ -247,11 +247,26 @@ function renderAlbums() {
function getCoverImagePath(album) { function getCoverImagePath(album) {
if (!album.Artist || !album.Album || !album.Rank) return null; if (!album.Artist || !album.Album || !album.Rank) return null;
// Sanitize artist and album names to match the downloaded filenames
const safeArtist = album.Artist.replace(/[<>:"/\\|?*]/g, '').replace(/[^\w\s\-_.]/g, '').replace(/\s+/g, '_').slice(0, 100);
const safeAlbum = album.Album.replace(/[<>:"/\\|?*]/g, '').replace(/[^\w\s\-_.]/g, '').replace(/\s+/g, '_').slice(0, 100);
const rank = album.Rank.toString().padStart(3, '0'); const rank = album.Rank.toString().padStart(3, '0');
// For rank 32 (Beyoncé - Lemonade) and other special cases with Unicode
// we need to preserve the accented characters
let safeArtist = album.Artist;
let safeAlbum = album.Album;
// Match the Python script pattern: remove all non-word chars except spaces, hyphens, underscores, and dots
// But preserve Unicode characters (Python's \w includes Unicode, JS needs a different approach)
safeArtist = safeArtist.replace(/[<>:"/\\|?*]/g, ''); // Remove definitely problematic chars first
safeAlbum = safeAlbum.replace(/[<>:"/\\|?*]/g, '');
// Remove other punctuation but keep Unicode letters, digits, spaces, hyphens, underscores, dots
safeArtist = safeArtist.replace(/[^\p{L}\p{N}\s\-_.]/gu, '');
safeAlbum = safeAlbum.replace(/[^\p{L}\p{N}\s\-_.]/gu, '');
// Replace spaces with underscores
safeArtist = safeArtist.replace(/\s+/g, '_').slice(0, 100);
safeAlbum = safeAlbum.replace(/\s+/g, '_').slice(0, 100);
const filename = `rank_${rank}_${safeArtist}_${safeAlbum}.jpg`; const filename = `rank_${rank}_${safeArtist}_${safeAlbum}.jpg`;
return `covers/${filename}`; return `covers/${filename}`;
} }
@ -267,6 +282,11 @@ function createAlbumCard(album) {
const coverImagePath = getCoverImagePath(album); const coverImagePath = getCoverImagePath(album);
// Determine if this album has a next album in the current view
const currentIndex = filteredData.findIndex(a => a.Rank === album.Rank);
const hasNext = currentIndex !== -1 && currentIndex < filteredData.length - 1;
const nextAlbum = hasNext ? filteredData[currentIndex + 1] : null;
card.innerHTML = ` card.innerHTML = `
<div class="album-header-grid"> <div class="album-header-grid">
<div class="album-rank">#${album.Rank}</div> <div class="album-rank">#${album.Rank}</div>
@ -306,8 +326,8 @@ function createAlbumCard(album) {
Listen on Spotify Listen on Spotify
</a> </a>
</div> </div>
${album.Rank < 500 ? ` ${hasNext ? `
<button class="next-album-link next-album-link-wide" data-rank="${album.Rank}" title="Go to next album (#${album.Rank + 1})"> <button class="next-album-link next-album-link-wide" data-rank="${album.Rank}" title="Go to next album (#${nextAlbum.Rank})">
Next Album Next Album
</button> </button>
` : ''} ` : ''}
@ -330,10 +350,7 @@ function createAlbumCard(album) {
nextBtn.addEventListener('click', function(e) { nextBtn.addEventListener('click', function(e) {
e.stopPropagation(); // Prevent card click e.stopPropagation(); // Prevent card click
const currentRank = parseInt(this.getAttribute('data-rank')); const currentRank = parseInt(this.getAttribute('data-rank'));
const nextRank = currentRank + 1; navigateToNextAlbum(currentRank);
if (nextRank <= 500) {
jumpToRankNumber(nextRank);
}
}); });
} }
@ -726,6 +743,30 @@ function scrollToRank(rank) {
} }
} }
function navigateToNextAlbum(currentRank) {
// Find the current album in the filtered data
const currentIndex = filteredData.findIndex(album => album.Rank === currentRank);
if (currentIndex === -1) return;
// Get the next album in the current view
const nextIndex = currentIndex + 1;
if (nextIndex < filteredData.length) {
const nextAlbum = filteredData[nextIndex];
// Ensure we've loaded enough albums
const requiredPage = Math.ceil((nextIndex + 1) / itemsPerPage);
if (currentPage < requiredPage) {
currentPage = requiredPage;
renderAlbums();
}
// Scroll to the next album
setTimeout(() => scrollToRank(nextAlbum.Rank), 100);
}
}
function handleAlbumShare(rank, buttonElement) { function handleAlbumShare(rank, buttonElement) {
const albumUrl = generateAlbumUrl(rank); const albumUrl = generateAlbumUrl(rank);