Das eigene Bild führte aus dem Server heraus

Ein Klick auf den Buchstabenkreis meldete ab. Keine Rückfrage, kein Menü,
kein Weg zurück — hinter einer Geste, die überall sonst „mein Konto"
heisst. Jetzt führt er auf `#/profil`; Abmelden steht dort als Knopf, mit
Nachfrage.

Auf der Seite: Bild wählen, tauschen, entfernen. Dazu „Meine Sprache" —
**umgezogen, nicht kopiert.** Sie stand mitten in der Verwaltung, und die
zeigt der Client nur Administratoren; der Endpunkt erlaubte sie längst
jedem. Wer kein Admin war, konnte seine eigene Sprache nirgends ändern.
Der Warnhinweis „weicht ab" bleibt in der Verwaltung, denn dort entsteht
die Verwirrung — er verweist jetzt aufs Profil.

**Vorher musste ein Fehler weg, der seit Monaten dasteht.**
`ImageCache.StoreStreamAsync` baute den Schlüssel aus 32 Hexzeichen plus
Dateiendung; `PfadFür` verlangt genau 64 Hexzeichen und nichts sonst. Es
gab keinen Wert, den beide gelten liessen: **jeder Bildupload gab `null`
zurück und endete in einem 502.** Kein Test fragte je danach — jetzt vier.
Damit geht nebenbei auch „eigenes Bild hochladen" für Filme und Serien
wieder.

Weil der Schlüssel keine Endung mehr trägt, kommt der Medientyp beim
Ausliefern aus den ersten Bytes der Datei. Vorher ging alles als
`image/jpeg` hinaus — bei TMDB stimmte das, bei einem hochgeladenen PNG
nicht.

**Und die gemerkte Sitzung war eine Kopie, die nie nachzog.** Sie wurde
bei der Anmeldung geschrieben und danach nie wieder. Wer sein Bild am
Telefon setzt und den Browser am Schreibtisch öffnet, fand dort den alten
Buchstaben — bis zum nächsten Anmelden. Gemessen und behoben: beim
Hochfahren wird das Konto abgeglichen, und das Profil meldet Änderungen
an die Kopfleiste, damit sie ohne Neuladen nachzieht.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-10-06 17:07:40 +02:00
co-authored by Claude Opus 5
parent 9622da0284
commit d8a1633cf4
18 changed files with 1757 additions and 57 deletions
+34 -8
View File
@@ -1,10 +1,10 @@
import './style.css';
import {
systemauskunft, angemeldet, istAdmin, abmelden, sitzung, bibliotheken,
reihenfolgeSetzen,
systemauskunft, angemeldet, istAdmin, sitzung, bibliotheken,
reihenfolgeSetzen, bildAdresse, kontoAbgleichen,
type Hinweis, type Bibliothek,
} from './api';
import { icons, icon } from './icons';
import { icons } from './icons';
import { einrichtung } from './views/setup';
import { einhaengen, ablegen, vergessen } from './ansichten';
import { anmeldung } from './views/login';
@@ -13,6 +13,7 @@ import { mediathek } from './views/library';
import { detailseite } from './views/detail';
import { verwaltung, alsBereich } from './views/verwaltung';
import { suchseite } from './views/search';
import { profilseite } from './views/profil';
import { esc, meldung } from './util';
const anmeldeflaeche = document.getElementById('login')!;
@@ -45,10 +46,18 @@ async function hochfahren(): Promise<void> {
// **Vor dem ersten Zeichnen.** Die Seitenleiste führt jede Mediathek
// einzeln auf; ohne die Liste könnte der Router `#/m/{id}` nicht
// auflösen.
mediatheken = await bibliotheken();
// **Das Konto frisch holen, bevor die Kopfleiste steht.** Sie zeigt Bild
// und Namen aus der gemerkten Sitzung, und die stammt von der Anmeldung —
// ein Bild, das anderswo gesetzt wurde, käme hier nie an.
await Promise.all([kontoAbgleichen(), (async () => {
mediatheken = await bibliotheken();
})()]);
kopfleisteBauen();
leisteBauen();
// Das Profil meldet, wenn sich am Konto etwas geändert hat — dann zeigt
// die Kopfleiste das neue Bild sofort und nicht erst nach dem Neuladen.
window.addEventListener('flaro:konto', kopfleisteBauen);
window.addEventListener('hashchange', zeichnen);
zeichnen();
} catch (fehler) {
@@ -144,13 +153,27 @@ function kopfleisteBauen(): void {
const rechts = document.createElement('div');
rechts.className = 'rechts';
// **Das eigene Bild führt zum eigenen Konto, nicht aus dem Server heraus.**
//
// Hier stand `abmelden()` — ein Klick, keine Rückfrage, kein Weg zurück,
// hinter einer Geste, die überall sonst „mein Konto" heisst. Abmelden gibt
// es weiterhin, auf der Profilseite und mit Nachfrage.
const konto = document.createElement('button');
konto.className = 'konto';
const name = sitzung.wer?.userName ?? '';
konto.innerHTML = `<span class="kreis">${esc(name.slice(0, 1).toUpperCase())}</span>`;
konto.title = `${name} — abmelden`;
konto.appendChild(icon('abmelden'));
konto.onclick = () => { abmelden(); location.reload(); };
const bild = bildAdresse(sitzung.wer?.avatarImage ?? null);
if (bild) {
konto.innerHTML = `<img class="kreis" src="${bild}" alt="">`;
// Kommt es nicht an, tritt der Buchstabe an seine Stelle.
konto.querySelector('img')!.onerror = () => {
konto.innerHTML = `<span class="kreis">${esc(name.slice(0, 1).toUpperCase())}</span>`;
};
} else {
konto.innerHTML = `<span class="kreis">${esc(name.slice(0, 1).toUpperCase())}</span>`;
}
konto.title = `${name} — mein Profil`;
konto.setAttribute('aria-label', `${name} — mein Profil`);
konto.onclick = () => { location.hash = '#/profil'; };
rechts.appendChild(konto);
kopfleiste.append(marke, suche, rechts);
@@ -400,6 +423,9 @@ function zeichnen(): void {
} else if (teile[0] === 'suche' && kennung) {
einhaengen(weg, inhalt, () => suchseite(decodeURIComponent(kennung), oeffnen),
{ merken: true });
} else if (teile[0] === 'profil') {
// Nicht gemerkt: das Bild kann sich ändern, und ein Profil ist billig.
einhaengen(weg, inhalt, () => profilseite());
} else if (teile[0] === 'verwaltung' && istAdmin()) {
// **Die Verwaltung auch nicht** — sie zeigt Laufendes. Ihr Takt hängt am
// Signal und hört auf, sobald man weggeht.