<commit_before>import * as THREE from "three"
import * as NORMLIB from "../NormalizationLib.js"

// EXPERIMENTAL

/**
 * called from within JsPlot3D.js class plot
 * @param {object} parent this
 * @param {object} df df
 * @param {object} colors {dfColors, hueOffset}
 * @param {object} columns {x1col, x2col, x3col}
 * @param {object} normalization {normalizeX1, normalizeX2, normalizeX3, x1frac, x2frac, x3frac, minX1, minX2, minX3, maxX1, maxX2, maxX3}
 * @param {object} appearance {keepOldPlot, barchartPadding, barSizeThreshold, dataPointSize}
 * @param {object} dimensions {xLen, yLen, zLen}
 * @private
 */
export default function selforganizingmap(parent, df, colors, columns, normalization, appearance, dimensions)
{

    let x1col = columns.x1col
    let x2col = columns.x2col
    let x3col = columns.x3col

    let x1frac = normalization.x1frac
    let x2frac = normalization.x2frac
    let x3frac = normalization.x3frac

    let minX1 = normalization.minX1
    let minX2 = normalization.minX2
    let minX3 = normalization.minX3

    let maxX1 = normalization.maxX1
    let maxX2 = normalization.maxX2
    let maxX3 = normalization.maxX3

    let xLen = dimensions.xLen
    let yLen = dimensions.yLen
    let zLen = dimensions.zLen

    let xRes = dimensions.xRes
    let zRes = dimensions.zRes

    let keepOldPlot = appearance.keepOldPlot

    let datapointgeometry = new THREE.Geometry()

    // finding min and max
    let minmax
    minmax = NORMLIB.getMinMax(df, x1col, parent.oldData, keepOldPlot, minX1, maxX1)
    minX1 = minmax.min
    maxX1 = minmax.max
    minmax = NORMLIB.getMinMax(df, x2col, parent.oldData, keepOldPlot, minX2, maxX2)
    minX2 = minmax.min
    maxX2 = minmax.max
    minmax = NORMLIB.getMinMax(df, x3col, parent.oldData, keepOldPlot, minX3, maxX3)
    minX3 = minmax.min
    maxX3 = minmax.max
    x1frac = Math.abs(maxX1-minX1)
    if(x1frac === 0) x1frac = 1 // prevent division by zero
    x2frac = Math.abs(maxX2-minX2)
    if(x2frac === 0) x2frac = 1
    x3frac = Math.abs(maxX3-minX3)
    if(x3frac === 0) x3frac = 1

    parent.disposePlotMesh()

    // create plane, divided into segments
    let planegeometry = new THREE.PlaneGeometry(xLen, zLen, xRes, zRes)
    // move it
    planegeometry.rotateX(Math.PI/2)
    planegeometry.translate(xLen/2,0, zLen/2)
    //
    // color the plane
    let plotmat = new THREE.MeshNormalMaterial({
        side: THREE.DoubleSide,
        vertexColors: THREE.VertexColors,
        roughness: 0.85
    })
    //
    for(let i = 0;i < planegeometry.faces.length; i++)
    {
        let faceColors = planegeometry.faces[i].vertexColors
        faceColors[0] = new THREE.Color(0)
        faceColors[1] = new THREE.Color(0)
        faceColors[2] = new THREE.Color(0)
    }
    //
    let mesh = new THREE.Mesh(planegeometry, plotmat)
    mesh.frustumCulled = false // that is not needed, because there is only the centered plot object (no need for three.js to check and compute boundingSpheres -> more performance)
    parent.plotmesh = mesh
    parent.plotmesh.name = "selforganizingmap"
    parent.SceneHelper.scene.add(parent.plotmesh)

    for(let i = 0; i < df.length; i ++)
    {
        let vertex = new THREE.Vector3()
        vertex.x = df[i][x1col]
        vertex.y = df[i][x2col]
        vertex.z = df[i][x3col]

        // three.js handles invalid vertex already by skipping them
        datapointgeometry.vertices.push(vertex)
    }

    // normalize
    datapointgeometry.scale(xLen/x1frac, yLen/x2frac, zLen/x3frac)
    datapointgeometry.translate(-minX1/x1frac*xLen, -minX2/x2frac*yLen, -minX3/x3frac*zLen)

    // now geometry contains the normalized datapoints

    // SOM Algorithm starts here
    for(let i = 0;i < planegeometry.vertices.length;i++)
    {
        let vertex = planegeometry.vertices[i]
        vertex.xOriginal = vertex.x
        vertex.yOriginal = vertex.y
        vertex.zOriginal = vertex.z
    }

    for(let t = 1;t < 1000;t++)
    {
        // 1. select random datapoint from the datapointgeometry, because it is normalized and stuff
        let index = Math.random() * datapointgeometry.vertices.length | 0
        let datapoint = datapointgeometry.vertices[index]

        // 2. find nearest vertex from SOM (which is planegeometry)
        let bestMatchingUnit = planegeometry.vertices[0]
        let oldDist = Infinity
        for(let i = 0;i < planegeometry.vertices.length; i++)
        {
            let checkvertex = planegeometry.vertices[i]
            let dist = checkvertex.distanceTo(datapoint)

            if(dist < oldDist)
            {
                bestMatchingUnit = checkvertex
                oldDist = dist
            }
        }

        // 3. loop over neighborhood
        for(let v = 0;v < planegeometry.vertices.length; v++)
        {
            // get current weights
            let weight = planegeometry.vertices[v]

            // euclidian distance
            let xdist = weight.x - bestMatchingUnit.x
            let zdist = weight.z - bestMatchingUnit.z
            let distance = Math.sqrt(Math.pow(xdist, 2) + Math.pow(zdist, 2))

            // learning rate depends on progress
            let a = (t) => {
                return 1/(t+1)
            }

            // neighborhood function
            let O = (distance) => {
                let c = 0.1
                let gauss = Math.pow(Math.E, - Math.pow(distance, 2) / c)
                return gauss
            }

            // weight update
            weight.x = weight.x + O(distance) * a(t) * (datapoint.x - weight.x)
            weight.y = weight.y + O(distance) * a(t) * (datapoint.y - weight.y)
            weight.z = weight.z + O(distance) * a(t) * (datapoint.z - weight.z)
        }

        mesh.geometry.computeFaceNormals()
        mesh.geometry.computeVertexNormals()
        mesh.geometry.verticesNeedUpdate = true
        parent.SceneHelper.render()

    }

    // normals need to be recomputed so that the lighting works after the transformation
    mesh.geometry.computeFaceNormals()
    mesh.geometry.computeVertexNormals()
    mesh.geometry.__dirtyNormals = true
    // make sure the updated mesh is actually rendered
    mesh.geometry.verticesNeedUpdate = true
    mesh.geometry.colorsNeedUpdate = true

    mesh.material.needsUpdate = true

    parent.SceneHelper.makeSureItRenders(parent.animationFunc)

    // return by using pointers
    normalization.minX2 = minX2
    normalization.maxX2 = maxX2

    parent.benchmarkStamp("made a scatterplot")
}<commit_msg>improved the selforganizingmap mode, but it's still rather experimental<commit_after>import * as THREE from "three"
import * as NORMLIB from "../NormalizationLib.js"

// EXPERIMENTAL

/**
 * called from within JsPlot3D.js class plot
 * @param {object} parent this
 * @param {object} df df
 * @param {object} colors {dfColors, hueOffset}
 * @param {object} columns {x1col, x2col, x3col}
 * @param {object} normalization {normalizeX1, normalizeX2, normalizeX3, x1frac, x2frac, x3frac, minX1, minX2, minX3, maxX1, maxX2, maxX3}
 * @param {object} appearance {keepOldPlot, barchartPadding, barSizeThreshold, dataPointSize}
 * @param {object} dimensions {xLen, yLen, zLen}
 * @private
 */
