[SPIP] ~spip v3.2.0-->v3.2.1
[lhc/web/www.git] / www / plugins-dist / organiseur / lib / fullcalendar / gcal.js
1 /*!
2 * FullCalendar v3.5.1 Google Calendar Plugin
3 * Docs & License: https://fullcalendar.io/
4 * (c) 2017 Adam Shaw
5 */
6
7 (function(factory) {
8 if (typeof define === 'function' && define.amd) {
9 define([ 'jquery' ], factory);
10 }
11 else if (typeof exports === 'object') { // Node/CommonJS
12 module.exports = factory(require('jquery'));
13 }
14 else {
15 factory(jQuery);
16 }
17 })(function($) {
18
19
20 var FC = $.fullCalendar;
21 var Promise = FC.Promise;
22 var EventSource = FC.EventSource;
23 var JsonFeedEventSource = FC.JsonFeedEventSource;
24 var EventSourceParser = FC.EventSourceParser;
25 var applyAll = FC.applyAll;
26
27 ;;
28
29 var GcalEventSource = EventSource.extend({
30
31 // TODO: eventually remove "googleCalendar" prefix (API-breaking)
32 googleCalendarApiKey: null,
33 googleCalendarId: null,
34 googleCalendarError: null, // optional function
35 ajaxSettings: null,
36
37
38 fetch: function(start, end, timezone) {
39 var _this = this;
40 var url = this.buildUrl();
41 var requestParams = this.buildRequestParams(start, end, timezone);
42 var ajaxSettings = this.ajaxSettings;
43 var onSuccess = ajaxSettings.success;
44
45 if (!requestParams) { // could have failed
46 return Promise.reject();
47 }
48
49 return Promise.construct(function(onResolve, onReject) {
50 $.ajax($.extend(
51 {}, // destination
52 JsonFeedEventSource.AJAX_DEFAULTS,
53 ajaxSettings,
54 {
55 url: url,
56 data: requestParams,
57 success: function(responseData) {
58 var rawEventDefs;
59 var successRes;
60
61 if (responseData.error) {
62 _this.reportError('Google Calendar API: ' + responseData.error.message, responseData.error.errors);
63 onReject();
64 }
65 else if (responseData.items) {
66 rawEventDefs = _this.gcalItemsToRawEventDefs(
67 responseData.items,
68 requestParams.timeZone
69 );
70
71 successRes = applyAll(
72 onSuccess,
73 this, // forward `this`
74 // call the success handler(s) and allow it to return a new events array
75 [ rawEventDefs ].concat(Array.prototype.slice.call(arguments, 1))
76 );
77
78 if ($.isArray(successRes)) {
79 rawEventDefs = successRes;
80 }
81
82 onResolve(_this.parseEventDefs(rawEventDefs));
83 }
84 }
85 }
86 ));
87 });
88 },
89
90
91 gcalItemsToRawEventDefs: function(items, gcalTimezone) {
92 var _this = this;
93
94 return items.map(function(item) {
95 return _this.gcalItemToRawEventDef(item, gcalTimezone);
96 });
97 },
98
99
100 gcalItemToRawEventDef: function(item, gcalTimezone) {
101 var url = item.htmlLink || null;
102
103 // make the URLs for each event show times in the correct timezone
104 if (url && gcalTimezone) {
105 url = injectQsComponent(url, 'ctz=' + gcalTimezone);
106 }
107
108 return {
109 id: item.id,
110 title: item.summary,
111 start: item.start.dateTime || item.start.date, // try timed. will fall back to all-day
112 end: item.end.dateTime || item.end.date, // same
113 url: url,
114 location: item.location,
115 description: item.description
116 };
117 },
118
119
120 buildUrl: function() {
121 return GcalEventSource.API_BASE + '/' +
122 encodeURIComponent(this.googleCalendarId) +
123 '/events?callback=?'; // jsonp
124 },
125
126
127 buildRequestParams: function(start, end, timezone) {
128 var apiKey = this.googleCalendarApiKey || this.calendar.opt('googleCalendarApiKey');
129 var params;
130
131 if (!apiKey) {
132 this.reportError("Specify a googleCalendarApiKey. See http://fullcalendar.io/docs/google_calendar/");
133 return null;
134 }
135
136 // The API expects an ISO8601 datetime with a time and timezone part.
137 // Since the calendar's timezone offset isn't always known, request the date in UTC and pad it by a day on each
138 // side, guaranteeing we will receive all events in the desired range, albeit a superset.
139 // .utc() will set a zone and give it a 00:00:00 time.
140 if (!start.hasZone()) {
141 start = start.clone().utc().add(-1, 'day');
142 }
143 if (!end.hasZone()) {
144 end = end.clone().utc().add(1, 'day');
145 }
146
147 params = $.extend(
148 this.ajaxSettings.data || {},
149 {
150 key: apiKey,
151 timeMin: start.format(),
152 timeMax: end.format(),
153 singleEvents: true,
154 maxResults: 9999
155 }
156 );
157
158 if (timezone && timezone !== 'local') {
159 // when sending timezone names to Google, only accepts underscores, not spaces
160 params.timeZone = timezone.replace(' ', '_');
161 }
162
163 return params;
164 },
165
166
167 reportError: function(message, apiErrorObjs) {
168 var calendar = this.calendar;
169 var calendarOnError = calendar.opt('googleCalendarError');
170 var errorObjs = apiErrorObjs || [ { message: message } ]; // to be passed into error handlers
171
172 if (this.googleCalendarError) {
173 this.googleCalendarError.apply(calendar, errorObjs);
174 }
175
176 if (calendarOnError) {
177 calendarOnError.apply(calendar, errorObjs);
178 }
179
180 // print error to debug console
181 FC.warn.apply(null, [ message ].concat(apiErrorObjs || []));
182 },
183
184
185 getPrimitive: function() {
186 return this.googleCalendarId;
187 },
188
189
190 applyManualRawProps: function(rawProps) {
191 var superSuccess = EventSource.prototype.applyManualRawProps.apply(this, arguments);
192 var googleCalendarId = rawProps.googleCalendarId;
193
194 if (googleCalendarId == null && rawProps.url) {
195 googleCalendarId = parseGoogleCalendarId(rawProps.url);
196 }
197
198 if (googleCalendarId != null) {
199 this.googleCalendarId = googleCalendarId;
200
201 return superSuccess;
202 }
203
204 return false;
205 },
206
207
208 applyOtherRawProps: function(rawProps) {
209 this.ajaxSettings = rawProps;
210 }
211
212 });
213
214
215 GcalEventSource.API_BASE = 'https://www.googleapis.com/calendar/v3/calendars';
216
217
218 GcalEventSource.allowRawProps({
219 // manually process...
220 url: false,
221 googleCalendarId: false,
222
223 // automatically transfer...
224 googleCalendarApiKey: true,
225 googleCalendarError: true
226 });
227
228
229 GcalEventSource.parse = function(rawInput, calendar) {
230 var rawProps;
231
232 if (typeof rawInput === 'object') { // long form. might fail in applyManualRawProps
233 rawProps = rawInput;
234 }
235 else if (typeof rawInput === 'string') { // short form
236 rawProps = { url: rawInput }; // url will be parsed with parseGoogleCalendarId
237 }
238
239 if (rawProps) {
240 return EventSource.parse.call(this, rawProps, calendar);
241 }
242
243 return false;
244 };
245
246
247 function parseGoogleCalendarId(url) {
248 var match;
249
250 // detect if the ID was specified as a single string.
251 // will match calendars like "asdf1234@calendar.google.com" in addition to person email calendars.
252 if (/^[^\/]+@([^\/\.]+\.)*(google|googlemail|gmail)\.com$/.test(url)) {
253 return url;
254 }
255 // try to scrape it out of a V1 or V3 API feed URL
256 else if (
257 (match = /^https:\/\/www.googleapis.com\/calendar\/v3\/calendars\/([^\/]*)/.exec(url)) ||
258 (match = /^https?:\/\/www.google.com\/calendar\/feeds\/([^\/]*)/.exec(url))
259 ) {
260 return decodeURIComponent(match[1]);
261 }
262 }
263
264
265 // Injects a string like "arg=value" into the querystring of a URL
266 function injectQsComponent(url, component) {
267 // inject it after the querystring but before the fragment
268 return url.replace(/(\?.*?)?(#|$)/, function(whole, qs, hash) {
269 return (qs ? qs + '&' : '?') + component + hash;
270 });
271 }
272
273
274 // expose
275
276 EventSourceParser.registerClass(GcalEventSource);
277
278 FC.GcalEventSource = GcalEventSource;
279
280 ;;
281
282 });