<commit_before>/**
 * Decorators for basic CI, injection and decoration.
 * @module core
 * @requires lodash
 * @requires json-stable-stringify
 * @requires jsonschema
 */

'use strict'
const debug = require('debug')('primavera:core')
import _ from 'lodash'
import stringify from 'json-stable-stringify'
import Container from './container'

/**
 * Register a component within the in-memory container for use with @Inject.
 * It can only be used on a class-level.
 *
 * @param {string} alias alias given to the component to identify within the in-memory container.
 * @name @Injectable
 * @static
 * @public
 * @example
 * import { Injectable, Inject } from 'primavera/core'
 *
 * \@Injectable('services/injectable')
 * class InjectableComponent { ... }
 *
 * class DepentantComponent {
 *     \@Inject('services/injectable')
 *     get injectable() {}
 * }
 */
export function Injectable(alias) {

    return function(target, name, descriptor) {
        if (!target) throw new Error(`@Injectable can only be set on a class.`)
        debug(`@Injectable ${target.name} stored in container as ${alias}`)

        Container.set(alias || target, target)

        return target
    }
}

/**
 * Alias for \@Injectable
 *
 * @name @Component
 * @function
 * @static
 * @public
 * @see Injectable
 */
export const Component  = Injectable

/**
 * Apply one or more decorators BEFORE a method is executed.
 * Decorators can transform the original arguments accessing the _arguments_ of the function,
 * or by providing an array as return value which will override the entire _arguments chain_
 * to be passed to the next intercepting function or the target method.
 *
 *
 * @name @Before
 * @param {...function} fns functions to be executed before the target method
 * @function
 * @static
 *
 * @example
 * \@Before(() => debug('do nothing'))
 * function somefunction() { ... }
 *
 * @example
 * \@Before((...args) => { return ['altered']})
 * testfunction(...args) {
 *     // args[0] == 'altered'
 * }
 *
 * @example
 * \@Before((...args) => {
 *     const altered = [...args]
 *     altered[0] = 'altered'
 * })
 * testfunction(...args) {
 *     // args[0] == 'altered'
 * }
 */
export function Before(...fns) {
    return function(target, name, descriptor) {
        const previous = descriptor.value
        descriptor.value = async function(...args) {
            let responseChain
            for (let fn of fns) {
                if (typeof fn == 'string' && fn.startsWith('$this')) {
                    debug(`@Before is fetching instance function from ${fn} with this`, this)
                    fn = _.get(this, fn.substring(fn.indexOf('.')))
                }
                debug(`@Before ${target.constructor.name}.${name}, executing ${fn.name}`)
                responseChain = await fn.apply(this, args)
                if (responseChain && !Array.isArray(responseChain)) responseChain = [responseChain]
            }
            return await (previous).apply(this, responseChain || args)
        }

        return descriptor
    }
}

/**
 * Alias for \@Before
 *
 * @name @Validate
 * @function
 * @static
 * @public
 * @see Before
 */
export const Validate = Before

/**
 * Apply one or more functions AFTER a method is executed.
 * Decorators can transform or choose to return a different return value by just returning something
 * or by altering the attributes of a returned object.
 *
 * Each function will receive as only parameter the return value of the previous one.
 * If no value is returned, the previous non-undefined return value will be kept.
 *
 * @function
 * @static
 * @name @After
 * @param {...function} fns functions to be executed before the target method
 *
 * @example
 * \@After(() => debug('do nothing'))
 * function testfunction() { return 'original' }
 * testfunction() == 'original'
 *
 * @example
 * \@After((value) => { return 'altered'})
 * testfunction() { return 'original' }
 * testfunction() == 'altered'
 *
 * @example
 * \@After((obj) => {
 *     obj.altered = true
 * })
 * testfunction() { return {altered:false} }
 * testfunction().altered == true
 */
export function After(...fns) {
    return function(target, name, descriptor) {
        const previous = descriptor.value;
        descriptor.value = async function(...args) {
            let response, current
            response = current = await previous.apply(this, args)

            for (let fn of fns) {
                debug(`@After ${target.constructor.name}.${name}, executing ${fn.name || 'anonymous function'}`)
                current = await fn.apply(this, [response])
                if (current != undefined) {//:
                    debug(`@After ${target.constructor.name}.${name}, ${fn.name || 'anonymous function'} returned `, current)
                    response = current
                }
            }
            return response
        }

        return descriptor
    }
}
/**
 * Alias for Before used for syntactic sugar.
 *
 * @function
 * @static
 * @name @Project
 * @see Before
 */
export const Project = After

/**
 * Smart pooling describes the ability to keep a pool of "Singletons"
 * based on their instantiation parameters.
 *
 * This helps optimize creation/destruction of objects in the container,
 * reducing memory footpring as well as saving resources by reducing
 * object creation operations.
 *
 * To be used toogether with @Injectable or one of its aliases.
 *
 * @function
 * @static
 * @name @SmartPooling
 *
 * @example
 * \@SmartPooling()
 * class PooledClass {
 *     constructor() {
 *         this.random = Math.random()
 *     }
 * }
 *
 * ...
 * let base = new PooledClass({ value: 1 })
 * let same = new PooledClass({ value: 1 })
 * let different = new PooledClass({ value: 2 })
 *
 * base.random == same.random
 * base.random == different.random
 */
export function SmartPooling() {
    const instances = {}
    return function (target, name, descriptor) {
        if (!target || descriptor) throw new Error("@SmartPooling can only be set on a class.")

        const SmartPoolingProxy = function(...args) {
            const hash = args.length > 0 ? stringify(args) : 0
            const UUID = `${target}_${hash}`
            if (!instances[UUID])  //:
                instances[UUID] = new target(...args)

            debug(`@SmartPooling instance of ${target.name} ${hash}`, instances[UUID])

            return instances[UUID]

        }

        Object.setPrototypeOf(SmartPoolingProxy, target)

        return SmartPoolingProxy

    }
}
/**
 * Alias of @SmartPooling
 *
 * @function
 * @static
 * @name @Singleton
 * @see SmartPooling
 */
export const Singleton = SmartPooling

/**
 * Inject a contained instance into a given component.
 * If the dependency can't be found, it will create a SmartProxy
 * to delay instantiation until the dependency is first accessed (lazy initialization).
 *
 * @function
 * @static
 * @name @Inject
 * @param {string|object} [configuration] is the alias of the component within the in-memory container
 * @param {object} initializationArguments arguments to initialize the instance of the component (if required or desired)
 */
export function Inject(config, args) {
    if (args && !Array.isArray(args)) args = [args]
    const alias = (config && config.path) || config || false
    return function(target, name, descriptor) {
        if (!target || !name || !descriptor) throw new Error("@Inject can only be used on an attribute level.")

        const _fn = descriptor.get
        function wrapper() {
            const _clazz = Container.get(alias || name)
            debug(`@Inject dependency in ${target.constructor.name}.${name} (${_clazz.name})`)
            return new _clazz(...(args || []))
        }
        descriptor.get = wrapper
        return descriptor
    }
}

/**
 * Bind a given object element to the decorated method.
 *
 * @function
 * @static
 * @name @Bind
 * @param {...string} fixtures the fixtures to be applied. It can reference attributes or methods on the instance level.
 *
 * @example
 * \@Bind('myAttribute')
 * testfunction (value) {
 *     value == this.myAttribute
 * }
 *
 * @example
 * \@Bind('nested.attribute')
 * testfunction (value) {
 *     value == this.nested.attribute
 * }
 *
 * @example
 * \@Bind('someFunction')
 * testfunction (value) {
 *     value == this.someFunction()
 * }
 *
 * @example
 * \@Bind('someFunction().nestedAttribute')
 * testfunction (value) {
 *     value == this.someFunction().nestedAttribute
 * }
 *
 *
 */
export function Bind(...fixtures) {
    return function (target, name, descriptor) {
        debug(`@Bind ${fixtures} on ${target.constructor.name}::${name}`)

        const _fn = descriptor.value

        descriptor.value = async function(...args) {
            const bundle = []
            for (let fixture of fixtures) {
                let bound = (fixture instanceof Function) ? fixture : false

                if (!bound) {
                    const self = this
                    if (/\(\)/.test(fixture))
                        bound = _.result(self, fixture.replace('()',''))
                    else {
                        bound = _.get(self, fixture)
                    }
                }

                if (bound instanceof Function) {
                    const self = this
                    const _bound = bound
                    // Force "this" binding in fixture (.bind doesn't really bind for some reason)
                    bound = async function(...args) {
                        return await _bound.apply(self, args)
                    }
                }

                bundle.push(bound)
            }

            const params = [...bundle, ...args]
            debug(`@Bind is invoking the source function ${_fn.name} with params`, params)
            return await _fn.apply(this, params)
        }

        return descriptor
    }
}

/**
 * Indicates the potential sources of properties/values in priority order.
 * To be used together with @Property
 *
 * @PropertySources can take any object (and will inspect it) or _resolver function_.
 * It will iterate through such sources until the requested @Property is resolved.
 *
 * @function
 * @static
 * @name @PropertySources
 * @param {object|function} sources the sources in prioritized order
 */
export function PropertySources (...sources) {
    return function (target, name, descriptor) {
        debug("@PropertySources - target is descriptor? ", !!descriptor)
        target = descriptor || target

        if (!target.$$propertySources) //:
            target.$$propertySources = []

        for (let source of sources) { // push all sources into property sources list.
            target.$$propertySources.push(source)
        }

        return descriptor || target
    }
}

/**
 * Fetch a value from one of the @PropertySources declared on the class or method level.
 *
 * @function
 * @static
 * @name @Property
 * @param {string} path Path of the property within the source objects (or used)
 * @param {object|string|function} default value if property is not found within the sources
 *
 */
export function Property(path, dflt) {
    return function (target, name, descriptor) {

        /*
         * Internal implementation to get property from different source types.
         */
        function fetchPropertyBySourceType(path, source) {
            let value
            debug(`Trying to get property ${path} from ${typeof source} source: `, source)
            if (typeof source === "object") {//:
                value = _.get(source, path)
            }
            else if (typeof source === "function") //:
                value = source(path)

            return value
        }

        const isFunction = !!descriptor.value
        const overwrite = isFunction ? 'value' : 'get'
        const _fn = descriptor[overwrite]
        descriptor[overwrite] = function() {
            let value, source, container

            // checked all preferred sources already, check additional
            // method/property configured s ources or otherwise class-level
            let instance = target && target.constructor || target
            debug(`@Property sources for ${path}: `, descriptor, instance)
            for (container of [descriptor,instance]) {
                if (container && container.$$propertySources) {
                    for (source of container.$$propertySources) {
                        if (source === false) return dflt

                        value = fetchPropertyBySourceType(path, source)
                        if (value !== undefined) return value
                    }
                }
            }

            debug(`@Property couldn't find ${path} in sources, using default value: `, dflt)

            return dflt
        }

        return descriptor
    }
}

