Files
NodeBB/public/src/utils.js

488 lines
14 KiB
JavaScript
Raw Normal View History

(function (module) {
'use strict';
2017-02-17 20:20:42 -07:00
var utils;
var fs;
var XRegExp;
if ('undefined' === typeof window) {
fs = require('fs');
2016-02-24 18:11:42 +02:00
XRegExp = require('xregexp');
process.profile = function (operation, start) {
2014-03-18 18:51:03 -04:00
console.log('%s took %d milliseconds', operation, process.elapsedTimeSince(start));
2014-03-12 00:13:42 -04:00
};
process.elapsedTimeSince = function (start) {
var diff = process.hrtime(start);
2014-03-12 00:13:42 -04:00
return diff[0] * 1e3 + diff[1] / 1e6;
};
2014-01-04 14:24:50 -05:00
} else {
XRegExp = window.XRegExp;
}
module.exports = utils = {
generateUUID: function () {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
2017-02-17 20:20:42 -07:00
var r = Math.random() * 16 | 0;
var v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
},
2017-02-18 01:25:46 -07:00
// Adapted from http://stackoverflow.com/questions/5827612/node-js-fs-readdir-recursive-directory-search
walk: function (dir, done) {
2014-03-04 17:08:15 -05:00
var results = [];
2013-09-07 11:51:37 -04:00
fs.readdir(dir, function (err, list) {
if (err) {
return done(err);
}
2013-06-20 16:48:17 -04:00
var pending = list.length;
if (!pending) {
return done(null, results);
}
list.forEach(function (file) {
file = dir + '/' + file;
fs.stat(file, function (err, stat) {
2016-08-16 19:46:59 +02:00
if (err) {
return done(err);
}
if (stat && stat.isDirectory()) {
utils.walk(file, function (err, res) {
2016-08-16 19:46:59 +02:00
if (err) {
return done(err);
}
results = results.concat(res);
pending -= 1;
if (!pending) {
done(null, results);
}
});
} else {
2014-03-04 17:08:15 -05:00
results.push(file);
pending -= 1;
if (!pending) {
done(null, results);
}
2013-06-20 16:48:17 -04:00
}
});
});
});
},
invalidUnicodeChars: XRegExp('[^\\p{L}\\s\\d\\-_]', 'g'),
invalidLatinChars: /[^\w\s\d\-_]/g,
trimRegex: /^\s+|\s+$/g,
collapseWhitespace: /\s+/g,
collapseDash: /-+/g,
trimTrailingDash: /-$/g,
trimLeadingDash: /^-/g,
2015-03-12 13:39:04 -04:00
isLatin: /^[\w\d\s.,\-@]+$/,
languageKeyRegex: /\[\[[\w]+:.+\]\]/,
2014-01-21 12:01:09 -05:00
2017-02-18 01:25:46 -07:00
// http://dense13.com/blog/2009/05/03/converting-string-to-slug-javascript/
slugify: function (str, preserveCase) {
2015-03-12 13:26:29 -04:00
if (!str) {
return '';
}
2014-01-21 12:01:09 -05:00
str = str.replace(utils.trimRegex, '');
2014-01-21 12:02:46 -05:00
if(utils.isLatin.test(str)) {
str = str.replace(utils.invalidLatinChars, '-');
} else {
str = XRegExp.replace(str, utils.invalidUnicodeChars, '-');
}
2015-03-03 12:34:41 -05:00
str = !preserveCase ? str.toLocaleLowerCase() : str;
2015-01-19 10:39:44 -05:00
str = str.replace(utils.collapseWhitespace, '-');
2014-01-21 12:02:46 -05:00
str = str.replace(utils.collapseDash, '-');
2014-01-21 12:01:09 -05:00
str = str.replace(utils.trimTrailingDash, '');
2014-01-26 22:36:06 -05:00
str = str.replace(utils.trimLeadingDash, '');
return str;
},
2016-03-22 18:22:42 +02:00
cleanUpTag: function (tag, maxLength) {
2016-12-09 18:53:08 +03:00
if (typeof tag !== 'string' || !tag.length) {
2016-03-22 18:22:42 +02:00
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();
var matches = tag.match(/^[.-]*(.+?)[.-]*$/);
if (matches && matches.length > 1) {
tag = matches[1];
}
return tag;
},
removePunctuation: function (str) {
2014-05-21 21:15:11 -04:00
return str.replace(/[\.,-\/#!$%\^&\*;:{}=\-_`<>'"~()?]/g, '');
},
2014-03-17 21:47:37 -04:00
isEmailValid: function (email) {
2014-03-17 21:47:37 -04:00
return typeof email === 'string' && email.length && email.indexOf('@') !== -1;
},
isUserNameValid: function (name) {
2016-10-21 15:31:27 -04:00
return (name && name !== '' && (/^['"\s\-\+.*0-9\u00BF-\u1FFF\u2C00-\uD7FF\w]+$/.test(name)));
},
isPasswordValid: function (password) {
2014-08-26 15:05:42 -04:00
return typeof password === 'string' && password.length;
},
isNumber: function (n) {
return !isNaN(parseFloat(n)) && isFinite(n);
},
hasLanguageKey: function (input) {
return utils.languageKeyRegex.test(input);
},
// shallow objects merge
merge: function () {
2017-02-17 20:20:42 -07:00
var result = {};
var obj;
var keys;
for (var i = 0; i < arguments.length; i += 1) {
obj = arguments[i] || {};
keys = Object.keys(obj);
for (var j = 0; j < keys.length; j += 1) {
result[keys[j]] = obj[keys[j]];
}
}
return result;
},
2013-08-11 16:41:49 -04:00
fileExtension: function (path) {
return ('' + path).split('.').pop();
},
2015-02-18 21:09:33 -05:00
extensionMimeTypeMap: {
2017-02-18 01:19:20 -07:00
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",
2015-02-18 21:09:33 -05:00
},
fileMimeType: function (path) {
return utils.extensionToMimeType(utils.fileExtension(path));
2015-02-18 21:09:33 -05:00
},
extensionToMimeType: function (extension) {
2015-02-18 21:09:33 -05:00
return utils.extensionMimeTypeMap[extension] || '*';
},
isRelativeUrl: function (url) {
2013-09-17 13:04:40 -04:00
var firstChar = url.slice(0, 1);
return (firstChar === '.' || firstChar === '/');
},
makeNumbersHumanReadable: function (elements) {
elements.each(function () {
2014-03-31 14:43:44 -04:00
$(this).html(utils.makeNumberHumanReadable($(this).attr('title')));
});
},
makeNumberHumanReadable: function (num) {
2013-11-29 14:12:19 -05:00
var n = parseInt(num, 10);
if(!n) {
return num;
}
2013-11-29 14:12:19 -05:00
if (n > 999999) {
return (n / 1000000).toFixed(1) + 'm';
2017-02-17 22:11:35 -07:00
} else if(n > 999) {
2013-11-29 14:12:19 -05:00
return (n / 1000).toFixed(1) + 'k';
}
return n;
},
2014-03-31 14:43:44 -04:00
addCommasToNumbers: function (elements) {
elements.each(function (index, element) {
$(element).html(utils.addCommas($(element).html()));
});
},
// takes a string like 1000 and returns 1,000
addCommas: function (text) {
return text.replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,");
},
toISOString: function (timestamp) {
if (!timestamp || !Date.prototype.toISOString) {
return '';
}
return Date.prototype.toISOString ? new Date(parseInt(timestamp, 10)).toISOString() : timestamp;
2014-02-07 15:00:53 -05:00
},
2017-02-18 01:27:46 -07:00
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', 'var', 'video', 'wbr'],
2017-02-18 01:27:46 -07:00
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', 'var', 'video', 'wbr'],
escapeRegexChars: function (text) {
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
2014-04-08 17:35:12 -04:00
},
escapeHTML: function (raw) {
return raw.replace(/&/gm,"&amp;").replace(/</gm,"&lt;").replace(/>/gm,"&gt;");
},
isAndroidBrowser: function () {
2014-04-08 17:35:12 -04:00
// http://stackoverflow.com/questions/9286355/how-to-detect-only-the-native-android-browser
var 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 () {
2017-02-18 01:25:46 -07:00
// http://stackoverflow.com/questions/14441456/how-to-detect-which-device-view-youre-on-using-twitter-bootstrap-api
2017-02-17 20:20:42 -07:00
var envs = ['xs', 'sm', 'md', 'lg'];
var $el = $('<div>');
$el.appendTo($('body'));
for (var i = envs.length - 1; i >= 0; i -= 1) {
var env = envs[i];
2016-10-13 11:42:29 +02:00
$el.addClass('hidden-' + env);
if ($el.is(':hidden')) {
$el.remove();
return env;
}
}
},
isMobile: function () {
var env = utils.findBootstrapEnvironment();
return ['xs', 'sm'].some(function (targetEnv) {
return targetEnv === env;
});
},
getHoursArray: function () {
2017-02-17 20:20:42 -07:00
var currentHour = new Date().getHours();
var labels = [];
for (var i = currentHour, ii = currentHour - 24; i > ii; i -= 1) {
var hour = i < 0 ? 24 + i : i;
labels.push(hour + ':00');
}
return labels.reverse();
},
getDaysArray: function (from) {
2017-02-17 20:20:42 -07:00
var currentDay = new Date(from || Date.now()).getTime();
var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
var labels = [];
var tmpDate;
for(var x = 29; x >= 0; x -= 1) {
2016-10-13 11:42:29 +02:00
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) {
2017-02-18 01:25:46 -07:00
// special bonus for those using jQuery
if (typeof jQuery === "function" && el instanceof jQuery) {
el = el[0];
}
var rect = el.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
2017-02-18 01:25:46 -07:00
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) {
2017-02-17 20:20:42 -07:00
var a;
var hash = {};
var params;
options = options || {};
options.skipToType = options.skipToType || {};
if (options.url) {
a = utils.urlToLocation(options.url);
}
params = (a ? a.search : window.location.search).substring(1).split("&");
params.forEach(function (param) {
2017-02-17 20:20:42 -07:00
var val = param.split('=');
var key = decodeURI(val[0]);
var value = options.skipToType[key] ? decodeURI(val[1]) : utils.toType(decodeURI(val[1]));
2015-01-19 10:39:44 -05:00
if (key) {
if (key.substr(-2, 2) === '[]') {
key = key.slice(0, -2);
}
2015-02-05 14:55:36 -05:00
if (!hash[key]) {
hash[key] = value;
2015-03-12 13:26:29 -04:00
} else {
2015-02-05 14:55:36 -05:00
if (!$.isArray(hash[key])) {
hash[key] = [hash[key]];
}
hash[key].push(value);
}
2015-01-19 10:39:44 -05:00
}
});
return hash;
},
param: function (key) {
return this.params()[key];
},
urlToLocation: function (url) {
var 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) {
var type = typeof str;
if (type !== 'string') {
return str;
} else {
var nb = parseFloat(str);
2015-01-19 10:39:44 -05:00
if (!isNaN(nb) && isFinite(str)) {
return nb;
2015-01-19 10:39:44 -05:00
}
if (str === 'false') {
return false;
2015-01-19 10:39:44 -05:00
}
if (str === 'true') {
return true;
2015-01-19 10:39:44 -05:00
}
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) {
2015-01-19 10:39:44 -05:00
if(obj === undefined) {
obj = window;
2015-01-19 10:39:44 -05:00
}
if(props == null) {
return undefined;
2015-01-19 10:39:44 -05:00
}
var i = props.indexOf('.');
2017-02-18 01:21:34 -07:00
if(i == -1) {
2015-01-19 10:39:44 -05:00
if(value !== undefined) {
obj[props] = value;
2015-01-19 10:39:44 -05:00
}
return obj[props];
}
2017-02-17 20:20:42 -07:00
var prop = props.slice(0, i);
var newProps = props.slice(i + 1);
2017-02-18 01:21:34 -07:00
if(props !== undefined && !(obj[prop] instanceof Object)) {
obj[prop] = {};
2015-01-19 10:39:44 -05:00
}
2014-05-24 10:21:20 -04:00
return utils.props(obj[prop], newProps, value);
2016-08-22 16:24:28 -04:00
},
isInternalURI: function (targetLocation, referenceLocation, relative_path) {
2016-08-22 16:24:28 -04:00
return targetLocation.host === '' || // Relative paths are always internal links
(
targetLocation.host === referenceLocation.host && targetLocation.protocol === referenceLocation.protocol && // Otherwise need to check if protocol and host match
(relative_path.length > 0 ? targetLocation.pathname.indexOf(relative_path) === 0 : true) // Subfolder installs need this additional check
);
2017-02-17 19:31:21 -07:00
},
};
2017-02-17 22:31:05 -07:00
if (typeof String.prototype.startsWith !== 'function') {
String.prototype.startsWith = function (prefix) {
2015-01-19 10:39:44 -05:00
if (this.length < prefix.length) {
2014-09-06 23:57:51 -04:00
return false;
2015-01-19 10:39:44 -05:00
}
2017-02-17 22:31:05 -07:00
return this.slice(0, prefix.length) === prefix;
2014-09-06 23:57:51 -04:00
};
2015-03-20 19:42:59 -04:00
}
2015-03-20 19:36:18 -04:00
2017-02-17 22:31:05 -07:00
if (typeof String.prototype.endsWith !== 'function') {
String.prototype.endsWith = function (suffix) {
2015-03-20 19:36:18 -04:00
if (this.length < suffix.length) {
return false;
}
2017-02-17 22:31:05 -07:00
if (suffix.length === 0) {
return true;
2015-03-20 19:36:18 -04:00
}
2017-02-17 22:31:05 -07:00
return this.slice(-suffix.length) === suffix;
2015-03-20 19:36:18 -04:00
};
2014-09-06 23:57:51 -04:00
}
2017-02-17 22:31:05 -07:00
if (typeof String.prototype.rtrim !== 'function') {
String.prototype.rtrim = function () {
2015-10-14 16:00:45 -04:00
return this.replace(/\s+$/g, '');
};
}
if ('undefined' !== typeof window) {
window.utils = module.exports;
}
2016-10-13 11:40:10 +02:00
}('undefined' === typeof module ? {
2013-09-17 13:04:40 -04:00
module: {
2017-02-17 19:31:21 -07:00
exports: {},
},
2016-10-13 11:40:10 +02:00
} : module));