export default function selforganizingmap(parent, df, colors, columns, normalization, appearance, dimensions)
{

    let x1col = columns.x1col
    let x2col = columns.x2col
    let x3col = columns.x3col

    let x1frac = normalization.x1frac
    let x2frac = normalization.x2frac
    let x3frac = normalization.x3frac

    let minX1 = normalization.minX1
    let minX2 = normalization.minX2
    let minX3 = normalization.minX3

    let maxX1 = normalization.maxX1
    let maxX2 = normalization.maxX2
    let maxX3 = normalization.maxX3

    let xLen = dimensions.xLen
    let yLen = dimensions.yLen
    let zLen = dimensions.zLen

    let xRes = dimensions.xRes
    let zRes = dimensions.zRes

    let keepOldPlot = appearance.keepOldPlot

    let datapointgeometry = new THREE.Geometry()

    // finding min and max
    let minmax
    minmax = NORMLIB.getMinMax(df, x1col, parent.oldData, keepOldPlot, minX1, maxX1)
    minX1 = minmax.min
    maxX1 = minmax.max
    minmax = NORMLIB.getMinMax(df, x2col, parent.oldData, keepOldPlot, minX2, maxX2)
    minX2 = minmax.min
    maxX2 = minmax.max
    minmax = NORMLIB.getMinMax(df, x3col, parent.oldData, keepOldPlot, minX3, maxX3)
    minX3 = minmax.min
    maxX3 = minmax.max
    x1frac = Math.abs(maxX1-minX1)
    if(x1frac === 0) x1frac = 1 // prevent division by zero
    x2frac = Math.abs(maxX2-minX2)
    if(x2frac === 0) x2frac = 1
    x3frac = Math.abs(maxX3-minX3)
    if(x3frac === 0) x3frac = 1

    parent.disposePlotMesh()

    // create plane, divided into segments
    let planegeometry = new THREE.PlaneGeometry(xLen, zLen, xRes, zRes)
    /*let xVerticesCount = (2+(xRes-1))
    let zVerticesCount = (2+(zRes-1))*/
    // move it
    planegeometry.rotateX(Math.PI/2)
    planegeometry.translate(xLen/2,0, zLen/2)
    //
    // color the plane
    let plotmat = new THREE.MeshNormalMaterial({
        side: THREE.DoubleSide,
        vertexColors: THREE.VertexColors,
        roughness: 0.85
    })
    //
    for(let i = 0;i < planegeometry.faces.length; i++)
    {
        let faceColors = planegeometry.faces[i].vertexColors
        faceColors[0] = new THREE.Color(0)
        faceColors[1] = new THREE.Color(0)
        faceColors[2] = new THREE.Color(0)
    }
    //
    let mesh = new THREE.Mesh(planegeometry, plotmat)
    mesh.frustumCulled = false // that is not needed, because there is only the centered plot object (no need for three.js to check and compute boundingSpheres -> more performance)
    parent.plotmesh = mesh
    parent.plotmesh.name = "selforganizingmap"
    parent.SceneHelper.scene.add(parent.plotmesh)

    for(let i = 0; i < df.length; i ++)
    {
        let vertex = new THREE.Vector3()
        vertex.x = df[i][x1col]
        vertex.y = df[i][x2col]
        vertex.z = df[i][x3col]

        // three.js handles invalid vertex already by skipping them
        datapointgeometry.vertices.push(vertex)
    }

    // normalize
    datapointgeometry.scale(xLen/x1frac, yLen/x2frac, zLen/x3frac)
    datapointgeometry.translate(-minX1/x1frac*xLen, -minX2/x2frac*yLen, -minX3/x3frac*zLen)

    // now geometry contains the normalized datapoints

    // SOM Algorithm starts here
    let datapoints = datapointgeometry.vertices
    let vertices = planegeometry.vertices

    /*// store all the vertex in a 2D array
    let vertices = new Array(xVerticesCount)
    for(let i = 0;i < vertices.length;i++)
    {
        vertices[i] = new Array(zVerticesCount)
    }
    for(let i = 0;i < planegeometry.vertices.length;i++)
    {
        let vertex = planegeometry.vertices[i]
        let a = Math.round(vertex.x*xRes)
        let b = Math.round(vertex.z*zRes)
        vertices[a][b] = vertex
    }*/

    // learning rate. starts at 1 and slowly decreases
    // decreasing learning rates make the plot smoother
    let a = (t) => {
        return 4/(t+4)
    }

    // neighborhood function
    let O = (distance, t) => {
        let c = 2/(t+4) // decrease the radius of the gaussian function over time
        // a high numinator of c makes the plot more smooth

        let gauss = Math.pow(Math.E, - distance / c) // distance is already squared
        return gauss
    }

    // T iterations over all the datapoints
    let datapoint, guess, checkvertex, oldDist, xdist, zdist, distance, bestMatchingUnit, weight
    for(let t = 0;t < 80;t++)
    {
        // iterate once over all datapoints
        for(let index = 0;index < datapoints.length; index++)
        {
            // 1. select datapoint
            datapoint = datapoints[Math.random()*datapoints.length|0]

            // the first vertex is at x:0 and z:1
            // first x counts up, then z counts down and x resets to 0, when x reaches the maximum x
            // make a guess where the nearest vertex might be. use xRes and zRes isntead of the verticesCount to avoid making a too large index guess
            // the iterations below will quickly find the bestMatchingUnit because only a few iterations are needed because of the already good guess
            guess = Math.round((xLen - datapoint.z) * xRes * zRes + datapoint.x * xRes)

            // 2. find nearest vertex from SOM (which is planegeometry)
            checkvertex = vertices[guess]
            oldDist = Infinity
            xdist, zdist, distance, bestMatchingUnit
            for(let i = guess;i < vertices.length; i++)
            {
                xdist = checkvertex.x - datapoint.x
                zdist = checkvertex.z - datapoint.z

                // closest possible vertex already found? (this will quickly be true because of the guess)
                if(Math.abs(xdist) < xLen/xRes && Math.abs(zdist) < zLen/zRes)
                {
                    break
                }

                checkvertex = vertices[i]
                // squared euclidian distance, don't check y
                distance = Math.pow(xdist, 2) + Math.pow(zdist, 2)

                // is it closer?
                if(distance < oldDist)
                {
                    bestMatchingUnit = checkvertex
                    oldDist = distance
                }
            }

            // 3. loop over neighborhood
            for(let v = 0;v < vertices.length; v++)
            {
                // get current weights
                weight = vertices[v]

                // squared euclidian distance, don't check y
                xdist = weight.x - bestMatchingUnit.x
                zdist = weight.z - bestMatchingUnit.z
                distance = Math.pow(xdist, 2) + Math.pow(zdist, 2)
                // let distance = weight.distanceTo(bestMatchingUnit)

                // weight update
                // restrict it to updating the y-value
                // weight.x = weight.x + O(distance, t) * a(t) * (datapoint.x - weight.x)
                weight.y = weight.y + O(distance, t) * a(t) * (datapoint.y - weight.y)
                // weight.z = weight.z + O(distance, t) * a(t) * (datapoint.z - weight.z)
            }
        }

        // uncomment this to watch the SOM work during debugging
        /*mesh.geometry.computeFaceNormals()
        mesh.geometry.computeVertexNormals()
        mesh.geometry.verticesNeedUpdate = true
        parent.SceneHelper.render()*/
    }

    // normals need to be recomputed so that the lighting works after the transformation
    mesh.geometry.computeFaceNormals()
    mesh.geometry.computeVertexNormals()
    mesh.geometry.__dirtyNormals = true
    // make sure the updated mesh is actually rendered
    mesh.geometry.verticesNeedUpdate = true
    mesh.geometry.colorsNeedUpdate = true

    mesh.material.needsUpdate = true

    parent.SceneHelper.makeSureItRenders(parent.animationFunc)

    // return by using pointers
    normalization.minX2 = minX2
    normalization.maxX2 = maxX2

    parent.benchmarkStamp("made a scatterplot")
}<|endoftext|>


<commit_before>import forIn from '../core/for-in'
import resize from '../events/resize'
import unresize from '../events/unresize'

export class BreakpointListener {
    constructor(breakpoints) {
        this._current     = ''
        this._listeners   = []
        this._breakpoints = []

        forIn(breakpoints, (key, value) => {
            this._breakpoints.push({
                size: value,
                label: key
            })
        })

        this._breakpoints.sort((a, b) => a.size > b.size)

        this.resizeCallback = resize(this.resize.bind(this))

        this.resize({
            width: window.innerWidth
        })
    }

    set current(value) {
        if (this._current == value) return

        this._current = value

        this.trigger()
    }

    get current() {
        return this._current
    }

    trigger() {
        for (let listener of this._listeners) {
            listener(this._current)
        }
    }

    resize(size) {
        let width = size.width,
            i = this._breakpoints.length

        while(i-- && this._breakpoints[i].size > width){}

        this.current = this._breakpoints[Math.max(i, 0)].label
    }

    on(callback) {
        this._listeners.push(callback)
    }

    off(callback) {
        this._listeners.splice(this._listeners.indexOf(callback), 1)
    }

    kill() {
        unresize(this.resizeCallback)
    }
}

export default BreakpointListener
<commit_msg>fix breakpoint-listener<commit_after>import forIn from '../core/for-in'
import resize from '../events/resize'
import unresize from '../events/unresize'

export class BreakpointListener {
    constructor(breakpoints) {
        this._current     = ''
        this._listeners   = []
        this._breakpoints = []

        forIn(breakpoints, (key, value) => {
            this._breakpoints.push({
                size: value,
                label: key
            })
        })

        this._breakpoints.sort((a, b) => b.size - a.size)

        this.resizeCallback = resize(this.resize.bind(this))

        this.resize({
            width: window.innerWidth
        })
    }

    set current(value) {
        if (this._current == value) return

        this._current = value

        this.trigger()
    }

    get current() {
        return this._current
    }

    trigger() {
        for (let listener of this._listeners) {
            listener(this._current)
        }
    }

    resize(size) {
        let width = size.width,
            len = this._breakpoints.length,
            i = 0

        while(i+1 < len && this._breakpoints[i+1].size > width) ++i

        this.current = this._breakpoints[Math.max(i, 0)].label
    }

    on(callback) {
        this._listeners.push(callback)
    }

    off(callback) {
        this._listeners.splice(this._listeners.indexOf(callback), 1)
    }

    kill() {
        unresize(this.resizeCallback)
    }
}

export default BreakpointListener
<|endoftext|>


<commit_before>/**
 * Event dispatcher factory
 *
 * Although you should generally avoid multiple dispatchers in the same
 * project, it may be useful in case there are multiple bounded contexts
 * within the same project.
 *
 * @returns {function} The dispatcher factory
 */
malachi = (function(){

  /**
   * Get the name of an event signature
   *
   * @param {function} signature
   * @returns {string}
   */
  function nameOf(signature) {

    if(typeof signature !== "function") {
      throw new TypeError("Event signature must be a function");
    }

    let name = signature.toString();
    name = name.substr('function '.length);
    name = name.substr(0, name.indexOf('('));

    if(name === "") {
      throw new Error("Event signature must be a named function");
    }

    return name;
  }

  /**
   * Factory returning a dispatcher instance
   *
   * @returns {object}
   */
  return function() {

    /**
     * A map of event signatures and their registered handlers
     *
     * @type {object}
     */
    const handlers = {};

    return {

      /**
       * Register a handler to an event signature
       *
       * Multiple handlers can be registered to the same signature. Returns
       * the dispatcher instance for chaining.
       *
       * @param signature {function}
       * @param handler {function}
       * @returns {object}
       */
      on(signature, handler) {
        type = nameOf(signature);

        if(typeof handler !== "function") {
          throw new TypeError("Event handler must be a function");
        }

        if(typeof handlers[type] === "undefined") {
          handlers[type] = [];
        }

        handlers[type].push(handler);

        return this;
      },

      /**
       * Fire an event
       *
       * Calls all handlers registered to the event signature with this event's
       * data.
       *
       * @param signature
       * @param event
       */
      fire(signature, event) {
        const type = nameOf(signature);

        if (typeof handlers[type] === "undefined") {
          throw new ReferenceError(`No handlers registered for ${type}`);
        }

        for (const handler of handlers[type]) {
          handler(event);
        }
      }
    };
  }
}());
<commit_msg>fire() now throws if it is called with more than two arguments<commit_after>/**
 * Event dispatcher factory
 *
 * Although you should generally avoid multiple dispatchers in the same
 * project, it may be useful in case there are multiple bounded contexts
 * within the same project.
 *
 * @returns {function} The dispatcher factory
 */
malachi = (function(){

  /**
   * Get the name of an event signature
   *
   * @param {function} signature
   * @returns {string}
   */
  function nameOf(signature) {

    if(typeof signature !== "function") {
      throw new TypeError("Event signature must be a function");
    }

    let name = signature.toString();
    name = name.substr('function '.length);
    name = name.substr(0, name.indexOf('('));

    if(name === "") {
      throw new Error("Event signature must be a named function");
    }

    return name;
  }

  /**
   * Factory returning a dispatcher instance
   *
   * @returns {object}
   */
  return function() {

    /**
     * A map of event signatures and their registered handlers
     *
     * @type {object}
     */
    const handlers = {};

    return {

      /**
       * Register a handler to an event signature
       *
       * Multiple handlers can be registered to the same signature. Returns
       * the dispatcher instance for chaining.
       *
       * @param signature {function}
       * @param handler {function}
       * @returns {object}
       */
      on(signature, handler) {
        type = nameOf(signature);

        if(typeof handler !== "function") {
          throw new TypeError("Event handler must be a function");
        }

        if(typeof handlers[type] === "undefined") {
          handlers[type] = [];
        }

        handlers[type].push(handler);

        return this;
      },

      /**
       * Fire an event
       *
       * Calls all handlers registered to the event signature with this event's
       * data.
       *
       * @param signature
       * @param event
       */
      fire(signature, event) {
        const type = nameOf(signature);

        if (typeof handlers[type] === "undefined") {
          throw new ReferenceError(`No handlers registered for ${type}`);
        }

        if (arguments.length > 2) {
          throw new Error('Event data should be passed as a single object');
        }

        for (const handler of handlers[type]) {
          handler(event);
        }
      }
    };
  }
}());
<|endoftext|>


