feat(web): show ICS subscription events in detail view
This commit is contained in:
+51
-10
@@ -18,6 +18,7 @@ import (
|
||||
"git.arnef.de/arnef/nidus/internal/birthdays"
|
||||
"git.arnef.de/arnef/nidus/internal/db"
|
||||
"git.arnef.de/arnef/nidus/internal/icalfix"
|
||||
"git.arnef.de/arnef/nidus/internal/icssub"
|
||||
"git.arnef.de/arnef/nidus/internal/store"
|
||||
"git.arnef.de/arnef/nidus/internal/web/templates"
|
||||
ical "github.com/emersion/go-ical"
|
||||
@@ -650,12 +651,17 @@ func (s *Server) handleEventView(w http.ResponseWriter, r *http.Request) {
|
||||
ref := r.PathValue("ref")
|
||||
id := r.PathValue("id")
|
||||
|
||||
// For ICS subscriptions, ref is "!<name>" and id is the stable
|
||||
// icssub.EventID (already ".ics"-suffixed). For regular calendars,
|
||||
// ref is a bare name or "owner~name" and id is a "<hex>.ics" filename
|
||||
// from the eventIDRe character set. Both shapes share the same
|
||||
// "hex/alnum .ics" shape, so we only need the regex check.
|
||||
if !eventIDRe.MatchString(id) {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
color, form, err := s.eventForDisplay(username, ref, id)
|
||||
if errors.Is(err, store.ErrNotFound) {
|
||||
if errors.Is(err, store.ErrNotFound) || errors.Is(err, errCalendarNotFound) {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
@@ -670,8 +676,12 @@ func (s *Server) handleEventView(w http.ResponseWriter, r *http.Request) {
|
||||
// eventForDisplay resolves a calendar reference for read access, loads and
|
||||
// decodes the event with the given id, populates the form's display fields
|
||||
// (CalRef, CalendarLabel, Writable), and returns the calendar's color for
|
||||
// the detail view's header dot.
|
||||
// the detail view's header dot. It handles both ordinary (stored) events
|
||||
// and ICS-subscription events (ref "!<name>").
|
||||
func (s *Server) eventForDisplay(username, ref, id string) (color string, form templates.EventFormData, err error) {
|
||||
if strings.HasPrefix(ref, icsRefPrefix) {
|
||||
return s.icsEventForDisplay(username, ref, id)
|
||||
}
|
||||
owner, name, err := s.resolveCalRef(username, ref, false)
|
||||
if err != nil {
|
||||
return "", form, err
|
||||
@@ -701,6 +711,35 @@ func (s *Server) eventForDisplay(username, ref, id string) (color string, form t
|
||||
return color, form, nil
|
||||
}
|
||||
|
||||
// icsEventForDisplay resolves one of username's ICS subscriptions named
|
||||
// ref[len(!):] and looks up the event whose icssub.EventID hashes to id.
|
||||
// Returns the subscription's display color and a form with Writable=false.
|
||||
func (s *Server) icsEventForDisplay(username, ref, id string) (color string, form templates.EventFormData, err error) {
|
||||
name := strings.TrimPrefix(ref, icsRefPrefix)
|
||||
sub, err := s.dbase.GetICSSubscription(username, name)
|
||||
if err != nil {
|
||||
return "", form, errCalendarNotFound
|
||||
}
|
||||
cal, err := s.icsCache.Get(sub.URL)
|
||||
if err != nil {
|
||||
return "", form, err
|
||||
}
|
||||
for _, ev := range cal.Events() {
|
||||
if icssub.EventID(ev) != id {
|
||||
continue
|
||||
}
|
||||
form, err = eventFormFromComponent(id, ev)
|
||||
if err != nil {
|
||||
return "", form, err
|
||||
}
|
||||
form.CalRef = ref
|
||||
form.CalendarLabel = name
|
||||
form.Writable = false
|
||||
return sub.Color, form, nil
|
||||
}
|
||||
return "", form, errCalendarNotFound
|
||||
}
|
||||
|
||||
func (s *Server) handleEventEdit(w http.ResponseWriter, r *http.Request) {
|
||||
username := userFromContext(r.Context())
|
||||
ref := r.PathValue("ref")
|
||||
@@ -1264,11 +1303,11 @@ func (s *Server) addBirthdayEvents(username string, entry calendarEntry, gridSta
|
||||
}
|
||||
|
||||
// addICSEvents fetches entry's remote ICS/webcal calendar (via s.icsCache,
|
||||
// which caches it for a while so every month-view render doesn't re-fetch
|
||||
// from origin) and places each VEVENT's occurrence onto the month grid,
|
||||
// the same way a stored calendar object would be. There's no per-event
|
||||
// edit page for these (the source is external and read-only), so each
|
||||
// event's LinkURL is left pointing nowhere useful ("#").
|
||||
// which uses stale-while-revalidate so a month-view render never blocks on
|
||||
// network I/O) and places each VEVENT's occurrence onto the month grid,
|
||||
// the same way a stored calendar object would be. Each event's ID is the
|
||||
// stable icssub.EventID hash, which routes through the read-only detail
|
||||
// page (eventForDisplay → icsEventForDisplay).
|
||||
func (s *Server) addICSEvents(entry calendarEntry, gridStart, gridEnd time.Time, loc *time.Location, dayIndex map[string]int, days []templates.MonthDay) error {
|
||||
cal, err := s.icsCache.Get(entry.ICSURL)
|
||||
if err != nil {
|
||||
@@ -1276,8 +1315,11 @@ func (s *Server) addICSEvents(entry calendarEntry, gridStart, gridEnd time.Time,
|
||||
}
|
||||
|
||||
const totalDays = 42
|
||||
for i, ev := range cal.Events() {
|
||||
id := fmt.Sprintf("ics-%d", i)
|
||||
for _, ev := range cal.Events() {
|
||||
id := icssub.EventID(ev)
|
||||
if id == "" {
|
||||
continue
|
||||
}
|
||||
form, err := eventFormFromComponent(id, ev)
|
||||
if err != nil {
|
||||
continue
|
||||
@@ -1311,7 +1353,6 @@ func (s *Server) addICSEvents(entry calendarEntry, gridStart, gridEnd time.Time,
|
||||
Summary: form.Summary,
|
||||
TimeText: timeText,
|
||||
AllDay: form.AllDay,
|
||||
LinkURL: "#",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user