From d98978a6c7954272e6bba7f5a90aff8f893a41b8 Mon Sep 17 00:00:00 2001 From: Honey Date: Sun, 26 Jul 2026 23:27:29 +0200 Subject: [PATCH] feat(web): refresh the current view after deleting a song or playlist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Deletes left the removed item visible because the list loaders are cached (getPlaylists ~3 min): deletePlaylist navigated to a still-cached list, and deleteSong only reloaded playlists. Add refreshCurrentView() — drops the API cache and re-runs the active nav entry's loader (playlists, playlist/album/artist detail, starred, home, …). deleteSong calls it after the delete+rescan delay; deletePlaylist invalidates getPlaylists before navigating. Bump SW cache to ns-v46. Also record the post-1.2 work in the 1.2 changelog (importer Enter, search single-song + radio, NowPlaying order, official-audio artist-metadata match, JP-katakana album artist fix, year back-fill, delete refresh). Co-Authored-By: Claude Opus 4.8 --- CHANGELOG.md | 7 +++++++ public/sw.js | 2 +- src/ui/metadata.js | 10 ++++------ src/ui/modals.js | 3 ++- src/views/navigation.js | 22 ++++++++++++++++++++++ 5 files changed, 36 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5eab2d..63bdf1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,16 +61,23 @@ Follow-up to 1.1: web history navigation, correct download tagging for mixed-art ### Added - **Android** — Directional bottom-nav tab transitions: the slide follows the tapped tab's position (a tab to the right slides in from the right, one to the left from the left) instead of a fixed, sometimes-random-feeling direction. Applied across all four enter/exit/pop transitions; detail-screen push/pop is unchanged +- **Web** — Pressing **Enter** in the importer link field starts the import (same as the Importieren button) ### Fixed - **Web** — Browser / mouse **Forward** navigation now works (previously only Back did): `popstate` applies the target history entry (`nsIdx`) for both directions, and the back handler no longer `pushState()`s over the browser's forward stack. In-app back buttons route through `history.back()` so the browser and app stacks stay in sync +- **Web** — Deleting a song or playlist refreshes the current view immediately (drops the cached list) instead of leaving the removed item visible for up to ~3 minutes - **Web** — Album detail header showed the **first track's** artist (e.g. "TWICE" on a soundtrack) instead of the album artist; it now reads the album's own album-artist fields from `getAlbum` (matches the Android app) - **Web** — Metadata autofill (iTunes / MusicBrainz) normalizes the trailing `" & "` in a multi-artist credit ("A, B & C" → "A; B; C"), but only when the value is already a list, so band names like "Simon & Garfunkel" stay intact - **Android** — Erkunden (Apple) overlay back no longer *sometimes* jumped to Home: the root-level back callback is re-asserted on every navigation, so it can't lose back-priority to the NavHost after an album drill-down or tab switch - **Backend (download)** — Mixed-artist albums search **per track**: the YTM/YouTube query and channel filter use each track's own performer, so soundtrack tracks by other artists (TWICE's "Strategy", MeloMance's "Love, Maybe") are found instead of failing with "Nicht eindeutig gefunden" - **Backend (download)** — `album_artist` is derived from iTunes' album-level `collectionArtistName` (identical across the album) instead of the request's artist — which could be the lead single's performer, so a whole KPop soundtrack was tagged `album_artist = TWICE`. Applies to album and artist downloads; each track still gets its own per-track artist tag +- **Backend (download)** — Official audio is matched by yt-dlp's `artist` metadata, not just the channel name, so uploads on label channels (BANGTANTV, HYBE, "… - Topic") win over fan reuploads — e.g. BTS "Pied Piper" now grabs the official track. (`channel_is_verified` was rejected as a signal: lyric videos and fan MVs are also on verified channels) +- **Backend (download)** — Western artists are no longer tagged with the Japanese store's katakana album-artist ("DJスネイク" for DJ Snake): the JP-first iTunes lookup now keeps Japanese names only for Japanese songs and uses the US/English names otherwise (affects the `album_artist` tag and the on-disk folder) +- **Backend (download)** — Release year is back-filled from iTunes on single-track downloads where the source omitted it (the year tag was previously left empty) ### Changed +- **Web + Android** — Playing a song from search results queues only that song; Radio mode (if on) fills the rest — closer to Spotify. Album / playlist play still queues the whole list +- **Android** — NowPlaying track info is ordered title → album → artists, matching the web player - **Web** — NowPlaying favorite star is vertically centered with the whole title / album / artist block instead of pinned to the title line --- diff --git a/public/sw.js b/public/sw.js index 6b5e85b..b443590 100644 --- a/public/sw.js +++ b/public/sw.js @@ -1,4 +1,4 @@ -const CACHE = 'ns-v45'; +const CACHE = 'ns-v46'; const PRECACHE = ['/', 'index.html', 'manifest.json', 'icon.svg']; const SKIP_PATHS = ['/rest/stream', '/rest/download', '/config.js']; // Auth token params stripped from cache keys so artwork survives across sessions/token refreshes. diff --git a/src/ui/metadata.js b/src/ui/metadata.js index ce74e38..06501bb 100644 --- a/src/ui/metadata.js +++ b/src/ui/metadata.js @@ -2,7 +2,7 @@ import { S } from '../state.js'; import { esc, formatDur, toast } from '../utils.js'; import { api, coverUrl, fetchRomajiIndex, romajiLookup, getRomajiOverrides, invalidateRomajiIndex, invalidateApiCache } from '../api.js'; // Circular — safe (only used inside function bodies) -import { loadPlaylists, loadPlaylistDetail } from '../views/views.js'; +import { loadPlaylists, loadPlaylistDetail, refreshCurrentView } from '../views/views.js'; import { showConfirm } from './modals.js'; // ─── SONG INFO MODAL ─── @@ -312,11 +312,9 @@ export async function deleteSong() { if (!r.ok) throw new Error(d.error || `HTTP ${r.status}`); toast(`🗑 "${s.title}" gelöscht`); closeInfoModal(); - setTimeout(() => { - loadPlaylists(); - if (document.getElementById('page-playlist-detail')?.classList.contains('active') && S._currentPlaylist) - loadPlaylistDetail(S._currentPlaylist); - }, 3000); + // Backend delete + Navidrome rescan takes a moment; then refresh whatever view is open (album, + // artist, playlist, starred, …) so the removed song disappears without a manual reload. + setTimeout(() => refreshCurrentView(), 3000); } catch (e) { status.textContent = '❌ Fehler: ' + e.message; } diff --git a/src/ui/modals.js b/src/ui/modals.js index 10de8bf..675bba2 100644 --- a/src/ui/modals.js +++ b/src/ui/modals.js @@ -1,6 +1,6 @@ import { S } from '../state.js'; import { esc, toast } from '../utils.js'; -import { api, coverUrl, ndAuth } from '../api.js'; +import { api, coverUrl, ndAuth, invalidateApiCache } from '../api.js'; // Circular — safe (only used inside function bodies) import { playQueue, dlSongLocal, nativeInsertAt, renderQueue } from '../player/player.js'; import { navigate, loadPlaylists, loadPlaylistDetail } from '../views/views.js'; @@ -235,6 +235,7 @@ export async function deletePlaylist() { await api('deletePlaylist', `&id=${pl.id}`); closeEditPlModal(); toast(`🗑 "${pl.name}" gelöscht`); + invalidateApiCache('getPlaylists'); // drop the 3-min cache so the list reloads without the deleted playlist navigate('playlists'); } catch(e) { toast('Fehler beim Löschen: ' + e.message); } } diff --git a/src/views/navigation.js b/src/views/navigation.js index db1da4b..04268bf 100644 --- a/src/views/navigation.js +++ b/src/views/navigation.js @@ -8,6 +8,7 @@ import { loadHomeData, loadAlbumRow, loadArtists, loadAlbums, loadGenreDetail } import { loadArtistDetail, loadArtistAllSongs, loadAlbumDetail, loadItunesAlbumDetail, loadPartialAlbumDetail } from './detail.js'; import { loadPlaylists, loadPlaylistDetail, loadStarred, loadRandom } from './playlists.js'; import { loadNewReleases, loadRecommendations } from './discover.js'; +import { invalidateApiCache } from '../api.js'; // ─── NAVIGATION HISTORY ─── export function _navSave() { @@ -36,6 +37,27 @@ export function closeHeaderSearch() { clearTimeout(S._headerSearchTimer); } +// Force-refresh the active view after a mutating action (delete a song / playlist, etc.): drop the +// cached API responses so the reload is fresh, then re-run the current nav entry's loader. +export function refreshCurrentView() { + invalidateApiCache(); + const entry = S._navStack[S._navCursor]; + if (!entry) return; + const { page, data } = entry; + switch (page) { + case 'home': loadHomeData(true); break; + case 'albums': loadAlbums(); break; + case 'artists': loadArtists(); break; + case 'starred': loadStarred(); break; + case 'random': loadRandom(); break; + case 'playlists': loadPlaylists(); break; + case 'playlist-detail': if (data) loadPlaylistDetail(data); break; + case 'album-detail': if (data) loadAlbumDetail(data); break; + case 'artist-detail': if (data) loadArtistDetail(data); break; + case 'genre-detail': if (data) loadGenreDetail(data); break; + } +} + export function navigate(page, data=null) { closeHeaderSearch(); document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));