<commit_before>/*
 * wysiwyg web editor
 *
 * suneditor.js
 * Copyright 2017 JiHong Lee.
 * MIT license.
 */
'use strict';

(function (global, factory) {
    if (typeof module === 'object' && typeof module.exports === 'object') {
        module.exports = global.document ?
            factory(global, true) :
            function (w) {
                if (!w.document) {
                    throw new Error('SUNEDITOR_MODULES a window with a document');
                }
                return factory(w);
            };
    } else {
        factory(global);
    }
}(typeof window !== 'undefined' ? window : this, function (window, noGlobal) {
    const fileBrowser = {
        name: 'fileBrowser',
        _xmlHttp: null,
        _loading: null,

        /**
         * @description Constructor
         * @param {Object} core Core object
         */
        add: function (core) {
            const context = core.context;
            context.fileBrowser = {
                _closeSignal: false,
                area: null,
                header: null,
                tagArea: null,
                body: null,
                list: null,
                tagElements: null,
                items: [],
                selectedTags: [],
                selectorHandler: null,
                contextPlugin: '',
                columnSize: 4
            };

            /** fileBrowser */
            let browser_div = core.util.createElement('DIV');
            browser_div.className = 'se-file-browser sun-editor-common';

            let back = core.util.createElement('DIV');
            back.className = 'se-file-browser-back';

            let content = core.util.createElement('DIV');
            content.className = 'se-file-browser-inner';
            content.innerHTML = this.set_browser(core);

            browser_div.appendChild(back);
            browser_div.appendChild(content);
            this._loading = browser_div.querySelector('.se-loading-box');

            context.fileBrowser.area = browser_div;
            context.fileBrowser.header = content.querySelector('.se-file-browser-header');
            context.fileBrowser.titleArea = content.querySelector('.se-file-browser-title');
            context.fileBrowser.tagArea = content.querySelector('.se-file-browser-tags');
            context.fileBrowser.body = content.querySelector('.se-file-browser-body');
            context.fileBrowser.list = content.querySelector('.se-file-browser-list');

            /** add event listeners */
            context.fileBrowser.tagArea.addEventListener('click', this.onClickTag.bind(core));
            context.fileBrowser.list.addEventListener('click', this.onClickFile.bind(core));
            content.addEventListener('mousedown', this._onMouseDown_browser.bind(core));
            content.addEventListener('click', this._onClick_browser.bind(core));

            /** append html */
            context.element.relative.appendChild(browser_div);

            /** empty memory */
            browser_div = null, back = null, content = null;
        },

        set_browser: function (core) {
            const lang = core.lang;

            return '
' +
                    '
' +
                        '<button type="button" data-command="close" class="se-btn se-file-browser-close" class="close" aria-label="Close" title="' + lang.dialogBox.close + '">' +
                        core.icons.cancel +
                        '</button>' +
                        '' +
                        '

' +
                    '
' +
                    '
' +
                        '

' +
                        '

' +
                    '
' +
                '
';
        },

        /**
         * @description Event to control the behavior of closing the browser
         * @param {MouseEvent} e Event object
         * @private
         */
        _onMouseDown_browser: function (e) {
            if (/se-file-browser-inner/.test(e.target.className)) {
                this.context.fileBrowser._closeSignal = true;
            } else {
                this.context.fileBrowser._closeSignal = false;
            }
        },

        /**
         * @description Event to close the window when the outside area of the browser or close button is click
         * @param {MouseEvent} e Event object
         * @private
         */
        _onClick_browser: function (e) {
            e.stopPropagation();

            if (/close/.test(e.target.getAttribute('data-command')) || this.context.fileBrowser._closeSignal) {
                this.plugins.fileBrowser.close.call(this);
            }
        },

        /**
         * @description Open a file browser plugin
         * @param {String} pluginName Plugin name using the file browser
         * @param {Function|null} selectorHandler When the function comes as an argument value, it substitutes "context.selectorHandler".
         */
        open: function (pluginName, selectorHandler)  {
            if (this.plugins.fileBrowser._bindClose) {
                this._d.removeEventListener('keydown', this.plugins.fileBrowser._bindClose);
                this.plugins.fileBrowser._bindClose = null;
            }

            this.plugins.fileBrowser._bindClose = function (e) {
                if (!/27/.test(e.keyCode)) return;
                this.plugins.fileBrowser.close.call(this);
            }.bind(this);
            this._d.addEventListener('keydown', this.plugins.fileBrowser._bindClose);

            const fileBrowserContext = this.context.fileBrowser;
            fileBrowserContext.contextPlugin = pluginName;
            fileBrowserContext.selectorHandler = selectorHandler;

            const pluginContext = this.context[pluginName];
            const listClassName = pluginContext.listClass;
            if (!this.util.hasClass(fileBrowserContext.list, listClassName)) {
                fileBrowserContext.list.className = 'se-file-browser-list ' + listClassName;
            }

            if (this.options.popupDisplay === 'full') {
                fileBrowserContext.area.style.position = 'fixed';
            } else {
                fileBrowserContext.area.style.position = 'absolute';
            }

            fileBrowserContext.titleArea.textContent = pluginContext.title;
            fileBrowserContext.area.style.display = 'block';

            this.plugins.fileBrowser._drawFileList.call(this, this.context[pluginName].url, this.context[pluginName].header);
        },

        _bindClose: null,

        /**
         * @description Close a fileBrowser plugin
         * The plugin's "init" method is called.
         */
        close: function () {
            const fileBrowserPlugin = this.plugins.fileBrowser;

            if (fileBrowserPlugin._xmlHttp) {
                fileBrowserPlugin._xmlHttp.abort();
            }

            if (fileBrowserPlugin._bindClose) {
                this._d.removeEventListener('keydown', fileBrowserPlugin._bindClose);
                fileBrowserPlugin._bindClose = null;
            }

            const fileBrowserContext = this.context.fileBrowser;
            fileBrowserContext.area.style.display = 'none';
            fileBrowserContext.selectorHandler = null;
            fileBrowserContext.selectedTags = [];
            fileBrowserContext.items = [];
            fileBrowserContext.list.innerHTML = fileBrowserContext.tagArea.innerHTML = fileBrowserContext.titleArea.textContent = '';

            if (typeof this.plugins[fileBrowserContext.contextPlugin].init === 'function') this.plugins[fileBrowserContext.contextPlugin].init.call(this);
            fileBrowserContext.contextPlugin = '';
        },

        /**
         * @description Show file browser loading box
         */
        showBrowserLoading: function () {
            this._loading.style.display = 'block';
        },

        /**
         * @description Close file browser loading box
         */
        closeBrowserLoading: function () {
            this._loading.style.display = 'none';
        },

        _drawFileList: function (url, browserHeader) {
            const fileBrowserPlugin = this.plugins.fileBrowser;

            const xmlHttp = fileBrowserPlugin._xmlHttp = this.util.getXMLHttpRequest();
            xmlHttp.onreadystatechange = fileBrowserPlugin._callBackGet.bind(this, xmlHttp);
            if(browserHeader !== null && typeof browserHeader === 'object' && this._w.Object.keys(browserHeader).length > 0){
                for(let key in browserHeader){
                    xmlHttp.setRequestHeader(key, browserHeader[key]);
                }
            }
            xmlHttp.open('get', url, true);
            xmlHttp.send(null);

            this.plugins.fileBrowser.showBrowserLoading();
        },

        _callBackGet: function (xmlHttp) {
            if (xmlHttp.readyState === 4) {
                this.plugins.fileBrowser._xmlHttp = null;
                if (xmlHttp.status === 200) {
                    try {
                        this.plugins.fileBrowser._drawListItem.call(this, JSON.parse(xmlHttp.responseText).result, true);
                    } catch (e) {
                        throw Error('[SUNEDITOR.fileBrowser.drawList.fail] cause : "' + e.message + '"');
                    } finally {
                        this.plugins.fileBrowser.closeBrowserLoading();
                        this.context.fileBrowser.body.style.maxHeight = (this._w.innerHeight - this.context.fileBrowser.header.offsetHeight - 50) + 'px';
                    }
                } else { // exception
                    this.plugins.fileBrowser.closeBrowserLoading();
                    if (xmlHttp.status !== 0) {
                        const res = !xmlHttp.responseText ? xmlHttp : JSON.parse(xmlHttp.responseText);
                        const err = '[SUNEDITOR.fileBrowser.get.serverException] status: ' + xmlHttp.status + ', response: ' + (res.errorMessage || xmlHttp.responseText);
                        throw Error(err);
                    }
                }
            }
        },

        _drawListItem: function (items, update) {
            const fileBrowserContext = this.context.fileBrowser;
            const pluginContext = this.context[fileBrowserContext.contextPlugin];

            const _tags = [];
            const len = items.length;
            const columnSize = pluginContext.columnSize || fileBrowserContext.columnSize;
            const splitSize = columnSize <= 1 ? 1 : (Math.round(len/columnSize) || 1);
            const drawItemHandler = pluginContext.itemTemplateHandler;

            let tagsHTML = '';
            let listHTML = '
';
            let columns = 1;
            for (let i = 0, item, tags; i < len; i++) {
                item = items[i];
                tags = !item.tag ? [] : typeof item.tag === 'string' ? item.tag.split(',') : item.tag;
                tags = item.tag = tags.map(function (v) { return v.trim(); });
                listHTML += drawItemHandler(item);

                if ((i + 1) % splitSize === 0 && columns < columnSize && (i + 1) < len) {
                    columns++;
                    listHTML += '

';
                }

                if (update && tags.length > 0) {
                    for (let t = 0, tLen = tags.length, tag; t < tLen; t++) {
                        tag = tags[t];
                        if (tag && _tags.indexOf(tag) === -1) {
                            _tags.push(tag);
                            tagsHTML += '' + tag + '';
                        }
                    }
                }
            }
            listHTML += '
';

            fileBrowserContext.list.innerHTML = listHTML;

            if (update) {
                fileBrowserContext.items = items;
                fileBrowserContext.tagArea.innerHTML = tagsHTML;
                fileBrowserContext.tagElements = fileBrowserContext.tagArea.querySelectorAll('A');
            }
        },

        onClickTag: function (e) {
            const target = e.target;
            if (!this.util.isAnchor(target)) return;

            const tagName = target.textContent;
            const fileBrowserPlugin = this.plugins.fileBrowser;
            const fileBrowserContext = this.context.fileBrowser;

            const selectTag = fileBrowserContext.tagArea.querySelector('a[title="' + tagName + '"]');
            const selectedTags = fileBrowserContext.selectedTags;
            const sTagIndex = selectedTags.indexOf(tagName);

            if (sTagIndex > -1){
                selectedTags.splice(sTagIndex, 1);
                this.util.removeClass(selectTag, 'on');
            } else {
                selectedTags.push(tagName);
                this.util.addClass(selectTag, 'on');
            }

            fileBrowserPlugin._drawListItem.call(this,
                selectedTags.length === 0 ?
                fileBrowserContext.items :
                fileBrowserContext.items.filter(function (item) {
                    return item.tag.some(function (tag) {
                        return selectedTags.indexOf(tag) > -1;
                    });
                }), false);
        },

        onClickFile: function (e) {
            e.preventDefault();
            e.stopPropagation();

            const fileBrowserContext = this.context.fileBrowser;
            const listEl = fileBrowserContext.list;
            let target = e.target;
            let command = null;

            if (target === listEl) return;

            while (listEl !== target.parentNode) {
                command = target.getAttribute('data-command');
                if (command) break;
                target = target.parentNode;
            }

            if (!command) return;

            const handler = (fileBrowserContext.selectorHandler || this.context[fileBrowserContext.contextPlugin].selectorHandler);
            this.plugins.fileBrowser.close.call(this);
            handler(target);
        }
    };

    if (typeof noGlobal === typeof undefined) {
        if (!window.SUNEDITOR_MODULES) {
            Object.defineProperty(window, 'SUNEDITOR_MODULES', {
                enumerable: true,
                writable: false,
                configurable: false,
                value: {}
            });
        }

        Object.defineProperty(window.SUNEDITOR_MODULES, 'fileBrowser', {
            enumerable: true,
            writable: false,
            configurable: false,
            value: fileBrowser
        });
    }

    return fileBrowser;
}));<commit_msg>fix imageGallery RequestHeader has to be after xmlHttp.open<commit_after>/*
 * wysiwyg web editor
 *
 * suneditor.js
 * Copyright 2017 JiHong Lee.
 * MIT license.
 */
