'use strict' ; import { base64decode , base64encode } from '../platform/util.js' ; import { Xss } from '../platform/xss.js' ; export type Dict < T > = { [ key ] : T ; } ; export type UrlParam = string | number | null | undefined | boolean | string [ ] ; export type UrlParams = Dict < UrlParam > ; export type PromiseCancellation = { cancel : boolean ; } ; export class Str { public static readonly ltrChars = 'A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0370-\u0590\u0800-\u1FFF\u2C00-\uFB1C\uFDFE-\uFE6F\uFEFD-\uFFFF' ; public static readonly rtlChars = '\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC' ; public static parseEmail = ( full , flag = 'VALIDATE' ) => { let email ; let name ; if ( full . includes ( '<' ) && full . includes ( '>' ) ) { email = full . substr ( full . indexOf ( '<' ) + 1 , full . indexOf ( '>' ) - full . indexOf ( '<' ) - 1 ) . replace ( /["']/g , '' ) . trim ( ) . toLowerCase ( ) ; name = full . substr ( 0 , full . indexOf ( '<' ) ) . replace ( /["']/g , '' ) . trim ( ) ; } else { email = full . replace ( /["']/g , '' ) . trim ( ) . toLowerCase ( ) ; } if ( flag === 'VALIDATE' && ! Str . isEmailValid ( email ) ) { email = undefined ; } return { email , name , full } ; } ; public static getDomainFromEmailAddress = ( emailAddr ) => { return emailAddr . toLowerCase ( ) . split ( '@' ) [ 1 ] ; } ; public static rmSpecialCharsKeepUtf = ( str , mode ) : string => { str = str . replace ( /[@&#`();:'",<>\{\}\[\]\\\/\n\t\r]/gi , '' ) ; if ( mode === 'ALLOW-SOME' ) { return str ; } return str . replace ( /[.~!$%^*=?]/gi , '' ) ; } ; public static prettyPrint = ( obj ) => { return ( typeof obj === 'object' ) ? JSON . stringify ( obj , undefined , 2 ) . replace ( / /g , '&nbsp;' ) . replace ( /\n/g , '<br />' ) : String ( obj ) ; } ; public static normalizeSpaces = ( str ) => { return str . replace ( RegExp ( String . fromCharCode ( 160 ) , 'g' ) , String . fromCharCode ( 32 ) ) ; } ; public static normalizeDashes = ( str ) => { return str . replace ( /^—–|—–$/gm , '-----' ) ; } ; public static normalize = ( str ) => { return Str . normalizeSpaces ( Str . normalizeDashes ( str ) ) ; } ; public static spaced = ( longidOrFingerprint ) => { return longidOrFingerprint . replace ( /(.{4})/g , '$1 ' ) . trim ( ) ; } ; public static truncate = ( text , length ) : string => { return text . length <= length ? text : text . substring ( 0 , length ) + '...' ; } ; public static isEmailValid = ( email ) => { if ( email . indexOf ( ' ' ) !== - 1 ) { return false ; } email = email . replace ( /\:8001$/ , '' ) ; return /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i . test ( email ) ; } ; public static monthName = ( monthIndex ) => { return [ 'January' , 'February' , 'March' , 'April' , 'May' , 'June' , 'July' , 'August' , 'September' , 'October' , 'November' , 'December' ] [ monthIndex ] ; } ; public static sloppyRandom = ( length = 5 ) => { let id = '' ; const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' ; for ( let i = 0 ; i < length ; i ++ ) { id += possible . charAt ( Math . floor ( Math . random ( ) * possible . length ) ) ; } return id ; } ; public static splitExtended = ( str , regexp ) : string [ ] => { const result = [ ] ; while ( true ) { const match = regexp . exec ( str ) ; if ( match === null ) { break ; } result . push ( str . substring ( match . index ) ) ; } return result ; } ; public static splitAlphanumericExtended = ( str ) : string [ ] => { return Str . splitExtended ( str , /[a-z0-9]+/g ) ; } ; public static regexEscape = ( toBeUsedInRegex ) => { return toBeUsedInRegex . replace ( /[.*+?^${}()|[\]\\]/g , '\\$&' ) ; } ; public static escapeTextAsRenderableHtml = ( text ) => { const rtlRegexp = new RegExp ( `^([${ Str . rtlChars }].*)$` , 'gm' ) ; return Xss . escape ( text ) . replace ( rtlRegexp , '<div dir="rtl">$1</div>' ) . replace ( /\n/g , '<br>\n' ) . replace ( /^ +/gm , spaces => spaces . replace ( / /g , '&nbsp;' ) ) . replace ( /\n/g , '' ) ; } ; public static htmlAttrEncode = ( values ) : string => { return Str . base64urlUtfEncode ( JSON . stringify ( values ) ) ; } ; public static htmlAttrDecode = ( encoded ) : any => { try { return JSON . parse ( Str . base64urlUtfDecode ( encoded ) ) ; } catch ( e ) { return undefined ; } } ; public static capitalize = ( string ) : string => { return string . trim ( ) . split ( ' ' ) . map ( s => s . charAt ( 0 ) . toUpperCase ( ) + s . slice ( 1 ) ) . join ( ' ' ) ; } ; public static pluralize = ( count , noun , suffix = 's' ) : string => { return `${ count } ${ noun }${ count > 1 ? suffix : '' }` ; } ; public static toUtcTimestamp = ( datetimeStr , asStr = false ) => { return asStr ? String ( Date . parse ( datetimeStr ) ) : Date . parse ( datetimeStr ) ; } ; public static datetimeToDate = ( date ) => { return date . substr ( 0 , 10 ) . replace ( /"/g , '&quot;' ) . replace ( /'/g , '&#39;' ) . replace ( /</g , '&lt;' ) ; } ; public static fromDate = ( date ) => { return date . toISOString ( ) . replace ( /T/ , ' ' ) . replace ( /:[^:]+$/ , '' ) ; } ; public static mostlyRTL = ( string ) : boolean => { const rtlCount = string . match ( new RegExp ( '[' + Str . rtlChars + ']' , 'g' ) ) ?. length || 0 ; const lrtCount = string . match ( new RegExp ( '[' + Str . ltrChars + ']' , 'g' ) ) ?. length || 0 ; return rtlCount > lrtCount ; } ; private static base64urlUtfEncode = ( str ) => { if ( typeof str === 'undefined' ) { return str ; } return base64encode ( encodeURIComponent ( str ) . replace ( /%([0-9A-F]{2})/g , ( match , p1 ) => String . fromCharCode ( parseInt ( String ( p1 ) , 16 ) ) ) ) . replace ( /\+/g , '-' ) . replace ( /\//g , '_' ) . replace ( /=+$/ , '' ) ; } ; private static base64urlUtfDecode = ( str ) => { if ( typeof str === 'undefined' ) { return str ; } return decodeURIComponent ( Array . prototype . map . call ( base64decode ( str . replace ( /-/g , '+' ) . replace ( /_/g , '/' ) ) , ( c ) => { return '%' + ( '00' + c . charCodeAt ( 0 ) . toString ( 16 ) ) . slice ( - 2 ) ; } ) . join ( '' ) ) ; } ; } export class DateUtility { public static asNumber = ( date ) : number | null => { if ( typeof date === 'number' ) { return date ; } else if ( ! date ) { return null ; } else { return new Date ( date ) . getTime ( ) ; } } ; } export class Value { public static arr = { unique : < T > ( array ) : T [ ] => { const unique = [ ] ; for ( const v of array ) { if ( ! unique . includes ( v ) ) { unique . push ( v ) ; } } return unique ; } , withoutKey : < T > ( array , i ) => array . splice ( 0 , i ) . concat ( array . splice ( i + 1 , array . length ) ) , withoutVal : < T > ( array , withoutVal ) => { const result = [ ] ; for ( const value of array ) { if ( value !== withoutVal ) { result . push ( value ) ; } } return result ; } , contains : < T > ( arr , value ) : boolean => Boolean ( arr && typeof arr . indexOf === 'function' && ( arr as any [ ] ) . indexOf ( value ) !== - 1 ) , intersection : < T > ( array1 , array2 ) : T [ ] => array1 . filter ( value => array2 . includes ( value ) ) , hasIntersection : < T > ( array1 , array2 ) : boolean => array1 . some ( value => array2 . includes ( value ) ) , sum : ( arr ) => arr . reduce ( ( a , b ) => a + b , 0 ) , average : ( arr ) => Value . arr . sum ( arr ) / arr . length , zeroes : ( length ) : number [ ] => new Array ( length ) . map ( ( ) => 0 ) } ; public static obj = { keyByValue : < T > ( obj , v ) => { for ( const k of Object . keys ( obj ) ) { if ( obj [ k ] === v ) { return k ; } } return undefined ; } , } ; public static int = { lousyRandom : ( minVal , maxVal ) => minVal + Math . round ( Math . random ( ) * ( maxVal - minVal ) ) , getFutureTimestampInMonths : ( monthsToAdd ) => new Date ( ) . getTime ( ) + 1000 * 3600 * 24 * 30 * monthsToAdd , hoursAsMiliseconds : ( h ) => h * 1000 * 60 * 60 , } ; public static noop = ( ) : void => undefined ; } export class Url { private static URL_PARAM_DICT : Dict < boolean | null > = { '___cu_true___' : true , '___cu_false___' : false , '___cu_null___' : null } ; /**
     * will convert result to desired format: camelCase or snake_case, based on what was supplied in expectedKeys
     * todo - the camelCase or snake_case functionality can now be removed
     */ public static parse = ( expectedKeys , parseThisUrl ? ) => { const url = ( parseThisUrl || window . location . search . replace ( '?' , '' ) ) ; const valuePairs = url . split ( '?' ) . pop ( ) ! . split ( '&' ) ; const rawParams = { } ; const rawParamNameDict = { } ; for ( const valuePair of valuePairs ) { const pair = valuePair . split ( '=' ) ; rawParams [ pair [ 0 ] ] = pair [ 1 ] ; Url . fillPossibleUrlParamNameVariations ( pair [ 0 ] , rawParamNameDict ) ; } const processedParams = { } ; for ( const expectedKey of expectedKeys ) { processedParams [ expectedKey ] = Url . findAndProcessUrlParam ( expectedKey , rawParamNameDict , rawParams ) ; } return processedParams ; } ; public static create = ( link , params ) => { for ( const key of Object . keys ( params ) ) { const value = params [ key ] ; if ( typeof value !== 'undefined' ) { const transformed = Value . obj . keyByValue ( Url . URL_PARAM_DICT , value ) ; link += ( link . includes ( '?' ) ? '&' : '?' ) + encodeURIComponent ( key ) + '=' + encodeURIComponent ( String ( typeof transformed !== 'undefined' ? transformed : value ) ) ; } } return link ; } ; public static removeParamsFromUrl = ( url , paramsToDelete ) => { const urlParts = url . split ( '?' ) ; if ( ! urlParts [ 1 ] ) { return url ; } let queryParams = urlParts [ 1 ] ; queryParams = queryParams [ queryParams . length - 1 ] === '#' ? queryParams . slice ( 0 , - 1 ) : queryParams ; const params = new URLSearchParams ( queryParams ) ; for ( const p of paramsToDelete ) { params . delete ( p ) ; } return `${ urlParts [ 0 ] }?${ params . toString ( ) }` ; } ; private static snakeCaseToCamelCase = ( s ) => { return s . replace ( /_[a-z]/g , boundary => boundary [ 1 ] . toUpperCase ( ) ) ; } ; private static camelCaseToSnakeCase = ( s ) => { return s . replace ( /[a-z][A-Z]/g , boundary => `${ boundary [ 0 ] }_${ boundary [ 1 ] . toLowerCase ( ) }` ) ; } ; private static findAndProcessUrlParam = ( expectedParamName , rawParamNameDict , rawParms ) : UrlParam => { if ( typeof rawParamNameDict [ expectedParamName ] === 'undefined' ) { return undefined ; } const rawValue = rawParms [ rawParamNameDict [ expectedParamName ] ] ; if ( typeof rawValue === 'undefined' ) { return undefined ; } if ( typeof Url . URL_PARAM_DICT [ rawValue ] !== 'undefined' ) { return Url . URL_PARAM_DICT [ rawValue ] ; } return decodeURIComponent ( rawValue ) ; } ; private static fillPossibleUrlParamNameVariations = ( urlParamName , rawParamNameDict ) => { rawParamNameDict [ urlParamName ] = urlParamName ; rawParamNameDict [ Url . snakeCaseToCamelCase ( urlParamName ) ] = urlParamName ; rawParamNameDict [ Url . camelCaseToSnakeCase ( urlParamName ) ] = urlParamName ; const shortened = urlParamName . replace ( 'account' , 'acct' ) . replace ( 'message' , 'msg' ) ; rawParamNameDict [ Url . snakeCaseToCamelCase ( shortened ) ] = urlParamName ; rawParamNameDict [ Url . camelCaseToSnakeCase ( shortened ) ] = urlParamName ; } ; } export const emailKeyIndex = ( scope , key ) : string => { return `${ scope . replace ( /[^A-Za-z0-9]+/g , '' ) . toLowerCase ( ) }_${ key }` ; } ;