Support: melden mit Bild, und für die Leitung ein Eingang
Filipe: „da fehlt auch eine support kachel. die jeder sieht. jeder
benutzen kann. jeder kann da probleme von der seite melden und
hinweisen. fotos mit schicken mit einem text. … so einfach wie
möglich, will nichts kompliziertes oder zu viel. kurz und knapp …
bei dogfather und der rechten hand soll die kachel anders gebaut sein
weil die sind die die sich um die probleme kümmern."
WARUM DAS NICHT DER VORHANDENE MELDEWEG IST. Es gibt ihn schon
(hilfe.html) und er sieht aehnlich aus. Drei Unterschiede machen ihn
zu etwas anderem:
1. Er ist NICHT fuer alle -- in rechte.js steht
`["gast","hand","admin"]`. Modis, Creator, Scouts, Manager und
die linke Hand hatten gar keinen Weg, einen kaputten Knopf zu
melden.
2. Er kann keine Bilder. Bei „der Knopf tut nichts" ist ein
Bildschirmfoto die halbe Antwort.
3. Er ist bewusst schwer: strikter Wechsel, hoechstens drei offene
Faelle, ein Betreff. Richtig bei einem Vorfall zwischen
Menschen, zu viel fuer „das Datum steht falsch da".
Deshalb eigen und sehr kurz: EIN Feld, EIN Bild, fertig. Kein
Betreff, keine Kategorie, keine Dringlichkeitsstufe -- wer ein
Formular mit fuenf Feldern sieht, meldet den kleinen Fehler nicht,
und genau die kleinen erfaehrt sonst niemand.
DIE LEITUNG SIEHT ETWAS ANDERES: einen Eingang mit Zahlenband
(neu / in Arbeit / erledigt), drei Filtern und zwei Handgriffen --
„Ich kuemmere mich" und „Erledigt …". Zwei, nicht fuenf; eine
Zuweisung an eine Person und Prioritaeten waeren ein Ticketsystem
fuer zwei Menschen, die nebeneinander sitzen. Das Melde-Feld bleibt
auch fuer sie da, rutscht aber unter den Eingang.
Wer zumacht, schreibt einen Satz dazu -- der Melder sieht nur diesen
Satz, und ohne ihn weiss er nicht, ob etwas behoben wurde oder ob
niemand Zeit hatte. Beide Seiten bekommen eine Benachrichtigung.
UEBERNOMMEN STATT NEU ERFUNDEN:
* `dateiErkennen` aus workspace-chat.js -- EXPORTIERT, nicht
abgeschrieben. An ihr haengt die ganze Sicherheit der Uploads
(der Typ kommt aus den ersten Bytes, nicht aus Name oder
Content-Type), und eine zweite Fassung waere die, die beim
naechsten Dateiformat vergessen wird.
* Die zweigeteilte Sicht und `meldungFuer()` (gibt den Datensatz
oder null zurueck, nie true/false) aus dem vertraulichen
Meldeweg.
* Der Upload-Weg (express.raw, Text im Kopf, Ordner unter
DATEN_ORDNER, damit die Sicherungspruefung ihn findet).
DIE KACHEL BEKOMMT JEDER -- an EINER Stelle angehaengt: `bereicheFuer`
haengt sie an jede Rollenliste, statt sie in fuenf Listen
einzutragen. Genau so ist heute der Benachrichtigungs-Knopf auf 14
Seiten verschwunden. Dazu der Eintrag in rechte.js (ohne ihn
verschwindet die Kachel lautlos, `nurOffeneKacheln` filtert dagegen)
und in der Browser-Kachelliste fuer die Agentur-Rollen.
TON 44 GERECHNET, NICHT GEWAEHLT -- und dabei zweimal danebengelegen:
* Ich habe erst einen eigenen Modus in kachel-farben.mjs gebaut.
`tools/kachel-farbe-einzeln.mjs` gibt es aber seit dem 22.09. fuer
genau diesen Zweck. Meine Dopplung ist wieder weg.
* Von Hand hatte ich #bcdbff eingetragen. pruef-kachelfarben lehnte
es ab (Buntheit 0,060, verlangt sind 0,12) -- und das vorhandene
Werkzeug verteidigte es trotzdem, weil sein Abstand gut war. Ein
Werkzeug, das einen regelwidrigen Zustand haelt, weil er zufaellig
gut misst, behebt genau den Fehler nicht, fuer den es da ist.
Behoben: Zuerst die Regeln, dann der Abstand. Ergebnis #fd6401,
Abstand 0,0914 (engstes vorhandenes Paar: 0,0154).
DREI MESSFEHLER VON MIR, die wie schwere Befunde aussahen: `fetch`
verwirft den Host-Kopf (die Community kommt nur auf crew. herein),
die Community bestaetigt ihr Alter (`alter_ok`), und die Startroute
heisst /api/ich. Alle drei stehen in pruef-treff.mjs richtig.
Ein ECHTER Fehler kam dazu: support.html lud bereiche.js nicht, und
kopf.js stuerzte mit „Cannot read properties of undefined (reading
'LEITUNG')" ab -- sichtbar nur in der Browserkonsole. Und
pruef-css-klassen fand sofort, dass auch wahl.js fehlte.
Gemessen: pruef-support 45 Pruefungen, 0 Fehler (alle neun Rollen
kommen herein, jede sieht die Kachel, als Bild getarntes HTML wird
abgelehnt, eine fremde Meldung bleibt fremd, erledigt bleibt
erledigt). Dazu 24 Messungen am Bild in drei Groessen. Gruen:
pruef-kachelfarben (22), pruef-css-klassen.
Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,433 @@
|
||||
/* =====================================================================
|
||||
SUPPORT (24.09.2026)
|
||||
|
||||
Filipe: „jeder kann da probleme von der seite melden und hinweisen.
|
||||
fotos mit schicken mit einem text. … so einfach wie möglich, will
|
||||
nichts kompliziertes oder zu viel. kurz und knapp."
|
||||
|
||||
EINE SEITE, ZWEI ANSICHTEN. Wer meldet, sieht ein Feld und seine
|
||||
eigenen Meldungen. DogFather und die rechte Hand sehen den Eingang:
|
||||
alles, nach Stand sortiert, mit Bild und zwei Knöpfen.
|
||||
|
||||
WELCHE ANSICHT GILT, ENTSCHEIDET DER SERVER. Er schickt `leitung`
|
||||
in der Antwort — diese Datei fragt nicht nach der Rolle. Eine
|
||||
Rollenprüfung im Browser wäre eine zweite Stelle mit derselben
|
||||
Regel, und es wäre die, an der man sie umgehen kann.
|
||||
|
||||
KEIN innerHTML MIT FREMDEM TEXT. Alles, was jemand geschrieben hat,
|
||||
geht durch `textContent`. Hier landet Text aus einem Fehlerbericht —
|
||||
und genau dort steht irgendwann ein `<script>`, weil jemand es
|
||||
ausprobiert.
|
||||
===================================================================== */
|
||||
(() => {
|
||||
'use strict';
|
||||
|
||||
const $ = (id) => document.getElementById(id);
|
||||
const el = (was, klasse, text) => {
|
||||
const e = document.createElement(was);
|
||||
if (klasse) e.className = klasse;
|
||||
if (text !== undefined) e.textContent = text;
|
||||
return e;
|
||||
};
|
||||
|
||||
let leitung = false;
|
||||
let meldungen = [];
|
||||
let filter = 'offen';
|
||||
let gewaehltesBild = null;
|
||||
|
||||
/* Anzeige-Wörter, keine Regel. Der Server schickt den Schlüssel; was
|
||||
daraus auf dem Bildschirm wird, ist eine Frage der Sprache. */
|
||||
const STAND_WORT = {
|
||||
neu: 'neu',
|
||||
in_arbeit: 'wird bearbeitet',
|
||||
erledigt: 'erledigt',
|
||||
};
|
||||
|
||||
function melde(id, text) {
|
||||
const f = $(id);
|
||||
if (!f) return;
|
||||
f.textContent = text || '';
|
||||
f.hidden = !text;
|
||||
}
|
||||
|
||||
async function hole(pfad, einstellungen) {
|
||||
const a = await fetch(pfad, { credentials: 'same-origin', ...einstellungen });
|
||||
if (a.status === 401) { location.href = 'index.html'; throw new Error('weg'); }
|
||||
return a;
|
||||
}
|
||||
|
||||
/** Was der Server als Fehler geschickt hat — oder ein Ersatz.
|
||||
*
|
||||
* Die Antwort wegzuwerfen und „Ging nicht" zu schreiben schickt
|
||||
* einen auf die Suche nach etwas, das der Server längst gesagt hat. */
|
||||
async function fehlerText(a, ersatz) {
|
||||
try {
|
||||
const j = await a.json();
|
||||
return typeof j?.fehler === 'string' && j.fehler ? j.fehler : ersatz;
|
||||
} catch { return ersatz; }
|
||||
}
|
||||
|
||||
/** „vor 3 Tagen" statt eines Zeitstempels.
|
||||
*
|
||||
* Bei einer Meldung ist die Frage „wie lange liegt das schon?",
|
||||
* nicht „wann genau war das?". Das genaue Datum bleibt als Titel. */
|
||||
function herZeit(iso) {
|
||||
if (!iso) return '';
|
||||
const ms = Date.now() - Date.parse(iso);
|
||||
if (!Number.isFinite(ms)) return '';
|
||||
const min = Math.floor(ms / 60000);
|
||||
if (min < 2) return 'gerade eben';
|
||||
if (min < 60) return `vor ${min} Min.`;
|
||||
const std = Math.floor(min / 60);
|
||||
if (std < 24) return `vor ${std} Std.`;
|
||||
const tage = Math.floor(std / 24);
|
||||
if (tage === 1) return 'gestern';
|
||||
if (tage < 31) return `vor ${tage} Tagen`;
|
||||
const monate = Math.floor(tage / 30.44);
|
||||
return monate < 12 ? `vor ${monate} Mon.` : 'vor über einem Jahr';
|
||||
}
|
||||
|
||||
function genau(iso) {
|
||||
if (!iso) return '';
|
||||
const d = new Date(iso);
|
||||
if (Number.isNaN(d.getTime())) return '';
|
||||
const z = (n) => String(n).padStart(2, '0');
|
||||
return `${z(d.getDate())}.${z(d.getMonth() + 1)}.${d.getFullYear()} `
|
||||
+ `${z(d.getHours())}:${z(d.getMinutes())}`;
|
||||
}
|
||||
|
||||
/* ===================================================================
|
||||
MELDEN
|
||||
=================================================================== */
|
||||
|
||||
function bildZeigen(datei) {
|
||||
gewaehltesBild = datei || null;
|
||||
const name = $('bild-name');
|
||||
const weg = $('bild-weg');
|
||||
const vorschau = $('vorschau');
|
||||
if (!datei) {
|
||||
name.textContent = '';
|
||||
weg.hidden = true;
|
||||
vorschau.hidden = true;
|
||||
$('vorschau-bild').removeAttribute('src');
|
||||
return;
|
||||
}
|
||||
name.textContent = `${datei.name} · ${Math.round(datei.size / 1024)} KB`;
|
||||
weg.hidden = false;
|
||||
/* DIE VORSCHAU KOMMT AUS DER DATEI SELBST, nicht vom Server: Sie
|
||||
ist noch gar nicht hochgeladen. `URL.createObjectURL` zeigt das
|
||||
Bild aus dem Arbeitsspeicher — wer sieht, was er anhängt, hängt
|
||||
nicht versehentlich das falsche an. */
|
||||
const url = URL.createObjectURL(datei);
|
||||
const img = $('vorschau-bild');
|
||||
img.onload = () => URL.revokeObjectURL(url);
|
||||
img.src = url;
|
||||
vorschau.hidden = false;
|
||||
}
|
||||
|
||||
function meldenEinrichten() {
|
||||
$('bild-waehlen').addEventListener('click', () => $('bild').click());
|
||||
$('bild-weg').addEventListener('click', () => {
|
||||
$('bild').value = '';
|
||||
bildZeigen(null);
|
||||
});
|
||||
$('bild').addEventListener('change', (e) => {
|
||||
const datei = e.target.files?.[0] || null;
|
||||
if (!datei) { bildZeigen(null); return; }
|
||||
/* DIE GRENZE STEHT HIER NUR ALS FREUNDLICHKEIT. Abgelehnt wird
|
||||
am Server; dieser Hinweis erspart es, zwölf Megabyte erst
|
||||
hochzuladen, um danach eine Absage zu lesen. */
|
||||
if (datei.size > 12 * 1024 * 1024) {
|
||||
melde('melden-fehler', 'Das Bild ist größer als 12 MB – bitte ein kleineres.');
|
||||
$('bild').value = '';
|
||||
bildZeigen(null);
|
||||
return;
|
||||
}
|
||||
melde('melden-fehler', '');
|
||||
bildZeigen(datei);
|
||||
});
|
||||
|
||||
$('melden').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const text = $('text').value.trim();
|
||||
if (text.length < 5) {
|
||||
melde('melden-fehler', 'Schreib bitte kurz, was los ist – ein Satz reicht.');
|
||||
$('text').focus();
|
||||
return;
|
||||
}
|
||||
const knopf = $('senden');
|
||||
knopf.disabled = true;
|
||||
melde('melden-fehler', '');
|
||||
$('melden-hinweis').textContent = 'wird geschickt …';
|
||||
try {
|
||||
/* DER TEXT REIST IM KOPF, das Bild im Rumpf — derselbe Weg wie
|
||||
bei Chat-Anhängen. Ein Weg für beides, damit es keinen
|
||||
Zustand „Meldung da, Bild fehlt noch" gibt. */
|
||||
const kopf = { 'x-text': encodeURIComponent(text), 'x-seite': location.pathname };
|
||||
const a = await hole('/workspace/api/support', {
|
||||
method: 'POST',
|
||||
headers: kopf,
|
||||
body: gewaehltesBild || new Blob([]),
|
||||
});
|
||||
if (!a.ok) {
|
||||
melde('melden-fehler', await fehlerText(a, 'Das hat nicht geklappt.'));
|
||||
return;
|
||||
}
|
||||
$('text').value = '';
|
||||
$('bild').value = '';
|
||||
bildZeigen(null);
|
||||
$('melden-hinweis').textContent = 'Danke – ist angekommen.';
|
||||
setTimeout(() => { $('melden-hinweis').textContent = ''; }, 6000);
|
||||
await laden();
|
||||
} catch { /* umgeleitet */ } finally {
|
||||
knopf.disabled = false;
|
||||
if ($('melden-hinweis').textContent === 'wird geschickt …') {
|
||||
$('melden-hinweis').textContent = '';
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* ===================================================================
|
||||
EINE MELDUNG ALS KARTE
|
||||
=================================================================== */
|
||||
|
||||
function bildAnhaengen(karte, m) {
|
||||
if (!m.bild) return;
|
||||
const kasten = el('div', 's-karte__bild');
|
||||
const a = el('a');
|
||||
a.href = m.bild;
|
||||
a.target = '_blank';
|
||||
a.rel = 'noopener';
|
||||
a.title = 'In voller Größe ansehen';
|
||||
const img = document.createElement('img');
|
||||
img.src = m.bild;
|
||||
img.alt = 'Bild zur Meldung';
|
||||
img.loading = 'lazy';
|
||||
/* BREITE UND HÖHE STEHEN DRAN, wenn der Server sie kennt: Dann
|
||||
reserviert der Browser den Platz, bevor das Bild da ist, und die
|
||||
Liste springt beim Laden nicht. */
|
||||
if (m.bild_breite > 0 && m.bild_hoehe > 0) {
|
||||
img.width = m.bild_breite;
|
||||
img.height = m.bild_hoehe;
|
||||
}
|
||||
a.append(img);
|
||||
kasten.append(a);
|
||||
karte.append(kasten);
|
||||
}
|
||||
|
||||
function karteBauen(m) {
|
||||
const k = el('article', 's-karte');
|
||||
k.dataset.stand = m.stand;
|
||||
k.id = `meldung-${m.id}`;
|
||||
|
||||
const kopf = el('div', 's-karte__kopf');
|
||||
const marke = el('span', 's-marke', STAND_WORT[m.stand] || m.stand);
|
||||
marke.dataset.stand = m.stand;
|
||||
kopf.append(marke);
|
||||
if (leitung && m.von) {
|
||||
kopf.append(el('span', 's-karte__von', m.von));
|
||||
}
|
||||
const zeit = el('span', 's-karte__zeit', herZeit(m.erstellt));
|
||||
zeit.title = genau(m.erstellt);
|
||||
kopf.append(zeit);
|
||||
k.append(kopf);
|
||||
|
||||
/* `textContent` mit erhaltenen Zeilenumbrüchen (das CSS setzt
|
||||
`white-space: pre-wrap`). Kein innerHTML, siehe der Kopf. */
|
||||
k.append(el('p', 's-karte__text', m.text));
|
||||
bildAnhaengen(k, m);
|
||||
|
||||
if (leitung && m.seite) {
|
||||
const wo = el('p', 's-karte__wo', 'gemeldet auf ' + m.seite);
|
||||
if (m.geraet) wo.title = m.geraet;
|
||||
k.append(wo);
|
||||
}
|
||||
|
||||
if (m.antwort) {
|
||||
const ant = el('div', 's-antwort');
|
||||
ant.append(el('span', 's-antwort__schild', 'Antwort'));
|
||||
ant.append(el('p', 's-antwort__text', m.antwort));
|
||||
k.append(ant);
|
||||
}
|
||||
|
||||
if (leitung && m.stand !== 'erledigt') k.append(knoepfeFuerLeitung(m));
|
||||
return k;
|
||||
}
|
||||
|
||||
/** Die zwei Handgriffe der Leitung.
|
||||
*
|
||||
* ZWEI, NICHT FÜNF. „Ich kümmere mich" und „erledigt". Eine
|
||||
* Zuweisung an eine bestimmte Person, Prioritäten, Kategorien — das
|
||||
* wäre ein Ticketsystem für zwei Menschen, die nebeneinander
|
||||
* sitzen. */
|
||||
function knoepfeFuerLeitung(m) {
|
||||
const reihe = el('div', 's-karte__tun');
|
||||
|
||||
if (m.stand === 'neu') {
|
||||
const dran = el('button', 'schritt', 'Ich kümmere mich');
|
||||
dran.type = 'button';
|
||||
dran.addEventListener('click', () => aendern(m, 'in_arbeit', ''));
|
||||
reihe.append(dran);
|
||||
}
|
||||
|
||||
const fertig = el('button', 'schritt', 'Erledigt …');
|
||||
fertig.type = 'button';
|
||||
fertig.addEventListener('click', async () => {
|
||||
/* WER ZUMACHT, SAGT WARUM. Der Melder sieht nur diesen Satz —
|
||||
ohne ihn weiß er nicht, ob etwas behoben wurde oder ob
|
||||
niemand Zeit hatte. Der Server verlangt ihn ohnehin; hier
|
||||
wird er gefragt, statt eine Absage zu kassieren. */
|
||||
const antwort = await window.frageNach?.({
|
||||
titel: 'Meldung erledigen?',
|
||||
was: 'Sie verschwindet aus dem offenen Eingang.',
|
||||
bleibt: 'Der Melder bekommt eine Nachricht mit deinem Satz – '
|
||||
+ 'das ist alles, was er davon sieht.',
|
||||
ja: 'Erledigt',
|
||||
grund: 'Was ist daraus geworden?',
|
||||
grundPflicht: true,
|
||||
grundMin: 3,
|
||||
platzhalter: 'z. B. Behoben, der Knopf geht wieder.',
|
||||
});
|
||||
if (!antwort) return;
|
||||
await aendern(m, 'erledigt', antwort.grund || '');
|
||||
});
|
||||
reihe.append(fertig);
|
||||
return reihe;
|
||||
}
|
||||
|
||||
async function aendern(m, stand, antwort) {
|
||||
melde('eingang-fehler', '');
|
||||
try {
|
||||
const a = await hole(`/workspace/api/support/${m.id}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ stand, antwort }),
|
||||
});
|
||||
if (!a.ok) {
|
||||
melde('eingang-fehler', await fehlerText(a, 'Das hat nicht geklappt.'));
|
||||
return;
|
||||
}
|
||||
await laden();
|
||||
} catch { /* umgeleitet */ }
|
||||
}
|
||||
|
||||
/* ===================================================================
|
||||
ZEICHNEN
|
||||
=================================================================== */
|
||||
|
||||
function bilanzZeichnen() {
|
||||
const ziel = $('bilanz');
|
||||
ziel.textContent = '';
|
||||
const zahl = (stand) => meldungen.filter((m) => m.stand === stand).length;
|
||||
for (const [stand, wort] of [
|
||||
['neu', 'neu'], ['in_arbeit', 'in Arbeit'], ['erledigt', 'erledigt'],
|
||||
]) {
|
||||
const kasten = el('div', 's-bilanz__feld');
|
||||
kasten.dataset.stand = stand;
|
||||
kasten.append(el('span', 's-bilanz__zahl', String(zahl(stand))));
|
||||
kasten.append(el('span', 's-bilanz__wort', wort));
|
||||
ziel.append(kasten);
|
||||
}
|
||||
}
|
||||
|
||||
function filterZeichnen() {
|
||||
const ziel = $('filter');
|
||||
ziel.textContent = '';
|
||||
const zaehl = (welche) => welche === 'alle'
|
||||
? meldungen.length
|
||||
: welche === 'offen'
|
||||
? meldungen.filter((m) => m.stand !== 'erledigt').length
|
||||
: meldungen.filter((m) => m.stand === welche).length;
|
||||
for (const [wert, wort] of [
|
||||
['offen', 'Offen'], ['erledigt', 'Erledigt'], ['alle', 'Alle'],
|
||||
]) {
|
||||
const k = el('button', 's-filter__knopf');
|
||||
k.type = 'button';
|
||||
k.dataset.wert = wert;
|
||||
k.setAttribute('aria-pressed', String(filter === wert));
|
||||
k.append(el('span', null, wort));
|
||||
k.append(el('span', 's-filter__zahl', String(zaehl(wert))));
|
||||
k.addEventListener('click', () => { filter = wert; listeZeichnen(); });
|
||||
ziel.append(k);
|
||||
}
|
||||
}
|
||||
|
||||
function listeZeichnen() {
|
||||
if (leitung) {
|
||||
filterZeichnen();
|
||||
const ziel = $('eingang-liste');
|
||||
ziel.textContent = '';
|
||||
const drin = meldungen.filter((m) => filter === 'alle' ? true
|
||||
: filter === 'offen' ? m.stand !== 'erledigt' : m.stand === filter);
|
||||
if (!drin.length) {
|
||||
/* EIN LEERER EINGANG IST EINE GUTE NACHRICHT und soll auch so
|
||||
aussehen — nicht wie eine Seite, die nicht fertig geladen
|
||||
hat. */
|
||||
ziel.append(el('p', 's-leer', filter === 'offen'
|
||||
? 'Nichts offen. Alles abgearbeitet.'
|
||||
: 'Hier steht nichts.'));
|
||||
return;
|
||||
}
|
||||
for (const m of drin) ziel.append(karteBauen(m));
|
||||
return;
|
||||
}
|
||||
|
||||
const block = $('meine-block');
|
||||
const ziel = $('meine-liste');
|
||||
ziel.textContent = '';
|
||||
$('meine-zahl').textContent = String(meldungen.length);
|
||||
block.hidden = meldungen.length === 0;
|
||||
for (const m of meldungen) ziel.append(karteBauen(m));
|
||||
}
|
||||
|
||||
async function laden() {
|
||||
try {
|
||||
const a = await hole('/workspace/api/support');
|
||||
if (!a.ok) { melde('eingang-fehler', 'Die Meldungen ließen sich nicht laden.'); return; }
|
||||
const r = await a.json();
|
||||
leitung = r.leitung === true;
|
||||
meldungen = Array.isArray(r.meldungen) ? r.meldungen : [];
|
||||
|
||||
/* DIE SEITE STELLT SICH AUF DEN UM, DER SIE ÖFFNET. */
|
||||
$('eingang-block').hidden = !leitung;
|
||||
$('meine-block').hidden = leitung || meldungen.length === 0;
|
||||
if (leitung) {
|
||||
$('seiten-titel').textContent = 'Support';
|
||||
$('seiten-unter').textContent =
|
||||
'Was aus dem Team und der Community gemeldet wurde. '
|
||||
+ 'Das Älteste steht oben – was am längsten wartet, gehört nach vorn.';
|
||||
/* MELDEN KANN DIE LEITUNG AUCH. Filipe findet selbst Fehler,
|
||||
und ein Weg, der für ihn nicht gilt, ist ein zweiter Weg,
|
||||
den er dann anders benutzt. Das Feld rutscht nur nach
|
||||
unten: Wer den Eingang aufmacht, will zuerst sehen, was
|
||||
liegt. */
|
||||
/* WIRKLICH VERSCHIEBEN, nicht nur eine Klasse setzen.
|
||||
`order` wirkt nur in einem Flex- oder Rasterbehaelter, und
|
||||
`.inhalt` ist keiner -- nachgemessen in start.css. Ein
|
||||
`append` an denselben Elternteil bewegt den Kasten ans
|
||||
Ende, ohne ihn neu zu bauen: Der Text im Feld bleibt
|
||||
stehen, falls gerade jemand tippt. */
|
||||
const kasten = $('melden-block');
|
||||
kasten.classList.add('s-melden--unten');
|
||||
kasten.parentElement?.append(kasten);
|
||||
bilanzZeichnen();
|
||||
}
|
||||
listeZeichnen();
|
||||
|
||||
/* AUS EINER BENACHRICHTIGUNG KOMMT MAN MIT ?meldung=… — dann
|
||||
gehört der Blick genau dorthin und nicht an den Seitenanfang. */
|
||||
const wohin = new URLSearchParams(location.search).get('meldung');
|
||||
if (wohin) {
|
||||
const karte = document.getElementById(`meldung-${Number(wohin)}`);
|
||||
if (karte) {
|
||||
karte.scrollIntoView({ behavior: window.sanft?.() || 'auto', block: 'center' });
|
||||
karte.dataset.hervor = 'ja';
|
||||
}
|
||||
}
|
||||
} catch { /* umgeleitet */ }
|
||||
}
|
||||
|
||||
meldenEinrichten();
|
||||
laden();
|
||||
})();
|
||||
Reference in New Issue
Block a user