Files
dogfather-universe/workspace/assets/js/vorlagen.js
T
DogFatherGitandClaude Opus 5 461d41943a Keine Maschinensprache mehr auf dem Bildschirm
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]>
2026-09-19 14:46:13 +02:00

190 lines
7.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ===================================================================
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 (!confirm(`Wirklich alle ${liste.length} übernehmen? `
+ 'Sie landen als eigene Einträge in der Liste und lassen sich einzeln löschen.')) 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. */
})();