Die vier Saetze aus dem Anforderungsdokument, der Reihe nach: Team- Gruppenchat, Kategorie-Kanaele mit Zugriff fuer Owner und rechte Hand, private 1:1-Chats OHNE diesen Zugriff, und Pin-Nachrichten an alle. EIN KANAL IST KEINE VIERTE TABELLE, sondern eine dritte Art Raum (`art = 'kanal'` neben 'direkt' und 'gruppe'). Damit gilt fuer ihn ohne eine einzige neue Zeile alles, was schon da ist: Verlauf, Anhaenge, Suche, Ungelesen-Zaehler, Live-Strom, Wegraeumen. Eine eigene Tabelle haette all das ein zweites Mal gebraucht -- und die zweite Fassung waere die gewesen, in der die Zugriffsregel fehlt. Welche Zustaendigkeit, kommt aus MODI_KATEGORIEN -- derselben Liste, aus der auch die Aufgaben ihre Kategorie nehmen. Der NAME kommt aus der Kategorie und ist kein freies Feld: "Clipping" neben "Clipping-Team" waeren zwei halbe Verlaeufe, und man merkt es erst, wenn jemand die Antwort im falschen sucht. Ein eindeutiger Teilindex haelt das auch dann fest, wenn zwei Anfragen im selben Augenblick ankommen. DIE ZUGRIFFSREGEL STEHT IN istDrin() -- der Funktion, durch die alle sieben lesenden und schreibenden Wege gehen. In den Routen stuende sie in sechs davon und in der siebten nicht. Sie gilt AUSDRUECKLICH nur fuer 'kanal': Zweier-Gespraeche bleiben zu, auch fuer DogFather (so steht es im Dokument), und Gruppen ebenfalls -- wer eine Gruppe aufmacht, hat sich fuer einen geschlossenen Kreis entschieden, und den nachtraeglich still zu oeffnen waere das Gegenteil dessen, was er getan hat. Wenn Filipe das anders will, ist es eine Zeile -- aber es waere seine Entscheidung und muesste fuer die Beteiligten SICHTBAR sein. istDrin() nimmt dafuer die PERSON statt ihrer Nummer und wirft bei einer Nummer einen Fehler, statt stillschweigend "nein" zu antworten. HOECHSTENS DREI ANKUENDIGUNGEN je Raum. Nicht eine (Regeln, Live-Plan und Frist muessen gleichzeitig oben stehen koennen) und nicht beliebig viele -- eine Pinnwand, die scrollt, ist ein zweiter Verlauf. Der Aushang hat eine EIGENE Abfrage, weil der Verlauf nur 200 Zeilen liefert: Eine Ansage von vorletzter Woche waere sonst genau dann weg, wenn sie am laengsten oben stehen sollte. Wird die Nachricht zurueckgenommen, faellt sie ab UND gibt den Platz frei. DREI DINGE, DIE ERST DAS HINSEHEN GEZEIGT HAT: Der frisch angelegte Kanal hatte zwei Leute statt vier. Die Personenauswahl zeichnete nach der Rollenfolge aus bereiche.js -- wer dort nicht steht, wurde NICHT GEZEICHNET. Team Dogi steht dort nicht und darf es auch nicht (der Rollenname gehoert in keine Datei, die jeder herunterlaedt). Folge: DogFather konnte ueber die Auswahl niemandem aus seinem Team schreiben. Der Server schickt die Ueberschrift jetzt mit; der Browser braucht dafuer keinen Rollennamen. Der Kommentar, der genau davor warnte, stand die ganze Zeit darueber. Dieser Fehler war nebenbei ein Netz: Was nicht gezeichnet wird, kann auch nicht falsch gezeichnet werden. Deshalb ist jetzt gemessen, dass ein Manager und Spicy Media Team Dogi gar nicht erst geschickt bekommen -- und dabei fiel auf, dass Spicy Media in der EIGENEN Auswahl stand: Sobald es jemanden zu verbergen gibt, schreibt ohneVerborgene() aus "sieht alles" eine echte Liste, und darin steckt man selbst. Am Fuss jeder Nachricht stehen jetzt vier Handgriffe statt drei. Bei 390 px -- der haeufigsten Handybreite -- stand "kopieren" 18 px ueber der Blase, bei 320 px 75. Behoben mit `flex-wrap: wrap` und nicht mit einer Schwelle: Eine Regel, die misst, bleibt beim fuenften Handgriff richtig; eine Zahl nicht. pruef-chat-optik misst es ab jetzt. NACHGEZOGEN, was pruef-css-klassen an MEINEM letzten Commit fand: teamlage.html lud kopf.js ohne wahl.js (der Sicht-Umschalter sah aus wie aus einem anderen Programm -- kaputt war nichts, und genau deshalb faellt es niemandem auf), und .tampel__tag stand auf 11,2 px. Beides war schon gepusht, weil ich die Pruefung nicht laufen liess. pruef-chat-kanaele 73 (neu) · pruef-chat 48 · pruef-chat-ausbau 64 · pruef-chat-anhaenge 60 · pruef-chat-optik 24 -> 26 · pruef-css-klassen gruen · pruef-rollen 274 · pruef-modi-verborgen 78 · pruef-team-ampel 28 · pruef-start-ansicht gruen · pruef-modi-wortleck 5 · pruef-zwischenspeicher 21. Co-Authored-By: Claude Opus 5 <[email protected]>
292 lines
15 KiB
HTML
292 lines
15 KiB
HTML
<!doctype html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
||
<title>Chat · Spicy & Dogi</title>
|
||
<meta name="robots" content="noindex, nofollow" />
|
||
<link rel="manifest" href="/workspace/app.webmanifest" />
|
||
<!-- Die Farbe der Fensterleiste, wenn der Workspace als App laeuft.
|
||
Stand auf #0674b9 (kraeftiges Blau) und stach damit als einziger
|
||
heller Balken ueber einer durchweg dunklen Seite. Jetzt derselbe
|
||
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
|
||
eine Flaeche sind statt zweier. -->
|
||
<meta name="theme-color" content="#06090f" />
|
||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609102008" />
|
||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609102008" />
|
||
<link rel="stylesheet" href="assets/css/gate.css?v=202609102008" />
|
||
<link rel="stylesheet" href="assets/css/start.css?v=202609102008" />
|
||
<!-- aufgaben.css bringt die Dialog- und Formularstile mit (.dialog,
|
||
.neu__zeile, .dialog__knoepfe). Sie stehen dort seit dem
|
||
Aufgabenbrett; sie hierher zu kopieren hiesse, sie an zwei Stellen
|
||
zu pflegen. Dieselbe Lösung nutzen kalender.html und content.html.
|
||
Gemeldet von pruef-css-klassen.mjs, bevor jemand einen ungestylten
|
||
Dialog zu sehen bekam. -->
|
||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609102008" />
|
||
<link rel="stylesheet" href="assets/css/chat.css?v=202609102008" />
|
||
<link rel="stylesheet" href="assets/css/module.css?v=202609102008" />
|
||
</head>
|
||
|
||
<body class="start">
|
||
|
||
<div class="schleier" aria-hidden="true"></div>
|
||
|
||
<header class="kopfleiste">
|
||
<button type="button" class="zurueck-knopf" id="zurueck" hidden>
|
||
<svg class="zurueck-knopf__pfeil" viewBox="0 0 24 24" aria-hidden="true">
|
||
<path d="M14.5 5.5 8 12l6.5 6.5" />
|
||
</svg>
|
||
<span class="zurueck-knopf__text">Zurück</span>
|
||
</button>
|
||
<p class="marke"><span class="marke__text"><a class="zurueck" href="start.html">Creator Workspace</a> · Chat</span></p>
|
||
<div class="kopfleiste__rechts">
|
||
<span class="wer" id="wer">…</span>
|
||
<button type="button" class="abmelden" id="abmelden">Abmelden</button>
|
||
</div>
|
||
</header>
|
||
|
||
<main class="inhalt inhalt--breit chat-seite">
|
||
|
||
<!-- DIE GANZE SEITE IST EINE FLÄCHE, kein Dokument zum Scrollen.
|
||
|
||
Ein Chat ist kein Text, den man von oben nach unten liest --
|
||
man springt zwischen Gesprächen, und das Eingabefeld muss
|
||
immer erreichbar sein, ohne zu suchen. Deshalb: feste Höhe,
|
||
und gescrollt wird nur INNERHALB der beiden Spalten.
|
||
|
||
data-ansicht steuert am Handy, welche Spalte zu sehen ist:
|
||
"liste" oder "verlauf". Am Rechner stehen beide nebeneinander
|
||
und das Merkmal bewirkt nichts. -->
|
||
<div class="chat" id="chat" data-ansicht="liste">
|
||
|
||
<!-- ---------- Links: die Gespräche ---------- -->
|
||
<section class="chat__spalte chat__spalte--liste" aria-labelledby="liste-titel">
|
||
<div class="chat__kopf">
|
||
<h1 class="chat__titel" id="liste-titel">Gespräche</h1>
|
||
<button type="button" class="knopf knopf--klein" id="neu-oeffnen">Neu</button>
|
||
</div>
|
||
|
||
<!-- Suchen erst, wenn es etwas zu suchen gibt (chat.js blendet
|
||
es ab acht Gesprächen ein). Ein Suchfeld über drei Einträgen
|
||
ist Zierde. -->
|
||
<div class="chat__suche" id="suche-zeile" hidden>
|
||
<input type="search" id="suche" placeholder="Name oder Gruppe …"
|
||
aria-label="Gespräche durchsuchen" autocomplete="off" />
|
||
</div>
|
||
|
||
<ul class="chat__liste" id="raeume" aria-live="polite"></ul>
|
||
|
||
<!-- ---------- Fundstellen in Nachrichten (09.09.2026, Punkt 15)
|
||
|
||
Das Suchfeld darueber durchsuchte bisher nur die NAMEN der
|
||
Gespraeche. Das ist die kleinere Haelfte: Was man wirklich
|
||
sucht, ist ein Satz, den jemand vor drei Wochen geschrieben
|
||
hat.
|
||
|
||
Die Fundstellen stehen UNTER der Gespraechsliste und nicht
|
||
an ihrer Stelle -- beides ist eine Antwort auf dieselbe
|
||
Eingabe, und wer nach "Patrick" sucht, will vielleicht das
|
||
Gespraech und vielleicht die Nachricht. Ein Umschalter
|
||
zwischen zwei Ansichten haette ihn zwingen wollen, das
|
||
vorher zu wissen. -->
|
||
<div class="chat-funde" id="funde" hidden>
|
||
<p class="chat-funde__schild" id="funde-schild">In Nachrichten</p>
|
||
<ul class="chat-funde__liste" id="funde-liste"></ul>
|
||
</div>
|
||
|
||
<p class="chat__leer" id="liste-leer" hidden>
|
||
Noch keine Gespräche. Mit <strong>Neu</strong> schreibst du der
|
||
ersten Person.
|
||
</p>
|
||
</section>
|
||
|
||
<!-- ---------- Rechts: der Verlauf ---------- -->
|
||
<section class="chat__spalte chat__spalte--verlauf" aria-labelledby="verlauf-titel">
|
||
|
||
<!-- Der Kopf trägt am Handy den Zurück-Weg zur Liste. -->
|
||
<div class="chat__kopf chat__kopf--verlauf">
|
||
<button type="button" class="chat__zurueck" id="zur-liste" aria-label="Zurück zu den Gesprächen">
|
||
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M14.5 5.5 8 12l6.5 6.5" /></svg>
|
||
</button>
|
||
<div class="chat__kopf-text">
|
||
<h2 class="chat__titel" id="verlauf-titel">Wähle ein Gespräch</h2>
|
||
<p class="chat__unter" id="verlauf-unter"></p>
|
||
</div>
|
||
<!-- GESPRÄCH WEGRÄUMEN (09.09.2026).
|
||
Filipe: "man muss die chats auch gelöscht bekommen!!!"
|
||
Es verschwindet nur bei DIR -- das Gegenüber behält seinen
|
||
Verlauf. Der Knopf steht erst da, wenn auch ein Gespräch
|
||
offen ist; sonst wäre er ein Angebot ohne Gegenstand. -->
|
||
<!-- WER IST IM KANAL (10.09.2026, Kapitel 7.2).
|
||
Steht nur in einem Kanal da und nur bei der Teamleitung --
|
||
der Server entscheidet das, nicht diese Datei. -->
|
||
<button type="button" class="chat__weg chat__leute" id="kanal-leute" hidden
|
||
aria-label="Wer ist in diesem Kanal?"
|
||
title="Wer ist in diesem Kanal?">
|
||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||
<circle cx="9" cy="8.5" r="3" />
|
||
<path d="M3.5 19c0-3 2.5-5 5.5-5s5.5 2 5.5 5" />
|
||
<path d="M16 6.2a3 3 0 0 1 0 5.6M17.5 14.4c2 .7 3.3 2.4 3.3 4.6" />
|
||
</svg>
|
||
</button>
|
||
<button type="button" class="chat__weg" id="gespraech-weg" hidden
|
||
aria-label="Dieses Gespräch wegräumen"
|
||
title="Gespräch wegräumen – nur bei dir">
|
||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||
<path d="M5 7h14M10 7V5h4v2M6.5 7l.8 12h9.4l.8-12M10 10.5v5.5M14 10.5v5.5" />
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
|
||
<!-- aria-live="polite": Ein Vorleseprogramm sagt neue Nachrichten
|
||
an, ohne den Nutzer mitten im Satz zu unterbrechen. -->
|
||
<!-- ZUM NEUESTEN (09.09.2026). Vorher sprang der Verlauf bei jeder
|
||
neuen Nachricht ans Ende -- wer weiter oben nachlas, wurde
|
||
mitten im Satz weggerissen. Jetzt bleibt er stehen, und hier
|
||
steht, wie viel unten wartet. -->
|
||
<button type="button" class="chat__zum-neuesten" id="zum-neuesten" hidden></button>
|
||
<!-- HINEINZIEHEN (09.09.2026). Erscheint nur, solange wirklich
|
||
etwas über dem Fenster hängt. Eine dauerhaft sichtbare
|
||
Ablagefläche wäre jeden Tag im Weg für den Fall, dass man
|
||
sie vielleicht braucht. -->
|
||
<div class="chat__ablage" id="ablage" hidden aria-hidden="true">
|
||
<span>Loslassen – Foto oder PDF schicken</span>
|
||
</div>
|
||
<!-- ANGEHEFTETE ANKÜNDIGUNGEN (10.09.2026, Kapitel 7.2).
|
||
Sie stehen ÜBER dem Verlauf und rollen nicht mit: Eine
|
||
Ankündigung, die man erst suchen muss, ist keine. Leer bleibt
|
||
der Kasten verborgen – ein leerer Rahmen wäre jeden Tag im
|
||
Weg für den Fall, dass irgendwann etwas darin steht. -->
|
||
<div class="chat-pins" id="pins" hidden aria-label="Angeheftet"></div>
|
||
<div class="chat__verlauf" id="verlauf" role="log" aria-live="polite" aria-relevant="additions">
|
||
<p class="chat__leer" id="verlauf-leer">
|
||
Links ein Gespräch auswählen – oder mit <strong>Neu</strong> eines anfangen.
|
||
</p>
|
||
</div>
|
||
|
||
<!-- Das Eingabefeld. Ein <form>, damit Enter abschickt, ohne
|
||
dass dafür Code nötig wäre. -->
|
||
<!-- DIE ANTWORTLEISTE (09.09.2026, Punkt 15).
|
||
|
||
Sie steht UEBER dem Eingabefeld und innerhalb desselben
|
||
<form>: So sieht man beim Tippen, worauf man antwortet, und
|
||
kann es mit einem Griff wieder loswerden. Eine Leiste
|
||
ausserhalb waere beim Tippen ausserhalb des Blickfelds.
|
||
|
||
`hidden` als Vorgabe -- sie erscheint erst, wenn wirklich
|
||
auf etwas geantwortet wird. Ein leerer Kasten ueber dem
|
||
Eingabefeld waere jeden Tag im Weg fuer den Fall, dass man
|
||
ihn vielleicht braucht. -->
|
||
<form class="chat__eingabe" id="schreiben" hidden>
|
||
<div class="antwort-leiste" id="antwort-leiste" hidden>
|
||
<span class="antwort-leiste__strich" aria-hidden="true"></span>
|
||
<span class="antwort-leiste__inhalt">
|
||
<span class="antwort-leiste__schild">Antwort an</span>
|
||
<span class="antwort-leiste__von" id="antwort-von"></span>
|
||
<span class="antwort-leiste__text" id="antwort-text"></span>
|
||
</span>
|
||
<button type="button" class="antwort-leiste__weg" id="antwort-weg"
|
||
aria-label="Antwort verwerfen">✕</button>
|
||
</div>
|
||
<label class="chat__eingabe-schild" for="text">Nachricht</label>
|
||
<!-- KURZER PLATZHALTER. Hier stand der Hinweis "Enter schickt,
|
||
Umschalt+Enter macht eine neue Zeile" -- zwei Fehler auf
|
||
einmal: Am Handy brach er auf zwei Zeilen um und schob das
|
||
Feld halb unter den Bildschirmrand, UND er stimmte dort
|
||
nicht: Auf Geräten ohne Tastatur schickt Enter absichtlich
|
||
nicht, sonst käme man nie zu einer zweiten Zeile.
|
||
Die Tastenbelegung steht jetzt im title -- sie ist ein
|
||
Hinweis für Wiederkehrer, keine Aufforderung. -->
|
||
<!-- ANHANG (09.09.2026): "pdfs und fotos".
|
||
Das Feld selbst bleibt unsichtbar -- ein Datei-Auswahlfeld
|
||
sieht in jedem Browser anders aus und lässt sich nicht
|
||
gestalten. Bedient wird es über die Büroklammer daneben. -->
|
||
<input type="file" id="anhang-feld" class="chat__anhang-feld"
|
||
accept="image/png,image/jpeg,image/webp,image/gif,application/pdf"
|
||
tabindex="-1" aria-hidden="true" />
|
||
<button type="button" class="chat__anhang" id="anhang-auf"
|
||
aria-label="Foto oder PDF anhängen" title="Foto oder PDF anhängen">
|
||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||
<path d="M20 11.5 12.3 19.2a4.4 4.4 0 0 1-6.2-6.2l7.9-7.9a2.9 2.9 0 0 1 4.1 4.1l-7.9 7.9a1.5 1.5 0 0 1-2.1-2.1l7.2-7.2" />
|
||
</svg>
|
||
</button>
|
||
<textarea id="text" rows="1" maxlength="4000"
|
||
placeholder="Nachricht schreiben …"
|
||
title="Enter schickt ab, Umschalt+Enter macht eine neue Zeile"></textarea>
|
||
<!-- EMOJI-AUSWAHL (09.09.2026). Die Bildschirmtastatur am Handy
|
||
hat sie ohnehin -- am Rechner nicht, und dort schreibt die
|
||
Betreuung. Dreissig feste Zeichen statt einer nachgeladenen
|
||
Tafel: Das deckt den Alltag ab und laedt nichts. -->
|
||
<button type="button" class="chat__emoji" id="emoji-auf"
|
||
aria-expanded="false" aria-controls="emoji-tafel"
|
||
aria-label="Emoji einfügen" title="Emoji einfügen">☺</button>
|
||
<div class="emoji-tafel" id="emoji-tafel" role="group"
|
||
aria-label="Emoji auswählen" hidden></div>
|
||
<!-- Erscheint erst, wenn es eng wird -- siehe chat.js. -->
|
||
<span class="chat__zeichen" id="zeichen" aria-live="polite" hidden></span>
|
||
<button type="submit" class="chat__senden" id="senden" aria-label="Abschicken">
|
||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||
<path d="M4 12l16-7-7 16-2.5-6.5z" />
|
||
</svg>
|
||
</button>
|
||
</form>
|
||
<p class="fehler" id="fehler" role="alert" aria-live="polite"></p>
|
||
</section>
|
||
</div>
|
||
</main>
|
||
|
||
<!-- Neues Gespräch. Beides in einem Dialog: Wer eine Person anklickt,
|
||
schreibt ihr; wer mehrere anhakt, legt eine Gruppe an. Zwei
|
||
getrennte Dialoge hätten die Frage "Einzeln oder Gruppe?" VOR die
|
||
eigentliche Absicht gestellt. -->
|
||
<dialog class="dialog" id="neu-dialog">
|
||
<form method="dialog" id="neu-form" class="neu neu--blank">
|
||
<h2 class="dialog__titel" id="neu-titel">Neues Gespräch</h2>
|
||
<p class="dialog__unter" id="neu-unter">
|
||
Eine Person antippen, um ihr zu schreiben. Mehrere auswählen ergibt
|
||
eine Gruppe.
|
||
</p>
|
||
|
||
<!-- ZWEI ARTEN, EIN DIALOG (10.09.2026, Kapitel 7.2).
|
||
Ein zweiter Dialog wäre dieselbe Personenwahl ein zweites Mal –
|
||
und die zweite Fassung wäre die, in der eine Regel fehlt.
|
||
Sichtbar nur, wenn der Server sagt, dass Kanäle erlaubt sind. -->
|
||
<div class="neu__art" id="neu-art" role="group" aria-label="Was soll es werden?" hidden>
|
||
<button type="button" data-art="gespraech" data-an="ja" aria-pressed="true">Gespräch</button>
|
||
<button type="button" data-art="kanal" aria-pressed="false">Kanal</button>
|
||
</div>
|
||
|
||
<div class="neu__zeile" id="kanal-zeile" hidden>
|
||
<label class="feld-schild" for="kanal-kategorie">Zuständigkeit</label>
|
||
<select id="kanal-kategorie"></select>
|
||
<p class="neu__hinweis" id="kanal-alle" hidden>
|
||
Für jede Zuständigkeit gibt es schon einen Kanal.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="chat-wahl" id="partner"></div>
|
||
|
||
<!-- Erscheint erst ab zwei Ausgewählten -- vorher wäre ein
|
||
Gruppenname eine Frage ohne Anlass. -->
|
||
<div class="neu__zeile" id="gruppenname-zeile" hidden>
|
||
<label class="feld-schild" for="gruppenname">Name der Gruppe</label>
|
||
<input id="gruppenname" maxlength="80" placeholder="z. B. Luna & Betreuung" />
|
||
</div>
|
||
|
||
<p class="fehler" id="neu-fehler" role="alert" aria-live="polite"></p>
|
||
|
||
<div class="dialog__knoepfe">
|
||
<button type="submit" class="knopf knopf--klein" id="neu-los" disabled>Schreiben</button>
|
||
<button type="button" class="abmelden" id="neu-zu">Abbrechen</button>
|
||
</div>
|
||
</form>
|
||
</dialog>
|
||
|
||
<script src="assets/js/bereiche.js?v=202609102008"></script>
|
||
<script src="assets/js/wahl.js?v=202609102008" defer></script>
|
||
<script src="assets/js/kopf.js?v=202609102008"></script>
|
||
<script src="assets/js/glocke.js?v=202609102008"></script>
|
||
<script src="assets/js/chat.js?v=202609102008"></script>
|
||
</body>
|
||
</html>
|