some ICS fixes, handle empty calendar list

This commit is contained in:
Stefan Bühler 2024-12-18 15:23:37 +01:00
parent d7d86601f0
commit 6bf9758605

View File

@ -89,7 +89,7 @@ function display_event_date(item) {
return m("span.timerange", display_date_utc(start, utc_start_date), " - ", display_date_utc(end_day, utc_end_day_date)); return m("span.timerange", display_date_utc(start, utc_start_date), " - ", display_date_utc(end_day, utc_end_day_date));
} }
} }
if (start_date === end_date) { if (start_date === end_date) {
return m("span.timerange", display_datetime(start, `${start_date}, ${start_time}`), " - ", display_datetime(end, end_time)); return m("span.timerange", display_datetime(start, `${start_date}, ${start_time}`), " - ", display_datetime(end, end_time));
} else { } else {
@ -102,31 +102,43 @@ function display_event_date(item) {
} }
} }
function escapeICSField(text) { function format_ICS_text(text) {
return text.replace(/\\/g, "\\\\") return text.replace(/[\\,;]/g, "\\$&") // escape backslash and separators
.replace(/[,;]/g, "\\$&") .replace(/\n/g, "\\n") // escape newline
.replace(/\n/g, "\\n"); .replace(/[\x00-\x1f]+/g, " ") // replace control characters with whitespace
;
}
function format_ICS_date(date) {
if (date) {
date = new Date(date);
} else {
date = new Date(); // default to "now"
}
// remove "-" and ":" separators (but keep "T"), and drop microseconds
return date.toISOString().replace(/-|:|\.\d+/g, "");
} }
function createICSLink(item, settings) { function createICSLink(item, settings) {
const start = new Date(item.calculated.startDate).toISOString().replace(/-|:|\.\d+/g, ''); const start = format_ICS_date(item.calculated.startDate);
const end = new Date(item.calculated.endDate).toISOString().replace(/-|:|\.\d+/g, ''); const end = format_ICS_date(item.calculated.endDate);
const summary = item.base.caption; const summary = item.base.caption;
const description = item.base.information || ""; const description = item.base.information || "";
const location = item.base.address?.meetingAt || ""; const location = item.base.address?.meetingAt || "";
// Escape special characters for ICS fields // Escape special characters for ICS fields
const escapedSummary = escapeICSField(summary /* .replace(/['"]/g, "") */); const escapedSummary = format_ICS_text(summary /* .replace(/['"]/g, "") */);
const escapedDescription = escapeICSField(description /* .replace(/['"]/g, "") */); const escapedDescription = format_ICS_text(description /* .replace(/['"]/g, "") */);
const escapedLocation = escapeICSField(location); const escapedLocation = format_ICS_text(location);
const icsContent = `BEGIN:VCALENDAR const icsContent = `BEGIN:VCALENDAR
VERSION:2.0 VERSION:2.0
PRODID:-//Evang Kirchengemeinde Malmsheim//Gottesdienste//EN PRODID:-//churchtools-next-events//EN
BEGIN:VEVENT BEGIN:VEVENT
UID:${crypto.randomUUID()}@malmsheim-evangelisch.de UID:appointment-${item.base.id}@${window.location.hostname}
DTSTAMP:${new Date().toISOString().replace(/-|:|\.\d+/g, '')} CREATED:${format_ICS_date(item.base.meta?.createdDate)}
LAST-MODIFIED:${format_ICS_date(item.base.meta?.modifiedDate)}
DTSTART:${start} DTSTART:${start}
DTEND:${end} DTEND:${end}
SUMMARY:${escapedSummary} SUMMARY:${escapedSummary}
@ -273,7 +285,7 @@ var AppointmentLocation = {
if (!location) { if (!location) {
return m("["); return m("[");
} }
return m("p", [ return m("p", [
fa_solid_icon(settings, "location-dot"), fa_solid_icon(settings, "location-dot"),
" ", " ",
@ -293,7 +305,7 @@ var AppointmentLink = {
if (!url.startsWith("http")) { if (!url.startsWith("http")) {
url = "https://" + url; url = "https://" + url;
} }
return m("p", [ return m("p", [
fa_solid_icon(settings, "link"), fa_solid_icon(settings, "link"),
" ", " ",
@ -389,24 +401,30 @@ async function load_events(dom_node, defaults) {
credentials: "omit", credentials: "omit",
mode: "cors", mode: "cors",
} }
const r_cal_regex = new RegExp(settings.calendar_regex, "i"); const r_cal_regex = new RegExp(settings.calendar_regex, "i");
const r_event_regex = new RegExp(settings.event_regex, "i"); const r_event_regex = new RegExp(settings.event_regex, "i");
const all_calendars = (await (await fetch(settings.instance + "/api/calendars", req_config)).json()).data; const all_calendars = (await (await fetch(settings.instance + "/api/calendars", req_config)).json()).data;
const calendars = all_calendars.filter((calendar) => r_cal_regex.exec(calendar.name)); const calendars = all_calendars.filter((calendar) => r_cal_regex.exec(calendar.name));
const calendar_ids = calendars.map((calendar) => calendar.id); const calendar_ids = calendars.map((calendar) => calendar.id);
const filters = calendar_ids.map((cal_id) => `calendar_ids[]=${cal_id}`); let appointments;
const from = new Date();
filters.push(`from=${format_date(from)}`); if (calendar_ids.length) {
const to = new Date(from.valueOf() + settings.days * 86400 * 1000); const filters = calendar_ids.map((cal_id) => `calendar_ids[]=${cal_id}`);
filters.push(`to=${format_date(to)}`); const from = new Date();
const query = filters.join("&"); filters.push(`from=${format_date(from)}`);
const all_appointments = (await (await fetch(settings.instance + `/api/calendars/appointments?${query}`, req_config)).json()).data; const to = new Date(from.valueOf() + settings.days * 86400 * 1000);
const appointments = all_appointments.filter((appointment) => r_event_regex.exec(appointment.base.title)); filters.push(`to=${format_date(to)}`);
const query = filters.join("&");
if (appointments.length > settings.limit) { const all_appointments = (await (await fetch(settings.instance + `/api/calendars/appointments?${query}`, req_config)).json()).data;
appointments.length = settings.limit; appointments = all_appointments.filter((appointment) => r_event_regex.exec(appointment.base.title));
if (appointments.length > settings.limit) {
appointments.length = settings.limit;
}
} else {
appointments = [];
} }
const Appointments = { const Appointments = {