UNPKG

@withjoy/react-add-to-calendar

Version:

A simple and reusable add to calendar button component for React

701 lines (576 loc) 26.5 kB
(function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(require("react"), require("moment")); else if(typeof define === 'function' && define.amd) define(["react", "moment"], factory); else if(typeof exports === 'object') exports["ReactAddToCalendar"] = factory(require("react"), require("moment")); else root["ReactAddToCalendar"] = factory(root["React"], root["moment"]); })(this, function(__WEBPACK_EXTERNAL_MODULE_1__, __WEBPACK_EXTERNAL_MODULE_8__) { return /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) /******/ return installedModules[moduleId].exports; /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ exports: {}, /******/ id: moduleId, /******/ loaded: false /******/ }; /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ // Flag the module as loaded /******/ module.loaded = true; /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; /******/ // Load entry module and return exports /******/ return __webpack_require__(0); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _react = __webpack_require__(1); var _react2 = _interopRequireDefault(_react); var _propTypes = __webpack_require__(2); var _propTypes2 = _interopRequireDefault(_propTypes); var _helpers = __webpack_require__(7); var _helpers2 = _interopRequireDefault(_helpers); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } var helpers = new _helpers2.default(); var ReactAddToCalendar = function (_React$Component) { _inherits(ReactAddToCalendar, _React$Component); function ReactAddToCalendar(props) { _classCallCheck(this, ReactAddToCalendar); var _this = _possibleConstructorReturn(this, (ReactAddToCalendar.__proto__ || Object.getPrototypeOf(ReactAddToCalendar)).call(this, props)); _this.state = { optionsOpen: props.optionsOpen || false, isCrappyIE: false }; _this.toggleCalendarDropdown = _this.toggleCalendarDropdown.bind(_this); _this.handleDropdownLinkClick = _this.handleDropdownLinkClick.bind(_this); return _this; } _createClass(ReactAddToCalendar, [{ key: "componentWillMount", value: function componentWillMount() { // polyfill for startsWith to fix IE bug if (!String.prototype.startsWith) { String.prototype.startsWith = function (searchString, position) { position = position || 0; return this.indexOf(searchString, position) === position; }; } var isCrappyIE = false; if (typeof window !== "undefined" && window.navigator.msSaveOrOpenBlob && window.Blob) { isCrappyIE = true; } this.setState({ isCrappyIE: isCrappyIE }); } }, { key: "toggleCalendarDropdown", value: function toggleCalendarDropdown() { var showOptions = !this.state.optionsOpen; if (showOptions) { document.addEventListener("click", this.toggleCalendarDropdown, false); } else { document.removeEventListener("click", this.toggleCalendarDropdown); } this.setState({ optionsOpen: showOptions }); } }, { key: "handleDropdownLinkClick", value: function handleDropdownLinkClick(e) { e.preventDefault(); var url = e.currentTarget.getAttribute("href"); if (!helpers.isMobile() && (url.startsWith("data") || url.startsWith("BEGIN"))) { var filename = "download.ics"; var blob = new Blob([url], { type: "text/calendar;charset=utf-8" }); if (this.state.isCrappyIE) { window.navigator.msSaveOrOpenBlob(blob, filename); } else { /**************************************************************** // many browsers do not properly support downloading data URIs // (even with "download" attribute in use) so this solution // ensures the event will download cross-browser ****************************************************************/ var link = document.createElement("a"); link.href = window.URL.createObjectURL(blob); link.setAttribute("download", filename); document.body.appendChild(link); link.click(); document.body.removeChild(link); } } else { window.open(url, "_blank"); } this.toggleCalendarDropdown(); } }, { key: "renderDropdown", value: function renderDropdown() { var self = this; var items = this.props.listItems.map(function (listItem) { var currentItem = Object.keys(listItem)[0]; var currentLabel = listItem[currentItem]; var icon = null; if (self.props.displayItemIcons) { var currentIcon = currentItem === "outlook" || currentItem === "outlookcom" ? "windows" : currentItem; icon = _react2.default.createElement("i", { className: "fa fa-" + currentIcon }); } return _react2.default.createElement( "li", { key: helpers.getRandomKey() }, _react2.default.createElement( "a", { className: currentItem + "-link", onClick: self.handleDropdownLinkClick, href: helpers.buildUrl(self.props.event, currentItem, self.state.isCrappyIE), target: "_blank" }, icon, currentLabel ) ); }); return _react2.default.createElement( "div", { className: this.props.dropdownClass }, _react2.default.createElement( "ul", null, items ) ); } }, { key: "renderButton", value: function renderButton() { var buttonLabel = this.props.buttonLabel; var buttonIcon = null; var template = Object.keys(this.props.buttonTemplate); if (template[0] !== "textOnly") { var iconPlacement = this.props.buttonTemplate[template]; var buttonClassPrefix = this.props.buttonIconClass === "react-add-to-calendar__icon--" ? "" + this.props.buttonIconClass + iconPlacement : this.props.buttonIconClass; var iconPrefix = this.props.useFontAwesomeIcons ? "fa fa-" : ""; var mainButtonIconClass = template[0] === "caret" ? this.state.optionsOpen ? "caret-up" : "caret-down" : template[0]; var buttonIconClass = buttonClassPrefix + " " + iconPrefix + mainButtonIconClass; buttonIcon = _react2.default.createElement("i", { className: buttonIconClass }); buttonLabel = iconPlacement === "right" ? _react2.default.createElement( "span", null, buttonLabel + " ", buttonIcon ) : _react2.default.createElement( "span", null, buttonIcon, " " + buttonLabel ); } var buttonClass = this.state.optionsOpen ? this.props.buttonClassClosed + " " + this.props.buttonClassOpen : this.props.buttonClassClosed; return _react2.default.createElement( "div", { className: this.props.buttonWrapperClass }, _react2.default.createElement( "a", { className: buttonClass, onClick: this.toggleCalendarDropdown }, buttonLabel ) ); } }, { key: "render", value: function render() { var options = null; if (this.state.optionsOpen) { options = this.renderDropdown(); } var addToCalendarBtn = null; if (this.props.event) { addToCalendarBtn = this.renderButton(); } return _react2.default.createElement( "div", { className: this.props.rootClass }, addToCalendarBtn, options ); } }]); return ReactAddToCalendar; }(_react2.default.Component); exports.default = ReactAddToCalendar; ReactAddToCalendar.displayName = "Add To Calendar"; ReactAddToCalendar.propTypes = { buttonClassClosed: _propTypes2.default.string, buttonClassOpen: _propTypes2.default.string, buttonLabel: _propTypes2.default.string, buttonTemplate: _propTypes2.default.object, buttonIconClass: _propTypes2.default.string, useFontAwesomeIcons: _propTypes2.default.bool, buttonWrapperClass: _propTypes2.default.string, displayItemIcons: _propTypes2.default.bool, optionsOpen: _propTypes2.default.bool, dropdownClass: _propTypes2.default.string, event: _propTypes2.default.shape({ title: _propTypes2.default.string, description: _propTypes2.default.string, location: _propTypes2.default.string, startTime: _propTypes2.default.string, endTime: _propTypes2.default.string }).isRequired, listItems: _propTypes2.default.arrayOf(_propTypes2.default.object), rootClass: _propTypes2.default.string }; ReactAddToCalendar.defaultProps = { buttonClassClosed: "react-add-to-calendar__button", buttonClassOpen: "react-add-to-calendar__button--light", buttonLabel: "Add to My Calendar", buttonTemplate: { caret: "right" }, buttonIconClass: "react-add-to-calendar__icon--", useFontAwesomeIcons: true, buttonWrapperClass: "react-add-to-calendar__wrapper", displayItemIcons: true, optionsOpen: false, dropdownClass: "react-add-to-calendar__dropdown", event: { title: "Sample Event", description: "This is the sample event provided as an example only", location: "Portland, OR", startTime: "2016-09-16T20:15:00-04:00", endTime: "2016-09-16T21:45:00-04:00" }, listItems: [{ apple: "Apple Calendar" }, { google: "Google" }, { outlook: "Outlook" }, { outlookcom: "Outlook.com" }, { yahoo: "Yahoo" }], rootClass: "react-add-to-calendar" }; /***/ }), /* 1 */ /***/ (function(module, exports) { module.exports = __WEBPACK_EXTERNAL_MODULE_1__; /***/ }), /* 2 */ /***/ (function(module, exports, __webpack_require__) { /** * Copyright (c) 2013-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ if (false) { var REACT_ELEMENT_TYPE = (typeof Symbol === 'function' && Symbol.for && Symbol.for('react.element')) || 0xeac7; var isValidElement = function(object) { return typeof object === 'object' && object !== null && object.$$typeof === REACT_ELEMENT_TYPE; }; // By explicitly using `prop-types` you are opting into new development behavior. // http://fb.me/prop-types-in-prod var throwOnDirectAccess = true; module.exports = require('./factoryWithTypeCheckers')(isValidElement, throwOnDirectAccess); } else { // By explicitly using `prop-types` you are opting into new production behavior. // http://fb.me/prop-types-in-prod module.exports = __webpack_require__(3)(); } /***/ }), /* 3 */ /***/ (function(module, exports, __webpack_require__) { /** * Copyright (c) 2013-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ 'use strict'; var emptyFunction = __webpack_require__(4); var invariant = __webpack_require__(5); var ReactPropTypesSecret = __webpack_require__(6); module.exports = function() { function shim(props, propName, componentName, location, propFullName, secret) { if (secret === ReactPropTypesSecret) { // It is still safe when called from React. return; } invariant( false, 'Calling PropTypes validators directly is not supported by the `prop-types` package. ' + 'Use PropTypes.checkPropTypes() to call them. ' + 'Read more at http://fb.me/use-check-prop-types' ); }; shim.isRequired = shim; function getShim() { return shim; }; // Important! // Keep this list in sync with production version in `./factoryWithTypeCheckers.js`. var ReactPropTypes = { array: shim, bool: shim, func: shim, number: shim, object: shim, string: shim, symbol: shim, any: shim, arrayOf: getShim, element: shim, instanceOf: getShim, node: shim, objectOf: getShim, oneOf: getShim, oneOfType: getShim, shape: getShim, exact: getShim }; ReactPropTypes.checkPropTypes = emptyFunction; ReactPropTypes.PropTypes = ReactPropTypes; return ReactPropTypes; }; /***/ }), /* 4 */ /***/ (function(module, exports) { "use strict"; /** * Copyright (c) 2013-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * */ function makeEmptyFunction(arg) { return function () { return arg; }; } /** * This function accepts and discards inputs; it has no side effects. This is * primarily useful idiomatically for overridable function endpoints which * always need to be callable, since JS lacks a null-call idiom ala Cocoa. */ var emptyFunction = function emptyFunction() {}; emptyFunction.thatReturns = makeEmptyFunction; emptyFunction.thatReturnsFalse = makeEmptyFunction(false); emptyFunction.thatReturnsTrue = makeEmptyFunction(true); emptyFunction.thatReturnsNull = makeEmptyFunction(null); emptyFunction.thatReturnsThis = function () { return this; }; emptyFunction.thatReturnsArgument = function (arg) { return arg; }; module.exports = emptyFunction; /***/ }), /* 5 */ /***/ (function(module, exports, __webpack_require__) { /** * Copyright (c) 2013-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * */ 'use strict'; /** * Use invariant() to assert state which your program assumes to be true. * * Provide sprintf-style format (only %s is supported) and arguments * to provide information about what broke and what you were * expecting. * * The invariant message will be stripped in production, but the invariant * will remain to ensure logic does not differ in production. */ var validateFormat = function validateFormat(format) {}; if (false) { validateFormat = function validateFormat(format) { if (format === undefined) { throw new Error('invariant requires an error message argument'); } }; } function invariant(condition, format, a, b, c, d, e, f) { validateFormat(format); if (!condition) { var error; if (format === undefined) { error = new Error('Minified exception occurred; use the non-minified dev environment ' + 'for the full error message and additional helpful warnings.'); } else { var args = [a, b, c, d, e, f]; var argIndex = 0; error = new Error(format.replace(/%s/g, function () { return args[argIndex++]; })); error.name = 'Invariant Violation'; } error.framesToPop = 1; // we don't care about invariant's own frame throw error; } } module.exports = invariant; /***/ }), /* 6 */ /***/ (function(module, exports) { /** * Copyright (c) 2013-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ 'use strict'; var ReactPropTypesSecret = 'SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED'; module.exports = ReactPropTypesSecret; /***/ }), /* 7 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _moment = __webpack_require__(8); var _moment2 = _interopRequireDefault(_moment); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var helpers = function () { function helpers() { _classCallCheck(this, helpers); } _createClass(helpers, [{ key: "getRandomKey", value: function getRandomKey() { var n = Math.floor(Math.random() * 999999999999).toString(); return new Date().getTime().toString() + "_" + n; } }, { key: "formatTime", value: function formatTime(date) { var formattedDate = _moment2.default.utc(date).format("YYYYMMDDTHHmmssZ"); return formattedDate.replace("+00:00", "Z"); } }, { key: "formatRFC5545Text", value: function formatRFC5545Text(raw) { // https://tools.ietf.org/html/rfc5545#section-3.1 // Lines of text SHOULD NOT be longer than 75 octets, excluding the line // break. Long content lines SHOULD be split into a multiple line // representations using a line "folding" technique. // https://tools.ietf.org/html/rfc5545#section-3.3.11 // An intentional formatted text line break MUST only be included in // a "TEXT" property value by representing the line break with the // character sequence of BACKSLASH, followed by a LATIN SMALL LETTER // N or a LATIN CAPITAL LETTER N, that is "\n" or "\N". var text = raw || ""; var escaped = text.replace(/[\r\n]/g, '\\n'); var splits = []; for (var split = escaped; split.length !== 0; split = split.substr(75)) { splits.push(split.substr(0, 75)); } var formatted = splits.join("\n "); return formatted; } }, { key: "calculateDuration", value: function calculateDuration(startTime, endTime) { // snag parameters and format properly in UTC var end = _moment2.default.utc(endTime).format("DD/MM/YYYY HH:mm:ss"); var start = _moment2.default.utc(startTime).format("DD/MM/YYYY HH:mm:ss"); // calculate the difference in milliseconds between the start and end times var difference = (0, _moment2.default)(end, "DD/MM/YYYY HH:mm:ss").diff((0, _moment2.default)(start, "DD/MM/YYYY HH:mm:ss")); // convert difference from above to a proper momentJs duration object var duration = _moment2.default.duration(difference); return Math.floor(duration.asHours()) + _moment2.default.utc(difference).format(":mm"); } }, { key: "buildUrl", value: function buildUrl(event, type, isCrappyIE) { var calendarUrl = ""; // allow mobile browsers to open the gmail data URI within native calendar app // type = (type == "google" && this.isMobile()) ? "outlook" : type; switch (type) { case "google": calendarUrl = "https://calendar.google.com/calendar/render"; calendarUrl += "?action=TEMPLATE"; calendarUrl += "&dates=" + this.formatTime(event.startTime); calendarUrl += "/" + this.formatTime(event.endTime); calendarUrl += "&location=" + encodeURIComponent(event.location); calendarUrl += "&text=" + encodeURIComponent(event.title); calendarUrl += "&details=" + encodeURIComponent(event.description); break; case "yahoo": // yahoo doesn't utilize endTime so we need to calulate duration var duration = this.calculateDuration(event.startTime, event.endTime); calendarUrl = "https://calendar.yahoo.com/?v=60&view=d&type=20"; calendarUrl += "&title=" + encodeURIComponent(event.title); calendarUrl += "&st=" + this.formatTime(event.startTime); calendarUrl += "&dur=" + duration; calendarUrl += "&desc=" + encodeURIComponent(event.description); calendarUrl += "&in_loc=" + encodeURIComponent(event.location); break; case "outlookcom": calendarUrl = "https://outlook.live.com/owa/?rru=addevent"; calendarUrl += "&startdt=" + this.formatTime(event.startTime); calendarUrl += "&enddt=" + this.formatTime(event.endTime); calendarUrl += "&subject=" + encodeURIComponent(event.title); calendarUrl += "&location=" + encodeURIComponent(event.location); calendarUrl += "&body=" + encodeURIComponent(event.description); calendarUrl += "&allday=false"; calendarUrl += "&uid=" + this.getRandomKey(); calendarUrl += "&path=/calendar/view/Month"; break; default: calendarUrl = ["BEGIN:VCALENDAR", "VERSION:2.0", "BEGIN:VEVENT", "URL:" + document.URL, "DTSTART:" + this.formatTime(event.startTime), "DTEND:" + this.formatTime(event.endTime), "SUMMARY:" + this.formatRFC5545Text(event.title), "DESCRIPTION:" + this.formatRFC5545Text(event.description), "LOCATION:" + this.formatRFC5545Text(event.location), "END:VEVENT", "END:VCALENDAR"].join("\n"); if (!isCrappyIE && this.isMobile()) { calendarUrl = encodeURI("data:text/calendar;charset=utf8," + calendarUrl); } } return calendarUrl; } // determine if a mobile browser is being used }, { key: "isMobile", value: function isMobile() { var mobile = false; (function (a) { if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0, 4))) mobile = true; })(window.navigator.userAgent || window.navigator.vendor || window.opera); return mobile; } }]); return helpers; }(); exports.default = helpers; /***/ }), /* 8 */ /***/ (function(module, exports) { module.exports = __WEBPACK_EXTERNAL_MODULE_8__; /***/ }) /******/ ]) }); ;