Files
dogfather-universe/server/chat-erwaehnung.js
T
DogFatherGitandClaude Opus 5 2a26a49f91 Mit "@" jemanden im Chat ansprechen
Filipe: "ich will das man die leute mit @ markieren kann im chat."

MARKIEREN IST KEINE FARBE, SONDERN EINE ANSPRACHE. Sie funktioniert
nur, wenn drei Dinge zusammen stimmen: Der Richtige ist gemeint, er
MERKT es, und man sieht es im Satz. Fehlt eines davon, ist es eine
Attrappe -- und zwar eine, die gut aussieht.

WER GEMEINT IST, ENTSCHEIDET DER SERVER. Der Browser schickt nur Text.
Damit wirkt ein von Hand getipptes "@VanVan" genauso wie eines aus der
Auswahlliste, und niemand kann jemanden ansprechen, der gar nicht im
Raum sitzt -- auch nicht ueber die Schnittstelle.

Die Regeln stehen in server/chat-erwaehnung.js, jede mit Begruendung.
Zwei davon sind keine Feinheit, sondern der Unterschied zwischen
brauchbar und laestig:

  * Das Zeichen VOR dem "@" darf kein Buchstabe sein. Sonst piepst
    jede E-Mail-Adresse im Chat jemanden an
    ("[email protected]").
  * Das Zeichen DANACH auch nicht, und der laengste Name gewinnt.
    Sonst spricht "@Tilikum" Tili an.

MAN MERKT ES AUCH OHNE OFFENEN RAUM. Eine Zahl in der Gespraechsliste
sagt "hier ist etwas" -- nicht, ob es an dich war. Wer morgens vier
Raeume mit Zahlen sieht, macht den lautesten zuerst auf, und dort
steht selten das, was auf ihn wartet. Jetzt steht ein "@" daneben.

UND DIE MELDUNG AUFS HANDY SAGT ES: "VanVan hat dich erwaehnt" statt
"Nachricht von VanVan". Als EIGENE Art, die sich getrennt abschalten
laesst -- wer den lauten Chat stumm stellt, will trotzdem wissen, wenn
ihn jemand direkt anspricht. Ohne Ausnahme von der Ruhezeit: Ein Anruf
wartet auf eine Antwort, ein "@Anna" nicht.

DIE AUSWAHL BEIM TIPPEN loest drei Dinge, die man sonst selbst wissen
muesste: wie die Person genau heisst, wer ueberhaupt im Raum ist, und
ob man sich vertippt hat. Tastatur zuerst (Pfeile, Enter, Tab, Escape).
Sie erzwingt nichts -- wer weitertippt, schreibt einfach weiter.

ZWEI RECHNUNGEN, EINE ANTWORT: Der Browser braucht dieselbe Aufloesung,
um sofort hervorzuheben. Genau dort laufen Dinge auseinander, und dann
haette die Seite jemanden hervorgehoben, den niemand benachrichtigt
hat. pruef-erwaehnung LIEST die Browserfassung aus der Datei und fuehrt
sie aus -- ein Nachbau wuerde pruefen, ob ich zweimal dasselbe
schreiben kann.

---- DABEI GEFUNDEN: "gelesen bis 999999" ----------------------------

Der Server nahm fuer den Lesestand JEDE ganze Zahl an. Wer einmal eine
Zahl hinter allem Vorhandenen schickte, sah in diesem Gespraech nie
wieder einen Zaehler und nie wieder ein @-Zeichen: Alles Kuenftige galt
als gelesen, bevor es geschrieben war. Das faellt niemandem als
Zusammenhang auf -- man merkt nur, dass "die Benachrichtigungen nicht
gehen".

Der Browser schickt heute immer die letzte gesehene Nummer, ist also
nicht der Grund. Aber eine Grenze, die nur davon lebt, dass der
Aufrufer sich benimmt, ist keine. Jetzt wird auf die juengste
Nachricht des Raums begrenzt -- abgeleitet, nicht geraten.

Gefunden hat es meine eigene Pruefung: Sie setzte zum Aufraeumen 99999
und wunderte sich danach, warum das frische "@" nicht leuchtete.

WOHER MAN ERFAEHRT, DASS ES DAS GIBT: Im Chat gibt es keinen
Hilfeknopf. Der Hinweis steht deshalb im Leersatz einer neuen Gruppe --
dort, wo man beim ersten Oeffnen ohnehin hinsieht, und nur ab drei
Leuten. In einem Zweiergespraech waere er Ballast.

GEPRUEFT: pruef-erwaehnung 67/0 (neu) -- 13 Regelfaelle ohne Server,
der Weg durch die Schnittstelle, das Zeichen in der Liste samt
Gegenprobe bei jemandem, der dabei aber nicht gemeint war, beide
Aufloesungen nebeneinander, und der ganze Ablauf am echten Bildschirm
bis "Enter waehlt und schickt NICHT ab".
Dazu pruef-chat 48/0, pruef-chat-optik 36/0, pruef-treffchat 110/0,
pruef-chat-kanaele 79/0, pruef-glocke 31/0, pruef-push 24/0,
pruef-anruf-klingelt, pruef-css-klassen, pruef-meldungen,
pruef-nachfrage, pruef-tippziele.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-20 17:54:16 +02:00

153 lines
6.5 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.
---------------------------------------------------------------------
WAS ES BEWUSST NICHT GIBT
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.
AUFRUF (auch aus einer Prüfung heraus):
import { erwaehnungenFinden } from "./chat-erwaehnung.js";
===================================================================== */
/** 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.
* @returns {Array<{id:number, name:string, von:number, bis:number}>}
* 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.
*/
export function erwaehnungenFinden(text, leute) {
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;
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.
*/
export function erwaehnteIds(text, leute) {
return [...new Set(erwaehnungenFinden(text, leute).map((t) => t.id))];
}