VIER DINGE, und das erste ist eine Meldung aus dem Support.
1. MISS KAM AUS "AUFGABE BEARBEITEN" NICHT HERAUS.
„Ich konnte da wieder nicht zurück gehen, musste die App schließen
damit ich wieder auf die Hauptseite kam."
Gemessen (mess-dialog-ausgang.mjs), vier Größen:
412x915 App 782 px Inhalt in 784 px -- knapp ja
412x780 Browser 782 px Inhalt in 742 px -- SACKGASSE
360x640 klein 794 px Inhalt in 602 px -- SACKGASSE
412x430 Tastatur 794 px Inhalt in 392 px -- SACKGASSE
`.dialog` hatte `overflow: hidden`, eine Scroll-Höhe gab es NUR für
`.dialog--breit`. Alles unterhalb des Rands wurde abgeschnitten --
samt "Abbrechen". Jetzt rollt JEDES Fenster, und Kopf wie Knopfzeile
bleiben stehen (`position: sticky`), damit man den Ausgang SIEHT,
ohne erst durch acht Felder zu scrollen. Alle vier Größen: ja.
2. DIE VORLAGENKACHEL DREHT SICH.
„wenn ich drauf drücke dreht sich die kachel und dan seh ich wer es
gemacht hat, und immer noch die option es nochmal zu verteilen falls
neue leute ins team zustoßen."
Vorne bleibt die Kurzfassung ("liegt bei 3 von 4"), hinten stehen
die Namen mit ihrem Stand und zwei Knöpfe: "Nachholen – 1 fehlt"
(oder "Nochmal an alle", wenn wirklich alle sie haben) und "Zurück".
Nach dem Verteilen dreht sie sich von selbst; wer nur nachsehen
will, drückt "Wer hat sie?".
3. DER EINGANG SIEHT AUS WIE DAS HAUS.
Fase und Leuchtschiene statt flachem Kasten, die Schiene in der
Farbe des Stands. Die drei Zahlen werden drei Felder -- und die
"0 neu" leuchtet nicht mehr rot: Eine Warnung, die immer kommt, ist
keine Warnung. Ab 760 px steht das Bild neben dem Text statt
darunter; die Karte war dadurch dreimal so hoch wie nötig.
4. DER CREATOR-KATALOG IST AUF DER TEAM-SEITE WEG.
„es gibt keine creator auf dieser seite" -- dort stand "Wähle oben
einen Creator", eine Aufforderung zu etwas Unmöglichem. Gefragt wird
jetzt nach den Daten (gibt es jemanden, dem ich das geben kann?),
nicht nach der Adresse.
DAZU FERTIG GEMACHT, WAS VON GESTERN OFFEN WAR:
* Die zwei Serien ohne Haus ("Community-Call", "Schulung-Agentur").
Ursache war meine eigene Abschrift: Bei den Terminen frage ich die
Teilnehmerliste, bei den Serien hatte ich sie vergessen. Auf einer
Kopie der echten Datenbank: 0 offene Zeilen.
* Sieben Schreibwege setzen jetzt `haus` (Aufgaben, Einträge,
Dateien, Material, Wissen, Video-Titelbild). Dabei gefunden:
`material` verwaltet seine Spalten SELBST -- meine Spalte stand in
der falschen Liste und fehlte auf einer frischen Datenbank
(78 Fehlschläge in pruef-material, jetzt 159/0).
* unterstuetzen.html lud meldung.js gar nicht -- dort stand das
Maschinenwort des Servers statt eines Satzes (pruef-meldungen 8/0).
DREI VERALTETE PRÜFUNGEN NACHGEZOGEN, jede STRENGER als vorher:
* "der Modi legt eine Aufgabe an (201)" -- seit dem 22.09. ist das
403 und gewollt. Geprüft wird jetzt auch das WORT.
* "calls.html ist verboten" -- Filipe hat die Kachel selbst verlangt
("jeder der einen kalender hat"). Mit Gegenprobe ersetzt.
* "Review" heißt seit dem 20.09. "Zur Freigabe". Der Name wird jetzt
aus STATUS_NAME GELESEN statt abgeschrieben.
GEPRÜFT: modi-katalog 150/0 (war 144), modi-verborgen 85/0 (war 80/2),
haus-trennung 97/0, material 159/0, meldungen 8/0, abbrechen-optik 0
Fehler. Dazu grün: an-alle, vorlagen, support, css-klassen,
aufgabenbrett, aufgaben-vorlagen, unterstuetzung, formulare, loeschen,
nachfrage, kalender, chat, leerzustand.
OFFEN UND NICHT ANGEFASST: pruef-breiten meldet auf report.html ein
Berührziel von 27x18 px. Der Link (`class="zurueck"`) ist auf 30
Seiten derselbe und hat gar keinen eigenen Stil; beanstandet wird nur
diese eine Seite, weil dort hinter ihm nur "· Review" steht und die
Prüfung Fließtext-Links erst ab 12 Zeichen Umgebung ausnimmt. Eine
Klasse auf 30 Seiten ohne Prüflauf zu ändern wäre geraten.
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();
|
||
})();
|