/* Utilities */ import Telescope from './config.js'; import marked from 'marked'; import urlObject from 'url'; import moment from 'moment'; import sanitizeHtml from 'sanitize-html'; import getSlug from 'speakingurl'; /** * @summary The global namespace for Telescope utils. * @namespace Telescope.utils */ Telescope.utils = {}; /** * @summary Convert a camelCase string to dash-separated string * @param {String} str */ Telescope.utils.camelToDash = function (str) { return str.replace(/\W+/g, '-').replace(/([a-z\d])([A-Z])/g, '$1-$2').toLowerCase(); }; /** * @summary Convert a camelCase string to a space-separated capitalized string * See http://stackoverflow.com/questions/4149276/javascript-camelcase-to-regular-form * @param {String} str */ Telescope.utils.camelToSpaces = function (str) { return str.replace(/([A-Z])/g, ' $1').replace(/^./, function(str){ return str.toUpperCase(); }); }; /** * @summary Convert an underscore-separated string to dash-separated string * @param {String} str */ Telescope.utils.underscoreToDash = function (str) { return str.replace('_', '-'); }; /** * @summary Convert a dash separated string to camelCase. * @param {String} str */ Telescope.utils.dashToCamel = function (str) { return str.replace(/(\-[a-z])/g, function($1){return $1.toUpperCase().replace('-','');}); }; /** * @summary Convert a string to camelCase and remove spaces. * @param {String} str */ Telescope.utils.camelCaseify = function(str) { str = this.dashToCamel(str.replace(' ', '-')); str = str.slice(0,1).toLowerCase() + str.slice(1); return str; }; /** * @summary Trim a sentence to a specified amount of words and append an ellipsis. * @param {String} s - Sentence to trim. * @param {Number} numWords - Number of words to trim sentence to. */ Telescope.utils.trimWords = function(s, numWords) { if (!s) return s; var expString = s.split(/\s+/,numWords); if(expString.length >= numWords) return expString.join(" ")+"…"; return s; }; /** * @summary Trim a block of HTML code to get a clean text excerpt * @param {String} html - HTML to trim. */ Telescope.utils.trimHTML = function (html, numWords) { var text = Telescope.utils.stripHTML(html); return Telescope.utils.trimWords(text, numWords); }; /** * @summary Capitalize a string. * @param {String} str */ Telescope.utils.capitalize = function(str) { return str.charAt(0).toUpperCase() + str.slice(1); }; Telescope.utils.t = function(message) { var d = new Date(); console.log("### "+message+" rendered at "+d.getHours()+":"+d.getMinutes()+":"+d.getSeconds()); }; Telescope.utils.nl2br = function(str) { var breakTag = '
'; return (str + '').replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1'+ breakTag +'$2'); }; Telescope.utils.scrollPageTo = function(selector) { $('body').scrollTop($(selector).offset().top); }; Telescope.utils.getDateRange = function(pageNumber) { var now = moment(new Date()); var dayToDisplay=now.subtract(pageNumber-1, 'days'); var range={}; range.start = dayToDisplay.startOf('day').valueOf(); range.end = dayToDisplay.endOf('day').valueOf(); // console.log("after: ", dayToDisplay.startOf('day').format("dddd, MMMM Do YYYY, h:mm:ss a")); // console.log("before: ", dayToDisplay.endOf('day').format("dddd, MMMM Do YYYY, h:mm:ss a")); return range; }; ////////////////////////// // URL Helper Functions // ////////////////////////// /** * @summary Returns the user defined site URL or Meteor.absoluteUrl */ Telescope.utils.getSiteUrl = function () { return Telescope.settings.get('siteUrl', Meteor.absoluteUrl()); }; /** * @summary The global namespace for Telescope utils. * @param {String} url - the URL to redirect */ Telescope.utils.getOutgoingUrl = function (url) { return Telescope.utils.getSiteUrl() + "out?url=" + encodeURIComponent(url); }; Telescope.utils.slugify = function (s) { var slug = getSlug(s, { truncate: 60 }); // can't have posts with an "edit" slug if (slug === "edit") { slug = "edit-1"; } return slug; }; Telescope.utils.getUnusedSlug = function (collection, slug) { let suffix = ""; let index = 0; // handle edge case for Users collection const field = collection._name === 'users' ? '__slug' : 'slug'; // test if slug is already in use while (!!collection.findOne({[field]: slug+suffix})) { index++; suffix = "-"+index; } return slug+suffix; }; Telescope.utils.getShortUrl = function(post) { return post.shortUrl || post.url; }; Telescope.utils.getDomain = function(url) { try { return urlObject.parse(url).hostname.replace('www.', ''); } catch (error) { return null; } }; Telescope.utils.invitesEnabled = function() { return Telescope.settings.get("requireViewInvite") || Telescope.settings.get("requirePostInvite"); }; // add http: if missing Telescope.utils.addHttp = function (url) { try { if (url.substring(0, 5) !== "http:" && url.substring(0, 6) !== "https:") { url = "http:"+url; } return url; } catch (error) { return null; } }; ///////////////////////////// // String Helper Functions // ///////////////////////////// Telescope.utils.cleanUp = function(s) { return this.stripHTML(s); }; Telescope.utils.sanitize = function(s) { // console.log('// before sanitization:') // console.log(s) if(Meteor.isServer){ s = sanitizeHtml(s, { allowedTags: [ 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol', 'nl', 'li', 'b', 'i', 'strong', 'em', 'strike', 'code', 'hr', 'br', 'div', 'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre', 'img' ] }); // console.log('// after sanitization:') // console.log(s) } return s; }; Telescope.utils.stripHTML = function(s) { return s.replace(/<(?:.|\n)*?>/gm, ''); }; Telescope.utils.stripMarkdown = function(s) { var htmlBody = marked(s); return Telescope.utils.stripHTML(htmlBody); }; // http://stackoverflow.com/questions/2631001/javascript-test-for-existence-of-nested-object-key Telescope.utils.checkNested = function(obj /*, level1, level2, ... levelN*/) { var args = Array.prototype.slice.call(arguments); obj = args.shift(); for (var i = 0; i < args.length; i++) { if (!obj.hasOwnProperty(args[i])) { return false; } obj = obj[args[i]]; } return true; }; Telescope.log = function (s) { if(Telescope.settings.get('debug', false) || process.env.NODE_ENV === "development") { console.log(s); } }; // see http://stackoverflow.com/questions/8051975/access-object-child-properties-using-a-dot-notation-string Telescope.getNestedProperty = function (obj, desc) { var arr = desc.split("."); while(arr.length && (obj = obj[arr.shift()])); return obj; }; // see http://stackoverflow.com/a/14058408/649299 _.mixin({ compactObject : function(object) { var clone = _.clone(object); _.each(clone, function(value, key) { if(!value && typeof value !== "boolean") { delete clone[key]; } }); return clone; } }); // adapted from http://stackoverflow.com/a/22072374/649299 Telescope.utils.unflatten = function( array, idProperty, parentIdProperty, parent, tree ){ tree = typeof tree !== "undefined" ? tree : []; let children = []; if (typeof parent === "undefined") { // if there is no parent, we're at the root level // so we return all root nodes (i.e. nodes with no parent) children = _.filter( array, node => !node[parentIdProperty]); } else { // if there *is* a parent, we return all its child nodes // (i.e. nodes whose parentId is equal to the parent's id.) children = _.filter( array, node => node[parentIdProperty] === parent[idProperty]); } // if we found children, we keep on iterating if (!!children.length) { if (typeof parent === "undefined") { // if we're at the root, then the tree consist of all root nodes tree = children; } else { // else, we add the children to the parent as the "childrenResults" property parent.childrenResults = children; } // we call the function on each child children.forEach(child => { Telescope.utils.unflatten(array, idProperty, parentIdProperty, child); }); } return tree; } Telescope.utils.getFieldLabel = (fieldName, collection) => { const label = collection.simpleSchema()._schema[fieldName].label; const nameWithSpaces = Telescope.utils.camelToSpaces(fieldName.replace("__", "")); return label || nameWithSpaces; } Telescope.utils.getLogoUrl = () => { const logoUrl = Telescope.settings.get("logoUrl"); if (!!logoUrl) { const prefix = Telescope.utils.getSiteUrl().slice(0,-1); // the logo may be hosted on another website return logoUrl.indexOf('://') > -1 ? logoUrl : prefix + logoUrl; } }; // note(apollo): get collection's name from __typename given by react-apollo Telescope.utils.getCollectionNameFromTypename = (type) => { if (type.indexOf('Post') > -1) { return 'posts'; } else if (type.indexOf('Cat') > -1) { return 'categories'; } else if (type.indexOf('User') > -1) { return 'users'; } else if (type.indexOf('Comment') > -1) { return 'comments'; } }; Telescope.utils.findIndex = (array, predicate) => { let index = -1; let continueLoop = true; array.forEach((item, currentIndex) => { if (continueLoop && predicate(item)) { index = currentIndex continueLoop = false } }); return index; } // adapted from http://stackoverflow.com/a/22072374/649299 Telescope.utils.unflatten = function( array, idProperty, parentIdProperty, parent, tree ){ tree = typeof tree !== "undefined" ? tree : []; let children = []; if (typeof parent === "undefined") { // if there is no parent, we're at the root level // so we return all root nodes (i.e. nodes with no parent) children = _.filter( array, node => !node[parentIdProperty]); } else { // if there *is* a parent, we return all its child nodes // (i.e. nodes whose parentId is equal to the parent's id.) children = _.filter( array, node => node[parentIdProperty] === parent[idProperty]); } // if we found children, we keep on iterating if (!!children.length) { if (typeof parent === "undefined") { // if we're at the root, then the tree consist of all root nodes tree = children; } else { // else, we add the children to the parent as the "childrenResults" property parent.childrenResults = children; } // we call the function on each child children.forEach(child => { Telescope.utils.unflatten(array, idProperty, parentIdProperty, child); }); } return tree; }; // remove the telescope object from a schema and duplicate it at the root Telescope.utils.stripTelescopeNamespace = (schema) => { // grab the users schema keys const schemaKeys = Object.keys(schema); // remove any field beginning by telescope: .telescope, .telescope.upvotedPosts.$, ... const filteredSchemaKeys = schemaKeys.filter(key => key.slice(0,9) !== 'telescope'); // replace the previous schema by an object based on this filteredSchemaKeys return filteredSchemaKeys.reduce((sch, key) => ({...sch, [key]: schema[key]}), {}); } /** * Convert an array of field names into a Mongo fields specifier * @param {Array} fieldsArray */ Telescope.utils.arrayToFields = (fieldsArray) => { return _.object(fieldsArray, _.map(fieldsArray, function () {return true})); } /** * Get the display name of a React component * @param {React Component} WrappedComponent */ Telescope.utils.getComponentDisplayName = (WrappedComponent) => { return WrappedComponent.displayName || WrappedComponent.name || 'Component'; };