Skip to content

Commit

Permalink
Create new util escapeICSDescription
Browse files Browse the repository at this point in the history
- Used to build description in buildShareFile
- Create unitTest
- Update docs with example
  • Loading branch information
jasonleibowitz committed Jun 25, 2020
1 parent e0d11a0 commit edc9458
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 51 deletions.
3 changes: 3 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
testURL: 'http://localhost/'
};
26 changes: 10 additions & 16 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.buildShareUrl = exports.isInternetExplorer = exports.isMobile = exports.formatDuration = exports.formatDate = void 0;
exports.buildShareUrl = exports.escapeICSDescription = exports.isInternetExplorer = exports.isMobile = exports.formatDuration = exports.formatDate = void 0;

var _enums = require("./enums");

Expand Down Expand Up @@ -53,6 +53,12 @@ exports.isMobile = isMobile;
var isInternetExplorer = function isInternetExplorer() {
return /MSIE/.test(window.navigator.userAgent) || /Trident/.test(window.navigator.userAgent);
};

exports.isInternetExplorer = isInternetExplorer;

var escapeICSDescription = function escapeICSDescription(description) {
return description.replace(/(\r?\n|<br ?\/?>)/g, '\\n');
};
/**
* Takes an event object and returns a Google Calendar Event URL
* @param {string} event.description
Expand All @@ -64,7 +70,7 @@ var isInternetExplorer = function isInternetExplorer() {
*/


exports.isInternetExplorer = isInternetExplorer;
exports.escapeICSDescription = escapeICSDescription;

