<commit_before>/**
 * 下拉地域选择器
 * @author Brian Li
 * @email lbxxlht@163.com
 * @version 0.0.2.1
 */
define(function (require) {

    var React = require('react');
    var InputWidget = require('./mixins/InputWidget');

    var Layer = require('./Layer.jsx');
    var Region = require('./Region.jsx');
    var Button = require('./Button.jsx');

    var cTools = require('./core/componentTools');
    var language = require('./core/language').region.regionName;
    var buttonLabels = require('./core/language').rangeCalendar;

    return React.createClass({
        /**
         * @properties
         *
         * @param {Import|Properties} src\core\componentTools.js skin className style disabled
         * @param {String} label 下拉按钮上显示的文字，如果type为'single'并且value的值合法，将显示地域名称
         * @param {String} openLayerType 控制浮层打开的动作，onMouseEnter或onClick
         * @param {Import|Properties} src\Region.jsx.js type noLinkage provinceRenderer regionRenderer countryRenderer
         * @param {Import|Properties} src\Region.jsx.js countries
         * @param {Import|Properties} src\mixins\InputWidget.js
         *      value onChange name validations customErrorTemplates valueTemplate
         */
        /**
         * @fire Import src\mixins\InputWidget.js XXX onChange
         */
        // @override
        contextTypes: {
            appSkin: React.PropTypes.string
        },
        // @override
        mixins: [InputWidget],
        // @override
        getDefaultProps: function () {
            return {
                // base
                skin: '',
                className: '',
                style: {},
                disabled: false,
                // self
                label: 'DropDownRegion',
                openLayerType: 'onMouseEnter',
                type: 'multi',
                noLinkage: false,
                provinceRenderer: undefined,
                regionRenderer: undefined,
                countryRenderer: undefined,
                countries: undefined,
                // mixin
                valueTemplate: ''
            };
        },
        // @override
        componentWillReceiveProps: function(newProps) {
            this.setState({
                multiValue: newProps.value
            });
        },
        // @override
        getInitialState: function () {
            return {
                mouseenter: false,
                layerOpen: false,
                multiValue: this.props.value
            };
        },
        // @override
        componentDidMount: function () {
            this.___layerTimer___ = null;
            this.___closingTimer___ = null;
            document.body.addEventListener('click', this.onBodyClick);
        },
        // @override
        componentWillUnmount: function () {
            clearInterval(this.___layerTimer___);
            clearInterval(this.___closingTimer___);
            document.body.removeEventListener('click', this.onBodyClick);
        },
        onRegionChange: function (e) {
            var value = this.props.type === 'single' ? this.___getValue___() : this.state.multiValue;
            if (this.props.disabled || value === e.target.value) return;
            if (this.props.type === 'single') {
                this.___dispatchChange___(e);
                this.setState({layerOpen: false});
            }
            else {
                this.setState({multiValue: e.target.value});
            }
        },
        onEnterClick: function (e) {
            if (this.props.disabled) return;
            e.target = this.refs.container;
            e.target.value = this.state.multiValue;
            this.___dispatchChange___(e);
            this.setState({layerOpen: false});
        },
        onCancelClick: function () {
            this.setState({layerOpen: false});
        },
        onBodyClick: function () {
            if (
                this.props.type === 'single'
                || !this.refs.region
                || !this.state.layerOpen
                || this.refs.layer.state.mouseenter
                || this.refs.region.___currentLayer___
                || this.state.mouseenter
            ) {
                return;
            }
            this.setState({layerOpen: false});
        },
        onMainMouseEnter: function () {
            this.setState({mouseenter: true});
            if (this.props.openLayerType === 'onMouseEnter' && this.props.type === 'single') {
                this.open();
            }
        },
        onMainMouseLeave: function () {
            this.setState({mouseenter: false});
        },
        onLayerMouseLeave: function () {
            var me = this;
            clearInterval(me.___layerTimer___);
            me.___layerTimer___ = setInterval(leaving, 100);
            function leaving() {
                if (!me.refs.region || !me.state.layerOpen) {
                    clearInterval(me.___layerTimer___);
                    return;
                }
                if (me.refs.region.___currentLayer___ || me.refs.layer.state.mouseenter) {
                    return;
                }
                clearInterval(me.___layerTimer___);
                me.close();
            }
        },
        open: function () {
            this.setState({layerOpen: true});
        },
        close: function () {
            if (this.props.type !== 'single') return;
            var me = this;
            clearInterval(me.___closingTimer___);
            me.___closingTimer___ = setInterval(closing, 100);
            function closing() {
                if (!me.refs.region || !me.state.layerOpen) {
                    clearInterval(me.___layerTimer___);
                    return;
                }
                if (me.refs.layer.state.mouseenter || me.refs.region.___currentLayer___ || me.state.mouseenter) {
                    return;
                }
                clearInterval(me.___layerTimer___);
                me.setState({
                    mouseenter: false,
                    layerOpen: false
                });
            }
        },
        render: function () {
            var me = this;
            var value = this.___getValue___();
            var label = (this.props.type === 'single' && language[value]) ? language[value] : this.props.label;
            var containerProp = cTools.containerBaseProps('dropdownlist', this, {widthCorrect: -12});
            var layerProp = {
                ref: 'layer',
                isOpen: this.state.layerOpen && !this.props.disabled,
                anchor: this.refs.container,
                location: 'bottom right',
                style: {padding: '5px 0'},
                onMouseEnter: this.onLayerMouseEnter,
                onMouseLeave: this.onLayerMouseLeave,
                skin: this.context.appSkin ? (this.context.appSkin + '-normal') : 'normal'
            };
            var regionProp = {
                ref: 'region',
                value: this.props.type === 'single' ? this.___getValue___() : this.state.multiValue,
                disabled: this.props.disabled,
                type: this.props.type,
                noLinkage: this.props.noLinkage,
                provinceRenderer: this.props.provinceRenderer,
                regionRenderer: this.props.regionRenderer,
                countryRenderer: this.props.countryRenderer,
                countries: this.props.countries,
                onChange: this.onRegionChange
            };
            var enterProp = {
                label: buttonLabels.enter,
                skin: 'important',
                style: {margin: '5px 10px'},
                disabled: this.state.multiValue === me.props.value || !this.state.multiValue,
                onClick: this.onEnterClick
            };
            var cancelProp = {
                label: buttonLabels.cancel,
                onClick: this.onCancelClick
            };
            containerProp.onMouseEnter = this.onMainMouseEnter;
            containerProp.onMouseLeave = this.onMainMouseLeave;
            if (this.props.type !== 'single') {
                containerProp.onClick = this.open;
            }
            else if (this.props.openLayerType !== 'onMouseEnter') {
                containerProp[this.props.openLayerType] = this.open;
            }
            var skin = this.props.skin ? this.props.skin : 'normal';
            skin = this.context.appSkin ? (this.context.appSkin + '-' + skin) : skin;
            containerProp.className += layerProp.isOpen ? (' fcui2-dropdownlist-' + skin + '-hover') : '';
            return (

                    {label}
                    <Layer {...layerProp}>

                            <Region {...regionProp}/>
                            {this.props.type === 'single' ? null : <Button {...enterProp}/>}
                            {this.props.type === 'single' ? null : <Button {...cancelProp}/>}

                    </Layer>

            );
        }
    });

});
<commit_msg>add allowEmpty to DropDownList<commit_after>/**
 * 下拉地域选择器
 * @author Brian Li
 * @email lbxxlht@163.com
 * @version 0.0.2.1
 */
