/*! FullCalendar iCalendar Plugin v6.0.3 Docs & License: https://fullcalendar.io/docs/icalendar (c) 2022 Adam Shaw */ FullCalendar.ICalendar = (function (exports, core, internal, ICAL) { 'use strict'; function _interopNamespace(e) { if (e && e.__esModule) return e; var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n["default"] = e; return n; } var ICAL__namespace = /*#__PURE__*/_interopNamespace(ICAL); /* eslint-disable */ class IcalExpander { constructor(opts) { this.maxIterations = opts.maxIterations != null ? opts.maxIterations : 1000; this.skipInvalidDates = opts.skipInvalidDates != null ? opts.skipInvalidDates : false; this.jCalData = ICAL__namespace.parse(opts.ics); this.component = new ICAL__namespace.Component(this.jCalData); this.events = this.component.getAllSubcomponents('vevent').map(vevent => new ICAL__namespace.Event(vevent)); if (this.skipInvalidDates) { this.events = this.events.filter((evt) => { try { evt.startDate.toJSDate(); evt.endDate.toJSDate(); return true; } catch (err) { // skipping events with invalid time return false; } }); } } between(after, before) { function isEventWithinRange(startTime, endTime) { return (!after || endTime >= after.getTime()) && (!before || startTime <= before.getTime()); } function getTimes(eventOrOccurrence) { const startTime = eventOrOccurrence.startDate.toJSDate().getTime(); let endTime = eventOrOccurrence.endDate.toJSDate().getTime(); // If it is an all day event, the end date is set to 00:00 of the next day // So we need to make it be 23:59:59 to compare correctly with the given range if (eventOrOccurrence.endDate.isDate && (endTime > startTime)) { endTime -= 1; } return { startTime, endTime }; } const exceptions = []; this.events.forEach((event) => { if (event.isRecurrenceException()) exceptions.push(event); }); const ret = { events: [], occurrences: [], }; this.events.filter(e => !e.isRecurrenceException()).forEach((event) => { const exdates = []; event.component.getAllProperties('exdate').forEach((exdateProp) => { const exdate = exdateProp.getFirstValue(); exdates.push(exdate.toJSDate().getTime()); }); // Recurring event is handled differently if (event.isRecurring()) { const iterator = event.iterator(); let next; let i = 0; do { i += 1; next = iterator.next(); if (next) { const occurrence = event.getOccurrenceDetails(next); const { startTime, endTime } = getTimes(occurrence); const isOccurrenceExcluded = exdates.indexOf(startTime) !== -1; // TODO check that within same day? const exception = exceptions.find(ex => ex.uid === event.uid && ex.recurrenceId.toJSDate().getTime() === occurrence.startDate.toJSDate().getTime()); // We have passed the max date, stop if (before && startTime > before.getTime()) break; // Check that we are within our range if (isEventWithinRange(startTime, endTime)) { if (exception) { ret.events.push(exception); } else if (!isOccurrenceExcluded) { ret.occurrences.push(occurrence); } } } } while (next && (!this.maxIterations || i < this.maxIterations)); return; } // Non-recurring event: const { startTime, endTime } = getTimes(event); if (isEventWithinRange(startTime, endTime)) ret.events.push(event); }); return ret; } before(before) { return this.between(undefined, before); } after(after) { return this.between(after); } all() { return this.between(); } } const eventSourceDef = { parseMeta(refined) { if (refined.url && refined.format === 'ics') { return { url: refined.url, format: 'ics', }; } return null; }, fetch(arg, successCallback, errorCallback) { let meta = arg.eventSource.meta; let { internalState } = meta; /* NOTE: isRefetch is a HACK. we would do the recurring-expanding in a separate plugin hook, but we couldn't leverage built-in allDay-guessing, among other things. */ if (!internalState || arg.isRefetch) { internalState = meta.internalState = { response: null, iCalExpanderPromise: fetch(meta.url, { method: 'GET' }).then((response) => { return response.text().then((icsText) => { internalState.response = response; return new IcalExpander({ ics: icsText, skipInvalidDates: true, }); }); }), }; } internalState.iCalExpanderPromise.then((iCalExpander) => { successCallback({ rawEvents: expandICalEvents(iCalExpander, arg.range), response: internalState.response, }); }, errorCallback); }, }; function expandICalEvents(iCalExpander, range) { // expand the range. because our `range` is timeZone-agnostic UTC // or maybe because ical.js always produces dates in local time? i forget let rangeStart = internal.addDays(range.start, -1); let rangeEnd = internal.addDays(range.end, 1); let iCalRes = iCalExpander.between(rangeStart, rangeEnd); // end inclusive. will give extra results let expanded = []; // TODO: instead of using startDate/endDate.toString to communicate allDay, // we can query startDate/endDate.isDate. More efficient to avoid formatting/reparsing. // single events for (let iCalEvent of iCalRes.events) { expanded.push(Object.assign(Object.assign({}, buildNonDateProps(iCalEvent)), { start: iCalEvent.startDate.toString(), end: (specifiesEnd(iCalEvent) && iCalEvent.endDate) ? iCalEvent.endDate.toString() : null })); } // recurring event instances for (let iCalOccurence of iCalRes.occurrences) { let iCalEvent = iCalOccurence.item; expanded.push(Object.assign(Object.assign({}, buildNonDateProps(iCalEvent)), { start: iCalOccurence.startDate.toString(), end: (specifiesEnd(iCalEvent) && iCalOccurence.endDate) ? iCalOccurence.endDate.toString() : null })); } return expanded; } function buildNonDateProps(iCalEvent) { return { title: iCalEvent.summary, url: extractEventUrl(iCalEvent), extendedProps: { location: iCalEvent.location, organizer: iCalEvent.organizer, description: iCalEvent.description, }, }; } function extractEventUrl(iCalEvent) { let urlProp = iCalEvent.component.getFirstProperty('url'); return urlProp ? urlProp.getFirstValue() : ''; } function specifiesEnd(iCalEvent) { return Boolean(iCalEvent.component.getFirstProperty('dtend')) || Boolean(iCalEvent.component.getFirstProperty('duration')); } var plugin = core.createPlugin({ name: '@fullcalendar/icalendar', eventSourceDefs: [eventSourceDef], }); core.globalPlugins.push(plugin); exports["default"] = plugin; Object.defineProperty(exports, '__esModule', { value: true }); return exports; })({}, FullCalendar, FullCalendar.Internal, ICAL);