@@ -29,7 +29,36 @@ const fetchIcal = async () => {
2929
3030 const _tmp = [];
3131 for (let k in cal ) _tmp .push (cal [k ]);
32- const events = _tmp .filter ((ev ) => ev .type == ' VEVENT' && ev .start && ev .start > now );
32+
33+ const events: CalendarComponent [] = [];
34+ _tmp .forEach ((ev ) => {
35+ if (ev .type !== ' VEVENT' || ! ev .start ) return ;
36+
37+ if (ev .rrule ) {
38+ // Expand the recurring events for the next 30 days to avoid clutter
39+ const limit = new Date (now .getTime () + 30 * 24 * 60 * 60 * 1000 );
40+ let dates = ev .rrule .between (now , limit , true );
41+
42+ // Fallback: If no occurrences in the next 30 days (e.g. quarterly/annual events),
43+ // fetch the immediate next occurrence.
44+ if (dates .length === 0 ) {
45+ const nextDate = ev .rrule .after (now , true );
46+ if (nextDate ) dates = [nextDate ];
47+ }
48+
49+ dates .forEach ((date ) => {
50+ const clone = { ... ev };
51+ clone .start = date ;
52+ if (ev .end ) {
53+ const duration = ev .end .getTime () - ev .start .getTime ();
54+ clone .end = new Date (date .getTime () + duration );
55+ }
56+ events .push (clone );
57+ });
58+ } else if (ev .start > now ) {
59+ events .push (ev );
60+ }
61+ });
3362
3463 // If there are no upcoming events, show one from the past
3564 if (events .length == 0 ) {
@@ -69,7 +98,7 @@ onMounted(fetchIcal);
6998<template >
7099 <div class =" description" v-if =" evs.length === 0" >Loading events...</div >
71100 <div role =" list" class =" events" >
72- <div v-for =" ev in evs" role =" listitem" :key =" ev.summary" >
101+ <div v-for =" ev in evs" role =" listitem" :key =" `${ ev.uid || ev. summary}-${ev.start ? ev.start.getTime() : ''}` " >
73102 <article class =" event" >
74103 <time class =" date" v-if =" ev.start" :datetime =" getEventDate(ev.start)" >
75104 <span class =" month" >{{ ev.start.toLocaleDateString('default', { month: 'short' }) }}</span >
0 commit comments