define(function (require) {

    var React = require('react');
    var InputWidget = require('./mixins/InputWidget');

    var Layer = require('./Layer.jsx');
    var Region = require('./Region.jsx');
    var Button = require('./Button.jsx');

    var cTools = require('./core/componentTools');
    var language = require('./core/language').region.regionName;
    var buttonLabels = require('./core/language').rangeCalendar;

    return React.createClass({
        /**
         * @properties
         *
         * @param {Import|Properties} src\core\componentTools.js skin className style disabled
         * @param {String} label 下拉按钮上显示的文字，如果type为'single'并且value的值合法，将显示地域名称
         * @param {String} openLayerType 控制浮层打开的动作，onMouseEnter或onClick
         * @param {Import|Properties} src\Region.jsx.js type noLinkage provinceRenderer regionRenderer countryRenderer
         * @param {Import|Properties} src\Region.jsx.js countries
         * @param {Import|Properties} src\mixins\InputWidget.js
         *      value onChange name validations customErrorTemplates valueTemplate
         */
        /**
         * @fire Import src\mixins\InputWidget.js XXX onChange
         */
        // @override
        contextTypes: {
            appSkin: React.PropTypes.string
        },
        // @override
        mixins: [InputWidget],
        // @override
        getDefaultProps: function () {
            return {
                // base
                skin: '',
                className: '',
                style: {},
                disabled: false,
                // self
                label: 'DropDownRegion',
                openLayerType: 'onMouseEnter',
                type: 'multi',
                allowEmpty: false,
                noLinkage: false,
                provinceRenderer: undefined,
                regionRenderer: undefined,
                countryRenderer: undefined,
                countries: undefined,
                // mixin
                valueTemplate: ''
            };
        },
        // @override
        componentWillReceiveProps: function(newProps) {
            this.setState({
                multiValue: newProps.value
            });
        },
        // @override
        getInitialState: function () {
            return {
                mouseenter: false,
                layerOpen: false,
                multiValue: this.props.value
            };
        },
        // @override
        componentDidMount: function () {
            this.___layerTimer___ = null;
            this.___closingTimer___ = null;
            document.body.addEventListener('click', this.onBodyClick);
        },
        // @override
        componentWillUnmount: function () {
            clearInterval(this.___layerTimer___);
            clearInterval(this.___closingTimer___);
            document.body.removeEventListener('click', this.onBodyClick);
        },
        onRegionChange: function (e) {
            var value = this.props.type === 'single' ? this.___getValue___() : this.state.multiValue;
            if (this.props.disabled || value === e.target.value) return;
            if (this.props.type === 'single') {
                this.___dispatchChange___(e);
                this.setState({layerOpen: false});
            }
            else {
                this.setState({multiValue: e.target.value});
            }
        },
        onEnterClick: function (e) {
            if (this.props.disabled) return;
            e.target = this.refs.container;
            e.target.value = this.state.multiValue;
            this.___dispatchChange___(e);
            this.setState({layerOpen: false});
        },
        onCancelClick: function () {
            this.setState({layerOpen: false});
        },
        onBodyClick: function () {
            if (
                this.props.type === 'single'
                || !this.refs.region
                || !this.state.layerOpen
                || this.refs.layer.state.mouseenter
                || this.refs.region.___currentLayer___
                || this.state.mouseenter
            ) {
                return;
            }
            this.setState({layerOpen: false});
        },
        onMainMouseEnter: function () {
            this.setState({mouseenter: true});
            if (this.props.openLayerType === 'onMouseEnter' && this.props.type === 'single') {
                this.open();
            }
        },
        onMainMouseLeave: function () {
            this.setState({mouseenter: false});
        },
        onLayerMouseLeave: function () {
            var me = this;
            clearInterval(me.___layerTimer___);
            me.___layerTimer___ = setInterval(leaving, 100);
            function leaving() {
                if (!me.refs.region || !me.state.layerOpen) {
                    clearInterval(me.___layerTimer___);
                    return;
                }
                if (me.refs.region.___currentLayer___ || me.refs.layer.state.mouseenter) {
                    return;
                }
                clearInterval(me.___layerTimer___);
                me.close();
            }
        },
        open: function () {
            this.setState({layerOpen: true});
        },
        close: function () {
            if (this.props.type !== 'single') return;
            var me = this;
            clearInterval(me.___closingTimer___);
            me.___closingTimer___ = setInterval(closing, 100);
            function closing() {
                if (!me.refs.region || !me.state.layerOpen) {
                    clearInterval(me.___layerTimer___);
                    return;
                }
                if (me.refs.layer.state.mouseenter || me.refs.region.___currentLayer___ || me.state.mouseenter) {
                    return;
                }
                clearInterval(me.___layerTimer___);
                me.setState({
                    mouseenter: false,
                    layerOpen: false
                });
            }
        },
        render: function () {
            var me = this;
            var value = this.___getValue___();
            var label = (this.props.type === 'single' && language[value]) ? language[value] : this.props.label;
            var containerProp = cTools.containerBaseProps('dropdownlist', this, {widthCorrect: -12});
            var layerProp = {
                ref: 'layer',
                isOpen: this.state.layerOpen && !this.props.disabled,
                anchor: this.refs.container,
                location: 'bottom right',
                style: {padding: '5px 0'},
                onMouseEnter: this.onLayerMouseEnter,
                onMouseLeave: this.onLayerMouseLeave,
                skin: this.context.appSkin ? (this.context.appSkin + '-normal') : 'normal'
            };
            var regionProp = {
                ref: 'region',
                value: this.props.type === 'single' ? this.___getValue___() : this.state.multiValue,
                disabled: this.props.disabled,
                type: this.props.type,
                noLinkage: this.props.noLinkage,
                provinceRenderer: this.props.provinceRenderer,
                regionRenderer: this.props.regionRenderer,
                countryRenderer: this.props.countryRenderer,
                countries: this.props.countries,
                onChange: this.onRegionChange
            };
            var enterProp = {
                label: buttonLabels.enter,
                skin: 'important',
                style: {margin: '5px 10px'},
                disabled: this.state.multiValue === me.props.value || !this.state.multiValue,
                onClick: this.onEnterClick
            };
            var cancelProp = {
                label: buttonLabels.cancel,
                onClick: this.onCancelClick
            };
            containerProp.onMouseEnter = this.onMainMouseEnter;
            containerProp.onMouseLeave = this.onMainMouseLeave;
            if (this.props.allowEmpty && !this.state.multiValue) {
                enterProp.disabled = false;
            }
            if (this.props.type !== 'single') {
                containerProp.onClick = this.open;
            }
            else if (this.props.openLayerType !== 'onMouseEnter') {
                containerProp[this.props.openLayerType] = this.open;
            }
            var skin = this.props.skin ? this.props.skin : 'normal';
            skin = this.context.appSkin ? (this.context.appSkin + '-' + skin) : skin;
            containerProp.className += layerProp.isOpen ? (' fcui2-dropdownlist-' + skin + '-hover') : '';
            return (

                    {label}
                    <Layer {...layerProp}>

                            <Region {...regionProp}/>
                            {this.props.type === 'single' ? null : <Button {...enterProp}/>}
                            {this.props.type === 'single' ? null : <Button {...cancelProp}/>}

                    </Layer>

            );
        }
    });

});
<|endoftext|>


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

// The original file lives here: http://go/cross_domain_channel.js

/**
 * @fileoverview Implements a cross-domain communication channel. A
 * typical web page is prevented by browser security from sending
 * request, such as a XMLHttpRequest, to other servers than the ones
 * from which it came. The Jsonp class provides a workound, by
 * using dynamically generated script tags. Typical usage:.
 *
 * var jsonp = new goog.net.Jsonp(new goog.Uri('http://my.host.com/servlet'));
 * var payload = { 'foo': 1, 'bar': true };
 * jsonp.send(payload, function(reply) { alert(reply) });
 *
 * This script works in all browsers that are currently supported by
 * the Google Maps API, which is IE 6.0+, Firefox 0.8+, Safari 1.2.4+,
 * Netscape 7.1+, Mozilla 1.4+, Opera 8.02+.
 *
 */

goog.provide('goog.net.Jsonp');

goog.require('goog.Uri');
goog.require('goog.dom');

// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
//
// This class allows us (Google) to send data from non-Google and thus
// UNTRUSTED pages to our servers. Under NO CIRCUMSTANCES return
// anything sensitive, such as session or cookie specific data. Return
// only data that you want parties external to Google to have. Also
// NEVER use this method to send data from web pages to untrusted
// servers, or redirects to unknown servers (www.google.com/cache,
// /q=xx&btnl, /url, www.googlepages.com, etc.)
//
// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING

/**
 * Creates a new cross domain channel that sends data to the specified
 * host URL. By default, if no reply arrives within 5s, the channel
 * assumes the call failed to complete successfully.
 *
 * @param {goog.Uri|string} uri The Uri of the server side code that receives
 *     data posted through this channel (e.g.,
 *     "http://maps.google.com/maps/geo").
 *
 * @param {string=} opt_callbackParamName The parameter name that is used to
 *     specify the callback. Defaults to "callback".
 *
 * @constructor
 */
goog.net.Jsonp = function(uri, opt_callbackParamName) {
  /**
   * The uri_ object will be used to encode the payload that is sent to the
   * server.
   * @type {goog.Uri}
   * @private
   */
  this.uri_ = new goog.Uri(uri);

  /**
   * This is the callback parameter name that is added to the uri.
   * @type {string}
   * @private
   */
  this.callbackParamName_ = opt_callbackParamName ?
      opt_callbackParamName : 'callback';

  /**
   * The length of time, in milliseconds, this channel is prepared
   * to wait for for a request to complete. The default value is 5 seconds.
   * @type {number}
   * @private
   */
  this.timeout_ = 5000;
};

/**
 * The name of the property of goog.global under which the callback is
 * stored.
 */
goog.net.Jsonp.CALLBACKS = '_callbacks_';

/**
 * Used to generate unique callback IDs. The counter must be global because
 * all channels share a common callback object.
 * @private
 */
goog.net.Jsonp.scriptCounter_ = 0;

/**
 * Sets the length of time, in milliseconds, this channel is prepared
 * to wait for for a request to complete. If the call is not competed
 * within the set time span, it is assumed to have failed. To wait
 * indefinitely for a request to complete set the timout to a negative
 * number.
 *
 * @param {number} timeout The length of time before calls are
 * interrupted.
 */
goog.net.Jsonp.prototype.setRequestTimeout = function(timeout) {
  this.timeout_ = timeout;
};

/**
 * Returns the current timeout value, in milliseconds.
 *
 * @return {number} The timeout value.
 */
goog.net.Jsonp.prototype.getRequestTimeout = function() {
  return this.timeout_;
};

/**
 * Sends the given payload to the URL specified at the construction
 * time. The reply is delivered to the given replyCallback. If the
 * errorCallback is specified and the reply does not arrive within the
 * timeout period set on this channel, the errorCallback is invoked
 * with the original payload.
 *
 * If no reply callback is specified, then the response is expected to
 * consist of calls to globally registered functions. No &callback=
 * URL parameter will be sent in the request, and the script element
 * will be cleaned up after the timeout.
 *
 * @param {Object=} opt_payload Name-value pairs.  If given, these will be
 *     added as parameters to the supplied URI as GET parameters to the
 *     given server URI.
 *
 * @param {Function=} opt_replyCallback A function expecting one
 *     argument, called when the reply arrives, with the response data.
 *
 * @param {Function=} opt_errorCallback A function expecting one
 *     argument, called on timeout, with the payload (if given), otherwise
 *     null.
 *
 * @param {string=} opt_callbackParamValue Value to be used as the
 *     parameter value for the callback parameter (callbackParamName).
 *     To be used when the value needs to be fixed by the client for a
 *     particular request, to make use of the cached responses for the request.
 *     NOTE: If multiple requests are made with the same
 *     opt_callbackParamValue, only the last call will work whenever the
 *     response comes back.
 *
 * @return {Object} A request descriptor that may be used to cancel this
 *     transmission, or null, if the message may not be cancelled.
 */