'use strict';

(function (global, factory) {
    if (typeof module === 'object' && typeof module.exports === 'object') {
        module.exports = global.document ?
            factory(global, true) :
            function (w) {
                if (!w.document) {
                    throw new Error('SUNEDITOR_MODULES a window with a document');
                }
                return factory(w);
            };
    } else {
        factory(global);
    }
}(typeof window !== 'undefined' ? window : this, function (window, noGlobal) {
    const fileBrowser = {
        name: 'fileBrowser',
        _xmlHttp: null,
        _loading: null,

        /**
         * @description Constructor
         * @param {Object} core Core object
         */
        add: function (core) {
            const context = core.context;
            context.fileBrowser = {
                _closeSignal: false,
                area: null,
                header: null,
                tagArea: null,
                body: null,
                list: null,
                tagElements: null,
                items: [],
                selectedTags: [],
                selectorHandler: null,
                contextPlugin: '',
                columnSize: 4
            };

            /** fileBrowser */
            let browser_div = core.util.createElement('DIV');
            browser_div.className = 'se-file-browser sun-editor-common';

            let back = core.util.createElement('DIV');
            back.className = 'se-file-browser-back';

            let content = core.util.createElement('DIV');
            content.className = 'se-file-browser-inner';
            content.innerHTML = this.set_browser(core);

            browser_div.appendChild(back);
            browser_div.appendChild(content);
            this._loading = browser_div.querySelector('.se-loading-box');

            context.fileBrowser.area = browser_div;
            context.fileBrowser.header = content.querySelector('.se-file-browser-header');
            context.fileBrowser.titleArea = content.querySelector('.se-file-browser-title');
            context.fileBrowser.tagArea = content.querySelector('.se-file-browser-tags');
            context.fileBrowser.body = content.querySelector('.se-file-browser-body');
            context.fileBrowser.list = content.querySelector('.se-file-browser-list');

            /** add event listeners */
            context.fileBrowser.tagArea.addEventListener('click', this.onClickTag.bind(core));
            context.fileBrowser.list.addEventListener('click', this.onClickFile.bind(core));
            content.addEventListener('mousedown', this._onMouseDown_browser.bind(core));
            content.addEventListener('click', this._onClick_browser.bind(core));

            /** append html */
            context.element.relative.appendChild(browser_div);

            /** empty memory */
            browser_div = null, back = null, content = null;
        },

        set_browser: function (core) {
            const lang = core.lang;

            return '
' +
                    '
' +
                        '<button type="button" data-command="close" class="se-btn se-file-browser-close" class="close" aria-label="Close" title="' + lang.dialogBox.close + '">' +
                        core.icons.cancel +
                        '</button>' +
                        '' +
                        '

' +
                    '
' +
                    '
' +
                        '

' +
                        '

' +
                    '
' +
                '
';
        },

        /**
         * @description Event to control the behavior of closing the browser
         * @param {MouseEvent} e Event object
         * @private
         */
        _onMouseDown_browser: function (e) {
            if (/se-file-browser-inner/.test(e.target.className)) {
                this.context.fileBrowser._closeSignal = true;
            } else {
                this.context.fileBrowser._closeSignal = false;
            }
        },

        /**
         * @description Event to close the window when the outside area of the browser or close button is click
         * @param {MouseEvent} e Event object
         * @private
         */
        _onClick_browser: function (e) {
            e.stopPropagation();

            if (/close/.test(e.target.getAttribute('data-command')) || this.context.fileBrowser._closeSignal) {
                this.plugins.fileBrowser.close.call(this);
            }
        },

        /**
         * @description Open a file browser plugin
         * @param {String} pluginName Plugin name using the file browser
         * @param {Function|null} selectorHandler When the function comes as an argument value, it substitutes "context.selectorHandler".
         */
        open: function (pluginName, selectorHandler)  {
            if (this.plugins.fileBrowser._bindClose) {
                this._d.removeEventListener('keydown', this.plugins.fileBrowser._bindClose);
                this.plugins.fileBrowser._bindClose = null;
            }

            this.plugins.fileBrowser._bindClose = function (e) {
                if (!/27/.test(e.keyCode)) return;
                this.plugins.fileBrowser.close.call(this);
            }.bind(this);
            this._d.addEventListener('keydown', this.plugins.fileBrowser._bindClose);

            const fileBrowserContext = this.context.fileBrowser;
            fileBrowserContext.contextPlugin = pluginName;
            fileBrowserContext.selectorHandler = selectorHandler;

            const pluginContext = this.context[pluginName];
            const listClassName = pluginContext.listClass;
            if (!this.util.hasClass(fileBrowserContext.list, listClassName)) {
                fileBrowserContext.list.className = 'se-file-browser-list ' + listClassName;
            }

            if (this.options.popupDisplay === 'full') {
                fileBrowserContext.area.style.position = 'fixed';
            } else {
                fileBrowserContext.area.style.position = 'absolute';
            }

            fileBrowserContext.titleArea.textContent = pluginContext.title;
            fileBrowserContext.area.style.display = 'block';

            this.plugins.fileBrowser._drawFileList.call(this, this.context[pluginName].url, this.context[pluginName].header);
        },

        _bindClose: null,

        /**
         * @description Close a fileBrowser plugin
         * The plugin's "init" method is called.
         */
        close: function () {
            const fileBrowserPlugin = this.plugins.fileBrowser;

            if (fileBrowserPlugin._xmlHttp) {
                fileBrowserPlugin._xmlHttp.abort();
            }

            if (fileBrowserPlugin._bindClose) {
                this._d.removeEventListener('keydown', fileBrowserPlugin._bindClose);
                fileBrowserPlugin._bindClose = null;
            }

            const fileBrowserContext = this.context.fileBrowser;
            fileBrowserContext.area.style.display = 'none';
            fileBrowserContext.selectorHandler = null;
            fileBrowserContext.selectedTags = [];
            fileBrowserContext.items = [];
            fileBrowserContext.list.innerHTML = fileBrowserContext.tagArea.innerHTML = fileBrowserContext.titleArea.textContent = '';

            if (typeof this.plugins[fileBrowserContext.contextPlugin].init === 'function') this.plugins[fileBrowserContext.contextPlugin].init.call(this);
            fileBrowserContext.contextPlugin = '';
        },

        /**
         * @description Show file browser loading box
         */
        showBrowserLoading: function () {
            this._loading.style.display = 'block';
        },

        /**
         * @description Close file browser loading box
         */
        closeBrowserLoading: function () {
            this._loading.style.display = 'none';
        },

        _drawFileList: function (url, browserHeader) {
            const fileBrowserPlugin = this.plugins.fileBrowser;

            const xmlHttp = fileBrowserPlugin._xmlHttp = this.util.getXMLHttpRequest();
            xmlHttp.onreadystatechange = fileBrowserPlugin._callBackGet.bind(this, xmlHttp);
            xmlHttp.open('get', url, true);
            if(browserHeader !== null && typeof browserHeader === 'object' && this._w.Object.keys(browserHeader).length > 0){
                for(let key in browserHeader){
                    xmlHttp.setRequestHeader(key, browserHeader[key]);
                }
            }
            xmlHttp.send(null);

            this.plugins.fileBrowser.showBrowserLoading();
        },

        _callBackGet: function (xmlHttp) {
            if (xmlHttp.readyState === 4) {
                this.plugins.fileBrowser._xmlHttp = null;
                if (xmlHttp.status === 200) {
                    try {
                        this.plugins.fileBrowser._drawListItem.call(this, JSON.parse(xmlHttp.responseText).result, true);
                    } catch (e) {
                        throw Error('[SUNEDITOR.fileBrowser.drawList.fail] cause : "' + e.message + '"');
                    } finally {
                        this.plugins.fileBrowser.closeBrowserLoading();
                        this.context.fileBrowser.body.style.maxHeight = (this._w.innerHeight - this.context.fileBrowser.header.offsetHeight - 50) + 'px';
                    }
                } else { // exception
                    this.plugins.fileBrowser.closeBrowserLoading();
                    if (xmlHttp.status !== 0) {
                        const res = !xmlHttp.responseText ? xmlHttp : JSON.parse(xmlHttp.responseText);
                        const err = '[SUNEDITOR.fileBrowser.get.serverException] status: ' + xmlHttp.status + ', response: ' + (res.errorMessage || xmlHttp.responseText);
                        throw Error(err);
                    }
                }
            }
        },

        _drawListItem: function (items, update) {
            const fileBrowserContext = this.context.fileBrowser;
            const pluginContext = this.context[fileBrowserContext.contextPlugin];

            const _tags = [];
            const len = items.length;
            const columnSize = pluginContext.columnSize || fileBrowserContext.columnSize;
            const splitSize = columnSize <= 1 ? 1 : (Math.round(len/columnSize) || 1);
            const drawItemHandler = pluginContext.itemTemplateHandler;

            let tagsHTML = '';
            let listHTML = '
';
            let columns = 1;
            for (let i = 0, item, tags; i < len; i++) {
                item = items[i];
                tags = !item.tag ? [] : typeof item.tag === 'string' ? item.tag.split(',') : item.tag;
                tags = item.tag = tags.map(function (v) { return v.trim(); });
                listHTML += drawItemHandler(item);

                if ((i + 1) % splitSize === 0 && columns < columnSize && (i + 1) < len) {
                    columns++;
                    listHTML += '

';
                }

                if (update && tags.length > 0) {
                    for (let t = 0, tLen = tags.length, tag; t < tLen; t++) {
                        tag = tags[t];
                        if (tag && _tags.indexOf(tag) === -1) {
                            _tags.push(tag);
                            tagsHTML += '' + tag + '';
                        }
                    }
                }
            }
            listHTML += '
';

            fileBrowserContext.list.innerHTML = listHTML;

            if (update) {
                fileBrowserContext.items = items;
                fileBrowserContext.tagArea.innerHTML = tagsHTML;
                fileBrowserContext.tagElements = fileBrowserContext.tagArea.querySelectorAll('A');
            }
        },

        onClickTag: function (e) {
            const target = e.target;
            if (!this.util.isAnchor(target)) return;

            const tagName = target.textContent;
            const fileBrowserPlugin = this.plugins.fileBrowser;
            const fileBrowserContext = this.context.fileBrowser;

            const selectTag = fileBrowserContext.tagArea.querySelector('a[title="' + tagName + '"]');
            const selectedTags = fileBrowserContext.selectedTags;
            const sTagIndex = selectedTags.indexOf(tagName);

            if (sTagIndex > -1){
                selectedTags.splice(sTagIndex, 1);
                this.util.removeClass(selectTag, 'on');
            } else {
                selectedTags.push(tagName);
                this.util.addClass(selectTag, 'on');
            }

            fileBrowserPlugin._drawListItem.call(this,
                selectedTags.length === 0 ?
                fileBrowserContext.items :
                fileBrowserContext.items.filter(function (item) {
                    return item.tag.some(function (tag) {
                        return selectedTags.indexOf(tag) > -1;
                    });
                }), false);
        },

        onClickFile: function (e) {
            e.preventDefault();
            e.stopPropagation();

            const fileBrowserContext = this.context.fileBrowser;
            const listEl = fileBrowserContext.list;
            let target = e.target;
            let command = null;

            if (target === listEl) return;

            while (listEl !== target.parentNode) {
                command = target.getAttribute('data-command');
                if (command) break;
                target = target.parentNode;
            }

            if (!command) return;

            const handler = (fileBrowserContext.selectorHandler || this.context[fileBrowserContext.contextPlugin].selectorHandler);
            this.plugins.fileBrowser.close.call(this);
            handler(target);
        }
    };

    if (typeof noGlobal === typeof undefined) {
        if (!window.SUNEDITOR_MODULES) {
            Object.defineProperty(window, 'SUNEDITOR_MODULES', {
                enumerable: true,
                writable: false,
                configurable: false,
                value: {}
            });
        }

        Object.defineProperty(window.SUNEDITOR_MODULES, 'fileBrowser', {
            enumerable: true,
            writable: false,
            configurable: false,
            value: fileBrowser
        });
    }

    return fileBrowser;
}));<|endoftext|>


