Chat zwischen Creator, Scouts und Managern
Wunsch Filipe, 06.09.2026: *"eine kategorie chat, wo die creator mit
ihren manager nachrichten austauschen koennen, wie erinnerungen, fragen
und noch vieles mehr."* Auf Nachfrage entschieden: Zweier-Gespraeche UND
Gruppen; Manager und Scouts duerfen sich auch ohne gemeinsamen Creator
schreiben.
WER MIT WEM steht an EINER Stelle: schreibbareIds() in workspace.js.
Bewusst getrennt von einladbareIds() (Terminwahl): Dort geht es darum,
wen man zu einem Kalendereintrag dazustellen darf -- harmlos. Ein Chat
legt ein dauerhaftes Gespraech an, schickt eine Benachrichtigung und
macht den eigenen Namen sichtbar. Zwei Fragen, zwei Funktionen.
Ein Creator erreicht seine Betreuer und DogFather, KEINEN fremden
Creator. Eine Gruppe ist kein Schlupfloch: Jede Nummer wird einzeln
gegen dieselbe Regel geprueft.
SOFORT STATT NACHFRAGEN IM TAKT (SSE). Ein Chat, der alle fuenf
Sekunden fragt, ist fuenf Sekunden langsam und stellt bei zehn
Angemeldeten 7200 Anfragen in der Stunde fuer Nachrichten, die es
meistens nicht gibt. Kein WebSocket: Hier fliesst alles in eine
Richtung, und SSE uebersteht einen Verbindungsabriss von selbst.
DIE ZAHL STEHT AUF JEDER SEITE, nicht nur im Chat. Eine Nachricht, die
man erst sieht, wenn man den Chat aufmacht, ist keine Nachricht --
sie ist ein Fundstueck.
Push aufs Handy nur an die, die NICHT gerade zusehen. Wer die Seite
offen hat, sieht es ohnehin; ihm zusaetzlich etwas aufs Handy zu
schicken ist der schnellste Weg, dass er Benachrichtigungen abschaltet.
WAS BEWUSST NICHT GEHT:
* Niemand liest fremde Gespraeche mit -- auch DogFather nicht. Ein
Chat, in dem der Chef stillschweigend mitliest, ist kein Chat.
Er kann jederzeit jedem schreiben, aber sichtbar.
* Eine abgeschickte Nachricht laesst sich nicht aendern, nur
zuruecknehmen. Wer Absprachen nachtraeglich umschreiben kann, macht
den Verlauf wertlos. Zurueckgenommenes hinterlaesst einen Hinweis
statt eines Lochs.
DAZU: CALL-LISTE NACH ZEIT GEGLIEDERT
Wunsch mit Bild von "STEHT AN 27": *"die liste soll kategorisiert sein
und werden mit einem button zum auf und zu machen."* Jetzt Heute /
Diese Woche / Naechste Woche / Spaeter, offen nur der erste gefuellte
Block. Nach ZEIT und nicht nach Titel: Auf dem Bild waren fast alle
"Community-Talk" -- nach Titel gruppiert haette man zwei Ueberschriften
und darunter dieselbe lange Liste.
DER FUND, DER DIE FUNKTION GERETTET HAT: Ein zugeklappter Block zeigte
seinen Inhalt trotzdem -- 367 Pixel Hoehe bei open=false. <details>
versteckt seinen Inhalt naemlich nur, solange niemand dem Inhalt eine
eigene display-Angabe gibt, und .gruppe__karten traegt display:grid.
Der Knopf haette sich bewegt und sonst nichts getan. Im Bildschirmfoto
fiel es nicht auf, weil der Ausschnitt genau darueber endete.
Ausserdem gefunden und behoben:
* Ein Wettlauf beim Abschicken: War der Ereignisstrom schneller als
die Antwort auf das POST, stand die eigene Nachricht zweimal im
Verlauf.
* Die Chatseite war zu hoch -- die Kopfleistenhoehe stand als fester
Wert (62 px) im CSS, am Handy ist sie doppelt so hoch. Das
Eingabefeld stand halb unter dem Bildschirmrand. Jetzt gemessen.
* Der Platzhalter "Enter schickt ..." brach am Handy um UND stimmte
dort nicht: Ohne Tastatur schickt Enter absichtlich nicht.
* Der Dialog benutzte Klassen aus aufgaben.css, das chat.html nicht
lud -- gemeldet von pruef-css-klassen, bevor jemand einen
ungestylten Dialog zu sehen bekam.
Pruefungen: pruef-chat (44 Punkte, Rechte und Sichtbarkeit),
pruef-chat-optik (24, zwei echte Browser schreiben sich),
pruef-call-kategorien (16).
Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -449,6 +449,97 @@
|
||||
|
||||
const kopfRechts = document.querySelector('.kopfleiste__rechts');
|
||||
if (kopfRechts) aufbauSuche(kopfRechts);
|
||||
if (kopfRechts) aufbauChat(kopfRechts);
|
||||
|
||||
/* ---------- Der Chat-Knopf mit Zähler -----------------------------------
|
||||
|
||||
Wunsch Filipe, 06.09.2026: *"die andere person bekommt eine
|
||||
benachrichtigung und kann dann antworten."*
|
||||
|
||||
WARUM DER ZÄHLER IN DIE KOPFLEISTE GEHÖRT und nicht nur auf die
|
||||
Chatseite: Eine Nachricht, die man erst sieht, wenn man den Chat
|
||||
aufmacht, ist keine Nachricht -- sie ist ein Fundstück. Die
|
||||
Push-Benachrichtigung erreicht nur, wer sie erlaubt hat und wer
|
||||
gerade nicht am Rechner sitzt; hier drin sieht sie jeder, auf jeder
|
||||
Seite.
|
||||
|
||||
Auf der Chatseite selbst wird der Knopf NICHT gebaut -- dort ist
|
||||
man schon, und ein Knopf, der auf die eigene Seite führt, ist eine
|
||||
Sackgasse. Die Zahl aktualisiert chat.js dann direkt. */
|
||||
function aufbauChat(wohin) {
|
||||
/* Auf der Chatseite selbst gibt es nichts zu verlinken. */
|
||||
if (/\/chat\.html$/.test(location.pathname)) return;
|
||||
|
||||
const el = (tag, klasse, text) => {
|
||||
const k = document.createElement(tag);
|
||||
if (klasse) k.className = klasse;
|
||||
if (text !== undefined) k.textContent = text;
|
||||
return k;
|
||||
};
|
||||
|
||||
const a = el('a', 'chat-knopf');
|
||||
a.href = 'chat.html';
|
||||
a.title = 'Chat';
|
||||
a.setAttribute('aria-label', 'Chat');
|
||||
|
||||
const blase = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
blase.setAttribute('viewBox', '0 0 24 24');
|
||||
blase.setAttribute('class', 'chat-knopf__zeichen');
|
||||
blase.setAttribute('aria-hidden', 'true');
|
||||
blase.innerHTML = '<path d="M4 5.5h16v11H9.5L5 20.5V16.5H4z"/>';
|
||||
|
||||
const zahl = el('span', 'chat-knopf__zahl');
|
||||
zahl.hidden = true;
|
||||
|
||||
a.append(blase, el('span', 'chat-knopf__text', 'Chat'), zahl);
|
||||
/* Vor der Suche: Der Chat ist das, was am ehesten auf eine Antwort
|
||||
wartet -- die Suche fängt man selbst an, wenn man sie braucht. */
|
||||
wohin.prepend(a);
|
||||
|
||||
/** Setzt die Zahl. Wird auch von chat.js aufgerufen, damit sie dort
|
||||
* sofort verschwindet, wenn man gelesen hat. */
|
||||
window.chatZahlZeigen = (n) => {
|
||||
const wert = Number(n) || 0;
|
||||
zahl.hidden = wert === 0;
|
||||
/* Über 99 wird die Zahl breiter als der Knopf -- und die genaue
|
||||
Menge interessiert ab da ohnehin niemanden mehr. */
|
||||
zahl.textContent = wert > 99 ? '99+' : String(wert);
|
||||
a.dataset.neu = wert > 0 ? 'ja' : '';
|
||||
a.setAttribute('aria-label', wert
|
||||
? `Chat, ${wert} ungelesene ${wert === 1 ? 'Nachricht' : 'Nachrichten'}`
|
||||
: 'Chat');
|
||||
};
|
||||
|
||||
async function holen() {
|
||||
try {
|
||||
const antwort = await fetch('/workspace/api/chat/ungelesen', { credentials: 'same-origin' });
|
||||
if (!antwort.ok) return;
|
||||
window.chatZahlZeigen((await antwort.json()).ungelesen);
|
||||
} catch { /* die Zahl ist Zugabe, kein Grund für eine Fehlermeldung */ }
|
||||
}
|
||||
holen();
|
||||
|
||||
/* NEUE NACHRICHTEN KOMMEN VON SELBST -- derselbe Strom wie im Chat.
|
||||
Ohne ihn müsste man die Seite neu laden, um zu erfahren, dass
|
||||
jemand geschrieben hat. Kein Nachfragen im Takt: Bei zehn
|
||||
Angemeldeten wären das siebentausend Anfragen in der Stunde für
|
||||
eine Zahl, die sich selten ändert. */
|
||||
if (window.EventSource) {
|
||||
try {
|
||||
const strom = new EventSource('/workspace/api/chat/strom');
|
||||
strom.addEventListener('neu', holen);
|
||||
/* Nach einem Abriss verbindet der Browser selbst neu -- dann
|
||||
einmal nachzählen, was inzwischen kam. */
|
||||
strom.addEventListener('open', holen);
|
||||
} catch { /* dann eben nur beim Laden */ }
|
||||
}
|
||||
|
||||
/* Zurück an den Bildschirm: nachsehen. Fängt den Fall ab, dass der
|
||||
Strom währenddessen stillgelegt wurde. */
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (!document.hidden) holen();
|
||||
});
|
||||
}
|
||||
|
||||
function aufbauSuche(wohin) {
|
||||
const el = (tag, klasse, text) => {
|
||||
|
||||
Reference in New Issue
Block a user