goog.net.Jsonp.prototype.send = function(opt_payload,
                                         opt_replyCallback,
                                         opt_errorCallback,
                                         opt_callbackParamValue) {

  var payload = opt_payload || null;

  // This is a safeguard that we don't accidentally call appendChild
  // on a null.
  if (!document.documentElement.firstChild) {
    if (opt_errorCallback) {
      opt_errorCallback(payload);
    }
    return null;
  }

  var id = opt_callbackParamValue ||
      '_' + (goog.net.Jsonp.scriptCounter_++).toString(36) +
      goog.now().toString(36);

  if (!goog.global[goog.net.Jsonp.CALLBACKS]) {
    goog.global[goog.net.Jsonp.CALLBACKS] = {};
  }

  var script = goog.dom.createElement('script');

  var timeout = null;
  if (this.timeout_ > 0) {
    var error = goog.net.Jsonp.newErrorHandler_(id, script, payload,
                                                opt_errorCallback);
    timeout = goog.global.setTimeout(error, this.timeout_);
  }

  // Create a new Uri object onto which this payload will be added
  var uri = this.uri_.clone();
  if (payload) {
    goog.net.Jsonp.addPayloadToUri_(payload, uri);
  }

  if (opt_replyCallback) {
    var reply = goog.net.Jsonp.newReplyHandler_(id, script, opt_replyCallback,
                                                timeout);
    goog.global[goog.net.Jsonp.CALLBACKS][id] = reply;

    uri.setParameterValues(this.callbackParamName_,
                           goog.net.Jsonp.CALLBACKS + '.' + id);
  }

  goog.dom.setProperties(script, {
    'type': 'text/javascript',
    'id': id,
    'charset': 'UTF-8',
    // NOTE(user): Safari never loads the script if we don't set
    // the src attribute before appending.
    'src': uri.toString()
  });

  goog.dom.appendChild(document.getElementsByTagName('head')[0], script);
  return { id_: id, timeout_: timeout };
};

/**
 * Cancels a given request. The request must be exactly the object returned by
 * the send method.
 *
 * @param {Object} request The request object returned by the send method.
 */
goog.net.Jsonp.prototype.cancel = function(request) {
  if (request && request.id_) {
    var scriptNode = goog.dom.getElement(request.id_);

    if (scriptNode && scriptNode.tagName == 'SCRIPT' &&
        typeof goog.global[goog.net.Jsonp.CALLBACKS][request.id_] ==
        'function') {
      request.timeout_ && goog.global.clearTimeout(request.timeout_);
      goog.net.Jsonp.cleanup_(request.id_, scriptNode, false);
    }
  }
};

/**
 * Creates a timeout callback that removes the script node and calls
 * the given timeoutCallback with the original payload.
 *
 * @param {string} id The id of the script node.
 * @param {Element} scriptNode Script element.
 * @param {Object} payload The payload that was sent to the server.
 * @param {Function=} opt_errorCallback The function called on timeout.
 * @return {Function} A zero argument function that handles callback duties.
 * @private
 */
goog.net.Jsonp.newErrorHandler_ = function(id,
                                           scriptNode,
                                           payload,
                                           opt_errorCallback) {
  /**
   * When we call across domains with a request, this function is the
   * timeout handler. Once it's done executing the user-specified
   * error-handler, it removes the script node and original function.
   */
  return function() {
    goog.net.Jsonp.cleanup_(id, scriptNode, false);
    if (opt_errorCallback) {
      opt_errorCallback(payload);
    }
  }
};

/**
 * Creates a reply callback that removes the script node and calls the
 * given replyCallback with data returned by the server.
 *
 * @param {string} id The id of the script node.
 * @param {Element} scriptNode Script element.
 * @param {Function} replyCallback The function called on reply.
 * @param {?number} timeout A timeout call that needs to be cleared.
 * @return {Function} A reply callback function.
 * @private
 */
goog.net.Jsonp.newReplyHandler_ = function(id,
                                           scriptNode,
                                           replyCallback,
                                           timeout) {
  /**
   * This function is the handler for the all-is-well response. It
   * clears the error timeout handler, calls the user's handler, then
   * removes the script node and itself.
   *
   * @param {...Object} var_args The response data sent from the server.
   */
  return function(var_args) {
    goog.global.clearTimeout(timeout);
    goog.net.Jsonp.cleanup_(id, scriptNode, true);
    replyCallback.apply(undefined, arguments);
  };
};

/**
 * Removes the script node and reply handler with the given id.
 *
 * @param {string} id The id of the script node to be removed.
 * @param {Node} scriptNode The node to be removed.
 * @param {boolean} deleteReplyHandler If true, delete the reply handler
 *     instead of setting it to nullFunction (if we know the callback could
 *     never be called again).
 * @private
 */
goog.net.Jsonp.cleanup_ = function(id, scriptNode, deleteReplyHandler) {
  // Do this after a delay (removing the script node of a running script can
  // confuse older IEs).
  goog.global.setTimeout(function() {
    goog.dom.removeNode(scriptNode);
  }, 0);

  if (goog.global[goog.net.Jsonp.CALLBACKS][id]) {
    if (deleteReplyHandler) {
      delete goog.global[goog.net.Jsonp.CALLBACKS][id];
    } else {
      // Removing the script tag doesn't necessarily prevent the script
      // from firing, so we make the callback a noop.
      goog.global[goog.net.Jsonp.CALLBACKS][id] = goog.nullFunction;
    }
  }
};

/**
 * Returns URL encoded payload. The payload should be a map of name-value
 * pairs, in the form {"foo": 1, "bar": true, ...}.  If the map is empty,
 * the URI will be unchanged.
 *
 * <p>The method uses hasOwnProperty() to assure the properties are on the
 * object, not on its prototype.
 *
 * @param {!Object} payload A map of value name pairs to be encoded.
 *     A value may be specified as an array, in which case a query parameter
 *     will be created for each value, e.g.:
 *     {"foo": [1,2]} will encode to "foo=1&foo=2".
 *
 * @param {!goog.Uri} uri A Uri object onto which the payload key value pairs
 *     will be encoded.
 *
 * @return {!goog.Uri} A reference to the Uri sent as a parameter.
 * @private
 */
goog.net.Jsonp.addPayloadToUri_ = function(payload, uri) {
  for (var name in payload) {
    // NOTE(user): Safari/1.3 doesn't have hasOwnProperty(). In that
    // case, we iterate over all properties as a very lame workaround.
    if (!payload.hasOwnProperty || payload.hasOwnProperty(name)) {
      uri.setParameterValues(name, payload[name]);
    }
  }
  return uri;
};

// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
//
// This class allows us (Google) to send data from non-Google and thus
// UNTRUSTED pages to our servers. Under NO CIRCUMSTANCES return
// anything sensitive, such as session or cookie specific data. Return
// only data that you want parties external to Google to have. Also
// NEVER use this method to send data from web pages to untrusted
// servers, or redirects to unknown servers (www.google.com/cache,
// /q=xx&btnl, /url, www.googlepages.com, etc.)
//
// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
<commit_msg><commit_after>// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// The original file lives here: http://go/cross_domain_channel.js

/**
 * @fileoverview Implements a cross-domain communication channel. A
 * typical web page is prevented by browser security from sending
 * request, such as a XMLHttpRequest, to other servers than the ones
 * from which it came. The Jsonp class provides a workound, by
 * using dynamically generated script tags. Typical usage:.
 *
 * var jsonp = new goog.net.Jsonp(new goog.Uri('http://my.host.com/servlet'));
 * var payload = { 'foo': 1, 'bar': true };
 * jsonp.send(payload, function(reply) { alert(reply) });
 *
 * This script works in all browsers that are currently supported by
 * the Google Maps API, which is IE 6.0+, Firefox 0.8+, Safari 1.2.4+,
 * Netscape 7.1+, Mozilla 1.4+, Opera 8.02+.
 *
 */

goog.provide('goog.net.Jsonp');

goog.require('goog.Uri');
goog.require('goog.dom');

// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
//
// This class allows us (Google) to send data from non-Google and thus
// UNTRUSTED pages to our servers. Under NO CIRCUMSTANCES return
// anything sensitive, such as session or cookie specific data. Return
// only data that you want parties external to Google to have. Also
// NEVER use this method to send data from web pages to untrusted
// servers, or redirects to unknown servers (www.google.com/cache,
// /q=xx&btnl, /url, www.googlepages.com, etc.)
//
// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING

/**
 * Creates a new cross domain channel that sends data to the specified
 * host URL. By default, if no reply arrives within 5s, the channel
 * assumes the call failed to complete successfully.
 *
 * @param {goog.Uri|string} uri The Uri of the server side code that receives
 *     data posted through this channel (e.g.,
 *     "http://maps.google.com/maps/geo").
 *
 * @param {string=} opt_callbackParamName The parameter name that is used to
 *     specify the callback. Defaults to "callback".
 *
 * @constructor
 */
goog.net.Jsonp = function(uri, opt_callbackParamName) {
  /**
   * The uri_ object will be used to encode the payload that is sent to the
   * server.
   * @type {goog.Uri}
   * @private
   */
  this.uri_ = new goog.Uri(uri);

  /**
   * This is the callback parameter name that is added to the uri.
   * @type {string}
   * @private
   */
  this.callbackParamName_ = opt_callbackParamName ?
      opt_callbackParamName : 'callback';

  /**
   * The length of time, in milliseconds, this channel is prepared
   * to wait for for a request to complete. The default value is 5 seconds.
   * @type {number}
   * @private
   */
  this.timeout_ = 5000;
};

