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:
co-authored by
Claude Opus 5
parent
9622da0284
commit
d8a1633cf4
@@ -7,6 +7,8 @@ export type Rolle = {
|
||||
id: string; userName: string; isAdministrator: boolean;
|
||||
/** Die bevorzugte Sprache — `null` heisst: die des Servers. */
|
||||
language?: string | null;
|
||||
/** Das eigene Bild, als Schlüssel in die Bildablage. `null`: Buchstabenkreis. */
|
||||
avatarImage?: string | null;
|
||||
};
|
||||
export type Spur = {
|
||||
index: number; kind: 'Video' | 'Audio' | 'Subtitle';
|
||||
@@ -212,6 +214,42 @@ export const istAdmin = (): boolean => Boolean(sitzung.wer?.isAdministrator);
|
||||
/** Die eingestellte Sprache dieses Benutzers — `null` heisst: die des Servers. */
|
||||
export const meineSprache = (): string | null => sitzung.wer?.language ?? null;
|
||||
|
||||
/** Das eigene Konto, frisch vom Server. */
|
||||
export const meinKonto = () => anfrage<Rolle>('/users/me');
|
||||
|
||||
/** Holt das Konto und schreibt die gemerkte Sitzung fort.
|
||||
*
|
||||
* **Weil die Sitzung nur bei der Anmeldung geschrieben wurde.** Wer sein Bild
|
||||
* am Telefon setzt und danach den Browser am Schreibtisch öffnet, fand dort
|
||||
* den alten Buchstaben — und zwar bis zum nächsten Anmelden. Dasselbe gilt für
|
||||
* die Sprache. Eine Anfrage beim Hochfahren macht aus der Kopie wieder einen
|
||||
* Stand.
|
||||
*
|
||||
* Scheitert sie, bleibt die alte Kopie stehen: lieber ein veraltetes Bild als
|
||||
* gar keine Oberfläche. */
|
||||
export async function kontoAbgleichen(): Promise<void> {
|
||||
try {
|
||||
const frisch = await meinKonto();
|
||||
if (sitzung.wer) { Object.assign(sitzung.wer, frisch); merken(); }
|
||||
} catch { /* Offline oder abgelehnt — dann gilt, was gespeichert ist. */ }
|
||||
}
|
||||
|
||||
/** Legt ein Profilbild ab und gibt seinen Schlüssel zurück. */
|
||||
export async function profilbildSetzen(datei: File): Promise<string> {
|
||||
const form = new FormData();
|
||||
form.append('datei', datei);
|
||||
const antwort = await anfrage<{ image: string }>('/users/me/avatar', { method: 'POST', body: form });
|
||||
// **Die Sitzung gleich mitziehen.** Die Kopfleiste liest aus ihr; ohne das
|
||||
// stünde dort bis zum nächsten Anmelden der alte Buchstabe.
|
||||
if (sitzung.wer) { sitzung.wer.avatarImage = antwort.image; merken(); }
|
||||
return antwort.image;
|
||||
}
|
||||
|
||||
export async function profilbildEntfernen(): Promise<void> {
|
||||
await anfrage<void>('/users/me/avatar', { method: 'DELETE' });
|
||||
if (sitzung.wer) { sitzung.wer.avatarImage = null; merken(); }
|
||||
}
|
||||
|
||||
/* ---------- Anfragen ---------- */
|
||||
|
||||
/** Ein deutscher Satz zu einem Status, den der Server nicht erklärt hat.
|
||||
|
||||
+34
-8
@@ -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.
|
||||
|
||||
@@ -147,6 +147,9 @@ svg { width: 20px; height: 20px; display: block; flex: none; }
|
||||
background: var(--accent); color: #fff;
|
||||
display: grid; place-items: center; font-size: 13px; font-weight: 600;
|
||||
}
|
||||
/* Mit eigenem Bild dieselbe Scheibe, nur gefüllt. */
|
||||
#topbar .konto img.kreis { object-fit: cover; background: var(--elevated); }
|
||||
#topbar .konto:hover .kreis { box-shadow: 0 0 0 2px var(--accent-soft); }
|
||||
|
||||
/* ---------- Grundgerüst ---------- */
|
||||
|
||||
@@ -1226,6 +1229,42 @@ body:has(.pl.pl-klein) .auswahlleiste { bottom: 92px; }
|
||||
.hero-bald { font-size: 12px; padding: 0 var(--s-snug); }
|
||||
}
|
||||
|
||||
/* --- Profil --------------------------------------------------------------
|
||||
*
|
||||
* **Die eigene Seite.** Ein Klick auf das eigene Bild meldete ab — ohne
|
||||
* Rückfrage und ohne Weg zurück. Hier steht stattdessen, wem das Konto
|
||||
* gehört, und Abmelden ist ein Knopf unter allem anderen. */
|
||||
|
||||
.profil { max-width: 720px; }
|
||||
|
||||
.profil-kopf {
|
||||
display: flex; align-items: center; gap: var(--s-section);
|
||||
margin-bottom: var(--s-section);
|
||||
}
|
||||
.profil-bild {
|
||||
flex: none; width: 96px; height: 96px; border-radius: 50%;
|
||||
background: var(--accent); color: #fff; overflow: hidden;
|
||||
display: grid; place-items: center; font-size: 36px; font-weight: 600;
|
||||
}
|
||||
.profil-bild img { width: 100%; height: 100%; object-fit: cover; }
|
||||
.profil-text { display: flex; flex-direction: column; gap: var(--s-snug); min-width: 0; }
|
||||
.profil-text h1 { margin: 0; font-size: 26px; font-weight: 700; letter-spacing: -.02em; }
|
||||
|
||||
.profil h2 {
|
||||
font-size: 15px; font-weight: 600; color: var(--text-dim);
|
||||
margin: 0 0 var(--s-regular);
|
||||
}
|
||||
|
||||
/* Abgesetzt, weil es die einzige Handlung hier ist, die etwas beendet. */
|
||||
.profil-abmelden {
|
||||
margin-top: var(--s-section); padding-top: var(--s-section);
|
||||
border-top: 1px solid var(--line);
|
||||
}
|
||||
|
||||
@media (max-width: 860px) {
|
||||
.profil-kopf { flex-direction: column; align-items: flex-start; gap: var(--s-loose); }
|
||||
}
|
||||
|
||||
/* --- Verwaltung ----------------------------------------------------------
|
||||
*
|
||||
* **Eine zweite Leiste, keine zweite Seite.** Die Verwaltung war elf
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
import {
|
||||
sitzung, meinKonto, profilbildSetzen, profilbildEntfernen, bildAdresse,
|
||||
spracheSetzen, sprachen, abmelden, istAdmin, type Rolle,
|
||||
} from '../api';
|
||||
import { esc, knopf, fehlerzeile, meldung, hinweisZeigen } from '../util';
|
||||
import { dialog, nachfragen } from '../dialog';
|
||||
import { icon } from '../icons';
|
||||
|
||||
/** Die eigene Seite.
|
||||
*
|
||||
* **Warum es sie gibt.** Ein Klick auf das eigene Bild meldete ab — ohne
|
||||
* Rückfrage, ohne Menü, ohne Weg zurück. Das ist die unfreundlichste Deutung,
|
||||
* die ein Bild mit dem eigenen Namen haben kann. Jetzt führt es hierher, und
|
||||
* Abmelden steht als eigener Knopf am Ende, mit Rückfrage.
|
||||
*
|
||||
* **Und eine Einstellung wird dadurch überhaupt erst erreichbar.** „Meine
|
||||
* Sprache" stand mitten in der Verwaltung; die zeigt der Client nur
|
||||
* Administratoren, der Endpunkt erlaubte sie längst jedem. Wer kein Admin war,
|
||||
* konnte seine Sprache nirgends ändern.
|
||||
*/
|
||||
export function profilseite(): HTMLElement {
|
||||
const wurzel = document.createElement('div');
|
||||
wurzel.className = 'profil';
|
||||
wurzel.innerHTML = '<div class="spinner"></div>';
|
||||
|
||||
void laden();
|
||||
return wurzel;
|
||||
|
||||
async function laden(): Promise<void> {
|
||||
let konto: Rolle;
|
||||
try {
|
||||
konto = await meinKonto();
|
||||
} catch (fehler) {
|
||||
wurzel.innerHTML = '';
|
||||
wurzel.appendChild(fehlerzeile(meldung(fehler)));
|
||||
return;
|
||||
}
|
||||
|
||||
// Die Sitzung mitziehen: die Kopfleiste liest aus ihr.
|
||||
if (sitzung.wer) Object.assign(sitzung.wer, konto);
|
||||
|
||||
wurzel.innerHTML = '';
|
||||
wurzel.append(kopf(konto), einstellungen(konto), abmeldeteil());
|
||||
}
|
||||
|
||||
/* ---------- Bild und Name ---------- */
|
||||
|
||||
function kopf(konto: Rolle): HTMLElement {
|
||||
const reihe = document.createElement('section');
|
||||
reihe.className = 'profil-kopf';
|
||||
|
||||
const bild = document.createElement('div');
|
||||
bild.className = 'profil-bild';
|
||||
const adresse = bildAdresse(konto.avatarImage ?? null);
|
||||
if (adresse) {
|
||||
const img = document.createElement('img');
|
||||
img.src = adresse;
|
||||
img.alt = '';
|
||||
// Kommt es nicht an, tritt der Buchstabe an seine Stelle — dieselbe
|
||||
// Vorsorge wie im Aufmacher.
|
||||
img.onerror = () => { img.remove(); bild.textContent = anfangsbuchstabe(konto.userName); };
|
||||
bild.appendChild(img);
|
||||
} else {
|
||||
bild.textContent = anfangsbuchstabe(konto.userName);
|
||||
}
|
||||
|
||||
const text = document.createElement('div');
|
||||
text.className = 'profil-text';
|
||||
text.innerHTML = `<h1>${esc(konto.userName)}</h1>`
|
||||
+ `<span class="hinweis">${konto.isAdministrator ? 'Administrator' : 'Benutzer'}</span>`;
|
||||
|
||||
const waehlen = document.createElement('input');
|
||||
waehlen.type = 'file';
|
||||
waehlen.accept = 'image/*';
|
||||
waehlen.className = 'hidden';
|
||||
waehlen.onchange = () => void hochladen(waehlen.files?.[0] ?? null);
|
||||
|
||||
const reihen = document.createElement('div');
|
||||
reihen.className = 'knopfreihe';
|
||||
|
||||
const neu = knopf(konto.avatarImage ? 'Bild tauschen' : 'Bild wählen', 'btn');
|
||||
neu.prepend(icon('stift'));
|
||||
neu.onclick = () => waehlen.click();
|
||||
reihen.appendChild(neu);
|
||||
|
||||
if (konto.avatarImage) {
|
||||
const weg = knopf('Entfernen', 'btn');
|
||||
weg.onclick = () => void entfernen();
|
||||
reihen.appendChild(weg);
|
||||
}
|
||||
|
||||
text.appendChild(reihen);
|
||||
reihe.append(bild, text, waehlen);
|
||||
return reihe;
|
||||
}
|
||||
|
||||
async function hochladen(datei: File | null): Promise<void> {
|
||||
if (!datei) return;
|
||||
// **Vorher prüfen, nicht hinterher.** Fünf Megabyte sind die Grenze des
|
||||
// Servers; sie hier zu kennen spart einen Upload, der mit 400 endet.
|
||||
if (datei.size > 5 * 1024 * 1024) {
|
||||
hinweisZeigen('Das Bild ist grösser als 5 MB. Nimm ein kleineres.');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await profilbildSetzen(datei);
|
||||
hinweisZeigen('Bild gesetzt.');
|
||||
await laden();
|
||||
kopfleisteAuffrischen();
|
||||
} catch (fehler) {
|
||||
hinweisZeigen(meldung(fehler));
|
||||
}
|
||||
}
|
||||
|
||||
async function entfernen(): Promise<void> {
|
||||
try {
|
||||
await profilbildEntfernen();
|
||||
hinweisZeigen('Bild entfernt.');
|
||||
await laden();
|
||||
kopfleisteAuffrischen();
|
||||
} catch (fehler) {
|
||||
hinweisZeigen(meldung(fehler));
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- Einstellungen, die jedem gehören ---------- */
|
||||
|
||||
function einstellungen(konto: Rolle): HTMLElement {
|
||||
const bereich = document.createElement('section');
|
||||
|
||||
const h = document.createElement('h2');
|
||||
h.textContent = 'Einstellungen';
|
||||
bereich.appendChild(h);
|
||||
|
||||
const liste = document.createElement('div');
|
||||
liste.className = 'einstellungen';
|
||||
|
||||
const gewaehlt = sprachen.find(s => s.wert === konto.language);
|
||||
liste.appendChild(zeile(
|
||||
'Meine Sprache',
|
||||
gewaehlt?.text ?? 'Wie der Server',
|
||||
'Die Metadaten liegen in der Sprache des Servers vor. Eine andere zu '
|
||||
+ 'wählen heisst: du siehst, was übersetzt vorliegt — und für den Rest '
|
||||
+ 'die Serversprache.',
|
||||
() => dialog({
|
||||
titel: 'Meine Sprache',
|
||||
text: 'Gilt nur für dich. Was nicht übersetzt vorliegt, steht weiter in '
|
||||
+ 'der Sprache des Servers.',
|
||||
felder: [{
|
||||
name: 'sprache', beschriftung: 'Sprache',
|
||||
auswahl: [{ wert: '', text: 'Wie der Server' }, ...sprachen],
|
||||
wert: konto.language ?? '',
|
||||
}],
|
||||
bestaetigen: 'Übernehmen',
|
||||
tun: async wert => {
|
||||
await spracheSetzen(wert('sprache') || null);
|
||||
location.reload();
|
||||
},
|
||||
})));
|
||||
|
||||
if (istAdmin()) {
|
||||
liste.appendChild(zeile(
|
||||
'Server einstellen',
|
||||
'Mediatheken, Wiedergabe, Metadaten',
|
||||
'Was für alle gilt, steht in der Verwaltung.',
|
||||
() => { location.hash = '#/verwaltung'; }, 'Öffnen'));
|
||||
}
|
||||
|
||||
bereich.appendChild(liste);
|
||||
return bereich;
|
||||
}
|
||||
|
||||
/* ---------- Abmelden ---------- */
|
||||
|
||||
function abmeldeteil(): HTMLElement {
|
||||
const bereich = document.createElement('section');
|
||||
bereich.className = 'profil-abmelden';
|
||||
|
||||
const k = knopf('Abmelden', 'btn btn-gefahr');
|
||||
k.prepend(icon('abmelden'));
|
||||
// **Mit Rückfrage.** Vorher war der Klick aufs Bild selbst das Abmelden —
|
||||
// eine Aktion ohne Weg zurück hinter einer Geste, die wie „mein Konto"
|
||||
// aussieht.
|
||||
k.onclick = () => nachfragen({
|
||||
titel: 'Abmelden',
|
||||
text: `Du meldest dich als „${sitzung.wer?.userName ?? ''}“ von diesem Gerät ab. `
|
||||
+ 'Sehstände und Favoriten bleiben auf dem Server.',
|
||||
bestaetigen: 'Abmelden',
|
||||
tun: async () => { abmelden(); location.reload(); },
|
||||
});
|
||||
|
||||
bereich.appendChild(k);
|
||||
return bereich;
|
||||
}
|
||||
}
|
||||
|
||||
/** Die Kopfleiste zeigt das Bild — nach einem Wechsel muss sie es neu lesen. */
|
||||
function kopfleisteAuffrischen(): void {
|
||||
window.dispatchEvent(new CustomEvent('flaro:konto'));
|
||||
}
|
||||
|
||||
export function anfangsbuchstabe(name: string): string {
|
||||
return (name.slice(0, 1) || '?').toUpperCase();
|
||||
}
|
||||
|
||||
/** Dieselbe Zeile wie in der Verwaltung — hier mit frei wählbarer Beschriftung. */
|
||||
function zeile(
|
||||
name: string, wert: string, hinweis: string, tun: () => void, text = 'Ändern',
|
||||
): HTMLElement {
|
||||
const d = document.createElement('div');
|
||||
d.className = 'einstellung';
|
||||
|
||||
const oben = document.createElement('div');
|
||||
oben.className = 'knopfreihe';
|
||||
const links = document.createElement('div');
|
||||
links.className = 'zeile';
|
||||
links.style.flex = '1';
|
||||
links.innerHTML = `<strong>${esc(name)}</strong><span class="winzig">${esc(wert)}</span>`;
|
||||
oben.appendChild(links);
|
||||
|
||||
const b = knopf(text, 'btn');
|
||||
b.prepend(icon('stift'));
|
||||
b.onclick = tun;
|
||||
oben.appendChild(b);
|
||||
d.appendChild(oben);
|
||||
|
||||
const h = document.createElement('div');
|
||||
h.className = 'hinweis';
|
||||
h.textContent = hinweis;
|
||||
d.appendChild(h);
|
||||
return d;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
sprachen, spracheSetzen, uebersetzungenHolen, meineSprache, metadatenEinstellung,
|
||||
sprachen, uebersetzungenHolen, meineSprache, metadatenEinstellung,
|
||||
metadatenSetzen, sprachverteilung, abweichendeAuffrischen,
|
||||
} from '../../api';
|
||||
import { knopf, fehlerzeile, meldung } from '../../util';
|
||||
@@ -115,35 +115,25 @@ export function sprachbereich(): HTMLElement {
|
||||
const weichtAb = eigeneSprache !== null && eigeneSprache !== stand.language;
|
||||
const serversprache = sprachen.find(x => x.wert === stand.language)?.text ?? stand.language;
|
||||
|
||||
liste.appendChild(zeile(
|
||||
weichtAb ? 'Meine Sprache — weicht ab' : 'Meine Sprache',
|
||||
eigeneSprache ? (sprachen.find(x => x.wert === eigeneSprache)?.text ?? eigeneSprache)
|
||||
: 'Wie der Server',
|
||||
weichtAb
|
||||
? `Du siehst alles auf ${sprachen.find(x => x.wert === eigeneSprache)?.text ?? eigeneSprache}, `
|
||||
+ `obwohl der Server auf ${serversprache} holt. Titel ohne Übersetzung bleiben `
|
||||
+ `auf ${serversprache}. Auf „Wie der Server" stellen, um zu sehen, was wirklich `
|
||||
+ 'gespeichert ist.'
|
||||
: 'Gilt nur für dich. Was nicht übersetzt vorliegt, erscheint weiterhin in '
|
||||
+ 'der Sprache des Servers.',
|
||||
() => dialog({
|
||||
titel: 'Meine Sprache',
|
||||
text: 'Die Metadaten liegen in der Sprache des Servers vor. Eine andere '
|
||||
+ 'zu wählen heisst: du siehst, was übersetzt vorliegt — und für den '
|
||||
+ 'Rest die Serversprache. Ein Administrator kann die Übersetzungen '
|
||||
+ 'unten für alle Titel auf einmal holen.',
|
||||
felder: [{
|
||||
name: 'sprache', beschriftung: 'Sprache',
|
||||
auswahl: [{ wert: '', text: 'Wie der Server' }, ...sprachen],
|
||||
wert: eigeneSprache ?? '',
|
||||
}],
|
||||
bestaetigen: 'Übernehmen',
|
||||
tun: async wert => {
|
||||
const neu = wert('sprache');
|
||||
await spracheSetzen(neu || null);
|
||||
location.reload();
|
||||
},
|
||||
})));
|
||||
// **Geändert wird sie im Profil, nicht hier.**
|
||||
//
|
||||
// Sie gehört einem Menschen, nicht dem Server — und sie stand in einem
|
||||
// Bereich, den der Client nur Administratoren zeigt. Wer kein Admin war,
|
||||
// konnte seine eigene Sprache nirgends umstellen, obwohl der Endpunkt es
|
||||
// längst erlaubte.
|
||||
//
|
||||
// Der Hinweis bleibt trotzdem hier: Die Verwirrung entsteht an dieser
|
||||
// Stelle. Wer die Mediathek englisch vorfindet und hier die Serversprache
|
||||
// auf Deutsch stehen sieht, sucht den Fehler sonst beim Auffrischen.
|
||||
if (weichtAb) {
|
||||
liste.appendChild(zeile(
|
||||
'Deine Sprache weicht ab',
|
||||
sprachen.find(x => x.wert === eigeneSprache)?.text ?? eigeneSprache!,
|
||||
`Du siehst alles auf ${sprachen.find(x => x.wert === eigeneSprache)?.text ?? eigeneSprache}, `
|
||||
+ `obwohl der Server auf ${serversprache} holt. Titel ohne Übersetzung bleiben `
|
||||
+ `auf ${serversprache}. Umstellen kannst du das in deinem Profil.`,
|
||||
() => { location.hash = '#/profil'; }));
|
||||
}
|
||||
|
||||
liste.appendChild(zeile(
|
||||
'Übersetzungen holen',
|
||||
|
||||
Reference in New Issue
Block a user