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:
parent
75bbf157e7
commit
dc0c775eb4
2 changed files with 52 additions and 10 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
59
script.js
59
script.js
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue