Deutsch oeffnet mit „ und schliesst mit “. An 199 Stellen, die ein
Mensch liest, stand als Schlusszeichen ein GERADES " -- „Nächstes"
statt „Nächstes“. Auf sechzehn oeffentlichen Seiten, in vierzig
Dateien des Workspace und in zwoelf Servermodulen, die Texte
verschicken.
Auf dem Bildschirm sieht man den Unterschied sofort. Beim Schreiben
nicht: Das gerade " liegt auf der Tastatur, die anderen nicht.
WARUM EIN ERSTER ANLAUF ZURUECKGENOMMEN WURDE
Ein gerades " ist an vielen Stellen SYNTAX und kein Schriftzeichen --
Grenze einer Zeichenkette, Grenze eines HTML-Attributs, Zeichen in
einem regulaeren Ausdruck. Wer stumpf ersetzt, macht aus
„<a href="https://… ein kaputtes Attribut
/^["'„»\s]+|["'“«.\s]+$/ einen kaputten Ausdruck
"… nichts „mal " + "eben …" eine kaputte Zeichenkette
DIE UNTERSCHEIDUNG LAEUFT AN MERKMALEN, NICHT AN EINER LISTE
< > = dazwischen -> HTML-Marke oder Attribut
endet auf Leerzeichen -> die Zeichenkette hoert hier auf, der
Satz geht in der naechsten Zeile weiter.
Ein deutsches Schlusszeichen steht NIE
hinter einem Leerzeichen.
Rueckstrich mittendrin -> regulaerer Ausdruck
${ ohne } -> mitten in einem Ausdruck
Eine Liste erlaubter Ausnahmen waere die naechste, die niemand
pflegt. Zwoelf Stellen bleiben dadurch stehen, alle zwoelf einzeln
angesehen und alle zwoelf zu Recht -- dort steht das richtige
Schlusszeichen ohnehin weiter unten im Satz.
Fuenf davon waren allerdings ECHTE Fehler HINTER dem Link
(`…>HasiDog</a>".`) -- die erste Regel hatte nur das Attribut
gesehen, nicht den Satz danach. Gezielt nachgezogen.
Ein maskiertes `\"` in workspace-vorlagen.js (28 Hooks) wird zu “ --
ohne Rueckstrich, denn “ begrenzt nichts.
KOMMENTARE BLEIBEN, WIE SIE SIND. Dort liest es niemand ausser mir;
eine Wache, die auch Kosmetik anmahnt, wird weggeklickt. Beim ersten
Messen fielen ausserdem acht Stellen aus buehne.html faelschlich an,
weil `/* */` in HTML (in <style> und <script>) nicht ausgeblendet
war -- jetzt schon.
DIE WACHE DAZU
pruef-struktur prueft es ab sofort mit derselben Regel: 322 Dateien
mit sichtbarem Text, 0 Funde, und die zwoelf bewussten Ausnahmen
werden GEZAEHLT und genannt (erlaubt: 12). Eine Ausnahme, die niemand
sieht, waechst -- und irgendwann steht der echte Fall darin.
GEPRUEFT
pruef-struktur 59 -> 68 Pruefungen, 0 Fehler
node --check auf allen geaenderten JS-Dateien
nachgemessen: 199 geaendert, 12 mit Grund stehen geblieben
gruen geblieben: bewerbung-aufgaben 163, nachwuchs 262,
reaktion 421, support 63, content 45, terminregel 35, treff 85
Und nachgesehen, ob eine Pruefung noch die alte Schreibweise
ERWARTET: 13 Fundstellen, alle dreizehn nur Text in ihrer eigenen
Ausgabe, keine einzige ein Vergleich mit dem Seitentext.
GEGENPROBE: In reaktion.html ein Schlusszeichen zurueckgedreht ->
„workspace/reaktion.html:546 „Nächstes"", mit Datei und Zeile. Und
die Erkennung einzeln gegen HTML-Attribut, fortgesetzte
Zeichenkette, regulaeren Ausdruck und eingesetzten Wert geprueft.
Stempel gesetzt: workspace 670 Verweise in 45 Dateien, oeffentlich
554 in 48 Seiten.
Co-Authored-By: Claude Opus 5 <[email protected]>
156 lines
5.8 KiB
JavaScript
156 lines
5.8 KiB
JavaScript
/* =====================================================================
|
|
DIE SPENDENTAFEL ALS BROWSER-QUELLE (28.09.2026)
|
|
|
|
Hört am Bühnenstrom und lässt jede bestätigte Spende als Karte
|
|
laufen. Sonst tut diese Seite nichts.
|
|
|
|
---------------------------------------------------------------------
|
|
SIE MUSS TAGE LAUFEN, OHNE DASS JEMAND HINSIEHT
|
|
|
|
Das ist der ganze Unterschied zu einer Seite im Browser: Wer eine
|
|
Seite offen hat, merkt, wenn sie hängt, und lädt neu. Eine Quelle
|
|
in OBS läuft im Hintergrund, und wenn sie einmal stehenbleibt,
|
|
fällt es erst auf, wenn die erste Spende nicht erscheint — mitten
|
|
in der Sendung, vor Publikum.
|
|
|
|
Deshalb drei Dinge:
|
|
|
|
Der Browser verbindet einen abgerissenen Ereignisstrom von
|
|
selbst neu (`retry:` kommt vom Server). Darauf allein zu bauen
|
|
reicht aber nicht: Schläft der Rechner, kann der Strom in einem
|
|
Zustand stehenbleiben, aus dem der Browser nicht von selbst
|
|
herausfindet.
|
|
|
|
Also eine eigene WACHE: Kommt 70 Sekunden lang gar nichts — auch
|
|
kein Lebenszeichen, das alle 25 Sekunden gesendet wird —, wird
|
|
die Verbindung weggeworfen und neu aufgebaut.
|
|
|
|
Und ein FEHLER WIRD ANGEZEIGT, aber nur der, der etwas bedeutet:
|
|
ein falscher Schlüssel. Alles andere bleibt still, weil jede
|
|
Fläche hier im Stream zu sehen wäre.
|
|
===================================================================== */
|
|
(() => {
|
|
'use strict';
|
|
|
|
const tafel = document.getElementById('tafel');
|
|
|
|
/* ==== GROESSE UND LAGE STEHEN IN DER ADRESSE ====================
|
|
|
|
Wer in OBS eine Browser-Quelle einrichtet, hat die Adresse
|
|
ohnehin vor sich. Ein Wert, den man stattdessen im Regiepult
|
|
suchen muesste, waere ein Fensterwechsel mitten im Einrichten --
|
|
und beim Probieren aendert man zehnmal hintereinander etwas.
|
|
|
|
DIE VORGABE IST 1,4 UND NICHT 1. Im Saal steht die Karte neben
|
|
einem Chat und darf nichts zudecken; auf der Buehne deckt sie
|
|
nichts zu, was man lesen muesste, und darf deutlich groesser
|
|
sein. Eine Vorgabe, bei der man immer nachstellen muss, ist die
|
|
falsche. */
|
|
{
|
|
const p = new URLSearchParams(location.search);
|
|
const g = Number(String(p.get('g') || '').replace(',', '.'));
|
|
/* Zwischen 0,5 und 4. Darunter ist nichts mehr zu lesen, darueber
|
|
passt keine Karte mehr ins Bild -- beides waere eine Quelle,
|
|
die aussieht, als sei sie kaputt. */
|
|
const faktor = Number.isFinite(g) && g >= 0.5 && g <= 4 ? g : 1.4;
|
|
document.documentElement.style.fontSize = `${Math.round(16 * faktor)}px`;
|
|
|
|
const lagen = {
|
|
ul: ['flex-end', 'flex-start'], ur: ['flex-end', 'flex-end'],
|
|
ol: ['flex-start', 'flex-start'], or: ['flex-start', 'flex-end'],
|
|
mitte: ['center', 'center'],
|
|
};
|
|
const [senkrecht, waagerecht] = lagen[String(p.get('pos') || 'ul')] || lagen.ul;
|
|
tafel.style.justifyContent = senkrecht;
|
|
tafel.style.alignItems = waagerecht;
|
|
}
|
|
const schluessel = new URLSearchParams(location.search).get('s') || '';
|
|
|
|
function sagFehler(text) {
|
|
let k = document.querySelector('.obs-fehler');
|
|
if (!k) {
|
|
k = document.createElement('div');
|
|
k.className = 'obs-fehler';
|
|
document.body.append(k);
|
|
}
|
|
k.textContent = text;
|
|
}
|
|
|
|
if (!schluessel) {
|
|
sagFehler('Diese Adresse ist unvollständig — der Schlüssel fehlt. '
|
|
+ 'Hol sie dir im Regiepult unter „Bühne“.');
|
|
return;
|
|
}
|
|
|
|
let strom = null;
|
|
let letztes = Date.now();
|
|
|
|
function verbinden() {
|
|
try { if (strom) strom.close(); } catch { /* war schon zu */ }
|
|
strom = new EventSource(
|
|
`/workspace/api/buehne/strom?s=${encodeURIComponent(schluessel)}`);
|
|
|
|
strom.addEventListener('open', () => {
|
|
letztes = Date.now();
|
|
const k = document.querySelector('.obs-fehler');
|
|
if (k) k.remove();
|
|
});
|
|
|
|
/* JEDE NACHRICHT ZAEHLT ALS LEBENSZEICHEN -- auch die Doppelpunkt-
|
|
Zeile, die der Server alle 25 Sekunden schickt. Sie kommt hier
|
|
als „message" ohne Inhalt an. */
|
|
strom.addEventListener('message', () => { letztes = Date.now(); });
|
|
|
|
strom.addEventListener('spende', (e) => {
|
|
letztes = Date.now();
|
|
try {
|
|
const d = JSON.parse(e.data);
|
|
if (d.was === 'zeigen' && window.Spendenkarte) {
|
|
window.Spendenkarte.zeigen(tafel, d.karte);
|
|
}
|
|
} catch { /* eine kaputte Meldung darf die Quelle nicht reissen */ }
|
|
});
|
|
|
|
strom.addEventListener('error', () => {
|
|
/* KEINE MELDUNG INS BILD. Ein kurzer Abriss ist normal, und der
|
|
Browser verbindet von selbst neu. Eine rote Fläche dafür
|
|
stünde im Stream. */
|
|
if (strom.readyState === EventSource.CLOSED) {
|
|
setTimeout(verbinden, 3000);
|
|
}
|
|
});
|
|
}
|
|
|
|
/* Einmal fragen, ob der Schlüssel überhaupt stimmt. Eine Quelle,
|
|
die still bleibt, weil die Adresse falsch ist, sieht genauso aus
|
|
wie eine, bei der nur niemand gespendet hat -- und das ist die
|
|
eine Verwechslung, die hier wirklich schadet. */
|
|
fetch(`/workspace/api/buehne/lage?s=${encodeURIComponent(schluessel)}`)
|
|
.then((a) => {
|
|
if (a.status === 404) {
|
|
sagFehler('Der Schlüssel stimmt nicht mehr. '
|
|
+ 'Hol dir die Adresse neu im Regiepult unter „Bühne“.');
|
|
return;
|
|
}
|
|
verbinden();
|
|
})
|
|
.catch(() => verbinden());
|
|
|
|
/* DIE WACHE. Kommt 70 Sekunden nichts -- nicht einmal ein
|
|
Lebenszeichen --, ist die Verbindung tot, auch wenn der Browser
|
|
sie fuer offen haelt. */
|
|
setInterval(() => {
|
|
if (Date.now() - letztes > 70_000) {
|
|
letztes = Date.now();
|
|
verbinden();
|
|
}
|
|
}, 10_000);
|
|
|
|
/* WACHT DER RECHNER AUF, WIRD SOFORT NEU VERBUNDEN. Nach dem
|
|
Ruhezustand ist ein Ereignisstrom fast immer tot, und bis die
|
|
Wache greift, vergeht eine Minute. */
|
|
document.addEventListener('visibilitychange', () => {
|
|
if (!document.hidden && Date.now() - letztes > 30_000) verbinden();
|
|
});
|
|
})();
|