Filipe: "keine kryptischen oder technischen fehlermeldungen, sondern
klare aussagen darueber, was passiert ist und wie man das problem
loesen kann."
DAS PROBLEM, NACHGEMESSEN.
Der Server antwortet im Fehlerfall mit { fehler: "..." }. Darin stehen
ZWEI verschiedene Dinge, und von aussen sehen sie gleich aus:
Maschinenkennungen (nicht_verfuegbar, nicht_gefunden, ungueltig -- 43
verschiedene) und fertige deutsche Saetze.
An 84 Stellen stand `textContent = d.fehler` -- ungefiltert. Wer beim
Hochladen einer Datei Pech hatte, las woertlich "nicht_verfuegbar" auf
dem Bildschirm und wusste nicht einmal, ob er selbst schuld war.
Gezaehlt: 474 Antworten mit "nicht_verfuegbar", 392 mit
"nicht_gefunden", 125 mit "ungueltig".
DREI AUSGAENGE, NICHT ZWEI.
`sagWas()` in workspace/assets/js/meldung.js:
bekannte Kennung -> ihr Satz
unbekannte Kennung -> der Ersatzsatz der Stelle, NIE die Kennung
(sie geht in die Konsole, wo sie jemandem
auffaellt, der sie beheben kann)
fertiger Satz -> unveraendert durch
Erkannt wird eine Kennung daran, dass sie nur aus Kleinbuchstaben,
Ziffern und Unterstrichen besteht. Ein deutscher Satz hat immer
Leerzeichen; eine Kennung nie. Die Unterscheidung ist entschieden,
nicht geraten.
UND WARUM DIE TABELLE NICHT ALTERN KANN.
Eine von Hand gepflegte Liste ist hier schon zweimal teuer geworden
(die abgeschriebene Spaltenliste, die feste Umbruchschwelle). Deshalb
rechnet pruef-meldungen.mjs die Kennungen AUS DEM SERVER aus statt sie
zu kennen -- eine neue ohne Satz macht sie rot. Man kann es nicht mehr
vergessen.
Sie prueft drei Dinge und beweist zu jedem, dass sie auch "nicht in
Ordnung" sagen kann:
1. vollstaendig -- jede Kennung hat einen Satz
2. angeschlossen -- keine Rohanzeige, und jede Seite, deren Skript
sagWas benutzt, laedt auch meldung.js
3. lesbar -- kein Satz enthaelt Kennung, Zahl oder englisches Wort
GEFUNDEN HAT SIE SOFORT EINEN ECHTEN FEHLER: leistung.html laedt seine
Skripte ohne `defer` und fiel damit durch meinen Einbau. Dort waere
sagWas nicht definiert gewesen -- aus einer Fehlermeldung waere ein
Absturz geworden, also schlimmer als vorher.
NEBENBEFUND, DER MICH FAST EINE FALSCHE MELDUNG GEKOSTET HAETTE:
"alter_offen" heisst NICHT "etwas Aelteres ist offen", sondern
"Altersbestaetigung steht noch aus" (workspace.js:4864). Geraten
haette ich es falsch uebersetzt.
GEMESSEN: pruef-meldungen 8/0, alle 46 Skripte syntaktisch in Ordnung.
Co-Authored-By: Claude Opus 5 <[email protected]>
280 lines
11 KiB
JavaScript
280 lines
11 KiB
JavaScript
/* =====================================================================
|
||
DIE ANFRAGE AUS DER COMMUNITY -- der Fragebogen (17.09.2026)
|
||
|
||
Filipe: „ich will dass die leute sich da anonym bei mir melden können
|
||
und eine anfrage machen können ... wie ein fragebogen wieso die
|
||
person es werden will warum sie sollte, positiv negative sachen."
|
||
|
||
(Sein Satz nennt die Rolle beim Namen -- hier steht sie nicht: Diese
|
||
Datei ist ohne Anmeldung aus dem offenen Netz lesbar.)
|
||
|
||
DIE FRAGEN STEHEN NICHT HIER. Sie kommen vom Server, mit ihren
|
||
Gruppen, ihren Pflichtmarken und den Längengrenzen. Eine zweite Liste
|
||
im Browser wäre die, die beim nächsten Nachschärfen auseinanderläuft
|
||
-- und dann verlangt das Formular etwas, das der Server nicht prüft,
|
||
oder umgekehrt. Genau dieser Fehler hat heute Mittag die Rolle
|
||
„Community" zwei Stunden lang unanlegbar gemacht.
|
||
|
||
DER ENTWURF BLEIBT IM BROWSER, NICHT AUF DEM SERVER.
|
||
|
||
Fünfzehn Fragen sind nichts, was man zwischen zwei Streams tippt. Wer
|
||
aufhören und später weitermachen will, muss das können -- sonst wird
|
||
die Anfrage nie fertig, und das ist dieselbe Wirkung wie eine Absage.
|
||
Der Entwurf liegt deshalb auf dem Gerät.
|
||
|
||
Und er liegt AUSDRÜCKLICH NICHT auf dem Server: Ein halb ausgefülltes
|
||
Formular dort wäre bereits eine Anfrage, die niemand gestellt hat --
|
||
lesbar für DogFather, bevor die Person entschieden hat, ob sie sie
|
||
überhaupt schicken will. Beim Abschicken wird der Entwurf gelöscht.
|
||
===================================================================== */
|
||
(() => {
|
||
const $ = (id) => document.getElementById(id);
|
||
const ENTWURF = 'dogi-bewerbung-entwurf';
|
||
|
||
const melde = (t) => {
|
||
const f = $('fehler');
|
||
if (f) { f.textContent = t || ''; f.hidden = !t; }
|
||
};
|
||
|
||
const el = (art, klasse, text) => {
|
||
const k = document.createElement(art);
|
||
if (klasse) k.className = klasse;
|
||
if (text != null) k.textContent = text;
|
||
return k;
|
||
};
|
||
|
||
/* Der Entwurf. Ein kaputter Eintrag darf die Seite nicht aufhalten --
|
||
dann lieber ohne Entwurf anfangen als gar nicht. */
|
||
const entwurfLesen = () => {
|
||
try { return JSON.parse(localStorage.getItem(ENTWURF) || '{}') || {}; }
|
||
catch { return {}; }
|
||
};
|
||
const entwurfSchreiben = (w) => {
|
||
try { localStorage.setItem(ENTWURF, JSON.stringify(w)); } catch { /* voll oder gesperrt */ }
|
||
};
|
||
|
||
const ZUSTAND_WORT = {
|
||
neu: 'Deine Anfrage liegt vor.',
|
||
gespraech: 'DogFather möchte mit dir reden.',
|
||
warteschlange: 'Du stehst auf der Warteliste.',
|
||
angenommen: 'Du bist dabei.',
|
||
abgelehnt: 'Diesmal nicht.',
|
||
};
|
||
|
||
(async () => {
|
||
let r;
|
||
try {
|
||
const a = await fetch('/workspace/api/bewerbung', { credentials: 'same-origin' });
|
||
if (a.status === 401) { location.assign('/workspace/'); return; }
|
||
if (!a.ok) { melde('Der Fragebogen ist gerade nicht erreichbar.'); return; }
|
||
r = await a.json();
|
||
} catch {
|
||
melde('Der Fragebogen ist gerade nicht erreichbar.');
|
||
return;
|
||
}
|
||
$('ladezeile').hidden = true;
|
||
|
||
/* DAS WORT KOMMT VOM SERVER. Bleibt es aus, steht „Anfrage
|
||
stellen" da -- eine ehrliche Ueberschrift, die nichts Falsches
|
||
behauptet. Ein Platzhalter („…") waere hier schlechter: Er sieht
|
||
kaputt aus, obwohl die Seite vollstaendig arbeitet. */
|
||
if (r.rolle_name) {
|
||
const t = $('titel');
|
||
if (t) t.textContent = r.rolle_name + ' werden';
|
||
const m = $('marke-was');
|
||
if (m) m.textContent = r.rolle_name + ' werden';
|
||
document.title = r.rolle_name + ' werden · Der Treff';
|
||
}
|
||
|
||
/* ---------------------------------------------------------------
|
||
1. WAS SCHON IST -- Stand oder Sperrfrist
|
||
--------------------------------------------------------------- */
|
||
const stand = $('stand');
|
||
let zeigeFormular = r.darf;
|
||
|
||
if (r.meine) {
|
||
stand.hidden = false;
|
||
stand.append(el('p', 'bw-stand__wort',
|
||
ZUSTAND_WORT[r.meine.zustand] || 'Deine Anfrage liegt vor.'));
|
||
if (r.meine.grund) {
|
||
/* DER SATZ AUS DER ENTSCHEIDUNG GEHÖRT DER PERSON. Er wird beim
|
||
Ablehnen verlangt, und zwar genau dafür -- nicht für die
|
||
Akte. Ihn hier wegzulassen hieße, ihn umsonst verlangt zu
|
||
haben. */
|
||
stand.append(el('p', 'bw-stand__grund', r.meine.grund));
|
||
}
|
||
if (r.offen) {
|
||
stand.append(el('p', 'leise',
|
||
'Solange sie offen ist, kannst du keine zweite schicken. '
|
||
+ 'Du hörst von uns – das kann ein paar Tage dauern.'));
|
||
zeigeFormular = false;
|
||
}
|
||
}
|
||
|
||
if (r.sperre) {
|
||
stand.hidden = false;
|
||
stand.append(el('p', 'bw-stand__sperre',
|
||
'In ' + r.sperre.tage + ' Tagen kannst du es wieder versuchen.'));
|
||
stand.append(el('p', 'leise',
|
||
'Eine Absage heißt hier „noch nicht“, nicht „nie“. '
|
||
+ 'Bis dahin: mitreden, Wünsche einstellen, dabei sein.'));
|
||
zeigeFormular = false;
|
||
}
|
||
|
||
if (!r.darf) {
|
||
stand.hidden = false;
|
||
stand.append(el('p', 'leise',
|
||
'Diese Seite ist für die Community. Du bist schon im Team.'));
|
||
}
|
||
|
||
if (!zeigeFormular) return;
|
||
|
||
/* ---------------------------------------------------------------
|
||
2. DER BOGEN
|
||
--------------------------------------------------------------- */
|
||
const bogen = $('bogen');
|
||
const ziel = $('gruppen');
|
||
const werte = entwurfLesen();
|
||
const felder = new Map();
|
||
|
||
for (const g of r.gruppen) {
|
||
const fragen = r.fragen.filter((f) => f.gruppe === g.schluessel);
|
||
if (!fragen.length) continue;
|
||
|
||
const block = el('section', 'bw-gruppe');
|
||
const kopf = el('div', 'bw-gruppe__kopf');
|
||
kopf.append(el('h2', 'bw-gruppe__titel', g.name));
|
||
if (g.unter) kopf.append(el('p', 'bw-gruppe__unter', g.unter));
|
||
block.append(kopf);
|
||
|
||
for (const f of fragen) {
|
||
const kurz = f.art === 'kurz';
|
||
const grenze = kurz ? r.grenzen.kurz : r.grenzen.lang;
|
||
const id = 'f-' + f.schluessel;
|
||
|
||
const feld = el('div', 'bw-feld');
|
||
const schild = el('label', 'bw-feld__schild');
|
||
schild.htmlFor = id;
|
||
schild.append(document.createTextNode(f.titel));
|
||
/* PFLICHT WIRD ANGESCHRIEBEN, NICHT ERZWUNGEN-BIS-ES-KLEMMT.
|
||
Ein Sternchen ohne Erklärung ist eine Falle; hier steht das
|
||
Wort. Und was NICHT Pflicht ist, steht auch dran -- sonst
|
||
füllt man aus Vorsicht alles aus und schreibt bei drei
|
||
Fragen nichts Ehrliches mehr. */
|
||
schild.append(el('span', f.pflicht ? 'bw-feld__pflicht' : 'bw-feld__frei',
|
||
f.pflicht ? 'nötig' : 'freiwillig'));
|
||
feld.append(schild);
|
||
|
||
if (f.hinweis) feld.append(el('p', 'bw-feld__hinweis', f.hinweis));
|
||
|
||
const eingabe = kurz ? document.createElement('input')
|
||
: document.createElement('textarea');
|
||
eingabe.id = id;
|
||
eingabe.className = 'bw-feld__eingabe';
|
||
eingabe.maxLength = grenze;
|
||
if (kurz) { eingabe.type = 'text'; eingabe.autocomplete = 'off'; }
|
||
else { eingabe.rows = 4; }
|
||
eingabe.value = werte[f.schluessel] || '';
|
||
feld.append(eingabe);
|
||
|
||
/* Der Zähler zählt, was noch geht -- nicht, was schon steht.
|
||
„Noch 80 Zeichen" ist eine Auskunft; „180 von 1200" ist eine
|
||
Aufforderung, mehr zu schreiben. Deshalb meldet er sich auch
|
||
erst, wenn es eng wird. */
|
||
const zaehler = el('p', 'bw-feld__zaehler');
|
||
const nachziehen = () => {
|
||
const rest = grenze - eingabe.value.length;
|
||
zaehler.textContent = rest < 80 ? ('noch ' + rest + ' Zeichen') : '';
|
||
zaehler.hidden = rest >= 80;
|
||
};
|
||
nachziehen();
|
||
eingabe.addEventListener('input', () => {
|
||
werte[f.schluessel] = eingabe.value;
|
||
entwurfSchreiben(werte);
|
||
nachziehen();
|
||
feld.classList.remove('bw-feld--fehlt');
|
||
});
|
||
feld.append(zaehler);
|
||
|
||
block.append(feld);
|
||
felder.set(f.schluessel, { feld, eingabe, frage: f });
|
||
}
|
||
ziel.append(block);
|
||
}
|
||
|
||
$('wer-liest').textContent = 'Wer das liest: ' + (r.wer_liest || '');
|
||
bogen.hidden = false;
|
||
|
||
/* ---------------------------------------------------------------
|
||
3. ABSCHICKEN
|
||
--------------------------------------------------------------- */
|
||
bogen.addEventListener('submit', async (e) => {
|
||
e.preventDefault();
|
||
melde('');
|
||
|
||
/* Die Pflichtliste kommt aus den Fragen des Servers -- dieselbe
|
||
Quelle, die er selbst prüft. */
|
||
const fehlt = [...felder.values()]
|
||
.filter((x) => x.frage.pflicht && !x.eingabe.value.trim());
|
||
for (const x of felder.values()) x.feld.classList.remove('bw-feld--fehlt');
|
||
if (fehlt.length) {
|
||
for (const x of fehlt) x.feld.classList.add('bw-feld--fehlt');
|
||
melde(fehlt.length === 1
|
||
? 'Eine Antwort fehlt noch – sie ist markiert.'
|
||
: fehlt.length + ' Antworten fehlen noch – sie sind markiert.');
|
||
fehlt[0].eingabe.focus();
|
||
fehlt[0].feld.scrollIntoView({ block: 'center', behavior: 'smooth' });
|
||
return;
|
||
}
|
||
|
||
const knopf = $('abschicken');
|
||
knopf.disabled = true;
|
||
knopf.textContent = 'wird geschickt …';
|
||
|
||
const antworten = {};
|
||
for (const [k, x] of felder) {
|
||
const v = x.eingabe.value.trim();
|
||
if (v) antworten[k] = v;
|
||
}
|
||
|
||
try {
|
||
const a = await fetch('/workspace/api/bewerbung', {
|
||
method: 'POST',
|
||
headers: { 'content-type': 'application/json' },
|
||
credentials: 'same-origin',
|
||
body: JSON.stringify({ antworten }),
|
||
});
|
||
const d = await a.json().catch(() => ({}));
|
||
if (!a.ok) {
|
||
melde(sagWas(d.fehler, 'Das hat nicht geklappt.'));
|
||
knopf.disabled = false;
|
||
knopf.textContent = 'Anfrage abschicken';
|
||
return;
|
||
}
|
||
/* ERST JETZT den Entwurf löschen -- nicht vorher. Wäre er beim
|
||
Absenden schon weg und der Server antwortet nicht, stünde man
|
||
vor einem leeren Formular und einer halben Stunde Arbeit im
|
||
Nichts. */
|
||
try { localStorage.removeItem(ENTWURF); } catch { /* egal */ }
|
||
|
||
bogen.hidden = true;
|
||
const fertig = $('stand');
|
||
fertig.hidden = false;
|
||
fertig.textContent = '';
|
||
fertig.append(el('p', 'bw-stand__wort', 'Angekommen.'));
|
||
fertig.append(el('p', 'leise',
|
||
'DogFather und die rechte Hand lesen sie – sonst niemand. '
|
||
+ 'Du hörst von uns; das kann ein paar Tage dauern.'));
|
||
const zurueck = el('a', 'knopf', 'Zurück zum Treff');
|
||
zurueck.href = 'bereich.html?b=mitmachen';
|
||
fertig.append(zurueck);
|
||
fertig.scrollIntoView({ block: 'start', behavior: 'smooth' });
|
||
} catch {
|
||
melde('Das hat nicht geklappt. Dein Entwurf ist noch da.');
|
||
knopf.disabled = false;
|
||
knopf.textContent = 'Anfrage abschicken';
|
||
}
|
||
});
|
||
})();
|
||
})();
|