/* ===================================================================== NACHFRAGEN, BEVOR ETWAS WEG IST — window.frageNach() Filipe, 19.09.2026: „Es darf vor allem keine Stellen geben, an denen ein Benutzer etwas falsch machen kann, nur weil die Seite es nicht verstaendlich genug erklaert." Und: „Fehler moeglichst verhindern, bevor sie entstehen." --------------------------------------------------------------------- WARUM ES DIESE DATEI GIBT Das Haus hatte die richtige Bauweise laengst -- in `aufgaben.html` steht sie sogar begruendet: ein , kein confirm(), weil confirm die ganze Seite anhaelt, kein Textfeld aufnimmt und auf jedem Rechner anders aussieht. Nur stand diese Bauweise IN EINER SEITE. Wer auf einer anderen Seite etwas loeschen liess, hatte sie nicht -- und griff zu confirm(). Gemessen am 19.09.2026: 30 Stellen im Workspace, in 16 Dateien. Also wandert sie hierher, wo jede Seite sie hat. Das ist dieselbe Lehre wie bei den Fehlermeldungen (`meldung.js`) und bei den Kachelfarben: Was an 30 Stellen gepflegt werden muss, wird an 30 Stellen unterschiedlich. Eine Stelle, die alle benutzen, kann nicht auseinanderlaufen. --------------------------------------------------------------------- WAS EIN GUTER NACHFRAGE-DIALOG SAGT confirm('Wirklich loeschen?') stellt die falsche Frage. Es fragt, ob man sicher ist -- und wer soll das wissen, wenn nicht dasteht, was danach passiert? Der Dialog hier beantwortet drei Dinge: 1. WAS passiert -- "Die Datei wird geloescht." 2. Was NICHT passiert -- "Der Chat-Verlauf bleibt." 3. Ob es ZURUECK geht -- "Das laesst sich nicht rueckgaengig machen." Punkt 3 ist der wichtigste und in confirm() gar nicht ausdrueckbar. Deshalb traegt der Bestaetigungsknopf bei `endgueltig: true` eine sichtbare Warnfarbe UND der Text sagt es noch einmal in Worten -- Farbe allein traegt keine Aussage (Rot-Gruen-Schwaeche, Kontrastmodus). --------------------------------------------------------------------- DREI AUSGAENGE, wie ueberall im Haus vorhanden -> der richtige Dialog fehlt (alt) -> confirm() als Notnagel, mit DEMSELBEN Text, damit der Nutzer dieselbe Auskunft bekommt -- nur haesslicher Nutzer bricht ab -> false, und zwar immer: bei Esc, beim Klick daneben und beim "Doch nicht" Der mittlere Ausgang ist nicht theoretisch: showModal gibt es erst ab Safari 15.4. Ein iPhone, das nicht aktualisiert wurde, wuerde ohne Notnagel gar nichts anzeigen -- und der Knopf taete stillschweigend nichts. Das waere schlimmer als ein haesslicher Dialog. --------------------------------------------------------------------- AUFRUF if (!await frageNach({ titel: 'Datei loeschen?', was: 'Die Datei wird vom Server entfernt.', bleibt:'Die Nachricht im Chat bleibt stehen.', endgueltig: true, ja: 'Loeschen', })) return; Mit Grund (gibt dann { grund } statt true zurueck): const a = await frageNach({ titel: '...', grund: 'Warum?', grundPflicht: true }); if (!a) return; // abgebrochen ... a.grund ... ===================================================================== */ (function () { 'use strict'; /* Ein einziger Dialog fuer die ganze Seite. Er wird beim ersten Gebrauch gebaut und danach wiederverwendet -- zwei offene Dialoge gleichzeitig kann der Browser ohnehin nicht, und ein neuer je Aufruf wuerde den Fokus verlieren. */ var dlg = null, feld = null, tippfeld = null, zahlfeld = null, fehlerZeile = null; function bauen() { if (dlg) return dlg; dlg = document.createElement('dialog'); dlg.className = 'dialog nachfrage'; dlg.innerHTML = '
' + '

' + '

' + '

' + '' + '' + '' + '' + '' + '
' + '' + '' + '
'; document.body.appendChild(dlg); feld = dlg.querySelector('#nachfrage-grund'); tippfeld = dlg.querySelector('#nachfrage-tipp'); zahlfeld = dlg.querySelector('#nachfrage-zahl'); fehlerZeile = dlg.querySelector('[data-teil="fehler"]'); return dlg; } function teil(name) { return dlg.querySelector('[data-teil="' + name + '"]'); } /** Setzt Text und blendet die Zeile aus, wenn es keinen gibt. * Eine leere Zeile, die Platz einnimmt, sieht aus wie ein Fehler. */ function zeile(name, text) { var e = teil(name); e.textContent = text || ''; e.hidden = !text; } window.frageNach = function frageNach(o) { o = o || {}; var titel = o.titel || 'Sicher?'; var was = o.was || ''; var bleibt = o.bleibt || ''; var ja = o.ja || 'Ja, machen'; var nein = o.nein || 'Doch nicht'; var endgueltig = !!o.endgueltig; var grund = o.grund || ''; var grundPflicht = !!o.grundPflicht; var platzhalter = o.platzhalter || ''; /* ABTIPPEN STATT ANKLICKEN. Fuer die Handgriffe, bei denen ein Fehlklick nicht zu reparieren ist (eine Person loeschen). Wer den Namen abtippt, hat ihn gelesen -- das ist der eigentliche Zweck, nicht die Huerde. */ var tippen = o.tippen || ''; /* EIN EINZEILIGES FELD, wenn nach einem Namen gefragt wird und nicht nach einem Grund. Ein Textfeld ueber drei Zeilen fuer einen Vornamen sieht aus, als erwarte jemand einen Aufsatz. Benutzt dasselbe Feld wie `tippen` -- beides gleichzeitig kommt nicht vor und waere auch nicht sinnvoll. */ var eingabe = o.eingabe || ''; var eingabeWert = o.eingabeWert || ''; var eingabeMin = o.eingabeMin === undefined ? 2 : o.eingabeMin; /* EINE ZAHL IM SELBEN DIALOG. Vorher fragte die Moderation zweimal hintereinander (Grund, dann Dauer) -- zwei Kaesten nacheinander, und wer beim zweiten abbrach, hatte den ersten umsonst getippt. Reihenfolge bleibt: erst WARUM, dann WIE LANGE. Wer zuerst nach der Dauer gefragt wird, hat die Strafe festgelegt, bevor er sie begruendet hat. */ var zahl = o.zahl || ''; var zahlWert = o.zahlWert === undefined ? '' : String(o.zahlWert); var zahlMax = o.zahlMax || 0; var zahlLeer = !!o.zahlLeer; var zahlHinweis = o.zahlHinweis || ''; var NIE_ZURUECK = 'Das lässt sich danach nicht mehr rückgängig machen.'; /* ---- Ausgang 2: kein -> confirm() mit DEMSELBEN Text ---- Nicht "Sicher?" -- wer den Notnagel bekommt, soll dieselbe Auskunft haben wie alle anderen. */ if (typeof HTMLDialogElement === 'undefined' || !HTMLDialogElement.prototype.showModal) { var text = [titel, was, bleibt, endgueltig ? NIE_ZURUECK : ''] .filter(Boolean).join('\n\n'); if (!window.confirm(text)) return Promise.resolve(false); if (tippen) { var t0 = window.prompt('Zum Bestätigen genau eingeben: ' + tippen, ''); if (t0 === null || t0.trim() !== tippen) return Promise.resolve(false); } var erg = {}; if (eingabe) { var e0 = window.prompt(eingabe, eingabeWert); if (e0 === null || e0.trim().length < eingabeMin) return Promise.resolve(false); erg.eingabe = e0.trim(); } if (grund) { var g0 = window.prompt(grund, ''); if (g0 === null) return Promise.resolve(false); if (grundPflicht && g0.trim().length < 3) return Promise.resolve(false); erg.grund = g0.trim(); } if (zahl) { var z0 = window.prompt(zahl + (zahlHinweis ? ' (' + zahlHinweis + ')' : ''), zahlWert); if (z0 === null) return Promise.resolve(false); erg.zahl = z0.trim() === '' ? null : Number(z0); } return Promise.resolve((grund || zahl || eingabe) ? erg : true); } bauen(); teil('titel').textContent = titel; zeile('was', was); zeile('bleibt', bleibt); teil('endgueltig').hidden = !endgueltig; teil('ja').textContent = ja; teil('nein').textContent = nein; /* Warnfarbe NUR zusaetzlich zum Satz oben, nie statt seiner. */ teil('ja').classList.toggle('knopf--warn', endgueltig); teil('tippzeile').hidden = !(tippen || eingabe); if (eingabe) { teil('tippschild').textContent = eingabe; tippfeld.value = eingabeWert; tippfeld.placeholder = ''; } else if (tippen) { teil('tippschild').textContent = 'Zum Bestätigen genau eingeben: ' + tippen; tippfeld.value = ''; /* KEIN Platzhalter mit dem erwarteten Wort. Er sieht in einem leeren Feld aus wie ein bereits eingetragener Text -- und wer glaubt, es stehe schon da, drueckt auf "Loeschen" und wundert sich ueber die Fehlermeldung. Der Name steht ohnehin in der Beschriftung direkt darueber. */ tippfeld.placeholder = ''; } teil('grundzeile').hidden = !grund; if (grund) { teil('grundschild').textContent = grund; feld.value = ''; feld.placeholder = platzhalter; } teil('zahlzeile').hidden = !zahl; if (zahl) { teil('zahlschild').textContent = zahl; teil('zahlhinweis').textContent = zahlHinweis; zahlfeld.value = zahlWert; if (zahlMax) zahlfeld.max = String(zahlMax); else zahlfeld.removeAttribute('max'); zahlfeld.required = !zahlLeer; } fehlerZeile.textContent = ''; return new Promise(function (fertig) { var antwort = false; var form = dlg.querySelector('form'); function aufraeumen() { dlg.removeEventListener('close', beiSchliessen); form.removeEventListener('submit', beiJa); teil('nein').removeEventListener('click', beiNein); dlg.removeEventListener('click', beiDaneben); } function beiSchliessen() { aufraeumen(); fertig(antwort); } function beiNein() { antwort = false; dlg.close(); } function beiDaneben(e) { /* Klick auf den abgedunkelten Rand = abbrechen. Das erwartet jeder, und ohne es bleibt der Dialog am Handy gefuehlt haengen, weil dort kein Esc zur Verfuegung steht. */ if (e.target === dlg) { antwort = false; dlg.close(); } } function beiJa(e) { e.preventDefault(); if (eingabe && tippfeld.value.trim().length < eingabeMin) { fehlerZeile.textContent = eingabeMin > 1 ? 'Bitte mindestens ' + eingabeMin + ' Zeichen eintragen.' : 'Bitte etwas eintragen.'; tippfeld.focus(); return; } if (tippen && tippfeld.value.trim() !== tippen) { /* Sagt, WAS nicht stimmt -- "falsch" allein hilft niemandem, der sich vertippt hat. */ fehlerZeile.textContent = 'Das stimmt noch nicht. Erwartet wird genau: ' + tippen; tippfeld.focus(); tippfeld.select(); return; } var erg2 = {}; if (eingabe) erg2.eingabe = tippfeld.value.trim(); if (grund) { var g = feld.value.trim(); if (grundPflicht && g.length < 3) { fehlerZeile.textContent = 'Bitte kurz sagen, warum.'; feld.focus(); return; /* NICHT schliessen -- sonst waere die Eingabe weg */ } erg2.grund = g; } if (zahl) { var roh = zahlfeld.value.trim(); if (roh === '' && !zahlLeer) { fehlerZeile.textContent = 'Bitte eine Anzahl eintragen.'; zahlfeld.focus(); return; } var n = roh === '' ? null : Number(roh); if (n !== null && !(n > 0)) { fehlerZeile.textContent = 'Das ist keine gültige Anzahl.'; zahlfeld.focus(); return; } if (n !== null && zahlMax && n > zahlMax) { fehlerZeile.textContent = 'Mehr als ' + zahlMax + ' geht hier nicht.'; zahlfeld.focus(); return; } erg2.zahl = n; } antwort = (grund || zahl || eingabe) ? erg2 : true; dlg.close(); } dlg.addEventListener('close', beiSchliessen); form.addEventListener('submit', beiJa); teil('nein').addEventListener('click', beiNein); dlg.addEventListener('click', beiDaneben); dlg.showModal(); /* Der Fokus liegt auf dem harmlosen Knopf, nicht auf "Loeschen". Wer aus Gewohnheit Enter drueckt, bricht damit ab -- das ist die richtige Richtung fuer einen Fehlgriff. Ausser es gibt ein Textfeld, dann gehoert der Fokus dorthin. */ if (tippen || eingabe) tippfeld.focus(); else if (grund) feld.focus(); else teil('nein').focus(); }); }; })();