'use strict'; (function (factory) { if (typeof module === 'object' && module.exports) { module.exports = factory(); process.profile = function (operation, start) { console.log('%s took %d milliseconds', operation, process.elapsedTimeSince(start)); }; process.elapsedTimeSince = function (start) { const diff = process.hrtime(start); return (diff[0] * 1e3) + (diff[1] / 1e6); }; } else { window.utils = factory(); } // eslint-disable-next-line }(function () { // add default escape function for escaping HTML entities const escapeCharMap = Object.freeze({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', '`': '`', '=': '=', }); function replaceChar(c) { return escapeCharMap[c]; } const escapeChars = /[&<>"'`=]/g; const HTMLEntities = Object.freeze({ amp: '&', gt: '>', lt: '<', quot: '"', apos: "'", AElig: 198, Aacute: 193, Acirc: 194, Agrave: 192, Aring: 197, Atilde: 195, Auml: 196, Ccedil: 199, ETH: 208, Eacute: 201, Ecirc: 202, Egrave: 200, Euml: 203, Iacute: 205, Icirc: 206, Igrave: 204, Iuml: 207, Ntilde: 209, Oacute: 211, Ocirc: 212, Ograve: 210, Oslash: 216, Otilde: 213, Ouml: 214, THORN: 222, Uacute: 218, Ucirc: 219, Ugrave: 217, Uuml: 220, Yacute: 221, aacute: 225, acirc: 226, aelig: 230, agrave: 224, aring: 229, atilde: 227, auml: 228, ccedil: 231, eacute: 233, ecirc: 234, egrave: 232, eth: 240, euml: 235, iacute: 237, icirc: 238, igrave: 236, iuml: 239, ntilde: 241, oacute: 243, ocirc: 244, ograve: 242, oslash: 248, otilde: 245, ouml: 246, szlig: 223, thorn: 254, uacute: 250, ucirc: 251, ugrave: 249, uuml: 252, yacute: 253, yuml: 255, copy: 169, reg: 174, nbsp: 160, iexcl: 161, cent: 162, pound: 163, curren: 164, yen: 165, brvbar: 166, sect: 167, uml: 168, ordf: 170, laquo: 171, not: 172, shy: 173, macr: 175, deg: 176, plusmn: 177, sup1: 185, sup2: 178, sup3: 179, acute: 180, micro: 181, para: 182, middot: 183, cedil: 184, ordm: 186, raquo: 187, frac14: 188, frac12: 189, frac34: 190, iquest: 191, times: 215, divide: 247, 'OElig;': 338, 'oelig;': 339, 'Scaron;': 352, 'scaron;': 353, 'Yuml;': 376, 'fnof;': 402, 'circ;': 710, 'tilde;': 732, 'Alpha;': 913, 'Beta;': 914, 'Gamma;': 915, 'Delta;': 916, 'Epsilon;': 917, 'Zeta;': 918, 'Eta;': 919, 'Theta;': 920, 'Iota;': 921, 'Kappa;': 922, 'Lambda;': 923, 'Mu;': 924, 'Nu;': 925, 'Xi;': 926, 'Omicron;': 927, 'Pi;': 928, 'Rho;': 929, 'Sigma;': 931, 'Tau;': 932, 'Upsilon;': 933, 'Phi;': 934, 'Chi;': 935, 'Psi;': 936, 'Omega;': 937, 'alpha;': 945, 'beta;': 946, 'gamma;': 947, 'delta;': 948, 'epsilon;': 949, 'zeta;': 950, 'eta;': 951, 'theta;': 952, 'iota;': 953, 'kappa;': 954, 'lambda;': 955, 'mu;': 956, 'nu;': 957, 'xi;': 958, 'omicron;': 959, 'pi;': 960, 'rho;': 961, 'sigmaf;': 962, 'sigma;': 963, 'tau;': 964, 'upsilon;': 965, 'phi;': 966, 'chi;': 967, 'psi;': 968, 'omega;': 969, 'thetasym;': 977, 'upsih;': 978, 'piv;': 982, 'ensp;': 8194, 'emsp;': 8195, 'thinsp;': 8201, 'zwnj;': 8204, 'zwj;': 8205, 'lrm;': 8206, 'rlm;': 8207, 'ndash;': 8211, 'mdash;': 8212, 'lsquo;': 8216, 'rsquo;': 8217, 'sbquo;': 8218, 'ldquo;': 8220, 'rdquo;': 8221, 'bdquo;': 8222, 'dagger;': 8224, 'Dagger;': 8225, 'bull;': 8226, 'hellip;': 8230, 'permil;': 8240, 'prime;': 8242, 'Prime;': 8243, 'lsaquo;': 8249, 'rsaquo;': 8250, 'oline;': 8254, 'frasl;': 8260, 'euro;': 8364, 'image;': 8465, 'weierp;': 8472, 'real;': 8476, 'trade;': 8482, 'alefsym;': 8501, 'larr;': 8592, 'uarr;': 8593, 'rarr;': 8594, 'darr;': 8595, 'harr;': 8596, 'crarr;': 8629, 'lArr;': 8656, 'uArr;': 8657, 'rArr;': 8658, 'dArr;': 8659, 'hArr;': 8660, 'forall;': 8704, 'part;': 8706, 'exist;': 8707, 'empty;': 8709, 'nabla;': 8711, 'isin;': 8712, 'notin;': 8713, 'ni;': 8715, 'prod;': 8719, 'sum;': 8721, 'minus;': 8722, 'lowast;': 8727, 'radic;': 8730, 'prop;': 8733, 'infin;': 8734, 'ang;': 8736, 'and;': 8743, 'or;': 8744, 'cap;': 8745, 'cup;': 8746, 'int;': 8747, 'there4;': 8756, 'sim;': 8764, 'cong;': 8773, 'asymp;': 8776, 'ne;': 8800, 'equiv;': 8801, 'le;': 8804, 'ge;': 8805, 'sub;': 8834, 'sup;': 8835, 'nsub;': 8836, 'sube;': 8838, 'supe;': 8839, 'oplus;': 8853, 'otimes;': 8855, 'perp;': 8869, 'sdot;': 8901, 'lceil;': 8968, 'rceil;': 8969, 'lfloor;': 8970, 'rfloor;': 8971, 'lang;': 9001, 'rang;': 9002, 'loz;': 9674, 'spades;': 9824, 'clubs;': 9827, 'hearts;': 9829, 'diams;': 9830, }); const utils = { generateUUID: function () { /* eslint-disable no-bitwise */ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { const r = Math.random() * 16 | 0; const v = c === 'x' ? r : ((r & 0x3) | 0x8); return v.toString(16); }); /* eslint-enable no-bitwise */ }, // https://github.com/substack/node-ent/blob/master/index.js decodeHTMLEntities: function (html) { return String(html) .replace(/&#(\d+);?/g, function (_, code) { return String.fromCharCode(code); }) .replace(/&#[xX]([A-Fa-f0-9]+);?/g, function (_, hex) { return String.fromCharCode(parseInt(hex, 16)); }) .replace(/&([^;\W]+;?)/g, function (m, e) { const ee = e.replace(/;$/, ''); const target = HTMLEntities[e] || (e.match(/;$/) && HTMLEntities[ee]); if (typeof target === 'number') { return String.fromCharCode(target); } else if (typeof target === 'string') { return target; } return m; }); }, // https://github.com/jprichardson/string.js/blob/master/lib/string.js stripHTMLTags: function (str, tags) { const pattern = (tags || ['']).join('|'); return String(str).replace(new RegExp('<(\\/)?(' + (pattern || '[^\\s>]+') + ')(\\s+[^<>]*?)?\\s*(\\/)?>', 'gi'), ''); }, cleanUpTag: function (tag, maxLength) { if (typeof tag !== 'string' || !tag.length) { return ''; } tag = tag.trim().toLowerCase(); // see https://github.com/NodeBB/NodeBB/issues/4378 tag = tag.replace(/\u202E/gi, ''); tag = tag.replace(/[,/#!$%^*;:{}=_`<>'"~()?|]/g, ''); tag = tag.substr(0, maxLength || 15).trim(); const matches = tag.match(/^[.-]*(.+?)[.-]*$/); if (matches && matches.length > 1) { tag = matches[1]; } return tag; }, removePunctuation: function (str) { return str.replace(/[.,-/#!$%^&*;:{}=\-_`<>'"~()?]/g, ''); }, isEmailValid: function (email) { return typeof email === 'string' && email.length && email.indexOf('@') !== -1 && email.indexOf(',') === -1 && email.indexOf(';') === -1; }, isUserNameValid: function (name) { return (name && name !== '' && (/^['" \-+.*[\]0-9\u00BF-\u1FFF\u2C00-\uD7FF\w]+$/.test(name))); }, isPasswordValid: function (password) { return typeof password === 'string' && password.length; }, isNumber: function (n) { // `isFinite('') === true` so isNan parseFloat check is necessary return !isNaN(parseFloat(n)) && isFinite(n); }, languageKeyRegex: /\[\[[\w]+:.+\]\]/, hasLanguageKey: function (input) { return utils.languageKeyRegex.test(input); }, userLangToTimeagoCode: function (userLang) { const mapping = { 'en-GB': 'en', 'en-US': 'en', 'fa-IR': 'fa', 'pt-BR': 'pt-br', nb: 'no', }; return mapping.hasOwnProperty(userLang) ? mapping[userLang] : userLang; }, // shallow objects merge merge: function () { const result = {}; let obj; let keys; for (let i = 0; i < arguments.length; i += 1) { obj = arguments[i] || {}; keys = Object.keys(obj); for (let j = 0; j < keys.length; j += 1) { result[keys[j]] = obj[keys[j]]; } } return result; }, fileExtension: function (path) { return ('' + path).split('.').pop(); }, extensionMimeTypeMap: { bmp: 'image/bmp', cmx: 'image/x-cmx', cod: 'image/cis-cod', gif: 'image/gif', ico: 'image/x-icon', ief: 'image/ief', jfif: 'image/pipeg', jpe: 'image/jpeg', jpeg: 'image/jpeg', jpg: 'image/jpeg', png: 'image/png', pbm: 'image/x-portable-bitmap', pgm: 'image/x-portable-graymap', pnm: 'image/x-portable-anymap', ppm: 'image/x-portable-pixmap', ras: 'image/x-cmu-raster', rgb: 'image/x-rgb', svg: 'image/svg+xml', tif: 'image/tiff', tiff: 'image/tiff', xbm: 'image/x-xbitmap', xpm: 'image/x-xpixmap', xwd: 'image/x-xwindowdump', }, fileMimeType: function (path) { return utils.extensionToMimeType(utils.fileExtension(path)); }, extensionToMimeType: function (extension) { return utils.extensionMimeTypeMap.hasOwnProperty(extension) ? utils.extensionMimeTypeMap[extension] : '*'; }, isPromise: function (object) { // https://stackoverflow.com/questions/27746304/how-do-i-tell-if-an-object-is-a-promise#comment97339131_27746324 return object && typeof object.then === 'function'; }, promiseParallel: function (obj) { const keys = Object.keys(obj); return Promise.all( keys.map(function (k) { return obj[k]; }) ).then(function (results) { const data = {}; keys.forEach(function (k, i) { data[k] = results[i]; }); return data; }); }, // https://github.com/sindresorhus/is-absolute-url isAbsoluteUrlRE: /^[a-zA-Z][a-zA-Z\d+\-.]*:/, isWinPathRE: /^[a-zA-Z]:\\/, isAbsoluteUrl: function (url) { if (utils.isWinPathRE.test(url)) { return false; } return utils.isAbsoluteUrlRE.test(url); }, isRelativeUrl: function (url) { return !utils.isAbsoluteUrl(url); }, makeNumbersHumanReadable: function (elements) { elements.each(function () { $(this) .html(utils.makeNumberHumanReadable($(this).attr('title'))) .removeClass('hidden'); }); }, makeNumberHumanReadable: function (num) { const n = parseInt(num, 10); if (!n) { return num; } if (n > 999999) { return (n / 1000000).toFixed(1) + 'm'; } else if (n > 999) { return (n / 1000).toFixed(1) + 'k'; } return n; }, addCommasToNumbers: function (elements) { elements.each(function (index, element) { $(element) .html(utils.addCommas($(element).html())) .removeClass('hidden'); }); }, // takes a string like 1000 and returns 1,000 addCommas: function (text) { return String(text).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,'); }, toISOString: function (timestamp) { if (!timestamp || !Date.prototype.toISOString) { return ''; } // Prevent too-high values to be passed to Date object timestamp = Math.min(timestamp, 8640000000000000); try { return new Date(parseInt(timestamp, 10)).toISOString(); } catch (e) { return timestamp; } }, tags: ['a', 'abbr', 'acronym', 'address', 'applet', 'area', 'article', 'aside', 'audio', 'b', 'base', 'basefont', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'command', 'datalist', 'dd', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'em', 'embed', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'frame', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hr', 'html', 'i', 'iframe', 'img', 'input', 'ins', 'kbd', 'keygen', 'label', 'legend', 'li', 'link', 'map', 'mark', 'menu', 'meta', 'meter', 'nav', 'noframes', 'noscript', 'object', 'ol', 'optgroup', 'option', 'output', 'p', 'param', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'script', 'section', 'select', 'small', 'source', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'textarea', 'tfoot', 'th', 'thead', 'time', 'title', 'tr', 'track', 'tt', 'u', 'ul', 'const', 'video', 'wbr'], stripTags: ['abbr', 'acronym', 'address', 'applet', 'area', 'article', 'aside', 'audio', 'base', 'basefont', 'bdi', 'bdo', 'big', 'blink', 'body', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'command', 'datalist', 'dd', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'em', 'embed', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'frame', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hr', 'html', 'iframe', 'input', 'ins', 'kbd', 'keygen', 'label', 'legend', 'li', 'link', 'map', 'mark', 'marquee', 'menu', 'meta', 'meter', 'nav', 'noframes', 'noscript', 'object', 'ol', 'optgroup', 'option', 'output', 'param', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'script', 'section', 'select', 'source', 'span', 'strike', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'textarea', 'tfoot', 'th', 'thead', 'time', 'title', 'tr', 'track', 'tt', 'u', 'ul', 'const', 'video', 'wbr'], escapeRegexChars: function (text) { return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); }, escapeHTML: function (str) { if (str == null) { return ''; } if (!str) { return String(str); } return str.toString().replace(escapeChars, replaceChar); }, isAndroidBrowser: function () { // http://stackoverflow.com/questions/9286355/how-to-detect-only-the-native-android-browser const nua = navigator.userAgent; return ((nua.indexOf('Mozilla/5.0') > -1 && nua.indexOf('Android ') > -1 && nua.indexOf('AppleWebKit') > -1) && !(nua.indexOf('Chrome') > -1)); }, isTouchDevice: function () { return 'ontouchstart' in document.documentElement; }, findBootstrapEnvironment: function () { // http://stackoverflow.com/questions/14441456/how-to-detect-which-device-view-youre-on-using-twitter-bootstrap-api const envs = ['xs', 'sm', 'md', 'lg']; const $el = $('
'); $el.appendTo($('body')); for (let i = envs.length - 1; i >= 0; i -= 1) { const env = envs[i]; $el.addClass('hidden-' + env); if ($el.is(':hidden')) { $el.remove(); return env; } } }, isMobile: function () { const env = utils.findBootstrapEnvironment(); return ['xs', 'sm'].some(function (targetEnv) { return targetEnv === env; }); }, getHoursArray: function () { const currentHour = new Date().getHours(); const labels = []; for (let i = currentHour, ii = currentHour - 24; i > ii; i -= 1) { const hour = i < 0 ? 24 + i : i; labels.push(hour + ':00'); } return labels.reverse(); }, getDaysArray: function (from, amount) { const currentDay = new Date(parseInt(from, 10) || Date.now()).getTime(); const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; const labels = []; let tmpDate; for (let x = (amount || 30) - 1; x >= 0; x -= 1) { tmpDate = new Date(currentDay - (1000 * 60 * 60 * 24 * x)); labels.push(months[tmpDate.getMonth()] + ' ' + tmpDate.getDate()); } return labels; }, /* Retrieved from http://stackoverflow.com/a/7557433 @ 27 Mar 2016 */ isElementInViewport: function (el) { // special bonus for those using jQuery if (typeof jQuery === 'function' && el instanceof jQuery) { el = el[0]; } const rect = el.getBoundingClientRect(); return ( rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && /* or $(window).height() */ rect.right <= (window.innerWidth || document.documentElement.clientWidth) /* or $(window).width() */ ); }, // get all the url params in a single key/value hash params: function (options) { const hash = {}; options = options || {}; options.skipToType = options.skipToType || {}; let searchStr = window.location.search; if (options.hasOwnProperty('url')) { if (options.url) { const a = utils.urlToLocation(options.url); searchStr = a ? a.search : ''; } else { searchStr = ''; } } const params = searchStr.substring(1).split('&'); params.forEach(function (param) { const val = param.split('='); let key = decodeURI(val[0]); const value = ( options.disableToType || options.skipToType[key] ? decodeURI(val[1]) : utils.toType(decodeURI(val[1])) ); if (key) { if (key.substr(-2, 2) === '[]') { key = key.slice(0, -2); } if (!hash[key]) { hash[key] = value; } else { if (!Array.isArray(hash[key])) { hash[key] = [hash[key]]; } hash[key].push(value); } } }); return hash; }, param: function (key) { return this.params()[key]; }, urlToLocation: function (url) { const a = document.createElement('a'); a.href = url; return a; }, // return boolean if string 'true' or string 'false', or if a parsable string which is a number // also supports JSON object and/or arrays parsing toType: function (str) { const type = typeof str; if (type !== 'string') { return str; } const nb = parseFloat(str); if (!isNaN(nb) && isFinite(str)) { return nb; } if (str === 'false') { return false; } if (str === 'true') { return true; } try { str = JSON.parse(str); } catch (e) {} return str; }, // Safely get/set chained properties on an object // set example: utils.props(A, 'a.b.c.d', 10) // sets A to {a: {b: {c: {d: 10}}}}, and returns 10 // get example: utils.props(A, 'a.b.c') // returns {d: 10} // get example: utils.props(A, 'a.b.c.foo.bar') // returns undefined without throwing a TypeError // credits to github.com/gkindel props: function (obj, props, value) { if (obj === undefined) { obj = window; } if (props == null) { return undefined; } const i = props.indexOf('.'); if (i === -1) { if (value !== undefined) { obj[props] = value; } return obj[props]; } const prop = props.slice(0, i); const newProps = props.slice(i + 1); if (props !== undefined && !(obj[prop] instanceof Object)) { obj[prop] = {}; } return utils.props(obj[prop], newProps, value); }, isInternalURI: function (targetLocation, referenceLocation, relative_path) { return targetLocation.host === '' || // Relative paths are always internal links ( targetLocation.host === referenceLocation.host && // Otherwise need to check if protocol and host match targetLocation.protocol === referenceLocation.protocol && // Subfolder installs need this additional check (relative_path.length > 0 ? targetLocation.pathname.indexOf(relative_path) === 0 : true) ); }, rtrim: function (str) { return str.replace(/\s+$/g, ''); }, debounce: function (func, wait, immediate) { // modified from https://davidwalsh.name/javascript-debounce-function let timeout; return function () { const context = this; const args = arguments; const later = function () { timeout = null; if (!immediate) { func.apply(context, args); } }; const callNow = immediate && !timeout; clearTimeout(timeout); timeout = setTimeout(later, wait); if (callNow) { func.apply(context, args); } }; }, throttle: function (func, wait, immediate) { let timeout; return function () { const context = this; const args = arguments; const later = function () { timeout = null; if (!immediate) { func.apply(context, args); } }; const callNow = immediate && !timeout; if (!timeout) { timeout = setTimeout(later, wait); } if (callNow) { func.apply(context, args); } }; }, }; return utils; }));