feat(calendar): Phase 3 – Weboberfläche mit Monats-, Wochen- und Listenansicht

Neue Seite CalendarPage (Menüpunkt 'Calendar' in der Seitenleiste,
zwischen Home und Papierkorb) mit Kalenderliste zum Anlegen/Umbenennen/
Löschen/Ein- und Ausblenden, Export/Import als .ics über die
Phase-2-API, und Termine anlegen/ändern/löschen über einen neuen
JSON-Endpunkt.

Backend: neuer events_api.go-Handler unter /api/v1/calendars/{id}/events
(Session-Auth) neben dem bestehenden CalDAV-Protokoll – ein Termin, den
die Web-Oberfläche anlegt, ist ein ganz normales CalDAV-Objekt und
synchronisiert genauso zu DAVx5 wie umgekehrt. Ein per CalDAV mit RRULE
angelegter, wiederkehrender Termin zeigt sich hier nur (recurring:true),
lässt sich aber bewusst nicht über dieses vereinfachte Formular
bearbeiten (409) – das würde die Wiederholungsregel eher beschädigen
als korrekt anpassen.

Gegen den laufenden Container getestet: Kalender/Termin anlegen, per
Web-API ändern und löschen, ein per CalDAV mit RRULE angelegter
Wiederkehrer taucht als recurring:true auf und blockt PATCH mit 409.
TypeScript-Build (tsc + vite build) sauber.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
marcandClaude Sonnet 5 committed 2026-09-03 23:14:28 +02:00
1 parent 4d635fff20
commit 27a026421f
10 files changed
+1163 -6

No files matched your search

+11 -4
View File
@@ -294,6 +294,14 @@ func timeRange(cal *ical.Calendar) (start, end *time.Time) {
return
}
func encodeICal(cal *ical.Calendar) (string, error) {
var buf bytes.Buffer
if err := ical.NewEncoder(&buf).Encode(cal); err != nil {
return "", fmt.Errorf("encode object: %w", err)
}
return buf.String(), nil
}
func etagFor(data string) string {
sum := sha256.Sum256([]byte(data))
return hex.EncodeToString(sum[:])
@@ -328,11 +336,10 @@ func (b *Backend) PutCalendarObject(ctx context.Context, p string, cal *ical.Cal
}
}
var buf bytes.Buffer
if err := ical.NewEncoder(&buf).Encode(cal); err != nil {
return nil, fmt.Errorf("encode object: %w", err)
data, err := encodeICal(cal)
if err != nil {
return nil, err
}
data := buf.String()
etag := etagFor(data)
start, end := timeRange(cal)