/**
 * @Self allows to declare the context in which a function should be executed.
 * It basically invokes fn.apply() with an instance of the indicated class.
 *
 * @function
 * @static
 * @name @Self
 * @param {class} clazz the class to be used
 */
export function Self(clazz) {
    return function(target, name, descriptor) {
        if (!descriptor) throw new Error(`@Self can only be used in class method definitions.`)

        if (typeof clazz == 'string') {
            let clazzname = clazz
            clazz = function () {
                const clazzfn = eval(clazz)
                return new clazzfn
            }
            clazz.$$name = clazzname
        }

        const fn = descriptor.value || descriptor.get

        descriptor.value = function(...args) {
            let self = this
            if (!this || !(this instanceof clazz)) {
                try {
                    debug(`@Self is trying to initialize ${clazz.$$name || clazz.constructor.name} to execute ${fn.name}`)
                    self = clazz
                }
                catch (err) {
                    throw new Error(`Error trying to initialize ${clazz.constructor.name}`, err)
                }
            }

            return fn.apply(self, args)
        }

        return descriptor
    }
}<commit_msg>Finished basic docs for the entire thing.<commit_after>/**
 * Decorators for basic CI, injection and decoration.
 * @module core
 * @requires lodash
 * @requires json-stable-stringify
 * @requires jsonschema
 */

'use strict'
const debug = require('debug')('primavera:core')
import _ from 'lodash'
import stringify from 'json-stable-stringify'
import Container from './container'

/**
 * Register a component within the in-memory container for use with @Inject.
 * It can only be used on a class-level.
 *
 * @param {string} alias alias given to the component to identify within the in-memory container.
 * @name @Injectable
 * @function
 * @static
 * @example
 * import { Injectable, Inject } from 'primavera/core'
 *
 * \@Injectable('services/injectable')
 * class InjectableComponent { ... }
 *
 * class DepentantComponent {
 *     \@Inject('services/injectable')
 *     get injectable() {}
 * }
 */
export function Injectable(alias) {

    return function(target, name, descriptor) {
        if (!target) throw new Error(`@Injectable can only be set on a class.`)
        debug(`@Injectable ${target.name} stored in container as ${alias}`)

        Container.set(alias || target, target)

        return target
    }
}

/**
 * Alias for \@Injectable
 *
 * @name @Component
 * @function
 * @static
 * @public
 * @see Injectable
 */
export const Component  = Injectable

/**
 * Apply one or more decorators BEFORE a method is executed.
 * Decorators can transform the original arguments accessing the _arguments_ of the function,
 * or by providing an array as return value which will override the entire _arguments chain_
 * to be passed to the next intercepting function or the target method.
 *
 *
 * @name @Before
 * @param {...function} fns functions to be executed before the target method
 * @function
 * @static
 *
 * @example
 * \@Before(() => debug('do nothing'))
 * function somefunction() { ... }
 *
 * @example
 * \@Before((...args) => { return ['altered']})
 * testfunction(...args) {
 *     // args[0] == 'altered'
 * }
 *
 * @example
 * \@Before((...args) => {
 *     const altered = [...args]
 *     altered[0] = 'altered'
 * })
 * testfunction(...args) {
 *     // args[0] == 'altered'
 * }
 */
export function Before(...fns) {
    return function(target, name, descriptor) {
        const previous = descriptor.value
        descriptor.value = async function(...args) {
            let responseChain
            for (let fn of fns) {
                if (typeof fn == 'string' && fn.startsWith('$this')) {
                    debug(`@Before is fetching instance function from ${fn} with this`, this)
                    fn = _.get(this, fn.substring(fn.indexOf('.')))
                }
                debug(`@Before ${target.constructor.name}.${name}, executing ${fn.name}`)
                responseChain = await fn.apply(this, args)
                if (responseChain && !Array.isArray(responseChain)) responseChain = [responseChain]
            }
            return await (previous).apply(this, responseChain || args)
        }

        return descriptor
    }
}

/**
 * Alias for \@Before
 *
 * @name @Validate
 * @function
 * @static
 * @public
 * @see Before
 */
export const Validate = Before

/**
 * Apply one or more functions AFTER a method is executed.
 * Decorators can transform or choose to return a different return value by just returning something
 * or by altering the attributes of a returned object.
 *
 * Each function will receive as only parameter the return value of the previous one.
 * If no value is returned, the previous non-undefined return value will be kept.
 *
 * @function
 * @static
 * @name @After
 * @param {...function} fns functions to be executed before the target method
 *
 * @example
 * \@After(() => debug('do nothing'))
 * function testfunction() { return 'original' }
 * testfunction() == 'original'
 *
 * @example
 * \@After((value) => { return 'altered'})
 * testfunction() { return 'original' }
 * testfunction() == 'altered'
 *
 * @example
 * \@After((obj) => {
 *     obj.altered = true
 * })
 * testfunction() { return {altered:false} }
 * testfunction().altered == true
 */
export function After(...fns) {
    return function(target, name, descriptor) {
        const previous = descriptor.value;
        descriptor.value = async function(...args) {
            let response, current
            response = current = await previous.apply(this, args)

            for (let fn of fns) {
                debug(`@After ${target.constructor.name}.${name}, executing ${fn.name || 'anonymous function'}`)
                current = await fn.apply(this, [response])
                if (current != undefined) {//:
                    debug(`@After ${target.constructor.name}.${name}, ${fn.name || 'anonymous function'} returned `, current)
                    response = current
                }
            }
            return response
        }

        return descriptor
    }
}
/**
 * Alias for Before used for syntactic sugar.
 *
 * @function
 * @static
 * @name @Project
 * @see Before
 */
export const Project = After

/**
 * Smart pooling describes the ability to keep a pool of "Singletons"
 * based on their instantiation parameters.
 *
 * This helps optimize creation/destruction of objects in the container,
 * reducing memory footpring as well as saving resources by reducing
 * object creation operations.
 *
 * To be used toogether with @Injectable or one of its aliases.
 *
 * @function
 * @static
 * @name @SmartPooling
 *
 * @example
 * \@SmartPooling()
 * class PooledClass {
 *     constructor() {
 *         this.random = Math.random()
 *     }
 * }
 *
 * ...
 * let base = new PooledClass({ value: 1 })
 * let same = new PooledClass({ value: 1 })
 * let different = new PooledClass({ value: 2 })
 *
 * base.random == same.random
 * base.random == different.random
 */
export function SmartPooling() {
    const instances = {}
    return function (target, name, descriptor) {
        if (!target || descriptor) throw new Error("@SmartPooling can only be set on a class.")

        const SmartPoolingProxy = function(...args) {
            const hash = args.length > 0 ? stringify(args) : 0
            const UUID = `${target}_${hash}`
            if (!instances[UUID])  //:
                instances[UUID] = new target(...args)

            debug(`@SmartPooling instance of ${target.name} ${hash}`, instances[UUID])

            return instances[UUID]

        }

        Object.setPrototypeOf(SmartPoolingProxy, target)

        return SmartPoolingProxy

    }
}
/**
 * Alias of @SmartPooling
 *
 * @function
 * @static
 * @name @Singleton
 * @see SmartPooling
 */
export const Singleton = SmartPooling

/**
 * Inject a contained instance into a given component.
 * If the dependency can't be found, it will create a SmartProxy
 * to delay instantiation until the dependency is first accessed (lazy initialization).
 *
 * @function
 * @static
 * @name @Inject
 * @param {string|object} [configuration] is the alias of the component within the in-memory container
 * @param {object} initializationArguments arguments to initialize the instance of the component (if required or desired)
 */
export function Inject(config, args) {
    if (args && !Array.isArray(args)) args = [args]
    const alias = (config && config.path) || config || false
    return function(target, name, descriptor) {
        if (!target || !name || !descriptor) throw new Error("@Inject can only be used on an attribute level.")

        const _fn = descriptor.get
        function wrapper() {
            const _clazz = Container.get(alias || name)
            debug(`@Inject dependency in ${target.constructor.name}.${name} (${_clazz.name})`)
            return new _clazz(...(args || []))
        }
        descriptor.get = wrapper
        return descriptor
    }
}

/**
 * Bind a given object element to the decorated method.
 *
 * @function
 * @static
 * @name @Bind
 * @param {...string} fixtures the fixtures to be applied. It can reference attributes or methods on the instance level.
 *
 * @example
 * \@Bind('myAttribute')
 * testfunction (value) {
 *     value == this.myAttribute
 * }
 *
 * @example
 * \@Bind('nested.attribute')
 * testfunction (value) {
 *     value == this.nested.attribute
 * }
 *
 * @example
 * \@Bind('someFunction')
 * testfunction (value) {
 *     value == this.someFunction()
 * }
 *
 * @example
 * \@Bind('someFunction().nestedAttribute')
 * testfunction (value) {
 *     value == this.someFunction().nestedAttribute
 * }
 *
 *
 */
export function Bind(...fixtures) {
    return function (target, name, descriptor) {
        debug(`@Bind ${fixtures} on ${target.constructor.name}::${name}`)

        const _fn = descriptor.value

        descriptor.value = async function(...args) {
            const bundle = []
            for (let fixture of fixtures) {
                let bound = (fixture instanceof Function) ? fixture : false

                if (!bound) {
                    const self = this
                    if (/\(\)/.test(fixture))
                        bound = _.result(self, fixture.replace('()',''))
                    else {
                        bound = _.get(self, fixture)
                    }
                }

                if (bound instanceof Function) {
                    const self = this
                    const _bound = bound
                    // Force "this" binding in fixture (.bind doesn't really bind for some reason)
                    bound = async function(...args) {
                        return await _bound.apply(self, args)
                    }
                }

                bundle.push(bound)
            }

            const params = [...bundle, ...args]
            debug(`@Bind is invoking the source function ${_fn.name} with params`, params)
            return await _fn.apply(this, params)
        }

        return descriptor
    }
}

