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]>
496 lines
19 KiB
JavaScript
496 lines
19 KiB
JavaScript
/* =====================================================================
|
||
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();
|
||
})();
|