<commit_before>/**
 * @fileoverview Prevent using unwrapped literals in a React component definition
 * @author Caleb morris
 * @author David Buchan-Swanson
 */
'use strict';

// ------------------------------------------------------------------------------
// Requirements
// ------------------------------------------------------------------------------

const rule = require('../../../lib/rules/jsx-no-literals');
const RuleTester = require('eslint').RuleTester;

const parserOptions = {
  ecmaVersion: 8,
  sourceType: 'module',
  ecmaFeatures: {
    experimentalObjectRestSpread: true,
    jsx: true
  }
};

// ------------------------------------------------------------------------------
// Tests
// ------------------------------------------------------------------------------

const ruleTester = new RuleTester({parserOptions});
ruleTester.run('jsx-no-literals', rule, {

  valid: [
    {
      code: `
        class Comp1 extends Component {
          render() {
            return (

                {'asdjfl'}

            );
          }
        }
      `,
      parser: 'babel-eslint'
    }, {
      code: `
        class Comp1 extends Component {
          render() {
            return (
{'test'}
);
          }
        }
      `,
      parser: 'babel-eslint'
    }, {
      code: `
        class Comp1 extends Component {
          render() {
            const bar = (
{'hello'}
);
            return bar;
          }
        }
      `,
      parser: 'babel-eslint'
    }, {
      code: `
        var Hello = createReactClass({
          foo: (
{'hello'}
),
          render() {
            return this.foo;
          },
        });
      `,
      parser: 'babel-eslint'
    }, {
      code: `
        class Comp1 extends Component {
          render() {
            return (

                {'asdjfl'}
                {'test'}
                {'foo'}

            );
          }
        }
      `,
      parser: 'babel-eslint'
    }, {
      code: `
        class Comp1 extends Component {
          render() {
            return (

            );
          }
        }
      `,
      parser: 'babel-eslint'
    }, {
      code: `
        var foo = require('foo');
      `,
      parser: 'babel-eslint'
    }, {
      code: `
        <Foo bar='test'>
          {'blarg'}
        </Foo>
      `,
      parser: 'babel-eslint'
    }, {
      code: `
        <Foo bar="test">
          {intl.formatText(message)}
        </Foo>
      `,
      parser: 'babel-eslint',
      options: [{noStrings: true}]
    }, {
      code: `
        <Foo bar="test">
          {translate('my.translate.key')}
        </Foo>
      `,
      parser: 'babel-eslint',
      options: [{noStrings: true}]
    }, {
      code: `
        <Foo bar="test">
          {intl.formatText(message)}
        </Foo>
      `,
      options: [{noStrings: true}]
    }, {
      code: `
        <Foo bar="test">
          {translate('my.translate.key')}
        </Foo>
      `,
      options: [{noStrings: true}]
    }, {
      code: '<Foo bar={true} />',
      options: [{noStrings: true}]
    }, {
      code: '<Foo bar={false} />',
      options: [{noStrings: true}]
    }, {
      code: '<Foo bar={100} />',
      options: [{noStrings: true}]
    }, {
      code: '<Foo bar={null} />',
      options: [{noStrings: true}]
    }, {
      code: '<Foo bar={{}} />',
      options: [{noStrings: true}]
    }, {
      code: `
        class Comp1 extends Component {
          asdf() {}
          render() {
            return <Foo bar={this.asdf} />;
          }
        }
      `,
      options: [{noStrings: true}]
    }, {
      code: `
        class Comp1 extends Component {
          render() {
            let foo = \`bar\`;
            return
;
          }
        }
      `,
      options: [{noStrings: true}]
    }

  ],

  invalid: [
    {
      code: `
        class Comp1 extends Component {
          render() {
            return (
test
);
          }
        }
      `,
      parser: 'babel-eslint',
      errors: [{message: 'Missing JSX expression container around literal string'}]
    }, {
      code: `
        class Comp1 extends Component {
          render() {
            const foo = (
test
);
            return foo;
          }
        }
      `,
      parser: 'babel-eslint',
      errors: [{message: 'Missing JSX expression container around literal string'}]
    }, {
      code: `
        class Comp1 extends Component {
          render() {
            const varObjectTest = { testKey : (
test
) };
            return varObjectTest.testKey;
          }
        }
      `,
      parser: 'babel-eslint',
      errors: [{message: 'Missing JSX expression container around literal string'}]
    }, {
      code: `
        var Hello = createReactClass({
          foo: (
hello
),
          render() {
            return this.foo;
          },
        });
      `,
      parser: 'babel-eslint',
      errors: [{message: 'Missing JSX expression container around literal string'}]
    }, {
      code: `
        class Comp1 extends Component {
          render() {
            return (

                asdjfl

            );
          }
        }
      `,
      parser: 'babel-eslint',
      errors: [{message: 'Missing JSX expression container around literal string'}]
    }, {
      code: `
        class Comp1 extends Component {
          render() {
            return (

                asdjfl
                test
                foo

            );
          }
        }
      `,
      parser: 'babel-eslint',
      errors: [{message: 'Missing JSX expression container around literal string'}]
    }, {
      code: `
        class Comp1 extends Component {
          render() {
            return (

                {'asdjfl'}
                test
                {'foo'}

            );
          }
        }
      `,
      parser: 'babel-eslint',
      errors: [{message: 'Missing JSX expression container around literal string'}]
    }, {
      code: `
        <Foo bar="test">
          {'Test'}
        </Foo>
      `,
      parser: 'babel-eslint',
      options: [{noStrings: true}],
      errors: [{message: 'Strings not allowed in JSX files'}]
    }, {
      code: `
        <Foo bar="test">
          {'Test'}
        </Foo>
      `,
      options: [{noStrings: true}],
      errors: [{message: 'Strings not allowed in JSX files'}]
    }, {
      code: `
        <Foo bar="test">
          {'Test' + name}
        </Foo>
      `,
      options: [{noStrings: true}],
      errors: [{message: 'Strings not allowed in JSX files'}]
    }, {
      code: `
        <Foo bar="test">
          Test
        </Foo>
      `,
      parser: 'babel-eslint',
      options: [{noStrings: true}],
      errors: [{message: 'Strings not allowed in JSX files'}]
    }, {
      code: `
        <Foo bar="test">
          Test
        </Foo>
      `,
      options: [{noStrings: true}],
      errors: [{message: 'Strings not allowed in JSX files'}]
    }, {
      code: `
        <Foo>
          {\`Test\`}
        </Foo>
      `,
      options: [{noStrings: true}],
      errors: [{message: 'Strings not allowed in JSX files'}]
    }, {
      code: '<Foo bar={`Test`} />',
      options: [{noStrings: true}],
      errors: [{message: 'Strings not allowed in JSX files'}]
    }
  ]
});
<commit_msg>jsx-no-literals Add tests combining literals and strings<commit_after>/**
 * @fileoverview Prevent using unwrapped literals in a React component definition
 * @author Caleb morris
 * @author David Buchan-Swanson
 */
'use strict';

// ------------------------------------------------------------------------------
// Requirements
// ------------------------------------------------------------------------------

const rule = require('../../../lib/rules/jsx-no-literals');
const RuleTester = require('eslint').RuleTester;

const parserOptions = {
  ecmaVersion: 8,
  sourceType: 'module',
  ecmaFeatures: {
    experimentalObjectRestSpread: true,
    jsx: true
  }
};

// ------------------------------------------------------------------------------
// Tests
// ------------------------------------------------------------------------------

