@fullcalendar/icalendar 5.11.3 → 6.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{LICENSE.txt → LICENSE.md} +0 -0
- package/index.cjs +217 -0
- package/index.d.ts +6 -0
- package/index.global.js +225 -0
- package/index.global.min.js +6 -0
- package/index.js +193 -0
- package/package.json +38 -18
- package/main.cjs.js +0 -271
- package/main.d.ts +0 -6
- package/main.global.js +0 -300
- package/main.global.min.js +0 -6
- package/main.js +0 -246
- package/main.js.map +0 -1
|
File without changes
|
package/index.cjs
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var index_cjs = require('@fullcalendar/core/index.cjs');
|
|
6
|
+
var internal_cjs = require('@fullcalendar/core/internal.cjs');
|
|
7
|
+
var ICAL = require('ical.js');
|
|
8
|
+
|
|
9
|
+
function _interopNamespace(e) {
|
|
10
|
+
if (e && e.__esModule) return e;
|
|
11
|
+
var n = Object.create(null);
|
|
12
|
+
if (e) {
|
|
13
|
+
Object.keys(e).forEach(function (k) {
|
|
14
|
+
if (k !== 'default') {
|
|
15
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
16
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
17
|
+
enumerable: true,
|
|
18
|
+
get: function () { return e[k]; }
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
n["default"] = e;
|
|
24
|
+
return Object.freeze(n);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
var ICAL__namespace = /*#__PURE__*/_interopNamespace(ICAL);
|
|
28
|
+
|
|
29
|
+
/* eslint-disable */
|
|
30
|
+
class IcalExpander {
|
|
31
|
+
constructor(opts) {
|
|
32
|
+
this.maxIterations = opts.maxIterations != null ? opts.maxIterations : 1000;
|
|
33
|
+
this.skipInvalidDates = opts.skipInvalidDates != null ? opts.skipInvalidDates : false;
|
|
34
|
+
this.jCalData = ICAL__namespace.parse(opts.ics);
|
|
35
|
+
this.component = new ICAL__namespace.Component(this.jCalData);
|
|
36
|
+
this.events = this.component.getAllSubcomponents('vevent').map(vevent => new ICAL__namespace.Event(vevent));
|
|
37
|
+
if (this.skipInvalidDates) {
|
|
38
|
+
this.events = this.events.filter((evt) => {
|
|
39
|
+
try {
|
|
40
|
+
evt.startDate.toJSDate();
|
|
41
|
+
evt.endDate.toJSDate();
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
// skipping events with invalid time
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
between(after, before) {
|
|
52
|
+
function isEventWithinRange(startTime, endTime) {
|
|
53
|
+
return (!after || endTime >= after.getTime()) &&
|
|
54
|
+
(!before || startTime <= before.getTime());
|
|
55
|
+
}
|
|
56
|
+
function getTimes(eventOrOccurrence) {
|
|
57
|
+
const startTime = eventOrOccurrence.startDate.toJSDate().getTime();
|
|
58
|
+
let endTime = eventOrOccurrence.endDate.toJSDate().getTime();
|
|
59
|
+
// If it is an all day event, the end date is set to 00:00 of the next day
|
|
60
|
+
// So we need to make it be 23:59:59 to compare correctly with the given range
|
|
61
|
+
if (eventOrOccurrence.endDate.isDate && (endTime > startTime)) {
|
|
62
|
+
endTime -= 1;
|
|
63
|
+
}
|
|
64
|
+
return { startTime, endTime };
|
|
65
|
+
}
|
|
66
|
+
const exceptions = [];
|
|
67
|
+
this.events.forEach((event) => {
|
|
68
|
+
if (event.isRecurrenceException())
|
|
69
|
+
exceptions.push(event);
|
|
70
|
+
});
|
|
71
|
+
const ret = {
|
|
72
|
+
events: [],
|
|
73
|
+
occurrences: [],
|
|
74
|
+
};
|
|
75
|
+
this.events.filter(e => !e.isRecurrenceException()).forEach((event) => {
|
|
76
|
+
const exdates = [];
|
|
77
|
+
event.component.getAllProperties('exdate').forEach((exdateProp) => {
|
|
78
|
+
const exdate = exdateProp.getFirstValue();
|
|
79
|
+
exdates.push(exdate.toJSDate().getTime());
|
|
80
|
+
});
|
|
81
|
+
// Recurring event is handled differently
|
|
82
|
+
if (event.isRecurring()) {
|
|
83
|
+
const iterator = event.iterator();
|
|
84
|
+
let next;
|
|
85
|
+
let i = 0;
|
|
86
|
+
do {
|
|
87
|
+
i += 1;
|
|
88
|
+
next = iterator.next();
|
|
89
|
+
if (next) {
|
|
90
|
+
const occurrence = event.getOccurrenceDetails(next);
|
|
91
|
+
const { startTime, endTime } = getTimes(occurrence);
|
|
92
|
+
const isOccurrenceExcluded = exdates.indexOf(startTime) !== -1;
|
|
93
|
+
// TODO check that within same day?
|
|
94
|
+
const exception = exceptions.find(ex => ex.uid === event.uid && ex.recurrenceId.toJSDate().getTime() === occurrence.startDate.toJSDate().getTime());
|
|
95
|
+
// We have passed the max date, stop
|
|
96
|
+
if (before && startTime > before.getTime())
|
|
97
|
+
break;
|
|
98
|
+
// Check that we are within our range
|
|
99
|
+
if (isEventWithinRange(startTime, endTime)) {
|
|
100
|
+
if (exception) {
|
|
101
|
+
ret.events.push(exception);
|
|
102
|
+
}
|
|
103
|
+
else if (!isOccurrenceExcluded) {
|
|
104
|
+
ret.occurrences.push(occurrence);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
} while (next && (!this.maxIterations || i < this.maxIterations));
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
// Non-recurring event:
|
|
112
|
+
const { startTime, endTime } = getTimes(event);
|
|
113
|
+
if (isEventWithinRange(startTime, endTime))
|
|
114
|
+
ret.events.push(event);
|
|
115
|
+
});
|
|
116
|
+
return ret;
|
|
117
|
+
}
|
|
118
|
+
before(before) {
|
|
119
|
+
return this.between(undefined, before);
|
|
120
|
+
}
|
|
121
|
+
after(after) {
|
|
122
|
+
return this.between(after);
|
|
123
|
+
}
|
|
124
|
+
all() {
|
|
125
|
+
return this.between();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const eventSourceDef = {
|
|
130
|
+
parseMeta(refined) {
|
|
131
|
+
if (refined.url && refined.format === 'ics') {
|
|
132
|
+
return {
|
|
133
|
+
url: refined.url,
|
|
134
|
+
format: 'ics',
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
},
|
|
139
|
+
fetch(arg, successCallback, errorCallback) {
|
|
140
|
+
let meta = arg.eventSource.meta;
|
|
141
|
+
let { internalState } = meta;
|
|
142
|
+
/*
|
|
143
|
+
NOTE: isRefetch is a HACK. we would do the recurring-expanding in a separate plugin hook,
|
|
144
|
+
but we couldn't leverage built-in allDay-guessing, among other things.
|
|
145
|
+
*/
|
|
146
|
+
if (!internalState || arg.isRefetch) {
|
|
147
|
+
internalState = meta.internalState = {
|
|
148
|
+
response: null,
|
|
149
|
+
iCalExpanderPromise: fetch(meta.url, { method: 'GET' }).then((response) => {
|
|
150
|
+
return response.text().then((icsText) => {
|
|
151
|
+
internalState.response = response;
|
|
152
|
+
return new IcalExpander({
|
|
153
|
+
ics: icsText,
|
|
154
|
+
skipInvalidDates: true,
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
}),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
internalState.iCalExpanderPromise.then((iCalExpander) => {
|
|
161
|
+
successCallback({
|
|
162
|
+
rawEvents: expandICalEvents(iCalExpander, arg.range),
|
|
163
|
+
response: internalState.response,
|
|
164
|
+
});
|
|
165
|
+
}, errorCallback);
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
function expandICalEvents(iCalExpander, range) {
|
|
169
|
+
// expand the range. because our `range` is timeZone-agnostic UTC
|
|
170
|
+
// or maybe because ical.js always produces dates in local time? i forget
|
|
171
|
+
let rangeStart = internal_cjs.addDays(range.start, -1);
|
|
172
|
+
let rangeEnd = internal_cjs.addDays(range.end, 1);
|
|
173
|
+
let iCalRes = iCalExpander.between(rangeStart, rangeEnd); // end inclusive. will give extra results
|
|
174
|
+
let expanded = [];
|
|
175
|
+
// TODO: instead of using startDate/endDate.toString to communicate allDay,
|
|
176
|
+
// we can query startDate/endDate.isDate. More efficient to avoid formatting/reparsing.
|
|
177
|
+
// single events
|
|
178
|
+
for (let iCalEvent of iCalRes.events) {
|
|
179
|
+
expanded.push(Object.assign(Object.assign({}, buildNonDateProps(iCalEvent)), { start: iCalEvent.startDate.toString(), end: (specifiesEnd(iCalEvent) && iCalEvent.endDate)
|
|
180
|
+
? iCalEvent.endDate.toString()
|
|
181
|
+
: null }));
|
|
182
|
+
}
|
|
183
|
+
// recurring event instances
|
|
184
|
+
for (let iCalOccurence of iCalRes.occurrences) {
|
|
185
|
+
let iCalEvent = iCalOccurence.item;
|
|
186
|
+
expanded.push(Object.assign(Object.assign({}, buildNonDateProps(iCalEvent)), { start: iCalOccurence.startDate.toString(), end: (specifiesEnd(iCalEvent) && iCalOccurence.endDate)
|
|
187
|
+
? iCalOccurence.endDate.toString()
|
|
188
|
+
: null }));
|
|
189
|
+
}
|
|
190
|
+
return expanded;
|
|
191
|
+
}
|
|
192
|
+
function buildNonDateProps(iCalEvent) {
|
|
193
|
+
return {
|
|
194
|
+
title: iCalEvent.summary,
|
|
195
|
+
url: extractEventUrl(iCalEvent),
|
|
196
|
+
extendedProps: {
|
|
197
|
+
location: iCalEvent.location,
|
|
198
|
+
organizer: iCalEvent.organizer,
|
|
199
|
+
description: iCalEvent.description,
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
function extractEventUrl(iCalEvent) {
|
|
204
|
+
let urlProp = iCalEvent.component.getFirstProperty('url');
|
|
205
|
+
return urlProp ? urlProp.getFirstValue() : '';
|
|
206
|
+
}
|
|
207
|
+
function specifiesEnd(iCalEvent) {
|
|
208
|
+
return Boolean(iCalEvent.component.getFirstProperty('dtend')) ||
|
|
209
|
+
Boolean(iCalEvent.component.getFirstProperty('duration'));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
var index = index_cjs.createPlugin({
|
|
213
|
+
name: '@fullcalendar/icalendar',
|
|
214
|
+
eventSourceDefs: [eventSourceDef],
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
exports["default"] = index;
|
package/index.d.ts
ADDED
package/index.global.js
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
FullCalendar iCalendar Plugin v6.0.0-beta.2
|
|
3
|
+
Docs & License: https://fullcalendar.io/
|
|
4
|
+
(c) 2022 Adam Shaw
|
|
5
|
+
*/
|
|
6
|
+
FullCalendar.ICalendar = (function (exports, internal, core, ICAL) {
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
function _interopNamespace(e) {
|
|
10
|
+
if (e && e.__esModule) return e;
|
|
11
|
+
var n = Object.create(null);
|
|
12
|
+
if (e) {
|
|
13
|
+
Object.keys(e).forEach(function (k) {
|
|
14
|
+
if (k !== 'default') {
|
|
15
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
16
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
17
|
+
enumerable: true,
|
|
18
|
+
get: function () { return e[k]; }
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
n["default"] = e;
|
|
24
|
+
return n;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
var ICAL__namespace = /*#__PURE__*/_interopNamespace(ICAL);
|
|
28
|
+
|
|
29
|
+
/* eslint-disable */
|
|
30
|
+
class IcalExpander {
|
|
31
|
+
constructor(opts) {
|
|
32
|
+
this.maxIterations = opts.maxIterations != null ? opts.maxIterations : 1000;
|
|
33
|
+
this.skipInvalidDates = opts.skipInvalidDates != null ? opts.skipInvalidDates : false;
|
|
34
|
+
this.jCalData = ICAL__namespace.parse(opts.ics);
|
|
35
|
+
this.component = new ICAL__namespace.Component(this.jCalData);
|
|
36
|
+
this.events = this.component.getAllSubcomponents('vevent').map(vevent => new ICAL__namespace.Event(vevent));
|
|
37
|
+
if (this.skipInvalidDates) {
|
|
38
|
+
this.events = this.events.filter((evt) => {
|
|
39
|
+
try {
|
|
40
|
+
evt.startDate.toJSDate();
|
|
41
|
+
evt.endDate.toJSDate();
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
// skipping events with invalid time
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
between(after, before) {
|
|
52
|
+
function isEventWithinRange(startTime, endTime) {
|
|
53
|
+
return (!after || endTime >= after.getTime()) &&
|
|
54
|
+
(!before || startTime <= before.getTime());
|
|
55
|
+
}
|
|
56
|
+
function getTimes(eventOrOccurrence) {
|
|
57
|
+
const startTime = eventOrOccurrence.startDate.toJSDate().getTime();
|
|
58
|
+
let endTime = eventOrOccurrence.endDate.toJSDate().getTime();
|
|
59
|
+
// If it is an all day event, the end date is set to 00:00 of the next day
|
|
60
|
+
// So we need to make it be 23:59:59 to compare correctly with the given range
|
|
61
|
+
if (eventOrOccurrence.endDate.isDate && (endTime > startTime)) {
|
|
62
|
+
endTime -= 1;
|
|
63
|
+
}
|
|
64
|
+
return { startTime, endTime };
|
|
65
|
+
}
|
|
66
|
+
const exceptions = [];
|
|
67
|
+
this.events.forEach((event) => {
|
|
68
|
+
if (event.isRecurrenceException())
|
|
69
|
+
exceptions.push(event);
|
|
70
|
+
});
|
|
71
|
+
const ret = {
|
|
72
|
+
events: [],
|
|
73
|
+
occurrences: [],
|
|
74
|
+
};
|
|
75
|
+
this.events.filter(e => !e.isRecurrenceException()).forEach((event) => {
|
|
76
|
+
const exdates = [];
|
|
77
|
+
event.component.getAllProperties('exdate').forEach((exdateProp) => {
|
|
78
|
+
const exdate = exdateProp.getFirstValue();
|
|
79
|
+
exdates.push(exdate.toJSDate().getTime());
|
|
80
|
+
});
|
|
81
|
+
// Recurring event is handled differently
|
|
82
|
+
if (event.isRecurring()) {
|
|
83
|
+
const iterator = event.iterator();
|
|
84
|
+
let next;
|
|
85
|
+
let i = 0;
|
|
86
|
+
do {
|
|
87
|
+
i += 1;
|
|
88
|
+
next = iterator.next();
|
|
89
|
+
if (next) {
|
|
90
|
+
const occurrence = event.getOccurrenceDetails(next);
|
|
91
|
+
const { startTime, endTime } = getTimes(occurrence);
|
|
92
|
+
const isOccurrenceExcluded = exdates.indexOf(startTime) !== -1;
|
|
93
|
+
// TODO check that within same day?
|
|
94
|
+
const exception = exceptions.find(ex => ex.uid === event.uid && ex.recurrenceId.toJSDate().getTime() === occurrence.startDate.toJSDate().getTime());
|
|
95
|
+
// We have passed the max date, stop
|
|
96
|
+
if (before && startTime > before.getTime())
|
|
97
|
+
break;
|
|
98
|
+
// Check that we are within our range
|
|
99
|
+
if (isEventWithinRange(startTime, endTime)) {
|
|
100
|
+
if (exception) {
|
|
101
|
+
ret.events.push(exception);
|
|
102
|
+
}
|
|
103
|
+
else if (!isOccurrenceExcluded) {
|
|
104
|
+
ret.occurrences.push(occurrence);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
} while (next && (!this.maxIterations || i < this.maxIterations));
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
// Non-recurring event:
|
|
112
|
+
const { startTime, endTime } = getTimes(event);
|
|
113
|
+
if (isEventWithinRange(startTime, endTime))
|
|
114
|
+
ret.events.push(event);
|
|
115
|
+
});
|
|
116
|
+
return ret;
|
|
117
|
+
}
|
|
118
|
+
before(before) {
|
|
119
|
+
return this.between(undefined, before);
|
|
120
|
+
}
|
|
121
|
+
after(after) {
|
|
122
|
+
return this.between(after);
|
|
123
|
+
}
|
|
124
|
+
all() {
|
|
125
|
+
return this.between();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const eventSourceDef = {
|
|
130
|
+
parseMeta(refined) {
|
|
131
|
+
if (refined.url && refined.format === 'ics') {
|
|
132
|
+
return {
|
|
133
|
+
url: refined.url,
|
|
134
|
+
format: 'ics',
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
},
|
|
139
|
+
fetch(arg, successCallback, errorCallback) {
|
|
140
|
+
let meta = arg.eventSource.meta;
|
|
141
|
+
let { internalState } = meta;
|
|
142
|
+
/*
|
|
143
|
+
NOTE: isRefetch is a HACK. we would do the recurring-expanding in a separate plugin hook,
|
|
144
|
+
but we couldn't leverage built-in allDay-guessing, among other things.
|
|
145
|
+
*/
|
|
146
|
+
if (!internalState || arg.isRefetch) {
|
|
147
|
+
internalState = meta.internalState = {
|
|
148
|
+
response: null,
|
|
149
|
+
iCalExpanderPromise: fetch(meta.url, { method: 'GET' }).then((response) => {
|
|
150
|
+
return response.text().then((icsText) => {
|
|
151
|
+
internalState.response = response;
|
|
152
|
+
return new IcalExpander({
|
|
153
|
+
ics: icsText,
|
|
154
|
+
skipInvalidDates: true,
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
}),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
internalState.iCalExpanderPromise.then((iCalExpander) => {
|
|
161
|
+
successCallback({
|
|
162
|
+
rawEvents: expandICalEvents(iCalExpander, arg.range),
|
|
163
|
+
response: internalState.response,
|
|
164
|
+
});
|
|
165
|
+
}, errorCallback);
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
function expandICalEvents(iCalExpander, range) {
|
|
169
|
+
// expand the range. because our `range` is timeZone-agnostic UTC
|
|
170
|
+
// or maybe because ical.js always produces dates in local time? i forget
|
|
171
|
+
let rangeStart = internal.addDays(range.start, -1);
|
|
172
|
+
let rangeEnd = internal.addDays(range.end, 1);
|
|
173
|
+
let iCalRes = iCalExpander.between(rangeStart, rangeEnd); // end inclusive. will give extra results
|
|
174
|
+
let expanded = [];
|
|
175
|
+
// TODO: instead of using startDate/endDate.toString to communicate allDay,
|
|
176
|
+
// we can query startDate/endDate.isDate. More efficient to avoid formatting/reparsing.
|
|
177
|
+
// single events
|
|
178
|
+
for (let iCalEvent of iCalRes.events) {
|
|
179
|
+
expanded.push(Object.assign(Object.assign({}, buildNonDateProps(iCalEvent)), { start: iCalEvent.startDate.toString(), end: (specifiesEnd(iCalEvent) && iCalEvent.endDate)
|
|
180
|
+
? iCalEvent.endDate.toString()
|
|
181
|
+
: null }));
|
|
182
|
+
}
|
|
183
|
+
// recurring event instances
|
|
184
|
+
for (let iCalOccurence of iCalRes.occurrences) {
|
|
185
|
+
let iCalEvent = iCalOccurence.item;
|
|
186
|
+
expanded.push(Object.assign(Object.assign({}, buildNonDateProps(iCalEvent)), { start: iCalOccurence.startDate.toString(), end: (specifiesEnd(iCalEvent) && iCalOccurence.endDate)
|
|
187
|
+
? iCalOccurence.endDate.toString()
|
|
188
|
+
: null }));
|
|
189
|
+
}
|
|
190
|
+
return expanded;
|
|
191
|
+
}
|
|
192
|
+
function buildNonDateProps(iCalEvent) {
|
|
193
|
+
return {
|
|
194
|
+
title: iCalEvent.summary,
|
|
195
|
+
url: extractEventUrl(iCalEvent),
|
|
196
|
+
extendedProps: {
|
|
197
|
+
location: iCalEvent.location,
|
|
198
|
+
organizer: iCalEvent.organizer,
|
|
199
|
+
description: iCalEvent.description,
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
function extractEventUrl(iCalEvent) {
|
|
204
|
+
let urlProp = iCalEvent.component.getFirstProperty('url');
|
|
205
|
+
return urlProp ? urlProp.getFirstValue() : '';
|
|
206
|
+
}
|
|
207
|
+
function specifiesEnd(iCalEvent) {
|
|
208
|
+
return Boolean(iCalEvent.component.getFirstProperty('dtend')) ||
|
|
209
|
+
Boolean(iCalEvent.component.getFirstProperty('duration'));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
var plugin = core.createPlugin({
|
|
213
|
+
name: '@fullcalendar/icalendar',
|
|
214
|
+
eventSourceDefs: [eventSourceDef],
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
internal.globalPlugins.push(plugin);
|
|
218
|
+
|
|
219
|
+
exports["default"] = plugin;
|
|
220
|
+
|
|
221
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
222
|
+
|
|
223
|
+
return exports;
|
|
224
|
+
|
|
225
|
+
})({}, FullCalendar.Internal, FullCalendar, ICAL);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
FullCalendar iCalendar Plugin v6.0.0-beta.2
|
|
3
|
+
Docs & License: https://fullcalendar.io/
|
|
4
|
+
(c) 2022 Adam Shaw
|
|
5
|
+
*/
|
|
6
|
+
FullCalendar.ICalendar=function(e,t,n,r){"use strict";function a(e){if(e&&e.__esModule)return e;var t=Object.create(null);return e&&Object.keys(e).forEach((function(n){if("default"!==n){var r=Object.getOwnPropertyDescriptor(e,n);Object.defineProperty(t,n,r.get?r:{enumerable:!0,get:function(){return e[n]}})}})),t.default=e,t}var s=a(r);class i{constructor(e){this.maxIterations=null!=e.maxIterations?e.maxIterations:1e3,this.skipInvalidDates=null!=e.skipInvalidDates&&e.skipInvalidDates,this.jCalData=s.parse(e.ics),this.component=new s.Component(this.jCalData),this.events=this.component.getAllSubcomponents("vevent").map(e=>new s.Event(e)),this.skipInvalidDates&&(this.events=this.events.filter(e=>{try{return e.startDate.toJSDate(),e.endDate.toJSDate(),!0}catch(e){return!1}}))}between(e,t){function n(n,r){return(!e||r>=e.getTime())&&(!t||n<=t.getTime())}function r(e){const t=e.startDate.toJSDate().getTime();let n=e.endDate.toJSDate().getTime();return e.endDate.isDate&&n>t&&(n-=1),{startTime:t,endTime:n}}const a=[];this.events.forEach(e=>{e.isRecurrenceException()&&a.push(e)});const s={events:[],occurrences:[]};return this.events.filter(e=>!e.isRecurrenceException()).forEach(e=>{const i=[];if(e.component.getAllProperties("exdate").forEach(e=>{const t=e.getFirstValue();i.push(t.toJSDate().getTime())}),e.isRecurring()){const o=e.iterator();let c,l=0;do{if(l+=1,c=o.next(),c){const o=e.getOccurrenceDetails(c),{startTime:l,endTime:u}=r(o),d=-1!==i.indexOf(l),p=a.find(t=>t.uid===e.uid&&t.recurrenceId.toJSDate().getTime()===o.startDate.toJSDate().getTime());if(t&&l>t.getTime())break;n(l,u)&&(p?s.events.push(p):d||s.occurrences.push(o))}}while(c&&(!this.maxIterations||l<this.maxIterations));return}const{startTime:o,endTime:c}=r(e);n(o,c)&&s.events.push(e)}),s}before(e){return this.between(void 0,e)}after(e){return this.between(e)}all(){return this.between()}}const o={parseMeta:e=>e.url&&"ics"===e.format?{url:e.url,format:"ics"}:null,fetch(e,t,n){let r=e.eventSource.meta,{internalState:a}=r;a&&!e.isRefetch||(a=r.internalState={response:null,iCalExpanderPromise:fetch(r.url,{method:"GET"}).then(e=>e.text().then(t=>(a.response=e,new i({ics:t,skipInvalidDates:!0}))))}),a.iCalExpanderPromise.then(n=>{t({rawEvents:c(n,e.range),response:a.response})},n)}};function c(e,n){let r=t.addDays(n.start,-1),a=t.addDays(n.end,1),s=e.between(r,a),i=[];for(let e of s.events)i.push(Object.assign(Object.assign({},l(e)),{start:e.startDate.toString(),end:d(e)&&e.endDate?e.endDate.toString():null}));for(let e of s.occurrences){let t=e.item;i.push(Object.assign(Object.assign({},l(t)),{start:e.startDate.toString(),end:d(t)&&e.endDate?e.endDate.toString():null}))}return i}function l(e){return{title:e.summary,url:u(e),extendedProps:{location:e.location,organizer:e.organizer,description:e.description}}}function u(e){let t=e.component.getFirstProperty("url");return t?t.getFirstValue():""}function d(e){return Boolean(e.component.getFirstProperty("dtend"))||Boolean(e.component.getFirstProperty("duration"))}var p=n.createPlugin({name:"@fullcalendar/icalendar",eventSourceDefs:[o]});return t.globalPlugins.push(p),e.default=p,Object.defineProperty(e,"__esModule",{value:!0}),e}({},FullCalendar.Internal,FullCalendar,ICAL);
|
package/index.js
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { createPlugin } from '@fullcalendar/core/index.js';
|
|
2
|
+
import { addDays } from '@fullcalendar/core/internal.js';
|
|
3
|
+
import * as ICAL from 'ical.js';
|
|
4
|
+
|
|
5
|
+
/* eslint-disable */
|
|
6
|
+
class IcalExpander {
|
|
7
|
+
constructor(opts) {
|
|
8
|
+
this.maxIterations = opts.maxIterations != null ? opts.maxIterations : 1000;
|
|
9
|
+
this.skipInvalidDates = opts.skipInvalidDates != null ? opts.skipInvalidDates : false;
|
|
10
|
+
this.jCalData = ICAL.parse(opts.ics);
|
|
11
|
+
this.component = new ICAL.Component(this.jCalData);
|
|
12
|
+
this.events = this.component.getAllSubcomponents('vevent').map(vevent => new ICAL.Event(vevent));
|
|
13
|
+
if (this.skipInvalidDates) {
|
|
14
|
+
this.events = this.events.filter((evt) => {
|
|
15
|
+
try {
|
|
16
|
+
evt.startDate.toJSDate();
|
|
17
|
+
evt.endDate.toJSDate();
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
// skipping events with invalid time
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
between(after, before) {
|
|
28
|
+
function isEventWithinRange(startTime, endTime) {
|
|
29
|
+
return (!after || endTime >= after.getTime()) &&
|
|
30
|
+
(!before || startTime <= before.getTime());
|
|
31
|
+
}
|
|
32
|
+
function getTimes(eventOrOccurrence) {
|
|
33
|
+
const startTime = eventOrOccurrence.startDate.toJSDate().getTime();
|
|
34
|
+
let endTime = eventOrOccurrence.endDate.toJSDate().getTime();
|
|
35
|
+
// If it is an all day event, the end date is set to 00:00 of the next day
|
|
36
|
+
// So we need to make it be 23:59:59 to compare correctly with the given range
|
|
37
|
+
if (eventOrOccurrence.endDate.isDate && (endTime > startTime)) {
|
|
38
|
+
endTime -= 1;
|
|
39
|
+
}
|
|
40
|
+
return { startTime, endTime };
|
|
41
|
+
}
|
|
42
|
+
const exceptions = [];
|
|
43
|
+
this.events.forEach((event) => {
|
|
44
|
+
if (event.isRecurrenceException())
|
|
45
|
+
exceptions.push(event);
|
|
46
|
+
});
|
|
47
|
+
const ret = {
|
|
48
|
+
events: [],
|
|
49
|
+
occurrences: [],
|
|
50
|
+
};
|
|
51
|
+
this.events.filter(e => !e.isRecurrenceException()).forEach((event) => {
|
|
52
|
+
const exdates = [];
|
|
53
|
+
event.component.getAllProperties('exdate').forEach((exdateProp) => {
|
|
54
|
+
const exdate = exdateProp.getFirstValue();
|
|
55
|
+
exdates.push(exdate.toJSDate().getTime());
|
|
56
|
+
});
|
|
57
|
+
// Recurring event is handled differently
|
|
58
|
+
if (event.isRecurring()) {
|
|
59
|
+
const iterator = event.iterator();
|
|
60
|
+
let next;
|
|
61
|
+
let i = 0;
|
|
62
|
+
do {
|
|
63
|
+
i += 1;
|
|
64
|
+
next = iterator.next();
|
|
65
|
+
if (next) {
|
|
66
|
+
const occurrence = event.getOccurrenceDetails(next);
|
|
67
|
+
const { startTime, endTime } = getTimes(occurrence);
|
|
68
|
+
const isOccurrenceExcluded = exdates.indexOf(startTime) !== -1;
|
|
69
|
+
// TODO check that within same day?
|
|
70
|
+
const exception = exceptions.find(ex => ex.uid === event.uid && ex.recurrenceId.toJSDate().getTime() === occurrence.startDate.toJSDate().getTime());
|
|
71
|
+
// We have passed the max date, stop
|
|
72
|
+
if (before && startTime > before.getTime())
|
|
73
|
+
break;
|
|
74
|
+
// Check that we are within our range
|
|
75
|
+
if (isEventWithinRange(startTime, endTime)) {
|
|
76
|
+
if (exception) {
|
|
77
|
+
ret.events.push(exception);
|
|
78
|
+
}
|
|
79
|
+
else if (!isOccurrenceExcluded) {
|
|
80
|
+
ret.occurrences.push(occurrence);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
} while (next && (!this.maxIterations || i < this.maxIterations));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
// Non-recurring event:
|
|
88
|
+
const { startTime, endTime } = getTimes(event);
|
|
89
|
+
if (isEventWithinRange(startTime, endTime))
|
|
90
|
+
ret.events.push(event);
|
|
91
|
+
});
|
|
92
|
+
return ret;
|
|
93
|
+
}
|
|
94
|
+
before(before) {
|
|
95
|
+
return this.between(undefined, before);
|
|
96
|
+
}
|
|
97
|
+
after(after) {
|
|
98
|
+
return this.between(after);
|
|
99
|
+
}
|
|
100
|
+
all() {
|
|
101
|
+
return this.between();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const eventSourceDef = {
|
|
106
|
+
parseMeta(refined) {
|
|
107
|
+
if (refined.url && refined.format === 'ics') {
|
|
108
|
+
return {
|
|
109
|
+
url: refined.url,
|
|
110
|
+
format: 'ics',
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
},
|
|
115
|
+
fetch(arg, successCallback, errorCallback) {
|
|
116
|
+
let meta = arg.eventSource.meta;
|
|
117
|
+
let { internalState } = meta;
|
|
118
|
+
/*
|
|
119
|
+
NOTE: isRefetch is a HACK. we would do the recurring-expanding in a separate plugin hook,
|
|
120
|
+
but we couldn't leverage built-in allDay-guessing, among other things.
|
|
121
|
+
*/
|
|
122
|
+
if (!internalState || arg.isRefetch) {
|
|
123
|
+
internalState = meta.internalState = {
|
|
124
|
+
response: null,
|
|
125
|
+
iCalExpanderPromise: fetch(meta.url, { method: 'GET' }).then((response) => {
|
|
126
|
+
return response.text().then((icsText) => {
|
|
127
|
+
internalState.response = response;
|
|
128
|
+
return new IcalExpander({
|
|
129
|
+
ics: icsText,
|
|
130
|
+
skipInvalidDates: true,
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
}),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
internalState.iCalExpanderPromise.then((iCalExpander) => {
|
|
137
|
+
successCallback({
|
|
138
|
+
rawEvents: expandICalEvents(iCalExpander, arg.range),
|
|
139
|
+
response: internalState.response,
|
|
140
|
+
});
|
|
141
|
+
}, errorCallback);
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
function expandICalEvents(iCalExpander, range) {
|
|
145
|
+
// expand the range. because our `range` is timeZone-agnostic UTC
|
|
146
|
+
// or maybe because ical.js always produces dates in local time? i forget
|
|
147
|
+
let rangeStart = addDays(range.start, -1);
|
|
148
|
+
let rangeEnd = addDays(range.end, 1);
|
|
149
|
+
let iCalRes = iCalExpander.between(rangeStart, rangeEnd); // end inclusive. will give extra results
|
|
150
|
+
let expanded = [];
|
|
151
|
+
// TODO: instead of using startDate/endDate.toString to communicate allDay,
|
|
152
|
+
// we can query startDate/endDate.isDate. More efficient to avoid formatting/reparsing.
|
|
153
|
+
// single events
|
|
154
|
+
for (let iCalEvent of iCalRes.events) {
|
|
155
|
+
expanded.push(Object.assign(Object.assign({}, buildNonDateProps(iCalEvent)), { start: iCalEvent.startDate.toString(), end: (specifiesEnd(iCalEvent) && iCalEvent.endDate)
|
|
156
|
+
? iCalEvent.endDate.toString()
|
|
157
|
+
: null }));
|
|
158
|
+
}
|
|
159
|
+
// recurring event instances
|
|
160
|
+
for (let iCalOccurence of iCalRes.occurrences) {
|
|
161
|
+
let iCalEvent = iCalOccurence.item;
|
|
162
|
+
expanded.push(Object.assign(Object.assign({}, buildNonDateProps(iCalEvent)), { start: iCalOccurence.startDate.toString(), end: (specifiesEnd(iCalEvent) && iCalOccurence.endDate)
|
|
163
|
+
? iCalOccurence.endDate.toString()
|
|
164
|
+
: null }));
|
|
165
|
+
}
|
|
166
|
+
return expanded;
|
|
167
|
+
}
|
|
168
|
+
function buildNonDateProps(iCalEvent) {
|
|
169
|
+
return {
|
|
170
|
+
title: iCalEvent.summary,
|
|
171
|
+
url: extractEventUrl(iCalEvent),
|
|
172
|
+
extendedProps: {
|
|
173
|
+
location: iCalEvent.location,
|
|
174
|
+
organizer: iCalEvent.organizer,
|
|
175
|
+
description: iCalEvent.description,
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
function extractEventUrl(iCalEvent) {
|
|
180
|
+
let urlProp = iCalEvent.component.getFirstProperty('url');
|
|
181
|
+
return urlProp ? urlProp.getFirstValue() : '';
|
|
182
|
+
}
|
|
183
|
+
function specifiesEnd(iCalEvent) {
|
|
184
|
+
return Boolean(iCalEvent.component.getFirstProperty('dtend')) ||
|
|
185
|
+
Boolean(iCalEvent.component.getFirstProperty('duration'));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
var index = createPlugin({
|
|
189
|
+
name: '@fullcalendar/icalendar',
|
|
190
|
+
eventSourceDefs: [eventSourceDef],
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
export { index as default };
|