<commit_before>import {getType, getTypes, getTypeQueries} from 'util/types'
import DomUtil from 'client/util/dom'
import config from 'gen/config-client'

/**
 * Object with general client helper methods. It is also accessible in the CMS Editor
 */
const Util = {
    getType: getType,
    getTypes: getTypes,
    getTypeQueries: getTypeQueries,
    escapeDoubleQuotes: (str) => {
        if (str && str.constructor === String) {
            return str.replace(/"/g, '\\"')
        }
        return str
    },
    safeTags: str => {
        return str ? str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>') : ''
    },
    escapeForJson: (str) => {
        if (str === undefined || str === null) return ''
        if (str.constructor !== String)
            str = JSON.stringify(str)

        return str.replace(/[\\]/g, '\\\\')
            .replace(/[\"]/g, '\\\"')
            .replace(/[\b]/g, '\\b')
            .replace(/[\f]/g, '\\f')
            .replace(/[\n]/g, '\\n')
            .replace(/[\r]/g, '\\r')
            .replace(/[\t]/g, '\\t')
    },
    tryCatch: function (str, ignoreError) {
        try {
            return new Function(DomUtil.toES5(`const {${Object.keys(this).join(',')}}=this;return ${str}`)).bind(this).call()
        } catch (e) {
            if (!ignoreError)
                console.log(e, str)
        }

        return ''
    },
    /* deprecated -> set USE_COOKIES=true */
    getAuthToken: () => {
        // get the authentication token from local storage if it exists
        const token = !_app_.noStorage && localStorage.getItem('token')
        return token ? `JWT ${token}` : null

    },
    dateFromObjectId: (objectId, defValue) => {
        if (!objectId) {
            return defValue || ''
        }
        if (objectId.indexOf('#') === 0) {
            // this is only a tmp id / timestemp
            return parseInt(objectId.substring(1))
        }

        return new Date(parseInt(objectId.substring(0, 8), 16) * 1000)
    },
    getDateTimeFormat: (options) => {
        let lang
        if (options) {
            lang = options.lang
        }
        const intlOptions = Intl.DateTimeFormat().resolvedOptions(), numeric = 'numeric'
        return new Intl.DateTimeFormat(lang || _app_.lang || intlOptions.locale, Object.assign({
            year: numeric,
            month: numeric,
            day: numeric,
            hour: numeric,
            minute: numeric,
            second: numeric,
            timeZone: intlOptions.timeZone || 'UTC'
        }, options))
    },
    formattedDateFromObjectId: (objectId, options) => {
        return Util.getDateTimeFormat(options).format(Util.dateFromObjectId(objectId, new Date()))
    },
    formattedDatetimeFromObjectId: (objectId) => {
        return Util.getDateTimeFormat().format(Util.dateFromObjectId(objectId, new Date()))
    },
    formattedDatetime(stamp, options) {
        if (!stamp) return ''
        if (typeof stamp === 'string') {
            stamp = parseFloat(stamp);
        }

        return Util.getDateTimeFormat(options).format(new Date(stamp))
    },
    formatDate(d, options) {
        try {
            return Util.getDateTimeFormat(options).format(new Date(d))
        } catch (e) {
            return ''
        }
    },
    textFromHtml: str => {
        if (str.constructor !== String) return str
        return str.replace(/<[^>]+>/g, ' ').replace(/\s/g, ' ')
    },
    /*escapeHtml: (str) => {
        const entityMap = {
            '&': '&',
            '<': '<',
            '>': '>',
            '"': '"',
            '\'': ''',
            '/': '/',
            '`': '`',
            '=': '='
        }
        return String(str).replace(/[&<>"'`=\/]/g, function (s) {
            return entityMap[s]
        })
    },*/
    removeNullValues: (obj, options = {}) => {
        Object.keys(obj).forEach((prop) => {

            if (obj[prop] === undefined) {
                if (options.removeUndefined) {
                    delete obj[prop]
                }
            } else if (obj[prop] !== null) {
                if (obj[prop].constructor === Array) {
                    if (options.emptyArray && obj[prop].length === 0) {
                        //remove empty arrays
                        delete obj[prop]
                    } else if (options.recursiv) {

                        let index = obj[prop].length - 1

                        while (index >= 0) {
                            const aObj = obj[prop][index]
                            if (aObj && aObj.constructor === Object) {
                                Util.removeNullValues(aObj, options)
                            } else if (options.nullArrayItems && (aObj === null || aObj === undefined)) {
                                obj[prop].splice(index, 1)
                            }
                            index -= 1
                        }
                    }
                } else if (options.recursiv && obj[prop].constructor === Object) {
                    Util.removeNullValues(obj[prop], options)
                    if (options.emptyObject && Object.keys(obj[prop]).length === 0) {
                        delete obj[prop]
                    }
                }
            } else {
                delete obj[prop]
            }
        })
        return obj
    },
    extractQueryParams: (query, options) => {
        if (!options) {
            options = {}
        } else if (options === true) {
            options = {typeDetection: true}
        }

        if (!query) {
            query = window && window.location.search.substring(1)
        }

        const a = (options.decodeURI === false ? query : decodeURI(query)).split('&'),b = {}
        for (let i = 0; i < a.length; ++i) {
            const p = a[i].split('=', 2)
            const key = p[0].trim()
            if (key) {
                if (p.length === 1)
                    b[key] = ''
                else {
                    const str = p[1].replace(/\+/g, ' ').trim()
                    if (options.typeDetection) {
                        if (str === 'true') {
                            b[key] = true
                        } else if (str === 'false') {
                            b[key] = false
                        } else {
                            b[key] = decodeURIComponent(str)
                        }

                    } else {
                        b[key] = decodeURIComponent(str)
                    }
                }
            }
        }
        return b
    },
    paramsToQuery: (obj) => {
        return Object.keys(obj).reduce(function (a, k) {
            if (obj[k] !== undefined && obj[k] !== '') {
                a.push(k + '=' + encodeURIComponent(obj[k]))
            }
            return a
        }, []).join('&')
    },
    hasCapability(user, capa) {
        const capabilities = (user && user.userData && user.userData.role && user.userData.role.capabilities) || []
        return capabilities.indexOf(capa) >= 0
    },
    hightlight(text, query, cls) {
        if (!text) return ''
        if (!query) return text

        const pattern = new RegExp(`(${query.replace(/\s/g, '|')})`, 'gi');

        return text.replace(pattern, match => `<span class='${cls || ''}'>${match}</span>`);
    },
    getProfileImage(userAny) {
        const user = userAny && userAny.userData ? userAny.userData : userAny
        if (user && user.picture) {

            return _app_.config.UPLOAD_URL + '/' + (user.picture._id ? user.picture._id : user.picture)
        }
        return '/placeholder.svg'
    },
    getMediaSrc(media, src) {
        return src ? src : (media.src ? media.src : _app_.config.UPLOAD_URL + '/' + media._id)
    },
    getImageObject(raw, options) {
        let image
        if (!raw) {
            let src
            if (options && options.resize && options.resize.height && options.resize.width) {
                src = Util.createDummySvg(options.resize.width, options.resize.height)
            } else {
                src = '/placeholder.svg'
            }

            return {
                src,
                alt: 'Placeholder'
            }
        } else if (raw.constructor === String) {
            try {
                image = JSON.parse(raw)
                if (!image) {
                    image = {
                        src: raw
                    }
                }
            } catch (e) {
                image = {
                    src: raw
                }
            }
        } else {
            image = raw
        }
        if (Array.isArray(image)) {
            image = image[0]
            if (!image) {
                return {}
            }
        }
        const data = {}
        if (image.name) {
            data.alt = image.name
        }
        if (!image.src) {
            data.src = _app_.config.UPLOAD_URL + '/' + image._id + '/' + config.PRETTYURL_SEPERATOR + '/' + image.name
        } else {
            data.src = image.src
        }

        if (_app_.ssr && data.src.indexOf('https://') !== 0 && data.src.indexOf('http://') !== 0) {
            try {
                data.src = new URL(data.src, location.origin).href
            } catch (e) {
                console.error(e, data.src)
            }
        }

        if (options) {
            let resize = options.resize, h, w, params = ''
            if (resize) {

                if (resize.width) {
                    w = resize.width
                }
                if (resize.height) {
                    h = resize.height
                }
                if (resize === 'auto' || resize.responsive) {
                    const ww = window.innerWidth
                    if (!w || w > ww) {
                        if (ww <= 800) {
                            w = 800
                        } else if (ww <= 1200) {
                            w = 1200
                        } else if (ww <= 1400) {
                            w = 1400
                        } else {
                            w = 1600
                        }

                        if (h) {
                            h = Math.ceil((w / resize.width) * h)
                        }
                    }
                }
                if (w) {
                    params += `&width=${w}`
                }
                if (h) {
                    params += `&height=${h}`
                }
            }
            if (options.quality) {
                params += `&quality=${options.quality}`
            }

            if (options.format) {
                params += '&format=' + options.format
            } else if (options.webp) {
                params += '&format=webp'
            }
            if (params) {
                data.src += '?' + params.substring(1)
            }
        }

        return data
    },
    // mini jQuery
    $(expr, p) {
        const nodeList = (p || document).querySelectorAll(expr)
        if (nodeList.length === 1) {
            return nodeList[0]
        }
        return nodeList
    },
    replacePlaceholders(template, context, name) {
        if (name !== undefined) {
            const re = new RegExp('\\$\\.' + name + '{', 'g')
            template = template.replace(re, '${')
        }
        try {
            return new Function(DomUtil.toES5('const {' + Object.keys(context).join(',') + '} = this.context;return `' + template + '`')).call({context})
        } catch (e) {
            console.warn(e)
            return e.message
        }
    },
    baseUrl(path, query) {
        let url = Util.removeTrailingSlash(location.pathname.split('/'+config.PRETTYURL_SEPERATOR+'/')[0])

        if(path){
            url += '/'+config.PRETTYURL_SEPERATOR+'/'+encodeURI(path)
        }
        if(query){
            const queryStr = Util.paramsToQuery(query)
            if(queryStr) {
                url += '?'+queryStr
            }
        }

        return url
    },
    removeTrailingSlash (url) {
        if(url !== '/' && url.substr(-1) === '/') {
            return url.substr(0, url.length - 1)
        }
        return url
    },
    translateUrl(lang) {
        const path = window.location.pathname
        if (lang === _app_.lang) return path
        const p = path.split('/')
        if (p[1].length === 2 && p[1] !== lang) {
            if (lang === '' || lang === config.DEFAULT_LANGUAGE) {
                //default language
                p.splice(1, 1)
            } else {
                p[1] = lang
            }
        } else {
            p.splice(1, 0, lang)
        }
        return p.join('/') + window.location.search + window.location.hash
    },
    createWorker(fn) {
        const blob = new Blob([`self.onmessage = (args)=>{
                ${fn.toString()}
                self.postMessage(${fn.name}(args))
            }`], {type: 'text/javascript'}),
            url = URL.createObjectURL(blob)

        const worker = new Worker(url)

        return {
            run: (data) => new Promise((resolve) => {
                    worker.addEventListener('message', resolve, false)
                    worker.postMessage(data)
                }
            )
        }
    },
    // a simple implementation of the shallowCompare.
    // only compares the first level properties and hence shallow.
    shallowCompare(newObj, prevObj) {
        if (!newObj || newObj.constructor !== Object) {
            return newObj !== prevObj
        }
        if (!prevObj || prevObj.constructor !== Object) {
            return newObj !== prevObj
        }
        for (const key in newObj) {
            if (newObj[key] !== prevObj[key]) {
                return true
            }
        }
        return false
    },
    chunkArray(arr, chunk, opts) {

        if (!arr || arr.constructor !== Array) {
            return []
        }

        let chunkInt = parseInt(chunk)

        if (isNaN(chunkInt)) {
            chunkInt = parseInt(new Function('return `' + chunk + '`').call({}))
        }
        if (!isNaN(chunkInt)) {
            const res = arr.reduce((all, one, i) => {
                const ch = Math.floor(i / chunkInt)
                all[ch] = [].concat((all[ch] || []), one)
                return all
            }, [])

            if (opts && opts.fill && res.length > 0) {
                while (res[res.length - 1].length < chunkInt) {
                    res[res.length - 1].push(opts.fill)
                }
            }
            return res
        }
        return arr
    },
    createDummySvg(width, height) {
        return 'data:image/svg+xml;charset=UTF-8,' + encodeURIComponent(`<svg xmlns='http://www.w3.org/2000/svg' width='${width}' height='${height}' viewBox='0 0 ${width} ${height}'><rect fill='#dedfe0' width='${width}' height='${height}'/></svg>`)
    }
}
export default Util

<commit_msg>Add escapeRegex method to client util<commit_after>import {getType, getTypes, getTypeQueries} from 'util/types'
import DomUtil from 'client/util/dom'
import config from 'gen/config-client'

/**
 * Object with general client helper methods. It is also accessible in the CMS Editor
 */
const Util = {
    getType: getType,
    getTypes: getTypes,
    getTypeQueries: getTypeQueries,
    escapeDoubleQuotes: (str) => {
        if (str && str.constructor === String) {
            return str.replace(/"/g, '\\"')
        }
        return str
    },
    safeTags: str => {
        return str ? str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>') : ''
    },
    escapeForJson: (str, options) => {
        if (str === undefined || str === null) return ''
        if (str.constructor !== String)
            str = JSON.stringify(str)
        if(options){

            if(options.regex){
                str = Util.escapeRegex(str)
            }
        }

        return str.replace(/[\\]/g, '\\\\')
            .replace(/[\"]/g, '\\\"')
            .replace(/[\b]/g, '\\b')
            .replace(/[\f]/g, '\\f')
            .replace(/[\n]/g, '\\n')
            .replace(/[\r]/g, '\\r')
            .replace(/[\t]/g, '\\t')
    },
    escapeRegex: (str) => {
        return str.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')
    },
    tryCatch: function (str, ignoreError) {
        try {
            return new Function(DomUtil.toES5(`const {${Object.keys(this).join(',')}}=this;return ${str}`)).bind(this).call()
        } catch (e) {
            if (!ignoreError)
                console.log(e, str)
        }

        return ''
    },
    /* deprecated -> set USE_COOKIES=true */
    getAuthToken: () => {
        // get the authentication token from local storage if it exists
        const token = !_app_.noStorage && localStorage.getItem('token')
        return token ? `JWT ${token}` : null

    },
    dateFromObjectId: (objectId, defValue) => {
        if (!objectId) {
            return defValue || ''
        }
        if (objectId.indexOf('#') === 0) {
            // this is only a tmp id / timestemp
            return parseInt(objectId.substring(1))
        }

        return new Date(parseInt(objectId.substring(0, 8), 16) * 1000)
    },
    getDateTimeFormat: (options) => {
        let lang
        if (options) {
            lang = options.lang
        }
        const intlOptions = Intl.DateTimeFormat().resolvedOptions(), numeric = 'numeric'
        return new Intl.DateTimeFormat(lang || _app_.lang || intlOptions.locale, Object.assign({
            year: numeric,
            month: numeric,
            day: numeric,
            hour: numeric,
            minute: numeric,
            second: numeric,
            timeZone: intlOptions.timeZone || 'UTC'
        }, options))
    },
    formattedDateFromObjectId: (objectId, options) => {
        return Util.getDateTimeFormat(options).format(Util.dateFromObjectId(objectId, new Date()))
    },
    formattedDatetimeFromObjectId: (objectId) => {
        return Util.getDateTimeFormat().format(Util.dateFromObjectId(objectId, new Date()))
    },
    formattedDatetime(stamp, options) {
        if (!stamp) return ''
        if (typeof stamp === 'string') {
            stamp = parseFloat(stamp);
        }

        return Util.getDateTimeFormat(options).format(new Date(stamp))
    },
    formatDate(d, options) {
        try {
            return Util.getDateTimeFormat(options).format(new Date(d))
        } catch (e) {
            return ''
        }
    },
    textFromHtml: str => {
        if (str.constructor !== String) return str
        return str.replace(/<[^>]+>/g, ' ').replace(/\s/g, ' ')
    },
    /*escapeHtml: (str) => {
        const entityMap = {
            '&': '&',
            '<': '<',
            '>': '>',
            '"': '"',
            '\'': ''',
            '/': '/',
            '`': '`',
            '=': '='
        }
        return String(str).replace(/[&<>"'`=\/]/g, function (s) {
            return entityMap[s]
        })
    },*/
    removeNullValues: (obj, options = {}) => {
        Object.keys(obj).forEach((prop) => {

            if (obj[prop] === undefined) {
                if (options.removeUndefined) {
                    delete obj[prop]
                }
            } else if (obj[prop] !== null) {
                if (obj[prop].constructor === Array) {
                    if (options.emptyArray && obj[prop].length === 0) {
                        //remove empty arrays
                        delete obj[prop]
                    } else if (options.recursiv) {

                        let index = obj[prop].length - 1

                        while (index >= 0) {
                            const aObj = obj[prop][index]
                            if (aObj && aObj.constructor === Object) {
                                Util.removeNullValues(aObj, options)
                            } else if (options.nullArrayItems && (aObj === null || aObj === undefined)) {
                                obj[prop].splice(index, 1)
                            }
                            index -= 1
                        }
                    }
                } else if (options.recursiv && obj[prop].constructor === Object) {
                    Util.removeNullValues(obj[prop], options)
                    if (options.emptyObject && Object.keys(obj[prop]).length === 0) {
                        delete obj[prop]
                    }
                }
            } else {
                delete obj[prop]
            }
        })
        return obj
    },
    extractQueryParams: (query, options) => {
        if (!options) {
            options = {}
        } else if (options === true) {
            options = {typeDetection: true}
        }

        if (!query) {
            query = window && window.location.search.substring(1)
        }

        const a = (options.decodeURI === false ? query : decodeURI(query)).split('&'),b = {}
        for (let i = 0; i < a.length; ++i) {
            const p = a[i].split('=', 2)
            const key = p[0].trim()
            if (key) {
                if (p.length === 1)
                    b[key] = ''
                else {
                    const str = p[1].replace(/\+/g, ' ').trim()
                    if (options.typeDetection) {
                        if (str === 'true') {
                            b[key] = true
                        } else if (str === 'false') {
                            b[key] = false
                        } else {
                            b[key] = decodeURIComponent(str)
                        }

                    } else {
                        b[key] = decodeURIComponent(str)
                    }
                }
            }
        }
        return b
    },
    paramsToQuery: (obj) => {
        return Object.keys(obj).reduce(function (a, k) {
            if (obj[k] !== undefined && obj[k] !== '') {
                a.push(k + '=' + encodeURIComponent(obj[k]))
            }
            return a
        }, []).join('&')
    },
    hasCapability(user, capa) {
        const capabilities = (user && user.userData && user.userData.role && user.userData.role.capabilities) || []
        return capabilities.indexOf(capa) >= 0
    },
    hightlight(text, query, cls) {
        if (!text) return ''
        if (!query) return text

        const pattern = new RegExp(`(${query.replace(/\s/g, '|')})`, 'gi');

        return text.replace(pattern, match => `<span class='${cls || ''}'>${match}</span>`);
    },
    getProfileImage(userAny) {
        const user = userAny && userAny.userData ? userAny.userData : userAny
        if (user && user.picture) {

            return _app_.config.UPLOAD_URL + '/' + (user.picture._id ? user.picture._id : user.picture)
        }
        return '/placeholder.svg'
    },
    getMediaSrc(media, src) {
        return src ? src : (media.src ? media.src : _app_.config.UPLOAD_URL + '/' + media._id)
    },
    getImageObject(raw, options) {
        let image
        if (!raw) {
            let src
            if (options && options.resize && options.resize.height && options.resize.width) {
                src = Util.createDummySvg(options.resize.width, options.resize.height)
            } else {
                src = '/placeholder.svg'
            }

            return {
                src,
                alt: 'Placeholder'
            }
        } else if (raw.constructor === String) {
            try {
                image = JSON.parse(raw)
                if (!image) {
                    image = {
                        src: raw
                    }
                }
            } catch (e) {
                image = {
                    src: raw
                }
            }
        } else {
            image = raw
        }
        if (Array.isArray(image)) {
            image = image[0]
            if (!image) {
                return {}
            }
        }
        const data = {}
        if (image.name) {
            data.alt = image.name
        }
        if (!image.src) {
            data.src = _app_.config.UPLOAD_URL + '/' + image._id + '/' + config.PRETTYURL_SEPERATOR + '/' + image.name
        } else {
            data.src = image.src
        }

        if (_app_.ssr && data.src.indexOf('https://') !== 0 && data.src.indexOf('http://') !== 0) {
            try {
                data.src = new URL(data.src, location.origin).href
            } catch (e) {
                console.error(e, data.src)
            }
        }

        if (options) {
            let resize = options.resize, h, w, params = ''
            if (resize) {

                if (resize.width) {
                    w = resize.width
                }
                if (resize.height) {
                    h = resize.height
                }
                if (resize === 'auto' || resize.responsive) {
                    const ww = window.innerWidth
                    if (!w || w > ww) {
                        if (ww <= 800) {
                            w = 800
                        } else if (ww <= 1200) {
                            w = 1200
                        } else if (ww <= 1400) {
                            w = 1400
                        } else {
                            w = 1600
                        }

                        if (h) {
                            h = Math.ceil((w / resize.width) * h)
                        }
                    }
                }
                if (w) {
                    params += `&width=${w}`
                }
                if (h) {
                    params += `&height=${h}`
                }
            }
            if (options.quality) {
                params += `&quality=${options.quality}`
            }

            if (options.format) {
                params += '&format=' + options.format
            } else if (options.webp) {
                params += '&format=webp'
            }
            if (params) {
                data.src += '?' + params.substring(1)
            }
        }

        return data
    },
    // mini jQuery
    $(expr, p) {
        const nodeList = (p || document).querySelectorAll(expr)
        if (nodeList.length === 1) {
            return nodeList[0]
        }
        return nodeList
    },
    replacePlaceholders(template, context, name) {
        if (name !== undefined) {
            const re = new RegExp('\\$\\.' + name + '{', 'g')
            template = template.replace(re, '${')
        }
        try {
            return new Function(DomUtil.toES5('const {' + Object.keys(context).join(',') + '} = this.context;return `' + template + '`')).call({context})
        } catch (e) {
            console.warn(e)
            return e.message
        }
    },
    baseUrl(path, query) {
        let url = Util.removeTrailingSlash(location.pathname.split('/'+config.PRETTYURL_SEPERATOR+'/')[0])

        if(path){
            url += '/'+config.PRETTYURL_SEPERATOR+'/'+encodeURI(path)
        }
        if(query){
            const queryStr = Util.paramsToQuery(query)
            if(queryStr) {
                url += '?'+queryStr
            }
        }

        return url
    },
    removeTrailingSlash (url) {
        if(url !== '/' && url.substr(-1) === '/') {
            return url.substr(0, url.length - 1)
        }
        return url
    },
    translateUrl(lang) {
        const path = window.location.pathname
        if (lang === _app_.lang) return path
        const p = path.split('/')
        if (p[1].length === 2 && p[1] !== lang) {
            if (lang === '' || lang === config.DEFAULT_LANGUAGE) {
                //default language
                p.splice(1, 1)
            } else {
                p[1] = lang
            }
        } else {
            p.splice(1, 0, lang)
        }
        return p.join('/') + window.location.search + window.location.hash
    },
    createWorker(fn) {
        const blob = new Blob([`self.onmessage = (args)=>{
                ${fn.toString()}
                self.postMessage(${fn.name}(args))
            }`], {type: 'text/javascript'}),
            url = URL.createObjectURL(blob)

        const worker = new Worker(url)

        return {
            run: (data) => new Promise((resolve) => {
                    worker.addEventListener('message', resolve, false)
                    worker.postMessage(data)
                }
            )
        }
    },
    // a simple implementation of the shallowCompare.
    // only compares the first level properties and hence shallow.
    shallowCompare(newObj, prevObj) {
        if (!newObj || newObj.constructor !== Object) {
            return newObj !== prevObj
        }
        if (!prevObj || prevObj.constructor !== Object) {
            return newObj !== prevObj
        }
        for (const key in newObj) {
            if (newObj[key] !== prevObj[key]) {
                return true
            }
        }
        return false
    },
    chunkArray(arr, chunk, opts) {

        if (!arr || arr.constructor !== Array) {
            return []
        }

        let chunkInt = parseInt(chunk)

        if (isNaN(chunkInt)) {
            chunkInt = parseInt(new Function('return `' + chunk + '`').call({}))
        }
        if (!isNaN(chunkInt)) {
            const res = arr.reduce((all, one, i) => {
                const ch = Math.floor(i / chunkInt)
                all[ch] = [].concat((all[ch] || []), one)
                return all
            }, [])

            if (opts && opts.fill && res.length > 0) {
                while (res[res.length - 1].length < chunkInt) {
                    res[res.length - 1].push(opts.fill)
                }
            }
            return res
        }
        return arr
    },
    createDummySvg(width, height) {
        return 'data:image/svg+xml;charset=UTF-8,' + encodeURIComponent(`<svg xmlns='http://www.w3.org/2000/svg' width='${width}' height='${height}' viewBox='0 0 ${width} ${height}'><rect fill='#dedfe0' width='${width}' height='${height}'/></svg>`)
    }
}
export default Util

<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react';
import ChatArea from './components/ChatArea';
import ChatInput from './components/ChatInput';
import styles from './Chat.css';

export default class Chat extends Component {
  static propTypes = {
    messages: PropTypes.array,
    onMessage: PropTypes.func,
    me: PropTypes.shape()
  };
  replay = (e) => {
    const name = e.currentTarget.textContent;
    const node = this.input.usermsg;
    node.value = name + ', ' + node.value;
    node.focus();
  };
  isMine = id => this.props.me.id === id;

  render() {
    return (

          <ChatArea messages={this.props.messages} replay={this.replay} isMine={this.isMine} />
          <ChatInput onMessage={this.props.onMessage} ref={node => {this.input = node;}} />

    );
  }
}
<commit_msg>Specify default props<commit_after>import React, { Component, PropTypes } from 'react';
import ChatArea from './components/ChatArea';
import ChatInput from './components/ChatInput';
import styles from './Chat.css';

export default class Chat extends Component {
  static propTypes = {
    messages: PropTypes.array,
    onMessage: PropTypes.func.isRequired,
    me: PropTypes.shape().isRequired
  };

  static defaultProps = {
    messages: []
  };

  replay = (e) => {
    const name = e.currentTarget.textContent;
    const node = this.input.usermsg;
    node.value = name + ', ' + node.value;
    node.focus();
  };
  isMine = id => this.props.me.id === id;

  render() {
    return (

          <ChatArea messages={this.props.messages} replay={this.replay} isMine={this.isMine} />
          <ChatInput onMessage={this.props.onMessage} ref={node => {this.input = node;}} />

    );
  }
}
<|endoftext|>


<commit_before>// Accompanying JS file for the default page template.
// Describes the page's metadata and actions.

Template.edit.events = {
  'submit #pageEditForm': function (e) {
    e.preventDefault();
    var page = Template.edit.page();
    var pageData = {};
    $("#pageEditForm input[type='text'], #pageEditForm textarea").each(function() {
      pageData[$(this).attr('id')] = $(this).val();
    });
    Pages.update({_id: Pages.findOne({slug: "home"})._id}, {$set: pageData});
    return false;
  }
};

Template.edit.page = function () {
  var page_slug = Session.get('page_slug');
  if (!page_slug)
    return {title: 'Sorry, we couldn\'t find the requested page'};
  return Pages.findOne({slug: page_slug});
};<commit_msg>Page saving comes correct<commit_after>// Accompanying JS file for the default page template.
// Describes the page's metadata and actions.

Template.edit.events = {
  // 'keyup input': function(e) {
  //   e.preventDefault();
  //   var page = Template.edit.page();
  //   var setting = {};
  //   debugger;
  //   var attr = $(this).attr('id');
  //   var val = $(this).val();
  //   setting[attr] = val;

  //   Pages.update({_id: Pages.findOne({slug: "home"})._id}, {$set: { attr: val }});
  //   return false;
  // },
  'submit #pageEditForm': function (e) {
    page = {};
    for (var attrname in this) {
      if(attrname != '_id') page[attrname] = $('#'+attrname).val(); // Clone this without _id (which we can't save)
    }
    e.preventDefault();
    Pages.update({_id: this._id}, {$set: page});
    return false;
  }
};

Template.edit.clone = (function(){
  return function (obj) { Object.prototype=obj; return new Object() };
  function Object(){}
}());

Template.edit.page = function () {
  var page_slug = Session.get('page_slug');
  if (!page_slug)
    return {title: 'Sorry, we couldn\'t find the requested page'};
  return Pages.findOne({slug: page_slug});
};<|endoftext|>


<commit_before>import React, { Component } from 'react';
import Avatar from 'material-ui/Avatar';
import {List, ListItem} from 'material-ui/List';
import Subheader from 'material-ui/Subheader';
import TextField from 'material-ui/TextField';
import IconButton from 'material-ui/IconButton';

const listItemStyle = {
  paddingTop: 10,
  paddingBottom: 10,
  minHeight: 40
};

const textStyle = {
  color: 'white',
  fontSize: '12px',
  margin: 0
};

const style = {
  width: 300,
  height: 300,
  textAlign: 'left',
  display: 'inline-block',
  float: 'right',
  background: 'rgb(0, 151, 167)'
};

function ChatMessage({ message, name, time }) {
  const timeFormatted = new Date(time);
  let minutes = timeFormatted.getMinutes();
  if (minutes < 10) {
    minutes = `0${minutes}`
  }

  return (

        {name}
        {timeFormatted.getHours()}:{minutes}

      {message}

  )
}

export default class Chat extends Component {
  constructor(props) {
    super(props);

    this.state = {
      message: ''
    };
  }

  defaultProps = {
    messages: []
  };

  submit(e) {
    e.preventDefault();

    if (this.state.message) {
      this.props.onMessageSubmit(this.state.message);
      // after submit
      this.setState({
        message: ''
      });
    }
  }

  componentDidUpdate() {
    this.listNode.scrollTop = this.listNode.scrollHeight;
  }

  render() {
    return (

 {this.listNode = node}}>
          <List style={{paddingBottom: 0}}>
            <Subheader>Recent chats</Subheader>
            {this.props.messages.map(({ message, id, time, name, photoUrl}) => (
              <ListItem
                innerDivStyle={listItemStyle}
                key={id}
                primaryText={<ChatMessage message={message} time={time} name={name} />}
                leftAvatar={<Avatar src={photoUrl} />}
              />
            ))}
          </List>

          <form onSubmit={e => this.submit(e)}>
            <TextField name="message"
                       value={this.state.message}
                       style={{ width: 240, fontSize: 12 }}
                       onChange={(e) => this.setState({ message: e.target.value })} />
            <IconButton iconStyle={{width: 30, height: 30}}
                        onClick={e => this.submit(e)}
                        iconClassName="fa fa-paper-plane" />
          </form>

    );
  }
}
<commit_msg>[chat] add divider<commit_after>import React, { Component } from 'react';
import Avatar from 'material-ui/Avatar';
import {List, ListItem} from 'material-ui/List';
import Subheader from 'material-ui/Subheader';
import TextField from 'material-ui/TextField';
import IconButton from 'material-ui/IconButton';
import Divider from 'material-ui/Divider';

const listItemStyle = {
  paddingTop: 10,
  paddingBottom: 10,
  minHeight: 40
};

const textStyle = {
  color: 'white',
  fontSize: '12px',
  margin: 0
};

const style = {
  width: 300,
  height: 300,
  textAlign: 'left',
  display: 'inline-block',
  float: 'right',
  background: 'rgb(0, 151, 167)'
};

function ChatMessage({ message, name, time }) {
  const timeFormatted = new Date(time);
  let minutes = timeFormatted.getMinutes();
  if (minutes < 10) {
    minutes = `0${minutes}`
  }

  return (

        {name}
        {timeFormatted.getHours()}:{minutes}

      {message}

  )
}

export default class Chat extends Component {
  constructor(props) {
    super(props);

    this.state = {
      message: ''
    };
  }

  defaultProps = {
    messages: []
  };

  submit(e) {
    e.preventDefault();

    if (this.state.message) {
      this.props.onMessageSubmit(this.state.message);
      // after submit
      this.setState({
        message: ''
      });
    }
  }

  componentDidUpdate() {
    this.listNode.scrollTop = this.listNode.scrollHeight;
  }

  render() {
    return (

 {this.listNode = node}}>
          <List style={{paddingBottom: 0}}>
            <Subheader>Recent chats</Subheader>
            {this.props.messages.map(({ message, id, time, name, photoUrl}) => (
              <ListItem
                innerDivStyle={listItemStyle}
                key={id}
                primaryText={<ChatMessage message={message} time={time} name={name} />}
                leftAvatar={<Avatar src={photoUrl} />}
              />
            ))}
          </List>

        <Divider />

          <form onSubmit={e => this.submit(e)}>
            <TextField name="message"
                       value={this.state.message}
                       style={{ width: 240, fontSize: 12 }}
                       inputStyle={{ paddingLeft: 10 }}
                       onChange={(e) => this.setState({ message: e.target.value })} />
            <IconButton iconStyle={{width: 30, height: 30}}
                        onClick={e => this.submit(e)}
                        iconClassName="fa fa-paper-plane" />
          </form>

    );
  }
}
<|endoftext|>


<commit_before>/**
 * @fileoverview The WebExposed Chrome extension allows users to
 * organize WebKit bugs labeled with the "WebExposed" keyword.
 * @author meh@chromium.org (Max Heinritz)
 */

/**
 * Adds a column corresponding an attribute of a bug.
 * @param {string} name This is the name of the column to add. Corresponds
 *     to the name of an attribute in the model (eg 'hidden' or 'crbug id').
 * @param {string} type This is the {@code HTML} {@code input} type for the
 *     column. (eg {@code checkbox} for 'hidden')
 */

var storage = chrome.storage.local;

function addColumn(name,type) {
// Create cols for matching CSS to columns
// Note: Bugzilla buglist tables contain at most 100 bugs per table,
// so there are multiple tables & colgorups per page with >100 bugs.
var colGroups = document.getElementsByTagName('colgroup');
var safeName = name.replace(/ _/g,"-");

for (var i = colGroups.length - 1; i >= 0; i--) {
  var col = document.createElement('col');
  col.className = 'webexposed_colgroup ' + safeName;
  colGroups[i].appendChild(col);
}

// Create headers from the attribute's name
var headerLists = document.getElementsByClassName('bz_buglist_header');
for (var i = headerLists.length - 1; i >= 0; i--) {
  var th = document.createElement('th');
  th.className = 'webexposed_header ' + safeName;
  th.innerHTML = name;
  headerLists[i].appendChild(th);
}

addUIElementToAllRows(name,type);
}

/**
 * Adds a UI element on each row that corresponds to an attribute of the bug
 * on that row. Different attributes require different input types.
 * @param {string} name This is the name of the column to add. Corresponds
 *     to the name of an attribute in the model (eg 'hidden' or 'crbug id').
 * @param {string} type This is the {@code HTML} {@code input} type for the
 *     attribute. (eg {@code checkbox} for 'hidden')
 */
function addUIElementToAllRows(name,type) {
	// Add UI elements to modify the attribute
  var rows = document.getElementsByClassName('bz_bugitem');
  var safeName = name.replace(/ _/g,"-");

  for (var i = rows.length - 1; i >= 0; i--) {
    // Extract the bug id from the link in the first cell in this row
    var bugId = rows[i].cells[0].getElementsByTagName('a')[0].innerHTML;

    var td = document.createElement('td');
    td.className = 'webexposed_column ' + safeName;

    var input;

    // Most inputs will be input elements, except for textareas.
    if (type === 'textarea') {
      input = document.createElement('textarea');
    } else {
      input = document.createElement('input');
      input.type = type;
      // Bug ids are typically <=7 digits.
      if (name === 'crbug id') {
        input.size = 7;
      }
    }

    input.name = name;
    input.className = 'webexposed_input ' + bugId;

    td.appendChild(input);
    rows[i].appendChild(td);
  }
}

/**
 * Adds a save button.
 */
function addSaveButton() {
  var header = document.getElementById('header');
  var button = document.createElement('input');
  button.type = 'submit';
  button.value = 'Save WebExposed Attributes';
  button.id = 'webexposed_button';
  button.addEventListener('click',saveChanges);

  // Add the button to the header
  header.appendChild(button);
}

/**
 * Save the changes to the bug states to the AppEngine backend.
 */
function saveChanges() {
  // Load secret word using the Chrome extension storage API.
  storage.get('secretword',function(items) {
    // Check if the user didn't enter the secret word on the options page
    if (!items['secretword']) {
      alert('Please enter the secret word on the options page.');
    }
    else {
      constructJsonUpdate();
      // TODO: Send update with secret word to App Engine backend
    }
  });
}

/**
 * Callback for the POST to the App Engine server
 */
function saveChangesCallback() {
  alert('Your changes have been saved.');
}

/**
 * Constructs a JSON array based on the current state of the input elements (eg
 * checkboxed checked vs not).
 * @return {[bugId]{hidden: boolean,
 * important: boolean,
 * crbug_id: String,
 * comments: String}} A JSON object with all the updates to send to the server.
 */
function constructJsonUpdate() {
  var rows = document.getElementsByClassName('bz_bugitem');

  var safeName = name.replace(/ _/g,"-");
  jsonUpdate = [];

  // Examine each bug and add an appropriate entry in the array
  // TODO: only send updates for bugs with status changes
  for (var i = rows.length - 1; i >= 0; i--) {
    // Extract the bug id from the link in the first cell in this row
    var bugId = rows[i].cells[0].getElementsByTagName('a')[0].innerHTML;
    var inputsForThisBug = document.getElementsByClassName(
      'webexposed_input ' + bugId);
    jsonUpdate[bugId] = [];

    // Parse the input element states to construct the JSON
    for (var j = 0; j < inputsForThisBug.length; j++) {
      var inputElement = inputsForThisBug[j];
      var varValue = '';

      // For each checkbox, the value is a boolean (where true means checked'
      if (inputElement.tagName === 'INPUT'
        && inputElement.type === 'checkbox') {
          varValue=inputElement.checked;
      // For other types of input (right now only textarea and text), the value
      // is the content in the input element
      } else {
        if(inputElement.value) {
          varValue = inputElement.value;
        }
      }

      // Add this attribute's value to the array
      jsonUpdate[bugId][inputElement.name] = varValue;
    }

    // Print the JSON to the console, for reference
    console.log(bugId + ': ');
    console.log(jsonUpdate[bugId]);
  }
  return jsonUpdate;
}

// only run the extension on the WebExposed bug list
if (document.URL.indexOf('keywords=WebExposed') != -1) {
  addSaveButton();
  addColumn('hidden','checkbox');
  addColumn('important','checkbox');
  addColumn('crbug id','text');
  addColumn('comment','textarea');
}

// TODO: Only allow one of hidden/important to be selected at a time
<commit_msg>fix tabs<commit_after>/**
 * @fileoverview The WebExposed Chrome extension allows users to
 * organize WebKit bugs labeled with the "WebExposed" keyword.
 * @author meh@chromium.org (Max Heinritz)
 */

/**
 * Adds a column corresponding an attribute of a bug.
 * @param {string} name This is the name of the column to add. Corresponds
 *     to the name of an attribute in the model (eg 'hidden' or 'crbug id').
 * @param {string} type This is the {@code HTML} {@code input} type for the
 *     column. (eg {@code checkbox} for 'hidden')
 */

var storage = chrome.storage.local;

function addColumn(name,type) {
  // Create cols for matching CSS to columns
  // Note: Bugzilla buglist tables contain at most 100 bugs per table,
  // so there are multiple tables & colgorups per page with >100 bugs.
  var colGroups = document.getElementsByTagName('colgroup');
  var safeName = name.replace(/ _/g,"-");

  for (var i = colGroups.length - 1; i >= 0; i--) {
    var col = document.createElement('col');
    col.className = 'webexposed_colgroup ' + safeName;
    colGroups[i].appendChild(col);
  }

  // Create headers from the attribute's name
  var headerLists = document.getElementsByClassName('bz_buglist_header');
  for (var i = headerLists.length - 1; i >= 0; i--) {
    var th = document.createElement('th');
    th.className = 'webexposed_header ' + safeName;
    th.innerHTML = name;
    headerLists[i].appendChild(th);
  }

  addUIElementToAllRows(name,type);
}

/**
 * Adds a UI element on each row that corresponds to an attribute of the bug
 * on that row. Different attributes require different input types.
 * @param {string} name This is the name of the column to add. Corresponds
 *     to the name of an attribute in the model (eg 'hidden' or 'crbug id').
 * @param {string} type This is the {@code HTML} {@code input} type for the
 *     attribute. (eg {@code checkbox} for 'hidden')
 */
function addUIElementToAllRows(name,type) {
	// Add UI elements to modify the attribute
  var rows = document.getElementsByClassName('bz_bugitem');
  var safeName = name.replace(/ _/g,"-");

  for (var i = rows.length - 1; i >= 0; i--) {
    // Extract the bug id from the link in the first cell in this row
    var bugId = rows[i].cells[0].getElementsByTagName('a')[0].innerHTML;

    var td = document.createElement('td');
    td.className = 'webexposed_column ' + safeName;

    var input;

    // Most inputs will be input elements, except for textareas.
    if (type === 'textarea') {
      input = document.createElement('textarea');
    } else {
      input = document.createElement('input');
      input.type = type;
      // Bug ids are typically <=7 digits.
      if (name === 'crbug id') {
        input.size = 7;
      }
    }

    input.name = name;
    input.className = 'webexposed_input ' + bugId;

    td.appendChild(input);
    rows[i].appendChild(td);
  }
}

/**
 * Adds a save button.
 */
function addSaveButton() {
  var header = document.getElementById('header');
  var button = document.createElement('input');
  button.type = 'submit';
  button.value = 'Save WebExposed Attributes';
  button.id = 'webexposed_button';
  button.addEventListener('click',saveChanges);

  // Add the button to the header
  header.appendChild(button);
}

/**
 * Save the changes to the bug states to the AppEngine backend.
 */
function saveChanges() {
  // Load secret word using the Chrome extension storage API.
  storage.get('secretword',function(items) {
    // Check if the user didn't enter the secret word on the options page
    if (!items['secretword']) {
      alert('Please enter the secret word on the options page.');
    }
    else {
      constructJsonUpdate();
      // TODO: Send update with secret word to App Engine backend
    }
  });
}

/**
 * Callback for the POST to the App Engine server
 */
function saveChangesCallback() {
  alert('Your changes have been saved.');
}

/**
 * Constructs a JSON array based on the current state of the input elements (eg
 * checkboxed checked vs not).
 * @return {[bugId]{hidden: boolean,
 * important: boolean,
 * crbug_id: String,
 * comments: String}} A JSON object with all the updates to send to the server.
 */
function constructJsonUpdate() {
  var rows = document.getElementsByClassName('bz_bugitem');

  var safeName = name.replace(/ _/g,"-");
  jsonUpdate = [];

  // Examine each bug and add an appropriate entry in the array
  // TODO: only send updates for bugs with status changes
  for (var i = rows.length - 1; i >= 0; i--) {
    // Extract the bug id from the link in the first cell in this row
    var bugId = rows[i].cells[0].getElementsByTagName('a')[0].innerHTML;
    var inputsForThisBug = document.getElementsByClassName(
      'webexposed_input ' + bugId);
    jsonUpdate[bugId] = [];

    // Parse the input element states to construct the JSON
    for (var j = 0; j < inputsForThisBug.length; j++) {
      var inputElement = inputsForThisBug[j];
      var varValue = '';

      // For each checkbox, the value is a boolean (where true means checked'
      if (inputElement.tagName === 'INPUT'
        && inputElement.type === 'checkbox') {
          varValue=inputElement.checked;
      // For other types of input (right now only textarea and text), the value
      // is the content in the input element
      } else {
        if(inputElement.value) {
          varValue = inputElement.value;
        }
      }

      // Add this attribute's value to the array
      jsonUpdate[bugId][inputElement.name] = varValue;
    }

    // Print the JSON to the console, for reference
    console.log(bugId + ': ');
    console.log(jsonUpdate[bugId]);
  }
  return jsonUpdate;
}

// only run the extension on the WebExposed bug list
if (document.URL.indexOf('keywords=WebExposed') != -1) {
  addSaveButton();
  addColumn('hidden','checkbox');
  addColumn('important','checkbox');
  addColumn('crbug id','text');
  addColumn('comment','textarea');
}

// TODO: Only allow one of hidden/important to be selected at a time
<|endoftext|>


<commit_before>// Ionic Starter App

// angular.module is a global place for creating, registering and retrieving Angular modules
// 'starter' is the name of this angular module example (also set in a <body> attribute in index.html)
// the 2nd parameter is an array of 'requires'
// 'starter.services' is found in services.js
// 'starter.controllers' is found in controllers.js
angular.module('starter', [
    'ionic',
    'ionic.service.core',
    'ionic.service.analytics',
    'starter.controllers',
    'starter.services',
    'ngCordova'
])
    .run(function($ionicPlatform, $ionicAnalytics) {
        $ionicPlatform.ready(function() {

            $ionicAnalytics.register();

            // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
            // for form inputs)
            if (window.cordova && window.cordova.plugins && window.cordova.plugins.Keyboard) {
                cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
                cordova.plugins.Keyboard.disableScroll(true);

            }
            if (window.StatusBar) {
                // org.apache.cordova.statusbar required
                //StatusBar.styleLightContent();
                StatusBar.hide();
                ionic.Platform.fullScreen();
            }
        });
    })

    .config(function($stateProvider, $urlRouterProvider, $ionicConfigProvider) {

        // Ionic uses AngularUI Router which uses the concept of states
        // Learn more here: https://github.com/angular-ui/ui-router
        // Set up the various states which the app can be in.
        // Each state's controller can be found in controllers.js
        $stateProvider

            // setup an abstract state for the tabs directive
            .state('tab', {
                url: '/tab',
                abstract: true,
                templateUrl: 'templates/tabs.html'
            })

            // Each tab has its own nav history stack:

            .state('tab.forecast', {
                url: '/forecast',
                views: {
                    'tab-forecast': {
                        templateUrl: 'templates/tab-forecast.html',
                        controller: 'ForecastCtrl'
                    }
                }
            })

            .state('tab.search', {
                url: '/search',
                views: {
                    'tab-search': {
                        templateUrl: 'templates/tab-search.html',
                        controller: 'SearchCtrl'
                    }
                }
            })
            .state('tab.chat-detail', {
                url: '/chats/:chatId',
                views: {
                    'tab-chats': {
                        templateUrl: 'templates/chat-detail.html',
                        controller: 'ChatDetailCtrl'
                    }
                }
            })

            .state('tab.account', {
                url: '/account',
                views: {
                    'tab-account': {
                        templateUrl: 'templates/tab-account.html',
                        controller: 'AccountCtrl'
                    }
                }
            });

        // if none of the above states are matched, use this as the fallback
        $urlRouterProvider.otherwise('/tab/forecast');

        $ionicConfigProvider.tabs.style('standard');
        $ionicConfigProvider.tabs.position('bottom');
    })
    .directive('ngShortChart', function() {
        return {
            restrict: 'A',
            transclude: true,
            link: function (scope, iElement) {
                var duration = 1000;
                var margin = {top: 30, right: 0, bottom: 10, left: 0},
                    width = iElement[0].getBoundingClientRect().width - margin.left - margin.right,
                    height = iElement[0].getBoundingClientRect().height - margin.top - margin.bottom;

                var svg = d3.select(iElement[0]).append('svg')
                    .attr('width', width + margin.left + margin.right)
                    .attr('height', height + margin.top + margin.bottom)
                    .append('g')
                    .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

                var x = d3.scale.ordinal()
                    .rangeBands([width, 0]);

                var y = d3.scale.linear()
                    .range([height, 0]);

                var initLine = d3.svg.line()
                    .interpolate('linear')
                    .x(function (d, i) {
                        return x.rangeBand() * i + x.rangeBand() / 2;
                    })
                    .y(height);

                var line = d3.svg.line()
                    .interpolate('linear')
                    .x(function (d, i) {
                        return x.rangeBand() * i + x.rangeBand() / 2;
                    })
                    .y(function (d) {
                        return y(d.value.t3h);
                    });

                var chart = function () {
                    var data = scope.timeChart;

                    x.domain(d3.range(data[0].values.length));
                    y.domain([
                        d3.min(data, function (c) {
                            return d3.min(c.values, function (v) {
                                return v.value.t3h;
                            });
                        }),
                        d3.max(data, function (c) {
                            return d3.max(c.values, function (v) {
                                return v.value.t3h;
                            });
                        })
                    ]).nice();

                    var xAxis = d3.svg.axis()
                        .scale(x)
                        .orient('bottom');

                    var yAxis = d3.svg.axis()
                        .scale(y)
                        .orient('left');

                    var group = svg.selectAll('.line-group')
                        .data(data);

                    var group_enter = group.enter()
                        .append('g')
                        .attr('class', 'line-group');

                    // draw line
                    group_enter.append('path')
                        .attr('class', function (d) {
                            return 'line line-' + d.name;
                        })
                        .attr('d', function (d) {
                            return line(d.values);
                        });

                    group.select('.line')
                        .attr('d', function (d) {
                            return initLine(d.values);
                        })
                        .transition()
                        .duration(duration)
                        .attr('d', function (d) {
                            return line(d.values);
                        });

                    // draw point
                    group_enter.append('g')
                        .attr('class', 'line-point')
                        .selectAll('circle')
                        .data(function (d) {
                            return d.values;
                        })
                        .enter().append('circle');

                    group.select('.line-point')
                        .selectAll('circle')
                        .data(function (d) {
                            return d.values;
                        })
                        .attr('cx', function (d, i) {
                            return x.rangeBand() * i + x.rangeBand() / 2;
                        })
                        .attr('cy', height)
                        .attr('r', function (d, i) {
                            if (i === 8) {
                                return 5;
                            }
                            return 2;
                        })
                        .attr('class', function (d, i) {
                            if (i === 8) {
                                return 'circle-' + d.name + '-current';
                            }
                            return 'circle-' + d.name;
                        })
                        .transition()
                        .duration(duration)
                        .attr('cy', function (d) {
                            return y(d.value.t3h);
                        });

                    // draw value
                    group_enter.append('g')
                        .attr('class', 'line-value')
                        .selectAll('text')
                        .data(function (d) {
                            return d.values;
                        })
                        .enter().append('text');

                    group.select('.line-value')
                        .selectAll('text')
                        .data(function (d) {
                            return d.values;
                        })
                        .attr('x', function (d, i) {
                            return x.rangeBand() * i + x.rangeBand() / 2;
                        })
                        .attr('y', height)
                        .attr('dy', -10)
                        .attr('text-anchor', 'middle')
                        .text(function (d) {
                            if (d.name === 'today' && (d.value.tmn !== undefined || d.value.tmx !== undefined)) {
                                return (d.value.tmn | d.value.tmx) + '˚';
                            }
                            return '';
                        })
                        .attr('class', function (d, i) {
                            return 'text-today';
                        })
                        .transition()
                        .duration(duration)
                        .attr('y', function (d) {
                            return y(d.value.t3h);
                        });
                }

                scope.$watch('timeChart', function (newVal) {
                    if (newVal) {
                        chart();
                    }
                });

                scope.$watch('shortForecast', function (newVal) {
                    if (newVal === true) {
                        chart();
                    }
                });
            }
        };
    })
    .directive('ngMidChart', function() {
        return {
            restrict: 'A',
            transclude: true,
            link: function (scope, iElement) {
                var duration = 1000;
                var margin = {top: 30, right: 0, bottom: 30, left: 0},
                    width = iElement[0].getBoundingClientRect().width - margin.left - margin.right,
                    height = iElement[0].getBoundingClientRect().height - margin.top - margin.bottom;

                var svg = d3.select(iElement[0]).append('svg')
                    .attr('width', width + margin.left + margin.right)
                    .attr('height', height + margin.top + margin.bottom)
                    .append('g')
                    .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

                var x = d3.scale.ordinal()
                    .rangeBands([0, width]);

                var y = d3.scale.linear()
                    .range([height, 0]);

                var chart = function () {
                    var data = scope.dayChart;

                    x.domain(d3.range(data[0].values.length));
                    y.domain([
                        d3.min(data[0].values, function (c) {
                            return c.tmn;
                        }),
                        d3.max(data[0].values, function (c) {
                            return c.tmx;
                        })
                    ]).nice();

                    var xAxis = d3.svg.axis()
                        .scale(x)
                        .orient('bottom');

                    var yAxis = d3.svg.axis()
                        .scale(y)
                        .orient('left');

                    // draw bar
                    var group = svg.selectAll('.bar-group')
                        .data(data);

                    group.enter()
                        .append('g')
                        .attr('class', 'bar-group')
                        .selectAll('rect')
                        .data(function (d) {
                            return d.values;
                        })
                        .enter().append('rect');

                    group.selectAll('rect')
                        .data(function (d) {
                            return d.values;
                        })
                        .attr('class', 'rect')
                        .attr('x', function (d, i) {
                            return x.rangeBand() * i + x.rangeBand() / 2 - 1;
                        })
                        .attr('width', 2)
                        .attr("y", function (d) {
                            return y(d.tmn);
                        })
                        .attr("height", 0)
                        .transition()
                        .duration(duration)
                        .attr('y', function (d) {
                            return y(d.tmx);
                        })
                        .attr('height', function (d) {
                            return y(d.tmn) - y(d.tmx);
                        });

                    // draw max value
                    var maxValue = svg.selectAll('.bar-max-value')
                        .data(data);

                    maxValue.enter()
                        .append('g')
                        .attr('class', 'bar-max-value')
                        .selectAll('text')
                        .data(function (d) {
                            return d.values;
                        })
                        .enter().append('text');

                    maxValue.selectAll('text')
                        .data(function (d) {
                            return d.values;
                        })
                        .attr('class', 'text')
                        .attr('x', function (d, i) {
                            return x.rangeBand() * i + x.rangeBand() / 2;
                        })
                        .attr('y', function (d) {
                            return y(d.tmn);
                        })
                        .attr('dy', -10)
                        .attr('text-anchor', 'middle')
                        .text(function (d) {
                            return d.tmx + '˚';
                        })
                        .attr('class', 'text-today')
                        .transition()
                        .duration(duration)
                        .attr('y', function (d) {
                            return y(d.tmx);
                        });

                    // draw min value
                    var minValue = svg.selectAll('.bar-min-value')
                        .data(data);

                    minValue.enter()
                        .append('g')
                        .attr('class', 'bar-min-value')
                        .selectAll('text')
                        .data(function (d) {
                            return d.values;
                        })
                        .enter().append('text');

                    minValue.selectAll('text')
                        .data(function (d) {
                            return d.values;
                        })
                        .attr('class', 'text')
                        .attr('x', function (d, i) {
                            return x.rangeBand() * i + x.rangeBand() / 2;
                        })
                        .attr('y', function (d) {
                            return y(d.tmn);
                        })
                        .attr('dy', 20)
                        .attr('text-anchor', 'middle')
                        .text(function (d) {
                            return d.tmn + '˚';
                        })
                        .attr('class', 'text-today');

                    // draw point
                    var circle = svg.selectAll('circle')
                        .data(data)
                        .enter().append('circle');

                    svg.selectAll('circle')
                        .data(data)
                        .attr('class', 'circle circle-today-current')
                        .attr('cx', function (d, i) {
                            for (var i = 0; i < d.values.length; i++) {
                                if (d.values[i].week === "오늘") {
                                    return x.rangeBand() * i + x.rangeBand() / 2;
                                }
                            }
                            return 0;
                        })
                        .attr('cy', function (d) {
                            return y(d.temp);
                        })
                        .attr('r', 0)
                        .transition()
                        .delay(duration)
                        .attr('r', 5);
                }

                scope.$watch('dayChart', function (newVal) {
                    if (newVal) {
                        chart();
                    }
                });

                scope.$watch('shortForecast', function (newVal) {
                    if (newVal === false) {
                        chart();
                    }
                });
            }
        };
    });<commit_msg>Fixed #272 탭 추가 이후에 차트가 크기 맞지 않는 이슈. * document가 rendering이 될 때 tabs가 있으면 ion-content에 has-tabs class가 추가됨 * has-tabs에 의해 ion-content의 영역이 tabs을 제외한 나머지 영역으로 설정되므로 그 후에 차트를 생성하도록 함<commit_after>// Ionic Starter App

// angular.module is a global place for creating, registering and retrieving Angular modules
// 'starter' is the name of this angular module example (also set in a <body> attribute in index.html)
// the 2nd parameter is an array of 'requires'
// 'starter.services' is found in services.js
// 'starter.controllers' is found in controllers.js
angular.module('starter', [
    'ionic',
    'ionic.service.core',
    'ionic.service.analytics',
    'starter.controllers',
    'starter.services',
    'ngCordova'
])
    .run(function($ionicPlatform, $ionicAnalytics) {
        $ionicPlatform.ready(function() {

            $ionicAnalytics.register();

            // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
            // for form inputs)
            if (window.cordova && window.cordova.plugins && window.cordova.plugins.Keyboard) {
                cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
                cordova.plugins.Keyboard.disableScroll(true);

            }
            if (window.StatusBar) {
                // org.apache.cordova.statusbar required
                //StatusBar.styleLightContent();
                StatusBar.hide();
                ionic.Platform.fullScreen();
            }
        });
    })

    .config(function($stateProvider, $urlRouterProvider, $ionicConfigProvider) {

        // Ionic uses AngularUI Router which uses the concept of states
        // Learn more here: https://github.com/angular-ui/ui-router
        // Set up the various states which the app can be in.
        // Each state's controller can be found in controllers.js
        $stateProvider

            // setup an abstract state for the tabs directive
            .state('tab', {
                url: '/tab',
                abstract: true,
                templateUrl: 'templates/tabs.html'
            })

            // Each tab has its own nav history stack:

            .state('tab.forecast', {
                url: '/forecast',
                views: {
                    'tab-forecast': {
                        templateUrl: 'templates/tab-forecast.html',
                        controller: 'ForecastCtrl'
                    }
                }
            })

            .state('tab.search', {
                url: '/search',
                views: {
                    'tab-search': {
                        templateUrl: 'templates/tab-search.html',
                        controller: 'SearchCtrl'
                    }
                }
            })
            .state('tab.chat-detail', {
                url: '/chats/:chatId',
                views: {
                    'tab-chats': {
                        templateUrl: 'templates/chat-detail.html',
                        controller: 'ChatDetailCtrl'
                    }
                }
            })

            .state('tab.account', {
                url: '/account',
                views: {
                    'tab-account': {
                        templateUrl: 'templates/tab-account.html',
                        controller: 'AccountCtrl'
                    }
                }
            });

        // if none of the above states are matched, use this as the fallback
        $urlRouterProvider.otherwise('/tab/forecast');

        $ionicConfigProvider.tabs.style('standard');
        $ionicConfigProvider.tabs.position('bottom');
    })
    .directive('ngShortChart', function() {
        return {
            restrict: 'A',
            transclude: true,
            link: function (scope, iElement) {
                var duration = 1000;
                var margin = {top: 30, right: 0, bottom: 10, left: 0};
                var width, height, x, y;
                var svg, initLine, line;

                // document가 rendering이 될 때 tabs가 있으면 ion-content에 has-tabs class가 추가됨
                // has-tabs에 의해 ion-content의 영역이 tabs을 제외한 나머지 영역으로 설정되므로 그 후에 차트를 생성하도록 함
                scope.$watch('$hasTabs', function(val) {
                    width = iElement[0].getBoundingClientRect().width - margin.left - margin.right;
                    height = iElement[0].getBoundingClientRect().height - margin.top - margin.bottom;
                    x = d3.scale.ordinal().rangeBands([width, 0]);
                    y = d3.scale.linear().range([height, 0]);

                    svg = d3.select(iElement[0]).append('svg')
                        .attr('width', width + margin.left + margin.right)
                        .attr('height', height + margin.top + margin.bottom)
                        .append('g')
                        .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

                    initLine = d3.svg.line()
                        .interpolate('linear')
                        .x(function (d, i) {
                            return x.rangeBand() * i + x.rangeBand() / 2;
                        })
                        .y(height);

                    line = d3.svg.line()
                        .interpolate('linear')
                        .x(function (d, i) {
                            return x.rangeBand() * i + x.rangeBand() / 2;
                        })
                        .y(function (d) {
                            return y(d.value.t3h);
                        });
                });

                var chart = function () {
                    var data = scope.timeChart;

                    x.domain(d3.range(data[0].values.length));
                    y.domain([
                        d3.min(data, function (c) {
                            return d3.min(c.values, function (v) {
                                return v.value.t3h;
                            });
                        }),
                        d3.max(data, function (c) {
                            return d3.max(c.values, function (v) {
                                return v.value.t3h;
                            });
                        })
                    ]).nice();

                    var xAxis = d3.svg.axis()
                        .scale(x)
                        .orient('bottom');

                    var yAxis = d3.svg.axis()
                        .scale(y)
                        .orient('left');

                    var group = svg.selectAll('.line-group')
                        .data(data);

                    var group_enter = group.enter()
                        .append('g')
                        .attr('class', 'line-group');

                    // draw line
                    group_enter.append('path')
                        .attr('class', function (d) {
                            return 'line line-' + d.name;
                        })
                        .attr('d', function (d) {
                            return line(d.values);
                        });

                    group.select('.line')
                        .attr('d', function (d) {
                            return initLine(d.values);
                        })
                        .transition()
                        .duration(duration)
                        .attr('d', function (d) {
                            return line(d.values);
                        });

                    // draw point
                    group_enter.append('g')
                        .attr('class', 'line-point')
                        .selectAll('circle')
                        .data(function (d) {
                            return d.values;
                        })
                        .enter().append('circle');

                    group.select('.line-point')
                        .selectAll('circle')
                        .data(function (d) {
                            return d.values;
                        })
                        .attr('cx', function (d, i) {
                            return x.rangeBand() * i + x.rangeBand() / 2;
                        })
                        .attr('cy', height)
                        .attr('r', function (d, i) {
                            if (i === 8) {
                                return 5;
                            }
                            return 2;
                        })
                        .attr('class', function (d, i) {
                            if (i === 8) {
                                return 'circle-' + d.name + '-current';
                            }
                            return 'circle-' + d.name;
                        })
                        .transition()
                        .duration(duration)
                        .attr('cy', function (d) {
                            return y(d.value.t3h);
                        });

                    // draw value
                    group_enter.append('g')
                        .attr('class', 'line-value')
                        .selectAll('text')
                        .data(function (d) {
                            return d.values;
                        })
                        .enter().append('text');

                    group.select('.line-value')
                        .selectAll('text')
                        .data(function (d) {
                            return d.values;
                        })
                        .attr('x', function (d, i) {
                            return x.rangeBand() * i + x.rangeBand() / 2;
                        })
                        .attr('y', height)
                        .attr('dy', -10)
                        .attr('text-anchor', 'middle')
                        .text(function (d) {
                            if (d.name === 'today' && (d.value.tmn !== undefined || d.value.tmx !== undefined)) {
                                return (d.value.tmn | d.value.tmx) + '˚';
                            }
                            return '';
                        })
                        .attr('class', function (d, i) {
                            return 'text-today';
                        })
                        .transition()
                        .duration(duration)
                        .attr('y', function (d) {
                            return y(d.value.t3h);
                        });
                }

                scope.$watch('timeChart', function (newVal) {
                    if (newVal) {
                        chart();
                    }
                });

                scope.$watch('shortForecast', function (newVal) {
                    if (newVal === true) {
                        chart();
                    }
                });
            }
        };
    })
    .directive('ngMidChart', function() {
        return {
            restrict: 'A',
            transclude: true,
            link: function (scope, iElement) {
                var duration = 1000;
                var margin = {top: 30, right: 0, bottom: 30, left: 0},
                    width = iElement[0].getBoundingClientRect().width - margin.left - margin.right,
                    height = iElement[0].getBoundingClientRect().height - margin.top - margin.bottom;

                var svg = d3.select(iElement[0]).append('svg')
                    .attr('width', width + margin.left + margin.right)
                    .attr('height', height + margin.top + margin.bottom)
                    .append('g')
                    .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

                var x = d3.scale.ordinal()
                    .rangeBands([0, width]);

                var y = d3.scale.linear()
                    .range([height, 0]);

                var chart = function () {
                    var data = scope.dayChart;

                    x.domain(d3.range(data[0].values.length));
                    y.domain([
                        d3.min(data[0].values, function (c) {
                            return c.tmn;
                        }),
                        d3.max(data[0].values, function (c) {
                            return c.tmx;
                        })
                    ]).nice();

                    var xAxis = d3.svg.axis()
                        .scale(x)
                        .orient('bottom');

                    var yAxis = d3.svg.axis()
                        .scale(y)
                        .orient('left');

                    // draw bar
                    var group = svg.selectAll('.bar-group')
                        .data(data);

                    group.enter()
                        .append('g')
                        .attr('class', 'bar-group')
                        .selectAll('rect')
                        .data(function (d) {
                            return d.values;
                        })
                        .enter().append('rect');

                    group.selectAll('rect')
                        .data(function (d) {
                            return d.values;
                        })
                        .attr('class', 'rect')
                        .attr('x', function (d, i) {
                            return x.rangeBand() * i + x.rangeBand() / 2 - 1;
                        })
                        .attr('width', 2)
                        .attr("y", function (d) {
                            return y(d.tmn);
                        })
                        .attr("height", 0)
                        .transition()
                        .duration(duration)
                        .attr('y', function (d) {
                            return y(d.tmx);
                        })
                        .attr('height', function (d) {
                            return y(d.tmn) - y(d.tmx);
                        });

                    // draw max value
                    var maxValue = svg.selectAll('.bar-max-value')
                        .data(data);

                    maxValue.enter()
                        .append('g')
                        .attr('class', 'bar-max-value')
                        .selectAll('text')
                        .data(function (d) {
                            return d.values;
                        })
                        .enter().append('text');

                    maxValue.selectAll('text')
                        .data(function (d) {
                            return d.values;
                        })
                        .attr('class', 'text')
                        .attr('x', function (d, i) {
                            return x.rangeBand() * i + x.rangeBand() / 2;
                        })
                        .attr('y', function (d) {
                            return y(d.tmn);
                        })
                        .attr('dy', -10)
                        .attr('text-anchor', 'middle')
                        .text(function (d) {
                            return d.tmx + '˚';
                        })
                        .attr('class', 'text-today')
                        .transition()
                        .duration(duration)
                        .attr('y', function (d) {
                            return y(d.tmx);
                        });

                    // draw min value
                    var minValue = svg.selectAll('.bar-min-value')
                        .data(data);

                    minValue.enter()
                        .append('g')
                        .attr('class', 'bar-min-value')
                        .selectAll('text')
                        .data(function (d) {
                            return d.values;
                        })
                        .enter().append('text');

                    minValue.selectAll('text')
                        .data(function (d) {
                            return d.values;
                        })
                        .attr('class', 'text')
                        .attr('x', function (d, i) {
                            return x.rangeBand() * i + x.rangeBand() / 2;
                        })
                        .attr('y', function (d) {
                            return y(d.tmn);
                        })
                        .attr('dy', 20)
                        .attr('text-anchor', 'middle')
                        .text(function (d) {
                            return d.tmn + '˚';
                        })
                        .attr('class', 'text-today');

                    // draw point
                    var circle = svg.selectAll('circle')
                        .data(data)
                        .enter().append('circle');

                    svg.selectAll('circle')
                        .data(data)
                        .attr('class', 'circle circle-today-current')
                        .attr('cx', function (d, i) {
                            for (var i = 0; i < d.values.length; i++) {
                                if (d.values[i].week === "오늘") {
                                    return x.rangeBand() * i + x.rangeBand() / 2;
                                }
                            }
                            return 0;
                        })
                        .attr('cy', function (d) {
                            return y(d.temp);
                        })
                        .attr('r', 0)
                        .transition()
                        .delay(duration)
                        .attr('r', 5);
                }

                scope.$watch('dayChart', function (newVal) {
                    if (newVal) {
                        chart();
                    }
                });

                scope.$watch('shortForecast', function (newVal) {
                    if (newVal === false) {
                        chart();
                    }
                });
            }
        };
    });<|endoftext|>