/**
 * The name of the property of goog.global under which the callback is
 * stored.
 */
goog.net.Jsonp.CALLBACKS = '_callbacks_';

/**
 * Used to generate unique callback IDs. The counter must be global because
 * all channels share a common callback object.
 * @private
 */
goog.net.Jsonp.scriptCounter_ = 0;

/**
 * Sets the length of time, in milliseconds, this channel is prepared
 * to wait for for a request to complete. If the call is not competed
 * within the set time span, it is assumed to have failed. To wait
 * indefinitely for a request to complete set the timout to a negative
 * number.
 *
 * @param {number} timeout The length of time before calls are
 * interrupted.
 */
goog.net.Jsonp.prototype.setRequestTimeout = function(timeout) {
  this.timeout_ = timeout;
};

/**
 * Returns the current timeout value, in milliseconds.
 *
 * @return {number} The timeout value.
 */
goog.net.Jsonp.prototype.getRequestTimeout = function() {
  return this.timeout_;
};

/**
 * Sends the given payload to the URL specified at the construction
 * time. The reply is delivered to the given replyCallback. If the
 * errorCallback is specified and the reply does not arrive within the
 * timeout period set on this channel, the errorCallback is invoked
 * with the original payload.
 *
 * If no reply callback is specified, then the response is expected to
 * consist of calls to globally registered functions. No &callback=
 * URL parameter will be sent in the request, and the script element
 * will be cleaned up after the timeout.
 *
 * @param {Object=} opt_payload Name-value pairs.  If given, these will be
 *     added as parameters to the supplied URI as GET parameters to the
 *     given server URI.
 *
 * @param {Function=} opt_replyCallback A function expecting one
 *     argument, called when the reply arrives, with the response data.
 *
 * @param {Function=} opt_errorCallback A function expecting one
 *     argument, called on timeout, with the payload (if given), otherwise
 *     null.
 *
 * @param {string=} opt_callbackParamValue Value to be used as the
 *     parameter value for the callback parameter (callbackParamName).
 *     To be used when the value needs to be fixed by the client for a
 *     particular request, to make use of the cached responses for the request.
 *     NOTE: If multiple requests are made with the same
 *     opt_callbackParamValue, only the last call will work whenever the
 *     response comes back.
 *
 * @return {Object} A request descriptor that may be used to cancel this
 *     transmission, or null, if the message may not be cancelled.
 */
goog.net.Jsonp.prototype.send = function(opt_payload,
                                         opt_replyCallback,
                                         opt_errorCallback,
                                         opt_callbackParamValue) {

  var payload = opt_payload || null;

  // This is a safeguard that we don't accidentally call appendChild
  // on a null.
  if (!document.documentElement.firstChild) {
    if (opt_errorCallback) {
      opt_errorCallback(payload);
    }
    return null;
  }

  var id = opt_callbackParamValue ||
      '_' + (goog.net.Jsonp.scriptCounter_++).toString(36) +
      goog.now().toString(36);

  if (!goog.global[goog.net.Jsonp.CALLBACKS]) {
    goog.global[goog.net.Jsonp.CALLBACKS] = {};
  }

  var script = goog.dom.createElement('script');

  var timeout = null;
  if (this.timeout_ > 0) {
    var error = goog.net.Jsonp.newErrorHandler_(id, script, payload,
                                                opt_errorCallback);
    timeout = goog.global.setTimeout(error, this.timeout_);
  }

  // Create a new Uri object onto which this payload will be added
  var uri = this.uri_.clone();
  if (payload) {
    goog.net.Jsonp.addPayloadToUri_(payload, uri);
  }

  if (opt_replyCallback) {
    var reply = goog.net.Jsonp.newReplyHandler_(id, script, opt_replyCallback,
                                                timeout);
    goog.global[goog.net.Jsonp.CALLBACKS][id] = reply;

    uri.setParameterValues(this.callbackParamName_,
                           goog.net.Jsonp.CALLBACKS + '.' + id);
  }

  goog.dom.setProperties(script, {
    'type': 'text/javascript',
    'id': id,
    'charset': 'UTF-8',
    // NOTE(user): Safari never loads the script if we don't set
    // the src attribute before appending.
    'src': uri.toString()
  });

  goog.dom.appendChild(document.getElementsByTagName('head')[0], script);
  return { id_: id, timeout_: timeout };
};

/**
 * Cancels a given request. The request must be exactly the object returned by
 * the send method.
 *
 * @param {Object} request The request object returned by the send method.
 */
goog.net.Jsonp.prototype.cancel = function(request) {
  if (request && request.id_) {
    var scriptNode = goog.dom.getElement(request.id_);

    if (scriptNode && scriptNode.tagName == 'SCRIPT' &&
        typeof goog.global[goog.net.Jsonp.CALLBACKS][request.id_] ==
        'function') {
      request.timeout_ && goog.global.clearTimeout(request.timeout_);
      goog.net.Jsonp.cleanup_(request.id_, scriptNode, false);
    }
  }
};

/**
 * Creates a timeout callback that removes the script node and calls
 * the given timeoutCallback with the original payload.
 *
 * @param {string} id The id of the script node.
 * @param {Element} scriptNode Script element.
 * @param {Object} payload The payload that was sent to the server.
 * @param {Function=} opt_errorCallback The function called on timeout.
 * @return {Function} A zero argument function that handles callback duties.
 * @private
 */
goog.net.Jsonp.newErrorHandler_ = function(id,
                                           scriptNode,
                                           payload,
                                           opt_errorCallback) {
  /**
   * When we call across domains with a request, this function is the
   * timeout handler. Once it's done executing the user-specified
   * error-handler, it removes the script node and original function.
   */
  return function() {
    goog.net.Jsonp.cleanup_(id, scriptNode, false);
    if (opt_errorCallback) {
      opt_errorCallback(payload);
    }
  };
};

/**
 * Creates a reply callback that removes the script node and calls the
 * given replyCallback with data returned by the server.
 *
 * @param {string} id The id of the script node.
 * @param {Element} scriptNode Script element.
 * @param {Function} replyCallback The function called on reply.
 * @param {?number} timeout A timeout call that needs to be cleared.
 * @return {Function} A reply callback function.
 * @private
 */
goog.net.Jsonp.newReplyHandler_ = function(id,
                                           scriptNode,
                                           replyCallback,
                                           timeout) {
  /**
   * This function is the handler for the all-is-well response. It
   * clears the error timeout handler, calls the user's handler, then
   * removes the script node and itself.
   *
   * @param {...Object} var_args The response data sent from the server.
   */
  return function(var_args) {
    goog.global.clearTimeout(timeout);
    goog.net.Jsonp.cleanup_(id, scriptNode, true);
    replyCallback.apply(undefined, arguments);
  };
};

/**
 * Removes the script node and reply handler with the given id.
 *
 * @param {string} id The id of the script node to be removed.
 * @param {Node} scriptNode The node to be removed.
 * @param {boolean} deleteReplyHandler If true, delete the reply handler
 *     instead of setting it to nullFunction (if we know the callback could
 *     never be called again).
 * @private
 */
goog.net.Jsonp.cleanup_ = function(id, scriptNode, deleteReplyHandler) {
  // Do this after a delay (removing the script node of a running script can
  // confuse older IEs).
  goog.global.setTimeout(function() {
    goog.dom.removeNode(scriptNode);
  }, 0);

  if (goog.global[goog.net.Jsonp.CALLBACKS][id]) {
    if (deleteReplyHandler) {
      delete goog.global[goog.net.Jsonp.CALLBACKS][id];
    } else {
      // Removing the script tag doesn't necessarily prevent the script
      // from firing, so we make the callback a noop.
      goog.global[goog.net.Jsonp.CALLBACKS][id] = goog.nullFunction;
    }
  }
};

/**
 * Returns URL encoded payload. The payload should be a map of name-value
 * pairs, in the form {"foo": 1, "bar": true, ...}.  If the map is empty,
 * the URI will be unchanged.
 *
 * <p>The method uses hasOwnProperty() to assure the properties are on the
 * object, not on its prototype.
 *
 * @param {!Object} payload A map of value name pairs to be encoded.
 *     A value may be specified as an array, in which case a query parameter
 *     will be created for each value, e.g.:
 *     {"foo": [1,2]} will encode to "foo=1&foo=2".
 *
 * @param {!goog.Uri} uri A Uri object onto which the payload key value pairs
 *     will be encoded.
 *
 * @return {!goog.Uri} A reference to the Uri sent as a parameter.
 * @private
 */
goog.net.Jsonp.addPayloadToUri_ = function(payload, uri) {
  for (var name in payload) {
    // NOTE(user): Safari/1.3 doesn't have hasOwnProperty(). In that
    // case, we iterate over all properties as a very lame workaround.
    if (!payload.hasOwnProperty || payload.hasOwnProperty(name)) {
      uri.setParameterValues(name, payload[name]);
    }
  }
  return uri;
};

// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
//
// This class allows us (Google) to send data from non-Google and thus
// UNTRUSTED pages to our servers. Under NO CIRCUMSTANCES return
// anything sensitive, such as session or cookie specific data. Return
// only data that you want parties external to Google to have. Also
// NEVER use this method to send data from web pages to untrusted
// servers, or redirects to unknown servers (www.google.com/cache,
// /q=xx&btnl, /url, www.googlepages.com, etc.)
//
// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
<|endoftext|>


