Kampagnen-Kacheln: gegliederte Texte, Geschenke mit Preis, aufklappbar
Filipe, mit einem Bildschirmfoto des Regelblocks: "man soll die titel
in den texten besser erkennen und sehen was zu was passt. ich will
auch dass man sieht welche geschenke bei dem event zaehlen und so.
ich will alle informationen die moeglich sind wenn man den link
hochlaedt. ... auch wenn man teilt soll es einfach perfekt sein. man
soll alle infos sehen die man braucht. die kacheln von den events
soll auch nicht so lang sein, die soll man aufklappen koennen. genau
wie eine trennung fuer heute, letzte woche letzten monat und so."
DER FEHLER LAG NICHT IM TEXT, SONDERN IN SEINER ANZEIGE. Im Feld
stand die ganze Zeit eine Gliederung -- Hinweise mit "* ", eine
Ueberschrift mit Untertitel hinter einem Gedankenstrich, Absaetze
ohne Zeichen, Aufzaehlungen mit "- ". Die Kachel hat daraus mit
punkteAusText() EINE flache Liste gemacht: jede Zeile derselbe
Strich, alle gleich gross.
gliederung() liest die Gliederung, die schon dasteht, statt
den Text umzuschreiben. Die beiden Kampagnen, die
heute in der Datenbank liegen, sehen dadurch
sofort richtig aus -- ohne Umstellung.
Geschenke kommen aus der gelesenen LISTE, nicht aus dem
Text, und stehen OHNE Aufklappen auf der Kachel.
Aufklappen alles Uebrige hinter einem Druck: gemessen 555 px
weniger je Kachel bei Gipfelstuermer.
Gruppen je Spalte laeuft / kommt / vorbei, Vergangenes
nach diese Woche, dieser Monat, aelter.
NEU GELESEN (stand in allen vier gespeicherten Seiten und wurde nie
angesehen): die Rangliste mit ihren Gruppen ("Superstars" bis
"5. Liga", bei Glow Up "Team Glow"/"Team Shine"), die Beschreibung je
Missionsgruppe, der Steckbrief (Kurznummer, Markt, Sprache, Zone),
der zweite Bildbaustein, und Ueberschriften INNERHALB der Klapptexte
-- "WELCHE LIGA BIN ICH?" hat jetzt sechs eingerueckte Unterpunkte
statt zwoelf gleichrangiger Zeilen.
EVENT_TEXT_MAX 6000 -> 20000. Der Regeltext von Gipfelstuermer ist
7679 Zeichen lang; es fielen bisher jedes Mal rund 1600 Zeichen
echter Inhalt weg. Die Zahl steht jetzt an EINER Stelle.
TEILEN: Was die KAMPAGNE sagt, geht hinaus (Aufgaben, Geschenke,
Regeln) -- wer WIR sind, nicht (Name, Rolle, Haus, Kampagnennummer,
Steckbrief). Und keine Hinweiszeile: "Die Preise konnten nicht
gelesen werden -- bitte nachtragen" ist eine Notiz an DogFather und
hat auf einer Seite, die er in Discord postet, nichts zu suchen.
BERICHTIGT, WEIL NACHGEMESSEN:
- Number(null) ist 0 und 0 ist endlich -- "Feuerwerk" bekam an drei
Stellen "0 Coins" angeschrieben, also eine falsche Preisangabe.
- Ein Kommentar im Leser behauptete, der Wertungszeitraum weiche
vom Kampagnenzeitraum ab. Nachgemessen ist er in allen drei
Seiten mit Rangliste auf die Millisekunde gleich; ich hatte zwei
verschiedene Kampagnen verglichen.
- bild-kampagne.mjs verlangte "jede Kachel traegt den Ton ihrer
Seite" und war damit seit dem 07.10. rot, weil an dem Tag bewusst
das Gegenteil entschieden wurde (Zustandston, Seitenkante). Nicht
aufgefallen, weil nach dem Lauf nur die Zahl der MESSUNGEN notiert
wurde, nicht die der FEHLER.
- .kv-seite > .eintrag-karte wird .kv-seite .eintrag-karte: Die
Karten haengen durch die Gruppen eine Ebene tiefer. Das waere der
fuenfte Direktkind-Selektor gewesen, der nach einem Umbau still
ausfaellt -- diesmal vorher gesehen.
GEPRUEFT: 186 (lesen, +58), 148 (Route/Teilen, +25), 158 (Kachel,
+64), 67 (Agentur, +5), 67 Messungen (Bild, +14), dazu struktur,
css-klassen, zeichen, deutsche-texte, tippziele, lesbarkeit und
haus-trennung (107, unveraendert). Sechs Gegenproben: alle sechs
Sabotagen werden bemerkt, alle Dateien byte-gleich wiederhergestellt.
Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
+179
-9
@@ -531,13 +531,84 @@ let kachelId = null;
|
||||
"Titel und Einleitung stehen drin");
|
||||
|
||||
/* KEIN FELD LÄNGER ALS DAS FORMULAR ERLAUBT. Sonst entstünde eine
|
||||
Kachel, die DogFather öffnen, aber nicht speichern kann. */
|
||||
for (const feld of ["text", "event_aufgaben", "event_regeln"]) {
|
||||
ok((z[feld] || "").length <= 6000,
|
||||
` ${feld}: ${(z[feld] || "").length} Zeichen (höchstens 6000)`);
|
||||
Kachel, die DogFather öffnen, aber nicht speichern kann.
|
||||
|
||||
JE FELD SEINE EIGENE GRENZE (berichtigt 07.10.2026): `text`
|
||||
ist die Beschreibung und darf 6000 Zeichen haben (TEXT_MAX),
|
||||
die zwei Eventfelder 20000 (EVENT_TEXT_MAX). Beide gegen
|
||||
dieselbe Zahl zu prüfen hieße, eine der zwei Grenzen nicht
|
||||
zu prüfen. */
|
||||
const { EVENT_TEXT_MAX } = await import("./workspace-bereiche.js");
|
||||
ok(EVENT_TEXT_MAX === 20000,
|
||||
`die Grenze für die Eventfelder steht auf ${EVENT_TEXT_MAX}`);
|
||||
for (const [feld, grenze] of [
|
||||
["text", 6000], ["event_aufgaben", EVENT_TEXT_MAX], ["event_regeln", EVENT_TEXT_MAX],
|
||||
]) {
|
||||
ok((z[feld] || "").length <= grenze,
|
||||
` ${feld}: ${(z[feld] || "").length} Zeichen (höchstens ${grenze})`);
|
||||
}
|
||||
|
||||
/* ---- DER REGELTEXT PASST JETZT GANZ HINEIN (07.10.2026) ----
|
||||
|
||||
Hier stand das Gegenteil: „und wo gekürzt wurde, sagt die
|
||||
Kachel es selbst". Das war richtig, solange das Feld 6000
|
||||
Zeichen fasste -- der Regeltext von „Gipfelstürmer" ist
|
||||
gemessen 7679 Zeichen lang, also fielen jedes Mal rund 1600
|
||||
Zeichen echter Inhalt weg. Filipe: „ich will alle
|
||||
informationen die möglich sind wenn man den link hochlädt."
|
||||
|
||||
Gemessen wird deshalb jetzt, dass NICHTS gekürzt wurde -- und
|
||||
zwar mit Beleg, dass der Text wirklich länger als die alte
|
||||
Grenze ist. Sonst wäre die Zeile auch bei einem leeren Feld
|
||||
grün. */
|
||||
ok((z.event_regeln || "").length > 6000,
|
||||
`der Regeltext ist ${(z.event_regeln || "").length} Zeichen lang `
|
||||
+ "-- mit der alten Grenze von 6000 wäre hier gekürzt worden");
|
||||
ok(!(z.event_regeln || "").includes("Der Regeltext war länger"),
|
||||
"und er steht jetzt vollständig in der Kachel, ohne Kürzungsfußnote");
|
||||
/* DER SCHLUSS DES TEXTES IST DA. „Nicht gekürzt" ließe sich auch
|
||||
behaupten, wenn nur der Anfang drinstünde; geprüft wird der
|
||||
LETZTE Abschnitt, den die Kürzung als ersten weggenommen hat. */
|
||||
ok(/TEILNAHME\nLänder: /.test(z.event_regeln || "")
|
||||
|| /## TEILNAHME/.test(z.event_regeln || ""),
|
||||
"bis zum letzten Abschnitt (TEILNAHME mit den Ländern)");
|
||||
|
||||
/* ---- UND DIE KÜRZUNG SELBST BLEIBT GEPRÜFT ----------------
|
||||
|
||||
Sie läuft im Betrieb nicht mehr (kein gemessener Regeltext
|
||||
ist so lang). Eine Funktion, die nie läuft, geht beim
|
||||
nächsten Umbau still kaputt -- und ausgerechnet dann, wenn
|
||||
eine Kampagne mit 25000 Zeichen kommt. Also einzeln, mit
|
||||
kleiner Grenze. */
|
||||
{
|
||||
const { kuerzen } = await import("./workspace-kampagne.js");
|
||||
/* ZEHN ABSCHNITTE, rund 600 Zeichen. Beim ersten Versuch standen
|
||||
hier drei Abschnitte mit 61 Zeichen und eine Grenze von 200 --
|
||||
der Text passte also hinein und wurde zu Recht nicht gekürzt.
|
||||
Die Prüfung meldete „sagt nicht, dass gekürzt wurde" und hatte
|
||||
formal recht: Es wurde nichts gekürzt. Eine Prüfung, deren
|
||||
Voraussetzung nicht erfüllt ist, misst nichts (CLAUDE.md,
|
||||
03.09.2026) -- deshalb muss der Text WIRKLICH zu lang sein,
|
||||
und genau das wird vorher festgestellt. */
|
||||
const lang = Array.from({ length: 10 }, (x, i) =>
|
||||
`## ABSCHNITT ${i + 1}\nEine Zeile mit Inhalt, Nummer ${i + 1}.`).join("\n\n");
|
||||
ok(lang.length > 400,
|
||||
`der Probetext ist ${lang.length} Zeichen lang, die Grenze 400 `
|
||||
+ "-- er MUSS also gekürzt werden");
|
||||
const kurz = kuerzen(lang, 400);
|
||||
ok(kurz.length <= 400 && kurz.length < lang.length,
|
||||
`kuerzen(…, 400) hält die Grenze ein (${kurz.length} statt ${lang.length})`);
|
||||
ok(kurz.includes("Der Regeltext war länger"),
|
||||
" und sagt, dass gekürzt wurde");
|
||||
/* AN EINER ABSCHNITTSGRENZE, nicht mitten im Wort: Ein
|
||||
abgeschnittenes Wort sieht nicht nach einer Grenze aus,
|
||||
sondern nach einem Fehler beim Einlesen. */
|
||||
const inhalt = kurz.split("\n\n*")[0];
|
||||
ok(/\.$|^## [A-ZÄÖÜ]+$/m.test(inhalt.split("\n").pop() || ""),
|
||||
` an einer Zeilengrenze: …${JSON.stringify(inhalt.slice(-24))}`);
|
||||
ok(kuerzen(lang, 20000) === lang,
|
||||
" und ein Text, der passt, bleibt unangetastet");
|
||||
}
|
||||
ok((z.event_regeln || "").includes("Der Regeltext war länger"),
|
||||
"und wo gekürzt wurde, sagt die Kachel es selbst");
|
||||
|
||||
/* DER AUSZUG BEWAHRT, WAS NICHT INS FELD PASST. */
|
||||
const auszug = JSON.parse(z.kampagne_daten || "{}");
|
||||
@@ -919,16 +990,41 @@ melde("=== 12b. Ein Event nach draußen teilen ===");
|
||||
&& /noindex/.test(String(seiteOffen.kopf["x-robots-tag"] || "")),
|
||||
"Suchmaschinen bleiben draußen — im HTML UND im Kopf der Antwort");
|
||||
|
||||
/* ---- UND NUR DAS GEHT HINAUS ---------------------------------- */
|
||||
/* ---- WAS DIE KAMPAGNE SAGT, GEHT HINAUS -- WER WIR SIND, NICHT
|
||||
|
||||
DIE GRENZE IST AM 07.10.2026 VERSCHOBEN WORDEN, auf Filipes
|
||||
Ansage: „auch wenn man teilt soll es einfach perfekt sein. man
|
||||
soll alle infos sehen die man braucht."
|
||||
|
||||
Vorher standen „eine Aufgabe" und „der Regeltext" in dieser
|
||||
Liste, und das war zu eng gedacht. Diese Angaben stammen von
|
||||
einer OEFFENTLICHEN TikTok-Seite; sie weiterzugeben verraet
|
||||
nichts ueber uns, und ohne sie ist der Link eine Einladung,
|
||||
die nicht sagt, wozu.
|
||||
|
||||
DIE GRENZE LIEGT BEIM GEGENSTAND, NICHT BEI DER MENGE: nichts
|
||||
ueber Personen (Name, Rolle, Haken), nichts ueber die
|
||||
Verwaltung (Feldnamen, Haus, Keks), nichts aus der Buchhaltung
|
||||
(Kampagnennummer, Steckbrief). Jede dieser Zeilen steht hier
|
||||
weiterhin -- die Liste ist kuerzer geworden, nicht weicher.
|
||||
|
||||
UND NEU: KEINE HINWEISZEILE. „Die Preise konnten nicht gelesen
|
||||
werden — bitte nachtragen" ist eine Notiz AN DOGFATHER. Auf
|
||||
einer Seite, die er in Discord postet, stuende damit vor
|
||||
seinen Creatorn, dass seine Kachel unfertig ist. Geprueft wird
|
||||
das weiter unten an einer Kampagne, die wirklich einen Hinweis
|
||||
hat -- an „Gipfelstuermer" waere es ein gruener Haken ohne
|
||||
Messung, denn die hat gar keinen. */
|
||||
const verboten = [
|
||||
["eine Aufgabe", "1 Diamant = 1 Punkt"],
|
||||
["der Regeltext", "KAMPAGNENGESCHENKE"],
|
||||
["ein Name", "Dogfather"],
|
||||
["eine Rolle", "admin"],
|
||||
["die Kampagnennummer", ID_GIPFEL],
|
||||
["die Kurznummer", "1140116740"],
|
||||
["ein Feldname aus der Datenbank", "erstellt_von"],
|
||||
["die Hausangabe", "\"haus\""],
|
||||
["ein Keks", "workspace_sitzung"],
|
||||
["ein Hinweis an DogFather", "bitte nachtragen"],
|
||||
["die Kuerzungsfussnote", "länger als das Feld fasst"],
|
||||
];
|
||||
/* NUR DER KOERPER, NICHT DAS STILBLATT (berichtigt 07.10.2026).
|
||||
Hier stand zuerst schlicht "agentur" als verbotenes Wort -- und
|
||||
@@ -949,6 +1045,80 @@ melde("=== 12b. Ein Event nach draußen teilen ===");
|
||||
&& /04\.10\.2026/.test(seiteOffen.text),
|
||||
"Gegenprobe: Titel und Zeitraum stehen sehr wohl darauf");
|
||||
|
||||
/* ---- UND JETZT ALLES, WAS MAN BRAUCHT (07.10.2026) ---------
|
||||
|
||||
Gemessen wird nicht „die Seite ist lang", sondern dass jede
|
||||
der vier Auskünfte wirklich dasteht: Aufgaben, Geschenke mit
|
||||
Preis, Überschriften, gegliederte Unterstufen. */
|
||||
const koerper = ohneStil;
|
||||
ok(/<h2 class="k__schild2">Aufgaben<\/h2>/.test(koerper)
|
||||
&& koerper.includes("1 Diamant = 1 Punkt"),
|
||||
"die Aufgaben stehen auf der Seite");
|
||||
const wieVieleAufgaben = (koerper.match(/<li>/g) || []).length;
|
||||
ok(wieVieleAufgaben >= 5,
|
||||
`und zwar alle fünf, nicht nur eine (${wieVieleAufgaben} Zeilen insgesamt)`);
|
||||
|
||||
ok(/Diese Geschenke zählen/.test(koerper) && koerper.includes("Ahorn-Welt"),
|
||||
"die Geschenke stehen darauf");
|
||||
ok(/12\.000 Coins/.test(koerper),
|
||||
"mit Coinpreis und Tausenderpunkt");
|
||||
ok(/<p class="k__woher">KAMPAGNENGESCHENKE/.test(koerper),
|
||||
"unter der Überschrift, die die Kampagne dafür vorgesehen hat");
|
||||
|
||||
const titel = [...koerper.matchAll(/<h3 class="k__titel">([^<]+)<\/h3>/g)]
|
||||
.map((m) => m[1]);
|
||||
ok(titel.includes("BELOHNUNGEN") && titel.includes("TEILNAHMEBEDINGUNGEN"),
|
||||
`die Abschnitte sind als Überschriften gesetzt: ${JSON.stringify(titel.slice(0, 8))}`);
|
||||
ok(titel.includes("Superstars") && titel.includes("1. Liga"),
|
||||
"auch die Ligen aus dem Klapptext");
|
||||
ok(/data-stufe="3"/.test(koerper) && /data-stufe="2"/.test(koerper),
|
||||
"und sie stehen auf zwei Stufen, nicht alle gleichrangig");
|
||||
|
||||
/* DIE VORSCHAU IN DISCORD BLEIBT KURZ. Discord zeigt rund 300
|
||||
Zeichen; eine Beschreibung von 7000 Zeichen wäre dort
|
||||
abgeschnitten und hier nutzlos. Die LANGE Fassung steht im
|
||||
Körper, die kurze im Kopf -- das ist der ganze Trick. */
|
||||
const beschr = /<meta property="og:description" content="([^"]*)"/.exec(seiteOffen.text);
|
||||
/* 300 IST DIE ZAHL, DIE ZAEHLT -- so viel zeigt Discord.
|
||||
Beim ersten Lauf kam 304 heraus, weil die Textgrenze (280)
|
||||
den Zeitraum davor nicht mitrechnete. */
|
||||
ok(beschr && beschr[1].length <= 300,
|
||||
`og:description bleibt unter 300 Zeichen (${beschr?.[1].length})`);
|
||||
/* UND SIE IST NICHT LEER. „Kurz" ist auch eine leere
|
||||
Beschreibung -- dann zeigte Discord nur den Titel. */
|
||||
ok(beschr && beschr[1].length > 40,
|
||||
` und sagt wirklich etwas (${beschr?.[1].length} Zeichen)`);
|
||||
ok(koerper.length > 3000,
|
||||
`der Körper trägt dagegen alles (${koerper.length} Zeichen)`);
|
||||
|
||||
/* ---- EINE KAMPAGNE MIT HINWEIS ------------------------------
|
||||
|
||||
Das ist die Messung, die „keine Notiz an DogFather geht
|
||||
hinaus" überhaupt erst zu einer Messung macht:
|
||||
„Gipfelstürmer" hat null Hinweise, „ohne Preise" hat einen.
|
||||
Ohne diesen Abschnitt wäre die Zeile oben ein grüner Haken
|
||||
über einem leeren Feld (CLAUDE.md, 01.09.2026). */
|
||||
const opId = dbLesen("SELECT id FROM eintraege WHERE kampagne_id = ?", ID_OHNEPREISE)[0]?.id;
|
||||
ok(!!opId, `die Kampagne ohne Preise liegt in der Datenbank (#${opId})`);
|
||||
const opRegeln = dbLesen("SELECT event_regeln FROM eintraege WHERE id = ?", opId)[0];
|
||||
ok(/bitte nachtragen|keine Preisliste/.test(opRegeln?.event_regeln || ""),
|
||||
"und ihre Kachel trägt wirklich einen Hinweis (sonst misst das Folgende nichts)");
|
||||
const opT = await roh(`/workspace/api/eintrag/${opId}/teilen`,
|
||||
{ methode: "POST", keks: keksDogi, rumpf: {} });
|
||||
const opS = String(JSON.parse(opT.text || "{}").weg || "").split("/e/")[1];
|
||||
ok(!!opS, `sie lässt sich teilen (HTTP ${opT.code})`);
|
||||
const opSeite = await offen(`/e/${opS}`);
|
||||
const opKoerper = opSeite.text.split("<style")[0]
|
||||
+ (opSeite.text.split("</style>")[1] || "");
|
||||
ok(opSeite.code === 200, `die Seite steht (${opSeite.code})`);
|
||||
ok(!/bitte nachtragen/.test(opKoerper) && !/keine Preisliste/.test(opKoerper),
|
||||
"KEIN Hinweis an DogFather steht auf der geteilten Seite");
|
||||
/* GEGENPROBE: Der Rest der Kampagne steht sehr wohl darauf --
|
||||
sonst wäre die Zeile darüber auch bei einer leeren Seite grün. */
|
||||
ok(/Diese Geschenke zählen/.test(opKoerper) && /<h3 class="k__titel">/.test(opKoerper),
|
||||
"Gegenprobe: Geschenke und Überschriften stehen sehr wohl darauf");
|
||||
await roh(`/workspace/api/eintrag/${opId}/teilen`, { methode: "DELETE", keks: keksDogi });
|
||||
|
||||
/* ---- Das Banner ------------------------------------------------ */
|
||||
const bild = await offen(`/e/${schluessel}/bild`);
|
||||
ok(bild.code === 200 && /^image\//.test(String(bild.kopf["content-type"] || "")),
|
||||
|
||||
Reference in New Issue
Block a user