From b7a64ed581cddf8d2f4e987c041bf4a6909cda78 Mon Sep 17 00:00:00 2001 From: maximsmol <1472826+maximsmol@users.noreply.github.com> Date: Mon, 16 Feb 2026 15:45:02 -0800 Subject: [PATCH 1/2] ical pulling draft Signed-off-by: maximsmol <1472826+maximsmol@users.noreply.github.com> --- _events/_events.11tydata.js | 7 +++++- _events/_ical.md | 11 ++++++++++ _includes/event-card.html | 5 ++++- _includes/events.html | 2 +- eleventy.config.js | 43 +++++++++++++++++++++++++++++++++++-- eslint.config.ts | 2 ++ package.json | 1 + pnpm-lock.yaml | 31 ++++++++++++++++++++++++++ 8 files changed, 97 insertions(+), 5 deletions(-) create mode 100644 _events/_ical.md diff --git a/_events/_events.11tydata.js b/_events/_events.11tydata.js index ca5b3f6f..48c295b9 100644 --- a/_events/_events.11tydata.js +++ b/_events/_events.11tydata.js @@ -7,6 +7,8 @@ export default { layout: "event.11ty.tsx", eleventyComputed: { date(data) { + if (data.event != null) return; + const res = DateTime.fromFormat(data.date, "yyyy-MM-dd HH:mm", { zone: "America/New_York", }).toJSDate(); @@ -14,7 +16,10 @@ export default { return res; }, excerpt(data) { - const res = data.page.rawInput.trim().split("\n\n")[0]; + let content = data.page.rawInput; + if (data.event != null) content = data.event.content ?? ""; + + const res = content.trim().split("\n\n")[0]; data.page.excerpt = res; return res; }, diff --git a/_events/_ical.md b/_events/_ical.md new file mode 100644 index 00000000..d73df16c --- /dev/null +++ b/_events/_ical.md @@ -0,0 +1,11 @@ +--- +pagination: + data: ical_events + size: 1 + alias: event +permalink: "{{ event.url }}" +eleventyComputed: + title: "{{ event.data.title }}" +--- + +{{ event.content }} diff --git a/_includes/event-card.html b/_includes/event-card.html index 3d3abfb8..ad4b4400 100644 --- a/_includes/event-card.html +++ b/_includes/event-card.html @@ -47,7 +47,10 @@

📆
+ + {%- if event.data.locations -%} +
📍 {% include locations.html%} + {%- endif -%} diff --git a/_includes/events.html b/_includes/events.html index e78be12e..94d2fb55 100644 --- a/_includes/events.html +++ b/_includes/events.html @@ -1,6 +1,6 @@ {% assign berlin_events = site.data.berlin_events %} {% assign nl_events = site.data.nl_events %} -{% assign events = site.events | concat: nl_events | concat: berlin_events | date_sort | filter_tags: include.tags %} +{% assign events = site.events | concat: ical_events | concat: nl_events | concat: berlin_events | date_sort | filter_tags: include.tags %} {% assign future_events = events | where_future: site.time %} {% assign past_events = events | where_past: site.time %} diff --git a/eleventy.config.js b/eleventy.config.js index 0137f39c..9981ea6a 100644 --- a/eleventy.config.js +++ b/eleventy.config.js @@ -7,6 +7,7 @@ import * as jsxRuntime from "hastscript/jsx-runtime"; import { DateTime } from "luxon"; import markdownIt from "markdown-it"; import markdownItAnchor from "markdown-it-anchor"; +import ical from "node-ical"; import rehypeSlug from "rehype-slug"; import YAML from "yaml"; @@ -67,6 +68,7 @@ export default async (cfg) => { cfg.addDataExtension("yml", YAML.parse); cfg.addDataExtension("yaml", YAML.parse); + // fixme(maximsmol): calendar events do not get loaded into RSS const getRssConfig = (collection, name) => ({ type: "atom", outputPath: `/feed/${collection}.xml`, @@ -240,6 +242,41 @@ export default async (cfg) => { }); cfg.addTemplateFormats("mdx"); + const fetchCalendar = async () => { + const data = await ical.fromURL( + "https://dev.techworkerscoalition.org/nextcloud/remote.php/dav/public-calendars/EHFzzZQXSQoS3f5w/?export", + ); + + if (data == null) throw new Error("failed to load calendar data"); + + const events = []; + for (const [, entry] of Object.entries(data)) { + if (entry == null) continue; + if (entry.type !== "VEVENT") continue; + + const start = DateTime.fromJSDate(entry.start, { + zone: entry.start.tz, + }); + // todo(maximsmol): deal with recurring events + // todo(maximsmol): extract image from content + events.push({ + // todo(maximsmol): use a timezone-aware datetime? + date: start.toJSDate(), + url: `/events/${entry.uid}/`, + data: { + title: entry.summary, + time_zones: entry.start.tz != null ? [entry.start.tz] : undefined, + // todo(maximsmol): add locations + // locations: [], + }, + content: + entry.description != null ? md.render(entry.description) : undefined, + }); + } + + cfg.addGlobalData("ical_events", events); + }; + const remoteDataSrcs = [ { data: "berlin_press", @@ -254,7 +291,7 @@ export default async (cfg) => { url: "https://techwerkers.nl/en/twc-global/index.yaml", }, ]; - const deferred = []; + const deferred = [fetchCalendar()]; for (const x of remoteDataSrcs) deferred.push( (async () => { @@ -265,7 +302,9 @@ export default async (cfg) => { }); const data = YAML.parse(await req.text()); - console.log(`Fetched: ${x.data} from ${x.url}`); + console.log( + `Fetched ${data.length} ${x.data} items from from ${x.url}`, + ); cfg.addGlobalData(x.data, data); })(), diff --git a/eslint.config.ts b/eslint.config.ts index fc93f120..e9e04801 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -63,6 +63,8 @@ export default defineConfig([ tseslint.configs.stylisticTypeChecked, ], rules: { + "@typescript-eslint/no-confusing-void-expression": "off", + "@typescript-eslint/consistent-type-definitions": ["error", "type"], }, languageOptions: { diff --git a/package.json b/package.json index 08ca5800..962de612 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "luxon": "^3.7.2", "markdown-it": "^14.1.0", "markdown-it-anchor": "^9.2.0", + "node-ical": "^0.25.2", "rehype-slug": "^6.0.0", "tsx": "^4.21.0", "typescript-eslint": "^8.54.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 93f82e8a..db0a8824 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -94,6 +94,9 @@ importers: markdown-it-anchor: specifier: ^9.2.0 version: 9.2.0(@types/markdown-it@14.1.2)(markdown-it@14.1.0) + node-ical: + specifier: ^0.25.2 + version: 0.25.2 rehype-slug: specifier: ^6.0.0 version: 6.0.0(patch_hash=1bf83dfa419d0209fd1fba0392ca594fe12bf7a8aa0cc20d96d4d10ed6f8318f) @@ -370,6 +373,10 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} + '@js-temporal/polyfill@0.5.1': + resolution: {integrity: sha512-hloP58zRVCRSpgDxmqCWJNlizAlUgJFqG2ypq79DCvyv9tHjRYMDOcPFjzfl/A1/YxDvRCZz8wvZvmapQnKwFQ==} + engines: {node: '>=12'} + '@mdx-js/mdx@3.1.1': resolution: {integrity: sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==} @@ -1564,6 +1571,9 @@ packages: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true + jsbi@4.3.2: + resolution: {integrity: sha512-9fqMSQbhJykSeii05nxKl4m6Eqn2P6rOlYiS+C5Dr/HPIU/7yZxu5qzbs40tgaFORiw2Amd0mirjxatXYMkIew==} + jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} @@ -1892,6 +1902,10 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + node-ical@0.25.2: + resolution: {integrity: sha512-DJnT/UqBPxVO7Inx5RMuSSqtyk9oUz2DLiKsvlSjGNycUACSM5gGYSkKLIerwJXTeFxs3X24R9ds4iDwd5z4KA==} + engines: {node: '>=18'} + node-releases@2.0.27: resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} @@ -2177,6 +2191,9 @@ packages: resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} engines: {node: '>= 4'} + rrule-temporal@1.4.6: + resolution: {integrity: sha512-eRruK01K8uu0VWgn+lvKYXGZCER7eI2EaoAhsh5wwHaYMc4jPJ1cvY+bD0hBbYwCucggMpjtuwH2RpEH9oLfWg==} + sade@1.8.1: resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} engines: {node: '>=6'} @@ -2868,6 +2885,10 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 + '@js-temporal/polyfill@0.5.1': + dependencies: + jsbi: 4.3.2 + '@mdx-js/mdx@3.1.1(patch_hash=c3747fa40d5fa8b461c601e2f709b66223999ee269d6985ade30e835bf76f834)': dependencies: '@types/estree': 1.0.8 @@ -4363,6 +4384,8 @@ snapshots: dependencies: argparse: 2.0.1 + jsbi@4.3.2: {} + jsesc@3.1.0: {} json-buffer@3.0.1: {} @@ -4836,6 +4859,10 @@ snapshots: natural-compare@1.4.0: {} + node-ical@0.25.2: + dependencies: + rrule-temporal: 1.4.6 + node-releases@2.0.27: {} node-retrieve-globals@6.0.1: @@ -5171,6 +5198,10 @@ snapshots: retry@0.12.0: {} + rrule-temporal@1.4.6: + dependencies: + '@js-temporal/polyfill': 0.5.1 + sade@1.8.1: dependencies: mri: 1.2.0 From d0ed9f946444b1809bba91a92164d3e50aa22183 Mon Sep 17 00:00:00 2001 From: maximsmol <1472826+maximsmol@users.noreply.github.com> Date: Wed, 4 Mar 2026 11:42:40 -0800 Subject: [PATCH 2/2] pull locations and images (latter doesnt work) Signed-off-by: maximsmol <1472826+maximsmol@users.noreply.github.com> --- eleventy.config.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/eleventy.config.js b/eleventy.config.js index 9981ea6a..ded181a1 100644 --- a/eleventy.config.js +++ b/eleventy.config.js @@ -243,9 +243,9 @@ export default async (cfg) => { cfg.addTemplateFormats("mdx"); const fetchCalendar = async () => { - const data = await ical.fromURL( - "https://dev.techworkerscoalition.org/nextcloud/remote.php/dav/public-calendars/EHFzzZQXSQoS3f5w/?export", - ); + const base = + "https://dev.techworkerscoalition.org/nextcloud/remote.php/dav/public-calendars/EHFzzZQXSQoS3f5w/?export"; + const data = await ical.fromURL(base); if (data == null) throw new Error("failed to load calendar data"); @@ -254,6 +254,12 @@ export default async (cfg) => { if (entry == null) continue; if (entry.type !== "VEVENT") continue; + const img = + entry.attach?.val != null && + entry.attach?.params.FMTTYPE.startsWith("image/") + ? new URL(entry.attach.val, base) + : undefined; + const start = DateTime.fromJSDate(entry.start, { zone: entry.start.tz, }); @@ -266,8 +272,8 @@ export default async (cfg) => { data: { title: entry.summary, time_zones: entry.start.tz != null ? [entry.start.tz] : undefined, - // todo(maximsmol): add locations - // locations: [], + image: img?.href, + locations: entry.location != null ? [entry.location] : undefined, }, content: entry.description != null ? md.render(entry.description) : undefined,