<commit_before>import Logger from 'color-logger';
import CommentParser from '../Parser/CommentParser.js';
import FileDoc from '../Doc/FileDoc.js';
import ClassDoc from '../Doc/ClassDoc.js';
import MethodDoc from '../Doc/MethodDoc.js';
import MemberDoc from '../Doc/MemberDoc.js';
import FunctionDoc from '../Doc/FunctionDoc.js';
import VariableDoc from '../Doc/VariableDoc.js';
import AssignmentDoc from '../Doc/AssignmentDoc.js';
import TypedefDoc from '../Doc/TypedefDoc.js';
import ExternalDoc from '../Doc/ExternalDoc.js';

let already = Symbol('already');
let logger = new Logger('DocFactory');

export default class DocFactory {
  get results() {
    return [...this._results];
  }

  constructor(ast, pathResolver) {
    this._ast = ast;
    this._pathResolver = pathResolver;
    this._results = [];
    this._processedClassNodes = [];

    // file doc
    let doc = new FileDoc(ast, ast, pathResolver, []);
    this._results.push(doc.value);

    // ast does not child, so only comment.
    if (ast.body.length === 0 && ast.leadingComments) {
      let results = this._traverseComments(ast, null, ast.leadingComments);
      this._results.push(...results);
    }
  }

  push(node, parentNode) {
    if (node === this._ast) return;

    if (node[already]) return;

    let isLastNodeInParent = this._isLastNodeInParent(node, parentNode);

    node[already] = true;
    Object.defineProperty(node, 'parent', {value: parentNode});

    // unwrap export declaration
    if (['ExportDefaultDeclaration', 'ExportNamedDeclaration'].includes(node.type)) {
      parentNode = node;
      node = this._unwrapExportDeclaration(node);
      if (!node) return;
      node[already] = true;
      Object.defineProperty(node, 'parent', {value: parentNode});
    }

    let results = this._traverseComments(parentNode, node, node.leadingComments);
    this._results.push(...results);

    // for trailing comments.
    // traverse with only last node, because prevent duplication of trailing comments.
    if (node.trailingComments && isLastNodeInParent) {
      let results = this._traverseComments(parentNode, null, node.trailingComments);
      this._results.push(...results);
    }
  }

  _traverseComments(parentNode, node, comments) {
    if (!node) {
      let virtualNode = {};
      Object.defineProperty(virtualNode, 'parent', {value: parentNode});
      node = virtualNode;
    }

    if (comments && comments.length) {
      let temp = [];
      for (let comment of comments) {
        if (CommentParser.isESDoc(comment)) temp.push(comment);
      }
      comments = temp;
    } else {
      comments = [];
    }

    if (comments.length === 0) {
      comments = [{type: 'Block', value: '* @undocument'}];
    }

    let results = [];
    let lastComment = comments[comments.length - 1];
    for (let comment of comments) {
      let tags = CommentParser.parse(comment);

      let doc;
      if (comment === lastComment) {
        doc = this._createDoc(node, tags);
      } else {
        let virtualNode = {};
        Object.defineProperty(virtualNode, 'parent', {value: parentNode});
        doc = this._createDoc(virtualNode, tags);
      }

      if (doc) results.push(doc.value);
    }

    return results;
  }

  _createDoc(node, tags) {
    let result = this._decideType(tags, node);
    let type = result.type;
    node = result.node;

    if (!type) return null;

    if (type === 'Class') {
      this._processedClassNodes.push(node);
    }

    let clazz;
    switch (type) {
      case 'Class':   clazz = ClassDoc; break;
      case 'Method':  clazz = MethodDoc; break;
      case 'Member':  clazz = MemberDoc; break;
      case 'Function': clazz = FunctionDoc; break;
      case 'Variable': clazz = VariableDoc; break;
      case 'Assignment': clazz = AssignmentDoc; break;
      case 'Typedef': clazz = TypedefDoc; break;
      case 'External': clazz = ExternalDoc; break;
    }

    if (!clazz) return;
    if (!node.type) node.type = type;

    return new clazz(this._ast, node, this._pathResolver, tags);
  }

  _decideType(tags, node) {
    let type = null;
    for (let tag of tags) {
      let tagName = tag.tagName;
      switch (tagName) {
        case '@class':    type = 'Class'; break;
        case '@member':   type = 'Member'; break;
        case '@method':   type = 'Method'; break;
        case '@function': type = 'Function'; break;
        case '@var':      type = 'Variable'; break;
        case '@typedef':  type = 'Typedef'; break;
        case '@external': type = 'External'; break;
      }
    }

    if (type) return {type, node};

    if (!node) return {type, node};

    switch (node.type) {
      case 'ClassDeclaration':
        return this._decideClassDeclarationType(node);
      case 'MethodDefinition':
        return this._decideMethodDefinitionType(node);
      case 'ExpressionStatement':
        return this._decideExpressionStatementType(node);
      case 'FunctionDeclaration':
        return this._decideFunctionDeclarationType(node);
      case 'VariableDeclaration':
        return this._decideVariableType(node);
      case 'AssignmentExpression':
        return this._decideAssignmentType(node);
    }

    return {type: null, node: null};
  }

  _decideClassDeclarationType(node) {
    if (!this._isTopDepthInBody(node, this._ast.body)) return {type: null, node: null};

    return {type: 'Class', node: node};
  }

  _decideMethodDefinitionType(node) {
    let classNode = this._findUp(node, ['ClassDeclaration', 'ClassExpression']);
    if (this._processedClassNodes.includes(classNode)) {
      return {type: 'Method', node: node};
    } else {
      logger.w('this method is not in class', node);
      return {type: null, node: null};
    }
  }

  _decideFunctionDeclarationType(node) {
    if (!this._isTopDepthInBody(node, this._ast.body)) return {type: null, node: null};

    return {type: 'Function', node: node};
  }

  _decideExpressionStatementType(node) {
    let isTop = this._isTopDepthInBody(node, this._ast.body);
    Object.defineProperty(node.expression, 'parent', {value: node});
    node = node.expression;
    node[already] = true;

    let innerType;
    let innerNode;

    if (!node.right) return {type: null, node: null};

    switch (node.right.type) {
      case 'FunctionExpression':
        innerType = 'Function';
        break;
      case 'ClassExpression':
        innerType = 'Class';
        break;
      default:
        if (node.left.type === 'MemberExpression' && node.left.object.type === 'ThisExpression') {
          let classNode = this._findUp(node, ['ClassExpression', 'ClassDeclaration']);
          if (!this._processedClassNodes.includes(classNode)) {
            logger.w('this member is not in class.', this._pathResolver.filePath, node);
            return {type: null, node: null};
          }
          return {type: 'Member', node: node};
        } else {
          return {type: null, node: null};
        }
    }

    if (!isTop) return {type: null, node: null};

    innerNode = node.right;
    innerNode.id = this._copy(node.left.id || node.left.property);
    Object.defineProperty(innerNode, 'parent', {value: node});
    innerNode[already] = true;

    return {type: innerType, node: innerNode};
  }

  _decideVariableType(node) {
    if (!this._isTopDepthInBody(node, this._ast.body)) return {type: null, node: null};

    let innerType = null;
    let innerNode = null;

    if (!node.declarations[0].init) return {type: innerType, node: innerNode};

    switch (node.declarations[0].init.type) {
      case 'FunctionExpression':
        innerType = 'Function';
        break;
      case 'ClassExpression':
        innerType = 'Class';
        break;
      default:
        return {type: 'Variable', node: node};
    }

    innerNode = node.declarations[0].init;
    innerNode.id = this._copy(node.declarations[0].id);
    Object.defineProperty(innerNode, 'parent', {value: node});
    innerNode[already] = true;

    return {type: innerType, node: innerNode};
  }

  _decideAssignmentType(node) {
    if (!this._isTopDepthInBody(node, this._ast.body)) return {type: null, node: null};

    let innerType;
    let innerNode;

    switch (node.right.type) {
      case 'FunctionExpression':
        innerType = 'Function';
        break;
      case 'ClassExpression':
        innerType = 'Class';
        break;
      default:
        return {type: 'Assignment', node: node};
    }

    innerNode = node.right;
    innerNode.id = this._copy(node.left.id || node.left.property);
    Object.defineProperty(innerNode, 'parent', {value: node});
    innerNode[already] = true;

    return {type: innerType, node: innerNode};
  }

  _unwrapExportDeclaration(node) {
    // e.g. `export A from './A.js'` has not declaration
    if (!node.declaration) return;

    let exportedASTNode = node.declaration;
    if (!exportedASTNode.leadingComments) exportedASTNode.leadingComments = [];
    exportedASTNode.leadingComments.push(...node.leadingComments || []);

    if (!exportedASTNode.trailingComments) exportedASTNode.trailingComments = [];
    exportedASTNode.trailingComments.push(...node.trailingComments || []);

    return exportedASTNode;
  }

  _isLastNodeInParent(node, parentNode) {
    if (parentNode && parentNode.body) {
      let lastNode = parentNode.body[parentNode.body.length - 1];
      return node === lastNode;
    }

    return false;
  }

  _isTopDepthInBody(node, body) {
    if (!body) return false;
    if (!Array.isArray(body)) return false;

    let parentNode = node.parent;
    if (['ExportDefaultDeclaration', 'ExportNamedDeclaration'].includes(parentNode.type)) {
      node = parentNode;
    }

    for (let _node of body) {
      if (node === _node) return true;
    }
    return false;
  }

  _copy(obj) {
    return JSON.parse(JSON.stringify(obj));
  }

