Kategorie-Kanaele und angeheftete Ankuendigungen (Kapitel 7.2)
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]>
This commit is contained in:
+288
-16
@@ -48,6 +48,19 @@
|
||||
let strom = null; // die EventSource
|
||||
let gewaehlt = new Set(); // im Neu-Dialog
|
||||
|
||||
/* KANAELE UND ANGEHEFTETES (10.09.2026, Kapitel 7.2).
|
||||
|
||||
`darfKanal` und `darfAnheftenHier` kommen vom SERVER, nicht aus
|
||||
einem Rollenvergleich hier. Diese Datei bekommt jeder, der die
|
||||
Seite oeffnet -- ein `rolle === 'admin'` darin waere eine Auskunft
|
||||
ueber den Aufbau des Hauses an alle. Ein Ja/Nein ist keine. */
|
||||
let angeheftet = [];
|
||||
let darfAnheftenHier = false;
|
||||
let darfKanal = false;
|
||||
let kanalFrei = [];
|
||||
let neuArt = 'gespraech'; // 'gespraech' | 'kanal'
|
||||
let kanalUmbau = null; // Raumnummer, wenn die Besetzung geaendert wird
|
||||
|
||||
/* ---------- Kleinkram ---------------------------------------------------- */
|
||||
|
||||
async function hole(weg, einst) {
|
||||
@@ -104,7 +117,15 @@
|
||||
try {
|
||||
const a = await hole('/workspace/api/chat/raeume');
|
||||
if (!a.ok) return;
|
||||
raeume = (await a.json()).raeume || [];
|
||||
const daten = await a.json();
|
||||
raeume = daten.raeume || [];
|
||||
darfKanal = !!daten.darf_kanal;
|
||||
kanalFrei = daten.kanal_frei || [];
|
||||
/* Der Knopf erscheint nur, wenn der Server etwas zu bieten hat.
|
||||
Ein Knopf, der beim Druecken "darfst du nicht" sagt, ist eine
|
||||
Enttaeuschung mit Ansage. */
|
||||
$('neu-art').hidden = !darfKanal;
|
||||
if (!darfKanal) neuArt = 'gespraech';
|
||||
} catch { return; }
|
||||
raeumeZeichnen();
|
||||
ungelesenMelden();
|
||||
@@ -141,7 +162,12 @@
|
||||
|
||||
const zeichen = el('span', 'chat-raum__zeichen');
|
||||
zeichen.setAttribute('aria-hidden', 'true');
|
||||
if (r.art === 'gruppe') {
|
||||
if (r.art === 'kanal') {
|
||||
/* Das Rautenzeichen ist die Schreibweise, die jeder aus
|
||||
anderen Messengern kennt -- es muss nicht erklaert werden. */
|
||||
zeichen.dataset.kanal = 'ja';
|
||||
zeichen.textContent = '#';
|
||||
} else if (r.art === 'gruppe') {
|
||||
zeichen.dataset.gruppe = 'ja';
|
||||
zeichen.textContent = String(r.teilnehmer.length);
|
||||
} else {
|
||||
@@ -167,7 +193,12 @@
|
||||
|
||||
const vorschau = el('span', 'chat-raum__vorschau');
|
||||
if (r.letzte) {
|
||||
const wer = r.letzte.von === ich.name ? 'Du: ' : (r.art === 'gruppe' ? `${r.letzte.von}: ` : '');
|
||||
/* Wer geschrieben hat, gehoert ueberall dorthin, wo mehr als
|
||||
zwei Leute schreiben koennen -- in einer Gruppe wie in einem
|
||||
Kanal. Hier stand `=== 'gruppe'`, und ein Kanal waere damit
|
||||
die einzige Stelle ohne Absender in der Vorschau gewesen. */
|
||||
const wer = r.letzte.von === ich.name ? 'Du: '
|
||||
: (r.art !== 'direkt' ? `${r.letzte.von}: ` : '');
|
||||
vorschau.textContent = r.letzte.zurueckgenommen
|
||||
? `${wer}Nachricht zurückgenommen`
|
||||
: wer + r.letzte.text;
|
||||
@@ -271,6 +302,8 @@
|
||||
const daten = await a.json();
|
||||
offen = daten.raum;
|
||||
nachrichten = daten.nachrichten;
|
||||
angeheftet = daten.angeheftet || [];
|
||||
darfAnheftenHier = !!daten.darf_anheften;
|
||||
/* 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
|
||||
@@ -280,9 +313,15 @@
|
||||
} catch { return; }
|
||||
|
||||
$('verlauf-titel').textContent = offen.name;
|
||||
$('verlauf-unter').textContent = offen.art === 'gruppe'
|
||||
/* Bei allem, was mehr als zwei Leute hat, stehen die Namen darunter
|
||||
-- bei einem Zweier-Gespraech die Rolle des Gegenuebers. */
|
||||
$('verlauf-unter').textContent = offen.art !== 'direkt'
|
||||
? offen.teilnehmer.map((t) => t.name).join(' · ')
|
||||
: (ROLLENKURZ[offen.teilnehmer.find((t) => t.id !== ich.id)?.rolle] || '');
|
||||
/* Die Besetzung aendern kann nur, wer Kanaele fuehrt -- und nur in
|
||||
einem Kanal. In einer Gruppe waere es ein Eingriff in fremde
|
||||
Runden; siehe die Route dazu im Server. */
|
||||
$('kanal-leute').hidden = !(darfKanal && offen.art === 'kanal');
|
||||
$('schreiben').hidden = false;
|
||||
$('verlauf-leer').hidden = true;
|
||||
$('chat').dataset.ansicht = 'verlauf';
|
||||
@@ -292,6 +331,7 @@
|
||||
/* Ein frisch geoeffnetes Gespraech steht immer am Ende -- man will
|
||||
das Letzte sehen, nicht das Erste. */
|
||||
unten = true; neuUnten = 0;
|
||||
pinsZeichnen();
|
||||
verlaufZeichnen();
|
||||
/* Der Entwurf von vorhin, falls einer da ist. */
|
||||
$('text').value = entwurfHolen(id);
|
||||
@@ -306,6 +346,79 @@
|
||||
raeumeZeichnen();
|
||||
}
|
||||
|
||||
/* =======================================================================
|
||||
DIE ANGEHEFTETEN (10.09.2026, Kapitel 7.2)
|
||||
|
||||
Sie stehen UEBER dem Verlauf und rollen nicht mit. Eine Ankuendigung,
|
||||
die man erst suchen muss, ist keine.
|
||||
|
||||
Jeder Aushang fuehrt zu seiner Stelle im Verlauf. Ohne das waere er
|
||||
ein Bild von einer Nachricht: Man liest den Anfang und kann nicht
|
||||
nachsehen, was darauf geantwortet wurde.
|
||||
======================================================================= */
|
||||
function pinNadel() {
|
||||
const raum = 'http://www.w3.org/2000/svg';
|
||||
const s = document.createElementNS(raum, 'svg');
|
||||
s.setAttribute('viewBox', '0 0 24 24');
|
||||
s.setAttribute('aria-hidden', 'true');
|
||||
s.setAttribute('class', 'chat-pin__nadel');
|
||||
const pfad = document.createElementNS(raum, 'path');
|
||||
pfad.setAttribute('d', 'M9 3h6l-1 5 3 3v2h-5v6l-1 2-1-2v-6H5v-2l3-3z');
|
||||
pfad.setAttribute('fill', 'currentColor');
|
||||
s.append(pfad);
|
||||
return s;
|
||||
}
|
||||
|
||||
function pinsZeichnen() {
|
||||
const ziel = $('pins');
|
||||
ziel.textContent = '';
|
||||
ziel.hidden = !angeheftet.length;
|
||||
if (!angeheftet.length) return;
|
||||
|
||||
for (const a of angeheftet) {
|
||||
const kasten = el('div', 'chat-pin');
|
||||
|
||||
const auf = el('button', 'chat-pin__auf');
|
||||
auf.type = 'button';
|
||||
auf.title = 'Zur Nachricht springen';
|
||||
const kopf = el('span', 'chat-pin__kopf');
|
||||
kopf.append(pinNadel());
|
||||
kopf.append(el('span', 'chat-pin__von', a.von));
|
||||
if (a.heftete) kopf.append(el('span', 'chat-pin__wer', `angeheftet von ${a.heftete}`));
|
||||
auf.append(kopf);
|
||||
auf.append(el('span', 'chat-pin__text', a.text || 'Ohne Text'));
|
||||
auf.addEventListener('click', () => zurStelle(a.id));
|
||||
kasten.append(auf);
|
||||
|
||||
if (darfAnheftenHier) {
|
||||
const los = el('button', 'chat-pin__los', 'lösen');
|
||||
los.type = 'button';
|
||||
los.setAttribute('aria-label', `„${(a.text || '').slice(0, 40)}“ lösen`);
|
||||
los.addEventListener('click', () => anheften(a.id, false));
|
||||
kasten.append(los);
|
||||
}
|
||||
ziel.append(kasten);
|
||||
}
|
||||
}
|
||||
|
||||
async function anheften(id, an) {
|
||||
try {
|
||||
const a = await hole(`/workspace/api/chat/nachrichten/${id}/anheften`, {
|
||||
method: 'PUT', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ an }),
|
||||
});
|
||||
if (!a.ok) {
|
||||
melde((await a.json().catch(() => ({}))).fehler || 'Ging nicht.');
|
||||
return;
|
||||
}
|
||||
/* Neu holen statt hier nachzurechnen: Der Server kennt die Grenze
|
||||
von drei, das Abfallen zurueckgenommener Nachrichten und die
|
||||
Reihenfolge. Zwei Rechnungen darueber liefen irgendwann
|
||||
auseinander. */
|
||||
if (offen) await raumOeffnen(offen.id, { sanft: true });
|
||||
} catch { /* umgeleitet */ }
|
||||
}
|
||||
|
||||
function verlaufZeichnen() {
|
||||
const ziel = $('verlauf');
|
||||
/* NUR DIE NACHRICHTEN WEG — NICHT DER GANZE KASTEN.
|
||||
@@ -396,8 +509,10 @@
|
||||
zeile.append(z);
|
||||
}
|
||||
|
||||
if (n.angeheftet) zeile.dataset.angeheftet = 'ja';
|
||||
|
||||
const blase = el('div', 'chat-nachricht__blase');
|
||||
if (!gleicheFolge && !n.selbst && offen.art === 'gruppe') {
|
||||
if (!gleicheFolge && !n.selbst && offen.art !== 'direkt') {
|
||||
blase.append(el('p', 'chat-nachricht__von', n.von));
|
||||
}
|
||||
|
||||
@@ -456,6 +571,18 @@
|
||||
weg.addEventListener('click', () => zuruecknehmen(n.id));
|
||||
fuss.append(weg);
|
||||
}
|
||||
/* ANHEFTEN steht an derselben Stelle wie "antworten" -- dieselbe
|
||||
Sorte Handgriff, dieselbe Zeile. Nur fuer die, die es duerfen,
|
||||
und nie an einer zurueckgenommenen oder noch unterwegs
|
||||
befindlichen Nachricht (`n.id > 0`): Die haette noch keine
|
||||
Nummer, unter der man sie anheften koennte. */
|
||||
if (darfAnheftenHier && !n.zurueckgenommen && n.id > 0) {
|
||||
const pin = el('button', 'chat-nachricht__pin-knopf',
|
||||
n.angeheftet ? 'lösen' : 'anheften');
|
||||
pin.type = 'button';
|
||||
pin.addEventListener('click', () => anheften(n.id, !n.angeheftet));
|
||||
fuss.append(pin);
|
||||
}
|
||||
/* KOPIEREN. Eine Adresse, ein Code, eine Uhrzeit -- man will sie
|
||||
weitergeben, nicht abtippen. Der Knopf bestaetigt sich selbst
|
||||
und faellt danach zurueck; eine Meldung anderswo waere ein
|
||||
@@ -869,6 +996,15 @@
|
||||
raeumeLaden();
|
||||
});
|
||||
|
||||
/* ANGEHEFTET ODER GELOEST -- alle im Raum sehen es sofort.
|
||||
Betroffen ist nur, wer denselben Raum offen hat; sonst genuegt
|
||||
die Liste. */
|
||||
strom.addEventListener('pin', (e) => {
|
||||
let n;
|
||||
try { n = JSON.parse(e.data); } catch { return; }
|
||||
if (offen && n.raum_id === offen.id) raumOeffnen(offen.id, { sanft: true });
|
||||
});
|
||||
|
||||
/* Bei einem Abriss verbindet EventSource von selbst neu. Wir holen
|
||||
danach den Verlauf nach -- sonst fehlte, was während der Pause
|
||||
geschrieben wurde. */
|
||||
@@ -880,10 +1016,10 @@
|
||||
|
||||
/* ---------- Neues Gespräch ----------------------------------------------- */
|
||||
|
||||
async function partnerLaden() {
|
||||
async function partnerLaden(vorbelegt = []) {
|
||||
const ziel = $('partner');
|
||||
ziel.textContent = '';
|
||||
gewaehlt = new Set();
|
||||
gewaehlt = new Set(vorbelegt);
|
||||
$('gruppenname-zeile').hidden = true;
|
||||
$('neu-los').disabled = true;
|
||||
$('neu-fehler').textContent = '';
|
||||
@@ -906,17 +1042,13 @@
|
||||
fehlte 'spicy' -- und wer nicht in der Schleife vorkommt, taucht
|
||||
in der Gesprächsliste gar nicht auf. Kein Fehler, nur eine
|
||||
Person, die es fuer die Anderen nicht gibt. */
|
||||
for (const rolle of window.Bereiche.ROLLENFOLGE) {
|
||||
const leute = personen.filter((p) => p.rolle === rolle);
|
||||
if (!leute.length) continue;
|
||||
|
||||
function gruppeBauen(schluessel, ueberschrift, leute) {
|
||||
const gruppe = el('div', 'chat-wahl__gruppe');
|
||||
gruppe.dataset.rolle = rolle;
|
||||
gruppe.dataset.rolle = schluessel;
|
||||
const titel = el('p', 'chat-wahl__titel');
|
||||
const punkt = el('span', 'chat-wahl__punkt');
|
||||
punkt.setAttribute('aria-hidden', 'true');
|
||||
titel.append(punkt, document.createTextNode(
|
||||
window.Bereiche.ROLLEN_GRUPPE[rolle]));
|
||||
titel.append(punkt, document.createTextNode(ueberschrift));
|
||||
gruppe.append(titel);
|
||||
|
||||
const liste = el('div', 'chat-wahl__liste');
|
||||
@@ -926,7 +1058,8 @@
|
||||
b.type = 'button';
|
||||
b.dataset.id = String(p.id);
|
||||
b.dataset.rolle = p.rolle;
|
||||
b.setAttribute('aria-pressed', 'false');
|
||||
if (gewaehlt.has(p.id)) { b.dataset.an = 'ja'; }
|
||||
b.setAttribute('aria-pressed', gewaehlt.has(p.id) ? 'true' : 'false');
|
||||
|
||||
const z = el('span', 'chat-wahl__zeichen', initialen(p.name));
|
||||
z.setAttribute('aria-hidden', 'true');
|
||||
@@ -943,10 +1076,89 @@
|
||||
gruppe.append(liste);
|
||||
ziel.append(gruppe);
|
||||
}
|
||||
|
||||
const gezeichnet = new Set();
|
||||
for (const rolle of window.Bereiche.ROLLENFOLGE) {
|
||||
const leute = personen.filter((p) => p.rolle === rolle);
|
||||
gezeichnet.add(rolle);
|
||||
if (!leute.length) continue;
|
||||
gruppeBauen(rolle, window.Bereiche.ROLLEN_GRUPPE[rolle], leute);
|
||||
}
|
||||
|
||||
/* UND DANN ALLE UEBRIGEN (10.09.2026).
|
||||
|
||||
Genau die Lücke, vor der der Kommentar oben warnt -- sie war
|
||||
offen. `ROLLENFOLGE` hat fünf Einträge; wer eine sechste Rolle
|
||||
hat, wurde von der Schleife darüber nie erfasst und verschwand
|
||||
lautlos aus der Auswahl. Der Server schickte ihn mit, der
|
||||
Browser zeichnete ihn nicht, und niemand konnte sehen, dass
|
||||
etwas fehlt.
|
||||
|
||||
Aufgefallen ist es beim HINSEHEN, nicht beim Lesen: Ein frisch
|
||||
angelegter Kanal hatte hinterher zwei Leute statt vier.
|
||||
|
||||
Die Überschrift kommt vom Server -- so kann hier auch eine Rolle
|
||||
stehen, deren Namen diese Datei gar nicht kennen darf. Nach
|
||||
Überschrift gruppiert und nicht nach Rolle: Wer einen Menschen
|
||||
sucht, sucht ihn in einer Liste, nicht in einer Rangordnung. */
|
||||
const uebrig = personen.filter((p) => !gezeichnet.has(p.rolle));
|
||||
const nachTitel = new Map();
|
||||
for (const p of uebrig) {
|
||||
const t = p.gruppe || 'Weitere';
|
||||
if (!nachTitel.has(t)) nachTitel.set(t, []);
|
||||
nachTitel.get(t).push(p);
|
||||
}
|
||||
for (const [titel, leute] of nachTitel) gruppeBauen(leute[0].rolle, titel, leute);
|
||||
|
||||
wahlGeaendert();
|
||||
}
|
||||
|
||||
/* Welche Zustaendigkeiten noch keinen Kanal haben. Die Liste kommt vom
|
||||
Server; steht nichts darin, gibt es alle schon. */
|
||||
function kanalWahlFuellen() {
|
||||
const feld = $('kanal-kategorie');
|
||||
feld.textContent = '';
|
||||
for (const k of kanalFrei) {
|
||||
const o = document.createElement('option');
|
||||
o.value = k.wert; o.textContent = k.name;
|
||||
feld.append(o);
|
||||
}
|
||||
$('kanal-alle').hidden = kanalFrei.length > 0;
|
||||
feld.hidden = kanalFrei.length === 0;
|
||||
}
|
||||
|
||||
/* Umschalten zwischen "Gespräch" und "Kanal". Ein zweiter Dialog
|
||||
waere dieselbe Personenwahl ein zweites Mal -- und die zweite
|
||||
Fassung waere die, in der eine Regel fehlt. */
|
||||
function neuArtSetzen(art) {
|
||||
neuArt = art;
|
||||
for (const b of document.querySelectorAll('#neu-art [data-art]')) {
|
||||
const an = b.dataset.art === art;
|
||||
b.dataset.an = an ? 'ja' : '';
|
||||
b.setAttribute('aria-pressed', an ? 'true' : 'false');
|
||||
}
|
||||
$('kanal-zeile').hidden = art !== 'kanal' || !!kanalUmbau;
|
||||
$('neu-titel').textContent = kanalUmbau ? 'Wer ist im Kanal?'
|
||||
: art === 'kanal' ? 'Neuer Kanal' : 'Neues Gespräch';
|
||||
$('neu-unter').textContent = kanalUmbau
|
||||
? 'Antippen, um jemanden hinein- oder herauszunehmen. Die Leitung bleibt immer dabei.'
|
||||
: art === 'kanal'
|
||||
? 'Ein Kanal je Zuständigkeit. Der Name kommt aus der Kategorie – so gibt es ihn nur einmal.'
|
||||
: 'Eine Person antippen, um ihr zu schreiben. Mehrere auswählen ergibt eine Gruppe.';
|
||||
wahlGeaendert();
|
||||
}
|
||||
|
||||
function wahlGeaendert() {
|
||||
const n = gewaehlt.size;
|
||||
if (neuArt === 'kanal') {
|
||||
/* Ein Kanal braucht keinen Namen (er kommt aus der Kategorie) und
|
||||
auch niemanden -- man legt ihn an und fuellt ihn spaeter. Nur
|
||||
eine Kategorie muss es geben, sonst gibt es nichts anzulegen. */
|
||||
$('gruppenname-zeile').hidden = true;
|
||||
$('neu-los').disabled = !kanalUmbau && !$('kanal-kategorie').value;
|
||||
$('neu-los').textContent = kanalUmbau ? 'Übernehmen' : 'Kanal anlegen';
|
||||
return;
|
||||
}
|
||||
/* Ab zwei ist es eine Gruppe -- und die braucht einen Namen. */
|
||||
$('gruppenname-zeile').hidden = n < 2;
|
||||
$('neu-los').disabled = n === 0;
|
||||
@@ -956,8 +1168,48 @@
|
||||
async function neuAbschicken(e) {
|
||||
e.preventDefault();
|
||||
const ids = [...gewaehlt];
|
||||
if (!ids.length) return;
|
||||
const knopf = $('neu-los');
|
||||
|
||||
/* KANAL: anlegen oder umbesetzen. Steht zuerst, weil hier auch eine
|
||||
LEERE Auswahl erlaubt ist -- ein Kanal ohne Leute ist ein
|
||||
gueltiger Zwischenstand, ein Gespraech ohne Gegenueber nicht. */
|
||||
if (neuArt === 'kanal') {
|
||||
knopf.disabled = true;
|
||||
try {
|
||||
if (kanalUmbau) {
|
||||
const a = await hole(`/workspace/api/chat/kanal/${kanalUmbau}/mitglieder`, {
|
||||
method: 'PUT', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ mit: ids }),
|
||||
});
|
||||
if (!a.ok) {
|
||||
$('neu-fehler').textContent = (await a.json().catch(() => ({}))).fehler || 'Ging nicht.';
|
||||
return;
|
||||
}
|
||||
const raumId = kanalUmbau;
|
||||
$('neu-dialog').close();
|
||||
await raeumeLaden();
|
||||
await raumOeffnen(raumId, { sanft: true });
|
||||
return;
|
||||
}
|
||||
const kategorie = $('kanal-kategorie').value;
|
||||
if (!kategorie) { $('neu-fehler').textContent = 'Wähle eine Kategorie.'; return; }
|
||||
const a = await hole('/workspace/api/chat/kanal', {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ kategorie, mit: ids }),
|
||||
});
|
||||
if (!a.ok) {
|
||||
$('neu-fehler').textContent = (await a.json().catch(() => ({}))).fehler || 'Ging nicht.';
|
||||
return;
|
||||
}
|
||||
const raumId = (await a.json()).raum_id;
|
||||
$('neu-dialog').close();
|
||||
await raeumeLaden();
|
||||
await raumOeffnen(raumId);
|
||||
} catch { /* umgeleitet */ } finally { knopf.disabled = false; }
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ids.length) return;
|
||||
knopf.disabled = true;
|
||||
|
||||
try {
|
||||
@@ -1194,9 +1446,29 @@
|
||||
|
||||
$('suche').addEventListener('input', () => { raeumeZeichnen(); nachrichtenSuchen(); });
|
||||
$('neu-oeffnen').addEventListener('click', async () => {
|
||||
kanalUmbau = null;
|
||||
kanalWahlFuellen();
|
||||
neuArtSetzen('gespraech');
|
||||
await partnerLaden();
|
||||
$('neu-dialog').showModal();
|
||||
});
|
||||
|
||||
for (const b of document.querySelectorAll('#neu-art [data-art]')) {
|
||||
b.addEventListener('click', () => neuArtSetzen(b.dataset.art));
|
||||
}
|
||||
$('kanal-kategorie').addEventListener('change', wahlGeaendert);
|
||||
|
||||
/* WER IST IM KANAL. Derselbe Dialog, vorbelegt mit denen, die schon
|
||||
drin sind -- so sieht man beim Oeffnen den Stand und muss ihn nicht
|
||||
erst aus der Kopfzeile abschreiben. */
|
||||
$('kanal-leute').addEventListener('click', async () => {
|
||||
if (!offen || offen.art !== 'kanal') return;
|
||||
kanalUmbau = offen.id;
|
||||
kanalWahlFuellen();
|
||||
neuArtSetzen('kanal');
|
||||
await partnerLaden(offen.teilnehmer.map((t) => t.id));
|
||||
$('neu-dialog').showModal();
|
||||
});
|
||||
$('neu-zu').addEventListener('click', () => $('neu-dialog').close());
|
||||
$('neu-form').addEventListener('submit', neuAbschicken);
|
||||
$('zur-liste').addEventListener('click', () => {
|
||||
|
||||
Reference in New Issue
Block a user