-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathindex-cal.js
143 lines (124 loc) · 5.01 KB
/
index-cal.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
// assumes ical.js, rrule.js loaded
const icsUrl = 'calendar.ics';
function escape(str) {
return str.replace(/[&<>"'/]/g, function (char) {
const escapeMap = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
'/': '/'
};
return escapeMap[char] || char;
});
}
async function fetchCalendar(url) {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch calendar: ${response.status} ${response.statusText}`);
}
const icsData = await response.text();
const jcalData = ICAL.parse(icsData);
const vcalendar = new ICAL.Component(jcalData);
return vcalendar;
}
// Armed with a list of events, draw them into an HTML ul list.
function drawEventList(events) {
const dateOptions = {
weekday: 'long',
month: "numeric",
day: "numeric",
};
const timeOptions = {
hour: "numeric",
minute: "numeric",
};
const ul = document.createElement('ul');
events.forEach(event => {
const li = document.createElement('li');
const dateStr = `${event.start.toLocaleDateString('en-US', dateOptions)}`.toLocaleLowerCase();
const timeStr = `${event.start.toLocaleTimeString('en-US', timeOptions)} - ${event.end.toLocaleTimeString('en-US', timeOptions)}`.toLocaleLowerCase();
const descriptionStr = event.description ? `<br>${escape(event.description)}` : '';
const recurrence = event.recurrence ? `${event.recurrence}<br>`.toLocaleLowerCase() : '';
li.innerHTML = `${escape(event.summary)}<span class="secondary">${descriptionStr}<br>${recurrence}${dateStr}<br>${timeStr}</span>`;
ul.appendChild(li);
});
return ul;
}
// Given an ical event, return a human-readable description of the recurrence
// (such as "every week").
function describeRecurrence(icalEvent) {
const ruleStr = icalEvent.component.getFirstPropertyValue('rrule').toString();
const parsed = rrule.RRule.fromString(ruleStr);
return parsed.toText();
}
// Given a parsed ical.js calendar, return a list of upcoming events ("upcoming"
// here, for now, means events after 3 hours ago. This is so that in-progress
// events are still shown).
function getUpcomingEvents(vcalendar, numEvents = 10) {
const events = vcalendar.getAllSubcomponents('vevent');
const hoursAfterEnd = ICAL.Time.now();
hoursAfterEnd.addDuration(ICAL.Duration.fromSeconds(-3 * 60 * 60));
const upcomingEvents = [];
let titlesSeen = new Set();
events.forEach(event => {
const icalEvent = new ICAL.Event(event);
if (icalEvent.isRecurring()) {
// an attempt at preventing duplicate events when the same event was
// preempted by an one-off instance created by editing the single
// occurrence in NextCloud.
if (titlesSeen.has(icalEvent.summary)) {
return;
}
const expand = icalEvent.iterator();
let next;
// only add the next instance (later we show the recurrence)
// so this actually only runs once
while (next = expand.next()) {
const duration = icalEvent.duration;
const end = next.clone();
end.addDuration(duration);
if (end.compare(hoursAfterEnd) >= 0) {
titlesSeen.add(icalEvent.summary);
upcomingEvents.push({
summary: icalEvent.summary,
description: icalEvent.description,
start: next.toJSDate(),
end: end.toJSDate(),
recurrence: describeRecurrence(icalEvent)
});
break;
}
}
} else {
const eventStart = icalEvent.startDate;
if (eventStart.compare(hoursAfterEnd) >= 0) {
titlesSeen.add(icalEvent.summary);
upcomingEvents.push({
summary: icalEvent.summary,
description: icalEvent.description,
start: eventStart.toJSDate(),
end: icalEvent.endDate.toJSDate(),
});
}
}
});
console.log("events added: " + Array.from(titlesSeen));
return upcomingEvents.sort((a, b) => a.start - b.start).slice(0, numEvents);
}
// Main function :)
async function doCalendar(calendarUrl, targetElementId) {
const targetElement = document.getElementById(targetElementId);
try {
const vcalendar = await fetchCalendar(calendarUrl);
const upcomingEvents = getUpcomingEvents(vcalendar);
const eventList = drawEventList(upcomingEvents);
targetElement.innerHTML = '';
targetElement.appendChild(eventList);
}
catch (error) {
targetElement.innerHTML = '<p>Sorry - couldn\'t load the calendar :(</p> <small>' + error + '</small>';
}
}
doCalendar(icsUrl, 'cal-parsed');