From f3d54158d3b3a2da740ec6773f4c695447615764 Mon Sep 17 00:00:00 2001 From: marc Date: Fri, 18 Sep 2026 13:58:09 +0200 Subject: [PATCH] =?UTF-8?q?feat(web):=20Filter=20"=E2=9D=A4=20Gefolgt"=20p?= =?UTF-8?q?lus=20Suche=20auf=20der=20K=C3=BCnstlerseite?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Die Liste, wem man folgt, gehört dorthin, wo man Künstler ohnehin sucht — bisher gab es sie nur als waagerechte Reihe unter den Favoriten. getArtists liefert kein starred mit, die Markierungen kommen deshalb aus getStarred2 und werden 30 s gehalten, damit das Umschalten flott bleibt. Ein Herz-Klick verwirft diesen Zwischenspeicher sofort — sonst zeigte der Filter nach dem Entfolgen noch eine halbe Minute den alten Stand. Beim Testen aufgefallen. Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 6 ++++++ index.html | 8 ++++++++ src/main.js | 4 ++-- src/views/detail.js | 3 +++ src/views/home.js | 47 +++++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 64 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7877bc3..9c20dee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -101,6 +101,12 @@ Die Android-App zählt eigenständig weiter (mehrere APKs pro Release). Welche d - Wer ohne Künstler-Pin folgt, bekommt einen Hinweis: ohne Pinnung läuft die Deezer-Zuordnung über den Namen, und bei gleichnamigen Künstlern kämen dauerhaft die Neuheiten des Falschen. +- **Filter „❤ Gefolgt" auf der Künstlerseite (Web)** — die Künstlerseite hat jetzt zwei Chips + (*Alle* / *❤ Gefolgt (n)*) und ein Suchfeld. Damit ist die Liste, wem man folgt, dort, wo man + Künstler ohnehin sucht — statt nur als waagerechte Reihe unter den Favoriten. `getArtists` liefert + kein `starred` mit, die Markierungen kommen deshalb aus `getStarred2` und werden 30 s gehalten, + damit das Umschalten der Chips nicht jedes Mal nachfragt. Ein Herz-Klick verwirft diesen Zwischen- + speicher sofort — sonst zeigte der Filter nach dem Entfolgen noch eine halbe Minute den alten Stand. - **Zeitleiste „Neu von deinen Künstlern" (Web)** — eigener Punkt in der Seitenleiste. Zeigt die jüngsten Veröffentlichungen aller Künstler, denen du folgst: nach Monat gruppiert, neueste zuerst, pro Eintrag Cover, Künstler, Art (Album/Single/EP), Titelzahl und Datum. Umschaltbar auf 30 Tage, diff --git a/index.html b/index.html index 7b3bd0b..1c6f26f 100644 --- a/index.html +++ b/index.html @@ -147,6 +147,14 @@
+
+ + +
+
+ + +
diff --git a/src/main.js b/src/main.js index 13e3bc1..69c1fb4 100644 --- a/src/main.js +++ b/src/main.js @@ -20,7 +20,7 @@ import { navigate, _navGoBack, _navOnPopState, _navLoad, onHeaderSearch, closeHeaderSearch, onGlobalSearch, s_goArtist, s_goAlbum, playerBarGoArtist, playerBarGoAlbum, searchSimilarArtist, searchAndPreview, loadDeezerAlbum, closeEventsModal, - loadAlbums, loadStarred, loadRandom, filterStarred, filterPlaylist, + loadAlbums, loadArtists, filterArtists, loadStarred, loadRandom, filterStarred, filterPlaylist, plReorderEnter, plReorderSave, plReorderCancel, toggleAllGenres, } from './views/views.js'; import { loadSpotifyImporter, resolveSpotifyLink, clearSpotifyPreview, spotifyDlAll, @@ -367,7 +367,7 @@ Object.assign(window, { openCompare, closeCompare, closeArtistPin, clearArtistPin, // Views - loadAlbums, loadStarred, loadRandom, filterStarred, filterPlaylist, + loadAlbums, loadArtists, filterArtists, loadStarred, loadRandom, filterStarred, filterPlaylist, plReorderEnter, plReorderSave, plReorderCancel, toggleAllGenres, loadSpotifyImporter, resolveSpotifyLink, clearSpotifyPreview, spotifyDlAll, onImporterInput, resolveImporterLink, onGlobalInput, tryImporterResolve, diff --git a/src/views/detail.js b/src/views/detail.js index adfc154..4d6d511 100644 --- a/src/views/detail.js +++ b/src/views/detail.js @@ -537,6 +537,9 @@ function _verdrahteMerken(btn, id, markiert, textAn, textAus, meldungAn, meldung try { await api(vorher ? 'unstar' : 'star', `&id=${encodeURIComponent(id)}`); invalidateApiCache('getStarred2'); + // Der "Gefolgt"-Filter auf der Künstlerseite hält eine eigene kurze Liste — die muss mit weg, + // sonst zeigt er nach dem Entfolgen noch eine halbe Minute den alten Stand. + import('./home.js').then(m => m.invalidateGefolgt?.()).catch(() => {}); toast(an ? meldungAn : meldungAus); } catch (e) { an = vorher; malen(); diff --git a/src/views/home.js b/src/views/home.js index 2c14a7c..3b4b287 100644 --- a/src/views/home.js +++ b/src/views/home.js @@ -210,7 +210,12 @@ export async function loadAlbumRow(id, method, extra, force = false) { } // ─── ARTISTS ─── -export async function loadArtists() { +let _artistsFilter = 'all'; + +export async function loadArtists(filter = _artistsFilter) { + _artistsFilter = filter; + document.querySelectorAll('#artists-filter .sort-chip').forEach(c => + c.classList.toggle('active', c.dataset.filter === filter)); const g = document.getElementById('artists-grid'); if (!apiIsCached('getArtists')) g.innerHTML = '
'; try { @@ -218,8 +223,24 @@ export async function loadArtists() { const all = (r.artists?.index || []).flatMap(i => i.artist || []); cacheArtistSortNames(all); if (S.cfg.showRomaji) await fetchRomajiIndex().catch(() => {}); + + // "Gefolgt" = Navidromes Stern. getArtists liefert kein `starred` mit, die Liste kommt also + // aus getStarred2 — zwischengespeichert, damit das Umschalten nicht jedes Mal nachfragt. + const gefolgt = await _gefolgteIds(); + const zaehler = document.getElementById('artists-followed-count'); + if (zaehler) zaehler.textContent = gefolgt.size ? `(${gefolgt.size})` : ''; + + const liste = filter === 'followed' ? all.filter(a => gefolgt.has(a.id)) : all; g.innerHTML = ''; - all.forEach(a => { + const suche = document.getElementById('artists-search'); + if (suche) suche.value = ''; + if (!liste.length) { + g.innerHTML = filter === 'followed' + ? '
❤

Du folgst noch niemandem

Auf einer Künstlerseite oben rechts aufs Herz tippen.

' + : '
🎤

Keine Künstler

'; + return; + } + liste.forEach(a => { const c = makeCard(dispArtistById(a.id, a.name), a.albumCount+' Alben', null, '🎤'); c.classList.add('artist-card'); c.onclick = () => navigate('artist-detail', a); @@ -239,6 +260,28 @@ export async function loadArtists() { } catch(e) { g.innerHTML = `

${e.message}

`; } } +/** IDs der markierten Künstler; kurz gecacht, damit das Umschalten der Chips flott bleibt. */ +let _gefolgtCache = { ts: 0, ids: new Set() }; + +/** Nach einem Herz-Klick verwerfen — sonst zeigte der Filter bis zu 30 s den alten Stand. */ +export function invalidateGefolgt() { _gefolgtCache = { ts: 0, ids: _gefolgtCache.ids }; } +async function _gefolgteIds() { + if (Date.now() - _gefolgtCache.ts < 30_000) return _gefolgtCache.ids; + try { + const r = await api('getStarred2'); + _gefolgtCache = { ts: Date.now(), ids: new Set((r.starred2?.artist || []).map(a => a.id)) }; + } catch { /* Filter bleibt beim letzten bekannten Stand */ } + return _gefolgtCache.ids; +} + +export function filterArtists(val) { + const q = val.trim().toLowerCase(); + document.querySelectorAll('#artists-grid .card').forEach(c => { + const name = (c.querySelector('.card-title')?.textContent || '').toLowerCase(); + c.style.display = !q || name.includes(q) ? '' : 'none'; + }); +} + // ─── ALBUMS ─── let _albumSort = 'alphabeticalByName';