Teilen gibt einen fertigen Discord-Beitrag -- und die zwei Seiten bekommen ein Gesicht
Filipe: "beim teilen wird auch nur der link geteilt, und ich will dass
wenn ich es teile im discord der agentur, dass ein banner und ein
kompletter fertiger text fuer die creator entsteht und nicht einfach
der link kopiert wird damit die leute drauf gehen. das ist kake."
Und: "perfektionier die zwei events kategorien. ich will es noch viel
krasser und spezieller."
EIN LINK IN EINEM DISCORD-KANAL IST EINE AUFFORDERUNG. Wer ihn nicht
anklickt -- und das sind die meisten -- weiss nicht, dass es die
Kampagne gibt. Was in Discord steht, muss die Kampagne SEIN. Der
Teilen-Knopf legt deshalb einen fertigen Beitrag in die Zwischenablage:
Titel als Ueberschrift, Zeitraum, Einleitung, die Aufgaben als Liste,
die Geschenke mit Coinpreis, "Gut zu wissen" aus Rangliste, Zeitplan
und Teilnahme. Er steht zusaetzlich sichtbar unter der Kachel, mit
Zeichenzaehler und zweitem Kopierknopf -- was man nicht gesehen hat,
fuegt man blind ein.
ZWEI DINGE SIND DABEI NICHT BELIEBIG:
- Der Link steht GANZ AM ENDE. Discord setzt die Bannervorschau
UNTER die Nachricht; steht er oben, schiebt das Banner den Text
aus dem Blick.
- Der Link steht NACKT da. Spitze Klammern unterdruecken die
Vorschau -- und damit genau das Banner, um das es geht.
2000 ZEICHEN, UND NICHT EINS MEHR. Gekuerzt wird auf dem Server, in
fester Reihenfolge und an Abschnittsgrenzen: erst "Gut zu wissen",
dann die Einleitung auf zwei Saetze, dann die Geschenke. Titel,
Zeitraum, Aufgaben und Link bleiben IMMER. Gemessen gegen sieben
Laengen und einen 1500-Zeichen-Titel als Haertefall.
Gebaut wird der Beitrag aus `teilenDaten()` -- derselben Funktion, aus
der auch die offene Seite entsteht. Zwei Aufbauten fuer dasselbe Event
wuerden irgendwann Verschiedenes sagen, und niemand koennte entscheiden,
welcher recht hat.
DIE ZWEI SEITEN: ein gezeichnetes Wappen in der Farbe der Spalte, ein
Kopf mit Lichtkante und Verlauf, der beim Scrollen oben stehen bleibt,
und eine Lagezeile ("2 laufen - 1 kommt - 3 vorbei"). Gezaehlt mit
derselben Regel, die die Gruppen darunter bildet; was null ist,
erscheint nicht. Augenschonend bleibt es ueber Form und Tiefe, nicht
ueber Leuchtkraft.
NEBENBEFUND, BEHOBEN: Der Einlesekasten war durch Seitenwahl und
Bildweg auf 521 px gewachsen -- auf 412 px mehr als ein halber
Telefonbildschirm, bevor man die erste Kachel sieht. Er klappt jetzt zu
(82 px) und merkt sich die Wahl; ein Druck, und er bleibt offen.
BERICHTIGT, WEIL GEMESSEN:
- Der Bindestrich wurde entwertet: "4\-woechige", "Ahorn\-Welt". In
Discord ist "-" nur am Zeilenanfang eine Aufzaehlung.
- Das Klappschild hatte nur eine Handy-Regel und sonst kein
Aussehen -- auf 1400 px ein nackter Knopf mit 27 px statt 48.
- bild-kampagne mass elementFromPoint ausserhalb des
Sichtfensters und meldete "etwas liegt auf dem Knopf", wo nichts
lag; und r(null) liess den ganzen Lauf abstuerzen, statt die
Zusicherung rot zu machen.
- EINE GEGENPROBE WAR BLIND: DISCORD_MAX von 2000 auf 99999 zu
setzen liess die Pruefung gruen -- der Testbeitrag ist 1230
Zeichen lang, die Grenze wurde also nie geprueft. Jetzt mit einem
Beitrag, der ohne Kuerzung sicher zu lang waere.
GEPRUEFT: 190 (Route und Teilen, +8), 183 (Kachel, +13), 69 Messungen
(Bild, +2), 67 (Agentur), 43 (Bildleser), struktur, css-klassen,
zeichen, deutsche-texte, tippziele. Sechs Gegenproben: Link nur noch
Link, Link oben statt unten, Grenze weg, Lagezeile weg, Kopf klebt
nicht, Kasten wieder offen -- alle sechs werden bemerkt, alle Dateien
byte-gleich wiederhergestellt.
Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -155,6 +155,30 @@ const browser = await pw.chromium.launch();
|
||||
const fehlerAufSeite = [];
|
||||
|
||||
|
||||
/** Den Einlesekasten aufmachen, falls er zu ist.
|
||||
*
|
||||
* Seit dem 08.10.2026 steht er von sich aus zu -- er war auf
|
||||
* 412 px auf 521 px gewachsen, mehr als ein halber
|
||||
* Telefonbildschirm vor der ersten Kachel. Jede Stelle, die
|
||||
* seinen INHALT braucht, muss ihn also erst oeffnen.
|
||||
*
|
||||
* AN EINER STELLE, NICHT DREIMAL ABGESCHRIEBEN: Beim ersten
|
||||
* Versuch stand das Oeffnen nur oben bei der Messung, und die
|
||||
* zwei `fill()` weiter unten liefen in eine 30-Sekunden-Frist --
|
||||
* mit einer Meldung ueber ein Textfeld, das es laut Fehlertext
|
||||
* sehr wohl gab. Das ist die unangenehme Sorte Fehlschlag: Er
|
||||
* sagt die Wahrheit und fuehrt trotzdem in die Irre. */
|
||||
const kastenAufmachen = async (seite) => {
|
||||
const schild = await seite.$("#kampagne-schild");
|
||||
if (!schild) return false;
|
||||
const zu = await seite.evaluate(() =>
|
||||
document.getElementById("kampagne-form")?.hidden === true);
|
||||
if (!zu) return true;
|
||||
await schild.click();
|
||||
await seite.waitForTimeout(250);
|
||||
return true;
|
||||
};
|
||||
|
||||
const anmelden = async (seite, rolle, code) => {
|
||||
await seite.goto(BASIS + "/workspace/", { waitUntil: "networkidle" });
|
||||
await seite.click(`.rolle[data-rolle="${rolle}"]`);
|
||||
@@ -214,16 +238,47 @@ async function messen(breite, hoehe, name) {
|
||||
return { rolle: j.rolle, darf: j.darf_kampagne_einlesen, wirt: location.host };
|
||||
});
|
||||
console.log(" /api/ich: " + JSON.stringify(ichSagt));
|
||||
|
||||
/* ERST AUFKLAPPEN (08.10.2026).
|
||||
|
||||
Der Kasten steht seit heute zu -- er war auf 412 px auf 521 px
|
||||
gewachsen, mehr als ein halber Telefonbildschirm vor der ersten
|
||||
Kachel. Gemessen werden soll trotzdem der INHALT: Textfeld,
|
||||
Knopf, Kontrast, Tippziele. Zugeklappt haben die alle die Hoehe
|
||||
null, und die Messung meldete einen Schaden, wo keiner ist.
|
||||
|
||||
DASS ER ZUGEHT, MISST pruef-eventkarte (Abschnitt 4c) -- dort
|
||||
gehoert es hin, zusammen mit dem Rest der Bedienung. Hier geht
|
||||
es um das Aussehen des offenen Kastens. */
|
||||
await kastenAufmachen(seite);
|
||||
|
||||
const mass = await seite.evaluate(() => {
|
||||
const k = document.getElementById("kampagneholen");
|
||||
if (!k || k.hidden) return { da: false };
|
||||
const r = (e) => { const b = e.getBoundingClientRect();
|
||||
/* EIN FEHLENDES ELEMENT GIBT NULLEN, KEINEN ABSTURZ
|
||||
(berichtigt 08.10.2026). Vorher warf `r(null)` mitten in der
|
||||
Messung, und der ganze Lauf endete mit „unbehandelter
|
||||
Fehler" -- ohne zu sagen, WELCHES Element fehlte. Nullen
|
||||
machen die Zusicherung darunter rot und nennen dabei den
|
||||
Namen. Ein Fehlschlag ist rot und steht da; ein Absturz
|
||||
sieht aus wie ein kaputtes Werkzeug. */
|
||||
const r = (e) => {
|
||||
if (!e) return { x: 0, y: 0, b: 0, h: 0, fehlt: true };
|
||||
const b = e.getBoundingClientRect();
|
||||
return { x: Math.round(b.x), y: Math.round(b.y),
|
||||
b: Math.round(b.width), h: Math.round(b.height) }; };
|
||||
b: Math.round(b.width), h: Math.round(b.height) };
|
||||
};
|
||||
const feld = document.getElementById("kampagne-text");
|
||||
const knopf = document.getElementById("kampagne-holen");
|
||||
const sagt = document.getElementById("kampagne-sagt");
|
||||
const titel = k.querySelector(".kampagneholen__titel");
|
||||
/* DIE UEBERSCHRIFT IST SEIT DEM 08.10.2026 DAS KLAPPSCHILD.
|
||||
`.kampagneholen__titel` gibt es nicht mehr -- der `<h2>` ist
|
||||
ein `<button>` geworden, weil man ihn jetzt druecken kann.
|
||||
Die Messung sucht deshalb das Schild; faellt auch das weg,
|
||||
gibt `r()` keinen Fehler mehr, sondern Nullen, und die
|
||||
Zusicherungen unten werden rot statt den Lauf abzubrechen. */
|
||||
const titel = k.querySelector(".kampagneholen__schildwort")
|
||||
|| k.querySelector(".kampagneholen__schild");
|
||||
const st = getComputedStyle(sagt);
|
||||
return {
|
||||
da: true,
|
||||
@@ -237,6 +292,19 @@ async function messen(breite, hoehe, name) {
|
||||
/* Ueberdeckt etwas den Knopf? Nicht gerechnet, sondern gefragt:
|
||||
Was liegt an seinem Mittelpunkt wirklich obenauf? */
|
||||
obenAufKnopf: (() => {
|
||||
/* ERST IN DEN BLICK HOLEN (berichtigt 08.10.2026).
|
||||
|
||||
`elementFromPoint` fragt das SICHTFENSTER, nicht die
|
||||
Seite: Was darunter liegt, gibt `null`. Seit der Kasten
|
||||
die Seitenwahl und den Bildweg traegt, ist er auf 412 px
|
||||
hoeher als das Fenster -- und die Pruefung meldete
|
||||
„nichts liegt auf dem Knopf“ als Schaden, obwohl der
|
||||
Knopf nur weiter unten stand.
|
||||
|
||||
Die Frage bleibt dieselbe und bleibt wichtig: Am
|
||||
06.09.2026 lag der Sicht-Umschalter quer ueber dem
|
||||
Chat-Knopf. Nur muss man dafuer hinsehen, wo er ist. */
|
||||
knopf.scrollIntoView({ block: "center" });
|
||||
const b = knopf.getBoundingClientRect();
|
||||
const e = document.elementFromPoint(b.x + b.width / 2, b.y + b.height / 2);
|
||||
return e ? (e.id || e.className || e.tagName) : null;
|
||||
@@ -259,6 +327,27 @@ async function messen(breite, hoehe, name) {
|
||||
diesem Haus bei 40 und wurden genau deshalb gezogen. */
|
||||
ok(mass.knopf.h >= 44, `der Knopf ist ${mass.knopf.h} px hoch (mindestens 44)`);
|
||||
ok(mass.feld.b > 120, `das Textfeld ist ${mass.feld.b} px breit -- kein Schlitz`);
|
||||
/* WIE VIEL PLATZ FRISST DER KASTEN? (08.10.2026)
|
||||
|
||||
Er ist seit dem 07.10. um die Seitenwahl und seit dem 08.10.
|
||||
um den Bildweg gewachsen -- auf 412 px gemessen 539 px, also
|
||||
mehr als ein halber Telefonbildschirm, bevor man die erste
|
||||
Kachel sieht. Die Grenze steht hier, damit das naechste
|
||||
Stueck nicht unbemerkt dazukommt: Eine feste Zahl, die
|
||||
mitwaechst, ist keine Grenze (CLAUDE.md, 06.09.2026). */
|
||||
/* WIE VIEL PLATZ FRISST DER OFFENE KASTEN?
|
||||
|
||||
Er ist seit dem 07.10. um die Seitenwahl und seit dem 08.10. um
|
||||
den Bildweg gewachsen -- auf 412 px gemessen 521 px. Seitdem
|
||||
steht er zu (siehe oben); AUFGEKLAPPT darf er trotzdem nicht
|
||||
beliebig wachsen, sonst ist das Aufklappen ein Sprung ins
|
||||
Nichts. Die Grenze steht hier, damit das naechste Stueck nicht
|
||||
unbemerkt dazukommt: Eine feste Zahl, die mitwaechst, ist keine
|
||||
Grenze (CLAUDE.md, 06.09.2026). */
|
||||
const grenze = breite <= 560 ? 560 : 420;
|
||||
ok(mass.kasten.h <= grenze,
|
||||
`der offene Kasten bleibt bei ${mass.kasten.h} px `
|
||||
+ `(hoechstens ${grenze} auf ${breite} px)`);
|
||||
ok(mass.feld.h >= 72, `und ${mass.feld.h} px hoch, also mehrzeilig`);
|
||||
|
||||
/* NICHTS LIEGT AUF DEM KNOPF. Am 06.09.2026 lag der Sicht-Umschalter
|
||||
@@ -348,6 +437,7 @@ await messen(412, 915, "Auf dem Handy, haeufigste Android-Breite");
|
||||
await seite.goto(BASIS + "/workspace/bereich.html?b=agentur", { waitUntil: "networkidle" });
|
||||
await seite.waitForTimeout(1400);
|
||||
|
||||
await kastenAufmachen(seite);
|
||||
await seite.fill("#kampagne-text",
|
||||
"Dein Creator Network lädt dich ein: https://www.tiktok.com/t/ZSbqPsFa8/");
|
||||
await seite.$eval("#kampagne-holen", (b) => b.click());
|
||||
@@ -395,6 +485,7 @@ await messen(412, 915, "Auf dem Handy, haeufigste Android-Breite");
|
||||
}), { status: 502, headers: { "Content-Type": "application/json" } });
|
||||
};
|
||||
});
|
||||
await kastenAufmachen(seite);
|
||||
await seite.fill("#kampagne-text", "https://www.tiktok.com/t/ZSbqPsFa8/");
|
||||
await seite.$eval("#kampagne-holen", (b) => b.click());
|
||||
await seite.waitForFunction(
|
||||
|
||||
@@ -1423,6 +1423,127 @@ melde("\n=== 4c. Die Seitenwahl ===");
|
||||
ok(links.includes("Oktober Event") && links.includes("Glow Up Struktur"),
|
||||
`ohne Wahl entscheidet weiterhin die Herkunft (links: ${JSON.stringify(links)})`);
|
||||
|
||||
/* ---- DIE ZWEI SEITEN, KRASSER (08.10.2026) ----------------
|
||||
|
||||
Filipe: „perfektionier die zwei events kategorien. ich will es
|
||||
noch viel krasser und spezieller.“
|
||||
|
||||
Gemessen wird, was dabei herauskommen SOLL, und zwar so, dass ein
|
||||
leerer Kasten nicht durchrutscht: Jede Zusicherung trägt die Zahl
|
||||
der untersuchten Dinge in ihrer Bedingung. */
|
||||
const seiten = await seite.evaluate(() => {
|
||||
const lies = (w) => {
|
||||
const s = getComputedStyle(w);
|
||||
return {
|
||||
breite: Math.round(w.getBoundingClientRect().width),
|
||||
hoehe: Math.round(w.getBoundingClientRect().height),
|
||||
rand: s.borderTopWidth,
|
||||
grund: s.backgroundImage.slice(0, 40),
|
||||
klebt: s.position,
|
||||
};
|
||||
};
|
||||
return [...document.querySelectorAll(".kv-seite")].map((s) => {
|
||||
const w = s.querySelector(".kv-seite__wappen");
|
||||
const k = s.querySelector(".kv-seite__kopf");
|
||||
return {
|
||||
seite: s.dataset.seite,
|
||||
ton: getComputedStyle(s).getPropertyValue("--kv-ton").trim(),
|
||||
wappen: w ? lies(w) : null,
|
||||
kopf: k ? lies(k) : null,
|
||||
lage: [...s.querySelectorAll(".kv-seite__lageteil")].map((x) => ({
|
||||
stand: x.dataset.stand || null,
|
||||
wort: x.textContent.trim(),
|
||||
farbe: getComputedStyle(x).color,
|
||||
})),
|
||||
};
|
||||
});
|
||||
});
|
||||
melde(" -- Seiten: " + JSON.stringify(seiten));
|
||||
|
||||
ok(seiten.length === 2, `zwei Seiten (${seiten.length})`);
|
||||
|
||||
/* ---- Das Wappen ------------------------------------------- */
|
||||
const wappen = seiten.map((s) => s.wappen).filter(Boolean);
|
||||
ok(wappen.length === 2, `jede Seite trägt ein Wappen (${wappen.length})`);
|
||||
/* ES WIRD WIRKLICH GEZEICHNET. Ein Element mit 0 px ist kein
|
||||
Abzeichen, und ein Verlauf, der nicht ankommt, auch
|
||||
nicht — genau dieser Fehlgriff (Merkmal gesetzt, nichts zu sehen)
|
||||
hat am 07.10.2026 einen Farbpunkt 10 statt 22 px breit gemacht. */
|
||||
ok(wappen.every((w) => w.breite >= 24 && w.hoehe >= 24),
|
||||
`und es ist groß genug: ${JSON.stringify(wappen.map((w) => `${w.breite}x${w.hoehe}`))}`);
|
||||
ok(wappen.every((w) => /gradient/.test(w.grund)),
|
||||
"und wirklich gezeichnet, nicht nur vorhanden");
|
||||
|
||||
/* ---- Der Kopf klebt ---------------------------------------- */
|
||||
/* Bei fünf Gruppen und einem Dutzend Kacheln je Spalte ist „auf
|
||||
welcher Seite bin ich gerade“ eine echte Frage. */
|
||||
ok(seiten.every((s) => s.kopf && s.kopf.klebt === "sticky"),
|
||||
`der Spaltenkopf bleibt beim Scrollen oben (${
|
||||
JSON.stringify(seiten.map((s) => s.kopf?.klebt))})`);
|
||||
|
||||
/* ---- Die Lage der Seite ------------------------------------ */
|
||||
const alleTeile = seiten.flatMap((s) => s.lage);
|
||||
ok(alleTeile.length >= 2,
|
||||
`die Spalten sagen, was los ist (${alleTeile.length} Angaben: ${
|
||||
JSON.stringify(alleTeile.map((x) => x.wort))})`);
|
||||
/* KEINE NULL. „0 laufen“ ist keine Auskunft, sondern Füllmaterial —
|
||||
und drei Nullen lassen eine leere Spalte voll aussehen. */
|
||||
ok(!alleTeile.some((x) => /^0 /.test(x.wort)),
|
||||
"und nennen nie eine Null");
|
||||
/* DIE ZAHLEN STIMMEN MIT DEN GRUPPEN DARUNTER ÜBEREIN. Eine zweite
|
||||
Zählung könnte etwas anderes sagen als die Überschriften zwei
|
||||
Zentimeter weiter unten. */
|
||||
const summeLage = alleTeile
|
||||
.map((x) => Number((x.wort.match(/^(\d+)/) || [])[1] || 0))
|
||||
.reduce((a, b) => a + b, 0);
|
||||
ok(summeLage === 5,
|
||||
`und zählen alle 5 Events (Summe ${summeLage})`);
|
||||
/* Verschiedene Zustände, verschiedene Töne — sonst wäre die
|
||||
Einfärbung Zierde ohne Aussage. */
|
||||
const toene = new Set(alleTeile.filter((x) => x.stand).map((x) => x.farbe));
|
||||
ok(toene.size >= 2,
|
||||
`und verschiedene Zustände sehen verschieden aus (${toene.size} Töne)`);
|
||||
|
||||
/* ---- DER KASTEN STEHT ZU, UND DAS IST DER ZWECK (08.10.2026) --
|
||||
|
||||
Gemessen auf 412 px war er auf 521 px gewachsen -- mehr als ein
|
||||
halber Telefonbildschirm, bevor man die erste Kachel sieht. Er
|
||||
ist ein Werkzeug, nicht der Inhalt der Seite.
|
||||
|
||||
ZUERST WIRD GEMESSEN, DASS ER ZU IST, dann wird er
|
||||
aufgemacht. Ohne den ersten Teil waere „er laesst sich
|
||||
aufklappen" auch bei einem Kasten gruen, der nie zugeht. */
|
||||
const zuerstZu = await seite.evaluate(() => {
|
||||
const form = document.getElementById("kampagne-form");
|
||||
const schild = document.getElementById("kampagne-schild");
|
||||
return {
|
||||
formVerborgen: form?.hidden === true,
|
||||
gedrueckt: schild?.getAttribute("aria-expanded"),
|
||||
schildHoehe: schild ? Math.round(schild.getBoundingClientRect().height) : null,
|
||||
kastenHoehe: Math.round(
|
||||
document.getElementById("kampagneholen")?.getBoundingClientRect().height ?? 0),
|
||||
wort: schild?.querySelector(".kampagneholen__schildwort")?.textContent.trim() || null,
|
||||
};
|
||||
});
|
||||
melde(" -- Kasten zu: " + JSON.stringify(zuerstZu));
|
||||
ok(zuerstZu.formVerborgen === true && zuerstZu.gedrueckt === "false",
|
||||
`der Einlesekasten steht von sich aus zu (hidden=${zuerstZu.formVerborgen}, `
|
||||
+ `aria-expanded=${zuerstZu.gedrueckt})`);
|
||||
/* UND ER BRAUCHT DANN FAST KEINEN PLATZ. Die Zahl steht in der
|
||||
Bedingung, nicht nur im Meldetext: Ein zugeklappter Kasten, der
|
||||
trotzdem 500 px hoch ist, waere kein zugeklappter Kasten. */
|
||||
ok(zuerstZu.kastenHoehe <= 110,
|
||||
`und nimmt zugeklappt nur ${zuerstZu.kastenHoehe} px (hoechstens 110)`);
|
||||
/* 44 px Tippziel -- dieselbe Vorgabe wie ueberall im Haus. */
|
||||
ok(zuerstZu.schildHoehe >= 44,
|
||||
`sein Schild ist ${zuerstZu.schildHoehe} px hoch (mindestens 44)`);
|
||||
ok(/Kampagne aus einer Einladung/.test(zuerstZu.wort || ""),
|
||||
`und sagt, was dahinter steckt: ${JSON.stringify(zuerstZu.wort)}`);
|
||||
|
||||
/* EIN DRUCK MACHT IHN AUF. */
|
||||
await seite.click("#kampagne-schild");
|
||||
await seite.waitForTimeout(250);
|
||||
|
||||
/* ---- Die Auswahl im Kasten -------------------------------- */
|
||||
const wahl = await seite.evaluate(() => {
|
||||
const f = document.querySelector(".kampagneholen__wahl");
|
||||
|
||||
@@ -1115,6 +1115,131 @@ melde("=== 12b. Ein Event nach draußen teilen ===");
|
||||
ok(t1.code === 200 && /\/e\/[A-Za-z0-9_-]{20,}$/.test(j1.weg || ""),
|
||||
`der Link entsteht: ${String(j1.weg).replace(/^https?:\/\/[^/]+/, "…")}`);
|
||||
|
||||
/* ==== UND EIN FERTIGER BEITRAG FÜR DISCORD (08.10.2026) ======
|
||||
|
||||
Filipe: „beim teilen wird auch nur der link geteilt, und ich
|
||||
will dass wenn ich es teile im discord der agentur, dass ein
|
||||
banner und ein kompletter fertiger text für die creator
|
||||
entsteht und nicht einfach der link kopiert wird damit die
|
||||
leute drauf gehen. das ist kake.“
|
||||
|
||||
Gemessen wird nicht „es kommt ein Text“, sondern dass der Text
|
||||
die vier Dinge trägt, wegen denen man ihn schreibt: Titel,
|
||||
Zeitraum, Aufgaben, Link — und dass Discord ihn annimmt. */
|
||||
const beitrag = String(j1.text || "");
|
||||
ok(beitrag.length > 200,
|
||||
`der fertige Beitrag kommt mit (${beitrag.length} Zeichen)`);
|
||||
|
||||
/* DISCORD NIMMT 2000 ZEICHEN JE NACHRICHT. Eine längere wird
|
||||
beim Einfügen abgeschnitten — mitten im Wort, und niemand
|
||||
merkt es, bis ein Creator nachfragt. */
|
||||
ok(beitrag.length <= 2000,
|
||||
`und bleibt unter der Discord-Grenze (${beitrag.length} von 2000)`);
|
||||
|
||||
ok(beitrag.includes("Gipfelstürmer"), "der Titel steht drin");
|
||||
ok(/04\.10\.2026/.test(beitrag), "der Zeitraum steht drin");
|
||||
ok(beitrag.includes("1 Diamant = 1 Punkt"),
|
||||
"die Aufgaben stehen drin — genau das, was beim Link fehlte");
|
||||
ok(/Ahorn-Welt/.test(beitrag) && /12\.000 Coins/.test(beitrag),
|
||||
"die Geschenke mit Coinpreis ebenso");
|
||||
|
||||
/* DER LINK STEHT GANZ AM ENDE UND NACKT.
|
||||
Am Ende, weil Discord die Vorschau UNTER die Nachricht setzt --
|
||||
steht er oben, schiebt das Banner den Text aus dem Blick.
|
||||
Nackt, weil spitze Klammern die Vorschau unterdrücken: dann
|
||||
gäbe es genau das Banner nicht, um das es geht. */
|
||||
ok(beitrag.trimEnd().endsWith(j1.weg),
|
||||
`der Link steht ganz am Ende (…${beitrag.trimEnd().slice(-34)})`);
|
||||
ok(!beitrag.includes(`<${j1.weg}>`),
|
||||
" und nackt, nicht in spitzen Klammern — sonst kein Banner");
|
||||
|
||||
/* DISCORD-AUSZEICHNUNG: Überschriften und Aufzählungen, damit es
|
||||
in Discord wie ein Aushang aussieht und nicht wie eine
|
||||
Textwand. */
|
||||
ok(/^# /m.test(beitrag) && /^## /m.test(beitrag),
|
||||
"Überschriften sind gesetzt (# und ##)");
|
||||
ok(/^- /m.test(beitrag), "und die Aufgaben als Aufzählung");
|
||||
|
||||
/* EIN BINDESTRICH MITTEN IM WORT WIRD NICHT ENTWERTET. Beim
|
||||
ersten Blick auf den Beitrag stand dort „Ahorn\\-Welt“ und
|
||||
„4\\-wöchige“ — die Rückstriche sichtbar im Text. In Discord
|
||||
ist `-` nur am Zeilenanfang eine Aufzählung. */
|
||||
ok(!/\w\\-\w/.test(beitrag),
|
||||
"kein Rückstrich mitten im Wort");
|
||||
|
||||
/* ---- DIE GRENZE WIRD AN EINEM LANGEN BEITRAG GEMESSEN -------
|
||||
|
||||
GEFUNDEN DURCH DIE GEGENPROBE (08.10.2026): Als Sabotage wurde
|
||||
`DISCORD_MAX` von 2000 auf 99999 gesetzt -- und die Pruefung
|
||||
blieb GRUEN. Der Beitrag von „Gipfelstuermer" ist 1230 Zeichen
|
||||
lang; ob die Grenze bei 2000 oder bei 99999 steht, aendert daran
|
||||
nichts. `beitrag.length <= 2000` hat also nie die GRENZE
|
||||
geprueft, sondern nur diesen einen Beitrag.
|
||||
|
||||
Genau die Sorte gruener Haken, die nichts misst (CLAUDE.md,
|
||||
01.09.2026). Geprueft wird deshalb mit einem Beitrag, der ohne
|
||||
Kuerzung sicher zu lang WAERE -- und daneben, dass das
|
||||
Wesentliche trotzdem stehen bleibt. Ein Beitrag, der die Grenze
|
||||
einhaelt, indem er alles wegwirft, waere auch keine Loesung. */
|
||||
{
|
||||
const { discordText } = await import("./workspace-teilen.js");
|
||||
const satz = "Gipfelstürmer ist eine TikTok LIVE Kampagne, in welcher "
|
||||
+ "Creator*innen Punkte sammeln können, wenn sie Diamanten sammeln. ";
|
||||
const lang = {
|
||||
titel: "Ein sehr langer Kampagnentitel zum Messen",
|
||||
zeitraum: "04.10.2026 – 07.10.2026",
|
||||
text: satz.repeat(40),
|
||||
basis: "https://beispiel.de/e/abc",
|
||||
vonTikTok: true,
|
||||
aufgaben: Array.from({ length: 12 }, (x, i) =>
|
||||
`Aufgabe ${i + 1} mit einem recht ausführlichen Text dahinter`),
|
||||
gaben: Array.from({ length: 8 }, (x, i) =>
|
||||
({ text: `Geschenk ${i + 1}`, coins: (i + 1) * 999, zusatz: null })),
|
||||
abschnitte: [{ art: "rangliste", titel: "RANGLISTE", zeilen: [
|
||||
{ art: "punkt", text: "Gewertet wird vom 04.10. bis zum 07.10." }] }],
|
||||
};
|
||||
/* ERST BELEGEN, DASS DER FALL UEBERHAUPT EINTRITT. Ohne diese
|
||||
Zeile misst die naechste wieder nichts. */
|
||||
const ungekuerzt = satz.repeat(40).length;
|
||||
ok(ungekuerzt > 2000,
|
||||
`der Probetext allein ist ${ungekuerzt} Zeichen lang -- er MUSS gekürzt werden`);
|
||||
const kurz = discordText(lang);
|
||||
ok(kurz.length <= 2000,
|
||||
`und der Beitrag bleibt bei ${kurz.length} Zeichen`);
|
||||
ok(kurz.length < ungekuerzt,
|
||||
" es wurde also wirklich gekürzt, nicht nur gemessen");
|
||||
/* WAS IMMER STEHEN BLEIBT: Titel, Zeitraum, Aufgaben, Link.
|
||||
Das sind die vier Dinge, wegen denen man den Beitrag
|
||||
schreibt. */
|
||||
ok(kurz.includes("Ein sehr langer Kampagnentitel"), " der Titel bleibt");
|
||||
ok(/04\.10\.2026/.test(kurz), " der Zeitraum bleibt");
|
||||
ok(/^- Aufgabe 1 /m.test(kurz), " die Aufgaben bleiben");
|
||||
ok(kurz.trimEnd().endsWith("https://beispiel.de/e/abc"),
|
||||
" und der Link steht am Ende");
|
||||
|
||||
/* DER AEUSSERSTE FALL: ein Titel, der allein fast die Grenze
|
||||
sprengt. Auch dann muss der Link dastehen -- ohne ihn ist der
|
||||
Beitrag eine Sackgasse. */
|
||||
const riesig = discordText({ ...lang, titel: "A".repeat(1500) });
|
||||
ok(riesig.length <= 2000 && riesig.trimEnd().endsWith("https://beispiel.de/e/abc"),
|
||||
`selbst bei einem 1500-Zeichen-Titel: ${riesig.length} Zeichen, Link am Ende`);
|
||||
}
|
||||
|
||||
/* UND NICHTS ÜBER UNS. Dieselbe Grenze wie auf der offenen
|
||||
Seite: Was die Kampagne sagt, geht hinaus — wer wir sind,
|
||||
nicht. Der Beitrag landet in einem Kanal, den auch Fremde
|
||||
lesen können. */
|
||||
const verbotenImBeitrag = [
|
||||
["ein Name", "Dogfather"], ["eine Rolle", "admin"],
|
||||
["die Kampagnennummer", ID_GIPFEL],
|
||||
["ein Hinweis an DogFather", "bitte nachtragen"],
|
||||
["ein Feldname", "erstellt_von"],
|
||||
].filter(([, w]) => beitrag.includes(w));
|
||||
ok(verbotenImBeitrag.length === 0,
|
||||
"nichts Internes im Beitrag"
|
||||
+ (verbotenImBeitrag.length
|
||||
? `: ${verbotenImBeitrag.map((x) => x[0]).join(", ")}` : ""));
|
||||
|
||||
/* DERSELBE SCHLUESSEL BEIM ZWEITEN DRUCK. Ein neuer wuerde den
|
||||
Link still ins Leere laufen lassen, den jemand schon gepostet
|
||||
hat. */
|
||||
@@ -1123,6 +1248,11 @@ melde("=== 12b. Ein Event nach draußen teilen ===");
|
||||
const j2 = JSON.parse(t2.text || "{}");
|
||||
ok(j2.weg === j1.weg && j2.schon === true,
|
||||
"der zweite Druck gibt denselben Link, keinen neuen");
|
||||
/* UND DEN BEITRAG GLEICH MIT. Wer ihn ein zweites Mal in einen
|
||||
anderen Kanal stellen will, soll nicht erst zurückziehen und
|
||||
neu teilen müssen. */
|
||||
ok(String(j2.text || "") === beitrag,
|
||||
`und denselben Beitrag (${String(j2.text || "").length} Zeichen)`);
|
||||
|
||||
const schluessel = String(j1.weg).split("/e/")[1];
|
||||
|
||||
|
||||
+256
-16
@@ -157,7 +157,33 @@ teilenRouter.post("/workspace/api/eintrag/:id(\\d+)/teilen", (req, res) => {
|
||||
detail: `Eintrag ${e.id} "${String(e.titel).slice(0, 60)}"`,
|
||||
});
|
||||
}
|
||||
return res.json({ weg: `${TEILEN_BASIS}/e/${schluessel}`, schon: !!e.teilen_token });
|
||||
/* ---- DER FERTIGE BEITRAG KOMMT MIT (08.10.2026) ----------
|
||||
|
||||
Filipe: „beim teilen wird auch nur der link geteilt ... das
|
||||
ist kake.“ Er hat recht: Ein Link ist eine Aufforderung,
|
||||
und wer ihn nicht anklickt, weiß nicht, dass es die
|
||||
Kampagne gibt.
|
||||
|
||||
GEBAUT WIRD HIER, NICHT IM BROWSER. Der Beitrag enthält
|
||||
Aufgaben, Geschenke und Regeln -- dieselben Daten, die die
|
||||
offene Seite zeigt, aus derselben Funktion
|
||||
(`teilenDaten`). Im Browser gebaut wäre es die zweite
|
||||
Fassung derselben Sache, und die läuft auseinander.
|
||||
|
||||
FÄLLT ER AUS, BLEIBT DER LINK. Ein Teilen, das wegen eines
|
||||
Textbaufehlers gar nicht geht, wäre schlimmer als eines
|
||||
ohne Text -- deshalb steht der Bau in einem eigenen
|
||||
`try`. */
|
||||
const basis = `${TEILEN_BASIS}/e/${schluessel}`;
|
||||
let beitrag = null;
|
||||
try {
|
||||
const voll = eventMitNummer(e.id);
|
||||
if (voll) beitrag = discordText(teilenDaten(voll, basis));
|
||||
} catch (f) {
|
||||
console.error("[teilen] Beitrag:", f?.message);
|
||||
beitrag = null;
|
||||
}
|
||||
return res.json({ weg: basis, text: beitrag, schon: !!e.teilen_token });
|
||||
} catch (fehler) {
|
||||
console.error("[teilen] anlegen:", fehler?.message);
|
||||
return res.status(503).json({ fehler: "Das Teilen hat gerade nicht geklappt." });
|
||||
@@ -196,6 +222,31 @@ function tagText(iso) {
|
||||
return m ? `${m[3]}.${m[2]}.${m[1]}` : null;
|
||||
}
|
||||
|
||||
/* DIE SPALTEN STEHEN EINMAL DA (08.10.2026).
|
||||
|
||||
Seit es zwei Abfragen gibt -- eine ueber den Schluessel (die
|
||||
offene Seite) und eine ueber die Nummer (der Discord-Text beim
|
||||
Teilen) -- waere eine zweite Liste die Sorte Abschrift, die in
|
||||
diesem Haus schon einmal drei Spalten gekostet hat. Wer hier
|
||||
etwas ergaenzt, ergaenzt es fuer beide.
|
||||
|
||||
`erstellt_von` und `haus` stehen ABSICHTLICH nicht dabei: Was
|
||||
die Kampagne sagt, geht hinaus -- wer wir sind, nicht. */
|
||||
const TEILEN_SPALTEN = `id, titel, text, datum, event_ende, kampagne_id,
|
||||
teilen_seit, teilen_token, event_aufgaben, event_regeln,
|
||||
kampagne_daten`;
|
||||
|
||||
/** Das Event zu einer Nummer -- fuer den Weg ueber den Teilen-Knopf. */
|
||||
function eventMitNummer(id) {
|
||||
try {
|
||||
return db().prepare(
|
||||
`SELECT ${TEILEN_SPALTEN}
|
||||
FROM eintraege
|
||||
WHERE id = ? AND bereich = 'agentur' AND art = 'kampagne'`)
|
||||
.get(Number(id)) || null;
|
||||
} catch { return null; }
|
||||
}
|
||||
|
||||
/** Das Event zu einem Schlüssel -- oder null. */
|
||||
function eventZu(token) {
|
||||
if (!/^[A-Za-z0-9_-]{10,64}$/.test(String(token || ""))) return null;
|
||||
@@ -207,8 +258,7 @@ function eventZu(token) {
|
||||
hinaus; was ueber UNS etwas sagt, nicht. Deshalb steht
|
||||
`erstellt_von` hier weiterhin nicht, und `haus` auch
|
||||
nicht. */
|
||||
`SELECT id, titel, text, datum, event_ende, kampagne_id, teilen_seit,
|
||||
event_aufgaben, event_regeln, kampagne_daten
|
||||
`SELECT ${TEILEN_SPALTEN}
|
||||
FROM eintraege
|
||||
WHERE teilen_token = ? AND bereich = 'agentur' AND art = 'kampagne'`)
|
||||
.get(String(token)) || null;
|
||||
@@ -242,15 +292,21 @@ teilenRouter.get("/e/:token/bild", (req, res) => {
|
||||
return liefereDatei(req, res, pfad);
|
||||
});
|
||||
|
||||
teilenRouter.get("/e/:token", (req, res) => {
|
||||
nichtIndizieren(res);
|
||||
const e = eventZu(req.params.token);
|
||||
if (!e) {
|
||||
/* ZURUECKGEZOGEN SIEHT AUS WIE NIE DAGEWESEN. Ein eigener Satz
|
||||
(„war mal da") würde bestätigen, dass es diesen Schlüssel gab --
|
||||
und damit Durchprobieren belohnen. */
|
||||
return res.status(404).type("text/html; charset=utf-8").send(seiteLeer());
|
||||
}
|
||||
/* =====================================================================
|
||||
WAS VON EINEM EVENT NACH DRAUSSEN GEHT -- AN EINER STELLE
|
||||
(08.10.2026)
|
||||
|
||||
Gerufen von ZWEI Wegen: der offenen Seite (`GET /e/:token`) und
|
||||
dem Discord-Text (`POST .../teilen`). Beide zeigen dasselbe
|
||||
Event; haetten sie je einen eigenen Aufbau, wuerde die Seite
|
||||
irgendwann etwas anderes sagen als der Beitrag daneben -- und
|
||||
niemand koennte sagen, welcher von beiden recht hat.
|
||||
|
||||
Die Grenze ist dieselbe wie vorher: Was die KAMPAGNE sagt, geht
|
||||
hinaus; keine Hinweiszeile an DogFather, kein Steckbrief, kein
|
||||
Name, keine Rolle, kein Haus.
|
||||
===================================================================== */
|
||||
export function teilenDaten(e, basis) {
|
||||
const von = tagText(e.datum);
|
||||
const bis = tagText(e.event_ende);
|
||||
const zeitraum = von && bis && bis !== von ? `${von} – ${bis}` : (von || "");
|
||||
@@ -326,15 +382,13 @@ teilenRouter.get("/e/:token", (req, res) => {
|
||||
const aufgabenKurz = aufgaben.slice(0, 3).join(" · ");
|
||||
const beschreibung = [zeitraum, kurz || aufgabenKurz]
|
||||
.filter(Boolean).join(" · ");
|
||||
const basis = `${TEILEN_BASIS}/e/${encodeURIComponent(req.params.token)}`;
|
||||
const hatBild = (() => {
|
||||
try {
|
||||
return !!db().prepare("SELECT 1 FROM dateien WHERE eintrag_id = ? LIMIT 1").get(e.id);
|
||||
} catch { return false; }
|
||||
})();
|
||||
|
||||
res.type("text/html; charset=utf-8");
|
||||
return res.send(seitePlakat({
|
||||
return {
|
||||
titel: String(e.titel || "Event"),
|
||||
zeitraum,
|
||||
text: kurz,
|
||||
@@ -347,7 +401,193 @@ teilenRouter.get("/e/:token", (req, res) => {
|
||||
gabenTitel: gabenAb?.titel || null,
|
||||
gabenUnter: gabenAb?.unter || null,
|
||||
abschnitte: rest,
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/* =====================================================================
|
||||
EIN FERTIGER BEITRAG FÜR DISCORD (08.10.2026)
|
||||
|
||||
Filipe: „beim teilen wird auch nur der link geteilt, und ich will
|
||||
dass wenn ich es teile im discord der agentur, dass ein banner und
|
||||
ein kompletter fertiger text für die creator entsteht und nicht
|
||||
einfach der link kopiert wird damit die leute drauf gehen. das ist
|
||||
kake."
|
||||
|
||||
Er hat recht, und zwar aus einem Grund, den man im Workspace nicht
|
||||
sieht: Ein Link in einem Discord-Kanal ist eine AUFFORDERUNG. Wer
|
||||
ihn nicht anklickt — und das sind die meisten — weiß nicht, dass es
|
||||
die Kampagne gibt. Was in Discord steht, muss die Kampagne SEIN.
|
||||
|
||||
---------------------------------------------------------------------
|
||||
DER BANNER KOMMT VON SELBST, UND ZWAR ÜBER DEN LINK
|
||||
|
||||
Discord baut aus den Open-Graph-Angaben der offenen Seite eine
|
||||
Vorschau mit großem Bild. Die steht UNTER der Nachricht. Deshalb:
|
||||
|
||||
- Der Link steht GANZ AM ENDE. Steht er oben, schiebt die Vorschau
|
||||
den Text nach unten aus dem Blick.
|
||||
- Der Link steht NACKT da, nicht in spitzen Klammern. `<…>`
|
||||
unterdrückt die Vorschau — und damit genau das Banner, um das es
|
||||
geht.
|
||||
|
||||
---------------------------------------------------------------------
|
||||
2000 ZEICHEN, UND NICHT EINS MEHR
|
||||
|
||||
Discord nimmt pro Nachricht 2000 Zeichen. Eine Nachricht, die beim
|
||||
Einfügen abgeschnitten wird, endet mitten im Wort — und niemand
|
||||
merkt es, bis ein Creator nachfragt. Gekürzt wird deshalb HIER, in
|
||||
fester Reihenfolge und an Abschnittsgrenzen:
|
||||
|
||||
1. „Gut zu wissen" fällt weg (steht vollständig auf der Seite)
|
||||
2. die Einleitung wird kürzer (steht vollständig auf der Seite)
|
||||
3. die Geschenke fallen weg (stehen vollständig auf der Seite)
|
||||
|
||||
Titel, Zeitraum, Aufgaben und der Link bleiben IMMER. Das sind die
|
||||
vier Dinge, wegen denen man den Beitrag überhaupt schreibt.
|
||||
|
||||
---------------------------------------------------------------------
|
||||
KEINE NAMEN, KEINE ROLLEN
|
||||
|
||||
Derselbe Satz wie bei der offenen Seite: Was die Kampagne sagt, geht
|
||||
hinaus. Wer sie eingelesen hat, steht nirgends — der Beitrag kommt
|
||||
vom Team, nicht von einer Person.
|
||||
===================================================================== */
|
||||
|
||||
const DISCORD_MAX = 2000;
|
||||
|
||||
/** Discord-Auszeichnung in fremdem Text entwerten.
|
||||
*
|
||||
* EIN TITEL MIT EINEM STERN DARIN würde sonst die halbe Nachricht
|
||||
* kursiv setzen. Entwertet wird nur dort, wo FREMDER Text steht
|
||||
* (Titel, Aufgaben, Geschenke) -- nicht in den Überschriften, die wir
|
||||
* selbst schreiben.
|
||||
*
|
||||
* ZWEI SORTEN ZEICHEN, UND SIE BRAUCHEN ZWEI REGELN (berichtigt
|
||||
* 08.10.2026, beim ersten Blick auf den fertigen Beitrag):
|
||||
*
|
||||
* ÜBERALL \ * _ ~ ` | die wirken mitten im Wort
|
||||
* NUR AM ANFANG # - > die wirken nur am Zeilenanfang
|
||||
*
|
||||
* Zuerst stand der Bindestrich in der ersten Gruppe. Heraus kam
|
||||
* „4\-wöchige Wachstum Challenge" und „Ahorn\-Welt" -- die
|
||||
* Rückstriche standen sichtbar im Beitrag. Ein Bindestrich mitten im
|
||||
* Wort ist in Discord keine Aufzählung; ihn zu entwerten macht den
|
||||
* Text kaputt, statt ihn zu retten. Dasselbe gilt für `#` und `>`.
|
||||
*
|
||||
* Der Rückstrich ist in Discord das Entwertungszeichen, genau wie in
|
||||
* Markdown -- und steht deshalb selbst mit in der ersten Gruppe. */
|
||||
function discordRoh(s) {
|
||||
return String(s ?? "")
|
||||
.replace(/([\\*_~`|])/g, "\\$1")
|
||||
.replace(/^([#\->])/, "\\$1");
|
||||
}
|
||||
|
||||
/** Ein fertiger Beitrag für den Agentur-Kanal.
|
||||
*
|
||||
* @param {object} d was `teilenDaten()` geliefert hat
|
||||
* @returns {string} höchstens 2000 Zeichen, Link am Ende
|
||||
*/
|
||||
export function discordText(d) {
|
||||
const kopf = [];
|
||||
kopf.push(`# ${discordRoh(d.titel)}`);
|
||||
const zeile2 = [];
|
||||
if (d.zeitraum) zeile2.push(`**${discordRoh(d.zeitraum)}**`);
|
||||
zeile2.push(d.vonTikTok ? "TikTok-Kampagne" : "Event der Agentur");
|
||||
kopf.push(zeile2.join(" · "));
|
||||
|
||||
/* Die Einleitung. Sie darf lang sein -- gekürzt wird sie erst, wenn
|
||||
es wirklich eng wird (siehe unten). */
|
||||
const einleitung = d.text ? [``, discordRoh(d.text)] : [];
|
||||
|
||||
const aufgaben = (d.aufgaben || []).length
|
||||
? [``, `## 🎯 ${d.aufgaben.length === 1 ? "Deine Aufgabe" : "Deine Aufgaben"}`,
|
||||
...d.aufgaben.map((a) => `- ${discordRoh(a)}`)]
|
||||
: [];
|
||||
|
||||
const gaben = (d.gaben || []).length
|
||||
? [``, "## 🎁 Diese Geschenke zählen",
|
||||
...d.gaben.map((g) => {
|
||||
const c = coinText(g.coins);
|
||||
const zusatz = c || g.zusatz;
|
||||
return `- **${discordRoh(g.text)}**${zusatz ? ` · ${discordRoh(zusatz)}` : ""}`;
|
||||
})]
|
||||
: [];
|
||||
|
||||
/* „Gut zu wissen": die Punkte aus Rangliste, Zeitplan und Teilnahme
|
||||
-- aber nur die kurzen. Ein ganzer Teilnahmebedingungs-Absatz in
|
||||
einem Discord-Beitrag liest niemand; dafür gibt es die Seite. */
|
||||
const merken = [];
|
||||
for (const a of d.abschnitte || []) {
|
||||
if (!/rangliste|zeitplan|teilnahme/i.test(a.art || "")) continue;
|
||||
for (const z of a.zeilen || []) {
|
||||
if (z.art !== "punkt" && z.art !== "text") continue;
|
||||
if (z.text.length > 120) continue;
|
||||
merken.push(`- ${discordRoh(z.text)}`);
|
||||
if (merken.length >= 5) break;
|
||||
}
|
||||
if (merken.length >= 5) break;
|
||||
}
|
||||
const wissen = merken.length ? [``, "## ℹ️ Gut zu wissen", ...merken] : [];
|
||||
|
||||
/* DER LINK ZULETZT UND NACKT -- Begründung oben. */
|
||||
const fuss = [``, "-# Alle Regeln, Preise und Ligen im Detail:", d.basis];
|
||||
|
||||
/* ZUSAMMENSETZEN UND NOTFALLS KÜRZEN. Gebaut wird von der
|
||||
vollständigen Fassung abwärts, nicht von der kurzen aufwärts: So
|
||||
steht immer die beste Fassung da, die noch passt. */
|
||||
const bauen = (teile) => teile.flat().join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
||||
|
||||
let aus = bauen([kopf, einleitung, aufgaben, gaben, wissen, fuss]);
|
||||
if (aus.length <= DISCORD_MAX) return aus;
|
||||
|
||||
aus = bauen([kopf, einleitung, aufgaben, gaben, fuss]);
|
||||
if (aus.length <= DISCORD_MAX) return aus;
|
||||
|
||||
/* Die Einleitung auf zwei Sätze. An einer Satzgrenze, nicht mitten
|
||||
im Wort -- ein abgeschnittenes Wort sieht nach einem Fehler aus. */
|
||||
if (d.text) {
|
||||
const saetze = String(d.text).split(/(?<=[.!?])\s+/);
|
||||
const kurz = saetze.slice(0, 2).join(" ");
|
||||
aus = bauen([kopf, [``, discordRoh(kurz)], aufgaben, gaben, fuss]);
|
||||
if (aus.length <= DISCORD_MAX) return aus;
|
||||
}
|
||||
|
||||
aus = bauen([kopf, aufgaben, gaben, fuss]);
|
||||
if (aus.length <= DISCORD_MAX) return aus;
|
||||
|
||||
aus = bauen([kopf, aufgaben, fuss]);
|
||||
if (aus.length <= DISCORD_MAX) return aus;
|
||||
|
||||
/* Selbst Titel, Aufgaben und Link passen nicht -- dann werden die
|
||||
Aufgaben beschnitten. Der Link bleibt in jedem Fall: Ohne ihn ist
|
||||
der Beitrag eine Sackgasse. */
|
||||
const ohneAufgaben = bauen([kopf, fuss]);
|
||||
const platz = DISCORD_MAX - ohneAufgaben.length - 40;
|
||||
const gekuerzt = [];
|
||||
let laenge = 0;
|
||||
for (const a of aufgaben.slice(2)) {
|
||||
if (laenge + a.length + 1 > platz) break;
|
||||
gekuerzt.push(a);
|
||||
laenge += a.length + 1;
|
||||
}
|
||||
return bauen([kopf,
|
||||
gekuerzt.length ? [``, aufgaben[1], ...gekuerzt, "- …"] : [],
|
||||
fuss]);
|
||||
}
|
||||
|
||||
teilenRouter.get("/e/:token", (req, res) => {
|
||||
nichtIndizieren(res);
|
||||
const e = eventZu(req.params.token);
|
||||
if (!e) {
|
||||
/* ZURUECKGEZOGEN SIEHT AUS WIE NIE DAGEWESEN. Ein eigener Satz
|
||||
(„war mal da") würde bestätigen, dass es diesen Schlüssel gab --
|
||||
und damit Durchprobieren belohnen. */
|
||||
return res.status(404).type("text/html; charset=utf-8").send(seiteLeer());
|
||||
}
|
||||
const basis = `${TEILEN_BASIS}/e/${encodeURIComponent(req.params.token)}`;
|
||||
res.type("text/html; charset=utf-8");
|
||||
return res.send(seitePlakat(teilenDaten(e, basis)));
|
||||
});
|
||||
|
||||
/* ---------- Die beiden Seiten ----------------------------------------- */
|
||||
|
||||
Reference in New Issue
Block a user