Das Suchfeld nimmt wieder Eingaben, eigene Kanalnamen, Community in Kanälen

screen1, drei Teile.

=== 1. „bei suchen kann man nichts reinschreiben" ===

MEIN FEHLER VOM SELBEN TAG. Beim Popover-Umbau heute frueh hing die
Liste am <body>, damit sie nicht hinter dem modalen Dialog
verschwindet. Gemessen: Das Suchfeld war da, der Fokus landete nicht
darin, ein getipptes Zeichen kam nicht an.

DER GRUND IST DER FOKUS-KAEFIG. Ein Dialog aus showModal() sperrt den
Fokus auf seinen eigenen DOM-Baum ein. Die Liste lag im Top Layer --
sichtbar und anklickbar, aber ausserhalb des Kaefigs. Klicken braucht
keinen Fokus, Tippen schon. Deshalb fiel es niemandem auf: Die Liste
stand da, sie filterte auf Knopfdruck, nur eine Eingabe kam nicht an.

DIE LOESUNG WAR EINE KOMBINATION, die ich vorher fuer unmoeglich
hielt. Im Dialog wurde die Liste vom clip-path der abgeschraegten
Ecke abgeschnitten, draussen war sie nicht bedienbar. Ein POPOVER
wird aber in die Top Layer gehoben und dort gezeichnet -- der
Beschnitt des Elternteils erreicht es nicht mehr, waehrend der
DOM-Baum (und damit der Fokus) der des Dialogs bleibt. Jetzt haengt
sie wieder im Dialog UND ist ein Popover: bedienbar und unbeschnitten.

Gemessen beides gegeneinander: „comm" getippt -> kommt an, filtert auf
1 Treffer; und die LETZTE Zeile der vollen Liste ist wirklich
anklickbar (elementFromPoint trifft sie selbst, nicht den Dialog).
Daraus ist pruef-suchfeld geworden -- der Fehler war von aussen nicht
zu sehen, und gefunden hat ihn Filipe, nicht ich.

=== 2. „einen neuen namen erstellen den es noch nicht gibt" ===

`data-frei="ja"` war in wahl.js seit langem gebaut und wurde NIE
GESETZT -- dieselbe Sorte Lueue wie `grund_min` heute Morgen. Jetzt
gesetzt; der getippte Text erscheint als eigener Eintrag ganz oben.

