Filipe: "Es darf vor allem keine Stellen geben, an denen ein Benutzer
etwas falsch machen kann, nur weil die Seite es nicht verstaendlich
genug erklaert."
Gemessen: 30 Stellen in 16 Dateien benutzten confirm() oder prompt().
Das Haus hatte die richtige Bauweise laengst -- einen <dialog>, in
aufgaben.html sogar ausfuehrlich begruendet -- aber sie stand IN EINER
SEITE. Wer anderswo etwas loeschen liess, hatte sie nicht.
confirm('Wirklich loeschen?') stellt die falsche Frage: Es fragt, ob
man sicher ist, und nennt nicht, WAS passiert, was BLEIBT und ob es
ZURUECK geht. Jetzt beantwortet jeder der 41 Dialoge alle drei.
Neu: workspace/assets/js/nachfrage.js -- window.frageNach() mit
Pflichtgrund, Zahlenfeld, einzeiliger Eingabe und Abtippsicherung.
Drei Ausgaenge: <dialog> / confirm()-Notnagel fuer Safari vor 15.4 /
Abbruch (Esc, Klick daneben, "Doch nicht" -- immer false).
DREIMAL DERSELBE FALLSTRICK, dreimal nachgemessen statt vermutet:
.dialog stand in aufgaben.css und leistung.css -> auf dateien.html
waere der Dialog ein weisser Systemkasten gewesen. 14 Regeln
klammergenau nach module.css verschoben (Klammern gezaehlt, nicht
per Muster geschnitten -- heute frueh hat ein nicht-gieriges
Muster schon einmal CSS zerrissen).
Das Formular trug .neu neu--blank -- und .neu gibt seine Abstaende
nur in aufgaben.css. Gemessen: padding 0px, und die Felder
verloren ihre height:44px. Jetzt steht alles unter
.nachfrage__form in module.css; der Dialog borgt nichts mehr.
Die erste Fassung der Pruefung zaehlte nachfrage.js SELBST als
Nutzer -- damit war jede Seite trivialerweise "Nutzer" und die
Pruefung gruen ohne Inhalt. Jetzt ausdruecklich ausgenommen.
ZWEI FUNDE NEBENBEI:
hilfeAufraeumen() wird im Betrieb NIE aufgerufen. Der Kommentar
behauptete "wird beim Start aufgerufen (siehe index.js)" -- das
war nie wahr; einziger Aufrufer ist die eigene Pruefung. Folge:
geschlossene vertrauliche Faelle bleiben unbegrenzt stehen. NICHT
eingeschaltet (das loescht echte Daten und ist Filipes
Entscheidung), sondern der Kommentar richtiggestellt.
Einen Hilfe-Fall zu schliessen ist endgueltig -- es gibt keine
Route, die ihn wieder oeffnet. Vorher stand darueber nur die
Frage nach einem Schlusswort. Jetzt sagt der Dialog es.
pruef-struktur hat meine eigene Pruefung von heute Nachmittag
erwischt: Sie bildete ihr Datum aus UTC. Beim Beheben erst
heuteLokal(datum) genommen -- die Funktion nimmt gar kein Argument
und haette still "heute" statt "+3 Tage" geliefert. Jetzt tagLokal(3),
nachgerechnet: Abstand 3 Tage.
Am Bildschirm angesehen (Rechner 1280, Handy 390): passt rein, Esc
ergibt false, Fokus liegt auf dem harmlosen Knopf, Knoepfe 44px auf
Touch. Der Platzhalter im Abtippfeld zeigte den erwarteten Namen --
das sah aus wie ein schon ausgefuelltes Feld, entfernt.
Neu: server/pruef-nachfrage.mjs -- 17/0, mit sechs Gegenproben und
beiden Richtungen (wer fragt, laedt die Datei; wer nie fragt, laedt
sie nicht -- sonst truege die Anmeldewand 4,8 KB fuer nichts).
pruef-meldungen 8/0, pruef-css-klassen gruen, pruef-struktur gruen,
pruef-leistung gruen.
195 lines
7.8 KiB
JavaScript
195 lines
7.8 KiB
JavaScript
/* ===================================================================
|
||
Vorlagen — fertige Inhalte, die man übernehmen kann.
|
||
|
||
EIN Bauteil für mehrere Seiten (Content-Ideen, LIVE-Analyse). Es
|
||
hängt sich an einen Behälter mit `data-vorlagen="<art>"` und baut
|
||
dort eine aufklappbare Liste von Vorschlägen.
|
||
|
||
Warum ausgeklappt-per-Knopf und nicht offen: Wer schon zwanzig eigene
|
||
Einträge hat, braucht die Vorschläge nicht mehr im Weg. Wer noch
|
||
keinen hat, sieht den Knopf sofort -- und er ist dann das Auffälligste
|
||
auf der Seite, weil sonst nichts da ist.
|
||
|
||
Übernehmen macht aus dem Vorschlag einen ganz normalen Eintrag. Ab
|
||
dann ist er unabhängig von der Vorlage: änderbar, löschbar, und
|
||
spätere Änderungen an der Vorlage berühren ihn nicht.
|
||
=================================================================== */
|
||
(() => {
|
||
'use strict';
|
||
|
||
const el = (tag, klasse, text) => {
|
||
const k = document.createElement(tag);
|
||
if (klasse) k.className = klasse;
|
||
if (text !== undefined) k.textContent = text;
|
||
return k;
|
||
};
|
||
|
||
async function hole(pfad, optionen = {}) {
|
||
const a = await fetch(pfad, { credentials: 'same-origin', ...optionen });
|
||
if (a.status === 401) { location.assign('/workspace/'); throw new Error('abgemeldet'); }
|
||
return a;
|
||
}
|
||
|
||
let vorlagen = null;
|
||
|
||
/* Was in dieser Sitzung schon uebernommen wurde. Muss AUSSERHALB des
|
||
Bauvorgangs stehen: Nach dem Uebernehmen wird die Liste neu geladen
|
||
und der Block neu gebaut -- eine Markierung am Element allein waere
|
||
dabei jedes Mal verloren. Genau das hat die Pruefung gemeldet
|
||
("als uebernommen markiert: 0").
|
||
|
||
Bewusst nur fuer diese Sitzung und nicht dauerhaft gespeichert: Wer
|
||
die Seite neu laedt, hat seine Eintraege ja in der Liste stehen --
|
||
dort gehoeren sie hin, nicht in eine zweite Buchfuehrung. */
|
||
const genommen = new Set();
|
||
|
||
/** Baut den Vorlagenblock in einen Behälter.
|
||
* art: 'content' | 'live'
|
||
* unterart: bei live der Abschnitt (vorbereitung | mitschrift | auswertung)
|
||
* creatorId: für wen übernommen wird (Betreuer); bei Creator egal
|
||
* danach: wird nach dem Übernehmen aufgerufen (Liste neu laden) */
|
||
window.vorlagenBlock = async function (behaelter, { art, unterart, creatorId, danach }) {
|
||
if (!behaelter) return;
|
||
if (!vorlagen) {
|
||
try {
|
||
const a = await hole('/workspace/api/vorlagen');
|
||
if (!a.ok) return;
|
||
vorlagen = await a.json();
|
||
} catch { return; }
|
||
}
|
||
|
||
const liste = art === 'content' ? vorlagen.content
|
||
: (vorlagen.live?.[unterart] || []);
|
||
if (!liste.length) return;
|
||
|
||
behaelter.textContent = '';
|
||
const block = el('section', 'vorlagen');
|
||
|
||
/* ---- Kopf mit Aufklappknopf ---- */
|
||
const kopf = el('div', 'vorlagen__kopf');
|
||
const titel = el('div', null);
|
||
titel.append(el('p', 'feldschild', 'Fertige Vorschläge'));
|
||
titel.append(el('p', 'vorlagen__unter',
|
||
art === 'content'
|
||
? `${liste.length} Ideen mit Aufhänger und Format – zum Übernehmen und Anpassen.`
|
||
: `${liste.length} Punkte, die sich bewährt haben. Übernehmen und abhaken.`));
|
||
kopf.append(titel);
|
||
|
||
const auf = el('button', 'knopf knopf--klein');
|
||
auf.type = 'button';
|
||
auf.setAttribute('aria-expanded', 'false');
|
||
auf.textContent = 'Vorschläge zeigen';
|
||
kopf.append(auf);
|
||
block.append(kopf);
|
||
|
||
/* ---- Die Liste ---- */
|
||
const raster = el('div', 'vorlagen__raster');
|
||
raster.hidden = true;
|
||
|
||
for (const [nr, v] of liste.entries()) {
|
||
const k = el('article', 'vorlage');
|
||
const schluessel = art + ':' + (unterart || '') + ':' + nr;
|
||
if (genommen.has(schluessel)) k.dataset.genommen = 'ja';
|
||
if (v.saeule) k.dataset.saeule = v.saeule;
|
||
|
||
k.append(el('h3', 'vorlage__titel', v.titel));
|
||
|
||
/* Der Aufhänger ist der wichtigste Teil einer Content-Idee -- die
|
||
ersten Sekunden entscheiden über die Verbreitung. Deshalb steht
|
||
er hervorgehoben und nicht im Fließtext. */
|
||
if (v.hook) {
|
||
const h = el('p', 'vorlage__hook');
|
||
h.append(el('span', 'vorlage__hookschild', 'Aufhänger'));
|
||
h.append(document.createTextNode(v.hook));
|
||
k.append(h);
|
||
}
|
||
if (v.text) k.append(el('p', 'vorlage__text', v.text));
|
||
|
||
const fuss = el('div', 'vorlage__fuss');
|
||
if (v.format) fuss.append(el('span', 'vorlage__marke', v.format));
|
||
if (v.saeule) fuss.append(el('span', 'vorlage__marke vorlage__marke--saeule', v.saeule));
|
||
|
||
const nehmen = el('button', 'schritt', 'Übernehmen');
|
||
nehmen.type = 'button';
|
||
nehmen.addEventListener('click', async () => {
|
||
nehmen.disabled = true;
|
||
nehmen.textContent = 'wird übernommen …';
|
||
try {
|
||
const a = await hole('/workspace/api/vorlagen/uebernehmen', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ art, unterart, nr, creator_id: creatorId }),
|
||
});
|
||
if (!a.ok) {
|
||
const f = await a.json().catch(() => ({}));
|
||
nehmen.textContent = sagWas(f.fehler, 'ging nicht');
|
||
nehmen.disabled = false;
|
||
return;
|
||
}
|
||
/* Übernommen bleibt sichtbar markiert -- man soll sehen, was
|
||
man schon geholt hat, ohne die Liste zu verlassen. */
|
||
genommen.add(schluessel);
|
||
k.dataset.genommen = 'ja';
|
||
nehmen.textContent = 'übernommen';
|
||
danach?.();
|
||
} catch { nehmen.disabled = false; nehmen.textContent = 'Übernehmen'; }
|
||
});
|
||
fuss.append(nehmen);
|
||
k.append(fuss);
|
||
raster.append(k);
|
||
}
|
||
|
||
/* ---- Alle auf einmal ---- */
|
||
const alle = el('div', 'vorlagen__alle');
|
||
const allesNehmen = el('button', 'knopf-still',
|
||
art === 'content' ? `Alle ${liste.length} Ideen übernehmen`
|
||
: `Alle ${liste.length} Punkte übernehmen`);
|
||
allesNehmen.type = 'button';
|
||
allesNehmen.addEventListener('click', async () => {
|
||
if (!await frageNach({
|
||
titel: `Alle ${liste.length} übernehmen?`,
|
||
was: 'Sie landen als eigene Einträge in deiner Liste.',
|
||
bleibt: 'Die Vorlage bleibt unverändert. Jeder übernommene Eintrag '
|
||
+ 'lässt sich einzeln wieder löschen.',
|
||
ja: `Alle ${liste.length} übernehmen`,
|
||
})) return;
|
||
allesNehmen.disabled = true;
|
||
allesNehmen.textContent = 'wird übernommen …';
|
||
try {
|
||
const a = await hole('/workspace/api/vorlagen/uebernehmen', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ art: art + '_alle', unterart, creator_id: creatorId }),
|
||
});
|
||
if (!a.ok) { allesNehmen.textContent = 'ging nicht'; allesNehmen.disabled = false; return; }
|
||
const { angelegt } = await a.json();
|
||
/* Alle auf einmal: dann sind auch alle markiert. */
|
||
for (let i = 0; i < liste.length; i++) genommen.add(art + ':' + (unterart || '') + ':' + i);
|
||
allesNehmen.textContent = `${angelegt} übernommen`;
|
||
danach?.();
|
||
} catch { allesNehmen.disabled = false; }
|
||
});
|
||
alle.append(allesNehmen);
|
||
raster.append(alle);
|
||
|
||
block.append(raster);
|
||
|
||
auf.addEventListener('click', () => {
|
||
const offen = auf.getAttribute('aria-expanded') === 'true';
|
||
auf.setAttribute('aria-expanded', offen ? 'false' : 'true');
|
||
raster.hidden = offen;
|
||
auf.textContent = offen ? 'Vorschläge zeigen' : 'Vorschläge ausblenden';
|
||
});
|
||
|
||
behaelter.append(block);
|
||
behaelter.hidden = false;
|
||
};
|
||
|
||
/* Der frühere Knopf "Hilfe anfordern" ist am 01.09.2026 entfallen.
|
||
Es gab damit zwei Wege, dasselbe zu sagen -- einen Knopf, der eine
|
||
Aufgabe erzeugte, und ein Textfeld für eine Nachricht. Zwei Wege
|
||
für eine Sache heißt, dass niemand weiß, welcher der richtige ist,
|
||
und dass Antworten mal hier und mal dort landen. Geblieben ist die
|
||
Rückmeldung an jedem Punkt. */
|
||
})();
|