Aufgaben abbrechen, Teilnehmerwahl fuer alle, Benachrichtigungen
Zwei Wuensche vom 05.09.2026, dazu drei Fehler, die dabei ans Licht kamen.
ABBRECHEN (Wunsch: "in jedem status die aufgaben auch abbrechen koennen,
nur ich die manager und scouts")
Neuer Status mit Pflicht-Grund, aus jedem der vier Status heraus.
Festgehalten wird auch, WO die Aufgabe stand -- "im Review abgebrochen"
ist eine andere Aussage als "nie angefangen", und das Wiederaufnehmen
geht dorthin zurueck statt nach "offen".
Eigener Weg statt "abgebrochen" in der Statusliste: Dort entscheidet
darfAendern(), und das laesst auch den zustaendigen Creator aendern.
Der gewoehnliche PATCH kann diesen Zustand deshalb gar nicht erreichen
-- auch nicht fuer DogFather, sonst waere die Grund-Pflicht umgehbar.
Abgebrochenes steht in einem zugeklappten Bereich unter dem Brett, nicht
als fuenfte Spalte: Am Handy waeren dann alle fuenf unlesbar schmal.
Verschwinden darf es nicht, sonst waere der Abbruch ein Loeschen mit
Zwischenschritt.
Die Tabelle musste dafuer getauscht werden (SQLite kann CHECK nicht
aendern). Vorher auf einer Kopie durchgespielt: 40 von 40 Aufgaben,
Inhalte, Verweise und Indizes geprueft, Gegenprobe zeigt, dass der CHECK
noch lebt.
TEILNEHMERWAHL (Wunsch: "das soll viel besser aussehen und fuer jeden
verfuegbar sein")
Auf dem Bildschirm klebten die Namen aneinander: "DogfatherDogFather".
Ursache war, dass kalender.html das Stylesheet mit diesen Klassen nie
eingebunden hat -- sie standen in dateien.css. Vierzig gruene Pruefungen
zur Teilnehmerwahl hatten das nicht gemerkt, weil keine je gefragt hat,
ob es AUSSIEHT wie gedacht.
Jetzt eigene Klassen im eigenen Stylesheet, nach Rollen gruppiert: Die
Rolle steht einmal als Ueberschrift statt neunmal am Namen. Damit ist
das Kleben an der Wurzel weg, nicht zugepflastert.
"Fuer jeden" war mehr als ein hidden zu entfernen: darfEintragen() haette
einem Creator nur sich selbst erlaubt. Er haette seinen Scout gesehen,
angeklickt, und der Server haette ihn still weggelassen -- ein Knopf, der
nichts tut. einladbareIds() schaut jetzt in beide Richtungen, bewusst
getrennt von /api/personen: Wer die erweitert, gibt einem Creator
nebenbei die Moeglichkeit, seinem Scout Aufgaben zuzuweisen.
BENACHRICHTIGUNGEN (Wunsch: "sowas, und dass es perfekt funktioniert
fuer jeden")
Web Push nach RFC 8291/8292, ohne fremde Abhaengigkeit. Der Knopf sagt
in jeder Lage die Wahrheit, auch die unbequemen: abgelehnt (mit dem
Hinweis, wo man es zuruecknimmt), iPhone im Reiter (mit Anleitung),
Browser ohne Push. Ein Knopf, der bei abgelehnter Berechtigung nur
nichts tut, ist der sichere Weg zu "das funktioniert nicht".
DREI FEHLER, DIE DABEI AUFFIELEN
1. Ein defekter Zugangsdatensatz sperrte ALLE einer Rolle aus. Wirft
hashe() bei einer Person, flog die ganze Anmeldung in den catch: 503
"nicht verfuegbar" fuer jeden mit dieser Rolle. Aufgefallen durch
einen eigenen Testfehler. Jetzt wird die defekte Person uebersprungen
und laut protokolliert; die Gegenprobe zeigt, dass ein falscher Code
weiterhin abgelehnt wird.
2. Die Glocke sprengte die Kopfleiste -- zweimal. Bei 320 px lag die
Lupe des Suchknopfes auf dem Sicht-Umschalter (ein Knopf, der auf 12
Seiten ins Leere tippt), bei 768 px wurde der Abmelden-Knopf bis zu
15 px aus dem Bild geschoben, weil die Textgrenze auf 760 stand und
ein Tablet 768 hat. Nachgewiesen durch Messen mit und ohne Glocke,
nicht durch Vermuten.
3. .block__frage war viermal gestaltet und stand auf einer Seite, die
keine dieser Dateien laedt -- derselbe Fehler wie bei der
Teilnehmerwahl. Gefunden von der neuen Klassenpruefung beim ersten
Lauf.
NEUE PRUEFUNGEN
pruef-css-klassen jede gestaltete Klasse muss auf ihrer Seite ankommen
(unterscheidet Struktur-Anker von echtem Verlust)
pruef-dabei-optik die Wahl im Browser, an den echten Pixeln
pruef-abbrechen Umstellung auf einer Kopie, Rechte, Rueckweg
pruef-abbrechen-optik Knopf, Dialog, Bereich, Handy
pruef-glocke Zustaende, An/Abmelden, jede Rolle
pruef-push(-weg) Rechnung gegen die RFC-Vektoren, Zustellung
pruef-struktur prueft jetzt zusaetzlich, ob sich jedes Server-Modul als
ESM laden laesst. node --check auf einer .js-Datei prueft als CommonJS
und meldete "ok", waehrend der Import scheiterte.
Gesamtlauf: 55 von 55 Dateien, 1973 von 1973 Punkten.
Was NICHT geprueft werden konnte und deshalb dasteht: Der Schritt
"Browser holt eine Adresse beim Push-Dienst" braucht eine Verbindung zu
Googles FCM, die ein Pruef-Browser nicht hat. Die Pruefung misst das
zuerst und meldet es als dritten Ausgang, statt gruen zu sein.
Verschluesselung und Zustellung sind getrennt geprueft; diese eine
Strecke beweist sich erst auf dem Server.
Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -511,3 +511,148 @@
|
||||
.call-raum { align-items: flex-start; }
|
||||
.call-raum button { margin-left: 0; }
|
||||
}
|
||||
|
||||
/* =====================================================================
|
||||
ABBRECHEN — Knopf, Bereich und Dialog
|
||||
=====================================================================
|
||||
|
||||
Wunsch Filipe, 05.09.2026: *"ich will dass man in jedem status die
|
||||
aufgaben auch abbrechen kann."*
|
||||
|
||||
GESTALTUNGSREGEL HIER: zurückhaltend. Abbrechen ist die seltene
|
||||
Handlung neben "starten" und "erledigt" -- sie darf auf der Karte
|
||||
nicht lauter sein als die beiden, die man täglich braucht. Kein
|
||||
Signalrot, kein Ausrufezeichen: ein gedeckter Ton, der sich vom
|
||||
Weiterschalten unterscheidet, ohne die Karte an sich zu reißen.
|
||||
(Augenschonend ist hier ohnehin Hausregel.) */
|
||||
|
||||
/* Ein warmer, gedämpfter Ton statt Alarmrot. --f ist die Farbvariable,
|
||||
die .schritt in gate.css für Rand, Schrift und Schimmer benutzt. */
|
||||
.schritt--ab { --f: #c98a7d; }
|
||||
.schritt--ab:hover { color: color-mix(in srgb, #c98a7d 40%, #ffffff); }
|
||||
|
||||
/* ---------- Der Bereich unter dem Brett ---------------------------------- */
|
||||
|
||||
.abgebrochen {
|
||||
margin: 22px 0 0;
|
||||
border: 1px solid var(--rand);
|
||||
border-radius: 14px;
|
||||
background: rgba(255, 255, 255, .022);
|
||||
}
|
||||
|
||||
.abgebrochen__kopf {
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
padding: 13px 17px;
|
||||
cursor: pointer;
|
||||
font-size: .82rem; font-weight: 700;
|
||||
letter-spacing: .06em; text-transform: uppercase;
|
||||
color: var(--text-still);
|
||||
/* Das eigene Dreieck von <summary> weg -- es wird unten selbst
|
||||
gezeichnet, damit es links steht und sich mitdreht. */
|
||||
list-style: none;
|
||||
}
|
||||
.abgebrochen__kopf::-webkit-details-marker { display: none; }
|
||||
.abgebrochen__kopf:hover { color: var(--text-leise); }
|
||||
.abgebrochen__kopf:focus-visible { outline: 2px solid var(--akzent); outline-offset: -2px; border-radius: 14px; }
|
||||
|
||||
/* Das Dreieck. Dreht sich beim Aufklappen -- die Bewegung sagt, dass
|
||||
der Inhalt darunter erscheint und nicht woanders. */
|
||||
.abgebrochen__kopf::before {
|
||||
content: ""; flex: none;
|
||||
width: 0; height: 0;
|
||||
border-left: 5px solid currentColor;
|
||||
border-top: 4px solid transparent;
|
||||
border-bottom: 4px solid transparent;
|
||||
transition: transform .18s ease;
|
||||
}
|
||||
.abgebrochen[open] .abgebrochen__kopf::before { transform: rotate(90deg); }
|
||||
|
||||
.abgebrochen__name { flex: 1; }
|
||||
.abgebrochen__zahl {
|
||||
padding: 2px 9px; border-radius: 999px;
|
||||
font-size: .78rem; font-weight: 700; letter-spacing: 0;
|
||||
color: var(--text-leise);
|
||||
background: rgba(255, 255, 255, .06);
|
||||
border: 1px solid var(--rand);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.abgebrochen__unter {
|
||||
margin: 0; padding: 0 17px 4px;
|
||||
font-size: .8rem; color: var(--text-still); max-width: 72ch;
|
||||
}
|
||||
|
||||
.abgebrochen__liste {
|
||||
display: grid; gap: 10px;
|
||||
/* Mehrspaltig, sobald Platz ist: Abgebrochenes liest man am Stück,
|
||||
nicht einzeln -- eine lange Spalte zwänge zum Scrollen durch etwas,
|
||||
das man ohnehin nur überfliegt. */
|
||||
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
||||
padding: 10px 17px 17px;
|
||||
}
|
||||
|
||||
.abbruch-karte {
|
||||
padding: 13px 15px;
|
||||
border: 1px solid var(--rand);
|
||||
border-radius: 12px;
|
||||
background: rgba(255, 255, 255, .028);
|
||||
}
|
||||
.abbruch-karte__kopf {
|
||||
display: flex; align-items: baseline; gap: 9px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
/* Durchgestrichen sagt "gilt nicht mehr" schneller als jedes Wort --
|
||||
und bleibt lesbar, anders als bloßes Ausgrauen. */
|
||||
.abbruch-karte__titel {
|
||||
margin: 0; flex: 1;
|
||||
font-size: .92rem; font-weight: 600;
|
||||
color: var(--text-leise);
|
||||
text-decoration: line-through;
|
||||
text-decoration-color: color-mix(in srgb, var(--text-still) 70%, transparent);
|
||||
}
|
||||
/* Wo es stand, als es abgebrochen wurde. */
|
||||
.abbruch-karte__vorher {
|
||||
flex: none;
|
||||
padding: 2px 8px; border-radius: 999px;
|
||||
font-size: .68rem; font-weight: 700;
|
||||
letter-spacing: .04em; text-transform: uppercase;
|
||||
color: var(--text-still);
|
||||
background: rgba(255, 255, 255, .05);
|
||||
border: 1px solid var(--rand);
|
||||
}
|
||||
.abbruch-karte__grund {
|
||||
margin: 0 0 7px;
|
||||
font-size: .84rem; color: var(--text-leise);
|
||||
/* Der Grund ist der eigentliche Inhalt dieser Karte -- er steht in
|
||||
normaler Textfarbe, während der Titel durchgestrichen zurücktritt. */
|
||||
}
|
||||
.abbruch-karte__wer {
|
||||
margin: 0 0 9px;
|
||||
font-size: .74rem; color: var(--text-still);
|
||||
}
|
||||
.abbruch-karte .schritt { font-size: .8rem; padding: 7px 13px; }
|
||||
|
||||
/* ---------- Der Dialog --------------------------------------------------- */
|
||||
|
||||
.dialog__unter {
|
||||
margin: -4px 0 14px;
|
||||
font-size: .85rem; color: var(--text-still);
|
||||
max-width: 60ch; line-height: 1.5;
|
||||
}
|
||||
|
||||
#abbruch-grund {
|
||||
width: 100%;
|
||||
resize: vertical;
|
||||
min-height: 72px;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.abgebrochen__kopf::before { transition: none; }
|
||||
}
|
||||
|
||||
/* Ohne Farbverläufe trägt die Kante allein. Der Durchstrich bleibt --
|
||||
er ist hier das eigentliche Merkmal und keine Deko. */
|
||||
@media (forced-colors: active) {
|
||||
.abgebrochen, .abbruch-karte { border: 1px solid CanvasText; }
|
||||
.abgebrochen__zahl, .abbruch-karte__vorher { border: 1px solid CanvasText; }
|
||||
}
|
||||
|
||||
@@ -672,3 +672,133 @@
|
||||
bleibt das Unterscheidungsmerkmal einer pausierten Serie. */
|
||||
.k-serie[data-aktiv="nein"] { opacity: 1; border-style: dashed; }
|
||||
}
|
||||
|
||||
/* =====================================================================
|
||||
WER IST NOCH DABEI — die Teilnehmerwahl
|
||||
=====================================================================
|
||||
|
||||
Vorgeschichte (05.09.2026, Filipe mit Bildschirmfoto): *"das soll
|
||||
viel besser aussehen bitte und dass soll für jeden verfügbar sein."*
|
||||
|
||||
Auf dem Bild klebten die Namen aneinander -- "DogfatherDogFather",
|
||||
"CigdemManager", "BananaStiftScout". Zwei Fehler auf einmal:
|
||||
|
||||
1. DAS CSS WAR GAR NICHT DA. Die Wahl benutzte `.wahl__knopf` aus
|
||||
dateien.css -- und kalender.html bindet dateien.css nicht ein.
|
||||
Ohne `display:inline-flex` und `gap` stehen zwei <span> eben
|
||||
direkt nebeneinander. Vierzig grüne Prüfungen zur Teilnehmerwahl
|
||||
hatten das nicht gemerkt: Keine davon hat je gemessen, ob es auch
|
||||
AUSSIEHT wie gedacht. Deshalb gibt es jetzt eigene Klassen im
|
||||
eigenen Stylesheet -- eine Seite, die ihr CSS selbst mitbringt,
|
||||
kann es nicht verlieren.
|
||||
|
||||
2. DIE ROLLE STAND AN JEDEM NAMEN. Auch mit Abstand bliebe es eine
|
||||
lange graue Wurst, in der man niemanden findet. Jetzt tragen die
|
||||
GRUPPEN die Rolle ("Manager", "Scouts"), die Namen darunter nur
|
||||
noch den Namen. Das Wort steht einmal statt neunmal, und man
|
||||
sucht nicht mehr, sondern springt zur richtigen Gruppe.
|
||||
|
||||
AUGENSCHONEND (Projektregel): gedeckte Rollentöne, kein Neon, keine
|
||||
harten Weißflächen. Ausgewählt wird über Rand, Häkchen UND Schrift
|
||||
erkennbar -- nicht über Farbe allein, sonst ist es für
|
||||
Farbfehlsichtige nicht zu unterscheiden. */
|
||||
|
||||
.k-dabei { margin-top: 6px; }
|
||||
|
||||
.k-dabei__gruppe + .k-dabei__gruppe { margin-top: 12px; }
|
||||
|
||||
.k-dabei__titel {
|
||||
margin: 0 0 6px;
|
||||
font-size: .68rem; font-weight: 700;
|
||||
letter-spacing: .08em; text-transform: uppercase;
|
||||
color: var(--text-still);
|
||||
display: flex; align-items: center; gap: 7px;
|
||||
}
|
||||
/* Der Punkt trägt die Rollenfarbe -- ein ruhiger Anker fürs Auge,
|
||||
ohne die ganze Überschrift einzufärben. */
|
||||
.k-dabei__punkt {
|
||||
width: 7px; height: 7px; border-radius: 50%;
|
||||
background: var(--r, var(--text-still)); flex: none;
|
||||
}
|
||||
|
||||
.k-dabei__liste { display: flex; flex-wrap: wrap; gap: 7px; }
|
||||
|
||||
.k-dabei__knopf {
|
||||
--r: rgba(255, 255, 255, .5);
|
||||
display: inline-flex; align-items: center; gap: 9px;
|
||||
/* 40px Mindesthöhe: am Handy mit dem Daumen sicher zu treffen. */
|
||||
min-height: 40px;
|
||||
padding: 5px 14px 5px 6px;
|
||||
cursor: pointer; font: inherit;
|
||||
color: var(--text-leise);
|
||||
background: rgba(255, 255, 255, .035);
|
||||
border: 1px solid var(--rand); border-radius: 999px;
|
||||
transition: color .16s ease, border-color .16s ease, background .16s ease;
|
||||
}
|
||||
.k-dabei__knopf:hover {
|
||||
color: var(--text);
|
||||
border-color: rgba(255, 255, 255, .24);
|
||||
background: rgba(255, 255, 255, .06);
|
||||
}
|
||||
.k-dabei__knopf:focus-visible {
|
||||
outline: 2px solid var(--r); outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* Die Initiale. Ersetzt ein Bild, das es hier nicht gibt, und macht
|
||||
aus einer Textzeile ein Gesicht, das man wiedererkennt. */
|
||||
.k-dabei__zeichen {
|
||||
flex: none;
|
||||
width: 28px; height: 28px; border-radius: 50%;
|
||||
display: grid; place-items: center;
|
||||
font-size: .76rem; font-weight: 700; line-height: 1;
|
||||
color: var(--r);
|
||||
background: color-mix(in srgb, var(--r) 14%, transparent);
|
||||
border: 1px solid color-mix(in srgb, var(--r) 34%, transparent);
|
||||
}
|
||||
.k-dabei__name { font-size: .86rem; font-weight: 550; }
|
||||
|
||||
/* Das Häkchen steht NEBEN der Farbe, nicht statt ihr. Wer Farben
|
||||
schlecht unterscheidet, sieht trotzdem sofort, wer dabei ist. */
|
||||
.k-dabei__haken {
|
||||
flex: none; width: 14px; height: 14px;
|
||||
opacity: 0; transform: scale(.7);
|
||||
transition: opacity .16s ease, transform .16s ease;
|
||||
}
|
||||
.k-dabei__haken path { stroke: var(--r); stroke-width: 2.4; fill: none;
|
||||
stroke-linecap: round; stroke-linejoin: round; }
|
||||
|
||||
.k-dabei__knopf[data-an="ja"] {
|
||||
color: var(--text); font-weight: 600;
|
||||
border-color: color-mix(in srgb, var(--r) 55%, transparent);
|
||||
background: color-mix(in srgb, var(--r) 13%, transparent);
|
||||
}
|
||||
.k-dabei__knopf[data-an="ja"] .k-dabei__zeichen {
|
||||
color: #12151b;
|
||||
background: var(--r);
|
||||
border-color: var(--r);
|
||||
}
|
||||
.k-dabei__knopf[data-an="ja"] .k-dabei__haken { opacity: 1; transform: none; }
|
||||
|
||||
/* Rollentöne. Dieselben wie in personen.css -- eine Farbe pro Rolle im
|
||||
ganzen Workspace, sonst muss man sie auf jeder Seite neu lernen. */
|
||||
.k-dabei__gruppe[data-rolle="admin"],
|
||||
.k-dabei__knopf[data-rolle="admin"] { --r: #e8c07d; }
|
||||
.k-dabei__gruppe[data-rolle="manager"],
|
||||
.k-dabei__knopf[data-rolle="manager"] { --r: #8a76ff; }
|
||||
.k-dabei__gruppe[data-rolle="scout"],
|
||||
.k-dabei__knopf[data-rolle="scout"] { --r: #5fc99a; }
|
||||
.k-dabei__gruppe[data-rolle="creator"],
|
||||
.k-dabei__knopf[data-rolle="creator"] { --r: #3fbdf5; }
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.k-dabei__knopf, .k-dabei__haken { transition: none; }
|
||||
}
|
||||
|
||||
/* Ohne Farbverläufe trägt die Kante allein -- und das Häkchen, das
|
||||
hier zum eigentlichen Merkmal wird. */
|
||||
@media (forced-colors: active) {
|
||||
.k-dabei__knopf { border: 1px solid CanvasText; }
|
||||
.k-dabei__knopf[data-an="ja"] { border-width: 2px; }
|
||||
.k-dabei__zeichen { border: 1px solid CanvasText; }
|
||||
.k-dabei__haken path { stroke: CanvasText; }
|
||||
}
|
||||
|
||||
@@ -2785,6 +2785,7 @@ body.start #protokoll {
|
||||
flex: 0 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.sicht__schild {
|
||||
flex: none;
|
||||
/* 0,75 rem = 12 px. Nicht kleiner: Unter 11,5 px meldet die
|
||||
@@ -2879,6 +2880,47 @@ body[data-fremde-sicht="ja"]::after {
|
||||
.sicht { padding: 3px 4px; }
|
||||
.wer__name { display: none; }
|
||||
}
|
||||
|
||||
/* SEHR SCHMAL: die Kopfleiste darf umbrechen (05.09.2026).
|
||||
|
||||
Die Rechnung darueber ging genau auf, solange rechts vier Dinge
|
||||
standen. Mit der Glocke sind es fuenf, und bei 320 px (iPhone SE)
|
||||
passte es nicht mehr: Die Lupe des Suchknopfes lag danach mitten auf
|
||||
dem Sicht-Umschalter -- ein Knopf, der auf zwoelf Seiten ins Leere
|
||||
tippt. Gemeldet von pruef-handy, bevor es jemand gesehen hat.
|
||||
|
||||
WARUM UMBRECHEN und nicht weiter schrumpfen: Jedes Stueck hier hat
|
||||
seine Mindestgroesse aus einem guten Grund -- 104 px, damit der
|
||||
Umschalter mit dem Daumen zu treffen ist, und die Glocke ist bereits
|
||||
auf das blosse Zeichen zusammengefallen. Wer hier weiter kuerzt,
|
||||
tauscht ein sichtbares Problem gegen ein unsichtbares: Knoepfe, die
|
||||
zwar nebeneinander liegen, aber zu klein zum Treffen sind.
|
||||
|
||||
Eine zweite Zeile auf einem 320-Pixel-Schirm kostet 44 Pixel Hoehe.
|
||||
Das ist der guenstigste Preis, der hier zu haben ist -- und er faellt
|
||||
nur auf Geraeten an, die wirklich so schmal sind. */
|
||||
@media (max-width: 380px) {
|
||||
.kopfleiste { flex-wrap: wrap; row-gap: 8px; }
|
||||
/* Die Marke nimmt die erste Zeile ganz ein, die Bedienelemente
|
||||
ruecken geschlossen in die zweite. Ohne das brechen sie mitten
|
||||
zwischen Suche und Glocke um. */
|
||||
.kopfleiste > .marke { flex: 1 0 100%; }
|
||||
.kopfleiste__rechts {
|
||||
width: 100%; justify-content: flex-end;
|
||||
/* AUCH DIE ZWEITE ZEILE DARF UMBRECHEN. Ohne das reichten die
|
||||
294 Pixel weiterhin nicht: Umschalter (104) + Suche (47) +
|
||||
Glocke (37) + Plakette + Abmelden sind zusammen mehr. Die
|
||||
Elemente wurden dann zwar nicht mehr ueberdeckt, aber der
|
||||
Umschalter ragte aus seinem eigenen Kasten heraus -- ein
|
||||
stilles Ueberlaufen, das keine Pruefung meldet und trotzdem
|
||||
falsch aussieht. */
|
||||
flex-wrap: wrap; row-gap: 6px;
|
||||
}
|
||||
/* Und der Umschalter behaelt seine Breite, statt gequetscht zu
|
||||
werden: Sein Inhalt kann ohnehin nicht unter 104 px, also waere
|
||||
jedes Schrumpfen nur ein Ueberlauf mit anderem Namen. */
|
||||
.sicht { flex: 0 0 auto; }
|
||||
}
|
||||
@media (forced-colors: active) {
|
||||
body[data-fremde-sicht="ja"]::after { border-color: Highlight; }
|
||||
}
|
||||
@@ -3012,3 +3054,100 @@ body[data-fremde-sicht="ja"]::after {
|
||||
@media (forced-colors: active) {
|
||||
.kachel::after { display: none; }
|
||||
}
|
||||
|
||||
/* ---------- Der Benachrichtigungs-Knopf ------------------------------
|
||||
Wunsch Filipe (05.09.2026): ein Knopf wie "Benachrichtigungen aus",
|
||||
auf jeder Seite erreichbar.
|
||||
|
||||
Gebaut wie `.abmelden` daneben, damit die Kopfleiste eine Sprache
|
||||
spricht -- und mit denselben Umbruchregeln: Auf schmalen Schirmen
|
||||
bleibt nur die Glocke stehen, der Text weicht. Genau daran ist die
|
||||
Kopfleiste am 31.08.2026 schon einmal gescheitert (214 px aus dem
|
||||
Bild); deshalb hier von vornherein `min-width: 0` und ein Text, der
|
||||
verschwinden darf. */
|
||||
.glocke {
|
||||
display: inline-flex; align-items: center; gap: 7px;
|
||||
min-width: 0; flex: none;
|
||||
padding: 7px 13px;
|
||||
background: rgba(255, 255, 255, .04);
|
||||
border: 1px solid var(--rand);
|
||||
border-radius: 999px;
|
||||
color: var(--text-leise);
|
||||
font: inherit; font-size: .82rem;
|
||||
cursor: pointer;
|
||||
transition: border-color var(--tempo), background var(--tempo), color var(--tempo);
|
||||
}
|
||||
.glocke:hover:not(:disabled) { background: rgba(255, 255, 255, .07); border-color: var(--rand-hell); }
|
||||
.glocke:disabled { opacity: .55; cursor: not-allowed; }
|
||||
|
||||
.glocke__zeichen { width: 17px; height: 17px; flex: none; }
|
||||
.glocke__zeichen path {
|
||||
fill: none; stroke: currentColor; stroke-width: 1.7;
|
||||
stroke-linecap: round; stroke-linejoin: round;
|
||||
}
|
||||
.glocke__strich { opacity: 0; }
|
||||
|
||||
/* AN: die Glocke bekommt Farbe, der Strich bleibt weg. */
|
||||
.glocke[data-lage="an"] {
|
||||
color: var(--akzent);
|
||||
border-color: color-mix(in srgb, var(--akzent) 42%, transparent);
|
||||
background: color-mix(in srgb, var(--akzent) 10%, transparent);
|
||||
}
|
||||
|
||||
/* AUS: durchgestrichene Glocke -- man sieht auf einen Blick, dass
|
||||
nichts kommt, ohne den Text zu lesen. */
|
||||
.glocke[data-lage="aus"] .glocke__strich { opacity: .85; }
|
||||
|
||||
/* Die drei Faelle, in denen der Knopf nichts schalten kann, sind
|
||||
gedaempft und tragen ebenfalls den Strich: Sie sind naeher an "aus"
|
||||
als an "an", und genau so soll es aussehen. */
|
||||
.glocke[data-lage="iphone"], .glocke[data-lage="verboten"],
|
||||
.glocke[data-lage="geht-nicht"] {
|
||||
color: var(--text-still);
|
||||
}
|
||||
.glocke[data-lage="iphone"] .glocke__strich,
|
||||
.glocke[data-lage="verboten"] .glocke__strich,
|
||||
.glocke[data-lage="geht-nicht"] .glocke__strich { opacity: .7; }
|
||||
|
||||
/* Schmale Schirme: nur die Glocke. Der Text steht weiterhin im
|
||||
aria-label, Vorleseprogramme verlieren also nichts.
|
||||
|
||||
900 PIXEL, NICHT 760 (korrigiert 05.09.2026). Die erste Fassung
|
||||
nahm 760 -- und ein Tablet hochkant hat 768. Dort stand der Text
|
||||
also noch, die Glocke war rund 200 statt 37 Pixel breit, und der
|
||||
Abmelden-Knopf wurde um bis zu 15 Pixel aus dem Bild geschoben. Auf
|
||||
vier Seiten, gemeldet von pruef-breiten.
|
||||
|
||||
Die Grenze liegt jetzt auf derselben Stufe wie die uebrigen Regeln
|
||||
fuer die enge Kopfleiste (der Sicht-Umschalter wird ab 900 px
|
||||
ebenfalls schmaler). Eine eigene Schwelle daneben waere genau die
|
||||
Art Zahl, die beim naechsten Mal wieder knapp danebenliegt. */
|
||||
@media (max-width: 900px) {
|
||||
.glocke { padding: 7px 9px; }
|
||||
.glocke__text { display: none; }
|
||||
}
|
||||
|
||||
/* ERKLÄRSATZ UNTER EINER ÜBERSCHRIFT — der Grundwert.
|
||||
|
||||
Gefunden am 05.09.2026 von pruef-css-klassen.mjs, beim allerersten
|
||||
Lauf. `.block__frage` war viermal fast gleich definiert -- in
|
||||
automation.css, calls.css, report.css und wissen.css -- und wurde
|
||||
auf content.html benutzt, das keine dieser vier Dateien lädt. Der
|
||||
Absatz stand dort seit jeher unformatiert.
|
||||
|
||||
Genau dasselbe Muster wie bei der Teilnehmerwahl am selben Tag:
|
||||
Das CSS existiert, es sieht im Editor richtig aus, und auf der Seite
|
||||
kommt nichts an. Solange es nur in Seiten-Stylesheets steht, findet
|
||||
es niemand.
|
||||
|
||||
Hier steht deshalb der Grundwert, den jede Seite bekommt. Die vier
|
||||
Seiten-Stylesheets werden NACH start.css geladen und behalten damit
|
||||
ihre Feinheiten (12, 13 oder 14 Pixel Abstand) -- sie bleiben
|
||||
unverändert. Neu ist nur, dass eine Seite ohne eigene Fassung nicht
|
||||
mehr leer ausgeht. */
|
||||
.block__frage {
|
||||
margin: 0 0 13px;
|
||||
font-size: .85rem;
|
||||
color: var(--text-still);
|
||||
max-width: 76ch;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,17 @@
|
||||
const WEITER = { offen: 'arbeit', arbeit: 'review', review: 'erledigt' };
|
||||
const ZURUECK = { arbeit: 'offen', review: 'arbeit', erledigt: 'review' };
|
||||
|
||||
/* Wer darf abbrechen (05.09.2026): *"nur ich die manager und scouts
|
||||
sollen auch die aufgaben abbrechen können."* Ein Creator nicht.
|
||||
|
||||
Dieselbe Menge steht im Server (DARF_ABBRECHEN in
|
||||
workspace-aufgaben.js) und DORT sitzt die Sperre. Hier geht es nur
|
||||
darum, niemandem einen Knopf hinzustellen, der dann 403 sagt. */
|
||||
const DARF_ABBRECHEN = new Set(['admin', 'manager', 'scout']);
|
||||
const STATUS_NAME = {
|
||||
offen: 'Offen', arbeit: 'In Arbeit', review: 'Review', erledigt: 'Erledigt',
|
||||
};
|
||||
|
||||
const $ = (id) => document.getElementById(id);
|
||||
/* ORTSZEIT, nicht UTC -- sonst gilt nachts eine heute faellige Aufgabe
|
||||
noch nicht als faellig, und "Heute faellig" zeigt den Vortag. */
|
||||
@@ -181,6 +192,20 @@
|
||||
const schritte = el('div', 'karte__schritte');
|
||||
if (ZURUECK[a.status]) schritte.append(knopf('◀ zurück', a, ZURUECK[a.status]));
|
||||
if (WEITER[a.status]) schritte.append(knopf(beschriftung(a.status), a, WEITER[a.status]));
|
||||
/* ABBRECHEN — in JEDEM Status (05.09.2026), auch im erledigten:
|
||||
Auch etwas Abgehaktes kann sich als hinfällig herausstellen.
|
||||
|
||||
Nur DogFather, Manager und Scouts. Ein Creator sieht den Knopf
|
||||
gar nicht erst -- die eigentliche Sperre sitzt aber im Server,
|
||||
hier geht es nur darum, niemandem etwas anzubieten, das dann
|
||||
abgelehnt wird. */
|
||||
if (DARF_ABBRECHEN.has(ich.rolle)) {
|
||||
const ab = el('button', 'schritt schritt--ab', 'abbrechen');
|
||||
ab.type = 'button';
|
||||
ab.setAttribute('aria-label', `„${a.titel}" abbrechen`);
|
||||
ab.addEventListener('click', () => abbruchOeffnen(a));
|
||||
schritte.append(ab);
|
||||
}
|
||||
if (schritte.childElementCount) k.append(schritte);
|
||||
|
||||
/* Rückmeldungen ("Aufgaben & Feedback" aus dem Konzept). Der Knopf
|
||||
@@ -202,6 +227,127 @@
|
||||
return k;
|
||||
}
|
||||
|
||||
/* ---------- Abbrechen ----------------------------------------------------
|
||||
|
||||
Wunsch 05.09.2026: *"ich will dass man in jedem status die aufgaben
|
||||
auch abbrechen kann."*
|
||||
|
||||
MIT PFLICHT-GRUND. Ohne ihn weiss in vier Wochen niemand mehr,
|
||||
warum etwas wegfiel -- und dann ist ein Abbruch von einem Loeschen
|
||||
nicht mehr zu unterscheiden. Der Grund steht spaeter an der
|
||||
Aufgabe, zusammen mit dem Namen dessen, der abgebrochen hat.
|
||||
|
||||
Ein <dialog> und kein confirm(): confirm blockiert die ganze Seite,
|
||||
nimmt kein Textfeld auf und sieht auf jedem Rechner anders aus. */
|
||||
|
||||
function abbruchOeffnen(a) {
|
||||
const dlg = $('abbruch-dialog');
|
||||
if (!dlg) return;
|
||||
dlg.dataset.id = String(a.id);
|
||||
$('abbruch-titel').textContent = `„${a.titel}" abbrechen?`;
|
||||
/* Sagt, was gleich passiert -- und was NICHT passiert. Die Sorge
|
||||
bei so einem Knopf ist immer, ob etwas verloren geht. */
|
||||
$('abbruch-was').textContent = `Die Aufgabe steht danach unter „Abgebrochen" `
|
||||
+ `statt in der Spalte ${STATUS_NAME[a.status] || a.status}. `
|
||||
+ `Sie wird nicht gelöscht und lässt sich jederzeit wieder aufnehmen.`;
|
||||
$('abbruch-grund').value = '';
|
||||
$('abbruch-fehler').textContent = '';
|
||||
dlg.showModal();
|
||||
$('abbruch-grund').focus();
|
||||
}
|
||||
|
||||
async function abbruchSenden(e) {
|
||||
e.preventDefault();
|
||||
const dlg = $('abbruch-dialog');
|
||||
const id = Number(dlg.dataset.id);
|
||||
const grund = $('abbruch-grund').value.trim();
|
||||
if (grund.length < 3) {
|
||||
$('abbruch-fehler').textContent = 'Bitte kurz sagen, warum.';
|
||||
$('abbruch-grund').focus();
|
||||
return;
|
||||
}
|
||||
const knopf = $('abbruch-ok');
|
||||
knopf.disabled = true;
|
||||
try {
|
||||
const antwort = await hole(`/workspace/api/aufgaben/${id}/abbrechen`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ grund }),
|
||||
});
|
||||
if (!antwort.ok) {
|
||||
const d = await antwort.json().catch(() => ({}));
|
||||
$('abbruch-fehler').textContent = d.fehler || 'Hat nicht geklappt.';
|
||||
return;
|
||||
}
|
||||
dlg.close();
|
||||
await laden();
|
||||
} catch { /* umgeleitet */ } finally { knopf.disabled = false; }
|
||||
}
|
||||
|
||||
async function wiederAufnehmen(a, b) {
|
||||
b.disabled = true;
|
||||
melde('');
|
||||
try {
|
||||
const antwort = await hole(`/workspace/api/aufgaben/${a.id}/wiederaufnehmen`,
|
||||
{ method: 'POST' });
|
||||
if (!antwort.ok) {
|
||||
const d = await antwort.json().catch(() => ({}));
|
||||
melde(d.fehler || 'Hat nicht geklappt.');
|
||||
b.disabled = false;
|
||||
return;
|
||||
}
|
||||
await laden();
|
||||
} catch { b.disabled = false; }
|
||||
}
|
||||
|
||||
/** Der Bereich UNTER dem Brett -- zugeklappt, mit Anzahl im Titel.
|
||||
*
|
||||
* Bewusst keine fuenfte Spalte: Am Handy werden dadurch alle fuenf
|
||||
* so schmal, dass keine mehr lesbar ist. Und abgebrochene Aufgaben
|
||||
* sind nichts, was man taeglich sieht -- aber sie duerfen auch nicht
|
||||
* verschwinden, sonst waere es Loeschen mit Zwischenschritt. */
|
||||
function abgebrocheneZeigen(alle) {
|
||||
const kasten = $('abgebrochen');
|
||||
if (!kasten) return;
|
||||
const eigene = alle.filter((a) => a.status === 'abgebrochen');
|
||||
|
||||
if (!eigene.length) { kasten.hidden = true; return; }
|
||||
kasten.hidden = false;
|
||||
|
||||
$('abgebrochen-zahl').textContent = String(eigene.length);
|
||||
const liste = $('abgebrochen-liste');
|
||||
liste.textContent = '';
|
||||
|
||||
for (const a of eigene) {
|
||||
const k = el('article', 'abbruch-karte');
|
||||
const kopf = el('div', 'abbruch-karte__kopf');
|
||||
kopf.append(el('h3', 'abbruch-karte__titel', a.titel));
|
||||
/* Wo es stand, als es abgebrochen wurde. "Im Review abgebrochen"
|
||||
ist eine andere Aussage als "nie angefangen". */
|
||||
if (a.status_vorher) {
|
||||
kopf.append(el('span', 'abbruch-karte__vorher',
|
||||
STATUS_NAME[a.status_vorher] || a.status_vorher));
|
||||
}
|
||||
k.append(kopf);
|
||||
|
||||
if (a.abbruch_grund) k.append(el('p', 'abbruch-karte__grund', a.abbruch_grund));
|
||||
|
||||
const wer = [];
|
||||
if (a.abbruch_von_name) wer.push(a.abbruch_von_name);
|
||||
if (a.abgebrochen_am) wer.push(datumHuebsch(a.abgebrochen_am.slice(0, 10)));
|
||||
if (wer.length) k.append(el('p', 'abbruch-karte__wer', wer.join(' · ')));
|
||||
|
||||
if (DARF_ABBRECHEN.has(ich.rolle)) {
|
||||
const zurueck = el('button', 'schritt', 'wieder aufnehmen ↩');
|
||||
zurueck.type = 'button';
|
||||
zurueck.setAttribute('aria-label', `„${a.titel}" wieder aufnehmen`);
|
||||
zurueck.addEventListener('click', () => wiederAufnehmen(a, zurueck));
|
||||
k.append(zurueck);
|
||||
}
|
||||
liste.append(k);
|
||||
}
|
||||
}
|
||||
|
||||
const beschriftung = (status) => ({
|
||||
offen: 'starten ▶', arbeit: 'zum Review ▶', review: 'erledigt ▶',
|
||||
})[status];
|
||||
@@ -262,7 +408,11 @@
|
||||
let nurCreator = 0; /* 0 = alle */
|
||||
let alleAufgaben = [];
|
||||
|
||||
const istOffen = (a) => a.status !== 'erledigt';
|
||||
/* Abgebrochenes zählt NICHT als offen (05.09.2026). Ohne diese
|
||||
Ergänzung stünde eine abgebrochene Aufgabe weiter in "3 Aufgaben
|
||||
stehen an" und in "Überfällig" -- und würde als etwas gemahnt, das
|
||||
ausdrücklich niemand mehr tun soll. */
|
||||
const istOffen = (a) => a.status !== 'erledigt' && a.status !== 'abgebrochen';
|
||||
const istFaellig = (a) => istOffen(a) && a.frist && a.frist < heute;
|
||||
const istHeute = (a) => istOffen(a) && a.frist === heute;
|
||||
const istMeine = (a) => a.verantwortlich_id === ich.id;
|
||||
@@ -395,6 +545,13 @@
|
||||
ausschnitteBauen(alleAufgaben);
|
||||
creatorReiheBauen(alleAufgaben);
|
||||
zeichne(alleAufgaben.filter(passt));
|
||||
/* Der Bereich folgt dem Creator-Filter mit -- wer sich eine Person
|
||||
herausgreift, will auch nur deren Abgebrochenes sehen. Die
|
||||
Ausschnitte (heute/überfällig) gelten dagegen nicht: Sie fragen
|
||||
nach Fristen, und eine abgebrochene Aufgabe hat keine mehr. */
|
||||
abgebrocheneZeigen(nurCreator
|
||||
? alleAufgaben.filter((a) => a.creator_id === nurCreator)
|
||||
: alleAufgaben);
|
||||
}
|
||||
|
||||
function zeichne(aufgaben) {
|
||||
@@ -543,6 +700,12 @@
|
||||
|
||||
$('b-abbrechen').addEventListener('click', () => dialog.close());
|
||||
|
||||
/* Der Abbruch-Dialog. Am submit des Formulars und nicht am Knopf:
|
||||
So löst auch Enter im Textfeld aus, und Esc schließt von selbst
|
||||
(beides bringt <dialog> mit, wenn man es lässt). */
|
||||
$('abbruch-form').addEventListener('submit', abbruchSenden);
|
||||
$('abbruch-zurueck').addEventListener('click', () => $('abbruch-dialog').close());
|
||||
|
||||
$('b-speichern').addEventListener('click', async () => {
|
||||
const daten = {
|
||||
titel: $('b-titel').value,
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
/* =====================================================================
|
||||
Der Benachrichtigungs-Knopf — auf JEDER Seite, in der Kopfleiste.
|
||||
|
||||
Wunsch Filipe (05.09.2026), mit dem Bild eines "Benachrichtigungen
|
||||
aus"-Knopfes: *"ich will auch sowas und dass es perfekt funktioniert
|
||||
auf der seite fuer jeden."*
|
||||
|
||||
---------------------------------------------------------------------
|
||||
WARUM IN DER KOPFLEISTE UND NICHT AUF EINER EINSTELLUNGSSEITE
|
||||
|
||||
Dasselbe Argument wie bei der Suche, die hier schon so gebaut ist:
|
||||
Was man nur auf einer Extraseite findet, benutzt niemand. Der Knopf
|
||||
sagt jederzeit, woran man ist -- Glocke an oder durchgestrichen.
|
||||
|
||||
---------------------------------------------------------------------
|
||||
DER KNOPF SAGT IMMER DIE WAHRHEIT, auch wenn sie unbequem ist
|
||||
|
||||
Es gibt mehr Zustaende als "an" und "aus", und jeder braucht einen
|
||||
anderen Satz. Ein Knopf, der bei einem abgelehnten Recht einfach
|
||||
nichts tut, ist der sichere Weg zu "das funktioniert nicht":
|
||||
|
||||
geht nicht Der Browser kann kein Push (aeltere Fassungen).
|
||||
iPhone Safari kann es NUR nach "Zum Home-Bildschirm".
|
||||
Im Reiter gibt es die Berechtigung ueberhaupt nicht.
|
||||
verboten Schon einmal abgelehnt. Das laesst sich per
|
||||
JavaScript NICHT zuruecknehmen -- man muss es in den
|
||||
Browsereinstellungen tun. Also wird genau das gesagt.
|
||||
aus / an der Normalfall.
|
||||
===================================================================== */
|
||||
(() => {
|
||||
'use strict';
|
||||
|
||||
/* Nur im Workspace, und nicht auf der Anmeldeseite -- dort ist
|
||||
niemand angemeldet, den man benachrichtigen koennte. */
|
||||
if (!location.pathname.startsWith('/workspace/')) return;
|
||||
if (/\/workspace\/(index\.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 kannPush = 'serviceWorker' in navigator && 'PushManager' in window
|
||||
&& 'Notification' in window;
|
||||
|
||||
/* Laeuft die Seite als installierte App? Auf dem iPhone ist das die
|
||||
Bedingung dafuer, dass es ueberhaupt geht. */
|
||||
const alsApp = window.matchMedia('(display-mode: standalone)').matches
|
||||
|| window.navigator.standalone === true;
|
||||
const istApple = /iPad|iPhone|iPod/.test(navigator.userAgent)
|
||||
|| (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
|
||||
|
||||
let anmeldung = null;
|
||||
let knopf = null;
|
||||
|
||||
const b64uZuBytes = (s) => {
|
||||
const roh = atob(String(s).replace(/-/g, '+').replace(/_/g, '/')
|
||||
+ '='.repeat((4 - String(s).length % 4) % 4));
|
||||
return Uint8Array.from(roh, (c) => c.charCodeAt(0));
|
||||
};
|
||||
|
||||
const bytesZuB64u = (buf) => btoa(String.fromCharCode(...new Uint8Array(buf)))
|
||||
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
||||
|
||||
/** Ein Name, an dem man sein Geraet in der Liste wiedererkennt. */
|
||||
function geraetName() {
|
||||
const u = navigator.userAgent;
|
||||
const os = /Windows/.test(u) ? 'Windows'
|
||||
: /Android/.test(u) ? 'Android'
|
||||
: istApple ? (/iPad/.test(u) ? 'iPad' : 'iPhone')
|
||||
: /Mac/.test(u) ? 'Mac' : /Linux/.test(u) ? 'Linux' : 'Gerät';
|
||||
const browser = /Edg\//.test(u) ? 'Edge'
|
||||
: /Chrome\//.test(u) ? 'Chrome'
|
||||
: /Firefox\//.test(u) ? 'Firefox'
|
||||
: /Safari\//.test(u) ? 'Safari' : 'Browser';
|
||||
return `${os} · ${browser}${alsApp ? ' (App)' : ''}`;
|
||||
}
|
||||
|
||||
/** Was ist gerade der Fall? */
|
||||
function lage() {
|
||||
if (!kannPush) return 'geht-nicht';
|
||||
if (istApple && !alsApp) return 'iphone';
|
||||
if (Notification.permission === 'denied') return 'verboten';
|
||||
return anmeldung ? 'an' : 'aus';
|
||||
}
|
||||
|
||||
const TEXTE = {
|
||||
'geht-nicht': { text: 'Hier nicht möglich', titel: 'Dieser Browser kann keine Benachrichtigungen.' },
|
||||
iphone: {
|
||||
text: 'Erst zum Home-Bildschirm',
|
||||
titel: 'Auf dem iPhone gehen Benachrichtigungen nur, wenn die Seite '
|
||||
+ 'zum Home-Bildschirm hinzugefügt wurde: Teilen-Knopf → „Zum Home-Bildschirm“. '
|
||||
+ 'Danach hier noch einmal antippen.',
|
||||
},
|
||||
verboten: {
|
||||
text: 'Vom Browser blockiert',
|
||||
titel: 'Du hast Benachrichtigungen für diese Seite einmal abgelehnt. Das lässt sich '
|
||||
+ 'nur in den Browsereinstellungen wieder erlauben — bei Chrome über das Symbol '
|
||||
+ 'links in der Adresszeile.',
|
||||
},
|
||||
aus: { text: 'Benachrichtigungen aus', titel: 'Antippen, um Erinnerungen zu bekommen.' },
|
||||
an: { text: 'Benachrichtigungen an', titel: 'Antippen, um sie auf diesem Gerät wieder abzuschalten.' },
|
||||
};
|
||||
|
||||
function zeichnen() {
|
||||
if (!knopf) return;
|
||||
const l = lage();
|
||||
const t = TEXTE[l];
|
||||
knopf.dataset.lage = l;
|
||||
knopf.title = t.titel;
|
||||
knopf.setAttribute('aria-label', t.titel);
|
||||
knopf.querySelector('.glocke__text').textContent = t.text;
|
||||
/* aria-pressed sagt einem Vorleseprogramm, dass es ein Schalter
|
||||
ist und wie er gerade steht. */
|
||||
knopf.setAttribute('aria-pressed', l === 'an' ? 'true' : 'false');
|
||||
knopf.disabled = l === 'geht-nicht';
|
||||
}
|
||||
|
||||
async function anmelden() {
|
||||
const recht = await Notification.requestPermission();
|
||||
if (recht !== 'granted') { zeichnen(); return; }
|
||||
|
||||
const reg = await navigator.serviceWorker.register('/workspace/sw.js', { scope: '/workspace/' });
|
||||
await navigator.serviceWorker.ready;
|
||||
|
||||
const a = await fetch('/workspace/api/push/schluessel', { credentials: 'same-origin' });
|
||||
const { schluessel } = await a.json();
|
||||
|
||||
const abo = await reg.pushManager.subscribe({
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey: b64uZuBytes(schluessel),
|
||||
});
|
||||
|
||||
const roh = abo.toJSON();
|
||||
await fetch('/workspace/api/push/anmelden', {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
endpunkt: abo.endpoint,
|
||||
p256dh: roh.keys?.p256dh || bytesZuB64u(abo.getKey('p256dh')),
|
||||
auth: roh.keys?.auth || bytesZuB64u(abo.getKey('auth')),
|
||||
geraet: geraetName(),
|
||||
}),
|
||||
});
|
||||
|
||||
anmeldung = abo;
|
||||
zeichnen();
|
||||
|
||||
/* Sofort eine Probe. Das ist der Unterschied zwischen "sollte
|
||||
gehen" und "geht": Wer den Knopf drueckt, will WISSEN, dass es
|
||||
ankommt -- und nicht bis morgen warten. */
|
||||
await fetch('/workspace/api/push/probe', {
|
||||
method: 'POST', credentials: 'same-origin',
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
async function abmelden() {
|
||||
try {
|
||||
if (anmeldung) {
|
||||
await fetch('/workspace/api/push/abmelden', {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ endpunkt: anmeldung.endpoint }),
|
||||
});
|
||||
await anmeldung.unsubscribe();
|
||||
}
|
||||
} catch { /* der Servereintrag ist weg, das genuegt */ }
|
||||
anmeldung = null;
|
||||
zeichnen();
|
||||
}
|
||||
|
||||
async function bauen() {
|
||||
const kopf = document.querySelector('.kopfleiste__rechts');
|
||||
if (!kopf || document.getElementById('glocke')) return;
|
||||
|
||||
knopf = el('button', 'glocke');
|
||||
knopf.type = 'button';
|
||||
knopf.id = 'glocke';
|
||||
|
||||
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
svg.setAttribute('class', 'glocke__zeichen');
|
||||
svg.setAttribute('viewBox', '0 0 24 24');
|
||||
svg.setAttribute('aria-hidden', 'true');
|
||||
svg.innerHTML = '<path class="glocke__koerper" d="M12 4a5 5 0 0 0-5 5v3.5L5.5 15h13L17 12.5V9a5 5 0 0 0-5-5z"/>'
|
||||
/* "kloeppel" ohne Umlaut: Ein ö in einem Klassennamen ist erlaubt,
|
||||
aber es überlebt nicht jede Datei, die zwischendurch in einer
|
||||
anderen Kodierung gespeichert wird -- und im ganzen Haus wird
|
||||
ohnehin "oe"/"ue" geschrieben. */
|
||||
+ '<path class="glocke__kloeppel" d="M10 17.5a2 2 0 0 0 4 0"/>'
|
||||
+ '<path class="glocke__strich" d="M4 4l16 16"/>';
|
||||
knopf.append(svg);
|
||||
knopf.append(el('span', 'glocke__text', '…'));
|
||||
|
||||
/* VOR dem Abmelden-Knopf, nicht dahinter: Abmelden ist die letzte
|
||||
Handlung auf einer Seite und bleibt ganz rechts. */
|
||||
const abmeldenKnopf = kopf.querySelector('.abmelden');
|
||||
if (abmeldenKnopf) kopf.insertBefore(knopf, abmeldenKnopf);
|
||||
else kopf.append(knopf);
|
||||
|
||||
knopf.addEventListener('click', async () => {
|
||||
const l = lage();
|
||||
if (l === 'geht-nicht') return;
|
||||
/* Bei 'iphone' und 'verboten' nichts tun ausser erklaeren -- der
|
||||
Titel steht schon dran, und ein Klick, der nichts bewirkt und
|
||||
nichts sagt, wirkt kaputt. */
|
||||
if (l === 'iphone' || l === 'verboten') { window.alert(TEXTE[l].titel); return; }
|
||||
knopf.disabled = true;
|
||||
try {
|
||||
if (l === 'an') await abmelden(); else await anmelden();
|
||||
} catch (f) {
|
||||
console.warn('[glocke]', f);
|
||||
zeichnen();
|
||||
}
|
||||
knopf.disabled = false;
|
||||
});
|
||||
|
||||
/* Steht hier schon eine Anmeldung? */
|
||||
if (kannPush && Notification.permission === 'granted') {
|
||||
try {
|
||||
const reg = await navigator.serviceWorker.getRegistration('/workspace/');
|
||||
anmeldung = reg ? await reg.pushManager.getSubscription() : null;
|
||||
} catch { anmeldung = null; }
|
||||
}
|
||||
zeichnen();
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', bauen);
|
||||
} else bauen();
|
||||
})();
|
||||
+143
-26
@@ -83,9 +83,37 @@
|
||||
let alleLeute = [];
|
||||
|
||||
const ROLLENKURZ = { admin: 'DogFather', manager: 'Manager', scout: 'Scout', creator: 'Creator' };
|
||||
/* Mehrzahl für die Gruppenüberschriften. "Scout" über vier Namen
|
||||
liest sich falsch -- über der Gruppe steht, was die Leute SIND. */
|
||||
const ROLLENGRUPPE = {
|
||||
admin: 'DogFather', manager: 'Manager', scout: 'Scouts', creator: 'Creator',
|
||||
};
|
||||
const ROLLENFOLGE = ['admin', 'manager', 'scout', 'creator'];
|
||||
|
||||
/** Baut die Schalterreihe. `gewaehlt` sind die Nummern, die an
|
||||
* sein sollen -- beim Bearbeiten die des Termins, sonst nichts. */
|
||||
/** Ein bis zwei Buchstaben, die als Zeichen im Kreis stehen.
|
||||
* "Filipe Fernandes Ribeiro" -> "FR", "Luna" -> "L".
|
||||
* Absichtlich Anfang UND Ende: Bei "Patrick von Hohenstein" wäre
|
||||
* "PV" nichtssagend, "PH" nicht. */
|
||||
function initialen(name) {
|
||||
const teile = String(name || '').trim().split(/\s+/).filter(Boolean);
|
||||
if (!teile.length) return '?';
|
||||
/* [...] statt [0]: Ein Name kann mit einem Zeichen beginnen, das
|
||||
aus zwei Code-Einheiten besteht (Emoji, manche Schriften).
|
||||
name[0] würde davon die Hälfte nehmen und Bruch anzeigen. */
|
||||
const erst = [...teile[0]][0] || '';
|
||||
const letzt = teile.length > 1 ? ([...teile[teile.length - 1]][0] || '') : '';
|
||||
return (erst + letzt).toLocaleUpperCase('de-DE');
|
||||
}
|
||||
|
||||
/** Baut die Wahl -- nach Rollen gruppiert.
|
||||
*
|
||||
* `gewaehlt` sind die Nummern, die an sein sollen: beim Bearbeiten
|
||||
* die des Termins, sonst nichts.
|
||||
*
|
||||
* WARUM GRUPPIERT: Vorher stand die Rolle an JEDEM Namen, neunmal
|
||||
* hintereinander, als eine lange graue Reihe. Jetzt trägt die
|
||||
* Überschrift das Wort einmal, und man springt zur Gruppe statt zu
|
||||
* suchen. */
|
||||
function baueDabei(gewaehlt = []) {
|
||||
const ziel = $('dabei-wahl');
|
||||
if (!ziel) return;
|
||||
@@ -95,29 +123,76 @@
|
||||
$('dabei-hinweis').textContent = '';
|
||||
return;
|
||||
}
|
||||
for (const p of alleLeute) {
|
||||
const b = document.createElement('button');
|
||||
b.type = 'button';
|
||||
b.className = 'wahl__knopf';
|
||||
b.dataset.id = String(p.id);
|
||||
b.dataset.rolle = p.rolle;
|
||||
if (an.has(p.id)) b.dataset.an = 'ja';
|
||||
const n = document.createElement('span'); n.className = 'wahl__name'; n.textContent = p.name;
|
||||
const r = document.createElement('span'); r.className = 'wahl__rolle';
|
||||
r.textContent = ROLLENKURZ[p.rolle] || p.rolle;
|
||||
b.append(n, r);
|
||||
b.addEventListener('click', () => {
|
||||
if (b.dataset.an === 'ja') delete b.dataset.an; else b.dataset.an = 'ja';
|
||||
dabeiHinweis();
|
||||
});
|
||||
ziel.append(b);
|
||||
|
||||
for (const rolle of ROLLENFOLGE) {
|
||||
const leute = alleLeute.filter((p) => p.rolle === rolle);
|
||||
if (!leute.length) continue; // leere Gruppe gar nicht erst zeigen
|
||||
|
||||
const gruppe = document.createElement('div');
|
||||
gruppe.className = 'k-dabei__gruppe';
|
||||
gruppe.dataset.rolle = rolle;
|
||||
|
||||
const titel = document.createElement('p');
|
||||
titel.className = 'k-dabei__titel';
|
||||
const punkt = document.createElement('span');
|
||||
punkt.className = 'k-dabei__punkt';
|
||||
punkt.setAttribute('aria-hidden', 'true');
|
||||
titel.append(punkt, document.createTextNode(ROLLENGRUPPE[rolle] || rolle));
|
||||
|
||||
const liste = document.createElement('div');
|
||||
liste.className = 'k-dabei__liste';
|
||||
/* Die Gruppe ist eine Gruppe von Schaltern -- ein Vorleseprogramm
|
||||
soll die Überschrift dazu ansagen, sonst hört man neun Namen
|
||||
ohne zu wissen, wovon. */
|
||||
liste.setAttribute('role', 'group');
|
||||
liste.setAttribute('aria-label', ROLLENGRUPPE[rolle] || rolle);
|
||||
|
||||
for (const p of leute) {
|
||||
const b = document.createElement('button');
|
||||
b.type = 'button';
|
||||
b.className = 'k-dabei__knopf';
|
||||
b.dataset.id = String(p.id);
|
||||
b.dataset.rolle = p.rolle;
|
||||
if (an.has(p.id)) b.dataset.an = 'ja';
|
||||
|
||||
const z = document.createElement('span');
|
||||
z.className = 'k-dabei__zeichen';
|
||||
z.setAttribute('aria-hidden', 'true'); // der Name steht direkt daneben
|
||||
z.textContent = initialen(p.name);
|
||||
|
||||
const n = document.createElement('span');
|
||||
n.className = 'k-dabei__name';
|
||||
n.textContent = p.name;
|
||||
|
||||
const haken = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
haken.setAttribute('class', 'k-dabei__haken');
|
||||
haken.setAttribute('viewBox', '0 0 16 16');
|
||||
haken.setAttribute('aria-hidden', 'true');
|
||||
haken.innerHTML = '<path d="M3 8.5l3.2 3.2L13 5"/>';
|
||||
|
||||
b.append(z, n, haken);
|
||||
/* aria-pressed sagt einem Vorleseprogramm, dass das ein
|
||||
Schalter ist und wie er steht -- ohne das hört man nur
|
||||
"Knopf, Cigdem" und weiß nie, ob sie dabei ist. */
|
||||
b.setAttribute('aria-pressed', an.has(p.id) ? 'true' : 'false');
|
||||
b.addEventListener('click', () => {
|
||||
const jetztAn = b.dataset.an !== 'ja';
|
||||
if (jetztAn) b.dataset.an = 'ja'; else delete b.dataset.an;
|
||||
b.setAttribute('aria-pressed', jetztAn ? 'true' : 'false');
|
||||
dabeiHinweis();
|
||||
});
|
||||
liste.append(b);
|
||||
}
|
||||
|
||||
gruppe.append(titel, liste);
|
||||
ziel.append(gruppe);
|
||||
}
|
||||
dabeiHinweis();
|
||||
}
|
||||
|
||||
/** Die Nummern, die gerade angehakt sind. */
|
||||
const dabeiGewaehlt = () =>
|
||||
[...$('dabei-wahl').querySelectorAll('.wahl__knopf[data-an="ja"]')].map((b) => Number(b.dataset.id));
|
||||
[...$('dabei-wahl').querySelectorAll('.k-dabei__knopf[data-an="ja"]')].map((b) => Number(b.dataset.id));
|
||||
|
||||
function dabeiHinweis() {
|
||||
const n = dabeiGewaehlt().length;
|
||||
@@ -1178,10 +1253,16 @@
|
||||
/* Ein frei eingetragener Name statt einer Person. Der Server
|
||||
raeumt dann creator_id ab -- siehe workspace-kalender.js. */
|
||||
koerper.teilnehmer_extern = $('f-teilnehmer').dataset.extern || null;
|
||||
/* Wer sonst noch dabei ist. Das Haupt-Gegenueber muss hier NICHT
|
||||
mitgeschickt werden -- der Server legt es ohnehin dazu. */
|
||||
koerper.teilnehmer = dabeiGewaehlt();
|
||||
}
|
||||
/* Wer sonst noch dabei ist -- fuer JEDE Rolle, nicht nur die
|
||||
Leitung. Stand vorher im Block darueber und wurde damit bei
|
||||
Scouts und Creators nie mitgeschickt.
|
||||
|
||||
Das Haupt-Gegenueber gehoert hier NICHT hinein: Der Server legt
|
||||
es ohnehin dazu (teilnehmerSetzen mit hauptId), und zweimal
|
||||
dieselbe Nummer zu schicken hiesse, sich auf die Reihenfolge zu
|
||||
verlassen. */
|
||||
if (!$('feld-dabei').hidden) koerper.teilnehmer = dabeiGewaehlt();
|
||||
/* Mit Rhythmus entsteht keine einzelne Zeile, sondern eine Regel --
|
||||
der Server legt daraus die Termine an, den ersten eingeschlossen.
|
||||
Deshalb wird hier nichts zusätzlich abgeschickt: Sonst stünde der
|
||||
@@ -1226,9 +1307,31 @@
|
||||
} catch { return; }
|
||||
window.werZeigen?.(ich);
|
||||
|
||||
if (LEITUNG.has(ich.rolle)) {
|
||||
/* WER DARF WEN MARKIEREN — und wer entscheidet das.
|
||||
|
||||
"Mit wem" (die Haupt-Zuordnung) bleibt der Leitung vorbehalten:
|
||||
Das verschiebt einen Termin in einen fremden Kalender als dessen
|
||||
Eigentümer, und das ist eine Zuordnung, keine Einladung.
|
||||
|
||||
"Wer ist noch dabei?" bekommt dagegen JEDE Rolle (05.09.2026:
|
||||
*"das soll für jeden verfügbar sein"*). Sicher ist das, weil
|
||||
nicht diese Zeile über die Auswahl entscheidet, sondern der
|
||||
Server an zwei Stellen:
|
||||
* /api/personen liefert über sichtbarePersonenIds nur die
|
||||
zugeteilten Namen -- ein Creator sieht seine Betreuer, keine
|
||||
fremden Creator.
|
||||
* teilnehmerSetzen() prüft jede Nummer noch einmal gegen
|
||||
darfEintragen() und lässt Unerlaubtes still weg. Wer die
|
||||
Anfrage von Hand baut, kommt damit keinen Schritt weiter.
|
||||
Die Oberfläche zu verstecken war also nie die Sicherung -- sie
|
||||
hat nur denen etwas weggenommen, die es brauchen. */
|
||||
const darfZuordnen = LEITUNG.has(ich.rolle);
|
||||
|
||||
/* "Mit wem" -- die Haupt-Zuordnung. Nur Leitung: Sie macht die
|
||||
andere Person zum Gegenüber des Termins, das ist eine Zuordnung
|
||||
und keine Einladung. Liste: /api/personen (zeigt nach unten). */
|
||||
if (darfZuordnen) {
|
||||
$('feld-teilnehmer').hidden = false;
|
||||
$('feld-dabei').hidden = false;
|
||||
try {
|
||||
const { personen } = await (await hole('/workspace/api/personen')).json();
|
||||
for (const p of personen) {
|
||||
@@ -1237,12 +1340,26 @@
|
||||
o.textContent = p.name + ' (' + p.rolle + ')';
|
||||
$('f-teilnehmer').append(o);
|
||||
}
|
||||
alleLeute = personen;
|
||||
baueDabei();
|
||||
} catch { /* Zuordnung bleibt leer */ }
|
||||
window.personenwahl?.('f-teilnehmer', { hinweis: "Kein Konto im Workspace – der Name steht nur an diesem Eintrag." });
|
||||
}
|
||||
|
||||
/* "Wer ist noch dabei?" -- für jede Rolle. Eigene Liste über
|
||||
/api/einladbar, die auch nach oben zeigt: Ein Creator findet
|
||||
dort seinen Scout, ein Scout seinen Manager. */
|
||||
try {
|
||||
const { personen } = await (await hole('/workspace/api/einladbar')).json();
|
||||
/* Sich selbst braucht niemand zu markieren -- der eigene Termin
|
||||
steht ohnehin im eigenen Kalender. Ein Knopf, der nichts
|
||||
bewirkt, verwirrt nur. */
|
||||
alleLeute = (personen || []).filter((p) => p.id !== ich.id);
|
||||
/* Bleibt niemand übrig (jemand ganz ohne Zuordnung), hat das
|
||||
Feld keinen Zweck -- ein leerer Kasten mit Überschrift sieht
|
||||
aus wie ein Fehler. Sonst: zeigen. */
|
||||
$('feld-dabei').hidden = !alleLeute.length;
|
||||
baueDabei();
|
||||
} catch { $('feld-dabei').hidden = true; }
|
||||
|
||||
/* Vom Bericht kommend: VOR dem ersten Laden auf die Liste stellen,
|
||||
sonst baut die Seite erst den Monat auf und wirft ihn gleich
|
||||
wieder weg. Der Ansichtsknopf wird mitgesetzt -- sonst zeigt die
|
||||
|
||||
+65
-7
@@ -8,9 +8,9 @@
|
||||
<meta name="theme-color" content="#05070d" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609051732" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609051732" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609051732" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
@@ -104,6 +104,32 @@
|
||||
<p class="leise" id="laedt">Aufgaben werden geladen …</p>
|
||||
</div>
|
||||
|
||||
<!-- ABGEBROCHENE AUFGABEN (05.09.2026).
|
||||
|
||||
Ein eigener Bereich UNTER dem Brett, nicht als fünfte Spalte:
|
||||
Am Handy wären dann alle fünf so schmal, dass keine mehr lesbar
|
||||
ist. Und Abgebrochenes sieht man nicht täglich.
|
||||
|
||||
Verschwinden darf es aber auch nicht -- sonst wäre der Abbruch
|
||||
ein Löschen mit Zwischenschritt, und genau das soll er nicht
|
||||
sein. Deshalb: zugeklappt, mit der Anzahl im Titel, einen Klick
|
||||
entfernt.
|
||||
|
||||
<details> und nicht selbstgebaut: bringt Tastaturbedienung,
|
||||
aria-expanded und das Verhalten von Suchfunktionen im Browser
|
||||
von sich aus mit. -->
|
||||
<details class="abgebrochen" id="abgebrochen" hidden>
|
||||
<summary class="abgebrochen__kopf">
|
||||
<span class="abgebrochen__name">Abgebrochen</span>
|
||||
<span class="abgebrochen__zahl" id="abgebrochen-zahl">0</span>
|
||||
</summary>
|
||||
<p class="abgebrochen__unter">
|
||||
Nicht gelöscht – nur beiseitegelegt. Mit Grund, damit später
|
||||
nachvollziehbar bleibt, warum. Jederzeit wieder aufnehmbar.
|
||||
</p>
|
||||
<div class="abgebrochen__liste" id="abgebrochen-liste"></div>
|
||||
</details>
|
||||
|
||||
</main>
|
||||
|
||||
<!-- Bearbeiten. Als natives <dialog>: bringt Fokusfang, Schließen mit Esc
|
||||
@@ -153,9 +179,41 @@
|
||||
</form>
|
||||
</dialog>
|
||||
|
||||
<script src="assets/js/wahl.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/aufgaben.js?v=202609051419" defer></script>
|
||||
<!-- ABBRECHEN MIT GRUND (05.09.2026).
|
||||
|
||||
Kein confirm(): Das blockiert die ganze Seite, nimmt kein Textfeld
|
||||
auf und sieht auf jedem Rechner anders aus.
|
||||
|
||||
Der Grund ist PFLICHT. Ohne ihn weiß in vier Wochen niemand mehr,
|
||||
warum etwas wegfiel -- und dann ist ein Abbruch von einem Löschen
|
||||
nicht mehr zu unterscheiden. Der Server verlangt ihn ebenfalls;
|
||||
hier steht er, damit man es merkt, bevor man abschickt. -->
|
||||
<dialog class="dialog" id="abbruch-dialog">
|
||||
<form method="dialog" id="abbruch-form" class="neu neu--blank">
|
||||
<h2 class="dialog__titel" id="abbruch-titel">Aufgabe abbrechen?</h2>
|
||||
<!-- Sagt, was passiert UND was nicht passiert. Die Sorge bei so
|
||||
einem Knopf ist immer, ob etwas verloren geht. -->
|
||||
<p class="dialog__unter" id="abbruch-was"></p>
|
||||
|
||||
<div class="neu__zeile">
|
||||
<label class="feld-schild" for="abbruch-grund">Warum wird sie abgebrochen?</label>
|
||||
<textarea id="abbruch-grund" rows="3" maxlength="500"
|
||||
placeholder="z. B. Kampagne wurde verschoben – kommt im Oktober neu."></textarea>
|
||||
</div>
|
||||
|
||||
<p class="fehler" id="abbruch-fehler" role="alert" aria-live="polite"></p>
|
||||
|
||||
<div class="dialog__knoepfe">
|
||||
<button type="submit" class="knopf knopf--klein" id="abbruch-ok">Abbrechen & beiseitelegen</button>
|
||||
<button type="button" class="abmelden" id="abbruch-zurueck">Doch nicht</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
|
||||
<script src="assets/js/wahl.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/aufgaben.js?v=202609051732" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
<meta name="theme-color" content="#05070d" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/automation.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609051732" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609051732" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609051732" />
|
||||
<link rel="stylesheet" href="assets/css/automation.css?v=202609051732" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
@@ -133,9 +133,10 @@
|
||||
|
||||
</main>
|
||||
|
||||
<script src="assets/js/wahl.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/automation.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/automation.js?v=202609051732" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+13
-12
@@ -8,10 +8,10 @@
|
||||
<meta name="theme-color" content="#05070d" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/bereich.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609051732" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609051732" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609051732" />
|
||||
<link rel="stylesheet" href="assets/css/bereich.css?v=202609051732" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
@@ -116,13 +116,14 @@
|
||||
|
||||
</main>
|
||||
|
||||
<script src="assets/js/wahl.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/checkliste.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/ampel.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/schulung.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/vorlagen.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/bereich.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/checkliste.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/ampel.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/schulung.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/vorlagen.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/bereich.js?v=202609051732" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+10
-9
@@ -8,10 +8,10 @@
|
||||
<meta name="theme-color" content="#05070d" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/calls.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609051732" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609051732" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609051732" />
|
||||
<link rel="stylesheet" href="assets/css/calls.css?v=202609051732" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
@@ -75,10 +75,11 @@
|
||||
|
||||
</main>
|
||||
|
||||
<script src="assets/js/ki.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/calls.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/ki.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/calls.js?v=202609051732" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+11
-10
@@ -8,10 +8,10 @@
|
||||
<meta name="theme-color" content="#05070d" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/content.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609051732" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609051732" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609051732" />
|
||||
<link rel="stylesheet" href="assets/css/content.css?v=202609051732" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
@@ -165,11 +165,12 @@
|
||||
|
||||
</main>
|
||||
|
||||
<script src="assets/js/wahl.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/checkliste.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/vorlagen.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/content.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/checkliste.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/vorlagen.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/content.js?v=202609051732" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
<meta name="theme-color" content="#05070d" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/dateien.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609051732" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609051732" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609051732" />
|
||||
<link rel="stylesheet" href="assets/css/dateien.css?v=202609051732" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
@@ -94,9 +94,10 @@
|
||||
|
||||
</main>
|
||||
|
||||
<script src="assets/js/wahl.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/dateien.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/dateien.js?v=202609051732" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609051732" />
|
||||
</head>
|
||||
|
||||
<body class="gate">
|
||||
@@ -144,6 +144,6 @@
|
||||
</p>
|
||||
</main>
|
||||
|
||||
<script src="assets/js/gate.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/gate.js?v=202609051732" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+23
-13
@@ -8,10 +8,10 @@
|
||||
<meta name="theme-color" content="#05070d" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/kalender.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609051732" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609051732" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609051732" />
|
||||
<link rel="stylesheet" href="assets/css/kalender.css?v=202609051732" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
@@ -112,14 +112,23 @@
|
||||
|
||||
Einzelne Schalter statt eines <select multiple>: Das verliert
|
||||
bei einem Fehlklick die ganze Auswahl und ist am Handy kaum zu
|
||||
bedienen. Genau so ist es bei den Dateien schon gebaut, die
|
||||
Bedienung ist also bekannt.
|
||||
bedienen.
|
||||
|
||||
Das Feld erscheint nur für die Leitung -- wer keine Zuordnungen
|
||||
verschieben darf, bekommt es gar nicht erst zu sehen. -->
|
||||
FÜR JEDE ROLLE, nicht nur für die Leitung (05.09.2026:
|
||||
*"das soll für jeden verfügbar sein"*). Das ist ohne Risiko,
|
||||
weil nicht die Oberfläche entscheidet, wen man auswählen darf,
|
||||
sondern der Server: /api/personen liefert jeder Rolle nur die
|
||||
zugeteilten Namen, und teilnehmerSetzen() lässt alles andere
|
||||
still weg. Ein Creator sieht hier also seine Betreuer, keine
|
||||
fremden Creator.
|
||||
|
||||
Die Klassen heißen k-dabei__* und stehen in kalender.css.
|
||||
Vorher waren es die .wahl__*-Klassen aus dateien.css -- die
|
||||
diese Seite gar nicht einbindet, weshalb die Namen ohne jeden
|
||||
Abstand aneinanderklebten. -->
|
||||
<div id="feld-dabei" hidden>
|
||||
<p class="feld-schild">Wer ist noch dabei?</p>
|
||||
<div class="wahl" id="dabei-wahl"></div>
|
||||
<div class="k-dabei" id="dabei-wahl"></div>
|
||||
<p class="leise" id="dabei-hinweis"></p>
|
||||
</div>
|
||||
|
||||
@@ -196,9 +205,10 @@
|
||||
|
||||
</main>
|
||||
|
||||
<script src="assets/js/wahl.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/kalender.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/kalender.js?v=202609051732" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
<meta name="theme-color" content="#05070d" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/personen.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609051732" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609051732" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609051732" />
|
||||
<link rel="stylesheet" href="assets/css/personen.css?v=202609051732" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
@@ -122,9 +122,10 @@
|
||||
|
||||
</main>
|
||||
|
||||
<script src="assets/js/wahl.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/personen.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/personen.js?v=202609051732" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+10
-9
@@ -8,10 +8,10 @@
|
||||
<meta name="theme-color" content="#05070d" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/profil.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609051732" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609051732" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609051732" />
|
||||
<link rel="stylesheet" href="assets/css/profil.css?v=202609051732" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
@@ -200,10 +200,11 @@
|
||||
|
||||
</main>
|
||||
|
||||
<script src="assets/js/wahl.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/steckbrief.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/profil.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/steckbrief.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/profil.js?v=202609051732" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+10
-9
@@ -8,10 +8,10 @@
|
||||
<meta name="theme-color" content="#05070d" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/report.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609051732" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609051732" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609051732" />
|
||||
<link rel="stylesheet" href="assets/css/report.css?v=202609051732" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
@@ -92,10 +92,11 @@
|
||||
|
||||
</main>
|
||||
|
||||
<script src="assets/js/ki.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/report.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/ki.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/report.js?v=202609051732" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
<meta name="theme-color" content="#05070d" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/scouting.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609051732" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609051732" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609051732" />
|
||||
<link rel="stylesheet" href="assets/css/scouting.css?v=202609051732" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
@@ -125,9 +125,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="assets/js/wahl.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/scouting.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/scouting.js?v=202609051732" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
<meta name="theme-color" content="#05070d" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609051732" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609051732" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
@@ -100,9 +100,10 @@
|
||||
</p>
|
||||
</main>
|
||||
|
||||
<script src="assets/js/wahl.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/start.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/start.js?v=202609051732" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
<meta name="theme-color" content="#05070d" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/startcheck.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609051732" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609051732" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609051732" />
|
||||
<link rel="stylesheet" href="assets/css/startcheck.css?v=202609051732" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
@@ -71,10 +71,11 @@
|
||||
|
||||
</main>
|
||||
|
||||
<script src="assets/js/ki.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/startcheck.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/ki.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/startcheck.js?v=202609051732" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
<meta name="theme-color" content="#05070d" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/profil.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609051732" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609051732" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609051732" />
|
||||
<link rel="stylesheet" href="assets/css/profil.css?v=202609051732" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
@@ -112,9 +112,10 @@
|
||||
|
||||
</main>
|
||||
|
||||
<script src="assets/js/wahl.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/steckbrief.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/steckbrief.js?v=202609051732" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/* =====================================================================
|
||||
Service Worker des Creator Workspace.
|
||||
|
||||
Er hat GENAU EINE Aufgabe: Benachrichtigungen entgegennehmen und
|
||||
anzeigen.
|
||||
|
||||
⚠️ BEWUSST OHNE ZWISCHENSPEICHER. Der naheliegende Zusatz waere,
|
||||
hier auch Seiten und Dateien vorzuhalten ("offline verfuegbar").
|
||||
Genau das waere hier falsch:
|
||||
|
||||
* Der Workspace liegt hinter einer Anmeldung. Ein Service Worker,
|
||||
der Antworten aufhebt, hebt damit AUCH persoenliche Daten auf --
|
||||
auf einem Geraet, das jemand anderem gehoeren kann.
|
||||
|
||||
* Die Seiten aendern sich haeufig. Ein Zwischenspeicher, der etwas
|
||||
Falsches zeigt, ist schlimmer als gar keiner -- man glaubt ihm
|
||||
ja. Die Versionsstempel loesen das Problem bereits.
|
||||
|
||||
Wer spaeter Offline-Faehigkeit will, faengt hier NICHT einfach an,
|
||||
sondern klaert vorher, was zwischengespeichert werden darf.
|
||||
===================================================================== */
|
||||
|
||||
/* Beim Einbauen sofort uebernehmen, statt auf das Schliessen aller
|
||||
Reiter zu warten. Ohne diese zwei Zeilen wuerde eine neue Fassung
|
||||
erst Tage spaeter aktiv -- und niemand wuesste, welche gerade laeuft. */
|
||||
self.addEventListener("install", (e) => { self.skipWaiting(); });
|
||||
self.addEventListener("activate", (e) => { e.waitUntil(self.clients.claim()); });
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
Eine Benachrichtigung kommt an.
|
||||
------------------------------------------------------------------ */
|
||||
self.addEventListener("push", (e) => {
|
||||
/* Ohne Daten trotzdem etwas anzeigen: Manche Push-Dienste schicken
|
||||
im Zweifel eine leere Nachricht, und ein Browser, der eine
|
||||
Push-Berechtigung hat, MUSS etwas anzeigen -- sonst entzieht er
|
||||
sie nach mehrmaligem Schweigen wieder. */
|
||||
let d = { titel: "Creator Workspace", text: "Es gibt etwas Neues.", ziel: "/workspace/start.html" };
|
||||
try {
|
||||
if (e.data) d = { ...d, ...e.data.json() };
|
||||
} catch {
|
||||
try { d.text = e.data.text() || d.text; } catch { /* dann die Vorgabe */ }
|
||||
}
|
||||
|
||||
e.waitUntil(self.registration.showNotification(d.titel, {
|
||||
body: d.text,
|
||||
icon: "/assets/img/app-symbole/workspace-192.png",
|
||||
badge: "/assets/img/app-symbole/workspace-192.png",
|
||||
/* Gleiche Art ersetzt sich gegenseitig, statt sich zu stapeln --
|
||||
drei Meldungen "Aufgabe ueberfaellig" untereinander helfen
|
||||
niemandem. */
|
||||
tag: d.art || "workspace",
|
||||
renotify: false,
|
||||
data: { ziel: d.ziel || "/workspace/start.html" },
|
||||
/* Kein `requireInteraction`: Eine Benachrichtigung, die stehen
|
||||
bleibt, bis man sie wegklickt, ist eine Zumutung. */
|
||||
}));
|
||||
});
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
Jemand tippt die Benachrichtigung an.
|
||||
|
||||
Ist der Workspace schon offen, wird DIESES Fenster benutzt und dort
|
||||
hingesteuert -- sonst sammeln sich mit der Zeit fuenf Reiter mit
|
||||
derselben Seite. Nur wenn keines offen ist, wird eines geoeffnet.
|
||||
------------------------------------------------------------------ */
|
||||
self.addEventListener("notificationclick", (e) => {
|
||||
e.notification.close();
|
||||
const ziel = e.notification.data?.ziel || "/workspace/start.html";
|
||||
|
||||
e.waitUntil((async () => {
|
||||
const fenster = await self.clients.matchAll({ type: "window", includeUncontrolled: true });
|
||||
for (const f of fenster) {
|
||||
if (f.url.includes("/workspace/")) {
|
||||
await f.focus();
|
||||
if ("navigate" in f) { try { await f.navigate(ziel); } catch { /* schon dort */ } }
|
||||
return;
|
||||
}
|
||||
}
|
||||
await self.clients.openWindow(ziel);
|
||||
})());
|
||||
});
|
||||
@@ -8,9 +8,9 @@
|
||||
<meta name="theme-color" content="#05070d" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/uebersicht.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609051732" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609051732" />
|
||||
<link rel="stylesheet" href="assets/css/uebersicht.css?v=202609051732" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
@@ -58,9 +58,10 @@
|
||||
|
||||
</main>
|
||||
|
||||
<script src="assets/js/bereiche.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609051732" defer></script>
|
||||
|
||||
<script src="assets/js/kopf.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/uebersicht.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/uebersicht.js?v=202609051732" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
<meta name="theme-color" content="#05070d" />
|
||||
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png" />
|
||||
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/wissen.css?v=202609051419" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css?v=202609051732" />
|
||||
<link rel="stylesheet" href="assets/css/start.css?v=202609051732" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609051732" />
|
||||
<link rel="stylesheet" href="assets/css/wissen.css?v=202609051732" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
@@ -176,9 +176,10 @@
|
||||
|
||||
</main>
|
||||
|
||||
<script src="assets/js/wahl.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/wissen.js?v=202609051419" defer></script>
|
||||
<script src="assets/js/wahl.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/bereiche.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/kopf.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/glocke.js?v=202609051732" defer></script>
|
||||
<script src="assets/js/wissen.js?v=202609051732" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user