Chat: "@rudel" ruft das ganze Team -- und nur die vier duerfen es
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]>
This commit is contained in:
+81
-14
@@ -219,6 +219,13 @@
|
||||
let angeheftet = [];
|
||||
let darfAnheftenHier = false;
|
||||
let darfAllesLoeschen = false;
|
||||
/* DARF ICH DAS RUDEL RUFEN? (23.09.2026) Kommt wie die beiden
|
||||
darueber vom Server (`darf_rudel`) -- hier wird keine Rolle
|
||||
verglichen. Und das WORT kommt mit, damit es nicht an zwei Stellen
|
||||
steht: Der Vorschlag beim Tippen setzt genau das ein, was der
|
||||
Server hinterher erkennt. */
|
||||
let darfRudel = false;
|
||||
let rudelWort = 'rudel';
|
||||
let darfKanal = false;
|
||||
/* Was der Server ueber die Nachtruhe sagt. `null` heisst: Diese
|
||||
Person hat mit dem Treff nichts zu tun -- dann gibt es auch
|
||||
@@ -771,6 +778,8 @@
|
||||
angeheftet = daten.angeheftet || [];
|
||||
darfAnheftenHier = !!daten.darf_anheften;
|
||||
darfAllesLoeschen = !!daten.darf_alles_loeschen;
|
||||
darfRudel = !!daten.darf_rudel;
|
||||
rudelWort = daten.rudel_wort || 'rudel';
|
||||
/* DER STAND VON VORHIN, nicht der von jetzt. `gelesenMelden()`
|
||||
weiter unten setzt ihn gleich auf die letzte Nachricht -- die
|
||||
Linie muss also aus dem Wert gezogen werden, der beim OEFFNEN
|
||||
@@ -1344,7 +1353,7 @@
|
||||
NUR WENN WIRKLICH TEXT DA IST: Eine Nachricht, die nur aus
|
||||
einem Foto besteht, hätte sonst einen leeren Absatz darunter
|
||||
-- und der nimmt Platz, den niemand erklären kann. */
|
||||
if (n.text) blase.append(textAbsatz(n.text));
|
||||
if (n.text) blase.append(textAbsatz(n.text, !!n.rudel));
|
||||
}
|
||||
|
||||
const fuss = el('div', 'chat-nachricht__fuss');
|
||||
@@ -1647,9 +1656,23 @@
|
||||
ob ich zweimal dasselbe schreiben kann.
|
||||
|
||||
SIE STEHT BEWUSST OHNE AUSSENBEZUG DA -- auch das Zeichenmuster
|
||||
wird hier drinnen gebildet. Sonst liesse sie sich nicht einzeln
|
||||
herausloesen und ausfuehren. */
|
||||
function erwaehnungenFinden(text, leute) {
|
||||
und das Wort "rudel" werden hier drinnen gebildet. Sonst liesse
|
||||
sie sich nicht einzeln herausloesen und ausfuehren.
|
||||
|
||||
"@RUDEL" (23.09.2026): Der dritte Parameter sagt, ob DERJENIGE,
|
||||
der diesen Satz geschrieben hat, das ganze Rudel rufen durfte.
|
||||
Er wird nicht aus der Rolle geraten -- die Nachricht bringt ihn
|
||||
mit (`n.rudel`), der Server hat ihn beim Schreiben entschieden.
|
||||
|
||||
WARUM NICHT AUS DER ROLLE DES ABSENDERS: Die steht zwar in der
|
||||
Nachricht, aber es ist die HEUTIGE. Wechselt jemand die Rolle,
|
||||
verschwaende die Hervorhebung rueckwirkend aus einem Satz von
|
||||
vorletzter Woche, in dem sie damals richtig war. Und es waere die
|
||||
zweite Rechnung ueber dieselbe Frage -- genau die, vor der der
|
||||
Absatz darueber warnt. */
|
||||
function erwaehnungenFinden(text, leute, wie) {
|
||||
const RUDEL = 'rudel';
|
||||
const darfRudel = wie && wie.rudel === true;
|
||||
const WORT = /[\p{L}\p{N}_]/u;
|
||||
const wortBei = (t, i) => (i >= 0 && i < t.length && WORT.test(t[i]));
|
||||
const satz = String(text || '');
|
||||
@@ -1657,11 +1680,17 @@
|
||||
const nachLaenge = (Array.isArray(leute) ? leute.slice() : [])
|
||||
.filter((x) => x && x.id != null && String(x.name || '').trim())
|
||||
.sort((a, b) => String(b.name).length - String(a.name).length);
|
||||
if (!nachLaenge.length) return [];
|
||||
if (!nachLaenge.length && !darfRudel) return [];
|
||||
const klein = satz.toLowerCase();
|
||||
const treffer = [];
|
||||
for (let i = klein.indexOf('@'); i !== -1; i = klein.indexOf('@', i + 1)) {
|
||||
if (wortBei(satz, i - 1)) continue;
|
||||
if (darfRudel && klein.startsWith(RUDEL, i + 1)
|
||||
&& !wortBei(satz, i + 1 + RUDEL.length)) {
|
||||
treffer.push({ id: null, rudel: true, name: RUDEL,
|
||||
von: i, bis: i + 1 + RUDEL.length });
|
||||
continue;
|
||||
}
|
||||
for (const person of nachLaenge) {
|
||||
const name = String(person.name);
|
||||
if (!klein.startsWith(name.toLowerCase(), i + 1)) continue;
|
||||
@@ -1731,16 +1760,30 @@
|
||||
return { at, rumpf };
|
||||
}
|
||||
|
||||
/** Wer passt gerade? -- die Teilnehmer dieses Raums, ohne mich. */
|
||||
/** Wer passt gerade? -- die Teilnehmer dieses Raums, ohne mich.
|
||||
*
|
||||
* DAS RUDEL STEHT OBEN, wenn ich es rufen darf (23.09.2026). Es ist
|
||||
* keine Person, sondern die Gruppe -- deshalb bekommt es eine
|
||||
* Kennung, die keine Personennummer sein kann.
|
||||
*
|
||||
* ES ERSCHEINT NUR, WENN ICH ES DARF. Ein Eintrag, den man waehlen
|
||||
* kann und der dann nichts tut, ist schlimmer als keiner: Man
|
||||
* schreibt einen Satz an das ganze Team und merkt nie, dass ihn
|
||||
* niemand als Ruf bekommen hat. Ob ich darf, sagt der Server
|
||||
* (`darf_rudel`) -- hier wird keine Rolle verglichen. */
|
||||
function erwaehnTreffer(rumpf) {
|
||||
const klein = rumpf.toLowerCase();
|
||||
return (offen?.teilnehmer || [])
|
||||
const leute = (offen?.teilnehmer || [])
|
||||
/* SICH SELBST KANN MAN NICHT ANSPRECHEN -- der Server verwirft es
|
||||
ohnehin. Ein Eintrag, der nichts bewirkt, gehoert nicht in die
|
||||
Liste. */
|
||||
.filter((t) => t.id !== ich?.id)
|
||||
.filter((t) => String(t.name).toLowerCase().startsWith(klein))
|
||||
.slice(0, 8);
|
||||
if (darfRudel && rudelWort.toLowerCase().startsWith(klein)) {
|
||||
return [{ id: 'rudel', name: rudelWort, rudel: true, rolle: null }, ...leute];
|
||||
}
|
||||
return leute;
|
||||
}
|
||||
|
||||
function erwaehnSchliessen() {
|
||||
@@ -1776,11 +1819,19 @@
|
||||
b.setAttribute('role', 'option');
|
||||
b.setAttribute('aria-selected', i === neueWahl ? 'true' : 'false');
|
||||
if (i === neueWahl) b.dataset.wahl = 'ja';
|
||||
const z = el('span', 'erwaehn-liste__zeichen', initialen(t.name));
|
||||
const z = el('span', 'erwaehn-liste__zeichen',
|
||||
/* Das Rudel bekommt ein Zeichen statt Anfangsbuchstaben --
|
||||
"R" saehe aus wie ein Mensch namens Rita. */
|
||||
t.rudel ? '◉' : initialen(t.name));
|
||||
z.setAttribute('aria-hidden', 'true');
|
||||
z.dataset.rolle = t.rolle || '';
|
||||
if (t.rudel) { b.dataset.rudel = 'ja'; z.dataset.rudel = 'ja'; }
|
||||
b.append(z, el('span', 'erwaehn-liste__name', t.name));
|
||||
if (ROLLENKURZ[t.rolle]) b.append(el('span', 'erwaehn-liste__rolle', ROLLENKURZ[t.rolle]));
|
||||
/* WAS ES BEDEUTET, STEHT DANEBEN. "rudel" allein sagt keinem
|
||||
Neuen, dass damit das ganze Team gemeint ist -- und wer das
|
||||
erst nach dem Absenden erfaehrt, hat schon alle geweckt. */
|
||||
if (t.rudel) b.append(el('span', 'erwaehn-liste__rolle', 'alle im Team'));
|
||||
else if (ROLLENKURZ[t.rolle]) b.append(el('span', 'erwaehn-liste__rolle', ROLLENKURZ[t.rolle]));
|
||||
/* mousedown statt click: Ein Klick nimmt dem Textfeld zuerst den
|
||||
Schreibzeiger, und danach weiss niemand mehr, wohin der Name
|
||||
gehoert. `preventDefault` haelt ihn, wo er ist. */
|
||||
@@ -1798,6 +1849,9 @@
|
||||
if (!a || !feld) return;
|
||||
const person = a.treffer[i ?? a.wahl];
|
||||
if (!person) return;
|
||||
/* Fuer das Rudel gilt dasselbe wie fuer einen Namen: "@" davor,
|
||||
Leerzeichen dahinter. Es ist ein Wort im Satz, kein Sonderfall
|
||||
-- deshalb steht hier auch keine eigene Behandlung. */
|
||||
const bis = feld.selectionStart;
|
||||
/* EIN LEERZEICHEN HINTERHER -- sonst klebt das naechste Wort am
|
||||
Namen, und aus "@VanVankannst" wird gar keine Ansprache mehr.
|
||||
@@ -1883,13 +1937,13 @@
|
||||
Ohne textform.js (aeltere Fassung im Zwischenspeicher) faellt es
|
||||
auf den alten Zustand zurueck: ein Stueck, keine Stile. Dann sieht
|
||||
man Sternchen statt Fettschrift -- aber nichts ist kaputt. */
|
||||
function textAbsatz(text) {
|
||||
function textAbsatz(text, rudel = false) {
|
||||
const p = el('p', 'chat-nachricht__text');
|
||||
const teilchen = window.Textform
|
||||
? window.Textform.teile(text)
|
||||
: [{ text: String(text), stile: {} }];
|
||||
for (const t of teilchen) {
|
||||
const innen = stellenBauen(t.text);
|
||||
const innen = stellenBauen(t.text, rudel);
|
||||
p.append(window.Textform ? window.Textform.huelle(innen, t.stile) : innen);
|
||||
}
|
||||
return p;
|
||||
@@ -1898,7 +1952,7 @@
|
||||
/** Die vorhandene Suche nach Links und Erwaehnungen -- unveraendert,
|
||||
* nur arbeitet sie jetzt auf einem STUECK statt auf dem ganzen Text
|
||||
* und gibt einen Baustein zurueck statt einen Absatz. */
|
||||
function stellenBauen(roh) {
|
||||
function stellenBauen(roh, rudel = false) {
|
||||
const stueck = document.createDocumentFragment();
|
||||
const stellen = [];
|
||||
|
||||
@@ -1910,9 +1964,9 @@
|
||||
/* Gegen die Teilnehmer DIESES Raums -- wie im Server. Steht kein
|
||||
Raum offen (etwa beim Suchen), wird nichts hervorgehoben: lieber
|
||||
grau als falsch. */
|
||||
for (const t of erwaehnungenFinden(roh, offen?.teilnehmer || [])) {
|
||||
for (const t of erwaehnungenFinden(roh, offen?.teilnehmer || [], { rudel })) {
|
||||
stellen.push({ art: 'erwaehnung', von: t.von, bis: t.bis,
|
||||
wort: roh.slice(t.von, t.bis), id: t.id });
|
||||
wort: roh.slice(t.von, t.bis), id: t.id, rudel: !!t.rudel });
|
||||
}
|
||||
|
||||
stellen.sort((a, b) => a.von - b.von || b.bis - a.bis);
|
||||
@@ -1939,6 +1993,11 @@
|
||||
Zweck: In einem Raum mit acht Leuten soll der eigene Name
|
||||
aus dem Absatz herausspringen. */
|
||||
if (ich && st.id === ich.id) m.dataset.ich = 'ja';
|
||||
/* DAS RUDEL MEINT ALLE -- also auch mich, wenn ich dazugehoere.
|
||||
Es bekommt sein eigenes Merkmal statt `ich`: "alle sind
|
||||
gemeint" ist etwas anderes als "du bist gemeint", und wenn
|
||||
beides gleich aussieht, verliert das zweite seine Wirkung. */
|
||||
if (st.rudel) m.dataset.rudel = 'ja';
|
||||
stueck.append(m);
|
||||
}
|
||||
letzte = st.bis;
|
||||
@@ -2012,6 +2071,14 @@
|
||||
const vorlaeufig = {
|
||||
id: -Date.now(), von_id: ich.id, von: ich.name, rolle: ich.rolle,
|
||||
text, erstellt: new Date().toISOString(), selbst: true, unterwegs: true,
|
||||
/* DAMIT DAS EIGENE "@rudel" SOFORT LEUCHTET. Ohne diese Zeile
|
||||
saehe der Absender seinen Ruf zuerst grau und erst nach der
|
||||
Antwort des Servers farbig -- derselbe Satz, zwei
|
||||
Darstellungen, Unterschied nur im Zeitpunkt. Dass der Server
|
||||
es genauso sieht, entscheidet er selbst; die echte Nachricht
|
||||
ersetzt diese hier ohnehin. */
|
||||
rudel: darfRudel && erwaehnungenFinden(
|
||||
text, offen?.teilnehmer || [], { rudel: true }).some((t) => t.rudel),
|
||||
antwort: zielAntwort
|
||||
? { id: zielAntwort.id, von: zielAntwort.von, text: zielAntwort.text }
|
||||
: null,
|
||||
|
||||
Reference in New Issue
Block a user