switch to ics files; make calendars work; fix some minor issues
This commit is contained in:
parent
5368e75240
commit
c28f04c6e8
19 changed files with 318 additions and 259 deletions
113
static/js/upcoming.js
Normal file
113
static/js/upcoming.js
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
import ICAL from "https://unpkg.com/ical.js/dist/ical.min.js";
|
||||
|
||||
/**
|
||||
* Parse an ICS calendar and return upcoming event occurrences.
|
||||
*
|
||||
* @param {string} icsText The contents of the .ics file
|
||||
* @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}[]}
|
||||
*/
|
||||
function getUpcomingEvents(icsText, now, maxEvents, maxDays) {
|
||||
const jcal = ICAL.parse(icsText);
|
||||
const calendar = new ICAL.Component(jcal);
|
||||
|
||||
const end = new Date(now.getTime());
|
||||
end.setDate(end.getDate() + maxDays);
|
||||
|
||||
const events = [];
|
||||
|
||||
for (const component of calendar.getAllSubcomponents("vevent")) {
|
||||
const event = new ICAL.Event(component);
|
||||
|
||||
if (!event.startDate) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (event.isRecurring()) {
|
||||
const iterator = event.iterator();
|
||||
|
||||
while (true) {
|
||||
const occurrence = iterator.next();
|
||||
|
||||
if (!occurrence) {
|
||||
break;
|
||||
}
|
||||
|
||||
const start = occurrence.toJSDate();
|
||||
|
||||
// Recurrences are chronological, so we're done
|
||||
// once we pass the end of our search window.
|
||||
if (start > end) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (start > now) {
|
||||
events.push({
|
||||
start,
|
||||
name: event.summary ?? "",
|
||||
url: event.url ?? "",
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const start = event.startDate.toJSDate();
|
||||
|
||||
if (start > now && start <= end) {
|
||||
events.push({
|
||||
start,
|
||||
name: event.summary ?? "",
|
||||
url: event.url ?? "",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We have occurrences from multiple events, so sort them
|
||||
// before applying the maximum event count.
|
||||
events.sort((a, b) => a.start - b.start);
|
||||
|
||||
return events.slice(0, maxEvents);
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const ics = "/calendars/all.ics";
|
||||
const max_days = 20;
|
||||
const max_items = 5;
|
||||
|
||||
const now = new Date();
|
||||
|
||||
const table = document.getElementById("upcoming");
|
||||
fetch(ics)
|
||||
.then(response => response.text())
|
||||
.then(icsText => {
|
||||
getUpcomingEvents(icsText, now, max_items, max_days).forEach(event => {
|
||||
const row = document.createElement("tr");
|
||||
|
||||
const colBegin = document.createElement("td");
|
||||
|
||||
const formattedStart = event.start.toLocaleString("de-DE", {
|
||||
weekday: "long",
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
}).replace(",", "");
|
||||
|
||||
colBegin.innerText = `${formattedStart.replace(" ", ", ")} Uhr`;
|
||||
row.appendChild(colBegin);
|
||||
|
||||
const colName = document.createElement("td");
|
||||
|
||||
const a = document.createElement("a");
|
||||
a.href = event.url;
|
||||
a.text = event.name;
|
||||
|
||||
colName.appendChild(a);
|
||||
row.appendChild(colName);
|
||||
|
||||
table.appendChild(row);
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue