2016-11-26 11:17:01 +09:00
/ *
Utilities
* /
2016-06-07 15:03:00 +09:00
import marked from 'marked' ;
2016-04-23 18:56:08 +09:00
import urlObject from 'url' ;
2016-06-07 15:03:00 +09:00
import moment from 'moment' ;
import sanitizeHtml from 'sanitize-html' ;
import getSlug from 'speakingurl' ;
2016-12-12 15:00:56 +09:00
import { getSetting } from './settings.js' ;
2016-02-27 16:41:50 +09:00
2015-04-22 07:50:11 +09:00
/ * *
2016-04-09 09:41:20 +09:00
* @ summary The global namespace for Telescope utils .
2015-04-22 07:50:11 +09:00
* @ namespace Telescope . utils
* /
2016-12-12 11:34:28 +09:00
export const Utils = { } ;
2015-04-22 07:50:11 +09:00
/ * *
2016-04-09 09:41:20 +09:00
* @ summary Convert a camelCase string to dash - separated string
2015-04-22 07:50:11 +09:00
* @ param { String } str
* /
2016-12-12 11:34:28 +09:00
Utils . camelToDash = function ( str ) {
2015-04-22 07:50:11 +09:00
return str . replace ( /\W+/g , '-' ) . replace ( /([a-z\d])([A-Z])/g , '$1-$2' ) . toLowerCase ( ) ;
2015-05-01 18:22:00 +02:00
} ;
2015-04-22 07:50:11 +09:00
2016-03-29 17:28:14 +09:00
/ * *
2016-04-09 09:41:20 +09:00
* @ summary Convert a camelCase string to a space - separated capitalized string
2016-03-29 17:28:14 +09:00
* See http : //stackoverflow.com/questions/4149276/javascript-camelcase-to-regular-form
* @ param { String } str
* /
2016-12-12 11:34:28 +09:00
Utils . camelToSpaces = function ( str ) {
2016-03-29 17:28:14 +09:00
return str . replace ( /([A-Z])/g , ' $1' ) . replace ( /^./ , function ( str ) { return str . toUpperCase ( ) ; } ) ;
} ;
2015-05-18 10:30:08 +09:00
/ * *
2016-04-09 09:41:20 +09:00
* @ summary Convert an underscore - separated string to dash - separated string
2015-05-18 10:30:08 +09:00
* @ param { String } str
* /
2016-12-12 11:34:28 +09:00
Utils . underscoreToDash = function ( str ) {
2015-05-18 10:30:08 +09:00
return str . replace ( '_' , '-' ) ;
} ;
2015-04-22 07:50:11 +09:00
/ * *
2016-04-09 09:41:20 +09:00
* @ summary Convert a dash separated string to camelCase .
2015-04-22 07:50:11 +09:00
* @ param { String } str
* /
2016-12-12 11:34:28 +09:00
Utils . dashToCamel = function ( str ) {
2015-04-22 07:50:11 +09:00
return str . replace ( /(\-[a-z])/g , function ( $1 ) { return $1 . toUpperCase ( ) . replace ( '-' , '' ) ; } ) ;
2015-05-01 18:22:00 +02:00
} ;
2015-04-22 07:50:11 +09:00
/ * *
2016-04-09 09:41:20 +09:00
* @ summary Convert a string to camelCase and remove spaces .
2015-04-22 07:50:11 +09:00
* @ param { String } str
* /
2016-12-12 11:34:28 +09:00
Utils . camelCaseify = function ( str ) {
2015-08-24 11:40:00 +09:00
str = this . dashToCamel ( str . replace ( ' ' , '-' ) ) ;
str = str . slice ( 0 , 1 ) . toLowerCase ( ) + str . slice ( 1 ) ;
return str ;
2015-05-01 18:22:00 +02:00
} ;
2015-04-22 07:50:11 +09:00
/ * *
2016-04-09 09:41:20 +09:00
* @ summary Trim a sentence to a specified amount of words and append an ellipsis .
2015-04-22 07:50:11 +09:00
* @ param { String } s - Sentence to trim .
* @ param { Number } numWords - Number of words to trim sentence to .
* /
2016-12-12 11:34:28 +09:00
Utils . trimWords = function ( s , numWords ) {
2016-11-26 02:46:55 +08:00
2015-07-14 12:01:32 +09:00
if ( ! s )
return s ;
2015-05-01 18:22:00 +02:00
var expString = s . split ( /\s+/ , numWords ) ;
2015-04-22 07:50:11 +09:00
if ( expString . length >= numWords )
return expString . join ( " " ) + "…" ;
return s ;
} ;
2015-08-15 12:28:22 +09:00
/ * *
2016-04-09 09:41:20 +09:00
* @ summary Trim a block of HTML code to get a clean text excerpt
2015-08-15 12:28:22 +09:00
* @ param { String } html - HTML to trim .
* /
2016-12-12 11:34:28 +09:00
Utils . trimHTML = function ( html , numWords ) {
var text = Utils . stripHTML ( html ) ;
return Utils . trimWords ( text , numWords ) ;
2015-08-15 12:28:22 +09:00
} ;
2015-04-22 07:50:11 +09:00
/ * *
2016-04-09 09:41:20 +09:00
* @ summary Capitalize a string .
2015-04-22 07:50:11 +09:00
* @ param { String } str
* /
2016-12-12 11:34:28 +09:00
Utils . capitalize = function ( str ) {
2015-04-22 07:50:11 +09:00
return str . charAt ( 0 ) . toUpperCase ( ) + str . slice ( 1 ) ;
2015-05-01 18:22:00 +02:00
} ;
2015-04-22 07:50:11 +09:00
2016-12-12 11:34:28 +09:00
Utils . t = function ( message ) {
2015-04-22 07:50:11 +09:00
var d = new Date ( ) ;
2016-11-26 02:46:55 +08:00
console . log ( "### " + message + " rendered at " + d . getHours ( ) + ":" + d . getMinutes ( ) + ":" + d . getSeconds ( ) ) ; // eslint-disable-line
2015-04-22 07:50:11 +09:00
} ;
2016-12-12 11:34:28 +09:00
Utils . nl2br = function ( str ) {
2015-04-22 07:50:11 +09:00
var breakTag = '<br />' ;
return ( str + '' ) . replace ( /([^>\r\n]?)(\r\n|\n\r|\r|\n)/g , '$1' + breakTag + '$2' ) ;
} ;
2016-12-12 11:34:28 +09:00
Utils . scrollPageTo = function ( selector ) {
2015-04-22 07:50:11 +09:00
$ ( 'body' ) . scrollTop ( $ ( selector ) . offset ( ) . top ) ;
} ;
2016-12-12 11:34:28 +09:00
Utils . getDateRange = function ( pageNumber ) {
2015-04-22 07:50:11 +09:00
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 //
//////////////////////////
2015-06-19 11:52:57 +09:00
/ * *
2016-04-09 09:41:20 +09:00
* @ summary Returns the user defined site URL or Meteor . absoluteUrl
2015-06-19 11:52:57 +09:00
* /
2016-12-12 11:34:28 +09:00
Utils . getSiteUrl = function ( ) {
2016-12-12 15:00:56 +09:00
return getSetting ( 'siteUrl' , Meteor . absoluteUrl ( ) ) ;
2015-06-19 11:52:57 +09:00
} ;
/ * *
2016-04-09 09:41:20 +09:00
* @ summary The global namespace for Telescope utils .
2015-06-19 11:52:57 +09:00
* @ param { String } url - the URL to redirect
* /
2016-12-12 11:34:28 +09:00
Utils . getOutgoingUrl = function ( url ) {
return Utils . getSiteUrl ( ) + "out?url=" + encodeURIComponent ( url ) ;
2015-06-19 11:52:57 +09:00
} ;
2016-12-12 11:34:28 +09:00
Utils . slugify = function ( s ) {
2015-06-25 12:03:10 +09:00
var slug = getSlug ( s , {
2015-06-18 13:04:38 +09:00
truncate : 60
} ) ;
2015-06-25 12:03:10 +09:00
// can't have posts with an "edit" slug
if ( slug === "edit" ) {
slug = "edit-1" ;
}
return slug ;
2015-04-22 07:50:11 +09:00
} ;
2016-12-12 11:34:28 +09:00
Utils . getUnusedSlug = function ( collection , slug ) {
2016-10-05 08:05:11 +02:00
let suffix = "" ;
let index = 0 ;
2016-11-26 02:46:55 +08:00
2015-08-20 10:30:34 +09:00
// test if slug is already in use
2017-01-18 10:18:33 +09:00
while ( ! ! collection . findOne ( { slug : slug + suffix } ) ) {
2015-08-20 10:30:34 +09:00
index ++ ;
suffix = "-" + index ;
}
return slug + suffix ;
} ;
2016-12-12 11:34:28 +09:00
Utils . getShortUrl = function ( post ) {
2015-04-22 07:50:11 +09:00
return post . shortUrl || post . url ;
} ;
2016-12-12 11:34:28 +09:00
Utils . getDomain = function ( url ) {
2016-07-06 10:21:58 +09:00
try {
return urlObject . parse ( url ) . hostname . replace ( 'www.' , '' ) ;
} catch ( error ) {
return null ;
}
2015-04-22 07:50:11 +09:00
} ;
2016-12-12 11:34:28 +09:00
Utils . invitesEnabled = function ( ) {
2016-12-12 15:00:56 +09:00
return getSetting ( "requireViewInvite" ) || getSetting ( "requirePostInvite" ) ;
2015-04-22 07:50:11 +09:00
} ;
2015-07-27 15:15:38 +09:00
// add http: if missing
2016-12-12 11:34:28 +09:00
Utils . addHttp = function ( url ) {
2016-07-12 17:36:58 +09:00
try {
if ( url . substring ( 0 , 5 ) !== "http:" && url . substring ( 0 , 6 ) !== "https:" ) {
url = "http:" + url ;
}
return url ;
} catch ( error ) {
return null ;
2015-07-27 15:15:38 +09:00
}
} ;
2015-04-22 07:50:11 +09:00
/////////////////////////////
// String Helper Functions //
/////////////////////////////
2016-12-12 11:34:28 +09:00
Utils . cleanUp = function ( s ) {
2015-04-22 07:50:11 +09:00
return this . stripHTML ( s ) ;
} ;
2016-12-12 11:34:28 +09:00
Utils . sanitize = function ( s ) {
2015-04-22 07:50:11 +09:00
// console.log('// before sanitization:')
// console.log(s)
if ( Meteor . isServer ) {
2015-05-01 18:22:00 +02:00
s = sanitizeHtml ( s , {
2015-04-22 07:50:11 +09:00
allowedTags : [
2017-04-04 10:22:07 +09:00
'h1' , 'h2' , 'h3' , 'h4' , 'h5' , 'h6' , 'blockquote' , 'p' , 'a' , 'ul' ,
2015-04-22 07:50:11 +09:00
'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 ;
} ;
2016-12-12 11:34:28 +09:00
Utils . stripHTML = function ( s ) {
2015-04-22 07:50:11 +09:00
return s . replace ( /<(?:.|\n)*?>/gm , '' ) ;
} ;
2016-12-12 11:34:28 +09:00
Utils . stripMarkdown = function ( s ) {
2015-08-15 12:28:22 +09:00
var htmlBody = marked ( s ) ;
2016-12-12 11:34:28 +09:00
return Utils . stripHTML ( htmlBody ) ;
2015-04-22 07:50:11 +09:00
} ;
// http://stackoverflow.com/questions/2631001/javascript-test-for-existence-of-nested-object-key
2016-12-12 11:34:28 +09:00
Utils . checkNested = function ( obj /*, level1, level2, ... levelN*/ ) {
2015-05-01 18:22:00 +02:00
var args = Array . prototype . slice . call ( arguments ) ;
obj = args . shift ( ) ;
2015-04-22 07:50:11 +09:00
for ( var i = 0 ; i < args . length ; i ++ ) {
if ( ! obj . hasOwnProperty ( args [ i ] ) ) {
return false ;
}
obj = obj [ args [ i ] ] ;
}
return true ;
} ;
2015-04-22 11:49:42 +09:00
2016-12-12 11:34:28 +09:00
Utils . log = function ( s ) {
2016-12-12 15:00:56 +09:00
if ( getSetting ( 'debug' , false ) || process . env . NODE _ENV === "development" ) {
2016-11-26 02:46:55 +08:00
console . log ( s ) ; // eslint-disable-line
2016-01-07 18:46:17 +01:00
}
2015-05-01 18:22:00 +02:00
} ;
2015-05-06 12:56:59 +09:00
// see http://stackoverflow.com/questions/8051975/access-object-child-properties-using-a-dot-notation-string
2016-12-12 11:34:28 +09:00
Utils . getNestedProperty = function ( obj , desc ) {
2015-05-06 12:56:59 +09:00
var arr = desc . split ( "." ) ;
while ( arr . length && ( obj = obj [ arr . shift ( ) ] ) ) ;
return obj ;
2015-07-27 15:15:38 +09:00
} ;
2016-02-17 11:28:00 +09:00
2016-02-18 11:55:00 +09:00
// see http://stackoverflow.com/a/14058408/649299
_ . mixin ( {
2016-07-31 14:14:51 +02:00
compactObject : function ( object ) {
var clone = _ . clone ( object ) ;
_ . each ( clone , function ( value , key ) {
if ( ! value && typeof value !== "boolean" ) {
delete clone [ key ] ;
}
} ) ;
return clone ;
2016-02-18 11:55:00 +09:00
}
2016-02-18 17:53:04 +09:00
} ) ;
2016-04-01 12:38:50 +09:00
2016-12-12 11:34:28 +09:00
Utils . getFieldLabel = ( fieldName , collection ) => {
2016-04-01 20:57:37 +09:00
const label = collection . simpleSchema ( ) . _schema [ fieldName ] . label ;
2017-01-18 10:18:33 +09:00
const nameWithSpaces = Utils . camelToSpaces ( fieldName ) ;
2016-04-01 12:38:50 +09:00
return label || nameWithSpaces ;
2016-04-07 14:34:13 +02:00
}
2016-12-12 11:34:28 +09:00
Utils . getLogoUrl = ( ) => {
2016-12-12 15:00:56 +09:00
const logoUrl = getSetting ( "logoUrl" ) ;
2016-04-07 14:34:13 +02:00
if ( ! ! logoUrl ) {
2016-12-12 11:34:28 +09:00
const prefix = Utils . getSiteUrl ( ) . slice ( 0 , - 1 ) ;
2016-04-07 14:34:13 +02:00
// the logo may be hosted on another website
return logoUrl . indexOf ( '://' ) > - 1 ? logoUrl : prefix + logoUrl ;
}
2016-10-19 17:38:21 +02:00
} ;
// note(apollo): get collection's name from __typename given by react-apollo
2016-12-12 11:34:28 +09:00
Utils . getCollectionNameFromTypename = ( type ) => {
2016-10-19 17:38:21 +02:00
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' ;
}
2016-11-07 15:03:45 +09:00
} ;
2016-12-12 11:34:28 +09:00
Utils . findIndex = ( array , predicate ) => {
2016-11-07 15:03:45 +09:00
let index = - 1 ;
let continueLoop = true ;
array . forEach ( ( item , currentIndex ) => {
if ( continueLoop && predicate ( item ) ) {
index = currentIndex
continueLoop = false
}
} ) ;
return index ;
2016-11-08 14:56:39 +09:00
}
2016-11-09 11:55:28 +09:00
// adapted from http://stackoverflow.com/a/22072374/649299
2017-02-06 10:50:48 +09:00
Utils . unflatten = function ( array , idProperty , parentIdProperty , parent , level = 0 , tree ) {
level ++ ;
2016-11-09 11:55:28 +09:00
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)
2017-02-06 10:50:48 +09:00
children = _ . filter ( array , node => ! node [ parentIdProperty ] ) ;
2016-11-09 11:55:28 +09:00
} else {
// if there *is* a parent, we return all its child nodes
// (i.e. nodes whose parentId is equal to the parent's id.)
2017-02-06 10:50:48 +09:00
children = _ . filter ( array , node => node [ parentIdProperty ] === parent [ idProperty ] ) ;
2016-11-09 11:55:28 +09:00
}
// 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 => {
2017-02-06 10:50:48 +09:00
child . level = level ;
Utils . unflatten ( array , idProperty , parentIdProperty , child , level ) ;
2016-11-09 11:55:28 +09:00
} ) ;
}
return tree ;
2016-11-10 22:18:51 +01:00
} ;
// remove the telescope object from a schema and duplicate it at the root
2016-12-12 11:34:28 +09:00
Utils . stripTelescopeNamespace = ( schema ) => {
2016-11-10 22:18:51 +01:00
// 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' ) ;
2016-12-08 23:48:16 +01:00
2016-11-10 22:18:51 +01:00
// replace the previous schema by an object based on this filteredSchemaKeys
return filteredSchemaKeys . reduce ( ( sch , key ) => ( { ... sch , [ key ] : schema [ key ] } ) , { } ) ;
2016-11-15 15:59:34 +09:00
}
/ * *
* Convert an array of field names into a Mongo fields specifier
* @ param { Array } fieldsArray
* /
2016-12-12 11:34:28 +09:00
Utils . arrayToFields = ( fieldsArray ) => {
2016-11-15 15:59:34 +09:00
return _ . object ( fieldsArray , _ . map ( fieldsArray , function ( ) { return true } ) ) ;
2016-11-15 18:33:16 +01:00
}
/ * *
* Get the display name of a React component
* @ param { React Component } WrappedComponent
* /
2016-12-12 11:34:28 +09:00
Utils . getComponentDisplayName = ( WrappedComponent ) => {
2016-11-15 18:33:16 +01:00
return WrappedComponent . displayName || WrappedComponent . name || 'Component' ;
} ;
2017-01-14 17:57:44 +09:00
/ * *
* Take a collection and a list of documents , and convert all their date fields to date objects
* This is necessary because Apollo doesn ' t support custom scalars , and stores dates as strings
* @ param { Object } collection
* @ param { Array } list
* /
2017-01-14 18:04:53 +09:00
Utils . convertDates = ( collection , listOrDocument ) => {
// if undefined, just return
2017-02-12 22:00:13 +08:00
if ( ! listOrDocument || ! listOrDocument . length ) return listOrDocument ;
2017-01-14 17:57:44 +09:00
2017-01-14 18:04:53 +09:00
const list = Array . isArray ( listOrDocument ) ? listOrDocument : [ listOrDocument ] ;
2017-01-14 17:57:44 +09:00
const schema = collection . simpleSchema ( ) . _schema ;
const dateFields = _ . filter ( _ . keys ( schema ) , fieldName => schema [ fieldName ] . type === Date ) ;
const convertedList = list . map ( result => {
dateFields . forEach ( fieldName => {
if ( result [ fieldName ] && typeof result [ fieldName ] === 'string' ) {
result [ fieldName ] = new Date ( result [ fieldName ] ) ;
}
} ) ;
return result ;
} ) ;
2017-01-14 18:04:53 +09:00
return Array . isArray ( listOrDocument ) ? convertedList : convertedList [ 0 ] ;
2017-02-02 15:15:51 +01:00
}
Utils . encodeIntlError = error => typeof error !== "object" ? error : JSON . stringify ( error ) ;
2017-02-16 13:40:52 +01:00
Utils . decodeIntlError = ( error , options = { stripped : false } ) => {
2017-02-02 15:15:51 +01:00
try {
2017-02-16 13:40:52 +01:00
// do we get the error as a string or as an error object?
let strippedError = typeof error === 'string' ? error : error . message ;
2017-02-12 22:00:13 +08:00
2017-02-11 12:27:32 +01:00
// if the error hasn't been cleaned before (ex: it's not an error from a form)
if ( ! options . stripped ) {
2017-02-12 22:00:13 +08:00
// strip the "GraphQL Error: message [error_code]" given by Apollo if present
2017-02-11 12:27:32 +01:00
const graphqlPrefixIsPresent = strippedError . match ( /GraphQL error: (.*)/ ) ;
if ( graphqlPrefixIsPresent ) {
strippedError = graphqlPrefixIsPresent [ 1 ] ;
}
2017-02-12 22:00:13 +08:00
2017-02-11 12:27:32 +01:00
// strip the error code if present
const errorCodeIsPresent = strippedError . match ( /(.*)\[(.*)\]/ ) ;
if ( errorCodeIsPresent ) {
strippedError = errorCodeIsPresent [ 1 ] ;
}
}
2017-02-12 22:00:13 +08:00
2017-02-02 15:15:51 +01:00
// the error is an object internationalizable
2017-02-11 12:27:32 +01:00
const parsedError = JSON . parse ( strippedError ) ;
2017-02-12 22:00:13 +08:00
2017-02-02 15:15:51 +01:00
// check if the error has at least an 'id' expected by react-intl
if ( ! parsedError . id ) {
console . error ( '[Undecodable error]' , error ) ; // eslint-disable-line
return { id : 'app.something_bad_happened' , value : '[undecodable error]' }
2017-02-12 22:00:13 +08:00
}
2017-02-02 15:15:51 +01:00
// return the parsed error
2017-02-12 22:00:13 +08:00
return parsedError ;
} catch ( _ _ ) {
2017-02-02 15:15:51 +01:00
// the error is not internationalizable
return error ;
}
} ;
2017-02-12 22:00:13 +08:00
Utils . findWhere = ( array , criteria ) => array . find ( item => Object . keys ( criteria ) . every ( key => item [ key ] === criteria [ key ] ) ) ;
Utils . defineName = ( o , name ) => {
Object . defineProperty ( o , 'name' , { value : name } ) ;
return o ;
} ;
2017-03-18 15:59:31 +09:00
Utils . performCheck = ( mutation , user , document ) => {
if ( ! mutation . check ( user , document ) ) throw new Error ( Utils . encodeIntlError ( { id : ` app.mutation_not_allowed ` , value : ` " ${ mutation . name } " on _id " ${ document . _id } " ` } ) ) ;
}