From b8ec4578185b5aa794bfbea41f4e099aa5a86359 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Sat, 22 Aug 2026 19:37:37 +0200 Subject: [PATCH] Link upcoming events to their ICS URL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "Nächste Veranstaltungen" table read the event URL via `ICAL.Event.url`, but ical.js does not expose a `url` getter on `ICAL.Event` (it only has uid, summary, description, color, location, sequence, the dates, organizer and attendees). `event.url` was therefore always `undefined` and the `?? ""` fallback turned it into an empty string, so every row rendered as ``, a dead link that just reloads the start page. Read the URL from the VEVENT component instead. This also picks up the `URL;VALUE=URI:` form used by most events in the published calendar, which is exported from a CalDAV client and does not use a bare `URL:` property. Not every event has a URL, so only wrap the name in a link when one is present and emit plain text otherwise. Checked against https://berlin.ccc.de/calendars/all.ics (31 events, 1 of them without a URL): before: CCCB Plenum after: CCCB Plenum after: Aktionstag gegen Überwachung im Chaos Computer Club Berlin Fixes: c28f04c6e85c ("switch to ics files; make calendars work; fix some minor issues") Assisted-by: Claude:claude-opus-5 Signed-off-by: Hauke Mehrtens --- static/js/upcoming.js | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/static/js/upcoming.js b/static/js/upcoming.js index 6b7a711..cc8ad0b 100644 --- a/static/js/upcoming.js +++ b/static/js/upcoming.js @@ -7,7 +7,7 @@ import ICAL from "https://unpkg.com/ical.js/dist/ical.min.js"; * @param {Date} now Events must start after this date * @param {number} maxEvents Maximum number of events to return * @param {number} maxDays Maximum number of days into the future - * @returns {{start: Date, name: string, url: string}[]} + * @returns {{start: Date, name: string, url: string}[]} url is empty when the event has no URL */ function getUpcomingEvents(icsText, now, maxEvents, maxDays) { const jcal = ICAL.parse(icsText); @@ -25,6 +25,9 @@ function getUpcomingEvents(icsText, now, maxEvents, maxDays) { continue; } + // ICAL.Event does not expose the URL property, so read it from the component. + const url = component.getFirstPropertyValue("url") ?? ""; + if (event.isRecurring()) { const iterator = event.iterator(); @@ -47,7 +50,7 @@ function getUpcomingEvents(icsText, now, maxEvents, maxDays) { events.push({ start, name: event.summary ?? "", - url: event.url ?? "", + url, }); } } @@ -58,7 +61,7 @@ function getUpcomingEvents(icsText, now, maxEvents, maxDays) { events.push({ start, name: event.summary ?? "", - url: event.url ?? "", + url, }); } } @@ -100,11 +103,16 @@ document.addEventListener("DOMContentLoaded", () => { const colName = document.createElement("td"); - const a = document.createElement("a"); - a.href = event.url; - a.text = event.name; + if (event.url) { + const a = document.createElement("a"); + a.href = event.url; + a.text = event.name; + + colName.appendChild(a); + } else { + colName.innerText = event.name; + } - colName.appendChild(a); row.appendChild(colName); table.appendChild(row);