/**
 * Indicates the potential sources of properties/values in priority order.
 * To be used together with @Property
 *
 * @PropertySources can take any object (and will inspect it) or _resolver function_.
 * It will iterate through such sources until the requested @Property is resolved.
 *
 * @function
 * @static
 * @name @PropertySources
 * @param {object|function} sources the sources in prioritized order
 */
export function PropertySources (...sources) {
    return function (target, name, descriptor) {
        debug("@PropertySources - target is descriptor? ", !!descriptor)
        target = descriptor || target

        if (!target.$$propertySources) //:
            target.$$propertySources = []

        for (let source of sources) { // push all sources into property sources list.
            target.$$propertySources.push(source)
        }

        return descriptor || target
    }
}

/**
 * Fetch a value from one of the @PropertySources declared on the class or method level.
 *
 * @function
 * @static
 * @name @Property
 * @param {string} path Path of the property within the source objects (or used)
 * @param {object|string|function} default value if property is not found within the sources
 *
 */
export function Property(path, dflt) {
    return function (target, name, descriptor) {

        /*
         * Internal implementation to get property from different source types.
         */
        function fetchPropertyBySourceType(path, source) {
            let value
            debug(`Trying to get property ${path} from ${typeof source} source: `, source)
            if (typeof source === "object") {//:
                value = _.get(source, path)
            }
            else if (typeof source === "function") //:
                value = source(path)

            return value
        }

        const isFunction = !!descriptor.value
        const overwrite = isFunction ? 'value' : 'get'
        const _fn = descriptor[overwrite]
        descriptor[overwrite] = function() {
            let value, source, container

            // checked all preferred sources already, check additional
            // method/property configured s ources or otherwise class-level
            let instance = target && target.constructor || target
            debug(`@Property sources for ${path}: `, descriptor, instance)
            for (container of [descriptor,instance]) {
                if (container && container.$$propertySources) {
                    for (source of container.$$propertySources) {
                        if (source === false) return dflt

                        value = fetchPropertyBySourceType(path, source)
                        if (value !== undefined) return value
                    }
                }
            }

            debug(`@Property couldn't find ${path} in sources, using default value: `, dflt)

            return dflt
        }

        return descriptor
    }
}

/**
 * @Self allows to declare the context in which a function should be executed.
 * It basically invokes fn.apply() with an instance of the indicated class.
 *
 * @function
 * @static
 * @name @Self
 * @param {class} clazz the class to be used
 */
export function Self(clazz) {
    return function(target, name, descriptor) {
        if (!descriptor) throw new Error(`@Self can only be used in class method definitions.`)

        if (typeof clazz == 'string') {
            let clazzname = clazz
            clazz = function () {
                const clazzfn = eval(clazz)
                return new clazzfn
            }
            clazz.$$name = clazzname
        }

        const fn = descriptor.value || descriptor.get

        descriptor.value = function(...args) {
            let self = this
            if (!this || !(this instanceof clazz)) {
                try {
                    debug(`@Self is trying to initialize ${clazz.$$name || clazz.constructor.name} to execute ${fn.name}`)
                    self = clazz
                }
                catch (err) {
                    throw new Error(`Error trying to initialize ${clazz.constructor.name}`, err)
                }
            }

            return fn.apply(self, args)
        }

        return descriptor
    }
}<|endoftext|>


<commit_before>"use strict";

var TILE_SIZE = 256;

function radiansToDegrees(rad) {
  return rad / (Math.PI / 180);
}

function fromPointToLatLng(point) {
  // value from 0 to 256
  var pixelOrigin_ = new google.maps.Point(TILE_SIZE / 2,
                                           TILE_SIZE / 2);
  var origin = new google.maps.Point(TILE_SIZE/2, TILE_SIZE/2);

  var pixelsPerLonDegree_ = TILE_SIZE / 360;
  var pixelsPerLonRadian_ = TILE_SIZE / (2 * Math.PI);

  var origin = pixelOrigin_;
  var lng = (point.x - origin.x) / pixelsPerLonDegree_;
  var latRadians = (point.y - origin.y) / -pixelsPerLonRadian_;
  var lat = radiansToDegrees(2 * Math.atan(Math.exp(latRadians)) -
                             Math.PI / 2);
  return new google.maps.LatLng(lat, lng);
};

function midpointLat(map) {
  var tileFactor = 1 << map.getZoom();
  var divHeight = document.getElementById("map_canvas_1").clientHeight;
  var midpointFromTop = divHeight / tileFactor / 2;
  return fromPointToLatLng(new google.maps.Point(0, midpointFromTop)).lat();
}

function getAllowedBounds(map) {
  var bounds = map.getBounds();

  var sw = bounds.getSouthWest();
  var ne = bounds.getNorthEast();

  var swLng = sw.lng();
  var swLat = sw.lat();

  var neLng = ne.lng();
  var neLat = ne.lat();

  if (swLng >= neLng) {
    swLng -= 360;
  }
  var width = neLng - swLng;

  var left = Math.min(-179+(width/2),-0.000001);
  var right = Math.max(179-(width/2),0.000001);

  var divHeight = document.getElementById("map_canvas_1").clientHeight;

  var divCenterLat = fromPointToLatLng(new google.maps.Point(0, divHeight)).lat();
  var currentZoom = map.getZoom();

  var top = midpointLat(map);
  var bottom = -midpointLat(map);

  var allowedBounds = new google.maps.LatLngBounds(
    new google.maps.LatLng(bottom,left),
    new google.maps.LatLng(top,right));

  return allowedBounds;
}

function boxIn(map, allowedBounds) {
  if (allowedBounds.contains(map.getCenter())) {
    return;
  } else {
    var mapCenter = map.getCenter();
    var X = mapCenter.lng();
    var Y = mapCenter.lat();

    var AmaxX = allowedBounds.getNorthEast().lng();
    var AmaxY = allowedBounds.getNorthEast().lat();
    var AminX = allowedBounds.getSouthWest().lng();
    var AminY = allowedBounds.getSouthWest().lat();

    if (X < AminX) {
      X = AminX;
    }
    if (X > AmaxX) {
      X = AmaxX;
    }
    if (Y < AminY) {
      Y = AminY;
    }
    if (Y > AmaxY) {
      Y = AmaxY;
    }

    map.panTo(new google.maps.LatLng(Y, X));
  }
}
<commit_msg>extend map movement beyond sidebar's cover<commit_after>"use strict";

var TILE_SIZE = 256;

function radiansToDegrees(rad) {
  return rad / (Math.PI / 180);
}

function fromPointToLatLng(point) {
  // value from 0 to 256
  var pixelOrigin_ = new google.maps.Point(TILE_SIZE / 2,
                                           TILE_SIZE / 2);
  var origin = new google.maps.Point(TILE_SIZE/2, TILE_SIZE/2);

  var pixelsPerLonDegree_ = TILE_SIZE / 360;
  var pixelsPerLonRadian_ = TILE_SIZE / (2 * Math.PI);

  var origin = pixelOrigin_;
  var lng = (point.x - origin.x) / pixelsPerLonDegree_;
  var latRadians = (point.y - origin.y) / -pixelsPerLonRadian_;
  var lat = radiansToDegrees(2 * Math.atan(Math.exp(latRadians)) -
                             Math.PI / 2);
  return new google.maps.LatLng(lat, lng);
};

function midpointLat(map) {
  var tileFactor = 1 << map.getZoom();
  var divHeight = document.getElementById("map_canvas_1").clientHeight;
  var midpointFromTop = divHeight / tileFactor / 2;
  return fromPointToLatLng(new google.maps.Point(0, midpointFromTop)).lat();
}

function getAllowedBounds(map) {
  var bounds = map.getBounds();

  var sw = bounds.getSouthWest();
  var ne = bounds.getNorthEast();

  var swLng = sw.lng();
  var swLat = sw.lat();

  var neLng = ne.lng();
  var neLat = ne.lat();

  if (swLng >= neLng) {
    swLng -= 360;
  }
  var width = neLng - swLng;

  // var left = Math.min(-179+(width/2),-0.000001);
  // var right = Math.max(179-(width/2),0.000001);

  var left = Math.min(-179+(width/2),-0.000001);
//  var right = Math.max(179-(width/4),0.000001);
  var right = 179;

  var divHeight = document.getElementById("map_canvas_1").clientHeight;

  var divCenterLat = fromPointToLatLng(new google.maps.Point(0, divHeight)).lat();
  var currentZoom = map.getZoom();

  var top = midpointLat(map);
  var bottom = -midpointLat(map);

  var allowedBounds = new google.maps.LatLngBounds(
    new google.maps.LatLng(bottom,left),
    new google.maps.LatLng(top,right));

  return allowedBounds;
}

function boxIn(map, allowedBounds) {
  if (allowedBounds.contains(map.getCenter())) {
    return;
  } else {
    var mapCenter = map.getCenter();
    var X = mapCenter.lng();
    var Y = mapCenter.lat();

    var AmaxX = allowedBounds.getNorthEast().lng();
    var AmaxY = allowedBounds.getNorthEast().lat();
    var AminX = allowedBounds.getSouthWest().lng();
    var AminY = allowedBounds.getSouthWest().lat();

    if (X < AminX) {
      X = AminX;
    }
    if (X > AmaxX) {
      X = AmaxX;
    }
    if (Y < AminY) {
      Y = AminY;
    }
    if (Y > AmaxY) {
      Y = AmaxY;
    }

    map.panTo(new google.maps.LatLng(Y, X));
  }
}
<|endoftext|>


<commit_before>/**
 * SurveyQuestionMultiSelect component.
 *
 * Site Kit by Google, Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

const SurveyQuestionMultiSelect = () => {
	return (

			SurveyQuestionMultiSelect

	);
};

export default SurveyQuestionMultiSelect;
<commit_msg>Insert components first version.<commit_after>/**
 * SurveyQuestionMultiSelect component.
 *
 * Site Kit by Google, Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import PropTypes from 'prop-types';

/**
 * Internal dependencies
 */
import Button from '../Button';
// I don't see this used anywhere else. MaterialUI default better?
import Checkbox from '../Checkbox';
import SurveyHeader from './SurveyHeader';

const SurveyQuestionMultiSelect = ( { question, choices, answerQuestion, dismissSurvey } ) => {
	const handleSubmit = () => {
		answerQuestion( {} );
	};
	return (

			<SurveyHeader
				title={ question }
				dismissSurvey={ dismissSurvey }
			/>

				<Checkbox>Value</Checkbox>
				<Checkbox>Value</Checkbox>
				<Checkbox>Value</Checkbox>
				<Checkbox>Value</Checkbox>
				<Checkbox>Value</Checkbox>
				<Checkbox>Value</Checkbox>
				<Checkbox>Value</Checkbox>
				<Checkbox>Value</Checkbox>
				{ choices }

				<Button
					onClick={ handleSubmit }
				>
					Next
				</Button>

	);
};

