feat(web): Filter "❤ Gefolgt" plus Suche auf der Künstlerseite

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 <noreply@anthropic.com>
This commit is contained in:
marcandClaude Opus 5 committed 2026-09-18 13:58:09 +02:00
1 parent 908488a337
commit f3d54158d3
5 files changed
+64 -4

No files matched your search

+3
View File
@@ -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();
+45 -2
View File
@@ -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 = '<div class="spinner"></div>';
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'
? '<div class="empty-state"><div class="icon">❤</div><p>Du folgst noch niemandem</p><p style="font-size:12px;color:var(--text3);margin-top:6px">Auf einer Künstlerseite oben rechts aufs Herz tippen.</p></div>'
: '<div class="empty-state"><div class="icon">🎤</div><p>Keine Künstler</p></div>';
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 = `<p style="color:var(--danger)">${e.message}</p>`; }
}
/** 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';