From ff166b27cacba1cf08ce43ab94e355f64fa0e7ac Mon Sep 17 00:00:00 2001 From: Julien Maille Date: Thu, 8 Jan 2026 13:00:48 +0100 Subject: [PATCH] Fix: Refactor playlist loading and sharing logic Resolves an issue where Tidal playlists with UUIDs were incorrectly identified as missing user playlists. Introduces explicit source (api/user) parameter to renderPlaylistPage for reliable routing. Restricts the Share button to user playlists only and fixes npm deprecation warnings. # Conflicts: # package.json --- js/app.js | 4 +- js/router.js | 4 +- js/ui.js | 57 +++++++++++++------------- package-lock.json | 100 +++++++++++++--------------------------------- package.json | 4 ++ 5 files changed, 63 insertions(+), 106 deletions(-) diff --git a/js/app.js b/js/app.js index ab7391a..56d5840 100644 --- a/js/app.js +++ b/js/app.js @@ -378,7 +378,7 @@ document.addEventListener('DOMContentLoaded', async () => { ui.renderLibraryPage(); // Also update current page if we are on it if (window.location.hash === `#userplaylist/${editingId}`) { - ui.renderPlaylistPage(editingId); + ui.renderPlaylistPage(editingId, 'user'); } modal.style.display = 'none'; delete modal.dataset.editingId; @@ -553,7 +553,7 @@ document.addEventListener('DOMContentLoaded', async () => { const trackId = playlist.tracks[index].id; const updatedPlaylist = await db.removeTrackFromPlaylist(playlistId, trackId); syncManager.syncUserPlaylist(updatedPlaylist, 'update'); - ui.renderPlaylistPage(playlistId); + ui.renderPlaylistPage(playlistId, 'user'); } }); } diff --git a/js/router.js b/js/router.js index fa3839d..22606b5 100644 --- a/js/router.js +++ b/js/router.js @@ -17,10 +17,10 @@ export function createRouter(ui) { ui.renderArtistPage(param); break; case 'playlist': - ui.renderPlaylistPage(param); + ui.renderPlaylistPage(param, 'api'); break; case 'userplaylist': - ui.renderPlaylistPage(param); + ui.renderPlaylistPage(param, 'user'); break; case 'mix': ui.renderMixPage(param); diff --git a/js/ui.js b/js/ui.js index bf560ab..ffc666d 100644 --- a/js/ui.js +++ b/js/ui.js @@ -1103,7 +1103,7 @@ async showFullscreenCover(track, nextTrack, lyricsManager, audioPlayer) { } } - async renderPlaylistPage(playlistId) { + async renderPlaylistPage(playlistId, source = null) { this.showPage('playlist'); const imageEl = document.getElementById('playlist-detail-image'); const titleEl = document.getElementById('playlist-detail-title'); @@ -1133,15 +1133,25 @@ async showFullscreenCover(track, nextTrack, lyricsManager, audioPlayer) { // Check if it's a user playlist (UUID format) const isUUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(playlistId); - const ownedPlaylist = await db.getPlaylist(playlistId); - let playlistData = ownedPlaylist; - - // If not in local DB, check if it's a public Firebase playlist - if (!playlistData) { - try { - playlistData = await syncManager.getPublicPlaylist(playlistId); - } catch (e) { - console.warn('Failed to check public Firebase playlists:', e); + let playlistData = null; + let ownedPlaylist = null; + + // Priority: + // 1. If source is 'user', check DB/Sync. + // 2. If source is 'api', check API. + // 3. If no source, check DB if UUID, then API. + + if (source === 'user' || (!source && isUUID)) { + ownedPlaylist = await db.getPlaylist(playlistId); + playlistData = ownedPlaylist; + + // If not in local DB, check if it's a public Firebase playlist + if (!playlistData) { + try { + playlistData = await syncManager.getPublicPlaylist(playlistId); + } catch (e) { + console.warn('Failed to check public Firebase playlists:', e); + } } } @@ -1221,9 +1231,9 @@ async showFullscreenCover(track, nextTrack, lyricsManager, audioPlayer) { }); document.title = `${playlistData.name || playlistData.title} - Monochrome`; } else { - // If it is a UUID, we know it won't be in the API. - if (isUUID) { - throw new Error('Playlist not found. If this is a custom playlist, make sure it is set to Public.'); + // If source was explicitly 'user' and we didn't find it, fail. + if (source === 'user') { + throw new Error('Playlist not found. If this is a custom playlist, make sure it is set to Public.'); } // Render API playlist @@ -1282,7 +1292,7 @@ async showFullscreenCover(track, nextTrack, lyricsManager, audioPlayer) { } // Render Actions (Shuffle + Share) - this.updatePlaylistHeaderActions(playlist, false, tracks, true); + this.updatePlaylistHeaderActions(playlist, false, tracks, false); recentActivityManager.addPlaylist(playlist); document.title = playlist.title || 'Artist Mix'; @@ -1608,28 +1618,15 @@ async showFullscreenCover(track, nextTrack, lyricsManager, audioPlayer) { fragment.appendChild(deleteBtn); } - // Share - if (showShare || playlist.isPublic) { + // Share (User Playlists Only) + if (showShare || (isOwned && playlist.isPublic)) { const shareBtn = document.createElement('button'); shareBtn.id = 'share-playlist-btn'; shareBtn.className = 'btn-secondary'; shareBtn.innerHTML = 'Share'; - // Determine URL based on type (User Playlist or API Playlist) - // User Playlists use #userplaylist/ID, API use #playlist/UUID - // But we don't have isOwned strictly here for that decision? - // Actually, if it's owned it's #userplaylist. - // If it's public firebase, it's also #userplaylist. - // If it's API, it's #playlist. - - // Heuristic: If it has `isPublic` property (even if false), it's likely a User/Firebase playlist structure. - // API playlists don't usually have `isPublic`. - - const isUserType = 'isPublic' in playlist || isOwned; - const prefix = isUserType ? 'userplaylist' : 'playlist'; - shareBtn.onclick = () => { - const url = `${window.location.origin}${window.location.pathname}#${prefix}/${playlist.id || playlist.uuid}`; + const url = `${window.location.origin}${window.location.pathname}#userplaylist/${playlist.id || playlist.uuid}`; navigator.clipboard.writeText(url).then(() => alert('Link copied to clipboard!')); }; fragment.appendChild(shareBtn); diff --git a/package-lock.json b/package-lock.json index 2026095..780c53a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -62,6 +62,7 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -2542,6 +2543,7 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -2750,6 +2752,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -4296,13 +4299,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", - "dev": true, - "license": "MIT" - }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -4323,6 +4319,14 @@ "sourcemap-codec": "^1.4.8" } }, + "node_modules/magic-string/node_modules/sourcemap-codec": { + "name": "@jridgewell/sourcemap-codec", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -4577,16 +4581,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -5053,20 +5047,6 @@ "dev": true, "license": "MIT" }, - "node_modules/source-map": { - "version": "0.8.0-beta.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", - "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", - "deprecated": "The work that was done in this beta branch won't be included in future versions", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "whatwg-url": "^7.0.0" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -5089,23 +5069,15 @@ } }, "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", "dev": true, "license": "BSD-3-Clause", "engines": { - "node": ">=0.10.0" + "node": ">= 12" } }, - "node_modules/sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "deprecated": "Please use @jridgewell/sourcemap-codec instead", - "dev": true, - "license": "MIT" - }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", @@ -5384,6 +5356,7 @@ "integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", @@ -5414,16 +5387,6 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", - "dev": true, - "license": "MIT", - "dependencies": { - "punycode": "^2.1.0" - } - }, "node_modules/type-fest": { "version": "0.16.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", @@ -5649,6 +5612,7 @@ "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -5749,25 +5713,6 @@ } } }, - "node_modules/webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-url": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", - "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", - "dev": true, - "license": "MIT", - "dependencies": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -6045,6 +5990,7 @@ "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "rollup": "dist/bin/rollup" }, @@ -6055,6 +6001,16 @@ "fsevents": "~2.3.2" } }, + "node_modules/workbox-build/node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, "node_modules/workbox-cacheable-response": { "version": "7.4.0", "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-7.4.0.tgz", diff --git a/package.json b/package.json index 992aaed..836f62d 100644 --- a/package.json +++ b/package.json @@ -24,5 +24,9 @@ "devDependencies": { "vite": "^7.3.0", "vite-plugin-pwa": "^1.2.0" + }, + "overrides": { + "sourcemap-codec": "npm:@jridgewell/sourcemap-codec@^1.4.14", + "source-map": "^0.7.4" } }