SurveyQuestionMultiSelect.propTypes = {
	question: PropTypes.string.isRequired,
	// TODO - is this shape ok?
	choices: PropTypes.arrayOf(
		PropTypes.shape( {
			answer_ordinal: PropTypes.oneOfType( [
				PropTypes.string,
				PropTypes.number,
			] ),
			text: PropTypes.string,
		} ),
	).isRequired,
	answerQuestion: PropTypes.func.isRequired,
	dismissSurvey: PropTypes.func.isRequired,
};

export default SurveyQuestionMultiSelect;
<|endoftext|>


<commit_before>/**
 * User Input Select Options.
 *
 * Site Kit by Google, Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import PropTypes from 'prop-types';

/**
 * WordPress dependencies
 */
import { useCallback, useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';

/**
 * Internal dependencies
 */
import Data from 'googlesitekit-data';
import { CORE_USER } from '../../googlesitekit/datastore/user/constants';
import Radio from '../Radio';
import Checkbox from '../Checkbox';
import { Cell, Input, TextField } from '../../material-components';
const { useSelect, useDispatch } = Data;

export default function UserInputSelectOptions( { slug, options, max } ) {
	const values = useSelect( ( select ) => select( CORE_USER ).getUserInputSetting( slug ) || [] );
	const [ other, setOther ] = useState( values.filter( ( value ) => ! options[ value ] )[ 0 ] || '' );
	const { setUserInputSetting } = useDispatch( CORE_USER );

	// Need to make sure that dependencies list always has the same number of elements.
	const dependencies = values.concat( Array( max ) ).slice( 0, max );

	const onClick = useCallback( ( event ) => {
		const { target } = event;
		const { value, checked } = target;

		const newValues = new Set( [ value, ...values ] );
		if ( ! checked ) {
			newValues.delete( value );
		}

		setUserInputSetting( slug, Array.from( newValues ).slice( 0, max ) );
	}, dependencies );

	const onOtherChange = useCallback( ( { target } ) => {
		const newValues = [
			target.value,
			...values.filter( ( value ) => !! options[ value ] ),
		];

		setOther( target.value );
		setUserInputSetting( slug, newValues.slice( 0, max ) );
	}, dependencies );

	const onClickProps = {
		[ max > 1 ? 'onChange' : 'onClick' ]: onClick,
	};

	const ListComponent = max === 1 ? Radio : Checkbox;
	const items = Object.keys( options ).map( ( optionSlug ) => {
		const props = {
			id: `${ slug }-${ optionSlug }`,
			value: optionSlug,
			checked: values.includes( optionSlug ),
			...onClickProps,
		};

		if ( max > 1 ) {
			props.disabled = values.length >= max && ! values.includes( optionSlug );
			props.name = `${ slug }-${ optionSlug }`;
		} else {
			props.name = slug;
		}

		return (

				<ListComponent { ...props }>
					{ options[ optionSlug ] }
				</ListComponent>

		);
	} );

	return (
		<Cell lgStart={ 6 } lgSize={ 6 } mdSize={ 8 } smSize={ 4 }>

				{ items }

					<ListComponent
						id={ `${ slug }-other` }
						name={ max === 1 ? slug : `${ slug }-other` }
						value={ other }
						checked={ values.includes( other ) }
						disabled={ max > 1 && values.length >= max && ! values.includes( other ) }
						{ ...onClickProps }
					>
						{ __( 'Other:', 'google-site-kit' ) }
					</ListComponent>

					<TextField>
						<Input
							id={ `${ slug }-select-options` }
							value={ other }
							onChange={ onOtherChange }
						/>
					</TextField>

				{ max === 1 && __( 'Choose only one (1) answer', 'google-site-kit' ) }
				{ max === 2 && __( 'Choose only two (2) answers', 'google-site-kit' ) }
				{ max === 3 && __( 'Choose only three (3) answers', 'google-site-kit' ) }

		</Cell>
	);
}

UserInputSelectOptions.propTypes = {
	slug: PropTypes.string.isRequired,
	options: PropTypes.shape( {} ).isRequired,
	max: PropTypes.number,
};

UserInputSelectOptions.defaultProps = {
	max: 1,
};
<commit_msg>Enable and disable the Other: input in user-input flow.<commit_after>/**
 * User Input Select Options.
 *
 * Site Kit by Google, Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import PropTypes from 'prop-types';

/**
 * WordPress dependencies
 */
import { useCallback, useState, useRef } from '@wordpress/element';
import { __ } from '@wordpress/i18n';

/**
 * Internal dependencies
 */
import Data from 'googlesitekit-data';
import { CORE_USER } from '../../googlesitekit/datastore/user/constants';
import Radio from '../Radio';
import Checkbox from '../Checkbox';
import { Cell, Input, TextField } from '../../material-components';
const { useSelect, useDispatch } = Data;

export default function UserInputSelectOptions( { slug, options, max } ) {
	const values = useSelect( ( select ) => select( CORE_USER ).getUserInputSetting( slug ) || [] );
	const [ other, setOther ] = useState( values.filter( ( value ) => ! options[ value ] )[ 0 ] || '' );
	const { setUserInputSetting } = useDispatch( CORE_USER );
	const inputRef = useRef();
	const [ disabled, setDisabled ] = useState( false );

	// Need to make sure that dependencies list always has the same number of elements.
	const dependencies = values.concat( Array( max ) ).slice( 0, max );

	const onClick = useCallback( ( event ) => {
		const { target } = event;
		const { value, checked, name, type, id } = target;

		const newValues = new Set( [ value, ...values ] );
		if ( ! checked ) {
			newValues.delete( value );
		}

		if ( name === slug + '-other' && checked === true ) {
			inputRef.current.handleFocus();
			setDisabled( false );
		}

		if ( type === 'radio' && id === slug + '-other' ) {
			inputRef.current.handleFocus();
			setDisabled( false );
		}

		if ( type !== 'radio' && newValues.size === max && ! newValues.has( '' ) && ! newValues.has( other ) ) {
			setDisabled( true );
		} else {
			setDisabled( false );
		}

		setUserInputSetting( slug, Array.from( newValues ).slice( 0, max ) );
	}, dependencies );

	const onOtherChange = useCallback( ( { target } ) => {
		const newValues = [
			target.value,
			...values.filter( ( value ) => !! options[ value ] ),
		];

		setOther( target.value );
		setUserInputSetting( slug, newValues.slice( 0, max ) );
	}, dependencies );

	const onClickProps = {
		[ max > 1 ? 'onChange' : 'onClick' ]: onClick,
	};

	const ListComponent = max === 1 ? Radio : Checkbox;
	const items = Object.keys( options ).map( ( optionSlug ) => {
		const props = {
			id: `${ slug }-${ optionSlug }`,
			value: optionSlug,
			checked: values.includes( optionSlug ),
			...onClickProps,
		};

		if ( max > 1 ) {
			props.disabled = values.length >= max && ! values.includes( optionSlug );
			props.name = `${ slug }-${ optionSlug }`;
		} else {
			props.name = slug;
		}

		return (

				<ListComponent { ...props }>
					{ options[ optionSlug ] }
				</ListComponent>

		);
	} );

	return (
		<Cell lgStart={ 6 } lgSize={ 6 } mdSize={ 8 } smSize={ 4 }>

				{ items }

					<ListComponent
						id={ `${ slug }-other` }
						name={ max === 1 ? slug : `${ slug }-other` }
						value={ other }
						checked={ values.includes( other ) }
						disabled={ max > 1 && values.length >= max && ! values.includes( other ) }
						{ ...onClickProps }
					>
						{ __( 'Other:', 'google-site-kit' ) }
					</ListComponent>

					<TextField
						label={ __( 'Other', 'google-site-kit' ) }
						floatingLabelClassName="screen-reader-text"
					>
						<Input
							id={ `${ slug }-select-options` }
							value={ other }
							onChange={ onOtherChange }
							ref={ ( input ) => inputRef.current = input }
							disabled={ disabled }
						/>
					</TextField>

				{ max === 1 && __( 'Choose only one (1) answer', 'google-site-kit' ) }
				{ max === 2 && __( 'Choose only two (2) answers', 'google-site-kit' ) }
				{ max === 3 && __( 'Choose only three (3) answers', 'google-site-kit' ) }

		</Cell>
	);
}

UserInputSelectOptions.propTypes = {
	slug: PropTypes.string.isRequired,
	options: PropTypes.shape( {} ).isRequired,
	max: PropTypes.number,
};

UserInputSelectOptions.defaultProps = {
	max: 1,
};
<|endoftext|>


<commit_before>;(function() {
'use strict';

const $ = require('cheerio');
const munkres = require('munkres-js');
const levenshtein = require('levenshtein-sse');
const assert = require('assert');

/**
 * Provides methods for removing attributes from HTML fragments
 * and re-adding them later, possibly on a modified
 * (e.g. translated) HTML string.
 *
 * @param {object} [opt]
 *       Options for matching and modifying the HTML elements
 * @param {function[]} [opt.elementFilters]
 *       An array of callbacks for modifying the
 *       HTML elements (passed as DOM nodes).
 *       The default is a {@link ReMarkup.defaultElementFilter}
 *       which preserves id and translate-*
 *       attributes, as well as all semantically relevant attributes.
 *       (See {@link ReMarkup#semanticAttributes}).
 *       (For {@link ReMarkup#unMarkup}).
 * @param {function[]} [opt.additionalElementFilters]
 *       Like elementFilters, but appended in addition
 *       to the standard filters.
 * @param {number} [opt.nonexistentChildDistance]
 *       The distance that will be used when an child
 *       element is present in the original tree
 *       but not the modified one or vice versa.
 *       The default value is 10.
 *       (For {@link ReMarkup#reMarkup}).
 * @param {function} [opt.rawElementMetric]
 *       A distance function for DOM HTML elements.
 *       The default is {@link ReMarkup.defaultRawElementMetric}.
 *
 * @constructor ReMarkup
 * @public
 */
class ReMarkup {
  constructor(opt) {
    opt = opt || {};

    this.elementFilters = opt.elementFilters || [
      ReMarkup.defaultElementFilter(['id', /^(remarkup|translate)-.+$/]
        .concat(this.semanticAttributes()))
    ].concat(opt.additionalElementFilters || []);

    this.nonexistentChildDistance = opt.nonexistentChildDistance || 10;
    this.rawElementMetric = opt.rawElementMetric ||
      ReMarkup.defaultRawElementMetric;
  }

  /**
   * Add a filter to the elementFilters list.
   *
   * @param {function} filter  The element filter.
   *
   * @public
   * @method ReMarkup#addElementFilter
   */
  addElementFilter(filter) {
    this.elementFilters.push(filter);
  }

  /**
   * List of semantically relevant HTML attributes.
   * These will be preserved by the default {@link ReMarkup#unMarkup}
   * element filters and ignored by the default {@link ReMarkup#reMarkup}
   * metric and its copy mechanism.
   *
   * @private
   * @method ReMarkup#semanticAttributes
   */
  semanticAttributes() {
    return [
      'alt', 'label', 'placeholder', 'title', 'tooltip', 'data-info', 'popover',
      (name, element) => {
        return name == 'value' && ['button', 'submit'].indexOf(element.attr('type')) != -1;
      }
    ];
  }

  /**
   * Applies the list of element filters to a single element.
   *
   * @param {DOMElement} element  The target element.
   *
   * @private
   * @method ReMarkup#applyElementFilters
   */
  applyElementFilters(element) {
    for (let i = 0; i < this.elementFilters.length; ++i) {
      this.elementFilters[i](element);
    }
  }

  /**
   * Recursively apply the element filters to an element and all its children.
   *
   * @param {DOMElement} element  The target element.
   *
   * @return {DOMElement}  The original target element.
   *
   * @private
   * @method ReMarkup#unMarkupRecurse
   */
  unMarkupRecurse(element) {
    this.applyElementFilters(element);

    element.children().each((i, child) => {
      this.unMarkupRecurse($(child));
    });

    return element;
  }

  /**
   * Apply the element filters to an HTML fragment.
   *
   * @param {string} original  The target HTML fragment.
   *
   * @return {string}  A modified HTML fragment.
   *
   * @public
   * @method ReMarkup#unMarkup
   */
  unMarkup(original) {
    const doc = $.load(original);
    const root = doc.root();

    this.unMarkupRecurse(root);

    return root.html();
  }

  /**
   * Re-adds attributes from an original HTML fragment
   * to a, possibly modified, one.
   *
   * @param {string} original  The original HTML fragment, including all attributes.
   * @param {string} modified  The target HTML fragment.
   *
   * @return {string}  An HTML fragment, with the attributes from the original string
   *                   added to the modified one.
   *
   * @public
   * @method ReMarkup#reMarkup
   */
  reMarkup(original, modified) {
    const origDoc = $.load(original).root(),
          modDoc  = $.load(modified).root();

    // convert lists of all elements to arrays so that indices work
    const origElements = Array.prototype.slice.call(origDoc.find('*'));
    const modElements  = Array.prototype.slice.call(modDoc .find('*'));

    if (origElements.length == 0 || modElements.length == 0)
      return modified;

    const distanceMatrix = [];
    for (let i = 0; i < origElements.length; ++i)
      distanceMatrix[i] = [];

    // compute the distance of a original and a modified element
    // and enter it into the distance matrix
    const computeElementDistance = (e1unwrapped, e2unwrapped) => {
      const e1i = origElements.indexOf(e1unwrapped);
      const e2i = modElements .indexOf(e2unwrapped);

      assert.notStrictEqual(e1i, -1);
      assert.notStrictEqual(e2i, -1);

      const e1 = $(e1unwrapped), e2 = $(e2unwrapped);

      // do we already know the distance?
      if (typeof distanceMatrix[e1i][e2i] != 'undefined')
        return distanceMatrix[e1i][e2i];

      let totalChildDistance = 0;

      let e1children = e1.children();
      let e2children = e2.children();

      if (e1children.length > 0 && e2children.length > 0) {
        // compute all distances between the children of the elements...
        const childMatrix = [];

        for (let i = 0; i < e1children.length; ++i) {
          childMatrix[i] = [];

          for (let j = 0; j < e2children.length; ++j) {
            childMatrix[i][j] = computeElementDistance(e1children[i], e2children[j]);
          }
        }

        // ... and find the minimal assignment between these
        const m = new munkres.Munkres();
        const indices = m.compute(childMatrix);

        for (let k = 0; k < indices.length; ++k) {
          const ci = indices[k][0],
                cj = indices[k][1];

          totalChildDistance += childMatrix[ci][cj];
        }
      }

      // add penalty for differing number of child elements
      totalChildDistance += Math.abs(e1children.length - e2children.length) * this.nonexistentChildDistance;

      // compare to the element that unMarkup produces from e1
      const e1_ = this.unMarkupRecurse(e1.clone());
      const rawElementDistance = this.rawElementMetric(
          e1_, e2,
          e1i, e2i,
          e1.parent().children().length,
          e2.parent().children().length);

      return distanceMatrix[e1i][e2i] = totalChildDistance + rawElementDistance;
    }

    for (let i = 0; i < origElements.length; ++i)
      for (let j = 0; j < modElements.length; ++j)
        computeElementDistance(origElements[i], modElements[j]);

    const m = new munkres.Munkres();
    const indices = m.compute(distanceMatrix);

    for (let k = 0; k < indices.length; ++k) {
      const ci = indices[k][0], cj = indices[k][1];
      const e1 = origElements[ci];
      const e2 = modElements [cj];

      copyAttributes(e1, e2, this.semanticAttributes());
    }

    return modDoc.html();
  }
}

/**
 * An element filter for stripping whitespace after/before
 * tags and newlines and collapse multiple spaces into a single one.
 *
 * @param {DOMElement} cElement  The target element.
 *
 * @return {DOMElement}  The original target element.
 *
 * @public
 * @function ReMarkup.stripSpaces
 */
ReMarkup.stripSpaces = function (cElement) {
  const element = cElement[0];

  for (let i = 0; i < element.children.length; ++i) {
    const node = element.children[i];
    if (node.type !== 'text')
      continue;

    // collapse multiple spaces

    /* only \t, \n, \r, space since other spaces (e.g. nbsp)
     * may carry some semantic meaning */
    node.data = node.data
      .replace(/[\t\n\r ]+/g, ' ');

    // remove starting/ending whitespace
    if (i == 0)
      node.data = node.data.replace(/^[\t\n\r ]+/g, '');

    if (i == element.childNodes.length - 1)
      node.data = node.data.replace(/[\t\n\r ]+$/g, '');
  }

  return element;
};

/**
 * Creates a default element filter that removes most attributes.
 *
 * @param {string[]} preserveAttributes
 *                     A list of strings and/or regex objects that
 *                     element attributes are validated against.
 *
 * @return {function}  An element filter that removes all attributes
 *                     but those specified as preserved.
 *
 * @public
 * @function ReMarkup.defaultElementFilter
 */
ReMarkup.defaultElementFilter = function (preserveAttributes) {
  return element => {
    const originalElement = element.clone();

    Object.keys(element.attr() || {}).forEach(attrName => {
      let shouldBePreserved = false;
      for (let j = 0; j < preserveAttributes.length; ++j) {
        if ((preserveAttributes[j].call &&
             preserveAttributes[j].call(element, attrName, originalElement, element)) ||
            (preserveAttributes[j].test &&
             preserveAttributes[j].test(attrName)) ||
            attrName == preserveAttributes[j])
        {
          shouldBePreserved = true;
          break;
        }
      }

      if (!shouldBePreserved)
        element.removeAttr(attrName);
    });
  }
};

/**
 * The default element metric.
 * This compares elements and their position in the DOM tree
 * and returns a number that indicates how un-similar the
 * given elements are.
 *
 * This returns a distance of 0 for elements which share the
 * same value for either of the id,
 * translate-id or remarkup-id attributes.
 *
 * @public
 * @function ReMarkup.defaultRawElementMetric
 */
ReMarkup.defaultRawElementMetric = function (e1, e2, e1i, e2i, e1pl, e2pl) {
  // attributes that lead to definite matching of elements
  const identAttr = ['id', 'translate-id', 'remarkup-id'];

  for (let i = 0; i < identAttr.length; ++i) {
    if (typeof e1.attr(identAttr[i]) !== 'undefined' &&
        typeof e2.attr(identAttr[i]) !== 'undefined' &&
        e1.attr(identAttr[i]) === e2.attr(identAttr[i])) {
      return 0;
    }
  }

  let distance = 5; // minimum distance for elements with different IDs
  assert.ok(e1[0].tagName);
  assert.ok(e2[0].tagName);
  if (e1[0].tagName !== e2[0].tagName) {
    distance += 3;
  }

  const e1attribs = Object.keys(e1.attr() || {});
  const e2attribs = Object.keys(e2.attr() || {});
  for (let i = 0; i < e1attribs.length; ++i) {
    if (e2attribs.indexOf(e1attribs[i]) === -1 &&
        this.semanticAttributes().indexOf(e1attribs[i]) === -1) {
      distance++;
    }
  }

  for (let i = 0; i < e2attribs.length; ++i) {
    if (this.semanticAttributes().indexOf(e2attribs[i]) !== -1) {
      continue;
    }

    if (e1attribs.indexOf(e2attribs[i]) === -1) {
      distance++;
    } else {
      const attrValue1 = e1.attr(e2attribs[i]);
      const attrValue2 = e2.attr(e2attribs[i]);

      if (attrValue1 !== attrValue2) {
        distance += 2 * Math.log(levenshtein(attrValue1, attrValue2));
      }
    }
  }

  const positionDistance = Math.abs(e1i - e2i);
  if (positionDistance > 0)
    distance += 2 * Math.log(positionDistance) + 1;

  return distance;
};

module.exports = ReMarkup;

// copy all DOM attributes from src to dst
function copyAttributes (src, dst, ignored) {
  ignored = ignored || [];

  const srcAttribs = Object.keys(src.attribs || src.attributes);
  for (let i = 0; i < srcAttribs.length; ++i) {
    if (ignored.indexOf(srcAttribs[i]) === -1) {
      dst.attribs[srcAttribs[i]] = src.attribs[srcAttribs[i]];
    }
  }
}

})();
<commit_msg>Fix inconsistent usage of preserved attributes<commit_after>;(function() {
'use strict';

const $ = require('cheerio');
const munkres = require('munkres-js');
const levenshtein = require('levenshtein-sse');
const assert = require('assert');

/**
 * Internally used class for specifying sets of attributes.
 * Wraps around a list, whose elements can be strings (e.g. "href"),
 * regexes (/^data-/) or functions (name, element) => ...
 */
class AttributeSet {
  constructor(initialElements) {
    this.functions = [];
    this.regexps = [];
    this.strings = [];

    this.add(initialElements || []);
  }

  add(otherElements) {
    if (!Array.isArray(otherElements)) {
      otherElements = [otherElements];
    }

    otherElements.forEach(e => {
      if (typeof e === 'string') {
        return this.strings.push(e);
      }

      if (typeof e === 'function') {
        return this.functions.push(e);
      }

      this.regexps.push(e);
    });
  }

  test(string, element, originalElement) {
    if (this.strings.indexOf(string) !== -1) {
      return true;
    }

    for (let r of this.regexps) {
      if (r.test(string)) {
        return true;
      }
    }

    for (let fn of this.functions) {
      if (fn.call(element, string, originalElement, element)) {
        return true;
      }
    }

    return false;
  }
}

/**
 * Provides methods for removing attributes from HTML fragments
 * and re-adding them later, possibly on a modified
 * (e.g. translated) HTML string.
 *
 * @param {object} [opt]
 *       Options for matching and modifying the HTML elements
 * @param {function[]} [opt.elementFilters]
 *       An array of callbacks for modifying the
 *       HTML elements (passed as DOM nodes).
 *       The default is a {@link ReMarkup.defaultElementFilter}
 *       which preserves id and translate-*
 *       attributes, as well as all semantically relevant attributes.
 *       (See {@link ReMarkup#semanticAttributes}).
 *       (For {@link ReMarkup#unMarkup}).
 * @param {function[]} [opt.additionalElementFilters]
 *       Like elementFilters, but appended in addition
 *       to the standard filters.
 * @param {number} [opt.nonexistentChildDistance]
 *       The distance that will be used when an child
 *       element is present in the original tree
 *       but not the modified one or vice versa.
 *       The default value is 10.
 *       (For {@link ReMarkup#reMarkup}).
 * @param {function} [opt.rawElementMetric]
 *       A distance function for DOM HTML elements.
 *       The default is {@link ReMarkup.defaultRawElementMetric}.
 *
 * @constructor ReMarkup
 * @public
 */
class ReMarkup {
  constructor(opt) {
    opt = opt || {};

    this.keepAttributes = new AttributeSet(['id', /^(remarkup|translate)-.+$/]);
    this.keepAttributes.add(this.semanticAttributes());

    this.elementFilters = opt.elementFilters || [
      ReMarkup.defaultElementFilter(this.keepAttributes)
    ].concat(opt.additionalElementFilters || []);

    this.nonexistentChildDistance = opt.nonexistentChildDistance || 10;
    this.rawElementMetric = opt.rawElementMetric ||
      ReMarkup.defaultRawElementMetric;
  }

  /**
   * Add a filter to the elementFilters list.
   *
   * @param {function} filter  The element filter.
   *
   * @public
   * @method ReMarkup#addElementFilter
   */
  addElementFilter(filter) {
    this.elementFilters.push(filter);
  }

  /**
   * List of semantically relevant HTML attributes.
   * These will be preserved by the default {@link ReMarkup#unMarkup}
   * element filters and ignored by the default {@link ReMarkup#reMarkup}
   * metric and its copy mechanism.
   *
   * @private
   * @method ReMarkup#semanticAttributes
   */
  semanticAttributes() {
    return [
      'alt', 'label', 'placeholder', 'title', 'tooltip', 'data-info', 'popover',
      (name, element) => {
        return name == 'value' && element &&
          ['button', 'submit'].indexOf(element.attr('type')) != -1;
      }
    ];
  }

  /**
   * Applies the list of element filters to a single element.
   *
   * @param {DOMElement} element  The target element.
   *
   * @private
   * @method ReMarkup#applyElementFilters
   */
  applyElementFilters(element) {
    for (let i = 0; i < this.elementFilters.length; ++i) {
      this.elementFilters[i](element);
    }
  }

  /**
   * Recursively apply the element filters to an element and all its children.
   *
   * @param {DOMElement} element  The target element.
   *
   * @return {DOMElement}  The original target element.
   *
   * @private
   * @method ReMarkup#unMarkupRecurse
   */
  unMarkupRecurse(element) {
    this.applyElementFilters(element);

    element.children().each((i, child) => {
      this.unMarkupRecurse($(child));
    });

    return element;
  }

  /**
   * Apply the element filters to an HTML fragment.
   *
   * @param {string} original  The target HTML fragment.
   *
   * @return {string}  A modified HTML fragment.
   *
   * @public
   * @method ReMarkup#unMarkup
   */
  unMarkup(original) {
    const doc = $.load(original);
    const root = doc.root();

    this.unMarkupRecurse(root);

    return root.html();
  }

  /**
   * Re-adds attributes from an original HTML fragment
   * to a, possibly modified, one.
   *
   * @param {string} original  The original HTML fragment, including all attributes.
   * @param {string} modified  The target HTML fragment.
   *
   * @return {string}  An HTML fragment, with the attributes from the original string
   *                   added to the modified one.
   *
   * @public
   * @method ReMarkup#reMarkup
   */
  reMarkup(original, modified) {
    const origDoc = $.load(original).root(),
          modDoc  = $.load(modified).root();

    // convert lists of all elements to arrays so that indices work
    const origElements = Array.prototype.slice.call(origDoc.find('*'));
    const modElements  = Array.prototype.slice.call(modDoc .find('*'));

    if (origElements.length == 0 || modElements.length == 0)
      return modified;

    const distanceMatrix = [];
    for (let i = 0; i < origElements.length; ++i)
      distanceMatrix[i] = [];

    // compute the distance of a original and a modified element
    // and enter it into the distance matrix
    const computeElementDistance = (e1unwrapped, e2unwrapped) => {
      const e1i = origElements.indexOf(e1unwrapped);
      const e2i = modElements .indexOf(e2unwrapped);

      assert.notStrictEqual(e1i, -1);
      assert.notStrictEqual(e2i, -1);

      const e1 = $(e1unwrapped), e2 = $(e2unwrapped);

      // do we already know the distance?
      if (typeof distanceMatrix[e1i][e2i] != 'undefined')
        return distanceMatrix[e1i][e2i];

      let totalChildDistance = 0;

      let e1children = e1.children();
      let e2children = e2.children();

      if (e1children.length > 0 && e2children.length > 0) {
        // compute all distances between the children of the elements...
        const childMatrix = [];

        for (let i = 0; i < e1children.length; ++i) {
          childMatrix[i] = [];

          for (let j = 0; j < e2children.length; ++j) {
            childMatrix[i][j] = computeElementDistance(e1children[i], e2children[j]);
          }
        }

        // ... and find the minimal assignment between these
        const m = new munkres.Munkres();
        const indices = m.compute(childMatrix);

        for (let k = 0; k < indices.length; ++k) {
          const ci = indices[k][0],
                cj = indices[k][1];

          totalChildDistance += childMatrix[ci][cj];
        }
      }

      // add penalty for differing number of child elements
      totalChildDistance += Math.abs(e1children.length - e2children.length) * this.nonexistentChildDistance;

      // compare to the element that unMarkup produces from e1
      const e1_ = this.unMarkupRecurse(e1.clone());
      const rawElementDistance = this.rawElementMetric(
          e1_, e2,
          e1i, e2i,
          e1.parent().children().length,
          e2.parent().children().length);

      return distanceMatrix[e1i][e2i] = totalChildDistance + rawElementDistance;
    }

    for (let i = 0; i < origElements.length; ++i)
      for (let j = 0; j < modElements.length; ++j)
        computeElementDistance(origElements[i], modElements[j]);

    const m = new munkres.Munkres();
    const indices = m.compute(distanceMatrix);

    for (let k = 0; k < indices.length; ++k) {
      const ci = indices[k][0], cj = indices[k][1];
      const e1 = origElements[ci];
      const e2 = modElements [cj];

      copyAttributes(e1, e2, this.keepAttributes);
    }

    return modDoc.html();
  }
}

/**
 * An element filter for stripping whitespace after/before
 * tags and newlines and collapse multiple spaces into a single one.
 *
 * @param {DOMElement} cElement  The target element.
 *
 * @return {DOMElement}  The original target element.
 *
 * @public
 * @function ReMarkup.stripSpaces
 */
ReMarkup.stripSpaces = function (cElement) {
  const element = cElement[0];

  for (let i = 0; i < element.children.length; ++i) {
    const node = element.children[i];
    if (node.type !== 'text')
      continue;

    // collapse multiple spaces

    /* only \t, \n, \r, space since other spaces (e.g. nbsp)
     * may carry some semantic meaning */
    node.data = node.data
      .replace(/[\t\n\r ]+/g, ' ');

    // remove starting/ending whitespace
    if (i == 0)
      node.data = node.data.replace(/^[\t\n\r ]+/g, '');

    if (i == element.childNodes.length - 1)
      node.data = node.data.replace(/[\t\n\r ]+$/g, '');
  }

  return element;
};

/**
 * Creates a default element filter that removes most attributes.
 *
 * @param {string[]} keepAttributes
 *                     A list of strings and/or regex objects that
 *                     element attributes are validated against.
 *
 * @return {function}  An element filter that removes all attributes
 *                     but those specified as preserved.
 *
 * @public
 * @function ReMarkup.defaultElementFilter
 */
ReMarkup.defaultElementFilter = function (keepAttributes) {
  return element => {
    const originalElement = element.clone();

    Object.keys(element.attr() || {})
      .filter(attrName => !keepAttributes.test(attrName, element, originalElement))
      .forEach(attrName => element.removeAttr(attrName));
  };
};

/**
 * The default element metric.
 * This compares elements and their position in the DOM tree
 * and returns a number that indicates how un-similar the
 * given elements are.
 *
 * This returns a distance of 0 for elements which share the
 * same value for either of the id,
 * translate-id or remarkup-id attributes.
 *
 * @public
 * @function ReMarkup.defaultRawElementMetric
 */
ReMarkup.defaultRawElementMetric = function (e1, e2, e1i, e2i, e1pl, e2pl) {
  // attributes that lead to definite matching of elements
  const identAttr = ['id', 'translate-id', 'remarkup-id'];

  for (let i = 0; i < identAttr.length; ++i) {
    if (typeof e1.attr(identAttr[i]) !== 'undefined' &&
        typeof e2.attr(identAttr[i]) !== 'undefined' &&
        e1.attr(identAttr[i]) === e2.attr(identAttr[i])) {
      return 0;
    }
  }

  let distance = 5; // minimum distance for elements with different IDs
  assert.ok(e1[0].tagName);
  assert.ok(e2[0].tagName);
  if (e1[0].tagName !== e2[0].tagName) {
    distance += 3;
  }

  const e1attribs = Object.keys(e1.attr() || {});
  const e2attribs = Object.keys(e2.attr() || {});
  for (let i = 0; i < e1attribs.length; ++i) {
    if (e2attribs.indexOf(e1attribs[i]) === -1 &&
        !this.keepAttributes.test(e1attribs[i])) {
      distance++;
    }
  }

  for (let i = 0; i < e2attribs.length; ++i) {
    if (this.keepAttributes.test(e2attribs[i])) {
      continue;
    }

    if (e1attribs.indexOf(e2attribs[i]) === -1) {
      distance++;
    } else {
      const attrValue1 = e1.attr(e2attribs[i]);
      const attrValue2 = e2.attr(e2attribs[i]);

      if (attrValue1 !== attrValue2) {
        distance += 2 * Math.log(levenshtein(attrValue1, attrValue2));
      }
    }
  }

  const positionDistance = Math.abs(e1i - e2i);
  if (positionDistance > 0)
    distance += 2 * Math.log(positionDistance) + 1;

  return distance;
};

module.exports = ReMarkup;

// copy all DOM attributes from src to dst
function copyAttributes (src, dst, ignored) {
  ignored = ignored || [];

  const srcAttribs = Object.keys(src.attribs || src.attributes);
  for (let i = 0; i < srcAttribs.length; ++i) {
    if (!ignored.test(srcAttribs[i], $(dst), $(src))) {
      dst.attribs[srcAttribs[i]] = src.attribs[srcAttribs[i]];
    }
  }
}

})();
<|endoftext|>


<commit_before>
Calendar.mixin({

	el: null,
	contentEl: null,
	suggestedViewHeight: null,
	ignoreUpdateViewSize: 0,
	windowResizeProxy: null,

	render: function() {
		if (!this.contentEl) {
			this.initialRender();
		}
		else if (this.elementVisible()) {
			// mainly for the public API
			this.calcSize();
			this.renderView();
		}
	},

	initialRender: function() {
		var _this = this;
		var el = this.el;

		el.addClass('fc');

		// event delegation for nav links
		el.on('click.fc', 'a[data-goto]', function(ev) {
			var anchorEl = $(this);
			var gotoOptions = anchorEl.data('goto'); // will automatically parse JSON
			var date = _this.moment(gotoOptions.date);
			var viewType = gotoOptions.type;

			// property like "navLinkDayClick". might be a string or a function
			var customAction = _this.view.opt('navLink' + capitaliseFirstLetter(viewType) + 'Click');

			if (typeof customAction === 'function') {
				customAction(date, ev);
			}
			else {
				if (typeof customAction === 'string') {
					viewType = customAction;
				}
				_this.zoomTo(date, viewType);
			}
		});

		// called immediately, and upon option change
		this.optionsModel.watch('settingTheme', [ '?theme' ], function(opts) {
			var themeClass = ThemeRegistry.getThemeClass(opts.theme);
			var theme = new themeClass(_this.optionsModel);
			var widgetClass = theme.getClass('widget');

			_this.theme = theme;

			if (widgetClass) {
				el.addClass(widgetClass);
			}
		}, function() {
			var widgetClass = _this.theme.getClass('widget');

			_this.theme = null;

			if (widgetClass) {
				el.removeClass(widgetClass);
			}
		});

		// called immediately, and upon option change.
		// HACK: locale often affects isRTL, so we explicitly listen to that too.
		this.optionsModel.watch('applyingDirClasses', [ '?isRTL', '?locale' ], function(opts) {
			el.toggleClass('fc-ltr', !opts.isRTL);
			el.toggleClass('fc-rtl', opts.isRTL);
		});

		this.contentEl = $("
").prependTo(el);

		this.initToolbars();
		this.renderHeader();
		this.renderFooter();
		this.renderView(this.opt('defaultView'));

		if (this.opt('handleWindowResize')) {
			$(window).resize(
				this.windowResizeProxy = debounce( // prevents rapid calls
					this.windowResize.bind(this),
					this.opt('windowResizeDelay')
				)
			);
		}
	},

	destroy: function() {
		this.renderQueue.kill();

		if (this.view) {
			this.view.removeElement();

			// NOTE: don't null-out this.view in case API methods are called after destroy.
			// It is still the "current" view, just not rendered.
		}

		this.toolbarsManager.proxyCall('removeElement');
		this.contentEl.remove();
		this.el.removeClass('fc fc-ltr fc-rtl');

		// removes theme-related root className
		this.optionsModel.unwatch('settingTheme');

		this.el.off('.fc'); // unbind nav link handlers

		if (this.windowResizeProxy) {
			$(window).unbind('resize', this.windowResizeProxy);
			this.windowResizeProxy = null;
		}

		GlobalEmitter.unneeded();
	},

	elementVisible: function() {
		return this.el.is(':visible');
	},

	// Render Queue
	// -----------------------------------------------------------------------------------------------------------------

	buildRenderQueue: function() {
		var _this = this;
		var renderQueue = new RenderQueue({
			event: this.opt('eventRenderWait')
		});

		renderQueue.on('start', function() {
			var view = _this.view;

			_this.freezeContentHeight();

			view.addScroll(view.queryScroll()); // TODO: move to Calendar
		});

		renderQueue.on('stop', function() {
			var view = _this.view;

			if (_this.updateViewSize()) { // success?
				view.popScroll(); // TODO: move to Calendar
			}

			_this.thawContentHeight();
		});

		return renderQueue;
	},

	// TODO: evenutally make Calendar a DateComponent
	initBatchRenderingForView: function(view) {
		var _this = this;

		view.on('before:change.batchRender', function() {
			_this.startBatchRender();
		});

		view.on('change.batchRender', function() {
			_this.stopBatchRender();
		});
	},

	// TODO: evenutally make Calendar a DateComponent
	destroyBatchRenderingForView: function(view) {
		view.off('.batchRender');
	},

	startBatchRender: function() {
		if (!(this.batchRenderDepth++)) {
			this.renderQueue.pause();
		}
	},

	stopBatchRender: function() {
		if (!(--this.batchRenderDepth)) {
			this.renderQueue.resume();
		}
	},

	// View Rendering
	// -----------------------------------------------------------------------------------

	// Renders a view because of a date change, view-type change, or for the first time.
	// If not given a viewType, keep the current view but render different dates.
	// Accepts an optional scroll state to restore to.
	renderView: function(viewType, forcedScroll) {
		var oldView = this.view;

		if (oldView && oldView.type === viewType) {
			return;
		}

		this.freezeContentHeight();
		this.ignoreUpdateViewSize++;

		if (oldView) {
			this.clearView();
			this.destroyBatchRenderingForView(oldView); // do AFTER the clear b/c the clear updates lots of props
		}

		// if viewType changed, or the view was never created, create a fresh view
		if (!this.view && viewType) {
			this.view =
				this.viewsByType[viewType] ||
				(this.viewsByType[viewType] = this.instantiateView(viewType));

			this.initBatchRenderingForView(this.view);

			this.view.setElement(
				$("
").appendTo(this.contentEl)
			);
			this.toolbarsManager.proxyCall('activateButton', viewType);
		}

		if (this.view) {

			if (forcedScroll) {
				this.view.addForcedScroll(forcedScroll);
			}

			if (this.elementVisible()) {
				this.view.setDate(this.currentDate);
			}
		}

		this.ignoreUpdateViewSize--;
		this.updateViewSize();
		this.thawContentHeight();
		this.view.popScroll();
	},

	// Unrenders the current view and reflects this change in the Header.
	// Unregsiters the `view`, but does not remove from viewByType hash.
	clearView: function() {
		this.toolbarsManager.proxyCall('deactivateButton', this.view.type);
		this.view.removeElement();
		this.view = null;
	},

	// Destroys the view, including the view object. Then, re-instantiates it and renders it.
	// Maintains the same scroll state.
	// TODO: maintain any other user-manipulated state.
	reinitView: function() {
		this.freezeContentHeight();
		this.ignoreUpdateViewSize++;

		var viewType = this.view.type;
		var scrollState = this.view.queryScroll();
		this.clearView();
		this.calcSize();
		this.renderView(viewType, scrollState);

		this.ignoreUpdateViewSize--;
		this.updateViewSize();
		this.thawContentHeight();
	},

	// Resizing
	// -----------------------------------------------------------------------------------

	getSuggestedViewHeight: function() {
		if (this.suggestedViewHeight === null) {
			this.calcSize();
		}
		return this.suggestedViewHeight;
	},

	isHeightAuto: function() {
		return this.opt('contentHeight') === 'auto' || this.opt('height') === 'auto';
	},

	updateViewSize: function(isResize, force) {
		var view = this.view;
		var scroll;

		if ((force || !this.ignoreUpdateViewSize) && this.elementVisible()) {

			if (isResize) {
				this._calcSize();
				scroll = view.queryScroll();
			}

			this.ignoreUpdateViewSize++;

			view.updateSize(
				this.getSuggestedViewHeight(),
				this.isHeightAuto(),
				isResize
			);

			this.ignoreUpdateViewSize--;

			if (isResize) {
				view.applyScroll(scroll);
			}

			return true; // signal success
		}
	},

	calcSize: function() {
		if (this.elementVisible()) {
			this._calcSize();
		}
	},

	_calcSize: function() { // assumes elementVisible
		var contentHeightInput = this.opt('contentHeight');
		var heightInput = this.opt('height');

		if (typeof contentHeightInput === 'number') { // exists and not 'auto'
			this.suggestedViewHeight = contentHeightInput;
		}
		else if (typeof contentHeightInput === 'function') { // exists and is a function
			this.suggestedViewHeight = contentHeightInput();
		}
		else if (typeof heightInput === 'number') { // exists and not 'auto'
			this.suggestedViewHeight = heightInput - this.queryToolbarsHeight();
		}
		else if (typeof heightInput === 'function') { // exists and is a function
			this.suggestedViewHeight = heightInput() - this.queryToolbarsHeight();
		}
		else if (heightInput === 'parent') { // set to height of parent element
			this.suggestedViewHeight = this.el.parent().height() - this.queryToolbarsHeight();
		}
		else {
			this.suggestedViewHeight = Math.round(
				this.contentEl.width() /
				Math.max(this.opt('aspectRatio'), .5)
			);
		}
	},

	windowResize: function(ev) {
		if (
			ev.target === window && // so we don't process jqui "resize" events that have bubbled up
			this.view.isDatesRendered
		) {
			if (this.updateViewSize(true)) { // isResize=true, returns true on success
				this.publiclyTrigger('windowResize', [ this.view ]);
			}
		}
	},

	/* Height "Freezing"
	-----------------------------------------------------------------------------*/

	freezeContentHeight: function() {
		this.contentEl.css({
			width: '100%',
			height: this.contentEl.height(),
			overflow: 'hidden'
		});
	},

	thawContentHeight: function() {
		this.contentEl.css({
			width: '',
			height: '',
			overflow: ''
		});
	}

});
<commit_msg>fix scroll problems<commit_after>
Calendar.mixin({

	el: null,
	contentEl: null,
	suggestedViewHeight: null,
	ignoreUpdateViewSize: 0,
	windowResizeProxy: null,

	render: function() {
		if (!this.contentEl) {
			this.initialRender();
		}
		else if (this.elementVisible()) {
			// mainly for the public API
			this.calcSize();
			this.renderView();
		}
	},

	initialRender: function() {
		var _this = this;
		var el = this.el;

		el.addClass('fc');

		// event delegation for nav links
		el.on('click.fc', 'a[data-goto]', function(ev) {
			var anchorEl = $(this);
			var gotoOptions = anchorEl.data('goto'); // will automatically parse JSON
			var date = _this.moment(gotoOptions.date);
			var viewType = gotoOptions.type;

			// property like "navLinkDayClick". might be a string or a function
			var customAction = _this.view.opt('navLink' + capitaliseFirstLetter(viewType) + 'Click');

			if (typeof customAction === 'function') {
				customAction(date, ev);
			}
			else {
				if (typeof customAction === 'string') {
					viewType = customAction;
				}
				_this.zoomTo(date, viewType);
			}
		});

		// called immediately, and upon option change
		this.optionsModel.watch('settingTheme', [ '?theme' ], function(opts) {
			var themeClass = ThemeRegistry.getThemeClass(opts.theme);
			var theme = new themeClass(_this.optionsModel);
			var widgetClass = theme.getClass('widget');

			_this.theme = theme;

			if (widgetClass) {
				el.addClass(widgetClass);
			}
		}, function() {
			var widgetClass = _this.theme.getClass('widget');

			_this.theme = null;

			if (widgetClass) {
				el.removeClass(widgetClass);
			}
		});

		// called immediately, and upon option change.
		// HACK: locale often affects isRTL, so we explicitly listen to that too.
		this.optionsModel.watch('applyingDirClasses', [ '?isRTL', '?locale' ], function(opts) {
			el.toggleClass('fc-ltr', !opts.isRTL);
			el.toggleClass('fc-rtl', opts.isRTL);
		});

		this.contentEl = $("
").prependTo(el);

		this.initToolbars();
		this.renderHeader();
		this.renderFooter();
		this.renderView(this.opt('defaultView'));

		if (this.opt('handleWindowResize')) {
			$(window).resize(
				this.windowResizeProxy = debounce( // prevents rapid calls
					this.windowResize.bind(this),
					this.opt('windowResizeDelay')
				)
			);
		}
	},

	destroy: function() {
		this.renderQueue.kill();

		if (this.view) {
			this.view.removeElement();

			// NOTE: don't null-out this.view in case API methods are called after destroy.
			// It is still the "current" view, just not rendered.
		}

		this.toolbarsManager.proxyCall('removeElement');
		this.contentEl.remove();
		this.el.removeClass('fc fc-ltr fc-rtl');

		// removes theme-related root className
		this.optionsModel.unwatch('settingTheme');

		this.el.off('.fc'); // unbind nav link handlers

		if (this.windowResizeProxy) {
			$(window).unbind('resize', this.windowResizeProxy);
			this.windowResizeProxy = null;
		}

		GlobalEmitter.unneeded();
	},

	elementVisible: function() {
		return this.el.is(':visible');
	},

	// Render Queue
	// -----------------------------------------------------------------------------------------------------------------

	buildRenderQueue: function() {
		var _this = this;
		var renderQueue = new RenderQueue({
			event: this.opt('eventRenderWait')
		});

		renderQueue.on('start', function() {
			var view = _this.view;

			_this.freezeContentHeight();

			view.addScroll(view.queryScroll()); // TODO: move to Calendar
		});

		renderQueue.on('stop', function() {
			var view = _this.view;

			if (_this.updateViewSize()) { // success?
				view.popScroll(); // TODO: move to Calendar
			}

			_this.thawContentHeight();
		});

		return renderQueue;
	},

	// TODO: evenutally make Calendar a DateComponent
	initBatchRenderingForView: function(view) {
		var _this = this;

		view.on('before:change.batchRender', function() {
			_this.startBatchRender();
		});

		view.on('change.batchRender', function() {
			_this.stopBatchRender();
		});
	},

	// TODO: evenutally make Calendar a DateComponent
	destroyBatchRenderingForView: function(view) {
		view.off('.batchRender');
	},

	startBatchRender: function() {
		if (!(this.batchRenderDepth++)) {
			this.renderQueue.pause();
		}
	},

	stopBatchRender: function() {
		if (!(--this.batchRenderDepth)) {
			this.renderQueue.resume();
		}
	},

	// View Rendering
	// -----------------------------------------------------------------------------------

	// Renders a view because of a date change, view-type change, or for the first time.
	// If not given a viewType, keep the current view but render different dates.
	// Accepts an optional scroll state to restore to.
	renderView: function(viewType, forcedScroll) {
		var oldView = this.view;

		if (oldView) {
			oldView.addScroll(oldView.queryScroll());
		}

		this.freezeContentHeight();
		this.ignoreUpdateViewSize++;

		if (oldView && viewType && oldView.type !== viewType) {
			this.clearView();
			this.destroyBatchRenderingForView(oldView); // do AFTER the clear b/c the clear updates lots of props
		}

		// if viewType changed, or the view was never created, create a fresh view
		if (!this.view && viewType) {
			this.view =
				this.viewsByType[viewType] ||
				(this.viewsByType[viewType] = this.instantiateView(viewType));

			this.initBatchRenderingForView(this.view);

			this.view.setElement(
				$("
").appendTo(this.contentEl)
			);
			this.toolbarsManager.proxyCall('activateButton', viewType);
		}

		if (this.view) {

			if (forcedScroll) {
				this.view.addForcedScroll(forcedScroll);
			}

			if (this.elementVisible()) {
				this.view.setDate(this.currentDate);
			}
		}

		this.ignoreUpdateViewSize--;
		this.updateViewSize();
		this.thawContentHeight();
		this.view.popScroll();
	},

	// Unrenders the current view and reflects this change in the Header.
	// Unregsiters the `view`, but does not remove from viewByType hash.
	clearView: function() {
		this.toolbarsManager.proxyCall('deactivateButton', this.view.type);
		this.view.removeElement();
		this.view = null;
	},

	// Destroys the view, including the view object. Then, re-instantiates it and renders it.
	// Maintains the same scroll state.
	// TODO: maintain any other user-manipulated state.
	reinitView: function() {
		var scroll = this.view.queryScroll();
		this.freezeContentHeight();
		this.ignoreUpdateViewSize++;

		var viewType = this.view.type;
		var scrollState = this.view.queryScroll();
		this.clearView();
		this.calcSize();
		this.renderView(viewType, scrollState);

		this.ignoreUpdateViewSize--;
		this.updateViewSize();
		this.thawContentHeight();
		this.view.applyScroll(scroll);
	},

	// Resizing
	// -----------------------------------------------------------------------------------

	getSuggestedViewHeight: function() {
		if (this.suggestedViewHeight === null) {
			this.calcSize();
		}
		return this.suggestedViewHeight;
	},

	isHeightAuto: function() {
		return this.opt('contentHeight') === 'auto' || this.opt('height') === 'auto';
	},

	updateViewSize: function(isResize, force) {
		var view = this.view;
		var scroll;

		if ((force || !this.ignoreUpdateViewSize) && this.elementVisible()) {

			if (isResize) {
				this._calcSize();
				scroll = view.queryScroll();
			}

			this.ignoreUpdateViewSize++;

			view.updateSize(
				this.getSuggestedViewHeight(),
				this.isHeightAuto(),
				isResize
			);

			this.ignoreUpdateViewSize--;

			if (isResize) {
				view.applyScroll(scroll);
			}

			return true; // signal success
		}
	},

	calcSize: function() {
		if (this.elementVisible()) {
			this._calcSize();
		}
	},

	_calcSize: function() { // assumes elementVisible
		var contentHeightInput = this.opt('contentHeight');
		var heightInput = this.opt('height');

		if (typeof contentHeightInput === 'number') { // exists and not 'auto'
			this.suggestedViewHeight = contentHeightInput;
		}
		else if (typeof contentHeightInput === 'function') { // exists and is a function
			this.suggestedViewHeight = contentHeightInput();
		}
		else if (typeof heightInput === 'number') { // exists and not 'auto'
			this.suggestedViewHeight = heightInput - this.queryToolbarsHeight();
		}
		else if (typeof heightInput === 'function') { // exists and is a function
			this.suggestedViewHeight = heightInput() - this.queryToolbarsHeight();
		}
		else if (heightInput === 'parent') { // set to height of parent element
			this.suggestedViewHeight = this.el.parent().height() - this.queryToolbarsHeight();
		}
		else {
			this.suggestedViewHeight = Math.round(
				this.contentEl.width() /
				Math.max(this.opt('aspectRatio'), .5)
			);
		}
	},

	windowResize: function(ev) {
		if (
			ev.target === window && // so we don't process jqui "resize" events that have bubbled up
			this.view.isDatesRendered
		) {
			if (this.updateViewSize(true)) { // isResize=true, returns true on success
				this.publiclyTrigger('windowResize', [ this.view ]);
			}
		}
	},

	/* Height "Freezing"
	-----------------------------------------------------------------------------*/

	freezeContentHeight: function() {
		this.contentEl.css({
			width: '100%',
			height: this.contentEl.height(),
			overflow: 'hidden'
		});
	},

	thawContentHeight: function() {
		this.contentEl.css({
			width: '',
			height: '',
			overflow: ''
		});
	}

});
<|endoftext|>