Ein Klick, der nichts tat, und niemand sagte warum

Eine Serienkachel kannte der Server als Titel nicht: „als gesehen markieren"
lief in eine 404, und weil das Kontextmenü die Ablehnung verschluckte, geschah
sichtbar gar nichts. Dasselbe beim Favoriten. UserItemState bekommt eine
Serienkennung, das Häkchen zieht alle Folgen mit, und was wirft, erscheint als
Meldung statt in der Browserkonsole.

Dazu eine Mehrfachauswahl in der Mediathek — gesehen, ungesehen, Metadaten
neu holen für das Gewählte, in einer Anfrage statt in zweihundert. Ein neues
Plakat steht sofort auf der Kachel, und ein Titel, den es nicht mehr gibt,
endet nicht mehr in einer Sackgasse.

Gemessen: ein POST auf /refresh ohne Rumpf antwortete nach der Erweiterung mit
404 — also genau der Aufruf „alles auffrischen", den es länger gibt als die
Auswahl. Der Rumpf wird jetzt von Hand gelesen, und beide Formen stehen in
einer Prüfung.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-10-05 18:28:33 +02:00
co-authored by Claude Opus 5
parent c854c4a09a
commit 557708b769
21 changed files with 2085 additions and 78 deletions
+42 -16
View File
@@ -23,6 +23,8 @@ export type Kachel = {
stand?: Stand | null;
/** Die Zahl offener Folgen, als Plakette oben rechts. */
offen?: number | null;
/** Wie viele Folgen es überhaupt sind — um die Plakette wiederherzustellen. */
gesamt?: number | null;
/** `null` heisst: wartet auf Freigabe. Ein Zeitpunkt in der Zukunft: geplant. */
freigabe?: string | null;
};
@@ -35,8 +37,9 @@ export const alsKachel = (e: Film | Serie): Kachel => ({
titel: e.title,
zweite: e.year ? String(e.year) : null,
bild: e.posterImage,
stand: 'state' in e ? e.state : null,
stand: e.state,
offen: 'unwatchedCount' in e ? e.unwatchedCount : null,
gesamt: 'episodeCount' in e ? e.episodeCount : null,
freigabe: e.publishAt,
});
@@ -67,6 +70,11 @@ export const angefangenAlsKachel = (e: Angefangen): Kachel => e.kind === 'episod
export function kachel(k: Kachel, oeffnen: (art: 'filme' | 'serien', id: string) => void): HTMLElement {
const knopf = document.createElement('button');
knopf.className = 'kachel';
// **Die Kennung steht am Knoten.** So kann ein Auswahlmodus über dem Raster
// arbeiten, ohne dass jede der fünf Stellen, die Kacheln baut, davon wissen
// muss — sie liest sie vom angeklickten Element ab.
knopf.dataset.id = k.id;
knopf.dataset.art = k.art;
knopf.onclick = () => oeffnen(k.art, k.id);
// **Das Kontextmenü an jeder Kachel, überall.** Gesehen, Favorit und
@@ -248,11 +256,14 @@ function kachelpunkte(
text: istGesehen ? 'Als ungesehen markieren' : 'Als gesehen markieren',
icon: 'hakenKreis',
tun: async () => {
await gesehen(k.id, !istGesehen);
// **Die Antwort des Servers gilt, nicht die Annahme hier.** Bei einer
// Serie zieht das Häkchen alle Folgen mit; was dabei herauskommt,
// weiss nur der Server.
k.stand = await gesehen(k.id, !istGesehen);
// **Die Kachel spiegelt den neuen Zustand selbst.** Die Liste neu zu
// laden verlöre Bildlauf und Filter — für einen Haken.
if (k.stand) k.stand.watched = !istGesehen;
hakenZeigen(knopf, !istGesehen);
k.offen = istGesehen ? (k.gesamt ?? k.offen) : 0;
hakenZeigen(knopf, k, !istGesehen);
},
},
{
@@ -260,8 +271,7 @@ function kachelpunkte(
icon: 'stern',
tun: async () => {
const neuerWert = !k.stand?.favourite;
await favorit(k.id, neuerWert);
if (k.stand) k.stand.favourite = neuerWert;
k.stand = await favorit(k.id, neuerWert);
},
},
];
@@ -286,7 +296,9 @@ function kachelpunkte(
punkte.push({
text: 'Bilder bearbeiten',
icon: 'stift',
tun: () => bilddialog(art, k.id, k.titel),
// **Die Kachel holt sich das neue Plakat selbst.** Vorher blieb das
// alte stehen, bis man die ganze Oberfläche neu lud.
tun: () => bilddialog(art, k.id, k.titel, () => void kachelAuffrischen(k, knopf)),
});
punkte.push({
text: 'Titel ändern',
@@ -354,18 +366,32 @@ async function kachelAuffrischen(k: Kachel, knopf: HTMLElement): Promise<void> {
}
}
/** Setzt oder entfernt den Haken, ohne die Kachel neu zu bauen. */
function hakenZeigen(knopf: HTMLElement, an: boolean): void {
/** Setzt oder entfernt den Haken, ohne die Kachel neu zu bauen.
*
* **Die Ecke zeigt eines von beidem.** Haken oder Zahl offener Folgen — beides
* nebeneinander wäre ein Widerspruch. Wird eine Serie abgehakt, geht die Zahl;
* wird das Häkchen genommen, kommt sie zurück.
*/
function hakenZeigen(knopf: HTMLElement, k: Kachel, an: boolean): void {
const plakat = knopf.querySelector('.plakat');
if (!plakat) return;
plakat.querySelector('.ecke.haken')?.remove();
plakat.querySelector('.ecke')?.remove();
knopf.querySelector('.fortschritt')?.remove();
if (!an) return;
const ecke = document.createElement('span');
ecke.className = 'ecke haken';
ecke.title = 'Gesehen';
ecke.innerHTML = icons.haken;
plakat.appendChild(ecke);
if (an) {
const ecke = document.createElement('span');
ecke.className = 'ecke haken';
ecke.title = 'Gesehen';
ecke.innerHTML = icons.haken;
plakat.appendChild(ecke);
return;
}
if (k.offen == null || k.offen <= 0) return;
const plakette = document.createElement('span');
plakette.className = 'ecke zahl';
plakette.textContent = String(k.offen);
plakette.title = `${k.offen} Folgen offen`;
plakat.appendChild(plakette);
}