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 20var FC = $.fullCalendar; 21var Promise = FC.Promise; 22var EventSource = FC.EventSource; 23var JsonFeedEventSource = FC.JsonFeedEventSource; 24var EventSourceParser = FC.EventSourceParser; 25var applyAll = FC.applyAll; 26 27;; 28 29var 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 215GcalEventSource.API_BASE = 'https://www.googleapis.com/calendar/v3/calendars'; 216 217 218GcalEventSource.allowRawProps({ 219 // manually process... 220 url: false, 221 googleCalendarId: false, 222 223 // automatically transfer... 224 googleCalendarApiKey: true, 225 googleCalendarError: true 226}); 227 228 229GcalEventSource.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 247function parseGoogleCalendarId(url) { 248 var match; 249 250 // detect if the ID was specified as a single string. 251 // will match calendars like "[email protected]" 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 266function 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 276EventSourceParser.registerClass(GcalEventSource); 277 278FC.GcalEventSource = GcalEventSource; 279 280;; 281 282});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.