const ruleTester = new RuleTester({parserOptions});
ruleTester.run('jsx-no-literals', rule, {

  valid: [
    {
      code: `
        class Comp1 extends Component {
          render() {
            return (

                {'asdjfl'}

            );
          }
        }
      `,
      parser: 'babel-eslint'
    }, {
      code: `
        class Comp1 extends Component {
          render() {
            return (
{'test'}
);
          }
        }
      `,
      parser: 'babel-eslint'
    }, {
      code: `
        class Comp1 extends Component {
          render() {
            const bar = (
{'hello'}
);
            return bar;
          }
        }
      `,
      parser: 'babel-eslint'
    }, {
      code: `
        var Hello = createReactClass({
          foo: (
{'hello'}
),
          render() {
            return this.foo;
          },
        });
      `,
      parser: 'babel-eslint'
    }, {
      code: `
        class Comp1 extends Component {
          render() {
            return (

                {'asdjfl'}
                {'test'}
                {'foo'}

            );
          }
        }
      `,
      parser: 'babel-eslint'
    }, {
      code: `
        class Comp1 extends Component {
          render() {
            return (

            );
          }
        }
      `,
      parser: 'babel-eslint'
    }, {
      code: `
        var foo = require('foo');
      `,
      parser: 'babel-eslint'
    }, {
      code: `
        <Foo bar='test'>
          {'blarg'}
        </Foo>
      `,
      parser: 'babel-eslint'
    }, {
      code: `
        <Foo bar="test">
          {intl.formatText(message)}
        </Foo>
      `,
      parser: 'babel-eslint',
      options: [{noStrings: true}]
    }, {
      code: `
        <Foo bar="test">
          {translate('my.translate.key')}
        </Foo>
      `,
      parser: 'babel-eslint',
      options: [{noStrings: true}]
    }, {
      code: `
        <Foo bar="test">
          {intl.formatText(message)}
        </Foo>
      `,
      options: [{noStrings: true}]
    }, {
      code: `
        <Foo bar="test">
          {translate('my.translate.key')}
        </Foo>
      `,
      options: [{noStrings: true}]
    }, {
      code: '<Foo bar={true} />',
      options: [{noStrings: true}]
    }, {
      code: '<Foo bar={false} />',
      options: [{noStrings: true}]
    }, {
      code: '<Foo bar={100} />',
      options: [{noStrings: true}]
    }, {
      code: '<Foo bar={null} />',
      options: [{noStrings: true}]
    }, {
      code: '<Foo bar={{}} />',
      options: [{noStrings: true}]
    }, {
      code: `
        class Comp1 extends Component {
          asdf() {}
          render() {
            return <Foo bar={this.asdf} />;
          }
        }
      `,
      options: [{noStrings: true}]
    }, {
      code: `
        class Comp1 extends Component {
          render() {
            let foo = \`bar\`;
            return
;
          }
        }
      `,
      options: [{noStrings: true}]
    }

  ],

  invalid: [
    {
      code: `
        class Comp1 extends Component {
          render() {
            return (
test
);
          }
        }
      `,
      parser: 'babel-eslint',
      errors: [{message: 'Missing JSX expression container around literal string'}]
    }, {
      code: `
        class Comp1 extends Component {
          render() {
            const foo = (
test
);
            return foo;
          }
        }
      `,
      parser: 'babel-eslint',
      errors: [{message: 'Missing JSX expression container around literal string'}]
    }, {
      code: `
        class Comp1 extends Component {
          render() {
            const varObjectTest = { testKey : (
test
) };
            return varObjectTest.testKey;
          }
        }
      `,
      parser: 'babel-eslint',
      errors: [{message: 'Missing JSX expression container around literal string'}]
    }, {
      code: `
        var Hello = createReactClass({
          foo: (
hello
),
          render() {
            return this.foo;
          },
        });
      `,
      parser: 'babel-eslint',
      errors: [{message: 'Missing JSX expression container around literal string'}]
    }, {
      code: `
        class Comp1 extends Component {
          render() {
            return (

                asdjfl

            );
          }
        }
      `,
      parser: 'babel-eslint',
      errors: [{message: 'Missing JSX expression container around literal string'}]
    }, {
      code: `
        class Comp1 extends Component {
          render() {
            return (

                asdjfl
                test
                foo

            );
          }
        }
      `,
      parser: 'babel-eslint',
      errors: [{message: 'Missing JSX expression container around literal string'}]
    }, {
      code: `
        class Comp1 extends Component {
          render() {
            return (

                {'asdjfl'}
                test
                {'foo'}

            );
          }
        }
      `,
      parser: 'babel-eslint',
      errors: [{message: 'Missing JSX expression container around literal string'}]
    }, {
      code: `
        <Foo bar="test">
          {'Test'}
        </Foo>
      `,
      parser: 'babel-eslint',
      options: [{noStrings: true}],
      errors: [{message: 'Strings not allowed in JSX files'}]
    }, {
      code: `
        <Foo bar="test">
          {'Test'}
        </Foo>
      `,
      options: [{noStrings: true}],
      errors: [{message: 'Strings not allowed in JSX files'}]
    }, {
      code: `
        <Foo bar="test">
          {'Test' + name}
        </Foo>
      `,
      options: [{noStrings: true}],
      errors: [{message: 'Strings not allowed in JSX files'}]
    }, {
      code: `
        <Foo bar="test">
          Test
        </Foo>
      `,
      parser: 'babel-eslint',
      options: [{noStrings: true}],
      errors: [{message: 'Strings not allowed in JSX files'}]
    }, {
      code: `
        <Foo bar="test">
          Test
        </Foo>
      `,
      options: [{noStrings: true}],
      errors: [{message: 'Strings not allowed in JSX files'}]
    }, {
      code: `
        <Foo>
          {\`Test\`}
        </Foo>
      `,
      options: [{noStrings: true}],
      errors: [{message: 'Strings not allowed in JSX files'}]
    }, {
      code: '<Foo bar={`Test`} />',
      options: [{noStrings: true}],
      errors: [{message: 'Strings not allowed in JSX files'}]
    }, {
      code: '<Foo bar={`${baz}`} />',
      options: [{noStrings: true}],
      errors: [{message: 'Strings not allowed in JSX files'}]
    }, {
      code: '<Foo bar={`Test ${baz}`} />',
      options: [{noStrings: true}],
      errors: [{message: 'Strings not allowed in JSX files'}]
    }, {
      code: '<Foo bar={`foo` + \'bar\'} />',
      options: [{noStrings: true}],
      errors: [
        {message: 'Strings not allowed in JSX files'},
        {message: 'Strings not allowed in JSX files'}
      ]
    }, {
      code: '<Foo bar={`foo` + `bar`} />',
      options: [{noStrings: true}],
      errors: [
        {message: 'Strings not allowed in JSX files'},
        {message: 'Strings not allowed in JSX files'}
      ]
    }, {
      code: '<Foo bar={\'foo\' + `bar`} />',
      options: [{noStrings: true}],
      errors: [
        {message: 'Strings not allowed in JSX files'},
        {message: 'Strings not allowed in JSX files'}
      ]
    }
  ]
});
<|endoftext|>


<commit_before>// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

'use strict';

