Files
DogFatherGitandClaude Opus 5 95c4599baa Anfuehrungszeichen: 199 falsche Schlusszeichen im sichtbaren Text
Deutsch oeffnet mit „ und schliesst mit “. An 199 Stellen, die ein
Mensch liest, stand als Schlusszeichen ein GERADES " -- „Nächstes"
statt „Nächstes“. Auf sechzehn oeffentlichen Seiten, in vierzig
Dateien des Workspace und in zwoelf Servermodulen, die Texte
verschicken.

Auf dem Bildschirm sieht man den Unterschied sofort. Beim Schreiben
nicht: Das gerade " liegt auf der Tastatur, die anderen nicht.

WARUM EIN ERSTER ANLAUF ZURUECKGENOMMEN WURDE

Ein gerades " ist an vielen Stellen SYNTAX und kein Schriftzeichen --
Grenze einer Zeichenkette, Grenze eines HTML-Attributs, Zeichen in
einem regulaeren Ausdruck. Wer stumpf ersetzt, macht aus

    „<a href="https://…                  ein kaputtes Attribut
    /^["'„»\s]+|["'“«.\s]+$/              einen kaputten Ausdruck
    "… nichts „mal " + "eben …"          eine kaputte Zeichenkette

DIE UNTERSCHEIDUNG LAEUFT AN MERKMALEN, NICHT AN EINER LISTE

  < > = dazwischen        -> HTML-Marke oder Attribut
  endet auf Leerzeichen   -> die Zeichenkette hoert hier auf, der
                             Satz geht in der naechsten Zeile weiter.
                             Ein deutsches Schlusszeichen steht NIE
                             hinter einem Leerzeichen.
  Rueckstrich mittendrin  -> regulaerer Ausdruck
  ${ ohne }               -> mitten in einem Ausdruck

Eine Liste erlaubter Ausnahmen waere die naechste, die niemand
pflegt. Zwoelf Stellen bleiben dadurch stehen, alle zwoelf einzeln
angesehen und alle zwoelf zu Recht -- dort steht das richtige
Schlusszeichen ohnehin weiter unten im Satz.

Fuenf davon waren allerdings ECHTE Fehler HINTER dem Link
(`…>HasiDog</a>".`) -- die erste Regel hatte nur das Attribut
gesehen, nicht den Satz danach. Gezielt nachgezogen.

Ein maskiertes `\"` in workspace-vorlagen.js (28 Hooks) wird zu “ --
ohne Rueckstrich, denn “ begrenzt nichts.

KOMMENTARE BLEIBEN, WIE SIE SIND. Dort liest es niemand ausser mir;
eine Wache, die auch Kosmetik anmahnt, wird weggeklickt. Beim ersten
Messen fielen ausserdem acht Stellen aus buehne.html faelschlich an,
weil `/* */` in HTML (in <style> und <script>) nicht ausgeblendet
war -- jetzt schon.

DIE WACHE DAZU

pruef-struktur prueft es ab sofort mit derselben Regel: 322 Dateien
mit sichtbarem Text, 0 Funde, und die zwoelf bewussten Ausnahmen
werden GEZAEHLT und genannt (erlaubt: 12). Eine Ausnahme, die niemand
sieht, waechst -- und irgendwann steht der echte Fall darin.

GEPRUEFT

  pruef-struktur   59 -> 68 Pruefungen, 0 Fehler
  node --check auf allen geaenderten JS-Dateien
  nachgemessen: 199 geaendert, 12 mit Grund stehen geblieben

  gruen geblieben: bewerbung-aufgaben 163, nachwuchs 262,
  reaktion 421, support 63, content 45, terminregel 35, treff 85

  Und nachgesehen, ob eine Pruefung noch die alte Schreibweise
  ERWARTET: 13 Fundstellen, alle dreizehn nur Text in ihrer eigenen
  Ausgabe, keine einzige ein Vergleich mit dem Seitentext.

GEGENPROBE: In reaktion.html ein Schlusszeichen zurueckgedreht ->
„workspace/reaktion.html:546 „Nächstes"", mit Datei und Zeile. Und
die Erkennung einzeln gegen HTML-Attribut, fortgesetzte
Zeichenkette, regulaeren Ausdruck und eingesetzten Wert geprueft.

Stempel gesetzt: workspace 670 Verweise in 45 Dateien, oeffentlich
554 in 48 Seiten.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-01 01:56:15 +02:00

496 lines
19 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* =====================================================================
UNTERSTÜTZEN (24.09.2026)
=====================================================================
Filipe: „jeder soll diese kachel sehen aber nur dogfather soll sie
verändern können oder vieles mehr sehen. alle anderen, rechte und
linke hand, modis und community sollen supporten können."
---------------------------------------------------------------------
DREI DINGE, DIE HIER ANDERS SIND ALS AUF DEN ÜBRIGEN SEITEN
1. DIE OBERFLÄCHE ENTSCHEIDET NICHTS. `darf_aendern` kommt vom
Server und wird hier nur GELESEN. Wer das Feld im Browser auf
„true" setzt, bekommt trotzdem 404 zurück — die Regel steht in
workspace-unterstuetzung.js. Ein verstecktes Feld ist eine
Bitte, keine Sperre.
2. DER ZÄHLER DARF DEN WEG NIE AUFHALTEN. Ein Klick auf „Bei PayPal
öffnen" muss PayPal öffnen, auch wenn der Server gerade nicht
antwortet. Deshalb `sendBeacon`: Er schickt den Strich los und
wartet auf nichts. Ein `await fetch()` vor der Weiterleitung
wäre ein Zähler, der im Zweifel eine Zahlung verhindert.
3. EINE ADRESSE WIRD NICHT AUS TEXT ZUSAMMENGESETZT. Das Ziel geht
über `href` in ein `<a>`, nie über `innerHTML` — und jeder Weg
nach draußen bekommt `rel="noopener noreferrer"`. Ohne
`noopener` kann die geöffnete Seite über `window.opener` auf
diese hier zurückgreifen.
===================================================================== */
(() => {
const $ = (id) => document.getElementById(id);
/* DER SATZ ZU EINER SERVER-KENNUNG -- mit Rueckfall (25.09.2026).
Ueber `window` und nicht als blanker Aufruf: Wuerde diese Datei
einmal ohne meldung.js geladen, waere `sagWas(...)` ein
ReferenceError -- und der traete ausgerechnet dann auf, wenn
ohnehin schon etwas schiefgegangen ist. Eine Fehlerbehandlung,
die selbst abstuerzt, verschluckt den eigentlichen Fehler.
Dieselbe Bauart steht in vorlagenbrett.js und ist dort aus
demselben Grund entstanden. */
const sagWas = (fehler, ersatz) => (typeof window.sagWas === 'function'
? window.sagWas(fehler, ersatz)
: (ersatz || 'Das hat nicht geklappt.'));
const el = (t, k, text) => {
const n = document.createElement(t);
if (k) n.className = k;
if (text !== undefined) n.textContent = text;
return n;
};
const hole = (pfad, optionen) => window.holen(pfad, optionen);
const melde = (text, gut) => {
const f = $('fehler');
if (!f) return;
f.textContent = text || '';
f.hidden = !text;
f.dataset.gut = gut ? 'ja' : 'nein';
};
let stand = null; // die Antwort des Servers
let aendern = false; // steht der Änderungsmodus an?
/* =====================================================================
DIE ZEICHEN
Eines je Weg, mit einem Rückfall je Art. Ein Weg, den DogFather
neu anlegt, hat keinen eigenen Schlüssel in dieser Liste — und
bekommt trotzdem ein Zeichen, statt ein Loch zu lassen.
`voll` markiert die Teile, die Fläche sind statt Linie. Auf 24
Pixeln verschwindet eine reine Strichzeichnung, und ein ganz
gefülltes Zeichen sieht aus wie ein Klecks — dieselbe Aufteilung
wie bei den Kachelzeichen in bereiche.js.
===================================================================== */
const ZEICHEN = {
paypal: [
['M3.6 8.2h16.8a1.4 1.4 0 0 1 1.4 1.4v8.2a1.4 1.4 0 0 1-1.4 1.4H3.6a1.4 1.4 0 0 1-1.4-1.4V9.6a1.4 1.4 0 0 1 1.4-1.4z'],
['M2.2 12.4h19.6'],
['M5.6 8.2 7.2 5a1.4 1.4 0 0 1 1.9-.6l6.9 3.8', 'voll-nein'],
],
amazon: [
['M4 11.2h16v7.2a1.4 1.4 0 0 1-1.4 1.4H5.4A1.4 1.4 0 0 1 4 18.4z'],
['M3 8.1h18v3.1H3z'],
['M12 8.1v11.7'],
['M12 8.1S10.9 4.2 8.8 4.2a2 2 0 0 0 0 3.9z'],
['M12 8.1s1.1-3.9 3.2-3.9a2 2 0 0 1 0 3.9z'],
],
dabei: [
['M9.2 11.4a3.1 3.1 0 1 0 0-6.2 3.1 3.1 0 0 0 0 6.2z'],
['M2.8 19.6c0-3.3 2.9-5.6 6.4-5.6s6.4 2.3 6.4 5.6'],
['M16.4 6a3 3 0 0 1 0 5.6'],
['M17.6 14.6c2.2.6 3.6 2.4 3.6 5'],
],
weitersagen: [
['M17.8 8.4a2.6 2.6 0 1 0 0-5.2 2.6 2.6 0 0 0 0 5.2z'],
['M5.6 14.6a2.6 2.6 0 1 0 0-5.2 2.6 2.6 0 0 0 0 5.2z'],
['M17.8 20.8a2.6 2.6 0 1 0 0-5.2 2.6 2.6 0 0 0 0 5.2z'],
['M7.9 10.7 15.5 7', 'M7.9 13.3l7.6 3.7'],
],
draussen: [
['M11 4.8H5.4A1.4 1.4 0 0 0 4 6.2v12.4a1.4 1.4 0 0 0 1.4 1.4h12.4a1.4 1.4 0 0 0 1.4-1.4V13'],
['M13.8 4H20v6.2'],
['M19.4 4.6 12 12'],
],
/* Rückfall je Art. */
geld: [['M3.6 8.2h16.8a1.4 1.4 0 0 1 1.4 1.4v8.2a1.4 1.4 0 0 1-1.4 1.4H3.6a1.4 1.4 0 0 1-1.4-1.4V9.6a1.4 1.4 0 0 1 1.4-1.4z'], ['M2.2 12.4h19.6']],
geschenk: [['M4 11.2h16v7.2a1.4 1.4 0 0 1-1.4 1.4H5.4A1.4 1.4 0 0 1 4 18.4z'], ['M3 8.1h18v3.1H3z'], ['M12 8.1v11.7']],
frei: [['M12 20.2 4.4 12.6a4.6 4.6 0 0 1 6.3-6.7l1.3 1.2 1.3-1.2a4.6 4.6 0 0 1 6.3 6.7z']],
};
function zeichenBauen(w) {
const pfade = ZEICHEN[w.schluessel] || ZEICHEN[w.art] || ZEICHEN.frei;
const NS = 'http://www.w3.org/2000/svg';
const svg = document.createElementNS(NS, 'svg');
svg.setAttribute('viewBox', '0 0 24 24');
svg.setAttribute('aria-hidden', 'true');
for (const gruppe of pfade) {
for (const d of gruppe) {
if (!d.startsWith('M')) continue;
const p = document.createElementNS(NS, 'path');
p.setAttribute('d', d);
svg.append(p);
}
}
return svg;
}
const PFEIL_RAUS = ['M13.8 4H20v6.2', 'M19.4 4.6 12 12',
'M11 4.8H5.4A1.4 1.4 0 0 0 4 6.2v12.4a1.4 1.4 0 0 0 1.4 1.4h12.4a1.4 1.4 0 0 0 1.4-1.4V13'];
function pfeilBauen() {
const NS = 'http://www.w3.org/2000/svg';
const svg = document.createElementNS(NS, 'svg');
svg.setAttribute('viewBox', '0 0 24 24');
svg.setAttribute('aria-hidden', 'true');
for (const d of PFEIL_RAUS) {
const p = document.createElementNS(NS, 'path');
p.setAttribute('d', d);
svg.append(p);
}
return svg;
}
/* =====================================================================
EINE KARTE
===================================================================== */
/** Führt die Adresse aus dem Haus hinaus?
*
* Entscheidet über `target` und `rel` — und darüber, ob überhaupt
* ein `rel` nötig ist. Eine Seite dieses Hauses öffnet im selben
* Fenster: Ein neuer Tab für „Draußen" wäre ein Fenster mehr, das
* niemand zumacht. */
const nachDraussen = (ziel) => /^https:\/\//i.test(ziel);
function karte(w) {
const k = el('article', 'u-weg');
k.dataset.art = w.art;
k.dataset.schluessel = w.schluessel;
if (!w.sichtbar) k.dataset.aus = 'ja';
const kopf = el('div', 'u-weg__kopf');
const zeichen = el('span', 'u-weg__zeichen');
zeichen.setAttribute('aria-hidden', 'true');
zeichen.append(zeichenBauen(w));
kopf.append(zeichen);
const namen = el('div', 'u-weg__namen');
namen.append(el('h3', 'u-weg__titel', w.titel));
namen.append(el('span', 'u-weg__marke', stand.arten[w.art] || w.art));
kopf.append(namen);
k.append(kopf);
if (w.text) k.append(el('p', 'u-weg__text', w.text));
if (aendern && stand.darf_aendern) {
k.append(formular(w));
return k;
}
/* NUR EIN KNOPF, WENN ES EINEN WEG GIBT. „Einfach da sein" hat
keinen — und ein Knopf, der nichts tut, lässt die Seite kaputt
aussehen statt den Weg selbsterklärend. */
if (w.ziel && w.knopf) {
const a = el('a', 'u-weg__knopf');
a.href = w.ziel;
a.append(document.createTextNode(w.knopf));
if (nachDraussen(w.ziel)) {
a.target = '_blank';
/* `noopener` ist die Sicherung: Ohne ihn kann die geöffnete
Seite über `window.opener` auf diese hier zugreifen. */
a.rel = 'noopener noreferrer';
a.append(pfeilBauen());
}
a.addEventListener('click', () => strich(w.id));
k.append(a);
}
/* WAS NUR DOGFATHER SIEHT. Ohne Änderungsmodus, damit er im Alltag
dieselbe Seite sieht wie alle anderen — und nur dann mehr, wenn
er danach fragt. */
if (stand.darf_aendern) {
if (!w.sichtbar) k.append(el('span', 'u-weg__aus', 'steht für niemanden da'));
const z = stand.zahlen?.[w.id];
k.append(el('span', 'u-weg__zahl', z
? `${z.gesamt}× geöffnet · ${z.tage30} in 30 Tagen · ${z.tage7} in 7 Tagen`
: 'noch nicht geöffnet'));
}
return k;
}
/* =====================================================================
DER STRICH
Kein Name, keine Nummer — der Server zählt Weg und Tag (siehe
unterstuetzung-tabellen.js).
`sendBeacon` und nicht `fetch`: Der Browser folgt dem Link im
selben Augenblick, und eine laufende Anfrage würde dabei
abgebrochen. Ein Strich, der nur gezählt wird, wenn die Seite noch
offen ist, zählt ausgerechnet die erfolgreichen Klicks nicht.
===================================================================== */
function strich(id) {
const weg = `/workspace/api/unterstuetzung/${id}/strich`;
try {
if (navigator.sendBeacon && navigator.sendBeacon(weg, new Blob())) return;
} catch { /* dann eben der Rückfall darunter */ }
/* RÜCKFALL, und er darf ebenfalls nichts aufhalten: `keepalive`
lässt die Anfrage weiterlaufen, während die Seite wechselt. */
try {
fetch(weg, { method: 'POST', credentials: 'same-origin', keepalive: true })
.catch(() => {});
} catch { /* der Weg nach draußen zählt, nicht der Strich */ }
}
/* =====================================================================
DAS FORMULAR — nur im Änderungsmodus
===================================================================== */
function feld(art, name, wert, hinweis) {
const l = el('label');
l.append(el('span', null, hinweis));
const i = document.createElement(art === 'lang' ? 'textarea' : 'input');
if (art !== 'lang') i.type = 'text';
i.value = wert ?? '';
i.dataset.feld = name;
l.append(i);
return l;
}
function formular(w) {
const f = el('div', 'u-form');
f.append(feld('kurz', 'titel', w.titel, 'Name'));
f.append(feld('lang', 'text', w.text, 'Was dahintersteht'));
const reihe = el('div', 'u-form__reihe');
const artFeld = el('label');
artFeld.append(el('span', null, 'Art'));
const auswahl = document.createElement('select');
auswahl.dataset.feld = 'art';
for (const [wert, name] of Object.entries(stand.arten)) {
const o = document.createElement('option');
o.value = wert; o.textContent = name;
if (wert === w.art) o.selected = true;
auswahl.append(o);
}
artFeld.append(auswahl);
reihe.append(artFeld);
const an = el('label', 'u-form__an');
const haken = document.createElement('input');
haken.type = 'checkbox';
haken.dataset.feld = 'sichtbar';
haken.checked = !!w.sichtbar;
an.append(haken, document.createTextNode('steht für alle da'));
reihe.append(an);
f.append(reihe);
f.append(feld('kurz', 'knopf', w.knopf, 'Aufschrift des Knopfes (leer = kein Knopf)'));
const zielFeld = feld('kurz', 'ziel', w.ziel, 'Adresse (https://… oder seite.html)');
f.append(zielFeld);
const knoepfe = el('div', 'u-form__knoepfe');
const sichern = el('button', 'knopf knopf--klein', 'Speichern');
sichern.type = 'button';
sichern.addEventListener('click', () => sichere(w.id, f));
knoepfe.append(sichern);
const weg = el('button', 'abmelden u-form__weg', 'Entfernen');
weg.type = 'button';
weg.addEventListener('click', () => entferne(w));
knoepfe.append(weg);
f.append(knoepfe);
return f;
}
const lies = (f, name) => {
const e = f.querySelector(`[data-feld="${name}"]`);
if (!e) return undefined;
return e.type === 'checkbox' ? e.checked : e.value;
};
async function sichere(id, f) {
melde('');
const koerper = {
titel: lies(f, 'titel'), text: lies(f, 'text'),
knopf: lies(f, 'knopf'), ziel: lies(f, 'ziel'),
art: lies(f, 'art'), sichtbar: lies(f, 'sichtbar'),
};
try {
const a = await hole(`/workspace/api/unterstuetzung/${id}`, {
method: 'PUT', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(koerper),
});
if (!a.ok) {
const r = await a.json().catch(() => ({}));
/* SATZ STATT MASCHINENWORT (25.09.2026). Hier stand
`r.fehler || …` -- also wurde die Kennung des Servers
ungefiltert angezeigt. Wer "nicht_gefunden" liest, weiss
weder, was los ist, noch was er tun soll. `sagWas` macht
daraus den Satz aus meldung.js und faellt auf den Text
dahinter zurueck, wenn es die Kennung dort nicht gibt. */
melde(sagWas(r.fehler, 'Das ließ sich nicht speichern.'));
return;
}
melde('Gespeichert.', true);
await laden();
} catch { melde('Das ließ sich nicht speichern.'); }
}
async function entferne(w) {
/* GEFRAGT WIRD ÜBER DIE HAUS-NACHFRAGE und nicht über `confirm`.
Sie sieht aus wie der Rest des Hauses und lässt sich mit der
Tastatur bedienen — `confirm` sperrt die ganze Seite. */
const ja = window.bestaetige
? await window.bestaetige({
titel: `„${w.titel}“ entfernen?`,
text: 'Der Weg verschwindet für alle, und die Striche der letzten '
+ 'Monate gehen mit. Das lässt sich nicht zurückholen.',
ja: 'Entfernen', nein: 'Behalten',
})
: true;
if (!ja) return;
try {
const a = await hole(`/workspace/api/unterstuetzung/${w.id}`, { method: 'DELETE' });
if (!a.ok) { melde('Das ließ sich nicht entfernen.'); return; }
melde('Entfernt.', true);
await laden();
} catch { melde('Das ließ sich nicht entfernen.'); }
}
async function neuerWeg() {
melde('');
try {
const a = await hole('/workspace/api/unterstuetzung', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ titel: 'Neuer Weg', art: 'frei', text: '' }),
});
if (!a.ok) {
const r = await a.json().catch(() => ({}));
melde(sagWas(r.fehler, 'Das ließ sich nicht anlegen.'));
return;
}
await laden();
} catch { melde('Das ließ sich nicht anlegen.'); }
}
/* =====================================================================
ZEICHNEN
===================================================================== */
function gruppe(titel, satz, wege) {
if (!wege.length) return null;
const s = el('section', 'u-gruppe');
const kopf = el('div', 'u-gruppe__kopf');
kopf.append(el('h2', 'u-gruppe__titel', titel));
if (satz) kopf.append(el('p', 'u-gruppe__satz', satz));
s.append(kopf);
const gitter = el('div', 'u-gitter');
for (const w of wege) gitter.append(karte(w));
s.append(gitter);
return s;
}
function zeichnen() {
const ziel = $('wege');
ziel.textContent = '';
const geben = stand.wege.filter((w) => w.art !== 'frei');
const frei = stand.wege.filter((w) => w.art === 'frei');
/* ZWEI GRUPPEN, UND DIE ZWEITE IST NICHT DIE ABTEILUNG FÜR ARME.
Sie trägt dieselbe Kartenform und dieselbe Größe; nur die
Überschrift sortiert. Ohne Überschriften stünden fünf Karten
nebeneinander und man müsste jede lesen, um zu wissen, was
Geld kostet. */
const a = gruppe('Direkt etwas geben',
'Geht an Dogi selbst – ohne Umweg über TikTok.', geben);
const b = gruppe('Kostet nichts',
'Genauso viel wert. Wirklich.', frei);
if (a) ziel.append(a);
if (b) ziel.append(b);
if (aendern && stand.darf_aendern) {
const neu = el('button', 'u-neu');
neu.type = 'button';
neu.append(el('span', 'u-neu__plus', '+'));
neu.append(el('span', null, 'Noch einen Weg dazunehmen'));
neu.addEventListener('click', neuerWeg);
const gitter = el('div', 'u-gitter');
gitter.append(neu);
const s = el('section', 'u-gruppe');
s.append(gitter);
ziel.append(s);
}
if (!stand.wege.length) {
ziel.append(el('p', 'u-laedt', stand.darf_aendern
? 'Noch kein Weg eingetragen. Leg oben einen an.'
: 'Hier steht gerade nichts. Schau später noch einmal vorbei.'));
}
}
function leitungZeichnen() {
const block = $('leitung');
if (!stand.darf_aendern) { block.hidden = true; return; }
block.hidden = false;
/* DER HINWEIS AUF DEN FEHLENDEN PAYPAL-LINK.
Er steht hier und nicht als Platzhalter in der Datenbank: Eine
geratene Zahlungsadresse wäre der gefährlichste Fehler dieser
ganzen Seite — Geld an einen Fremden, und niemand merkt es.
Also steht dort nichts, der Weg ist unsichtbar, und genau EINE
Person erfährt, dass er fehlt. */
const ohne = stand.wege.filter((w) => w.knopf === '' && w.ziel === '' && w.art !== 'frei');
const unsichtbar = stand.wege.filter((w) => !w.sichtbar);
const satz = $('leitung-satz');
if (ohne.length || unsichtbar.length) {
const namen = [...new Set([...ohne, ...unsichtbar].map((w) => w.titel))].join(', ');
satz.textContent = `Noch ohne Adresse und deshalb für niemanden sichtbar: ${namen}. `
+ 'Drück auf „Ändern“, trag die Adresse ein und setz den Haken bei '
+ '„steht für alle da“.';
satz.dataset.warnung = 'ja';
} else {
satz.textContent = 'Du kannst hier jeden Weg umschreiben, ausblenden oder '
+ 'einen neuen dazunehmen. Alle anderen sehen nur, was sichtbar ist.';
delete satz.dataset.warnung;
}
/* DIE SUMME ÜBER ALLE WEGE. Eine Zahl je Karte steht unten an der
Karte; hier oben steht, ob die Seite überhaupt benutzt wird. */
const kasten = $('zahlen');
kasten.textContent = '';
const z = Object.values(stand.zahlen || {});
if (!z.length) { kasten.hidden = true; return; }
kasten.hidden = false;
const summe = (feld) => z.reduce((n, x) => n + (x[feld] || 0), 0);
for (const [wert, text] of [
[summe('tage7'), 'geöffnet in 7 Tagen'],
[summe('tage30'), 'in 30 Tagen'],
[summe('gesamt'), 'insgesamt'],
]) {
const kachel = el('div', 'u-zahl');
kachel.append(el('span', 'u-zahl__n', String(wert)));
kachel.append(el('span', 'u-zahl__text', text));
kasten.append(kachel);
}
/* KEIN NAME, NIRGENDS. Die Tabelle hat gar keine Spalte dafür —
das steht so im Schema und ist nicht nur eine Abfrage, die
jemand später ändern könnte. */
}
async function laden() {
try {
const a = await hole('/workspace/api/unterstuetzung');
if (!a.ok) { melde('Die Seite ließ sich nicht laden.'); return; }
stand = await a.json();
$('laedt')?.remove();
leitungZeichnen();
zeichnen();
} catch {
window.ladefehler?.();
}
}
$('aendern-an')?.addEventListener('click', () => {
aendern = !aendern;
$('aendern-an').textContent = aendern ? 'Fertig' : 'Ändern';
melde('');
zeichnen();
});
laden();
})();