Der Server nahm bisher nur die neun festen Zustaendigkeiten. Jetzt
auch einen eigenen Namen: Der SCHLUESSEL wird daraus abgeleitet
(„Technik & Ton" -> "eigen-technik-ton"), der ANGEZEIGTE Name bleibt
wie geschrieben. Das Praefix ist kein Schmuck -- ohne es entstuende
aus dem Namen „Community" derselbe Schluessel wie beim festen Thema,
und der eindeutige Index lehnte ihn ab, obwohl der Kanal nicht
existiert. Gemessen: genau dieser Fall antwortet jetzt mit 201.

Der alte Kommentar sprach sich gegen freie Namen aus („der sichere
Weg zu Clipping neben Clipping-Team"). Das Risiko bleibt und wird
begrenzt: Derselbe Name zweimal ergibt denselben Schluessel und damit
409.

=== 3. „kanäle mit den leuten mit der community rolle" ===

Seit dem 19.09. nimmt `ohneAussen()` die Community aus den Listen
aller anderen. Die Begruendung dort ist ausdruecklich: „Ein
Zweier-Gespraech ist kein Kanal. Es hat keine Nachtruhe, kein Modi
liest mit, und niemand koennte moderieren."

Genau diese Begruendung laesst den Kanal offen. Die Sperre bleibt
fuer GESPRAECHE und faellt fuer KANAELE -- `?fuer=kanal` an der
Partnerliste, und nur fuer den, der Kanaele ueberhaupt aufmachen
darf. Sonst waere der Parameter ein Weg, an `ohneAussen` vorbei Namen
zu erfahren.

Gemessen: DogFather sieht im Gespraech VanVan und Miss, im Kanal
zusaetzlich Kessi und Tom (beide Community). Ein Modi bekommt die
erweiterte Liste auch mit dem Parameter nicht.

Gruen: pruef-suchfeld (8, neu), pruef-chat-kanaele (81),
pruef-treffchat, pruef-chat-neu, pruef-freie-namen (32),
pruef-nachfrage (53), pruef-css-klassen.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-24 11:37:03 +02:00
co-authored by Claude Opus 5
parent 8c599d7961
commit 7acd7a7674
44 changed files with 969 additions and 629 deletions
+81 -8
View File
@@ -764,7 +764,11 @@ function kanalName(kategorie) {
Teams, sondern ein eigener Raum mit eigenen Regeln. Ohne diese
Zeile hiesse er schlicht "Kanal". */
if (kategorie === TREFF_KATEGORIE) return TREFF_NAME;
return MODI_KATEGORIEN.find((k) => k.wert === kategorie)?.name || "Kanal";
/* EIN EIGENER KANAL TRAEGT SEINEN NAMEN IN DER ZEILE (24.09.2026).
MODI_KATEGORIEN kennt ihn nicht -- fuer ihn steht der Name in
der Spalte "name" des Raums. Ohne diesen Weg hiesse jeder eigene
Kanal schlicht „Kanal". */
return MODI_KATEGORIEN.find((k) => k.wert === kategorie)?.name || null;
}
/** Wer darf hier etwas anheften?
@@ -918,7 +922,11 @@ function gehoertInKanal(id) {
}
function raumName(raum, teilnehmer, ichId) {
if (raum.art === "kanal") return raum.name || kanalName(raum.kategorie);
/* Der gespeicherte Name zuerst -- bei einem eigenen Kanal ist er
die einzige Quelle. Der Rueckfall auf "Kanal" gilt Raeumen aus
der Zeit vor der Namensspalte; ohne ihn stuende dort seit dem
24.09. gar nichts, weil kanalName() jetzt null liefert. */
if (raum.art === "kanal") return raum.name || kanalName(raum.kategorie) || "Kanal";
if (raum.art === "gruppe") return raum.name || "Gruppe";
const andere = teilnehmer.filter((t) => t.id !== ichId);
return andere.map((t) => t.name).join(", ") || "Gespräch";
@@ -931,7 +939,24 @@ function raumName(raum, teilnehmer, ichId) {
/** Mit wem kann ich ein Gespräch anfangen? */
chatRouter.get("/workspace/api/chat/partner", (req, res) => {
try {
const ids = schreibbareIds(req.person);
/* ==== FUER EINEN KANAL GILT EINE ANDERE LISTE (24.09.2026) ====
Filipe: „ich will auch die option kanäle mit den leuten mit der
community rolle."
Seit dem 19.09. nimmt "ohneAussen()" die Community aus den
Listen aller anderen -- weil ein Zweier-Gespraech mit einem
Zuschauer eine Standleitung ohne Moderation waere. Diese
Begruendung gilt fuer ein Gespraech und NICHT fuer einen Kanal:
Dort sitzt die ganze Teamfuehrung drin, und er ist moderierbar.
"?fuer=kanal" fragt also eine andere Frage, nicht dieselbe mit
weniger Sorgfalt. Wer Kanaele gar nicht aufmachen darf, bekommt
die erweiterte Liste auch nicht -- sonst waere der Parameter
ein Weg, an "ohneAussen" vorbei Namen zu erfahren. */
const fuerKanal = String(req.query?.fuer || "") === "kanal"
&& darfKanalAufmachen(req.person);
const ids = fuerKanal ? null : schreibbareIds(req.person);
const wo = ids === null
? "id <> ?" : `id IN (${ids.map(() => "?").join(",")})`;
const werte = ids === null ? [req.person.id] : ids;
@@ -1296,9 +1321,54 @@ chatRouter.post("/workspace/api/chat/kanal", gleicheHerkunft, express.json({ lim
nicht erfahren, dass es die Moeglichkeit gibt. */
if (!darfKanalAufmachen(req.person)) return res.status(404).json({ fehler: "nicht_gefunden" });
/* ==== EIN EIGENER NAME IST ERLAUBT (24.09.2026) ===============
Filipe: „ich will dass man die option hat einen neuen namen zu
erstellen den es noch nicht da in der liste gibt."
Bis heute musste die Zustaendigkeit aus MODI_KATEGORIEN
stammen -- neun feste Themen. Jetzt geht auch ein eigener
Name, und die Liste bleibt als Vorschlag.
DER SCHLUESSEL WIRD ABGELEITET, NICHT GETIPPT. Er landet in
der Datenbank und in Adressen; Leerzeichen, Umlaute und
Satzzeichen haetten dort nichts verloren. Aus „Technik & Ton"
wird "technik-ton". Der ANGEZEIGTE Name bleibt, wie er
geschrieben wurde -- er steht in "name".
WARUM EIN PRAEFIX: Ein eigener Schluessel darf nie mit einem
festen zusammenfallen. Schriebe jemand „Community", entstuende
"community" -- derselbe Schluessel wie das feste Thema, und
der eindeutige Index in der Datenbank wuerde ihn ablehnen,
obwohl der Kanal noch gar nicht existiert. Mit "eigen-" davor
kann das nicht passieren.
DER NAME WIRD MITGESPEICHERT. Bei den festen Themen kommt er
aus MODI_KATEGORIEN (siehe kanalName weiter oben); ein eigener
hat dort keinen Eintrag, deshalb steht er in der Spalte
"name". */
const kategorie = String(req.body?.kategorie ?? "").trim();
if (!MODI_KATEGORIEN.some((k) => k.wert === kategorie)) {
return res.status(400).json({ fehler: "Unbekannte Kategorie." });
const eigenerName = String(req.body?.eigener_name ?? "").trim()
.replace(/\s+/g, " ").slice(0, 60);
const fest = MODI_KATEGORIEN.some((k) => k.wert === kategorie);
let schluessel = kategorie;
let anzeigeName = null;
if (!fest) {
if (eigenerName.length < 2) {
return res.status(400).json({
fehler: "Für einen eigenen Kanal brauchst du einen Namen (mindestens zwei Zeichen).",
});
}
const rein = eigenerName.toLowerCase()
.replace(/ä/g, "ae").replace(/ö/g, "oe").replace(/ü/g, "ue").replace(/ß/g, "ss")
.replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
if (!rein) {
return res.status(400).json({
fehler: "Der Name braucht mindestens einen Buchstaben oder eine Ziffer.",
});
}
schluessel = "eigen-" + rein;
anzeigeName = eigenerName;
}
const d = db();
@@ -1307,7 +1377,7 @@ chatRouter.post("/workspace/api/chat/kanal", gleicheHerkunft, express.json({ lim
jemand lesen kann, und dort eine Zusage, die auch dann gilt, wenn
einmal zwei Anfragen im selben Augenblick ankommen. */
const schon = d.prepare("SELECT id FROM chat_raeume WHERE art = 'kanal' AND kategorie = ?")
.get(kategorie);
.get(schluessel);
if (schon) {
return res.status(409).json({ fehler: "Diesen Kanal gibt es schon.", raum_id: schon.id });
}
@@ -1335,7 +1405,10 @@ chatRouter.post("/workspace/api/chat/kanal", gleicheHerkunft, express.json({ lim
const n = jetzt();
d.prepare(`INSERT INTO chat_raeume (art, name, kategorie, erstellt, erstellt_von)
VALUES ('kanal',?,?,?,?)`)
.run(kanalName(kategorie), kategorie, n, req.person.id);
/* Der eigene Name geht in die Spalte, der feste kommt aus
MODI_KATEGORIEN. Beides landet in derselben Spalte -- die
Zeile im Chat liest nur noch dort nach. */
.run(anzeigeName || kanalName(schluessel), schluessel, n, req.person.id);
const raumId = d.prepare("SELECT last_insert_rowid() AS id").get().id;
/* DIE GANZE TEAMFUEHRUNG KOMMT HINEIN, nicht nur der Anlegende.
@@ -1363,7 +1436,7 @@ chatRouter.post("/workspace/api/chat/kanal", gleicheHerkunft, express.json({ lim
personId: req.person.id, rolle: req.person.rolle, ip: echteIp(req),
detail: `#${raumId} ${kategorie} (${drin})`.slice(0, 120),
});
res.status(201).json({ raum_id: raumId, name: kanalName(kategorie) });
res.status(201).json({ raum_id: raumId, name: anzeigeName || kanalName(schluessel) });
} catch (fehler) {
console.error("[chat] Kanal:", fehler?.message);
res.status(503).json({ fehler: "nicht_verfuegbar" });