浏览代码

revamp google calendar plugin

Adam Shaw 7 年之前
父节点
当前提交
dccca19cb5
共有 2 个文件被更改,包括 169 次插入242 次删除
  1. 0 238
      plugins/gcal/GcalEventSource.ts
  2. 169 4
      plugins/gcal/main.ts

+ 0 - 238
plugins/gcal/GcalEventSource.ts

@@ -1,238 +0,0 @@
-import * as request from 'superagent'
-import { EventSource, warn, applyAll, assignTo, DateEnv, DateMarker, addDays } from 'fullcalendar'
-
-
-export default class GcalEventSource extends EventSource {
-
-  static API_BASE = 'https://www.googleapis.com/calendar/v3/calendars'
-
-  // TODO: eventually remove "googleCalendar" prefix (API-breaking)
-  googleCalendarApiKey: any
-  googleCalendarId: any
-  googleCalendarError: any // optional function
-  ajaxSettings: any
-
-
-  static parse(rawInput, calendar) {
-    let rawProps
-
-    if (typeof rawInput === 'object') { // long form. might fail in applyManualStandardProps
-      rawProps = rawInput
-    } else if (typeof rawInput === 'string') { // short form
-      rawProps = { url: rawInput } // url will be parsed with parseGoogleCalendarId
-    }
-
-    if (rawProps) {
-      return EventSource.parse.call(this, rawProps, calendar)
-    }
-
-    return false
-  }
-
-
-  fetch(start: DateMarker, end: DateMarker, dateEnv: DateEnv, onSuccess, onFailure) {
-    let url = this.buildUrl()
-    let requestParams = this.buildRequestParams(start, end, dateEnv)
-    let ajaxSettings = this.ajaxSettings || {}
-
-    if (!requestParams) { // could have failed
-      onFailure()
-      return
-    }
-
-    this.calendar.pushLoading()
-
-    request.get(url)
-      .query(requestParams)
-      .end((error, res) => {
-        let rawEventDefs
-
-        this.calendar.popLoading()
-
-        if (res && res.body && res.body.error) {
-          this.reportError('Google Calendar API: ' + res.body.error.message, res.body.error.errors)
-        } else if (error) {
-          this.reportError('Google Calendar API', error)
-        } else {
-          rawEventDefs = this.gcalItemsToRawEventDefs(
-            res.body.items,
-            requestParams.timeZone
-          )
-        }
-
-        if (rawEventDefs) {
-          let callbackRes = applyAll(ajaxSettings.success, null, [ rawEventDefs, res ])
-
-          if (Array.isArray(callbackRes)) {
-            rawEventDefs = callbackRes
-          }
-
-          onSuccess(this.parseEventDefs(rawEventDefs))
-        } else {
-          applyAll(ajaxSettings.error, null, [ error, res ])
-          onFailure()
-        }
-      })
-  }
-
-
-  gcalItemsToRawEventDefs(items, gcalTimezone) {
-    return items.map((item) => {
-      return this.gcalItemToRawEventDef(item, gcalTimezone)
-    })
-  }
-
-
-  gcalItemToRawEventDef(item, gcalTimezone) {
-    let url = item.htmlLink || null
-
-    // make the URLs for each event show times in the correct timezone
-    if (url && gcalTimezone) {
-      url = injectQsComponent(url, 'ctz=' + gcalTimezone)
-    }
-
-    return {
-      id: item.id,
-      title: item.summary,
-      start: item.start.dateTime || item.start.date, // try timed. will fall back to all-day
-      end: item.end.dateTime || item.end.date, // same
-      url: url,
-      location: item.location,
-      description: item.description
-    }
-  }
-
-
-  buildUrl() {
-    return GcalEventSource.API_BASE + '/' + encodeURIComponent(this.googleCalendarId) + '/events'
-  }
-
-
-  buildRequestParams(start: DateMarker, end: DateMarker, dateEnv: DateEnv) {
-    let apiKey = this.googleCalendarApiKey || this.calendar.opt('googleCalendarApiKey')
-    let params
-    let startStr
-    let endStr
-
-    if (!apiKey) {
-      this.reportError('Specify a googleCalendarApiKey. See http://fullcalendar.io/docs/google_calendar/')
-      return null
-    }
-
-    if (dateEnv.canComputeOffset) {
-      // strings will naturally have offsets, which GCal needs
-      startStr = dateEnv.formatIso(start)
-      endStr = dateEnv.formatIso(end)
-    } else {
-      // when timezone isn't known, we don't know what the UTC offset should be, so ask for +/- 1 day
-      // from the UTC day-start to guarantee we're getting all the events
-      // (start/end will be UTC-coerced dates, so toISOString is okay)
-      startStr = addDays(start, -1).toISOString()
-      endStr = addDays(end, 1).toISOString()
-    }
-
-    params = assignTo(
-      this.ajaxSettings.data || {},
-      {
-        key: apiKey,
-        timeMin: startStr,
-        timeMax: endStr,
-        singleEvents: true,
-        maxResults: 9999
-      }
-    )
-
-    // when sending timezone names to Google, only accepts underscores, not spaces
-    if (dateEnv.timeZone !== 'local') {
-      params.timeZone = dateEnv.timeZone.replace(' ', '_')
-    }
-
-    return params
-  }
-
-
-  reportError(message, apiErrorObjs?) {
-    let calendar = this.calendar
-    let calendarOnError = calendar.opt('googleCalendarError')
-    let errorObjs = apiErrorObjs || [ { message: message } ] // to be passed into error handlers
-
-    if (this.googleCalendarError) {
-      this.googleCalendarError.apply(calendar, errorObjs)
-    }
-
-    if (calendarOnError) {
-      calendarOnError.apply(calendar, errorObjs)
-    }
-
-    // print error to debug console
-    warn.apply(null, [ message ].concat(apiErrorObjs || []))
-  }
-
-
-  getPrimitive() {
-    return this.googleCalendarId
-  }
-
-
-  applyManualStandardProps(rawProps) {
-    let superSuccess = EventSource.prototype.applyManualStandardProps.apply(this, arguments)
-    let googleCalendarId = rawProps.googleCalendarId
-
-    if (googleCalendarId == null && rawProps.url) {
-      googleCalendarId = parseGoogleCalendarId(rawProps.url)
-    }
-
-    if (googleCalendarId != null) {
-      this.googleCalendarId = googleCalendarId
-
-      return superSuccess
-    }
-
-    return false
-  }
-
-
-  applyMiscProps(rawProps) {
-    if (!this.ajaxSettings) {
-      this.ajaxSettings = {}
-    }
-    assignTo(this.ajaxSettings, rawProps)
-  }
-
-}
-
-
-GcalEventSource.defineStandardProps({
-  // manually process...
-  url: false,
-  googleCalendarId: false,
-
-  // automatically transfer...
-  googleCalendarApiKey: true,
-  googleCalendarError: true
-})
-
-
-function parseGoogleCalendarId(url) {
-  let match
-
-  // detect if the ID was specified as a single string.
-  // will match calendars like "[email protected]" in addition to person email calendars.
-  if (/^[^\/]+@([^\/\.]+\.)*(google|googlemail|gmail)\.com$/.test(url)) {
-    return url
-  } else if (
-    (match = /^https:\/\/www.googleapis.com\/calendar\/v3\/calendars\/([^\/]*)/.exec(url)) ||
-    (match = /^https?:\/\/www.google.com\/calendar\/feeds\/([^\/]*)/.exec(url))
-  ) {
-    return decodeURIComponent(match[1])
-  }
-}
-
-
-// Injects a string like "arg=value" into the querystring of a URL
-function injectQsComponent(url, component) {
-  // inject it after the querystring but before the fragment
-  return url.replace(/(\?.*?)?(#|$)/, function(whole, qs, hash) {
-    return (qs ? qs + '&' : '?') + component + hash
-  })
-}

