Skip to content

Commit

Permalink
refactor: Add utility class to generate calendar urls (google, outlook,
Browse files Browse the repository at this point in the history
...) from event instances

Addresses #111 as well
  • Loading branch information
fabiodrg committed Mar 29, 2022
1 parent 2c63909 commit 5e71447
Showing 1 changed file with 220 additions and 0 deletions.
220 changes: 220 additions & 0 deletions src/js/utils/calendar.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ function __compileURL(url) {
}

/**
* @deprecated
* Create a 'Add to Calendar' URL for Google Calendar
*
* Base URL: `https://calendar.google.com/calendar/render`
Expand Down Expand Up @@ -135,6 +136,7 @@ function __getMicrosoftQueryParams(extractor, event, repeat) {
}

/**
* @deprecated
* Create a 'Add to Calendar' URL for Outlook.com
*
* Base URL: `https://outlook.live.com/calendar/0/deeplink/compose`
Expand All @@ -154,6 +156,7 @@ function eventToOutlookCalendar(extractor, event, repeat) {
}

/**
* @deprecated
* Create a 'Add to Calendar' URL for Office365
*
* Base URL: `https://outlook.office.com/calendar/0/deeplink/compose`
Expand All @@ -173,6 +176,7 @@ function eventToOffice365Calendar(extractor, event, repeat) {
}

/**
* @deprecated
* Create a 'Add to Calendar' URL for Yahoo
*
* Base URL: `https://calendar.yahoo.com/`
Expand Down Expand Up @@ -212,6 +216,214 @@ function eventToYahooCalendar(extractor, event, repeat) {
});
}

/**
* Generate URLs for adding singular events in common online calendars such
* as Google, Microsoft Outlook/Office 365 and Yahoo
*/
class CalendarUrlGenerator {
/**
*
* @param {CalEvent} event
*/
constructor(event) {
this.event = event;
this.recur = this.event.getRecurRule();
}

/**
* Create a 'Add to Calendar' URL for Google Calendar
*
* @returns {String} 'Add to calendar' link for Google Calendars
*/
google() {
/**
* Base URL: `https://calendar.google.com/calendar/render`
*
* Query parameters:
* * `action`: `TEMPLATE`
* * `dates`: `<start date>/<end date>`
* * Both dates in ISO 8601 simplified (YYYYMMDDTHHmmss+Z)
* * `text`: The event title (string)
* * `details`: The event description (string, with html support)
* * `location`: Event location (string)
* * `recur`: A recurrence rule with format `RRULE:FREQ=<freq>;UNTIL=<date>`
* * Standard: {@link https://icalendar.org/iCalendar-RFC-5545/3-8-5-3-recurrence-rule.html}
* * Date is in the same format as start/end dates
*/
const luxonSettings = {
format: "basic",
suppressMilliseconds: true,
includeOffset: false,
};
const startDate = DateTime.fromJSDate(this.event.start).toISO(luxonSettings);
const endDate = DateTime.fromJSDate(this.event.end).toISO(luxonSettings);

return this._compileURL({
baseURL: "https://calendar.google.com/calendar/render",
queryParams: {
action: "TEMPLATE",
dates: `${startDate}/${endDate}`,
text: this.event.titleEncoded,
details: this.event.descriptionEncoded,
location: this.event.location,
recur: this.recur
? `RRULE:FREQ=${this.recur.freq};UNTIL=${DateTime.fromJSDate(this.recur.until).toISO(
luxonSettings
)}`
: null,
},
});
}

/**
* Create a 'Add to Calendar' URL for Outlook.com
*
* @returns {String} 'Add to calendar' link for Outlook.com calendars
*/
outlook() {
/**
* Base URL: `https://outlook.live.com/calendar/0/deeplink/compose`
*
* Query parameters: {@link _getMicrosoftQueryParams}
*/
return this._compileURL({
baseURL: "https://outlook.live.com/calendar/0/deeplink/compose",
queryParams: this._getMicrosoftQueryParams(),
});
}

/**
* Create a 'Add to Calendar' URL for Office365
*
* @returns {String} 'Add to calendar' link for Office365 calendars
*/
office365() {
/**
* Base URL: `https://outlook.office.com/calendar/0/deeplink/compose`
*
* Query parameters: {@link _getMicrosoftQueryParams}
*/
return this._compileURL({
baseURL: "https://outlook.office.com/calendar/0/deeplink/compose",
queryParams: this._getMicrosoftQueryParams(),
});
}

/**
* Create a 'Add to Calendar' URL for Yahoo
*
* @returns {String} 'Add to calendar' link for Yahoo.com calendars
*/
yahoo() {
/**
* Base URL: `https://calendar.yahoo.com/`
*
* Query parameters:
* * `title`: The event title (string)
* * `desc`: The event description (string, unknown html support)
* * `in_loc`: Event location (string)
* * `st`: Event start time in ISO 8601 simplified ({@link google})
* * `et`: Event end time in ISO 8601 simplified
* * `v`: `60`
*/
const luxonSettings = {
format: "basic",
suppressMilliseconds: true,
includeOffset: false,
};
const startDate = DateTime.fromJSDate(this.event.start).toISO(luxonSettings);
const endDate = DateTime.fromJSDate(this.event.end).toISO(luxonSettings);

return __compileURL({
baseURL: "https://calendar.yahoo.com/",
queryParams: {
title: this.event.titleEncoded,
desc: this.event.descriptionEncoded,
in_loc: this.event.location,
st: startDate,
et: endDate,
v: "60",
},
});
}

/**
* @private
* Creates the final URL for adding events to third-party calendars, formating
* the query parameters automatically
*
* @param {Object} url
* @param {String} url.baseURL - The base URL for the third-party calendar
* @param {Object} url.queryParams - An object of key/values for query
* parameters. Null/undefined values are skipped automatically
*
* @returns {String} URL string, e.g.
* `https://calendar.google.com/calendar/render/?text=Example&dates=...`
*/
_compileURL(url) {
return (
url.baseURL +
"?" +
Object.entries(url.queryParams)
// remove parameters with undefined values (e.g. start and end time)
.filter(([key, value]) => value !== undefined && value !== null)
.map(([key, value]) => `${key}=${value}`)
.join("&")
);
}

/**
* @private
* Prepares query parameters for Office365 and Outlook.com 'Add to calendar'
* links
*
* Query parameters:
* * `path`: `/calendar/action/compose`
* * `rru`: `addevent`
* * `subject`: The event title (string)
* * `body`: The event description (string, no html support)
* * `location`: Event location (string)
* * `startdt`: Event start time in ISO 8601 (YYYY-mm-ddTHH:mm:ss+Z).
* Apparently, timezone offsets are not supported, convert to UTC instead
* * `enddt`: Event start time in ISO 8601 (YYYY-mm-ddTHH:mm:ss+Z)
*
* @see {@link https://en.wikipedia.org/wiki/ISO_8601 ISO 8601}
*
* @returns {Object} Object of key/value for the supported query parameters in
* Microsoft calendars
*/
_getMicrosoftQueryParams() {
/**
* Custom encoding for Microsoft Calendars
* * Unicode for "normal" space: `U+0020`
* * Unicode for thin space: `U+2009`
*
* Note: replaceAll() for strings still not well supported, and using
* unicode in regex (to replace all ocurrences with replace()) is tricky
* as well. Hacky solution below uses split() and join()
*
* @see {@link https://github.com/msramalho/SigTools/issues/83}
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replaceAll}
*/
const customEncode = (str) => str.split("\u0020").join("\u2009");

const luxonSettings = {
suppressMilliseconds: true,
includeOffset: true,
};

return {
rru: "addevent",
subject: encodeURIComponent(customEncode(this.event.title)),
startdt: DateTime.fromJSDate(this.event.start).toUTC().toISO(luxonSettings),
enddt: DateTime.fromJSDate(this.event.end).toUTC().toISO(luxonSettings),
location: encodeURIComponent(customEncode(this.event.location || "")),
body: encodeURIComponent(customEncode(this.event.description)),
path: "/calendar/action/compose",
};
}
}

/**
* Represents a calendar event instance, e.g. an exam, a recurring class or
* a bill deadline
Expand Down Expand Up @@ -312,6 +524,14 @@ class CalEvent {
else return this._end;
}

get titleEncoded() {
return encodeURIComponent(this.title);
}

get descriptionEncoded() {
return encodeURIComponent(this.description);
}

/**
* @private
* Calculates the start/end datetimes accordingly with the recurrence
Expand Down

0 comments on commit 5e71447

Please sign in to comment.