var googleShareUrl = function googleShareUrl(_ref) {
var description = _ref.description,
Expand Down Expand Up @@ -94,18 +100,6 @@ var yahooShareUrl = function yahooShareUrl(_ref2) {
title = _ref2.title;
return "https://calendar.yahoo.com/?v=60&view=d&type=20&title=".concat(title, "&st=").concat(startDatetime, "&dur=").concat(duration, "&desc=").concat(description, "&in_loc=").concat(location);
};

/**
* Takes a multiline description field string and escapes newlines as literal \\n characters for ICS previews
*
* @param {string} description
* @returns {string} ics-compatible multiline string file
*/

var escapeICSDescription = function escapeICSDescription(description) {
return description.replace(/(\r?\n|<br\/? ?>)/g, '\\n');
};

/**
* Takes an event object and returns an array to be downloaded as ics file
* @param {string} event.description
Expand All @@ -119,7 +113,7 @@ var escapeICSDescription = function escapeICSDescription(description) {

var buildShareFile = function buildShareFile(_ref3) {
var _ref3$description = _ref3.description,
description = escapeICSDescription(_ref3$description === void 0 ? '' : _ref3$description),
description = _ref3$description === void 0 ? '' : _ref3$description,
_ref3$ctz = _ref3.ctz,
ctz = _ref3$ctz === void 0 ? '' : _ref3$ctz,
endDatetime = _ref3.endDatetime,
Expand All @@ -132,7 +126,7 @@ var buildShareFile = function buildShareFile(_ref3) {
title = _ref3$title === void 0 ? '' : _ref3$title;
var content = ['BEGIN:VCALENDAR', 'VERSION:2.0', 'BEGIN:VEVENT', "URL:".concat(document.URL), 'METHOD:PUBLISH', // TODO: Will need to parse the date without Z for ics
// This means I'll probably have to require a date lib - luxon most likely or datefns
timezone === '' ? "DTSTART:".concat(startDatetime) : "DTSTART;TZID=".concat(timezone, ":").concat(startDatetime), timezone === '' ? "DTEND:".concat(endDatetime) : "DTEND;TZID=".concat(timezone, ":").concat(endDatetime), "SUMMARY:".concat(title), "DESCRIPTION:".concat(description), "LOCATION:".concat(location), 'END:VEVENT', 'END:VCALENDAR'].join('\n');
timezone === '' ? "DTSTART:".concat(startDatetime) : "DTSTART;TZID=".concat(timezone, ":").concat(startDatetime), timezone === '' ? "DTEND:".concat(endDatetime) : "DTEND;TZID=".concat(timezone, ":").concat(endDatetime), "SUMMARY:".concat(title), "DESCRIPTION:".concat(escapeICSDescription(description)), "LOCATION:".concat(location), 'END:VEVENT', 'END:VCALENDAR'].join('\n');
return isMobile() ? encodeURI("data:text/calendar;charset=utf8,".concat(content)) : content;
};
/**
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 26 additions & 1 deletion src/docs/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,31 @@ function Demo() {
`}
</CodeSnippet>

<h2 className={subTitleStyles}>Dropdown Example - Handle Newlines in Description</h2>
<AddToCalendarDropdown
className={componentStyles}
linkProps={{
className: linkStyles,
}}
event={{
...event,
description: 'Description of event. <br>Going to have a lot of fun doing things that we scheduled ahead of time.'
}}
/>
<CodeSnippet>
{`
const AddToCalendarDropdown = AddToCalendarHOC(Button, Dropdown);
...
<AddToCalendarDropdown
className={componentStyles}
linkProps={{
className: linkStyles,
}}
event={event}
/>
`}
</CodeSnippet>

<h2 className={subTitleStyles}>Modal Example</h2>
<AddToCalendarModal
className={componentStyles}
Expand Down Expand Up @@ -299,7 +324,7 @@ function Demo() {
`}
</CodeSnippet>

<h2 className={subTitleStyles}>Use Moment Alternative</h2>
<h2 className={subTitleStyles}>Use Moment Alternative</h2>
<p className={paragraphStyles}>Moment is known to be a MASSIVE library. v2.22.2 is 64.2kb minified + gzipped and moment-timezone v0.5.21 is 89.8kb minified + gzipped. There are plenty of other date time libraries for JS that are way smaller. Using one of these helps you avoid overly bloating your application and sending too many vendor files to the client. One great option is Luxon. Luxon v.1.4.4 is 16.9kb minified + gzipped.</p>
<p className={paragraphStyles}>This example shows how to use the Luxon library (instead of Moment) to construct <span className={highlightText}>startDatetime</span> and <span className={highlightText}>endDatetime</span></p>
<AddToCalendarModal
Expand Down
47 changes: 29 additions & 18 deletions src/lib/utils.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { SHARE_SITES } from './enums';
import {
SHARE_SITES
} from './enums';


/**
Expand Down Expand Up @@ -31,6 +33,8 @@ export const isMobile = () => /Mobile|iP(hone|od|ad)|Android|BlackBerry|IEMobile
*/
export const isInternetExplorer = () => /MSIE/.test(window.navigator.userAgent) || /Trident/.test(window.navigator.userAgent);

export const escapeICSDescription = description => description.replace(/(\r?\n|<br ?\/?>)/g, '\\n');

/**
* Takes an event object and returns a Google Calendar Event URL
* @param {string} event.description
Expand All @@ -41,13 +45,13 @@ export const isInternetExplorer = () => /MSIE/.test(window.navigator.userAgent)
* @returns {string} Google Calendar Event URL
*/
const googleShareUrl = ({
description,
endDatetime,
location,
startDatetime,
timezone,
title,
}) =>
description,
endDatetime,
location,
startDatetime,
timezone,
title,
}) =>
`https://calendar.google.com/calendar/render?action=TEMPLATE&dates=${
startDatetime
}/${endDatetime}${timezone && `&ctz=${timezone}`}&location=${location}&text=${title}&details=${description}`;
Expand All @@ -62,12 +66,12 @@ const googleShareUrl = ({
* @returns {string} Yahoo Calendar Event URL
*/
const yahooShareUrl = ({
description,
duration,
location,
startDatetime,
title,
}) =>
description,
duration,
location,
startDatetime,
title,
}) =>
`https://calendar.yahoo.com/?v=60&view=d&type=20&title=${title}&st=${
startDatetime
}&dur=${duration}&desc=${description}&in_loc=${location}`;
Expand Down Expand Up @@ -101,7 +105,7 @@ const buildShareFile = ({
timezone === '' ? `DTSTART:${startDatetime}` : `DTSTART;TZID=${timezone}:${startDatetime}`,
timezone === '' ? `DTEND:${endDatetime}` : `DTEND;TZID=${timezone}:${endDatetime}`,
`SUMMARY:${title}`,
`DESCRIPTION:${description}`,
`DESCRIPTION:${escapeICSDescription(description)}`,
`LOCATION:${location}`,
'END:VEVENT',
'END:VCALENDAR',
Expand All @@ -121,8 +125,15 @@ const buildShareFile = ({
* @param {string} event.title
* @param {enum} type One of SHARE_SITES from ./enums
*/
export const buildShareUrl = (
{ description = '', duration, endDatetime, location = '', startDatetime, timezone = '', title = '' },
export const buildShareUrl = ({
description = '',
duration,
endDatetime,
location = '',
startDatetime,
timezone = '',
title = ''
},
type,
) => {
const encodeURI = type !== SHARE_SITES.ICAL && type !== SHARE_SITES.OUTLOOK;
Expand All @@ -145,4 +156,4 @@ export const buildShareUrl = (
default:
return buildShareFile(data);
}
};
};
56 changes: 41 additions & 15 deletions src/lib/utils.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import moment from 'moment';
import { SHARE_SITES } from './enums';
import { buildShareUrl, formatDate, formatDuration, isInternetExplorer, isMobile } from './utils';
import {
SHARE_SITES
} from './enums';
import {
buildShareUrl,
formatDate,
formatDuration,
isInternetExplorer,
isMobile,
escapeICSDescription
} from './utils';

const testEvent = {
description: 'Description of event. Going to have a lot of fun doing things that we scheduled ahead of time.',
Expand All @@ -14,8 +23,8 @@ const testEvent = {
const expectedOutputs = {
google: 'https://calendar.google.com/calendar/render?action=TEMPLATE&dates=20150126T000000Z/20150126T020000Z&location=NYC&text=Super%20Fun%20Event&details=Description%20of%20event.%20Going%20to%20have%20a%20lot%20of%20fun%20doing%20things%20that%20we%20scheduled%20ahead%20of%20time.',
yahoo: 'https://calendar.yahoo.com/?v=60&view=d&type=20&title=Super%20Fun%20Event&st=20150126T000000Z&dur=0200&desc=Description%20of%20event.%20Going%20to%20have%20a%20lot%20of%20fun%20doing%20things%20that%20we%20scheduled%20ahead%20of%20time.&in_loc=NYC',
ics: 'BEGIN:VCALENDAR\nVERSION:2.0\nBEGIN:VEVENT\nURL:about:blank\nMETHOD:PUBLISH\nDTSTART:20150126T000000Z\nDTEND:20150126T020000Z\nSUMMARY:Super Fun Event\nDESCRIPTION:Description of event. Going to have a lot of fun doing things that we scheduled ahead of time.\nLOCATION:NYC\nEND:VEVENT\nEND:VCALENDAR',
icsMobile: 'data:text/calendar;charset=utf8,BEGIN:VCALENDAR\nVERSION:2.0\nBEGIN:VEVENT\nURL:about:blank\nMETHOD:PUBLISH\nDTSTART:20150126T000000Z\nDTEND:20150126T020000Z\nSUMMARY:Super Fun Event\nDESCRIPTION:Description of event. Going to have a lot of fun doing things that we scheduled ahead of time.\nLOCATION:NYC\nEND:VEVENT\nEND:VCALENDAR',
ics: 'BEGIN:VCALENDAR\nVERSION:2.0\nBEGIN:VEVENT\nURL:http://localhost/\nMETHOD:PUBLISH\nDTSTART:20150126T000000Z\nDTEND:20150126T020000Z\nSUMMARY:Super Fun Event\nDESCRIPTION:Description of event. Going to have a lot of fun doing things that we scheduled ahead of time.\nLOCATION:NYC\nEND:VEVENT\nEND:VCALENDAR',
icsMobile: 'data:text/calendar;charset=utf8,BEGIN:VCALENDAR\nVERSION:2.0\nBEGIN:VEVENT\nURL:http://localhost/\nMETHOD:PUBLISH\nDTSTART:20150126T000000Z\nDTEND:20150126T020000Z\nSUMMARY:Super Fun Event\nDESCRIPTION:Description of event. Going to have a lot of fun doing things that we scheduled ahead of time.\nLOCATION:NYC\nEND:VEVENT\nEND:VCALENDAR',
}

describe('formatDate', () => {
Expand All @@ -25,7 +34,7 @@ describe('formatDate', () => {
});

describe('formatDuration', () => {
it ('converts number 2 to string 0200', () => {
it('converts number 2 to string 0200', () => {
expect(formatDuration(2)).toEqual('0200');
});

Expand Down Expand Up @@ -53,7 +62,10 @@ describe('buildShareUrl', () => {
});

it('returns a proper Yahoo share URL when duration is a number', () => {
const result = buildShareUrl({...testEvent, duration: 2}, SHARE_SITES.YAHOO);
const result = buildShareUrl({
...testEvent,
duration: 2
}, SHARE_SITES.YAHOO);
expect(result).toEqual(expectedOutputs.yahoo);
});
});
Expand All @@ -66,7 +78,7 @@ describe('buildShareUrl', () => {
});

it('prepends a data URL when userAgent is mobile', () => {
navigator.__defineGetter__('userAgent', function(){
navigator.__defineGetter__('userAgent', function () {
return "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1";
});

Expand All @@ -78,7 +90,7 @@ describe('buildShareUrl', () => {

describe('isInternetExplorer', () => {
it('returns true is userAgent is IE 11', () => {
navigator.__defineGetter__('userAgent', function(){
navigator.__defineGetter__('userAgent', function () {
return "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729; rv:11.0) like Gecko";
});

Expand All @@ -87,7 +99,7 @@ describe('isInternetExplorer', () => {
});

it('returns true is userAgent is IE 10', () => {
navigator.__defineGetter__('userAgent', function(){
navigator.__defineGetter__('userAgent', function () {
return "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0; .NET4.0E; .NET4.0C; .NET CLR 3.5.30729; .NET CLR 2.0.50727; .NET CLR 3.0.30729)";
});

Expand All @@ -96,7 +108,7 @@ describe('isInternetExplorer', () => {
});

it('returns true is userAgent is IE 9', () => {
navigator.__defineGetter__('userAgent', function(){
navigator.__defineGetter__('userAgent', function () {
return "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET4.0C; .NET4.0E)";
});

Expand All @@ -105,7 +117,7 @@ describe('isInternetExplorer', () => {
});

it('returns false is userAgent is MS Edge', () => {
navigator.__defineGetter__('userAgent', function(){
navigator.__defineGetter__('userAgent', function () {
return "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36";
});

Expand All @@ -114,7 +126,7 @@ describe('isInternetExplorer', () => {
});

it('returns false is userAgent is not IE', () => {
navigator.__defineGetter__('userAgent', function(){
navigator.__defineGetter__('userAgent', function () {
return "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36";
});

Expand All @@ -125,7 +137,7 @@ describe('isInternetExplorer', () => {

describe('isMobile', () => {
it('returns true if userAgent is iPhone', () => {
navigator.__defineGetter__('userAgent', function(){
navigator.__defineGetter__('userAgent', function () {
return "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1";
});

Expand All @@ -134,7 +146,7 @@ describe('isMobile', () => {
});

it('returns true if userAgent is Android', () => {
navigator.__defineGetter__('userAgent', function(){
navigator.__defineGetter__('userAgent', function () {
return "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.109 Mobile Safari/537.36";
});

Expand All @@ -143,11 +155,25 @@ describe('isMobile', () => {
});

it('returns false if userAgent is desktop', () => {
navigator.__defineGetter__('userAgent', function(){
navigator.__defineGetter__('userAgent', function () {
return "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36";
});

const result = isMobile();
expect(result).toBe(false);
})
});

describe('escapeICSDescription', () => {
it('replaces carriage returns with newline characters', () => {
const result = escapeICSDescription('Line One \r\nLine Two');
expect(result).toEqual('Line One \\nLine Two');
});

it('replaces <br> characters with newline characters', () => {
const expectedResult = 'Line One \\nLineTwo';
expect(escapeICSDescription('Line One <br>LineTwo')).toEqual(expectedResult);
expect(escapeICSDescription('Line One <br />LineTwo')).toEqual(expectedResult);
expect(escapeICSDescription('Line One <br >LineTwo')).toEqual(expectedResult);
});
});

0 comments on commit edc9458

Please sign in to comment.