+ 169 - 4
plugins/gcal/main.ts

@@ -1,6 +1,171 @@
-import * as exportHooks from 'fullcalendar'
-import GcalEventSource from './GcalEventSource'
+import * as request from 'superagent'
+import { registerSourceType, refineProps, addDays, assignTo } from 'fullcalendar'
 
 
-exportHooks.EventSourceParser.registerClass(GcalEventSource);
+// TODO: expose somehow
+const API_BASE = 'https://www.googleapis.com/calendar/v3/calendars'
 
 
-(exportHooks as any).GcalEventSource = GcalEventSource
+const STANDARD_PROPS = {
+  url: String,
+  googleCalendarApiKey: String, // TODO: rename with no prefix?
+  googleCalendarId: String,
+  data: null
+}
+
+registerSourceType('google-calendar', {
+
+  parse(raw) {
+    if (typeof raw === 'string') {
+      raw = { url: raw }
+    }
+
+    if (typeof raw === 'object') {
+      let standardProps = refineProps(raw, STANDARD_PROPS)
+
+      if (!standardProps.googleCalendarId && standardProps.url) {
+        standardProps.googleCalendarId = parseGoogleCalendarId(standardProps.url)
+      }
+
+      delete standardProps.url
+
+      if (standardProps.googleCalendarId) {
+        return standardProps
+      }
+    }
+  },
+
+  fetch(arg, onSuccess, onFailure) {
+    let calendar = arg.calendar
+    let meta = arg.eventSource.sourceTypeMeta
+    let apiKey = meta.googleCalendarApiKey || calendar.opt('googleCalendarApiKey')
+
+    if (!apiKey) {
+      onFailure('Specify a googleCalendarApiKey. See http://fullcalendar.io/docs/google_calendar/')
+    } else {
+      let url = buildUrl(meta)
+      let requestParams = buildRequestParams(
+        arg.range,
+        apiKey,
+        meta.data,
+        calendar.dateEnv
+      )
+
+      request.get(url)
+        .query(requestParams)
+        .end((error, res) => {
+          if (res && res.body && res.body.error) {
+            onFailure({
+              message: 'Google Calendar API: ' + res.body.error.message,
+              errors: res.body.error.errors
+            })
+          } else if (error) {
+            onFailure({
+              message: 'Google Calendar API',
+              error
+            })
+          } else {
+            onSuccess(
+              gcalItemsToRawEventDefs(
+                res.body.items,
+                requestParams.timeZone
+              )
+            )
+          }
+        })
+    }
+  }
+})
+
+
+function parseGoogleCalendarId(url) {
+  let match
+
+  // detect if the ID was specified as a single string.
+  // will match calendars like "[email protected]" in addition to person email calendars.
+  if (/^[^\/]+@([^\/\.]+\.)*(google|googlemail|gmail)\.com$/.test(url)) {
+    return url
+  } else if (
+    (match = /^https:\/\/www.googleapis.com\/calendar\/v3\/calendars\/([^\/]*)/.exec(url)) ||
+    (match = /^https?:\/\/www.google.com\/calendar\/feeds\/([^\/]*)/.exec(url))
+  ) {
+    return decodeURIComponent(match[1])
+  }
+}
+
+
+function buildUrl(meta) {
+  return API_BASE + '/' + encodeURIComponent(meta.googleCalendarId) + '/events'
+}
+
+
+function buildRequestParams(range, apiKey: string, extraData, dateEnv) {
+  let params
+  let startStr
+  let endStr
+
+  if (dateEnv.canComputeOffset) {
+    // strings will naturally have offsets, which GCal needs
+    startStr = dateEnv.formatIso(range.start)
+    endStr = dateEnv.formatIso(range.end)
+  } else {
+    // when timezone isn't known, we don't know what the UTC offset should be, so ask for +/- 1 day
+    // from the UTC day-start to guarantee we're getting all the events
+    // (start/end will be UTC-coerced dates, so toISOString is okay)
+    startStr = addDays(range.start, -1).toISOString()
+    endStr = addDays(range.end, 1).toISOString()
+  }
+
+  params = assignTo(
+    extraData || {},
+    {
+      key: apiKey,
+      timeMin: startStr,
+      timeMax: endStr,
+      singleEvents: true,
+      maxResults: 9999
+    }
+  )
+
+  // when sending timezone names to Google, only accepts underscores, not spaces
+  if (dateEnv.timeZone !== 'local') {
+    params.timeZone = dateEnv.timeZone.replace(' ', '_')
+  }
+
+  return params
+}
+
+
+function gcalItemsToRawEventDefs(items, gcalTimezone) {
+  return items.map((item) => {
+    return gcalItemToRawEventDef(item, gcalTimezone)
+  })
+}
+
+
+function gcalItemToRawEventDef(item, gcalTimezone) {
+  let url = item.htmlLink || null
+
+  // make the URLs for each event show times in the correct timezone
+  if (url && gcalTimezone) {
+    url = injectQsComponent(url, 'ctz=' + gcalTimezone)
+  }
+
+  return {
+    id: item.id,
+    title: item.summary,
+    start: item.start.dateTime || item.start.date, // try timed. will fall back to all-day
+    end: item.end.dateTime || item.end.date, // same
+    url: url,
+    location: item.location,
+    description: item.description
+  }
+}
+
+
+// Injects a string like "arg=value" into the querystring of a URL
+// TODO: move to a general util file?
+function injectQsComponent(url, component) {
+  // inject it after the querystring but before the fragment
+  return url.replace(/(\?.*?)?(#|$)/, function(whole, qs, hash) {
+    return (qs ? qs + '&' : '?') + component + hash
+  })
+}