Damit ist auf den ersten Blick erkennbar, dass auch der Ordner-Button
einen Upload ausloest (und nicht bloss eine Ordner-Aktion).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wenn der eigene und ein freigegebener Kalender/Adressbuch/Aufgabenliste
denselben Namen tragen, sind sie in den Anlegen-Dialogen jetzt
unterscheidbar.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Stift-Icon neben dem Namen oeffnet Inline-Editor (Eingabefeld + Check/X).
Enter speichert, ESC bricht ab. Nur fuer Eigentuemer sichtbar.
Backend-PUT-Endpunkte sind bereits vorhanden.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ContactsView: Adressbuch-Auswahl im Kontakt-Dialog (versteckt bei nur
einem beschreibbaren Buch). Neuer-Kontakt-Button disabled wenn keiner.
- TasksView: gleiches fuer Aufgabenlisten.
- CalendarView: writableCalendars (eigene + Schreibfreigaben) ersetzt
ownCalendars in Event-Dialog und Import-Auswahl. Auswahlfeld nur ab 2.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backend:
- User.first_name / User.last_name (nullable, Auto-Migrate fuegt sie an)
full_name/display_name als Properties + in to_dict
- TaskList.owner-Relationship ergaenzt (fehlte, daher wurden geteilte
Listen beim Empfaenger nicht korrekt aufgeloest)
- /auth/me GET + PUT (Profil bearbeiten: Vorname, Nachname, E-Mail)
- /users/search findet jetzt auch nach Vor-/Nachname und liefert
full_name/display_name mit
- list_tasklists/list_calendars/list_addressbooks liefern
owner_full_name und owner_display_name
Frontend:
- Sidebars bei Kontakten/Kalender/Aufgaben: "(geteilt von <Voller Name>)"
mit Fallback auf Username
- User-Search-Popup zeigt vollen Namen neben Username
- SettingsView: Vorname/Nachname/E-Mail bearbeiten
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Stift-Icon neben Freigabe oeffnet Inline-Editor mit Select "Lesen" /
"Lesen+Schreiben" (analog zu Kontakten/Kalender).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Vermeidet unnoetigen Platzbedarf beim Build (31 Pakete / 192 MB werden
sonst mitgezogen).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Analog zu Kontakten/Kalender: ab 2 Zeichen werden Vorschlaege per
/users/search eingeblendet.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- /api/settings gibt zusaetzlich timezone, timezone_abbr, server_time,
ntp_server zurueck (alle read-only, aus Config/ENV).
- AdminView zeigt neuen Abschnitt "System-Zeit" mit Zeitzone, aktueller
Server-Zeit und NTP-Server samt Hinweis "wird in der .env festgelegt".
- .env.example: Liste gaengiger TZ-Werte mit Link zur IANA-Vollliste.
- README.md: neuer Abschnitt "Zeitzone & NTP" mit Werte-Tabelle.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- .env / .env.example: TZ=Europe/Berlin und NTP_SERVER=ptbtime1.ptb.de
(offizielle deutsche Zeitreferenz, hohe Verfuegbarkeit)
- app/__init__.py setzt prozessweite Zeitzone frueh via os.environ+tzset
- Leichtgewichtiger SNTP-Client (pure socket, keine deps) prueft den
Uhr-Offset beim Start im Hintergrund-Thread und warnt bei Abweichung >5s
- Dockerfile installiert tzdata und ENV TZ=Europe/Berlin als Fallback
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Kontakte:
- Mehrfachauswahl in der Liste (Checkbox-Spalte) mit Bulk-Loeschen
- Export als Sammel-vCard (.vcf), als ZIP mit Einzel-vCards oder als CSV
- Import aus vCard (mehrere im File moeglich) oder CSV; Match per UID,
bestehende Kontakte werden aktualisiert
Kalender:
- Export als iCalendar (.ics) oder CSV
- Import aus .ics oder CSV; bestehende Termine via UID aktualisiert
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Browser/Kalender-App fragen sonst nach Benutzername+Passwort - der
Benutzername muss leer bleiben.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Checkbox-Spalte plus Header-Checkbox "Alle". Bulk-Aktion mit
Bestaetigung loescht ausgewaehlte Termine; Read-Only-Eintraege
werden uebersprungen.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Titel reagiert reaktiv auf Login/Logout. Favicon ist die Wolke aus
der Sidebar (pi-cloud-Style).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Anrede: Herr/Frau/Divers (fest), Titel: Dr./Prof./Dipl.-Ing./... (editierbar).
Beim Speichern werden beide zu vCard-PREFIX zusammengesetzt, beim Laden
wieder aufgesplittet.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Anrede/Suffix/PLZ etc. hatten max-width-Container, das InputText darin
behielt aber die Default-Breite und ueberlief. Globale CSS-Regel sorgt
nun dafuer, dass jedes Input/Select seinen Field-Container ausfuellt.
field-row wrappt auf schmalen Dialogen.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- caldav.py sendet SSE-Notifications bei Event-PUT/DELETE und Kalender-Loeschung,
damit das Web-UI auch auf Aenderungen aus DAVx5 sofort reagiert.
- FullCalendar navLinks: Klick auf Tagesnummer im Monatsraster wechselt in
die Tagesansicht.
- Neue Listen-Ansicht mit Volltext-Suche, Datumsbereich, Kalender-Filter,
Sortierung nach Datum/Titel und Loeschen-Button pro Zeile.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Master-Event eines Serientermins liegt oft vor dem sichtbaren Bereich -
das FullCalendar-RRULE-Plugin braucht ihn trotzdem zur Expansion.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Die CardDAV-Route /<username>/<ab_part>/ fing REPORT auf Kalender-URLs
(z.B. /dav/Adam/cal-1/) mit 404 ab, weil 'cal-1' nicht mit 'ab-' startet.
DAVx5 bekam bei der calendar-query einen 404 und markierte den EVENTS-
Sync als Hard Error. Fix analog zu PROPFIND/OPTIONS: wenn ab_part nicht
ab-* ist, an den CalDAV-REPORT-Handler delegieren.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DAVx5 registriert Dienste basierend auf dem DAV-Response-Header. Ohne
'addressbook' im Header wurde CardDAV bei der Auto-Discovery ignoriert,
obwohl addressbook-home-set korrekt gemeldet wurde. Das erklaert warum
nur der caldav-Service fuer Adam angelegt wurde.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Die zuletzt eingefuehrten Sub-Container (calendars/, addressbooks/) bei
PROPFIND Depth 1 auf /dav/<user>/ wurden von DAVx5 als leere Kalender
gezaehlt (DEFAULT_TASK_CALENDAR_NAME-Phantom-Eintraege). Da die CardDAV-
Route jetzt korrekt an den Home-Set-Handler delegiert, reicht es wenn der
Principal nur sich selbst liefert - Clients folgen den Home-Sets.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Die CardDAV-Route /<username>/<ab_part>/ ist in Flask spezifischer als
die generische /<path:subpath> des CalDAV-Handlers und hat daher auch
/dav/<user>/calendars/ abgefangen - mit 404, weil 'calendars' nicht mit
'ab-' anfaengt. Ergebnis: DAVx5 bekam auf das Home-Set eine 404 und
zeigte keine Eintraege mehr an.
Fix: wenn ab_part nicht mit 'ab-' anfaengt, an den CalDAV-PROPFIND/OPTIONS
delegieren statt 404 zurueckzugeben.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Checkbox pro Eintrag, "Alle auswaehlen" oben und roter Loesch-Button mit
Anzahl. Sicherheitsabfrage vor dem Loeschen.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DAVx5 brauchte Kind-Container unter /dav/<user>/ - sonst blieben die
Listen nach Aktualisieren leer. Die Home-Sets bleiben getrennt
(calendar-home-set vs addressbook-home-set), aber der Principal zeigt
beide Sub-Container jetzt explizit.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Kalender und Adressbuecher teilten sich den gleichen Home-Set
(/dav/<user>/). DAVx5 hat bei Depth-1-PROPFIND beide Collection-
Typen angezeigt und mangels bekanntem Resourcetype als
"DEFAULT_TASK_CALENDAR_NAME"-Kacheln gelistet.
Loesung:
* calendar-home-set zeigt auf /dav/<user>/calendars/
* addressbook-home-set zeigt auf /dav/<user>/addressbooks/
* Beide Pfade sind eigene Container-Collections - PROPFIND Depth 1
liefert nur den jeweils passenden Typ
* /dav/<user>/ selbst gibt bei Depth 1 keine Kinder mehr zurueck,
Clients folgen den Home-Sets
* Die konkreten URLs cal-<id> / ab-<id> liegen weiterhin unter
/dav/<user>/ (keine Breaking Change fuer existierende Clients;
nur die Discovery-URL aendert sich)
Frontend:
CalendarView + ContactsView zeigen als Auto-Discovery-URL nur
noch den Hostname - PROPFIND auf / funktioniert ja jetzt. Die
Direkt-URL bleibt vollstaendig mit /dav/<user>/cal-<id> bzw.
ab-<id> fuer Clients die das brauchen.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Komplette Kontakte-Ueberarbeitung analog zum Kalender-Ausbau.
Backend-Model:
* AddressBook: color (pro Buch), ausserdem Per-User-Color via
AddressBookShare.color wie bei CalendarShare.
* Contact: volle Outlook-artige Struktur - prefix/first/middle/
last/suffix, display_name, nickname, organization, department,
job_title, birthday, anniversary, notes, photo sowie JSON-
Spalten fuer mehrfach vorhandene Felder (emails, phones,
addresses mit allen Adressteilen, websites, impp, categories).
Backend-API:
* REST CRUD uebernimmt die neuen Felder und generiert vCard 3.0
als Source of Truth fuer CardDAV. Voller vCard-Parser +
-Builder mit Escape/Unescape, TYPE-Parametern, Line-Folding.
* Neuer Endpoint PUT /addressbooks/<id>/my-color - persoenliche
Farbe pro Buch ohne den Besitzer zu beeinflussen.
* SSE-Events vom Typ 'addressbook' an Besitzer + alle Share-
Empfaenger bei jeder Aenderung.
CardDAV-Server (backend/app/dav/carddav.py):
* Volle Discovery via principal - addressbook-home-set wird
neben calendar-home-set annonciert.
* PROPFIND/REPORT/GET/PUT/DELETE/MKCOL fuer
/dav/<user>/ab-<id>/ und /<...>/{uid}.vcf
* addressbook-query + addressbook-multiget REPORTs
* ETag-basierte Konfliktpruefung via If-Match/If-None-Match
Frontend (ContactsView.vue):
* Komplett neuer Editor mit vier Tabs: Allgemein (Name, Org),
Kommunikation (Emails/Phones/Websites/IMPP dynamisch),
Adressen (mehrere mit allen Teilen), Details (Geburtstag,
Jahrestag, Kategorien, Notizen).
* Avatar mit Fotoauswahl oder Initialen-Farbkreis.
* Kalender-Sharing-Flow 1:1 uebernommen: Autocomplete fuer
Benutzersuche, Share-Liste mit Stift zum Bearbeiten, Muelleimer
zum Entfernen, Per-User-Farbe, CardDAV-URL-Info-Block pro
Adressbuch, Live-Refresh via SSE.
* Suche durchsucht Displayname, E-Mail und Firma.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bisher wurde immer die komplette calendar-data mitgeschickt, auch
wenn der Client nur getetag wollte. DAVx5 macht einen zweistufigen
Sync: erst calendar-query nach ETags, dann multiget fuer die
neuen/geaenderten Events. Server-seitig zu viel zu liefern bricht
diesen Ablauf - Client denkt er hat alles und ueberspringt die
zweite Stufe, aber die Events landen nicht in der Android-Kalender-
DB.
Jetzt: calendar-query schaut nach ob <c:calendar-data/> in den
angefragten Props steht und liefert entsprechend.
calendar-multiget liefert weiterhin immer die vollen Daten.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DAVx5 sendet bei calendar-query oft nur <time-range start=.../>
ohne end. Mein Code hat dann blind CalendarEvent.dtstart < None
gefiltert, was SQLAlchemy mit TypeError abbrechen liess - Ergebnis
HTTP 500, Sync scheitert komplett.
Zwei Korrekturen:
* end-Filter wird nur gesetzt wenn end wirklich vorhanden ist
* time-range-Parser strippt tzinfo, damit Vergleiche mit den
tz-naiven DB-Spalten keine Exception werfen
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* GET-Route akzeptiert jetzt auch HEAD - manche Clients pruefen
Existenz einer Ressource via HEAD bevor sie GET senden.
* Neue PROPPATCH-Route auf der Kalender-Collection: erkennt
calendar-color + displayname und persistiert beides. Andere
Properties werden als "angewendet" bestaetigt, damit DAVx5
und Apple Calendar nicht enttaeuscht sind.
Damit sollten die 500-Fehler beim Sync verschwinden. Falls nicht,
bitte Server- oder DAVx5-Log posten.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bisher war der href in der Response immer /dav/, auch wenn DAVx5
einen PROPFIND auf / oder /.well-known/caldav gemacht hat. Das
kann Clients verwirren - die erwarten, dass der Response-Pfad zum
angefragten Pfad passt. current-user-principal zeigt weiterhin
korrekt auf /dav/Adam/.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DAVx5 macht beim Account-Setup zuerst PROPFIND auf / bevor es
/.well-known/caldav probiert. Der Server antwortete mit 405
Method Not Allowed (weil / nur fuer SPA-GET registriert war),
woraufhin DAVx5 den gesamten Server als "kein DAV" verwirft.
Jetzt: PROPFIND und OPTIONS auf / werden an die DAV-Handler
delegiert (gleiches Verhalten wie auf /dav/). GET/HEAD auf /
laeuft unveraendert zur SPA.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Flask hat trotz expliziter OPTIONS-Route Auto-OPTIONS generiert,
wodurch der DAV-Header fehlte. DAVx5 sieht so keinen Calendar-
Access und lehnt den Server ab.
Konsolidiert zu einem Handler mit method-basiertem Dispatch und
provide_automatic_options=False, damit Flask nicht dazwischenfunkt.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Aenderungen fuer besseren DAVx5-Support:
* /.well-known/caldav reagiert jetzt direkt auf PROPFIND/OPTIONS
ohne Redirect-Zickerei. GET/HEAD redirecten weiterhin auf /dav/
als visuelle Fallback.
* strict_slashes app-weit aus: /dav und /dav/ sind gleichwertig,
ebenso die Unterpfade. DAVx5 nutzt beides gemischt.
* Jede DAV-Response traegt jetzt den DAV-Header (1, 2, 3,
calendar-access), nicht nur OPTIONS.
* Kalender-Response enthaelt jetzt supported-report-set mit
calendar-query + calendar-multiget (DAVx5 prueft das).
* current-user-privilege-set wird mit konkreten Privilegien gefuellt
(read, write, write-properties, write-content, bind, unbind)
statt leer.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Property-Elemente wurden unter einem Container mit demselben Tag
erzeugt, z.B.:
<current-user-principal>
<current-user-principal> <!-- falsch, doppelt -->
<href>/dav/adam/</href>
</current-user-principal>
</current-user-principal>
Clients wie DAVx5 und Thunderbird erkennen dadurch den Principal
nicht und melden "Kein CalDAV-Dienst gefunden". XML-Generierung
umgebaut - Response-Helfer bekommen jetzt eine populate_prop-
Callback, die die tatsaechlichen Property-Children direkt ins
<prop>-Element setzt.
Zusaetzlich:
* /.well-known/caldav und /carddav akzeptieren jetzt auch PROPFIND,
OPTIONS, HEAD (einige Clients halten die Methode beim ersten
Aufruf bei).
* Kalender-Response enthaelt current-user-privilege-set (leer, als
Signal dass der Client nicht ACL-abhaengig pruefen muss).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CalendarShare bekommt color-Spalte. Im Kalender-Menue kann jeder
Benutzer eine eigene Anzeigefarbe fuer einen mit ihm geteilten
Kalender setzen, ohne dass sich dadurch die Farbe beim
Eigentuemer oder anderen Share-Empfaengern aendert.
* Owner: Farbe aendert den Kalender direkt (wie bisher).
* Share-Empfaenger: Farbe landet in CalendarShare.color und wird
nur fuer ihn ausgeliefert (list_calendars injiziert sie in
'color', Owner-Farbe bleibt in 'owner_color' als Referenz).
Neuer Endpoint: PUT /calendars/<id>/my-color.
UI-Hinweis: "Nur fuer deine Ansicht - <Owner> behaelt seine Farbe".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backend:
Neuer Event-Typ 'calendar' im Broadcaster. Wird bei Event-CRUD,
Serien-Ausnahmen, Freigaben hinzufuegen/entfernen und beim
Loeschen ganzer Kalender emittiert. Empfaenger: Eigentuemer +
alle User mit CalendarShare auf dem jeweiligen Kalender.
Frontend:
CalendarView oeffnet beim Mount eine EventSource zu
/api/sync/events und reloaded Kalenderliste + Events bei jedem
'calendar'-Event (300ms debounced). Damit sehen beteiligte
Nutzer Aenderungen in praktisch Echtzeit - kein F5 mehr noetig.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Im Drei-Punkte-Menue jedes Kalenders wird jetzt ein Info-Block mit
den CalDAV-URLs angezeigt:
* Auto-Discovery URL fuer Thunderbird / DAVx5 / Apple Calendar
* Direkt-URL fuer diesen speziellen Kalender (z.B. Outlook
CalDAV-Synchronizer)
* Kurz-Hinweis welcher Client welche URL nimmt
Jede URL hat ein Kopier-Icon. Ergaenzt den bestehenden iCal-Link
um die bidirektionale Sync-Moeglichkeit ueber CalDAV.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Analog zu den Datei-Freigaben: Stift neben der Muelltonne in der
Share-Liste macht die Zeile zur Inline-Edit-Zeile mit Permission-
Dropdown + Check/X. Speichern nutzt denselben POST /share-
Endpoint, der auch das initiale Teilen erledigt - er erkennt den
existierenden User und aktualisiert nur die Berechtigung.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
User.calendars hat bereits backref='owner', mein zusaetzlich
hinzugefuegtes Calendar.owner kollidierte damit und SQLAlchemy
weigerte sich, die Mappers zu initialisieren ("Error creating
backref 'owner'..."). Damit waren alle Auth-Endpoints tot.
Jetzt nur noch Kommentar, die backref uebernimmt die Aufgabe.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sharing-Fix:
Calendar-Model hatte keine owner-Relation zu User - list_calendars
stuerzte beim Listen geteilter Kalender ab (c.owner.username ->
AttributeError). Jetzt mit explizitem foreign_keys Relationship.
Benutzer-Autocomplete:
"Kalender teilen" nutzt jetzt /users/search wie bei Dateien.
Tippt man 2+ Zeichen, erscheint ein Dropdown mit passenden
Benutzernamen. Klick uebernimmt den Namen.
Bestehende Freigaben werden im Menue angezeigt mit Muelleimer
zum Entfernen.
Privat-Flag fuer Termine:
CalendarEvent bekommt is_private-Spalte. Checkbox im Termin-
Dialog "🔒 Privat (Teilnehmer sehen nur den Zeitblock)".
Redaction greift an drei Stellen:
* GET /events: Nicht-Owner sehen summary="Privat", description
und location = null. Zeitfenster bleibt voll sichtbar.
* iCal-Export (/ical/<token>): Privat-Events werden mit
CLASS:PRIVATE ausgegeben und SUMMARY/DESCRIPTION/LOCATION
werden gestrippt.
* CalDAV: aktuell werden eh nur eigene Kalender exportiert,
also keine Redaction noetig. Kommt bei Share-Support rein.
Der Eigentuemer sieht natuerlich in seiner eigenen Ansicht alle
Details seines privaten Termins.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Vollstaendige CalDAV-Implementierung unter /dav/ - Thunderbird,
DAVx5, Apple Calendar und Outlook (CalDAV-Synchronizer) koennen
sich einfach ueber HTTP-Basic-Auth mit ihrem Mini-Cloud-Account
anmelden und ihre Kalender synchronisieren.
Unterstuetzte Methoden:
* OPTIONS - DAV-Capabilities
* PROPFIND - Discovery, Principal, Calendar-Home, Kalender,
Termin-Listings (Depth 0/1 beachtet)
* REPORT - calendar-query + calendar-multiget mit
optionalem Zeitraumfilter (<time-range>)
* GET - einzelner Termin als VCALENDAR
* PUT - Termin erstellen/aktualisieren (mit ETag-Check
via If-Match + If-None-Match)
* DELETE - Termin oder ganzer Kalender
* MKCALENDAR - neuen Kalender vom Client aus anlegen
iCal-Parser verarbeitet SUMMARY, DESCRIPTION, LOCATION, DTSTART,
DTEND, RRULE, EXDATE - inklusive Line-Folding (RFC 5545).
Ganztages-Termine (VALUE=DATE) werden korrekt erkannt.
ETags basieren auf updated_at-Zeitstempel und werden pro
PUT-Response zurueckgegeben, damit Clients Konflikte erkennen.
nginx.example.conf: /dav/ mit proxy_request_buffering off fuer
groessere PUTs und Weiterleitung der .well-known-URLs.
README: eigener "CalDAV-Zugriff"-Block mit Tabelle pro Client.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
eventDisplay: 'block' zwingt FullCalendar dazu, auch zeitlich
terminierte Termine in der Monatsansicht als farbige Balken
anzuzeigen statt als Punkt mit Uhrzeit-Label. Damit sieht ein
per "Neuer Termin"-Button angelegter Termin genauso aus wie einer,
der per Klick auf den Tag erstellt wurde.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Klick auf einen wiederkehrenden Termin oeffnet zuerst einen Dialog:
"Nur diesen Termin" oder "Ganze Serie".
* Serie: bearbeitet den Master wie bisher
* Nur dieser: fuegt EXDATE fuer das geklickte Datum zum Master
hinzu und legt einen eigenstaendigen Ersatz-Termin mit den
bearbeiteten Daten an
Backend:
* CalendarEvent.exdates speichert Ausnahmedaten kommasepariert
* POST /events/<id>/exception fuegt EXDATE hinzu, erstellt
optional das Replacement-Event mit frischer UID
* _build_vevent schreibt jetzt EXDATE-Zeilen in die ical_data,
sodass CalDAV-Clients die Ausnahmen auch sehen werden
Frontend:
* FullCalendar rrule-Plugin bekommt die exdate-Liste und blendet
die uebersprungenen Tage aus
* Drag & Drop verschiebt weiterhin die ganze Serie (Shortcut -
fuer Einzelverschiebung Termin anklicken und bearbeiten)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* 📅-Icon bei ganztaegigen Terminen
* 🔁-Icon bei wiederkehrenden Terminen
* Anzeige "09:00-10:30" statt nur "09:00" in Woche/Tag-Ansicht
* Mouseover-Tooltip mit allen Termin-Infos inklusive Ort und
Beschreibung
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>