  _findUp(node, types) {
    let parent = node.parent;
    while(parent) {
      if (types.includes(parent.type)) return parent;
      parent = parent.parent;
    }

    return null;
  }
}
<commit_msg>fix: comment of method definition with literal<commit_after>import Logger from 'color-logger';
import CommentParser from '../Parser/CommentParser.js';
import FileDoc from '../Doc/FileDoc.js';
import ClassDoc from '../Doc/ClassDoc.js';
import MethodDoc from '../Doc/MethodDoc.js';
import MemberDoc from '../Doc/MemberDoc.js';
import FunctionDoc from '../Doc/FunctionDoc.js';
import VariableDoc from '../Doc/VariableDoc.js';
import AssignmentDoc from '../Doc/AssignmentDoc.js';
import TypedefDoc from '../Doc/TypedefDoc.js';
import ExternalDoc from '../Doc/ExternalDoc.js';

let already = Symbol('already');
let logger = new Logger('DocFactory');

export default class DocFactory {
  get results() {
    return [...this._results];
  }

  constructor(ast, pathResolver) {
    this._ast = ast;
    this._pathResolver = pathResolver;
    this._results = [];
    this._processedClassNodes = [];

    // file doc
    let doc = new FileDoc(ast, ast, pathResolver, []);
    this._results.push(doc.value);

    // ast does not child, so only comment.
    if (ast.body.length === 0 && ast.leadingComments) {
      let results = this._traverseComments(ast, null, ast.leadingComments);
      this._results.push(...results);
    }
  }

  push(node, parentNode) {
    if (node === this._ast) return;

    if (node[already]) return;

    let isLastNodeInParent = this._isLastNodeInParent(node, parentNode);

    node[already] = true;
    Object.defineProperty(node, 'parent', {value: parentNode});

    // unwrap export declaration
    if (['ExportDefaultDeclaration', 'ExportNamedDeclaration'].includes(node.type)) {
      parentNode = node;
      node = this._unwrapExportDeclaration(node);
      if (!node) return;
      node[already] = true;
      Object.defineProperty(node, 'parent', {value: parentNode});
    }

    let results = this._traverseComments(parentNode, node, node.leadingComments);
    this._results.push(...results);

    // for trailing comments.
    // traverse with only last node, because prevent duplication of trailing comments.
    if (node.trailingComments && isLastNodeInParent) {
      let results = this._traverseComments(parentNode, null, node.trailingComments);
      this._results.push(...results);
    }
  }

  _traverseComments(parentNode, node, comments) {
    if (!node) {
      let virtualNode = {};
      Object.defineProperty(virtualNode, 'parent', {value: parentNode});
      node = virtualNode;
    }

    // hack: leadingComment of MethodDefinition with Literal is not valid by espree(v2.0.2)
    if (node.type === 'MethodDefinition' && node.key.type === 'Literal') {
      let prevNode = null;
      for (let child of parentNode.body || []) {
        if (child === node) {
          break;
        }
        prevNode = child;
      }

      if (prevNode) {
        comments = prevNode.trailingComments;
      }
    }

    if (comments && comments.length) {
      let temp = [];
      for (let comment of comments) {
        if (CommentParser.isESDoc(comment)) temp.push(comment);
      }
      comments = temp;
    } else {
      comments = [];
    }

    if (comments.length === 0) {
      comments = [{type: 'Block', value: '* @undocument'}];
    }

    let results = [];
    let lastComment = comments[comments.length - 1];
    for (let comment of comments) {
      let tags = CommentParser.parse(comment);

      let doc;
      if (comment === lastComment) {
        doc = this._createDoc(node, tags);
      } else {
        let virtualNode = {};
        Object.defineProperty(virtualNode, 'parent', {value: parentNode});
        doc = this._createDoc(virtualNode, tags);
      }

      if (doc) results.push(doc.value);
    }

    return results;
  }

  _createDoc(node, tags) {
    let result = this._decideType(tags, node);
    let type = result.type;
    node = result.node;

    if (!type) return null;

    if (type === 'Class') {
      this._processedClassNodes.push(node);
    }

    let clazz;
    switch (type) {
      case 'Class':   clazz = ClassDoc; break;
      case 'Method':  clazz = MethodDoc; break;
      case 'Member':  clazz = MemberDoc; break;
      case 'Function': clazz = FunctionDoc; break;
      case 'Variable': clazz = VariableDoc; break;
      case 'Assignment': clazz = AssignmentDoc; break;
      case 'Typedef': clazz = TypedefDoc; break;
      case 'External': clazz = ExternalDoc; break;
    }

    if (!clazz) return;
    if (!node.type) node.type = type;

    return new clazz(this._ast, node, this._pathResolver, tags);
  }

  _decideType(tags, node) {
    let type = null;
    for (let tag of tags) {
      let tagName = tag.tagName;
      switch (tagName) {
        case '@class':    type = 'Class'; break;
        case '@member':   type = 'Member'; break;
        case '@method':   type = 'Method'; break;
        case '@function': type = 'Function'; break;
        case '@var':      type = 'Variable'; break;
        case '@typedef':  type = 'Typedef'; break;
        case '@external': type = 'External'; break;
      }
    }

    if (type) return {type, node};

    if (!node) return {type, node};

    switch (node.type) {
      case 'ClassDeclaration':
        return this._decideClassDeclarationType(node);
      case 'MethodDefinition':
        return this._decideMethodDefinitionType(node);
      case 'ExpressionStatement':
        return this._decideExpressionStatementType(node);
      case 'FunctionDeclaration':
        return this._decideFunctionDeclarationType(node);
      case 'VariableDeclaration':
        return this._decideVariableType(node);
      case 'AssignmentExpression':
        return this._decideAssignmentType(node);
    }

    return {type: null, node: null};
  }

  _decideClassDeclarationType(node) {
    if (!this._isTopDepthInBody(node, this._ast.body)) return {type: null, node: null};

    return {type: 'Class', node: node};
  }

  _decideMethodDefinitionType(node) {
    let classNode = this._findUp(node, ['ClassDeclaration', 'ClassExpression']);
    if (this._processedClassNodes.includes(classNode)) {
      return {type: 'Method', node: node};
    } else {
      logger.w('this method is not in class', node);
      return {type: null, node: null};
    }
  }

  _decideFunctionDeclarationType(node) {
    if (!this._isTopDepthInBody(node, this._ast.body)) return {type: null, node: null};

    return {type: 'Function', node: node};
  }

  _decideExpressionStatementType(node) {
    let isTop = this._isTopDepthInBody(node, this._ast.body);
    Object.defineProperty(node.expression, 'parent', {value: node});
    node = node.expression;
    node[already] = true;

    let innerType;
    let innerNode;

    if (!node.right) return {type: null, node: null};

    switch (node.right.type) {
      case 'FunctionExpression':
        innerType = 'Function';
        break;
      case 'ClassExpression':
        innerType = 'Class';
        break;
      default:
        if (node.left.type === 'MemberExpression' && node.left.object.type === 'ThisExpression') {
          let classNode = this._findUp(node, ['ClassExpression', 'ClassDeclaration']);
          if (!this._processedClassNodes.includes(classNode)) {
            logger.w('this member is not in class.', this._pathResolver.filePath, node);
            return {type: null, node: null};
          }
          return {type: 'Member', node: node};
        } else {
          return {type: null, node: null};
        }
    }

    if (!isTop) return {type: null, node: null};

    innerNode = node.right;
    innerNode.id = this._copy(node.left.id || node.left.property);
    Object.defineProperty(innerNode, 'parent', {value: node});
    innerNode[already] = true;

    return {type: innerType, node: innerNode};
  }

  _decideVariableType(node) {
    if (!this._isTopDepthInBody(node, this._ast.body)) return {type: null, node: null};

    let innerType = null;
    let innerNode = null;

    if (!node.declarations[0].init) return {type: innerType, node: innerNode};

    switch (node.declarations[0].init.type) {
      case 'FunctionExpression':
        innerType = 'Function';
        break;
      case 'ClassExpression':
        innerType = 'Class';
        break;
      default:
        return {type: 'Variable', node: node};
    }

    innerNode = node.declarations[0].init;
    innerNode.id = this._copy(node.declarations[0].id);
    Object.defineProperty(innerNode, 'parent', {value: node});
    innerNode[already] = true;

    return {type: innerType, node: innerNode};
  }

  _decideAssignmentType(node) {
    if (!this._isTopDepthInBody(node, this._ast.body)) return {type: null, node: null};

    let innerType;
    let innerNode;

    switch (node.right.type) {
      case 'FunctionExpression':
        innerType = 'Function';
        break;
      case 'ClassExpression':
        innerType = 'Class';
        break;
      default:
        return {type: 'Assignment', node: node};
    }

    innerNode = node.right;
    innerNode.id = this._copy(node.left.id || node.left.property);
    Object.defineProperty(innerNode, 'parent', {value: node});
    innerNode[already] = true;

    return {type: innerType, node: innerNode};
  }

  _unwrapExportDeclaration(node) {
    // e.g. `export A from './A.js'` has not declaration
    if (!node.declaration) return;

    let exportedASTNode = node.declaration;
    if (!exportedASTNode.leadingComments) exportedASTNode.leadingComments = [];
    exportedASTNode.leadingComments.push(...node.leadingComments || []);

    if (!exportedASTNode.trailingComments) exportedASTNode.trailingComments = [];
    exportedASTNode.trailingComments.push(...node.trailingComments || []);

    return exportedASTNode;
  }

  _isLastNodeInParent(node, parentNode) {
    if (parentNode && parentNode.body) {
      let lastNode = parentNode.body[parentNode.body.length - 1];
      return node === lastNode;
    }

    return false;
  }