(function(global) {

var kDefaultScrollAmount = 6;
var kDefaultScrollDelayMS = 85;
var kMinimumScrollDelayMS = 60;

var kDefaultLoopLimit = -1;

var kBehaviorScroll = 'scroll';
var kBehaviorSlide = 'slide';
var kBehaviorAlternate = 'alternate';

var kDirectionLeft = 'left';
var kDirectionRight = 'right';
var kDirectionUp = 'up';
var kDirectionDown = 'down';

var kPresentationalAttributes = [
    'bgcolor',
    'height',
    'hspace',
    'vspace',
    'width',
];

var pixelLengthRegexp = /^\s*([\d.]+)\s*$/;
var percentageLengthRegexp = /^\s*([\d.]+)\s*%\s*$/;

function convertHTMLLengthToCSSLength(value) {
    var pixelMatch = value.match(pixelLengthRegexp);
    if (pixelMatch)
        return pixelMatch[1] + 'px';
    var percentageMatch = value.match(percentageLengthRegexp);
    if (percentageMatch)
        return percentageMatch[1] + '%';
    return null;
}

function reflectAttribute(prototype, attributeName, propertyName) {
    Object.defineProperty(prototype, propertyName, {
        get: function() {
            return this.getAttribute(attributeName) || '';
        },
        set: function(value) {
            this.setAttribute(attributeName, value);
        },
    });
}

function reflectBooleanAttribute(prototype, attributeName, propertyName) {
    Object.defineProperty(prototype, propertyName, {
        get: function() {
            return parseInt(this.getAttribute(attributeName));
        },
        set: function(value) {
            this.setAttribute(attributeName, value ? '' : null);
        },
    });
}

function defineInlineEventHandler(prototype, eventName) {
  var propertyName = 'on' + eventName;
  // TODO(abarth): We should use symbols here instead.
  var functionPropertyName = propertyName + 'Function_';
  var eventHandlerPropertyName = propertyName + 'EventHandler_';
  Object.defineProperty(prototype, propertyName, {
    get: function() {
        var func = this[functionPropertyName];
        return func || null;
    },
    set: function(value) {
        var oldEventHandler = this[eventHandlerPropertyName];
        if (oldEventHandler)
            this.removeEventListener(eventName, oldEventHandler);
        // Notice that we wrap |value| in an anonymous function so that the
        // author can't call removeEventListener themselves to unregister the
        // inline event handler.
        var newEventHandler = value ? function() { value.apply(this, arguments) } : null;
        if (newEventHandler)
            this.addEventListener(eventName, newEventHandler);
        this[functionPropertyName] = value;
        this[eventHandlerPropertyName] = newEventHandler;
    },
  });
}

var HTMLMarqueeElementPrototype = Object.create(HTMLElement.prototype);

reflectAttribute(HTMLMarqueeElementPrototype, 'behavior', 'behavior');
reflectAttribute(HTMLMarqueeElementPrototype, 'bgcolor', 'bgColor');
reflectAttribute(HTMLMarqueeElementPrototype, 'direction', 'direction');
reflectAttribute(HTMLMarqueeElementPrototype, 'height', 'height');
reflectAttribute(HTMLMarqueeElementPrototype, 'hspace', 'hspace');
reflectAttribute(HTMLMarqueeElementPrototype, 'vspace', 'vspace');
reflectAttribute(HTMLMarqueeElementPrototype, 'width', 'width');
reflectBooleanAttribute(HTMLMarqueeElementPrototype, 'truespeed', 'trueSpeed');

defineInlineEventHandler(HTMLMarqueeElementPrototype, 'start');
defineInlineEventHandler(HTMLMarqueeElementPrototype, 'finish');
defineInlineEventHandler(HTMLMarqueeElementPrototype, 'bounce');

HTMLMarqueeElementPrototype.createdCallback = function() {
    var shadow = this.createShadowRoot();
    var style = global.document.createElement('style');
    style.textContent = ':host { display: inline-block; width: -webkit-fill-available; overflow: hidden; text-align: initial; }' +
                        ':host([direction="up"]), :host([direction="down"]) { height: 200px; }';
    shadow.appendChild(style);

    var mover = global.document.createElement('div');
    shadow.appendChild(mover);

    mover.appendChild(global.document.createElement('content'));

    this.loopCount_ = 0;
    this.mover_ = mover;
    this.player_ = null;
    this.continueCallback_ = null;

    for (var i = 0; i < kPresentationalAttributes.length; ++i)
        this.initializeAttribute_(kPresentationalAttributes[i]);
};

HTMLMarqueeElementPrototype.attachedCallback = function() {
    this.start();
};

HTMLMarqueeElementPrototype.detachedCallback = function() {
    this.stop();
};

HTMLMarqueeElementPrototype.attributeChangedCallback = function(name, oldValue, newValue) {
    switch (name) {
    case 'bgcolor':
        this.style.backgroundColor = newValue;
        break;
    case 'height':
        this.style.height = convertHTMLLengthToCSSLength(newValue);
        break;
    case 'hspace':
        var margin = convertHTMLLengthToCSSLength(newValue);
        this.style.marginLeft = margin;
        this.style.marginRight = margin;
        break;
    case 'vspace':
        var margin = convertHTMLLengthToCSSLength(newValue);
        this.style.marginTop = margin;
        this.style.marginBottom = margin;
        break;
    case 'width':
        this.style.width = convertHTMLLengthToCSSLength(newValue);
        break;
    case 'behavior':
    case 'direction':
        this.stop();
        this.loopCount_ = 0;
        this.start();
        break;
    }
};

HTMLMarqueeElementPrototype.initializeAttribute_ = function(name) {
    var value = this.getAttribute(name);
    if (value === null)
        return;
    this.attributeChangedCallback(name, null, value);
};

Object.defineProperty(HTMLMarqueeElementPrototype, 'scrollAmount', {
    get: function() {
        var value = this.getAttribute('scrollamount');
        return value === null ? kDefaultScrollAmount : parseInt(value);
    },
    set: function(value) {
        this.setAttribute('scrollamount', +value);
    },
});

Object.defineProperty(HTMLMarqueeElementPrototype, 'scrollDelay', {
    get: function() {
        var value = this.getAttribute('scrolldelay');
        if (value === null)
            return kDefaultScrollDelayMS;
        var scrollDelay = parseInt(value);
        if (scrollDelay < kMinimumScrollDelayMS && !this.trueSpeed)
            return kDefaultScrollDelayMS;
        return scrollDelay;
    },
    set: function(value) {
        this.setAttribute('scrolldelay', +value);
    },
});

Object.defineProperty(HTMLMarqueeElementPrototype, 'loop', {
    get: function() {
        var value = this.getAttribute('loop');
        return value === null ? kDefaultLoopLimit : parseInt(value);
    },
    set: function(value) {
        this.setAttribute('loop', +value);
    },
});

HTMLMarqueeElementPrototype.getGetMetrics_ = function() {
    this.mover_.style.width = '-webkit-max-content';

    var moverStyle = global.getComputedStyle(this.mover_);
    var marqueeStyle = global.getComputedStyle(this);

    var metrics = {};
    metrics.contentWidth = parseInt(moverStyle.width);
    metrics.contentHeight = parseInt(moverStyle.height);
    metrics.marqueeWidth = parseInt(marqueeStyle.width);
    metrics.marqueeHeight = parseInt(marqueeStyle.height);

    this.mover_.style.width = '';
    return metrics;
};

HTMLMarqueeElementPrototype.getAnimationParmeters_ = function() {
    var metrics = this.getGetMetrics_();

    var totalWidth = metrics.marqueeWidth + metrics.contentWidth;
    var totalHeight = metrics.marqueeHeight + metrics.contentHeight;

    var innerWidth = metrics.marqueeWidth - metrics.contentWidth;
    var innerHeight = metrics.marqueeHeight - metrics.contentHeight;

    var parameters = {};

    switch (this.behavior) {
    case kBehaviorScroll:
    default:
        switch (this.direction) {
        case kDirectionLeft:
        default:
            parameters.transformBegin = 'translateX(' + metrics.marqueeWidth + 'px)';
            parameters.transformEnd = 'translateX(-100%)';
            parameters.distance = totalWidth;
            break;
        case kDirectionRight:
            parameters.transformBegin = 'translateX(-' + metrics.contentWidth + 'px)';
            parameters.transformEnd = 'translateX(' + metrics.marqueeWidth + 'px)';
            parameters.distance = totalWidth;
            break;
        case kDirectionUp:
            parameters.transformBegin = 'translateY(' + metrics.marqueeHeight + 'px)';
            parameters.transformEnd = 'translateY(-' + metrics.contentHeight + 'px)';
            parameters.distance = totalHeight;
            break;
        case kDirectionDown:
            parameters.transformBegin = 'translateY(-' + metrics.contentHeight + 'px)';
            parameters.transformEnd = 'translateY(' + metrics.marqueeHeight + 'px)';
            parameters.distance = totalHeight;
            break;
        }
        break;
    case kBehaviorAlternate:
        switch (this.direction) {
        case kDirectionLeft:
        default:
            parameters.transformBegin = 'translateX(' + innerWidth + 'px)';
            parameters.transformEnd = 'translateX(0)';
            parameters.distance = innerWidth;
            break;
        case kDirectionRight:
            parameters.transformBegin = 'translateX(0)';
            parameters.transformEnd = 'translateX(' + innerWidth + 'px)';
            parameters.distance = innerWidth;
            break;
        case kDirectionUp:
            parameters.transformBegin = 'translateY(' + innerHeight + 'px)';
            parameters.transformEnd = 'translateY(0)';
            parameters.distance = innerHeight;
            break;
        case kDirectionDown:
            parameters.transformBegin = 'translateY(0)';
            parameters.transformEnd = 'translateY(' + innerHeight + 'px)';
            parameters.distance = innerHeight;
            break;
        }

        if (this.loopCount_ % 2) {
            var transform = parameters.transformBegin;
            parameters.transformBegin = parameters.transformEnd;
            parameters.transformEnd = transform;
        }

        break;
    case kBehaviorSlide:
        switch (this.direction) {
        case kDirectionLeft:
        default:
            parameters.transformBegin = 'translateX(' + metrics.marqueeWidth + 'px)';
            parameters.transformEnd = 'translateX(0)';
            parameters.distance = metrics.marqueeWidth;
            break;
        case kDirectionRight:
            parameters.transformBegin = 'translateX(-' + metrics.contentWidth + 'px)';
            parameters.transformEnd = 'translateX(' + innerWidth + 'px)';
            parameters.distance = metrics.marqueeWidth;
            break;
        case kDirectionUp:
            parameters.transformBegin = 'translateY(' + metrics.marqueeHeight + 'px)';
            parameters.transformEnd = 'translateY(0)';
            parameters.distance = metrics.marqueeHeight;
            break;
        case kDirectionDown:
            parameters.transformBegin = 'translateY(-' + metrics.contentHeight + 'px)';
            parameters.transformEnd = 'translateY(' + innerHeight + 'px)';
            parameters.distance = metrics.marqueeHeight;
            break;
        }
        break;
    }

    return parameters
};

HTMLMarqueeElementPrototype.shouldContinue_ = function() {
    var loop = this.loop;

    // By default, slide loops only once.
    if (loop <= 0 && this.behavior === kBehaviorSlide)
        loop = 1;

    if (loop <= 0)
        return true;
    return this.loopCount_ < loop;
};

HTMLMarqueeElementPrototype.continue_ = function() {
    if (!this.shouldContinue_()) {
        this.player_ = null;
        this.dispatchEvent(new Event('finish', false, true));
        return;
    }

    var parameters = this.getAnimationParmeters_();

    var player = this.mover_.animate([
        { transform: parameters.transformBegin },
        { transform: parameters.transformEnd },
    ], {
        duration: parameters.distance * this.scrollDelay / this.scrollAmount,
        fill: 'forwards',
    });

    this.player_ = player;

    player.addEventListener('finish', function() {
        if (player != this.player_)
            return;
        ++this.loopCount_;
        this.continue_();
        if (this.player_ && this.behavior === kBehaviorAlternate)
            this.dispatchEvent(new Event('bounce', false, true));
    }.bind(this));
};

HTMLMarqueeElementPrototype.start = function() {
    if (this.continueCallback_ || this.player_)
        return;
    this.continueCallback_ = global.requestAnimationFrame(function() {
        this.continueCallback_ = null;
        this.continue_();
    }.bind(this));
    this.dispatchEvent(new Event('start', false, true));
};

HTMLMarqueeElementPrototype.stop = function() {
    if (!this.continueCallback_ && !this.player_)
        return;

    if (this.continueCallback_) {
        global.cancelAnimationFrame(this.continueCallback_);
        this.continueCallback_ = null;
        return;
    }

    // TODO(abarth): Rather than canceling the animation, we really should just
    // pause the animation, but the pause function is still flagged as
    // experimental.
    if (this.player_) {
        var player = this.player_;
        this.player_ = null;
        player.cancel();
    }
};

global.document.registerElement('i-marquee', {
    prototype: HTMLMarqueeElementPrototype,
});

})(this);
<commit_msg>Boolean attributes ought to be booleans<commit_after>// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

'use strict';

