Filipe: "dan will ich auch dass nur die modis, rechte hand, linke hand
und dogfather, alle auch auf einmal markieren koennen im chat mit
einem, @rudel ,dann sollen alle eine benarichtigung bekommen."
DAS WAR EINE OFFENE FRAGE, UND ER HAT SIE BEANTWORTET
-----------------------------------------------------
In chat-erwaehnung.js stand seit dem 20.09. woertlich: "KEIN @alle. Es
waere in fuenf Minuten gebaut und ist der zuverlaessigste Weg, dass alle
die Benachrichtigungen abschalten -- und dann kommt auch die an, die
wirklich fuer einen bestimmten Menschen gedacht war. Wenn Filipe es
ausdruecklich will, gehoert dazu eine Entscheidung, WER es benutzen
darf; das ist eine Frage an ihn, keine, die ich hier beantworte."
Seine Antwort ist genau diese Entscheidung, und sie ist die Sicherung:
Team Dogi und DogFather duerfen rufen, sonst niemand.
WEN DER RUF ERREICHT: DAS TEAM IM RAUM, NICHT DEN RAUM
------------------------------------------------------
"Rudel" heisst das Team -- dieselben vier Gruppen, die auch rufen
duerfen. In einem Team-Kanal ist das jeder Anwesende, also "alle auf
einmal". Sichtbar wird der Unterschied im TREFF, und dort haette die
andere Lesart wehgetan: Dort sitzt die Community. Ein Ruf, der jeden
Zuschauer weckt, waere etwas anderes als der bestellte -- und beim
zweiten Mal haetten sie die Benachrichtigungen abgeschaltet.
WO WAS ENTSCHIEDEN WIRD
-----------------------
chat-erwaehnung.js bleibt ohne Abhaengigkeiten (die Pruefung soll sie
lesen koennen, ohne einen Server hochzufahren). Sie sagt nur, DASS
gerufen wurde; der Aufrufer sagt ihr, ob der Schreibende darf. Wer zum
Rudel gehoert, entscheidet workspace-chat.js ueber istTeamDogi().
Das Schluesselwort gewinnt gegen einen Menschen, der "Rudel" heisst.
Heute heisst niemand so -- aber das ist eine Tatsache von heute, kein
Gesetz. So herum verliert niemand eine Meldung (wer so heisst, ist im
Rudel dabei); andersherum haette ein einziger Zugang den Ruf ans Team
stillschweigend abgeschaltet.
istTeamDogi() NEU IN workspace.js
---------------------------------
Derselbe Ausdruck ("admin oder TEAM_DOGI_ROLLEN") stand dort dreimal
wortgleich: siehtModis, kanaeleFuer, kategorienFuer. Drei gleiche
Ausdruecke sind drei Gelegenheiten, dass einer beim naechsten
Rollenzuschnitt nicht mitgeht. Jetzt eine Stelle, die drei benutzen.
DIE NACHRICHT MERKT SICH DEN RUF (Spalte chat_nachrichten.rudel)
----------------------------------------------------------------
Beim Lesen muesste der Browser sonst wissen, ob der Absender es DAMALS
durfte. Er kennt nur dessen heutige Rolle -- wechselt jemand die Rolle,
verschwaende die Hervorhebung rueckwirkend aus einem Satz von vorletzter
Woche. Und es waere die zweite Rechnung ueber dieselbe Frage.
Vorgabe 0; alle alten Nachrichten haben kein Rudel gerufen, und das ist
keine Annahme, sondern eine Tatsache: Das Wort gab es noch nicht.
DIE MELDUNG SAGT, WAS LOS IST
-----------------------------
"X hat das Rudel gerufen", nicht "X hat dich erwaehnt" -- letzteres
stimmt beim Rudel nicht, und wer dreimal liest, dass er gemeint sei,
und jedes Mal merkt, dass es alle betraf, glaubt beim vierten Mal auch
dem echten nicht mehr. Auf DEMSELBEN Schalter wie die Erwaehnung: Ein
vierter Schalter waere der, den jemand abschaltet und der dann genau im
wichtigen Moment fehlt. Jeder Ruf steht im Protokoll (chat_rudel) --
"@rudel wird zu oft benutzt" soll eine Zahl sein koennen, kein Gefuehl.
DIE FARBE WIRD ABGELEITET, NICHT GESETZT
----------------------------------------
Eine Nachricht liegt in der Blase, deren Farbe ihr Absender AUSGESUCHT
hat -- siebzehn Moeglichkeiten. Eine feste Farbe darauf ist eine Wette,
und genau die habe ich am 22.09. beim Loeschknopf verloren (1,91:1 auf
Babyblau, gemessen, nachdem es live war). Die Marke nimmt deshalb
`--blase-text` -- die Schrift, die schriftFuer() fuer DIESE Blase mit
mindestens 7:1 ausgerechnet hat. Unterschieden wird ueber Form statt
Farbton: Toenung, Kante, Gewicht 700, ein Zeichen davor. In der
Auswahlliste darf es einen eigenen Ton haben -- sie liegt auf der
Flaeche des Hauses, deren Farbe feststeht.
DER VORSCHLAG ERSCHEINT NUR, WO ER ETWAS BEWIRKT
------------------------------------------------
In einem Zweier-Gespraech mit einem Creator ist ausser mir niemand aus
dem Team. `darf_rudel` fragt deshalb beides: darf ich, und sitzt hier
noch jemand aus dem Team. Dieselbe Ueberlegung wie beim eigenen Namen,
den die Liste auch nicht anbietet. Die Schranke beim Schreiben haengt
nicht daran -- wer es von Hand tippt, ruft eben niemanden.
GEPRUEFT
--------
pruef-erwaehnung: 119 Pruefungen, 0 Fehler (vorher 66).
Neu darin, und die zweite ist die wichtigere:
* der Modi ruft im Treff genau das Team -- die Liste wird aus der
Besetzung ABGELEITET, nicht abgeschrieben
* der Gast im selben Raum ruft NICHTS: kein Eintrag, kein Merkmal,
kein Vorschlag. Ohne diese Pruefung stuende Filipes "nur die
modis, rechte hand, linke hand und dogfather" bloss im Kommentar
* beide Fassungen der Regel (Server und Browser) an 14 zusaetzlichen
Proben nebeneinander, mit beiden Rechten -- samt Gegenprobe, dass
der Vergleich einen Unterschied ueberhaupt sehen kann
* am Bildschirm: der Ruf steht oben in der Liste, sagt daneben, was
er bedeutet, Enter setzt ihn ein, und im Satz ist er an Gewicht
und Rahmen erkennbar -- nicht nur an der Farbe
Beim Bauen gemessen statt vermutet: Ein Scout und ein Modi kommen gar
nicht in denselben Raum (403, Haeusertrennung) -- deshalb prueft der
Verhaltenstest im Treff. Und ein Gast meldet sich nur mit
Altersbestaetigung an (400 ohne).
Die Nachtruhe des Treffs wird in dieser Pruefung abgeschaltet (gleiche
Stunden = keine Nachtruhe). Sonst waere sie zwischen Mitternacht und
sechs rot und danach gruen -- ein Test, der die Wanduhr misst.
pruef-chat, pruef-treffchat (110), pruef-chat-kanaele (81),
pruef-chat-ausbau (64): alle unveraendert gruen.
Co-Authored-By: Claude Opus 5 <[email protected]>
231 lines
10 KiB
JavaScript
231 lines
10 KiB
JavaScript
/* =====================================================================
|
|
WER IST MIT "@" GEMEINT? (20.09.2026)
|
|
|
|
Filipe: "ich will das man die leute mit @ markieren kann im chat."
|
|
|
|
---------------------------------------------------------------------
|
|
WARUM DAS EINE EIGENE DATEI IST
|
|
|
|
Die Auflösung "Text -> Menschen" wird an DREI Stellen gebraucht und
|
|
muss überall dasselbe ergeben:
|
|
|
|
1. beim Schreiben -- wer bekommt eine Benachrichtigung,
|
|
2. im Browser -- welcher Teil des Satzes wird hervorgehoben,
|
|
3. in der Prüfung -- ohne dafür einen Server hochzufahren.
|
|
|
|
Stünde sie in workspace-chat.js, müsste die Prüfung die halbe
|
|
Anwendung laden, um einen Satz zu zerlegen. Und der Browser hätte
|
|
ohnehin eine zweite, eigene Fassung -- also genau die Stelle, an der
|
|
zwei Rechnungen irgendwann auseinanderlaufen und die Seite jemanden
|
|
farbig hervorhebt, den der Server nie benachrichtigt hat.
|
|
|
|
DIE DATEI HAT DESHALB KEINE ABHÄNGIGKEITEN. Kein Datenbankzugriff,
|
|
kein Import. Herein kommt ein Text und eine Liste von Menschen,
|
|
heraus kommt, wer gemeint ist. Der Browser bekommt dieselbe Logik in
|
|
`chat.js` -- bewusst als kurze, gleichlautende Fassung, die
|
|
pruef-erwaehnung.mjs Zeile für Zeile gegen diese hier prüft.
|
|
|
|
---------------------------------------------------------------------
|
|
DIE REGELN, UND WARUM JEDE EINZELNE NÖTIG IST
|
|
|
|
(1) NUR WER IM RAUM IST.
|
|
"@Marina" in einem Gespräch, in dem Marina nicht sitzt, ist kein
|
|
Ansprechen, sondern ein Wort über sie. Sie bekäme sonst eine
|
|
Meldung zu einem Gespräch, das sie nicht öffnen darf -- eine
|
|
Benachrichtigung, die auf eine verschlossene Tür führt.
|
|
|
|
(2) DAS ZEICHEN DAVOR DARF KEIN BUCHSTABE UND KEINE ZIFFER SEIN.
|
|
Ohne diese Regel wird aus "[email protected]" ein
|
|
Ansprechen von Dogfather -- jede E-Mail-Adresse im Chat würde
|
|
jemanden anpiepsen. Gemessen an einer echten Adresse, siehe
|
|
Prüfung.
|
|
|
|
(3) DAS ZEICHEN DANACH DARF KEIN BUCHSTABE UND KEINE ZIFFER SEIN.
|
|
Sonst wäre "@Tili" in "@Tilikum" enthalten. Satzzeichen dagegen
|
|
schon: "@Tili, kannst du?" und "@Tili!" sind normale Sätze.
|
|
|
|
(4) DER LÄNGSTE PASSENDE NAME GEWINNT.
|
|
Gäbe es "Van" und "VanVan", träfe die kurze Fassung zuerst und
|
|
ließe "Van" stehen. Heute hat niemand einen Namen, der im
|
|
Namen eines anderen steckt -- aber das ist eine Tatsache von
|
|
heute, kein Gesetz, und sie ändert sich mit dem nächsten Zugang.
|
|
|
|
(5) GROSS UND KLEIN EGAL.
|
|
Wer "@vanvan" tippt, meint VanVan. Auf einer Handytastatur
|
|
entscheidet die Autokorrektur mit, nicht der Mensch.
|
|
|
|
(6) JEDE PERSON HÖCHSTENS EINMAL.
|
|
Dreimal "@Anna" in einem Satz ist trotzdem eine Ansprache.
|
|
|
|
---------------------------------------------------------------------
|
|
---------------------------------------------------------------------
|
|
"@RUDEL" -- ALLE AUF EINMAL (23.09.2026)
|
|
|
|
Hier stand bis heute: „KEIN @alle. Es wäre in fünf Minuten gebaut und
|
|
ist der zuverlässigste Weg, dass alle die Benachrichtigungen
|
|
abschalten -- und dann kommt auch die an, die wirklich für einen
|
|
bestimmten Menschen gedacht war. Wenn Filipe es ausdrücklich will,
|
|
gehört dazu eine Entscheidung, WER es benutzen darf; das ist eine
|
|
Frage an ihn, keine, die ich hier beantworte."
|
|
|
|
Filipe hat sie beantwortet, und zwar genau in dem Punkt, an dem sie
|
|
offen war: „dan will ich auch dass nur die modis, rechte hand, linke
|
|
hand und dogfather, alle auch auf einmal markieren können im chat mit
|
|
einem, @rudel ,dann sollen alle eine benarichtigung bekommen."
|
|
|
|
DIE EINSCHRÄNKUNG IST DIE ANTWORT AUF DIE SORGE. Nicht jeder kann
|
|
das Rudel rufen -- nur Team Dogi und DogFather. In einem Raum, in dem
|
|
auch Creator und Scouts sitzen, kann ein Creator „@rudel" schreiben,
|
|
und es passiert nichts: kein Ruf, keine Hervorhebung, kein Eintrag.
|
|
|
|
WER DARF, ENTSCHEIDET DIESE DATEI NICHT. Sie hat keine
|
|
Abhängigkeiten und soll keine bekommen (siehe oben). Der Aufrufer
|
|
sagt es ihr: `erwaehnungenFinden(text, leute, { rudel: true })`. Im
|
|
Server kommt das aus `istTeamDogi(person)`, der einen Stelle des
|
|
Hauses dafür.
|
|
|
|
DAS SCHLÜSSELWORT GEWINNT GEGEN EINEN MENSCHEN, DER "RUDEL" HEISST.
|
|
Heute heißt niemand so -- aber das ist eine Tatsache von heute, kein
|
|
Gesetz (dieselbe Überlegung wie bei Regel 4). Andersherum wäre der
|
|
Schaden größer: Ein einziger Zugang mit diesem Namen würde den Ruf
|
|
ans ganze Team stillschweigend abschalten, und niemand fände je
|
|
heraus, warum. So herum kostet es nichts -- wer „Rudel" heißt, ist im
|
|
Rudel ohnehin dabei und bekommt seine Meldung.
|
|
|
|
---------------------------------------------------------------------
|
|
AUFRUF (auch aus einer Prüfung heraus):
|
|
import { erwaehnungenFinden } from "./chat-erwaehnung.js";
|
|
===================================================================== */
|
|
|
|
/** Das Wort, das alle meint. Klein geschrieben -- verglichen wird
|
|
* ohnehin kleingeschrieben (Regel 5). */
|
|
export const RUDEL_WORT = "rudel";
|
|
|
|
/** Ein Zeichen, das einen Namen fortsetzen könnte.
|
|
*
|
|
* BUCHSTABE ODER ZIFFER, und zwar auch mit Umlaut oder Akzent:
|
|
* `\p{L}` statt `[a-z]`. Mit `[a-zA-Z0-9]` wäre "@Jose" in "@José"
|
|
* ein Treffer gewesen -- das "é" hätte als Grenze gezählt. */
|
|
const WORTZEICHEN = /[\p{L}\p{N}_]/u;
|
|
|
|
/** Steht an dieser Stelle ein Wortzeichen?
|
|
* Außerhalb des Textes (Anfang/Ende) ist die Antwort NEIN -- ein
|
|
* Satzanfang ist eine Grenze wie jede andere. */
|
|
function wortzeichenBei(text, i) {
|
|
if (i < 0 || i >= text.length) return false;
|
|
return WORTZEICHEN.test(text[i]);
|
|
}
|
|
|
|
/**
|
|
* Wer ist in diesem Text mit "@" angesprochen?
|
|
*
|
|
* @param {string} text Die geschriebene Nachricht.
|
|
* @param {Array<{id:number,name:string}>} leute
|
|
* Die Teilnehmer DIESES Raums. Wer nicht darin steht, kann
|
|
* nicht gemeint sein -- siehe Regel (1) oben.
|
|
* @param {{rudel?: boolean}} [wie]
|
|
* `rudel: true` heisst: WER DAS HIER SCHREIBT, darf das ganze
|
|
* Rudel rufen. Vorgabe ist `false` -- wer nichts sagt, darf es
|
|
* nicht. Das ist Absicht: Eine vergessene Angabe soll zu wenig
|
|
* koennen, nicht zu viel.
|
|
* @returns {Array<{id:number|null, name:string, von:number, bis:number, rudel?:boolean}>}
|
|
* Je Treffer die Person und die Stelle im Text (`von` bis
|
|
* `bis`, ohne das "@"-Zeichen gerechnet wie `slice`). Die
|
|
* Stellen braucht der Browser zum Hervorheben; der Server
|
|
* benutzt nur `id`. Sortiert nach Vorkommen im Text.
|
|
* Beim Rudel ist `id` null und `rudel` true -- es ist keine
|
|
* Person, sondern alle.
|
|
*/
|
|
export function erwaehnungenFinden(text, leute, wie = {}) {
|
|
const darfRudel = wie?.rudel === true;
|
|
const satz = String(text || "");
|
|
if (!satz.includes("@")) return [];
|
|
|
|
/* REGEL (4): der längste Name zuerst. Einmal sortiert statt bei
|
|
jedem Treffer neu -- und auf einer Kopie, damit die Liste des
|
|
Aufrufers unberührt bleibt. */
|
|
const nachLaenge = (Array.isArray(leute) ? leute.slice() : [])
|
|
.filter((p) => p && p.id != null && String(p.name || "").trim())
|
|
.sort((a, b) => String(b.name).length - String(a.name).length);
|
|
if (!nachLaenge.length) return [];
|
|
|
|
const klein = satz.toLowerCase();
|
|
const treffer = [];
|
|
const schonDa = new Set(); // Regel (6)
|
|
|
|
for (let i = klein.indexOf("@"); i !== -1; i = klein.indexOf("@", i + 1)) {
|
|
/* REGEL (2): Was steht vor dem "@"? */
|
|
if (wortzeichenBei(satz, i - 1)) continue;
|
|
|
|
/* DAS RUDEL WIRD ZUERST GEPRUEFT -- und genau das ist die
|
|
Entscheidung aus dem Kopfkommentar: Das Schluesselwort gewinnt
|
|
gegen einen Menschen, der "Rudel" heisst. Die Regeln (2), (3)
|
|
und (5) gelten fuer es genauso wie fuer einen Namen, sonst
|
|
riefe eine Mailadresse "[email protected]" das ganze Team. */
|
|
if (darfRudel && klein.startsWith(RUDEL_WORT, i + 1)
|
|
&& !wortzeichenBei(satz, i + 1 + RUDEL_WORT.length)) {
|
|
treffer.push({ id: null, rudel: true, name: RUDEL_WORT,
|
|
von: i, bis: i + 1 + RUDEL_WORT.length });
|
|
continue;
|
|
}
|
|
|
|
for (const person of nachLaenge) {
|
|
const name = String(person.name);
|
|
const nk = name.toLowerCase();
|
|
if (!klein.startsWith(nk, i + 1)) continue;
|
|
/* REGEL (3): Was steht hinter dem Namen? */
|
|
if (wortzeichenBei(satz, i + 1 + name.length)) continue;
|
|
|
|
/* Die Stelle wird IMMER gemeldet, auch wenn dieselbe Person
|
|
schon einmal vorkam: Der Browser soll jedes "@Anna" im Satz
|
|
hervorheben, nicht nur das erste. Nur die ID-Liste für die
|
|
Benachrichtigung wird entdoppelt -- das macht `ideeVon()`
|
|
weiter unten. */
|
|
treffer.push({ id: person.id, name, von: i, bis: i + 1 + name.length });
|
|
schonDa.add(person.id);
|
|
break; // Regel (4): der Längste hat gewonnen
|
|
}
|
|
}
|
|
return treffer;
|
|
}
|
|
|
|
/**
|
|
* Nur die Personennummern, jede einmal -- das, was der Server zum
|
|
* Benachrichtigen braucht.
|
|
*
|
|
* ALS EIGENE FUNKTION, damit an der Aufrufstelle nicht steht
|
|
* `[...new Set(x.map(t => t.id))]`. Diese Zeile wäre an drei Orten
|
|
* gleich zu schreiben, und an einem davon irgendwann anders.
|
|
*
|
|
* DAS RUDEL STEHT HIER NICHT DRIN, und das ist Absicht. "@rudel" ist
|
|
* keine Liste von Namen im Satz, sondern eine Gruppe -- und WER zu ihr
|
|
* gehört, ist eine Frage des Hauses, nicht des Textes. Diese Datei hat
|
|
* keine Abhängigkeiten und kann sie nicht beantworten (sie sähe einen
|
|
* Raum voller `{id, name}` und wüsste nichts von Rollen). Sie sagt nur,
|
|
* DASS gerufen wurde -- siehe rudelGerufen() darunter.
|
|
*/
|
|
export function erwaehnteIds(text, leute, wie = {}) {
|
|
return [...new Set(erwaehnungenFinden(text, leute, wie)
|
|
.filter((t) => t.id != null)
|
|
.map((t) => t.id))];
|
|
}
|
|
|
|
/** Wurde in diesem Text das ganze Rudel gerufen?
|
|
*
|
|
* ALS EIGENE FRAGE, weil der Server sie getrennt beantworten muss:
|
|
* Die Nachricht merkt sich, DASS das Rudel gerufen wurde (Spalte
|
|
* `rudel`), und daraus entsteht spaeter die Hervorhebung beim Lesen
|
|
* und der Wortlaut der Meldung. Aus der ID-Liste liesse sich das
|
|
* nicht ablesen -- in einem Raum zu zweit sieht "alle" genauso aus
|
|
* wie "einer".
|
|
*
|
|
* WEN ES DANN TRIFFT, steht in workspace-chat.js: die Leute im Raum,
|
|
* die zu Team Dogi gehoeren (istTeamDogi). NICHT jeder im Raum -- im
|
|
* Treff sitzt die Community, und "Rudel" heisst das Team. Ein Ruf,
|
|
* der zweihundert Zuschauer weckt, waere etwas anderes als der, den
|
|
* Filipe bestellt hat.
|
|
*/
|
|
export function rudelGerufen(text, leute, wie = {}) {
|
|
return erwaehnungenFinden(text, leute, wie).some((t) => t.rudel === true);
|
|
}
|