  _isTopDepthInBody(node, body) {
    if (!body) return false;
    if (!Array.isArray(body)) return false;

    let parentNode = node.parent;
    if (['ExportDefaultDeclaration', 'ExportNamedDeclaration'].includes(parentNode.type)) {
      node = parentNode;
    }

    for (let _node of body) {
      if (node === _node) return true;
    }
    return false;
  }

  _copy(obj) {
    return JSON.parse(JSON.stringify(obj));
  }

  _findUp(node, types) {
    let parent = node.parent;
    while(parent) {
      if (types.includes(parent.type)) return parent;
      parent = parent.parent;
    }

    return null;
  }
}
<|endoftext|>


<commit_before>const debounce = (func, wait, immediate) => {
  let timeout
  return function() {
    const context = this
    const args = arguments
    const later = function() {
      timeout = null
      if (!immediate) func.apply(context, args)
    }
    const callNow = immediate && !timeout
    clearTimeout(timeout)
    timeout = setTimeout(later, wait)
    if (callNow) func.apply(context, args)
  }
}

const concatTranscripts = (...transcriptParts) => {
  return transcriptParts.map(t => t.trim()).join(' ').trim()
}

// The command matching code is a modified version of Backbone.Router by Jeremy Ashkenas, under the MIT license.
const optionalParam = /\s*\((.*?)\)\s*/g
const optionalRegex = /(\(\?:[^)]+\))\?/g
const namedParam = /(\(\?)?:\w+/g
const splatParam = /\*/g
const escapeRegExp = /[-{}[\]+?.,\\^$|#]/g
const commandToRegExp = (command) => {
  if (command instanceof RegExp) {
    return new RegExp(command.source, 'i')
  }
  command = command
    .replace(escapeRegExp, '\\$&')
    .replace(optionalParam, '(?:$1)?')
    .replace(namedParam, (match, optional) => {
      return optional ? match : '([^\\s]+)'
    })
    .replace(splatParam, '(.*?)')
    .replace(optionalRegex, '\\s*$1?\\s*')
  return new RegExp('^' + command + '$', 'i')
}

// this is from https://github.com/aceakash/string-similarity
const compareTwoStringsUsingDiceCoefficient = (first, second) => {
  first = first.replace(/\s+/g, '').toLowerCase()
  second = second.replace(/\s+/g, '').toLowerCase()

  if (!first.length && !second.length) return 1 // if both are empty strings
  if (!first.length || !second.length) return 0 // if only one is empty string
  if (first === second) return 1 // identical
  if (first.length === 1 && second.length === 1) return 0 // both are 1-letter strings
  if (first.length < 2 || second.length < 2) return 0 // if either is a 1-letter string

  const firstBigrams = new Map()
  for (let i = 0; i < first.length - 1; i++) {
    const bigram = first.substring(i, i + 2)
    const count = firstBigrams.has(bigram) ? firstBigrams.get(bigram) + 1 : 1

    firstBigrams.set(bigram, count)
  }

  let intersectionSize = 0
  for (let i = 0; i < second.length - 1; i++) {
    const bigram = second.substring(i, i + 2)
    const count = firstBigrams.has(bigram) ? firstBigrams.get(bigram) : 0

    if (count > 0) {
      firstBigrams.set(bigram, count - 1)
      intersectionSize++
    }
  }

  return (2.0 * intersectionSize) / (first.length + second.length - 2)
}

const browserSupportsPolyfills = () => {
  return typeof window !== 'undefined' &&
    window.navigator !== undefined &&
    window.navigator.mediaDevices !== undefined &&
    (window.AudioContext !== undefined || window.webkitAudioContext !== undefined)
}

export {
  debounce,
  concatTranscripts,
  commandToRegExp,
  compareTwoStringsUsingDiceCoefficient,
  browserSupportsPolyfills
}
<commit_msg>Also check for window.navigator.mediaDevices.getUserMedia<commit_after>const debounce = (func, wait, immediate) => {
  let timeout
  return function() {
    const context = this
    const args = arguments
    const later = function() {
      timeout = null
      if (!immediate) func.apply(context, args)
    }
    const callNow = immediate && !timeout
    clearTimeout(timeout)
    timeout = setTimeout(later, wait)
    if (callNow) func.apply(context, args)
  }
}

const concatTranscripts = (...transcriptParts) => {
  return transcriptParts.map(t => t.trim()).join(' ').trim()
}

// The command matching code is a modified version of Backbone.Router by Jeremy Ashkenas, under the MIT license.
const optionalParam = /\s*\((.*?)\)\s*/g
const optionalRegex = /(\(\?:[^)]+\))\?/g
const namedParam = /(\(\?)?:\w+/g
const splatParam = /\*/g
const escapeRegExp = /[-{}[\]+?.,\\^$|#]/g
const commandToRegExp = (command) => {
  if (command instanceof RegExp) {
    return new RegExp(command.source, 'i')
  }
  command = command
    .replace(escapeRegExp, '\\$&')
    .replace(optionalParam, '(?:$1)?')
    .replace(namedParam, (match, optional) => {
      return optional ? match : '([^\\s]+)'
    })
    .replace(splatParam, '(.*?)')
    .replace(optionalRegex, '\\s*$1?\\s*')
  return new RegExp('^' + command + '$', 'i')
}

// this is from https://github.com/aceakash/string-similarity
const compareTwoStringsUsingDiceCoefficient = (first, second) => {
  first = first.replace(/\s+/g, '').toLowerCase()
  second = second.replace(/\s+/g, '').toLowerCase()

  if (!first.length && !second.length) return 1 // if both are empty strings
  if (!first.length || !second.length) return 0 // if only one is empty string
  if (first === second) return 1 // identical
  if (first.length === 1 && second.length === 1) return 0 // both are 1-letter strings
  if (first.length < 2 || second.length < 2) return 0 // if either is a 1-letter string

  const firstBigrams = new Map()
  for (let i = 0; i < first.length - 1; i++) {
    const bigram = first.substring(i, i + 2)
    const count = firstBigrams.has(bigram) ? firstBigrams.get(bigram) + 1 : 1

    firstBigrams.set(bigram, count)
  }

  let intersectionSize = 0
  for (let i = 0; i < second.length - 1; i++) {
    const bigram = second.substring(i, i + 2)
    const count = firstBigrams.has(bigram) ? firstBigrams.get(bigram) : 0

    if (count > 0) {
      firstBigrams.set(bigram, count - 1)
      intersectionSize++
    }
  }

  return (2.0 * intersectionSize) / (first.length + second.length - 2)
}

const browserSupportsPolyfills = () => {
  return typeof window !== 'undefined' &&
    window.navigator !== undefined &&
    window.navigator.mediaDevices !== undefined &&
    window.navigator.mediaDevices.getUserMedia !== undefined &&
    (window.AudioContext !== undefined || window.webkitAudioContext !== undefined)
}

export {
  debounce,
  concatTranscripts,
  commandToRegExp,
  compareTwoStringsUsingDiceCoefficient,
  browserSupportsPolyfills
}
<|endoftext|>


<commit_before>// @flow
import React from 'react';
import styled from '@ncigdc/theme/styled';
import AppLink from '@ncigdc/components/GDCApps/AppLink';

import dataPortalImg from '@ncigdc/theme/images/GDC-App-data-portal-blue.svg';
import publicationsImg from '@ncigdc/theme/images/GDC-App-publications.svg';
import websiteImg from '@ncigdc/theme/images/GDC-App-website-blue.svg';

import { Row } from '@ncigdc/uikit/Flex';

const Container = styled(Row, {
  justifyContent: 'space-around',
  margin: '2rem 0',
  padding: '0 14rem',
  width: '100vw',
});

const GDCAppsRow = () => (
  <Container>
    <AppLink
      appName="portal home"
      description="Data Portal"
      href="https://portal.gdc.cancer.gov"
      imgSrc={dataPortalImg}
      imgWidth={35}
      title="GDC Data Portal"
      />

    <AppLink
      appName="portal home"
      description="Website"
      href="https://gdc.cancer.gov"
      imgSrc={websiteImg}
      imgWidth={35}
      title="GDC Website"
      />

    <AppLink
      appName="portal-api"
      description="API"
      drawnRange={11}
      href="https://gdc.cancer.gov/developers/gdc-application-programming-interface-api"
      imgWidth={29}
      title="API"
      />

    <AppLink
      appName="data-transfer-tool"
      description="Data Transfer Tool"
      drawnRange={9}
      href="https://docs.gdc.cancer.gov"
      imgWidth={29}
      title="GDC Data Transfer Tool"
      />

    <AppLink
      appName="docs"
      description="Documentation"
      drawnRange={15}
      href="https://docs.gdc.cancer.gov"
      imgWidth={29}
      title="GDC Docs"
      />

    <AppLink
      appName="submission-portal"
      description="Data Submission Portal"
      drawnRange={11}
      href="https://portal.gdc.cancer.gov/submission"
      imgWidth={29}
      title="GDC Data Submission Portal"
      />

    <AppLink
      appName="legacy-archive"
      description="Legacy Archive"
      drawnRange={11}
      href="https://portal.gdc.cancer.gov/legacy-archive"
      imgWidth={29}
      title="GDC Legacy Archive"
      />

    <AppLink
      appName="publications"
      description="Publications"
      href="https://gdc.cancer.gov/about-data/publications"
      imgSrc={publicationsImg}
      imgWidth={29}
      title="GDC Publications"
      />
  </Container>
);

export default GDCAppsRow;
<commit_msg>[PRTL-2867] update DTT link (#2806)<commit_after>// @flow
import React from 'react';
import styled from '@ncigdc/theme/styled';
import AppLink from '@ncigdc/components/GDCApps/AppLink';

import dataPortalImg from '@ncigdc/theme/images/GDC-App-data-portal-blue.svg';
import publicationsImg from '@ncigdc/theme/images/GDC-App-publications.svg';
import websiteImg from '@ncigdc/theme/images/GDC-App-website-blue.svg';

import { Row } from '@ncigdc/uikit/Flex';

const Container = styled(Row, {
  justifyContent: 'space-around',
  margin: '2rem 0',
  padding: '0 14rem',
  width: '100vw',
});

const GDCAppsRow = () => (
  <Container>
    <AppLink
      appName="portal home"
      description="Data Portal"
      href="https://portal.gdc.cancer.gov"
      imgSrc={dataPortalImg}
      imgWidth={35}
      title="GDC Data Portal"
      />

    <AppLink
      appName="portal home"
      description="Website"
      href="https://gdc.cancer.gov"
      imgSrc={websiteImg}
      imgWidth={35}
      title="GDC Website"
      />

    <AppLink
      appName="portal-api"
      description="API"
      drawnRange={11}
      href="https://gdc.cancer.gov/developers/gdc-application-programming-interface-api"
      imgWidth={29}
      title="API"
      />

    <AppLink
      appName="data-transfer-tool"
      description="Data Transfer Tool"
      drawnRange={9}
      href="https://docs.gdc.cancer.gov/Data_Transfer_Tool/Users_Guide/Getting_Started/"
      imgWidth={29}
      title="GDC Data Transfer Tool"
      />

    <AppLink
      appName="docs"
      description="Documentation"
      drawnRange={15}
      href="https://docs.gdc.cancer.gov"
      imgWidth={29}
      title="GDC Docs"
      />

    <AppLink
      appName="submission-portal"
      description="Data Submission Portal"
      drawnRange={11}
      href="https://portal.gdc.cancer.gov/submission"
      imgWidth={29}
      title="GDC Data Submission Portal"
      />

    <AppLink
      appName="legacy-archive"
      description="Legacy Archive"
      drawnRange={11}
      href="https://portal.gdc.cancer.gov/legacy-archive"
      imgWidth={29}
      title="GDC Legacy Archive"
      />

    <AppLink
      appName="publications"
      description="Publications"
      href="https://gdc.cancer.gov/about-data/publications"
      imgSrc={publicationsImg}
      imgWidth={29}
      title="GDC Publications"
      />
  </Container>
);

export default GDCAppsRow;
<|endoftext|>


<commit_before>/*
  QuoJS 1.0
  (c) 2011, 2012 Javi Jiménez Villar (@soyjavi)
  http://quojs.tapquo.com
*/

(function($$) {

    var TOUCH = {};
    var TOUCH_TIMEOUT;
    var LONGTAP_DELAY = 750;
    var GESTURES = ['swipe', 'swipeLeft', 'swipeRight', 'swipeUp', 'swipeDown', 'doubleTap', 'longTap'];

    /**
     * ?
     */
    GESTURES.forEach(function(event) {
        $$.fn[event] = function(callback) {
            return this.on(event, callback);
        };
    });

    /**
     * ?
     */
    $$(document).ready(function() {
        _listenTouches();
    });

    function _listenTouches() {
        var environment = $$(document.body);

        environment.bind('touchstart', _onTouchStart);
        environment.bind('touchmove', _onTouchMove);
        environment.bind('touchend', _onTouchEnd);
        environment.bind('touchcancel', _onTouchCancel);
    }

    function _onTouchStart(event) {
        var now = Date.now();
        var delta = now - (TOUCH.last || now);
        var first_touch = ($$.isMobile()) ? event.touches[0] : event;

        TOUCH_TIMEOUT && clearTimeout(TOUCH_TIMEOUT);
        TOUCH = {
            el: $$(_parentIfText(first_touch.target)),
            x1: first_touch.pageX,
            y1: first_touch.pageY,
            isDoubleTap: (delta > 0 && delta <= 250) ? true : false,
            last: now
        }
        setTimeout(_longTap, LONGTAP_DELAY);
    }

    function _onTouchMove(event) {
        var move_touch = ($$.isMobile()) ? event.touches[0] : event;
        TOUCH.x2 = move_touch.pageX;
        TOUCH.y2 = move_touch.pageY;
    }

    function _onTouchEnd(event) {
        if (TOUCH.isDoubleTap) {
            TOUCH.el.trigger('doubleTap');
            TOUCH = {};
        } else if (TOUCH.x2 > 0 || TOUCH.y2 > 0) {
            (Math.abs(TOUCH.x1 - TOUCH.x2) > 30 || Math.abs(TOUCH.y1 - TOUCH.y2) > 30)  &&
            TOUCH.el.trigger('swipe') &&
            TOUCH.el.trigger('swipe' + (_swipeDirection(TOUCH.x1, TOUCH.x2, TOUCH.y1, TOUCH.y2)));

            TOUCH.x1 = TOUCH.x2 = TOUCH.y1 = TOUCH.y2 = TOUCH.last = 0;
            TOUCH = {};
        } else {
            TOUCH_TIMEOUT = setTimeout(function(){
                TOUCH_TIMEOUT = null;
                TOUCH = {};
            }, 250);
        }
    }

    function _onTouchCancel(event) {
        TOUCH = {};
        clearTimeout(TOUCH_TIMEOUT);
    }

    function _parentIfText(node) {
        return 'tagName' in node ? node : node.parentNode;
    }

    function _swipeDirection(x1, x2, y1, y2) {
        var xDelta = Math.abs(x1 - x2);
        var yDelta = Math.abs(y1 - y2);

        if (xDelta >= yDelta) {
            return (x1 - x2 > 0 ? 'Left' : 'Right');
        } else {
            return (y1 - y2 > 0 ? 'Up' : 'Down');
        }
    }

    function _longTap() {
        if (TOUCH.last && (Date.now() - TOUCH.last >= LONGTAP_DELAY)) {
            TOUCH.el.trigger('longTap');
            TOUCH = {};
        }
    }

})(Quo);<commit_msg>Trigger 'tap' event/gesture<commit_after>/*
  QuoJS 1.0
  (c) 2011, 2012 Javi Jiménez Villar (@soyjavi)
  http://quojs.tapquo.com
*/

(function($$) {

    var TOUCH = {};
    var TOUCH_TIMEOUT;
    var LONGTAP_DELAY = 750;
    var GESTURES = ['swipe', 'swipeLeft', 'swipeRight', 'swipeUp', 'swipeDown', 'doubleTap', 'longTap'];

    /**
     * ?
     */
    GESTURES.forEach(function(event) {
        $$.fn[event] = function(callback) {
            return this.on(event, callback);
        };
    });

    /**
     * ?
     */
    $$(document).ready(function() {
        _listenTouches();
    });

    function _listenTouches() {
        var environment = $$(document.body);

        environment.bind('touchstart', _onTouchStart);
        environment.bind('touchmove', _onTouchMove);
        environment.bind('touchend', _onTouchEnd);
        environment.bind('touchcancel', _onTouchCancel);
    }

    function _onTouchStart(event) {
        var now = Date.now();
        var delta = now - (TOUCH.last || now);
        var first_touch = ($$.isMobile()) ? event.touches[0] : event;

        TOUCH_TIMEOUT && clearTimeout(TOUCH_TIMEOUT);
        TOUCH = {
            el: $$(_parentIfText(first_touch.target)),
            x1: first_touch.pageX,
            y1: first_touch.pageY,
            isDoubleTap: (delta > 0 && delta <= 250) ? true : false,
            last: now
        }
        setTimeout(_longTap, LONGTAP_DELAY);
    }

    function _onTouchMove(event) {
        var move_touch = ($$.isMobile()) ? event.touches[0] : event;
        TOUCH.x2 = move_touch.pageX;
        TOUCH.y2 = move_touch.pageY;
    }

    function _onTouchEnd(event) {
        if (TOUCH.isDoubleTap) {
            TOUCH.el.trigger('doubleTap');
            TOUCH = {};
        } else if (TOUCH.x2 > 0 || TOUCH.y2 > 0) {
            (Math.abs(TOUCH.x1 - TOUCH.x2) > 30 || Math.abs(TOUCH.y1 - TOUCH.y2) > 30)  &&
            TOUCH.el.trigger('swipe') &&
            TOUCH.el.trigger('swipe' + (_swipeDirection(TOUCH.x1, TOUCH.x2, TOUCH.y1, TOUCH.y2)));

            TOUCH.x1 = TOUCH.x2 = TOUCH.y1 = TOUCH.y2 = TOUCH.last = 0;
            TOUCH = {};
        } else {
            TOUCH.el.trigger('tap');
            TOUCH_TIMEOUT = setTimeout(function(){
                TOUCH_TIMEOUT = null;
                TOUCH = {};
            }, 250);
        }
    }

    function _onTouchCancel(event) {
        TOUCH = {};
        clearTimeout(TOUCH_TIMEOUT);
    }

    function _parentIfText(node) {
        return 'tagName' in node ? node : node.parentNode;
    }

    function _swipeDirection(x1, x2, y1, y2) {
        var xDelta = Math.abs(x1 - x2);
        var yDelta = Math.abs(y1 - y2);

        if (xDelta >= yDelta) {
            return (x1 - x2 > 0 ? 'Left' : 'Right');
        } else {
            return (y1 - y2 > 0 ? 'Up' : 'Down');
        }
    }

    function _longTap() {
        if (TOUCH.last && (Date.now() - TOUCH.last >= LONGTAP_DELAY)) {
            TOUCH.el.trigger('longTap');
            TOUCH = {};
        }
    }

})(Quo);<|endoftext|>