(function(global) {

var kDefaultScrollAmount = 6;
var kDefaultScrollDelayMS = 85;
var kMinimumScrollDelayMS = 60;

var kDefaultLoopLimit = -1;

var kBehaviorScroll = 'scroll';
var kBehaviorSlide = 'slide';
var kBehaviorAlternate = 'alternate';

var kDirectionLeft = 'left';
var kDirectionRight = 'right';
var kDirectionUp = 'up';
var kDirectionDown = 'down';

var kPresentationalAttributes = [
    'bgcolor',
    'height',
    'hspace',
    'vspace',
    'width',
];

var pixelLengthRegexp = /^\s*([\d.]+)\s*$/;
var percentageLengthRegexp = /^\s*([\d.]+)\s*%\s*$/;

function convertHTMLLengthToCSSLength(value) {
    var pixelMatch = value.match(pixelLengthRegexp);
    if (pixelMatch)
        return pixelMatch[1] + 'px';
    var percentageMatch = value.match(percentageLengthRegexp);
    if (percentageMatch)
        return percentageMatch[1] + '%';
    return null;
}

function reflectAttribute(prototype, attributeName, propertyName) {
    Object.defineProperty(prototype, propertyName, {
        get: function() {
            return this.getAttribute(attributeName) || '';
        },
        set: function(value) {
            this.setAttribute(attributeName, value);
        },
    });
}

function reflectBooleanAttribute(prototype, attributeName, propertyName) {
    Object.defineProperty(prototype, propertyName, {
        get: function() {
            return !!this.getAttribute(attributeName);
        },
        set: function(value) {
            this.setAttribute(attributeName, value ? '' : null);
        },
    });
}

function defineInlineEventHandler(prototype, eventName) {
  var propertyName = 'on' + eventName;
  // TODO(abarth): We should use symbols here instead.
  var functionPropertyName = propertyName + 'Function_';
  var eventHandlerPropertyName = propertyName + 'EventHandler_';
  Object.defineProperty(prototype, propertyName, {
    get: function() {
        var func = this[functionPropertyName];
        return func || null;
    },
    set: function(value) {
        var oldEventHandler = this[eventHandlerPropertyName];
        if (oldEventHandler)
            this.removeEventListener(eventName, oldEventHandler);
        // Notice that we wrap |value| in an anonymous function so that the
        // author can't call removeEventListener themselves to unregister the
        // inline event handler.
        var newEventHandler = value ? function() { value.apply(this, arguments) } : null;
        if (newEventHandler)
            this.addEventListener(eventName, newEventHandler);
        this[functionPropertyName] = value;
        this[eventHandlerPropertyName] = newEventHandler;
    },
  });
}

var HTMLMarqueeElementPrototype = Object.create(HTMLElement.prototype);

reflectAttribute(HTMLMarqueeElementPrototype, 'behavior', 'behavior');
reflectAttribute(HTMLMarqueeElementPrototype, 'bgcolor', 'bgColor');
reflectAttribute(HTMLMarqueeElementPrototype, 'direction', 'direction');
reflectAttribute(HTMLMarqueeElementPrototype, 'height', 'height');
reflectAttribute(HTMLMarqueeElementPrototype, 'hspace', 'hspace');
reflectAttribute(HTMLMarqueeElementPrototype, 'vspace', 'vspace');
reflectAttribute(HTMLMarqueeElementPrototype, 'width', 'width');
reflectBooleanAttribute(HTMLMarqueeElementPrototype, 'truespeed', 'trueSpeed');

defineInlineEventHandler(HTMLMarqueeElementPrototype, 'start');
defineInlineEventHandler(HTMLMarqueeElementPrototype, 'finish');
defineInlineEventHandler(HTMLMarqueeElementPrototype, 'bounce');

HTMLMarqueeElementPrototype.createdCallback = function() {
    var shadow = this.createShadowRoot();
    var style = global.document.createElement('style');
    style.textContent = ':host { display: inline-block; width: -webkit-fill-available; overflow: hidden; text-align: initial; }' +
                        ':host([direction="up"]), :host([direction="down"]) { height: 200px; }';
    shadow.appendChild(style);

    var mover = global.document.createElement('div');
    shadow.appendChild(mover);

    mover.appendChild(global.document.createElement('content'));

    this.loopCount_ = 0;
    this.mover_ = mover;
    this.player_ = null;
    this.continueCallback_ = null;

    for (var i = 0; i < kPresentationalAttributes.length; ++i)
        this.initializeAttribute_(kPresentationalAttributes[i]);
};

HTMLMarqueeElementPrototype.attachedCallback = function() {
    this.start();
};

HTMLMarqueeElementPrototype.detachedCallback = function() {
    this.stop();
};

HTMLMarqueeElementPrototype.attributeChangedCallback = function(name, oldValue, newValue) {
    switch (name) {
    case 'bgcolor':
        this.style.backgroundColor = newValue;
        break;
    case 'height':
        this.style.height = convertHTMLLengthToCSSLength(newValue);
        break;
    case 'hspace':
        var margin = convertHTMLLengthToCSSLength(newValue);
        this.style.marginLeft = margin;
        this.style.marginRight = margin;
        break;
    case 'vspace':
        var margin = convertHTMLLengthToCSSLength(newValue);
        this.style.marginTop = margin;
        this.style.marginBottom = margin;
        break;
    case 'width':
        this.style.width = convertHTMLLengthToCSSLength(newValue);
        break;
    case 'behavior':
    case 'direction':
        this.stop();
        this.loopCount_ = 0;
        this.start();
        break;
    }
};

HTMLMarqueeElementPrototype.initializeAttribute_ = function(name) {
    var value = this.getAttribute(name);
    if (value === null)
        return;
    this.attributeChangedCallback(name, null, value);
};

Object.defineProperty(HTMLMarqueeElementPrototype, 'scrollAmount', {
    get: function() {
        var value = this.getAttribute('scrollamount');
        return value === null ? kDefaultScrollAmount : parseInt(value);
    },
    set: function(value) {
        this.setAttribute('scrollamount', +value);
    },
});

Object.defineProperty(HTMLMarqueeElementPrototype, 'scrollDelay', {
    get: function() {
        var value = this.getAttribute('scrolldelay');
        if (value === null)
            return kDefaultScrollDelayMS;
        var scrollDelay = parseInt(value);
        if (scrollDelay < kMinimumScrollDelayMS && !this.trueSpeed)
            return kDefaultScrollDelayMS;
        return scrollDelay;
    },
    set: function(value) {
        this.setAttribute('scrolldelay', +value);
    },
});

Object.defineProperty(HTMLMarqueeElementPrototype, 'loop', {
    get: function() {
        var value = this.getAttribute('loop');
        return value === null ? kDefaultLoopLimit : parseInt(value);
    },
    set: function(value) {
        this.setAttribute('loop', +value);
    },
});

HTMLMarqueeElementPrototype.getGetMetrics_ = function() {
    this.mover_.style.width = '-webkit-max-content';

    var moverStyle = global.getComputedStyle(this.mover_);
    var marqueeStyle = global.getComputedStyle(this);

    var metrics = {};
    metrics.contentWidth = parseInt(moverStyle.width);
    metrics.contentHeight = parseInt(moverStyle.height);
    metrics.marqueeWidth = parseInt(marqueeStyle.width);
    metrics.marqueeHeight = parseInt(marqueeStyle.height);

    this.mover_.style.width = '';
    return metrics;
};

HTMLMarqueeElementPrototype.getAnimationParmeters_ = function() {
    var metrics = this.getGetMetrics_();

    var totalWidth = metrics.marqueeWidth + metrics.contentWidth;
    var totalHeight = metrics.marqueeHeight + metrics.contentHeight;

    var innerWidth = metrics.marqueeWidth - metrics.contentWidth;
    var innerHeight = metrics.marqueeHeight - metrics.contentHeight;

    var parameters = {};

    switch (this.behavior) {
    case kBehaviorScroll:
    default:
        switch (this.direction) {
        case kDirectionLeft:
        default:
            parameters.transformBegin = 'translateX(' + metrics.marqueeWidth + 'px)';
            parameters.transformEnd = 'translateX(-100%)';
            parameters.distance = totalWidth;
            break;
        case kDirectionRight:
            parameters.transformBegin = 'translateX(-' + metrics.contentWidth + 'px)';
            parameters.transformEnd = 'translateX(' + metrics.marqueeWidth + 'px)';
            parameters.distance = totalWidth;
            break;
        case kDirectionUp:
            parameters.transformBegin = 'translateY(' + metrics.marqueeHeight + 'px)';
            parameters.transformEnd = 'translateY(-' + metrics.contentHeight + 'px)';
            parameters.distance = totalHeight;
            break;
        case kDirectionDown:
            parameters.transformBegin = 'translateY(-' + metrics.contentHeight + 'px)';
            parameters.transformEnd = 'translateY(' + metrics.marqueeHeight + 'px)';
            parameters.distance = totalHeight;
            break;
        }
        break;
    case kBehaviorAlternate:
        switch (this.direction) {
        case kDirectionLeft:
        default:
            parameters.transformBegin = 'translateX(' + innerWidth + 'px)';
            parameters.transformEnd = 'translateX(0)';
            parameters.distance = innerWidth;
            break;
        case kDirectionRight:
            parameters.transformBegin = 'translateX(0)';
            parameters.transformEnd = 'translateX(' + innerWidth + 'px)';
            parameters.distance = innerWidth;
            break;
        case kDirectionUp:
            parameters.transformBegin = 'translateY(' + innerHeight + 'px)';
            parameters.transformEnd = 'translateY(0)';
            parameters.distance = innerHeight;
            break;
        case kDirectionDown:
            parameters.transformBegin = 'translateY(0)';
            parameters.transformEnd = 'translateY(' + innerHeight + 'px)';
            parameters.distance = innerHeight;
            break;
        }

        if (this.loopCount_ % 2) {
            var transform = parameters.transformBegin;
            parameters.transformBegin = parameters.transformEnd;
            parameters.transformEnd = transform;
        }

        break;
    case kBehaviorSlide:
        switch (this.direction) {
        case kDirectionLeft:
        default:
            parameters.transformBegin = 'translateX(' + metrics.marqueeWidth + 'px)';
            parameters.transformEnd = 'translateX(0)';
            parameters.distance = metrics.marqueeWidth;
            break;
        case kDirectionRight:
            parameters.transformBegin = 'translateX(-' + metrics.contentWidth + 'px)';
            parameters.transformEnd = 'translateX(' + innerWidth + 'px)';
            parameters.distance = metrics.marqueeWidth;
            break;
        case kDirectionUp:
            parameters.transformBegin = 'translateY(' + metrics.marqueeHeight + 'px)';
            parameters.transformEnd = 'translateY(0)';
            parameters.distance = metrics.marqueeHeight;
            break;
        case kDirectionDown:
            parameters.transformBegin = 'translateY(-' + metrics.contentHeight + 'px)';
            parameters.transformEnd = 'translateY(' + innerHeight + 'px)';
            parameters.distance = metrics.marqueeHeight;
            break;
        }
        break;
    }

    return parameters
};

HTMLMarqueeElementPrototype.shouldContinue_ = function() {
    var loop = this.loop;

    // By default, slide loops only once.
    if (loop <= 0 && this.behavior === kBehaviorSlide)
        loop = 1;

    if (loop <= 0)
        return true;
    return this.loopCount_ < loop;
};

HTMLMarqueeElementPrototype.continue_ = function() {
    if (!this.shouldContinue_()) {
        this.player_ = null;
        this.dispatchEvent(new Event('finish', false, true));
        return;
    }

    var parameters = this.getAnimationParmeters_();

    var player = this.mover_.animate([
        { transform: parameters.transformBegin },
        { transform: parameters.transformEnd },
    ], {
        duration: parameters.distance * this.scrollDelay / this.scrollAmount,
        fill: 'forwards',
    });

    this.player_ = player;

    player.addEventListener('finish', function() {
        if (player != this.player_)
            return;
        ++this.loopCount_;
        this.continue_();
        if (this.player_ && this.behavior === kBehaviorAlternate)
            this.dispatchEvent(new Event('bounce', false, true));
    }.bind(this));
};

HTMLMarqueeElementPrototype.start = function() {
    if (this.continueCallback_ || this.player_)
        return;
    this.continueCallback_ = global.requestAnimationFrame(function() {
        this.continueCallback_ = null;
        this.continue_();
    }.bind(this));
    this.dispatchEvent(new Event('start', false, true));
};

HTMLMarqueeElementPrototype.stop = function() {
    if (!this.continueCallback_ && !this.player_)
        return;

    if (this.continueCallback_) {
        global.cancelAnimationFrame(this.continueCallback_);
        this.continueCallback_ = null;
        return;
    }

    // TODO(abarth): Rather than canceling the animation, we really should just
    // pause the animation, but the pause function is still flagged as
    // experimental.
    if (this.player_) {
        var player = this.player_;
        this.player_ = null;
        player.cancel();
    }
};

global.document.registerElement('i-marquee', {
    prototype: HTMLMarqueeElementPrototype,
});

})(this);
<|endoftext|>