<commit_before>app.initAgentStatePoller = (function(_const,  utils, profile, eventListener, apiHelper){
	var $agentStatusText;
	var _timerHandler;

	return function(){
		var topBar = document.querySelector('.em-widget-header');
		$agentStatusText = topBar.querySelector('.em-header-status-text');

		eventListener.add(_const.SYSTEM_EVENT.SESSION_OPENED, startOrStopPollAgentState);
		eventListener.add(_const.SYSTEM_EVENT.SESSION_TRANSFERED, startOrStopPollAgentState);
		eventListener.add(_const.SYSTEM_EVENT.SESSION_CLOSED, startOrStopPollAgentState);
		eventListener.add(_const.SYSTEM_EVENT.SESSION_TRANSFERING, startOrStopPollAgentState);
		eventListener.add(_const.SYSTEM_EVENT.SESSION_RESTORED, startOrStopPollAgentState);
	};

	function _start() {
		if (_timerHandler) return;

		// start to poll
		_timerHandler = setInterval(function () {
			_update();
		}, 5000);
	}

	function _stop() {
		_timerHandler = clearInterval(_timerHandler);
		$agentStatusText.innerText = '';
	}

	function _update() {
		var officialAccount = profile.currentOfficialAccount || {};
		var agentId = officialAccount.agentId;
		var agentType = officialAccount.agentType;
		var isSessionOpen = officialAccount.isSessionOpen;

		if (
			!profile.nickNameOption
			|| !agentId
			|| profile.isChatWindowOpen
			|| utils.isBrowserMinimized()
		) return;

		if (!isSessionOpen){
			$agentStatusText.innerText = '';
		}
		else if (agentType === _const.AGENT_ROLE.ROBOT){
			// 机器人不去轮询，显示为在线
			$agentStatusText.innerText = _const.agentStatusText.Online;
		}
		else {
			apiHelper.getAgentStatus(agentId).then(function (state) {
				if (state) {
					$agentStatusText.innerText = _const.agentStatusText[state];
				}
			});
		}
	}

	function startOrStopPollAgentState(officialAccount, event){
		if (officialAccount !== profile.currentOfficialAccount) return;

		var sessionState = officialAccount.sessionState;

		if (sessionState === _const.SESSION_STATE.PROCESSING){
			_start();
		}
		else{
			_stop();
		}
	}
}(easemobim._const, easemobim.utils, app.profile, app.eventListener, app.apiHelper));
<commit_msg>bugfix|不获取坐席在线状态 CLOUD-7101<commit_after>app.initAgentStatePoller = (function(_const,  utils, profile, eventListener, apiHelper){
	var $agentStatusText;
	var _timerHandler;

	return function(){
		var topBar = document.querySelector('.em-widget-header');
		$agentStatusText = topBar.querySelector('.em-header-status-text');

		// 开始轮询坐席状态
		_timerHandler = setInterval(function (){
			var officialAccount = profile.currentOfficialAccount;
			_setAgentStatus(officialAccount);
		}, 5000);

		eventListener.add(_const.SYSTEM_EVENT.SESSION_OPENED, _setAgentStatus);
		eventListener.add(_const.SYSTEM_EVENT.SESSION_TRANSFERED, _setAgentStatus);
		eventListener.add(_const.SYSTEM_EVENT.SESSION_CLOSED, _setAgentStatus);
		eventListener.add(_const.SYSTEM_EVENT.SESSION_TRANSFERING, _setAgentStatus);
		eventListener.add(_const.SYSTEM_EVENT.SESSION_RESTORED, _setAgentStatus);
		eventListener.add(_const.SYSTEM_EVENT.OFFICIAL_ACCOUNT_SWITCHED, function (officialAccount){
			_update(officialAccount.status);
		});
	};

	function _setAgentStatus(officialAccount) {
		if (
			officialAccount !== profile.currentOfficialAccount
			|| !officialAccount
			|| !profile.isChatWindowOpen
			|| utils.isBrowserMinimized()
		) return;

		var agentId = officialAccount.agentId;
		var agentType = officialAccount.agentType;
		var isSessionOpen = officialAccount.isSessionOpen;

		if (agentType === _const.AGENT_ROLE.ROBOT){
			// 机器人不去轮询，显示为在线
			_update('Online');
		}
		else if (
			!profile.nickNameOption
			|| !agentId
			|| !isSessionOpen
		){
			_update(null);
		}
		else {
			apiHelper.getAgentStatus(agentId).then(function (status){
				officialAccount.agentState = status;
				_update(status);
			});
		}
	}

	function _update(status){
		var agentStatusText = _const.agentStatusText[status || 'Other'];
		$agentStatusText.innerText = agentStatusText;
	}
}(easemobim._const, easemobim.utils, app.profile, app.eventListener, app.apiHelper));
<|endoftext|>


<commit_before>/**
 * The DatePicker Component
 *
 * @module aui-datepicker
 * @submodule aui-datepicker-delegate
 */

var Lang = A.Lang,
    isString = Lang.isString,

    EVENT_ENTER_KEY = 'enterKey',
    EVENT_TAB_KEY = 'tabKey',

    _DOCUMENT = A.one(A.config.doc);

/**
 * Fired when then enter key is pressed on an input node.
 *
 * @event enterKey
 */

/**
 * A base class for `DatePickerDelegate`.
 *
 * @class A.DatePickerDelegate
 * @param {Object} config Object literal specifying widget configuration
 *     properties.
 * @constructor
 */

function DatePickerDelegate() {}

DatePickerDelegate.prototype = {
    _eventHandles: null,

    _userInteractionInProgress: false,

    /**
     * Construction logic executed during `DatePickerDelegate` instantiation.
     * Lifecycle.
     *
     * @method initializer
     * @protected
     */
    initializer: function() {
        var instance = this;

        instance.bindDelegateUI();
    },

    /**
     * Destructor logic implementation for the `DatePickerDelegate` class.
     *
     * @method destroy
     * @protected
     */
    destroy: function() {
        var instance = this;

        (new A.EventHandle(instance._eventHandles)).detach();
    },

    /**
     * Bind the events on the `DatePickerDelegate` UI. Lifecycle.
     *
     * @method bindDelegateUI
     * @protected
     */
    bindDelegateUI: function() {
        var instance = this,
            container = instance.get('container'),
            trigger = instance.get('trigger');

        instance._eventHandles = [
            container.delegate(
                ['focus', 'mousedown'],
                A.bind('_onceUserInteraction', instance), trigger),

            container.delegate(
                'blur',
                A.bind('_onUserInteractionRelease', instance), trigger),

            container.delegate(
                'click',
                A.bind('_onceUserInteractionRelease', instance), trigger),

            container.delegate(
                'key',
                A.bind('_handleTabKeyEvent', instance), 'tab', trigger),

            container.delegate(
                'key',
                A.bind('_handleEnterKeyEvent', instance), 'enter', trigger)

        ];

        instance.after(
            'activeInputChange',
            A.bind('_afterActiveInputChange', instance));

        instance.publish(
            'selectionChange', {
                defaultFn: instance._defSelectionChangeFn
            }
        );
    },

    /**
     * Method not implemented.
     *
     * @method focusSelectedValue
     */
    focusSelectedValue: function() {},

    /**
     * Gets the selected dates.
     *
     * @method getSelectedDates
     * @param node
     * @return {Object | null}
     */
    getSelectedDates: function(node) {
        var instance = this,
            activeInput = node || instance.get('activeInput'),
            selectedDates = null;

        if (activeInput) {
            selectedDates = activeInput.getData('datepickerSelection');
        }

        return selectedDates;
    },

    /**
     * Gets parsed dates from input value.
     *
     * @method getParsedDatesFromInputValue
     * @param opt_value
     * @return {Object | null}
     */
    getParsedDatesFromInputValue: function(opt_value) {
        var instance = this,
            valueExtractor = instance.get('valueExtractor'),
            parsedDates = valueExtractor.call(instance, opt_value);

        if (parsedDates) {
            return A.Array.filter(parsedDates, function(parsed) {
                return parsed !== false;
            });
        }

       return null;
    },

    /**
     * Method not implemented.
     *
     * @method useInputNode
     */
    useInputNode: function() {},

    /**
     * Triggers `useInputNode` method once.
     *
     * @method useInputNodeOnce
     * @param node
     */
    useInputNodeOnce: function(node) {
        var instance = this;

        if (!instance._userInteractionInProgress) {
            instance.useInputNode(node);
        }
    },

    /**
    * Fires when the 'activeInput' attribute changes.  The keydown listener is
    * removed from the old active input and is attached to the new one.
    *
    * @method _afterActiveInputChange
    * @param {EventFacade} event
    * @protected
    */
    _afterActiveInputChange: function(event) {
        var instance = this;

        if (event.prevVal) {
            event.prevVal.detach(
                'keydown', instance._handleKeydownEvent, instance);
        }

        if (event.newVal) {
            event.newVal.on('keydown', instance._handleKeydownEvent, instance);
        }
    },

    /**
     * Default behavior for selection change.
     *
     * @method _defSelectionChangeFn
     * @param event
     * @protected
     */
    _defSelectionChangeFn: function(event) {
        var instance = this,
            selection = event.newSelection,
            activeInput = instance.get('activeInput'),
            valueFormatter = instance.get('valueFormatter');

        valueFormatter.call(instance, selection);

        if (activeInput) {
            activeInput.setData('datepickerSelection', selection);
        }
    },

    /**
     * Focus on active calendar.
     *
     * @method _focusActiveCalendar
     * @protected
     */
    _focusActiveCalendar: function() {
        var instance = this;

        if (instance.calendar) {
            var calendar = instance.getCalendar();

            calendar.get('boundingBox').focus();
        }
    },

    /**
     * Formats a date according to a mask.
     *
     * @method _formatDate
     * @param event
     * @protected
     * @return {Date}
     */
    _formatDate: function(date) {
        var instance = this,
            mask = instance.get('mask');

        return A.Date.format(
            date,
            {
                format: mask
            }
        );
    },

    /**
    * Handles keydown events
    *
    * @method _handleKeydownEvent
    * @param event
    * @protected
    */
    _handleKeydownEvent: function(event) {
        var instance = this;

        if (event.isKey('enter')) {
            instance.fire(EVENT_ENTER_KEY);
        }
        else if (event.isKey('tab')) {
            instance.fire(EVENT_TAB_KEY);
        }
    },

    /**
     * Handles tab key events
     *
     * @method _handleTabKeyEvent
     * @param event
     * @protected
     */
    _handleTabKeyEvent: function(event) {
        var instance = this;

        instance._focusActiveCalendar();
    },

    /**
     * Handles enter key events
     *
     * @method _handleEnterKeyEvent
     * @param event
     * @protected
     */
    _handleEnterKeyEvent: function(event) {
        var instance = this;

        instance.show();
    },

    /**
     * Fires once user interacts.
     *
     * @method _onceUserInteraction
     * @param event
     * @protected
     */
    _onceUserInteraction: function(event) {
        var instance = this;

        instance.useInputNodeOnce(event.currentTarget);

        instance._userInteractionInProgress = true;

        instance._focusActiveCalendar();
    },

    /**
     * Fires once user interaction releases.
     *
     * @method _onceUserInteractionRelease
     * @param event
     * @protected
     */
    _onceUserInteractionRelease: function(event) {
        var instance = this;

        instance.useInputNodeOnce(event.currentTarget);

        instance.focusSelectedValue();

        instance._userInteractionInProgress = false;
    },

    /**
     * Fires when user interaction releases.
     *
     * @method _onUserInteractionRelease
     * @param event
     * @protected
     */
    _onUserInteractionRelease: function(event) {
        var instance = this;

        instance.useInputNode(event.currentTarget);

        instance._userInteractionInProgress = false;
    },

    /**
     * Extracts the input value.
     *
     * @method _valueExtractorFn
     * @protected
     */
    _valueExtractorFn: function() {
        return function(opt_value) {
            var instance = this,
                activeInput = instance.get('activeInput'),
                activeInputVal,
                activeInputValue,
                dateSeparator = instance.get('dateSeparator'),
                mask = instance.get('mask'),
                dates;

            if (activeInput) {
                activeInputVal = activeInput.val();
            }

            activeInputValue = Lang.trim(opt_value || activeInputVal);

            if (activeInputValue) {
                dates = [];
                A.Array.each(
                    activeInputValue.split(dateSeparator),
                    function(text) {
                        text = Lang.trim(text);
                        if (text) {
                            dates.push(A.Date.parse(mask, text));
                        }
                    });
            }

            return dates;
        };
    },

    /**
     * Formats a date value.
     *
     * @method _valueFormatterFn
     * @protected
     * @return {Function}
     */
    _valueFormatterFn: function() {
        return function(dates) {
            var instance = this,
                activeInput = instance.get('activeInput'),
                dateSeparator = instance.get('dateSeparator'),
                values = [];

            A.Array.each(dates, function(date) {
                values.push(instance._formatDate(date));
            });

            if (activeInput) {
                activeInput.val(values.join(dateSeparator));
            }
        };
    },
};

/**
 * Static property used to define the default attribute configuration for the
 * `DatePickerDelegate`.
 *
 * @property ATTRS
 * @type {Object}
 * @static
 */
DatePickerDelegate.ATTRS = {

    /**
     * The active input element that holds the calendar instance.
     *
     * @attribute activeInput
     */
    activeInput: {},

    /**
     * Contains an element.
     *
     * @attribute container
     * @writeOnce
     */
    container: {
        setter: A.one,
        value: _DOCUMENT,
        writeOnce: true
    },

    /**
     * Character that separate dates.
     *
     * @attribute dateSeparator
     * @default ' \u2014 '
     * @type {String}
     */
    dateSeparator: {
        value: ' \u2014 ',
        validator: Lang.isString
    },

    /**
     * Defines the date format.
     *
     * @attribute mask
     * @default '%m/%d/%Y'
     * @type {String}
     */
    mask: {
        value: '%m/%d/%Y',
        validator: Lang.isString
    },

    /**
     * Stores a trigger.
     *
     * @attribute trigger
     * @type {String}
     * @writeOnce
     */
    trigger: {
        validator: isString,
        writeOnce: true
    },

    /**
     * Extracts a value from a function.
     *
     * @attribute valueExtractor
     * @type {Function}
     */
    valueExtractor: {
        valueFn: '_valueExtractorFn',
        validator: Lang.isFunction
    },

    /**
     * Formats a value from a function.
     *
     * @attribute valueFormatter
     * @type {Function}
     */
    valueFormatter: {
        valueFn: '_valueFormatterFn',
        validator: Lang.isFunction
    }
};

A.DatePickerDelegate = DatePickerDelegate;<commit_msg>AUI-1353 - Add event.preventDefault() from enter key submitting the form<commit_after>/**
 * The DatePicker Component
 *
 * @module aui-datepicker
 * @submodule aui-datepicker-delegate
 */

var Lang = A.Lang,
    isString = Lang.isString,

    EVENT_ENTER_KEY = 'enterKey',
    EVENT_TAB_KEY = 'tabKey',

    _DOCUMENT = A.one(A.config.doc);

/**
 * Fired when then enter key is pressed on an input node.
 *
 * @event enterKey
 */

/**
 * A base class for `DatePickerDelegate`.
 *
 * @class A.DatePickerDelegate
 * @param {Object} config Object literal specifying widget configuration
 *     properties.
 * @constructor
 */

function DatePickerDelegate() {}

DatePickerDelegate.prototype = {
    _eventHandles: null,

    _userInteractionInProgress: false,

    /**
     * Construction logic executed during `DatePickerDelegate` instantiation.
     * Lifecycle.
     *
     * @method initializer
     * @protected
     */
    initializer: function() {
        var instance = this;

        instance.bindDelegateUI();
    },

    /**
     * Destructor logic implementation for the `DatePickerDelegate` class.
     *
     * @method destroy
     * @protected
     */
    destroy: function() {
        var instance = this;

        (new A.EventHandle(instance._eventHandles)).detach();
    },

    /**
     * Bind the events on the `DatePickerDelegate` UI. Lifecycle.
     *
     * @method bindDelegateUI
     * @protected
     */
    bindDelegateUI: function() {
        var instance = this,
            container = instance.get('container'),
            trigger = instance.get('trigger');

        instance._eventHandles = [
            container.delegate(
                ['focus', 'mousedown'],
                A.bind('_onceUserInteraction', instance), trigger),

            container.delegate(
                'blur',
                A.bind('_onUserInteractionRelease', instance), trigger),

            container.delegate(
                'click',
                A.bind('_onceUserInteractionRelease', instance), trigger),

            container.delegate(
                'key',
                A.bind('_handleTabKeyEvent', instance), 'tab', trigger),

            container.delegate(
                'key',
                A.bind('_handleEnterKeyEvent', instance), 'enter', trigger)

        ];

        instance.after(
            'activeInputChange',
            A.bind('_afterActiveInputChange', instance));

        instance.publish(
            'selectionChange', {
                defaultFn: instance._defSelectionChangeFn
            }
        );
    },

    /**
     * Method not implemented.
     *
     * @method focusSelectedValue
     */
    focusSelectedValue: function() {},

    /**
     * Gets the selected dates.
     *
     * @method getSelectedDates
     * @param node
     * @return {Object | null}
     */
    getSelectedDates: function(node) {
        var instance = this,
            activeInput = node || instance.get('activeInput'),
            selectedDates = null;

        if (activeInput) {
            selectedDates = activeInput.getData('datepickerSelection');
        }

        return selectedDates;
    },

    /**
     * Gets parsed dates from input value.
     *
     * @method getParsedDatesFromInputValue
     * @param opt_value
     * @return {Object | null}
     */
    getParsedDatesFromInputValue: function(opt_value) {
        var instance = this,
            valueExtractor = instance.get('valueExtractor'),
            parsedDates = valueExtractor.call(instance, opt_value);

        if (parsedDates) {
            return A.Array.filter(parsedDates, function(parsed) {
                return parsed !== false;
            });
        }

       return null;
    },

    /**
     * Method not implemented.
     *
     * @method useInputNode
     */
    useInputNode: function() {},

    /**
     * Triggers `useInputNode` method once.
     *
     * @method useInputNodeOnce
     * @param node
     */
    useInputNodeOnce: function(node) {
        var instance = this;

        if (!instance._userInteractionInProgress) {
            instance.useInputNode(node);
        }
    },

    /**
    * Fires when the 'activeInput' attribute changes.  The keydown listener is
    * removed from the old active input and is attached to the new one.
    *
    * @method _afterActiveInputChange
    * @param {EventFacade} event
    * @protected
    */
    _afterActiveInputChange: function(event) {
        var instance = this;

        if (event.prevVal) {
            event.prevVal.detach(
                'keydown', instance._handleKeydownEvent, instance);
        }

        if (event.newVal) {
            event.newVal.on('keydown', instance._handleKeydownEvent, instance);
        }
    },

    /**
     * Default behavior for selection change.
     *
     * @method _defSelectionChangeFn
     * @param event
     * @protected
     */
    _defSelectionChangeFn: function(event) {
        var instance = this,
            selection = event.newSelection,
            activeInput = instance.get('activeInput'),
            valueFormatter = instance.get('valueFormatter');

        valueFormatter.call(instance, selection);

        if (activeInput) {
            activeInput.setData('datepickerSelection', selection);
        }
    },

    /**
     * Focus on active calendar.
     *
     * @method _focusActiveCalendar
     * @protected
     */
    _focusActiveCalendar: function() {
        var instance = this;

        if (instance.calendar) {
            var calendar = instance.getCalendar();

            calendar.get('boundingBox').focus();
        }
    },

    /**
     * Formats a date according to a mask.
     *
     * @method _formatDate
     * @param event
     * @protected
     * @return {Date}
     */
    _formatDate: function(date) {
        var instance = this,
            mask = instance.get('mask');

        return A.Date.format(
            date,
            {
                format: mask
            }
        );
    },

    /**
    * Handles keydown events
    *
    * @method _handleKeydownEvent
    * @param event
    * @protected
    */
    _handleKeydownEvent: function(event) {
        var instance = this;

        if (event.isKey('enter')) {
            instance.fire(EVENT_ENTER_KEY);
        }
        else if (event.isKey('tab')) {
            instance.fire(EVENT_TAB_KEY);
        }
    },

    /**
     * Handles tab key events
     *
     * @method _handleTabKeyEvent
     * @param event
     * @protected
     */
    _handleTabKeyEvent: function(event) {
        var instance = this;

        instance._focusActiveCalendar();
    },

    /**
     * Handles enter key events
     *
     * @method _handleEnterKeyEvent
     * @param event
     * @protected
     */
    _handleEnterKeyEvent: function(event) {
        var instance = this;

        event.preventDefault();

        instance.show();
    },

    /**
     * Fires once user interacts.
     *
     * @method _onceUserInteraction
     * @param event
     * @protected
     */
    _onceUserInteraction: function(event) {
        var instance = this;

        instance.useInputNodeOnce(event.currentTarget);

        instance._userInteractionInProgress = true;

        instance._focusActiveCalendar();
    },

    /**
     * Fires once user interaction releases.
     *
     * @method _onceUserInteractionRelease
     * @param event
     * @protected
     */
    _onceUserInteractionRelease: function(event) {
        var instance = this;

        instance.useInputNodeOnce(event.currentTarget);

        instance.focusSelectedValue();

        instance._userInteractionInProgress = false;
    },

    /**
     * Fires when user interaction releases.
     *
     * @method _onUserInteractionRelease
     * @param event
     * @protected
     */
    _onUserInteractionRelease: function(event) {
        var instance = this;

        instance.useInputNode(event.currentTarget);

        instance._userInteractionInProgress = false;
    },

    /**
     * Extracts the input value.
     *
     * @method _valueExtractorFn
     * @protected
     */
    _valueExtractorFn: function() {
        return function(opt_value) {
            var instance = this,
                activeInput = instance.get('activeInput'),
                activeInputVal,
                activeInputValue,
                dateSeparator = instance.get('dateSeparator'),
                mask = instance.get('mask'),
                dates;

            if (activeInput) {
                activeInputVal = activeInput.val();
            }

            activeInputValue = Lang.trim(opt_value || activeInputVal);

            if (activeInputValue) {
                dates = [];
                A.Array.each(
                    activeInputValue.split(dateSeparator),
                    function(text) {
                        text = Lang.trim(text);
                        if (text) {
                            dates.push(A.Date.parse(mask, text));
                        }
                    });
            }

            return dates;
        };
    },

    /**
     * Formats a date value.
     *
     * @method _valueFormatterFn
     * @protected
     * @return {Function}
     */
    _valueFormatterFn: function() {
        return function(dates) {
            var instance = this,
                activeInput = instance.get('activeInput'),
                dateSeparator = instance.get('dateSeparator'),
                values = [];

            A.Array.each(dates, function(date) {
                values.push(instance._formatDate(date));
            });

            if (activeInput) {
                activeInput.val(values.join(dateSeparator));
            }
        };
    },
};

/**
 * Static property used to define the default attribute configuration for the
 * `DatePickerDelegate`.
 *
 * @property ATTRS
 * @type {Object}
 * @static
 */
DatePickerDelegate.ATTRS = {

    /**
     * The active input element that holds the calendar instance.
     *
     * @attribute activeInput
     */
    activeInput: {},

    /**
     * Contains an element.
     *
     * @attribute container
     * @writeOnce
     */
    container: {
        setter: A.one,
        value: _DOCUMENT,
        writeOnce: true
    },

    /**
     * Character that separate dates.
     *
     * @attribute dateSeparator
     * @default ' \u2014 '
     * @type {String}
     */
    dateSeparator: {
        value: ' \u2014 ',
        validator: Lang.isString
    },

    /**
     * Defines the date format.
     *
     * @attribute mask
     * @default '%m/%d/%Y'
     * @type {String}
     */
    mask: {
        value: '%m/%d/%Y',
        validator: Lang.isString
    },

    /**
     * Stores a trigger.
     *
     * @attribute trigger
     * @type {String}
     * @writeOnce
     */
    trigger: {
        validator: isString,
        writeOnce: true
    },

    /**
     * Extracts a value from a function.
     *
     * @attribute valueExtractor
     * @type {Function}
     */
    valueExtractor: {
        valueFn: '_valueExtractorFn',
        validator: Lang.isFunction
    },

    /**
     * Formats a value from a function.
     *
     * @attribute valueFormatter
     * @type {Function}
     */
    valueFormatter: {
        valueFn: '_valueFormatterFn',
        validator: Lang.isFunction
    }
};

A.DatePickerDelegate = DatePickerDelegate;<|endoftext|>


<commit_before>/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
 * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
 * full text of the license. */

/**
 * @requires OpenLayers/Util.js
 * @requires OpenLayers/Marker.js
 * @requires OpenLayers/Popup/AnchoredBubble.js
 */

/**
 * Class: OpenLayers.Feature
 * Features are combinations of geography and attributes. The OpenLayers.Feature
 *     class specifically combines a marker and a lonlat.
 */
OpenLayers.Feature = OpenLayers.Class({

    /**
     * Property: layer
     * {<OpenLayers.Layer>}
     */
    layer: null,

    /**
     * Property: id
     * {String}
     */
    id: null,

    /**
     * Property: lonlat
     * {<OpenLayers.LonLat>}
     */
    lonlat: null,

    /**
     * Property: data
     * {Object}
     */
    data: null,

    /**
     * Property: marker
     * {<OpenLayers.Marker>}
     */
    marker: null,

    /**
     * APIProperty: popupClass
     * {<OpenLayers.Class>} The class which will be used to instantiate
     *     a new Popup. Default is <OpenLayers.Popup.AnchoredBubble>.
     */
    popupClass: OpenLayers.Popup.AnchoredBubble,

    /**
     * Property: popup
     * {<OpenLayers.Popup>}
     */
    popup: null,

    /**
     * Constructor: OpenLayers.Feature
     * Constructor for features.
     *
     * Parameters:
     * layer - {<OpenLayers.Layer>}
     * lonlat - {<OpenLayers.LonLat>}
     * data - {Object}
     *
     * Returns:
     * {<OpenLayers.Feature>}
     */
    initialize: function(layer, lonlat, data) {
        this.layer = layer;
        this.lonlat = lonlat;
        this.data = (data != null) ? data : {};
        this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
    },

    /**
     * Method: destroy
     * nullify references to prevent circular references and memory leaks
     */
    destroy: function() {

        //remove the popup from the map
        if ((this.layer != null) && (this.layer.map != null)) {
            if (this.popup != null) {
                this.layer.map.removePopup(this.popup);
            }
        }

        this.layer = null;
        this.id = null;
        this.lonlat = null;
        this.data = null;
        if (this.marker != null) {
            this.destroyMarker(this.marker);
            this.marker = null;
        }
        if (this.popup != null) {
            this.destroyPopup(this.popup);
            this.popup = null;
        }
    },

    /**
     * Method: onScreen
     *
     * Returns:
     * {Boolean} Whether or not the feature is currently visible on screen
     *           (based on its 'lonlat' property)
     */
    onScreen:function() {

        var onScreen = false;
        if ((this.layer != null) && (this.layer.map != null)) {
            var screenBounds = this.layer.map.getExtent();
            onScreen = screenBounds.containsLonLat(this.lonlat);
        }
        return onScreen;
    },

    /**
     * Method: createMarker
     * Based on the data associated with the Feature, create and return a marker object.
     *
     * Returns:
     * {<OpenLayers.Marker>} A Marker Object created from the 'lonlat' and 'icon' properties
     *          set in this.data. If no 'lonlat' is set, returns null. If no
     *          'icon' is set, OpenLayers.Marker() will load the default image.
     *
     *          Note - this.marker is set to return value
     *
     */
    createMarker: function() {

        if (this.lonlat != null) {
            this.marker = new OpenLayers.Marker(this.lonlat, this.data.icon);
        }
        return this.marker;
    },

    /**
     * Method: destroyMarker
     * Destroys marker.
     * If user overrides the createMarker() function, s/he should be able
     *   to also specify an alternative function for destroying it
     */
    destroyMarker: function() {
        this.marker.destroy();
    },

    /**
     * Method: createPopup
     * Creates a popup object created from the 'lonlat', 'popupSize',
     *     and 'popupContentHTML' properties set in this.data. It uses
     *     this.marker.icon as default anchor.
     *
     *  If no 'lonlat' is set, returns null.
     *  If no this.marker has been created, no anchor is sent.
     *
     *  Note - this.popup is set to return value
     *
     * Parameters:
     * closeBox - {Boolean} create popup with closebox or not
     *
     * Returns:
     * {<OpenLayers.Popup>} Returns the created popup, which is also set
     *     as 'popup' property of this feature. Will be of whatever type
     *     specified by this feature's 'popupClass' property, but must be
     *     of type <OpenLayers.Popup>.
     *
     */
    createPopup: function(closeBox) {

        if (this.lonlat != null) {

            var id = this.id + "_popup";
            var anchor = (this.marker) ? this.marker.icon : null;

            if (!this.popup) {
                this.popup = new this.popupClass(id,
                                                 this.lonlat,
                                                 this.data.popupSize,
                                                 this.data.popupContentHTML,
                                                 anchor,
                                                 closeBox);
            }
            if (this.data.overflow != null) {
                this.popup.contentDiv.style.overflow = this.data.overflow;
            }

            this.popup.feature = this;
        }
        return this.popup;
    },

    /**
     * Method: destroyPopup
     * Destroys the popup created via createPopup.
     *
     * As with the marker, if user overrides the createPopup() function, s/he
     *   should also be able to override the destruction
     */
    destroyPopup: function() {
        if (this.popup) {
            this.popup.feature = null;
            this.popup.destroy();
            this.popup = null;
        }
    },

    CLASS_NAME: "OpenLayers.Feature"
});
<commit_msg>Closes #1490.  Add comment to make it clear that callers of createPopup must not call destroy on returned popups but rather use the feature.destroyPopup method.<commit_after>/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
 * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
 * full text of the license. */

/**
 * @requires OpenLayers/Util.js
 * @requires OpenLayers/Marker.js
 * @requires OpenLayers/Popup/AnchoredBubble.js
 */

/**
 * Class: OpenLayers.Feature
 * Features are combinations of geography and attributes. The OpenLayers.Feature
 *     class specifically combines a marker and a lonlat.
 */
OpenLayers.Feature = OpenLayers.Class({

    /**
     * Property: layer
     * {<OpenLayers.Layer>}
     */
    layer: null,

    /**
     * Property: id
     * {String}
     */
    id: null,

    /**
     * Property: lonlat
     * {<OpenLayers.LonLat>}
     */
    lonlat: null,

    /**
     * Property: data
     * {Object}
     */
    data: null,

    /**
     * Property: marker
     * {<OpenLayers.Marker>}
     */
    marker: null,

    /**
     * APIProperty: popupClass
     * {<OpenLayers.Class>} The class which will be used to instantiate
     *     a new Popup. Default is <OpenLayers.Popup.AnchoredBubble>.
     */
    popupClass: OpenLayers.Popup.AnchoredBubble,

    /**
     * Property: popup
     * {<OpenLayers.Popup>}
     */
    popup: null,

    /**
     * Constructor: OpenLayers.Feature
     * Constructor for features.
     *
     * Parameters:
     * layer - {<OpenLayers.Layer>}
     * lonlat - {<OpenLayers.LonLat>}
     * data - {Object}
     *
     * Returns:
     * {<OpenLayers.Feature>}
     */
    initialize: function(layer, lonlat, data) {
        this.layer = layer;
        this.lonlat = lonlat;
        this.data = (data != null) ? data : {};
        this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
    },

    /**
     * Method: destroy
     * nullify references to prevent circular references and memory leaks
     */
    destroy: function() {

        //remove the popup from the map
        if ((this.layer != null) && (this.layer.map != null)) {
            if (this.popup != null) {
                this.layer.map.removePopup(this.popup);
            }
        }

        this.layer = null;
        this.id = null;
        this.lonlat = null;
        this.data = null;
        if (this.marker != null) {
            this.destroyMarker(this.marker);
            this.marker = null;
        }
        if (this.popup != null) {
            this.destroyPopup(this.popup);
            this.popup = null;
        }
    },

    /**
     * Method: onScreen
     *
     * Returns:
     * {Boolean} Whether or not the feature is currently visible on screen
     *           (based on its 'lonlat' property)
     */
    onScreen:function() {

        var onScreen = false;
        if ((this.layer != null) && (this.layer.map != null)) {
            var screenBounds = this.layer.map.getExtent();
            onScreen = screenBounds.containsLonLat(this.lonlat);
        }
        return onScreen;
    },

    /**
     * Method: createMarker
     * Based on the data associated with the Feature, create and return a marker object.
     *
     * Returns:
     * {<OpenLayers.Marker>} A Marker Object created from the 'lonlat' and 'icon' properties
     *          set in this.data. If no 'lonlat' is set, returns null. If no
     *          'icon' is set, OpenLayers.Marker() will load the default image.
     *
     *          Note - this.marker is set to return value
     *
     */
    createMarker: function() {

        if (this.lonlat != null) {
            this.marker = new OpenLayers.Marker(this.lonlat, this.data.icon);
        }
        return this.marker;
    },

    /**
     * Method: destroyMarker
     * Destroys marker.
     * If user overrides the createMarker() function, s/he should be able
     *   to also specify an alternative function for destroying it
     */
    destroyMarker: function() {
        this.marker.destroy();
    },

    /**
     * Method: createPopup
     * Creates a popup object created from the 'lonlat', 'popupSize',
     *     and 'popupContentHTML' properties set in this.data. It uses
     *     this.marker.icon as default anchor.
     *
     *  If no 'lonlat' is set, returns null.
     *  If no this.marker has been created, no anchor is sent.
     *
     *  Note - the returned popup object is 'owned' by the feature, so you
     *      cannot use the popup's destroy method to discard the popup.
     *      Instead, you must use the feature's destroyPopup
     *
     *  Note - this.popup is set to return value
     *
     * Parameters:
     * closeBox - {Boolean} create popup with closebox or not
     *
     * Returns:
     * {<OpenLayers.Popup>} Returns the created popup, which is also set
     *     as 'popup' property of this feature. Will be of whatever type
     *     specified by this feature's 'popupClass' property, but must be
     *     of type <OpenLayers.Popup>.
     *
     */
    createPopup: function(closeBox) {

        if (this.lonlat != null) {

            var id = this.id + "_popup";
            var anchor = (this.marker) ? this.marker.icon : null;

            if (!this.popup) {
                this.popup = new this.popupClass(id,
                                                 this.lonlat,
                                                 this.data.popupSize,
                                                 this.data.popupContentHTML,
                                                 anchor,
                                                 closeBox);
            }
            if (this.data.overflow != null) {
                this.popup.contentDiv.style.overflow = this.data.overflow;
            }

            this.popup.feature = this;
        }
        return this.popup;
    },

    /**
     * Method: destroyPopup
     * Destroys the popup created via createPopup.
     *
     * As with the marker, if user overrides the createPopup() function, s/he
     *   should also be able to override the destruction
     */
    destroyPopup: function() {
        if (this.popup) {
            this.popup.feature = null;
            this.popup.destroy();
            this.popup = null;
        }
    },

    CLASS_NAME: "OpenLayers.Feature"
});
<|endoftext|>


<commit_before>// executed inside sandboxed browser environment,
// no access to scrope outside of function
export default function pruneNonCriticalSelectors ({
  selectors,
  maxElementsToCheckPerSelector
}) {
  console.log('debug: pruneNonCriticalSelectors init')
  var h = window.innerHeight

  // cache whether elements are above fold,
  // primarily because getBoundingClientRect() can be slow to query,
  // and some stylesheets have lots of generic selectors (like '.button', '.fa' etc)
  var isElementAboveFoldCache = new Map()

  function isElementAboveFold (element) {
    if (isElementAboveFoldCache.has(element)) {
      return isElementAboveFoldCache.get(element)
    }

    // temporarily force clear none in order to catch elements that clear previous
    // content themselves and who w/o their styles could show up unstyled in above
    // the fold content (if they rely on f.e. 'clear:both;' to clear some main content)

    // but do that only if elements have their style attribute defined
    // (ref: https://github.com/pocketjoso/penthouse/issues/342)
    if (typeof element.style !== 'undefined') {
      var originalClearStyle = element.style.clear || ''
      element.style.clear = 'none'
    }

    var aboveFold = element.getBoundingClientRect().top < h
    // cache so we dont have to re-query DOM for this value
    isElementAboveFoldCache.set(element, aboveFold)

    if (typeof element.style !== 'undefined') {
      // set clear style back to what it was
      element.style.clear = originalClearStyle
    }

    // Should not be needed anymore with Chrome Headless:
    // do some monitoring before complete removing the code (below)
    // if (!aboveFold) {
    //   // phantomJS/QT browser has some bugs regarding fixed position;
    //   // sometimes positioning elements outside of screen incorrectly.
    //   // just keep all fixed position elements - normally very few in a stylesheet anyway
    //   var styles = window.getComputedStyle(element, null)
    //   if (styles.position === 'fixed') {
    //     console.log('debug: force keeping fixed position styles')
    //     return true
    //   }
    // }
    return aboveFold
  }

  function isSelectorCritical (selector) {
    // we have a selector to test, first grab any matching elements
    let elements
    try {
      elements = document.querySelectorAll(selector)
    } catch (e) {
      // not a valid selector, remove it.
      return false
    }

    let nrElementsToCheck = elements.length
    if (
      maxElementsToCheckPerSelector &&
      nrElementsToCheck > maxElementsToCheckPerSelector
    ) {
      console.log(
        `debug: isSelectorCritical, selector: ${selector} appearing ${nrElementsToCheck} time on page, ONLY checking first ${maxElementsToCheckPerSelector}...`
      )
      nrElementsToCheck = maxElementsToCheckPerSelector
    }

    // only keep selectors that match at least one elements on the page above the fold
    for (let idx = 0; idx < nrElementsToCheck; idx++) {
      if (isElementAboveFold(elements[idx])) {
        return true
      }
    }

    return false
  }

  function filterSelectors (selectors) {
    console.log('debug: filterSelectors START')

    selectors = selectors.filter(isSelectorCritical)

    console.log('debug: filterSelectors DONE')
    return selectors
  }

  return filterSelectors(selectors)
}
<commit_msg>Create a variable to save computations<commit_after>// executed inside sandboxed browser environment,
// no access to scrope outside of function
export default function pruneNonCriticalSelectors ({
  selectors,
  maxElementsToCheckPerSelector
}) {
  console.log('debug: pruneNonCriticalSelectors init')
  var h = window.innerHeight

  // cache whether elements are above fold,
  // primarily because getBoundingClientRect() can be slow to query,
  // and some stylesheets have lots of generic selectors (like '.button', '.fa' etc)
  var isElementAboveFoldCache = new Map()

  function isElementAboveFold (element) {
    if (isElementAboveFoldCache.has(element)) {
      return isElementAboveFoldCache.get(element)
    }

    // temporarily force clear none in order to catch elements that clear previous
    // content themselves and who w/o their styles could show up unstyled in above
    // the fold content (if they rely on f.e. 'clear:both;' to clear some main content)

    // but do that only if elements have their style attribute defined
    // (ref: https://github.com/pocketjoso/penthouse/issues/342)
    const isElementStyleDefined = typeof element.style !== 'undefined'
    if (isElementStyleDefined) {
      var originalClearStyle = element.style.clear || ''
      element.style.clear = 'none'
    }

    var aboveFold = element.getBoundingClientRect().top < h
    // cache so we dont have to re-query DOM for this value
    isElementAboveFoldCache.set(element, aboveFold)

    if (isElementStyleDefined) {
      // set clear style back to what it was
      element.style.clear = originalClearStyle
    }

    // Should not be needed anymore with Chrome Headless:
    // do some monitoring before complete removing the code (below)
    // if (!aboveFold) {
    //   // phantomJS/QT browser has some bugs regarding fixed position;
    //   // sometimes positioning elements outside of screen incorrectly.
    //   // just keep all fixed position elements - normally very few in a stylesheet anyway
    //   var styles = window.getComputedStyle(element, null)
    //   if (styles.position === 'fixed') {
    //     console.log('debug: force keeping fixed position styles')
    //     return true
    //   }
    // }
    return aboveFold
  }

  function isSelectorCritical (selector) {
    // we have a selector to test, first grab any matching elements
    let elements
    try {
      elements = document.querySelectorAll(selector)
    } catch (e) {
      // not a valid selector, remove it.
      return false
    }

    let nrElementsToCheck = elements.length
    if (
      maxElementsToCheckPerSelector &&
      nrElementsToCheck > maxElementsToCheckPerSelector
    ) {
      console.log(
        `debug: isSelectorCritical, selector: ${selector} appearing ${nrElementsToCheck} time on page, ONLY checking first ${maxElementsToCheckPerSelector}...`
      )
      nrElementsToCheck = maxElementsToCheckPerSelector
    }

    // only keep selectors that match at least one elements on the page above the fold
    for (let idx = 0; idx < nrElementsToCheck; idx++) {
      if (isElementAboveFold(elements[idx])) {
        return true
      }
    }

    return false
  }

  function filterSelectors (selectors) {
    console.log('debug: filterSelectors START')

    selectors = selectors.filter(isSelectorCritical)

    console.log('debug: filterSelectors DONE')
    return selectors
  }

  return filterSelectors(selectors)
}
<|endoftext|>


<commit_before>import { replaceWithStagingDomain } from '../../../platform/utilities/environment/stagingDomains';

export default `

        Your browser is out of date. To use this website, please update your browser or use a different device.

An official website of the United States government

            <button id="usa-banner-toggle" class="usa-accordion-button usa-banner-button" aria-expanded="false" aria-controls="gov-banner">
              Here’s how you know
            </button>

                  The .gov means it’s official.

                  Federal government websites often end in .gov or .mil. Before sharing sensitive information, make sure you're on a federal government site.

                  The site is secure.

 The https:// ensures that you're connecting to the official website and that any information you provide is encrypted and sent securely.

      <button data-show="#modal-crisisline" class="va-crisis-line va-overlay-trigger">

          Talk to the Veterans Crisis Line now

      </button>

`;
<commit_msg>encode right single quote on headeer (#9031)<commit_after>import { replaceWithStagingDomain } from '../../../platform/utilities/environment/stagingDomains';

export default `

        Your browser is out of date. To use this website, please update your browser or use a different device.

An official website of the United States government

            <button id="usa-banner-toggle" class="usa-accordion-button usa-banner-button" aria-expanded="false" aria-controls="gov-banner">
              Here’s how you know
            </button>

                  The .gov means it’s official.

                  Federal government websites often end in .gov or .mil. Before sharing sensitive information, make sure you're on a federal government site.

                  The site is secure.

 The https:// ensures that you're connecting to the official website and that any information you provide is encrypted and sent securely.

      <button data-show="#modal-crisisline" class="va-crisis-line va-overlay-trigger">

          Talk to the Veterans Crisis Line now

      </button>

`;
<|endoftext|>


<commit_before>/* global ImportedStrings */

/// <reference path="Table.js" />
/// <reference path="Strings.js" />
/// <reference path="Headers.js" />

// Builds array of values for each header in receivedHeaderNames.
// This algorithm should work regardless of the order of the headers, given:
//  - The date, if present, is always at the end, separated by a ";".
// Values not attached to a header will not be reflected in output.
var ReceivedRow = function (receivedHeader) {
    var receivedHeaderNames = ["from", "by", "with", "id", "for", "via"];

    // Build array of header locations
    var headerMatches = [];

    this.sourceHeader = receivedHeader;

    // Some bad dates don't wrap UTC in paren - fix that first
    receivedHeader = receivedHeader.replace(/UTC|\(UTC\)/g, "(UTC)");

    // Read out the date first, then clear it from the string
    var iDate = receivedHeader.lastIndexOf(";");

    // No semicolon means no date - or maybe there's one there?
    // Sendgrid is bad about this
    if (iDate === -1) {
        // First try to find a day of the week
        receivedHeader = receivedHeader.replace(/\s*(Mon|Tue|Wed|Thu|Fri|Sat|Sun)/g, "; $1");
        iDate = receivedHeader.lastIndexOf(";")
    }

    if (iDate === -1) {
        // Next we look for year-month-day
        // Swap to month-day-year because IE can't parse it otherwise
        receivedHeader = receivedHeader.replace(/\s*(\d{4})-(\d{1,2})-(\d{1,2})/g, "; $2-$3-$1");
        iDate = receivedHeader.lastIndexOf(";")
    }

    if (iDate !== -1) {
        this.date = receivedHeader.substring(iDate + 1);
        receivedHeader = receivedHeader.substring(0, iDate);
    }

    // Scan for malformed postFix headers
    // Received: by example.com (Postfix, from userid 1001)
    //   id 1234ABCD; Thu, 21 Aug 2014 12:12:48 +0200 (CEST)
    var postFix = receivedHeader.match(/(.*)by (.*? \(Postfix, from userid .*?\))(.*)/);
    if (postFix) {
        this["by"] = postFix[2];
        receivedHeader = postFix[1] + postFix[3];
        receivedHeaderNames = RemoveEntry(receivedHeaderNames, "by");
    }

    // Scan for malformed qmail headers
    // Received: (qmail 10876 invoked from network); 24 Aug 2014 16:13:38 -0000
    postFix = receivedHeader.match(/(.*)\((qmail .*? invoked from .*?)\)(.*)/);
    if (postFix) {
        this["by"] = postFix[2];
        receivedHeader = postFix[1] + postFix[3];
        receivedHeaderNames = RemoveEntry(receivedHeaderNames, "by");
    }

    // Split up the string now so we can look for our headers
    var tokens = receivedHeader.split(/\s+/);

    var iMatch = 0;
    var iHeader;
    var iToken;

    for (iHeader = 0; iHeader < receivedHeaderNames.length; iHeader++) {
        this[receivedHeaderNames[iHeader]] = "";
        for (iToken = 0; iToken < tokens.length; iToken++) {
            if (receivedHeaderNames[iHeader] === tokens[iToken]) {
                headerMatches[iMatch++] = { iHeader: iHeader, iToken: iToken };
            }
        }
    }

    // Next bit assumes headerMatches[x,y] is increasing on y.
    // Sort it so it is.
    headerMatches.sort(function (a, b) { return a.iToken - b.iToken; });

    for (iMatch = 0; iMatch < headerMatches.length; iMatch++) {
        iHeader = headerMatches[iMatch].iHeader;
        var iTokenHeader = headerMatches[iMatch].iToken;
        var iFirstVal = iTokenHeader + 1;

        var iNextTokenHeader;
        var iLastVal;
        if (iMatch + 1 < headerMatches.length) {
            iNextTokenHeader = headerMatches[iMatch + 1].iToken;
        } else {
            iNextTokenHeader = tokens.length;
        }

        iLastVal = iNextTokenHeader - 1;

        var headerName = receivedHeaderNames[iHeader];
        if (this[headerName] !== "") { this[headerName] += "; "; }
        this[headerName] = tokens.slice(iFirstVal, iLastVal + 1).join(" ").trim();
    }

    if (this.date) {
        var milliseconds = this.date.match(/\d{1,2}:\d{2}:\d{2}.(\d+)/);
        var trimDate = this.date.replace(/(\d{1,2}:\d{2}:\d{2}).(\d+)/, "$1");
        this.dateNum = Date.parse(trimDate);
        if (milliseconds && milliseconds.length >= 2) {
            this.dateNum = this.dateNum + Math.floor(parseFloat("0." + milliseconds[1]) * 1000);
        }

        this.date = new Date(trimDate).toLocaleString().replace(/\u200E/g, "");
        this.dateSort = this.dateNum;
    }

    this.delaySort = -1; // Force the "no previous or current time" rows to sort before the 0 second rows
    this.percent = 0;
}

var Received = function () {
    this.receivedRows = [];
};

Received.prototype.tableName = "receivedHeaders";
Received.prototype.receivedRows = [];
Received.prototype.sortColumn = "hop";
Received.prototype.sortOrder = 1;

Received.prototype.exists = function () {
    return this.receivedRows.length > 0;
};

Received.prototype.doSort = function (col) {
    var that = this;
    if (this.sortColumn === col) {
        this.sortOrder *= -1;
    } else {
        this.sortColumn = col;
        this.sortOrder = 1;
    }

    if (this.sortColumn + "Sort" in this.receivedRows[0]) {
        col = col + "Sort";
    }

    this.receivedRows.sort(function (a, b) {
        return that.sortOrder * (a[col] < b[col] ? -1 : 1);
    });
};

function RemoveEntry(stringArray, entry) {
    var i = stringArray.indexOf(entry);
    if (i >= 0) {
        stringArray.splice(i, 1);
    }

    return stringArray;
}

Received.prototype.init = function (receivedHeader) {
    this.receivedRows.push(new ReceivedRow(receivedHeader));
};

Received.prototype.computeDeltas = function () {
    // Process received headers in reverse order
    this.receivedRows.reverse();

    // Parse rows and compute values needed for the "Delay" column
    var iStartTime = 0;
    var iEndTime = 0;
    var iLastTime = NaN;
    var iDelta = 0; // This will be the sum of our positive deltas

    this.receivedRows.forEach(function (row) {
        if (!isNaN(row.dateNum)) {
            if (!isNaN(iLastTime) && iLastTime < row.dateNum) {
                iDelta += row.dateNum - iLastTime;
            }

            iStartTime = iStartTime || row.dateNum;
            iEndTime = row.dateNum;
            iLastTime = row.dateNum;
        }
    });

    iLastTime = NaN;
    var totalTime = "";
    // Total time is still last minus first, even if negative.
    if (iEndTime !== iStartTime) {
        totalTime = computeTime(iEndTime, iStartTime);
    }

    this.receivedRows.forEach(function (row, index) {
        row.hop = index + 1;
        row.delay = computeTime(row.dateNum, iLastTime);

        if (!isNaN(row.dateNum) && !isNaN(iLastTime) && iDelta !== 0) {
            row.delaySort = row.dateNum - iLastTime;

            // Only positive delays will get percentage bars. Negative delays will be color coded at render time.
            if (row.delaySort > 0) {
                row.percent = 100 * row.delaySort / iDelta;
            }
        }

        if (!isNaN(row.dateNum)) {
            iLastTime = row.dateNum;
        }
    });

    return totalTime;
};

// Computes min/sec from the diff of current and last.
// Returns nothing if last or current is NaN.
function computeTime(current, last) {
    var time = [];

    if (isNaN(current) || isNaN(last)) { return ""; }
    var diff = current - last;
    var iDelay;
    var printedMinutes = false;

    if (Math.abs(diff) < 1000) {
        return "0 " + ImportedStrings.mha_seconds;
    }

    if (diff < 0) {
        time.push(ImportedStrings.mha_negative);
        diff = -diff;
    }

    if (diff >= 1000 * 60) {
        iDelay = Math.floor(diff / 1000 / 60);
        time.push(iDelay, " ");
        if (iDelay === 1) {
            time.push(ImportedStrings.mha_minute);
        } else {
            time.push(ImportedStrings.mha_minutes);
        }

        diff -= iDelay * 1000 * 60;
        printedMinutes = true;
    }

    if (printedMinutes && diff) {
        time.push(" ");
    }

    if (!printedMinutes || diff) {
        iDelay = Math.floor(diff / 1000);
        time.push(iDelay, " ");
        if (iDelay === 1) {
            time.push(ImportedStrings.mha_second);
        } else {
            time.push(ImportedStrings.mha_seconds);
        }
    }

    return time.join("");
}<commit_msg>Simplify total time computation<commit_after>/* global ImportedStrings */

/// <reference path="Table.js" />
/// <reference path="Strings.js" />
/// <reference path="Headers.js" />

// Builds array of values for each header in receivedHeaderNames.
// This algorithm should work regardless of the order of the headers, given:
//  - The date, if present, is always at the end, separated by a ";".
// Values not attached to a header will not be reflected in output.
var ReceivedRow = function (receivedHeader) {
    var receivedHeaderNames = ["from", "by", "with", "id", "for", "via"];

    // Build array of header locations
    var headerMatches = [];

    this.sourceHeader = receivedHeader;

    // Some bad dates don't wrap UTC in paren - fix that first
    receivedHeader = receivedHeader.replace(/UTC|\(UTC\)/g, "(UTC)");

    // Read out the date first, then clear it from the string
    var iDate = receivedHeader.lastIndexOf(";");

    // No semicolon means no date - or maybe there's one there?
    // Sendgrid is bad about this
    if (iDate === -1) {
        // First try to find a day of the week
        receivedHeader = receivedHeader.replace(/\s*(Mon|Tue|Wed|Thu|Fri|Sat|Sun)/g, "; $1");
        iDate = receivedHeader.lastIndexOf(";")
    }

    if (iDate === -1) {
        // Next we look for year-month-day
        // Swap to month-day-year because IE can't parse it otherwise
        receivedHeader = receivedHeader.replace(/\s*(\d{4})-(\d{1,2})-(\d{1,2})/g, "; $2-$3-$1");
        iDate = receivedHeader.lastIndexOf(";")
    }

    if (iDate !== -1) {
        this.date = receivedHeader.substring(iDate + 1);
        receivedHeader = receivedHeader.substring(0, iDate);
    }

    // Scan for malformed postFix headers
    // Received: by example.com (Postfix, from userid 1001)
    //   id 1234ABCD; Thu, 21 Aug 2014 12:12:48 +0200 (CEST)
    var postFix = receivedHeader.match(/(.*)by (.*? \(Postfix, from userid .*?\))(.*)/);
    if (postFix) {
        this["by"] = postFix[2];
        receivedHeader = postFix[1] + postFix[3];
        receivedHeaderNames = RemoveEntry(receivedHeaderNames, "by");
    }

    // Scan for malformed qmail headers
    // Received: (qmail 10876 invoked from network); 24 Aug 2014 16:13:38 -0000
    postFix = receivedHeader.match(/(.*)\((qmail .*? invoked from .*?)\)(.*)/);
    if (postFix) {
        this["by"] = postFix[2];
        receivedHeader = postFix[1] + postFix[3];
        receivedHeaderNames = RemoveEntry(receivedHeaderNames, "by");
    }

    // Split up the string now so we can look for our headers
    var tokens = receivedHeader.split(/\s+/);

    var iMatch = 0;
    var iHeader;
    var iToken;

    for (iHeader = 0; iHeader < receivedHeaderNames.length; iHeader++) {
        this[receivedHeaderNames[iHeader]] = "";
        for (iToken = 0; iToken < tokens.length; iToken++) {
            if (receivedHeaderNames[iHeader] === tokens[iToken]) {
                headerMatches[iMatch++] = { iHeader: iHeader, iToken: iToken };
            }
        }
    }

    // Next bit assumes headerMatches[x,y] is increasing on y.
    // Sort it so it is.
    headerMatches.sort(function (a, b) { return a.iToken - b.iToken; });

    for (iMatch = 0; iMatch < headerMatches.length; iMatch++) {
        iHeader = headerMatches[iMatch].iHeader;
        var iTokenHeader = headerMatches[iMatch].iToken;
        var iFirstVal = iTokenHeader + 1;

        var iNextTokenHeader;
        var iLastVal;
        if (iMatch + 1 < headerMatches.length) {
            iNextTokenHeader = headerMatches[iMatch + 1].iToken;
        } else {
            iNextTokenHeader = tokens.length;
        }

        iLastVal = iNextTokenHeader - 1;

        var headerName = receivedHeaderNames[iHeader];
        if (this[headerName] !== "") { this[headerName] += "; "; }
        this[headerName] = tokens.slice(iFirstVal, iLastVal + 1).join(" ").trim();
    }

    if (this.date) {
        var milliseconds = this.date.match(/\d{1,2}:\d{2}:\d{2}.(\d+)/);
        var trimDate = this.date.replace(/(\d{1,2}:\d{2}:\d{2}).(\d+)/, "$1");
        this.dateNum = Date.parse(trimDate);
        if (milliseconds && milliseconds.length >= 2) {
            this.dateNum = this.dateNum + Math.floor(parseFloat("0." + milliseconds[1]) * 1000);
        }

        this.date = new Date(trimDate).toLocaleString().replace(/\u200E/g, "");
        this.dateSort = this.dateNum;
    }

    this.delaySort = -1; // Force the "no previous or current time" rows to sort before the 0 second rows
    this.percent = 0;
}

var Received = function () {
    this.receivedRows = [];
};

Received.prototype.tableName = "receivedHeaders";
Received.prototype.receivedRows = [];
Received.prototype.sortColumn = "hop";
Received.prototype.sortOrder = 1;

Received.prototype.exists = function () {
    return this.receivedRows.length > 0;
};

Received.prototype.doSort = function (col) {
    var that = this;
    if (this.sortColumn === col) {
        this.sortOrder *= -1;
    } else {
        this.sortColumn = col;
        this.sortOrder = 1;
    }

    if (this.sortColumn + "Sort" in this.receivedRows[0]) {
        col = col + "Sort";
    }

    this.receivedRows.sort(function (a, b) {
        return that.sortOrder * (a[col] < b[col] ? -1 : 1);
    });
};

function RemoveEntry(stringArray, entry) {
    var i = stringArray.indexOf(entry);
    if (i >= 0) {
        stringArray.splice(i, 1);
    }

    return stringArray;
}

Received.prototype.init = function (receivedHeader) {
    this.receivedRows.push(new ReceivedRow(receivedHeader));
};

Received.prototype.computeDeltas = function () {
    // Process received headers in reverse order
    this.receivedRows.reverse();

    // Parse rows and compute values needed for the "Delay" column
    var iStartTime = 0;
    var iEndTime = 0;
    var iLastTime = NaN;
    var iDelta = 0; // This will be the sum of our positive deltas

    this.receivedRows.forEach(function (row) {
        if (!isNaN(row.dateNum)) {
            if (!isNaN(iLastTime) && iLastTime < row.dateNum) {
                iDelta += row.dateNum - iLastTime;
            }

            iStartTime = iStartTime || row.dateNum;
            iEndTime = row.dateNum;
            iLastTime = row.dateNum;
        }
    });

    iLastTime = NaN;

    this.receivedRows.forEach(function (row, index) {
        row.hop = index + 1;
        row.delay = computeTime(row.dateNum, iLastTime);

        if (!isNaN(row.dateNum) && !isNaN(iLastTime) && iDelta !== 0) {
            row.delaySort = row.dateNum - iLastTime;

            // Only positive delays will get percentage bars. Negative delays will be color coded at render time.
            if (row.delaySort > 0) {
                row.percent = 100 * row.delaySort / iDelta;
            }
        }

        if (!isNaN(row.dateNum)) {
            iLastTime = row.dateNum;
        }
    });

    // Total time is still last minus first, even if negative.
    return iEndTime !== iStartTime ? computeTime(iEndTime, iStartTime) : "";
};

// Computes min/sec from the diff of current and last.
// Returns nothing if last or current is NaN.
function computeTime(current, last) {
    var time = [];

    if (isNaN(current) || isNaN(last)) { return ""; }
    var diff = current - last;
    var iDelay;
    var printedMinutes = false;

    if (Math.abs(diff) < 1000) {
        return "0 " + ImportedStrings.mha_seconds;
    }

    if (diff < 0) {
        time.push(ImportedStrings.mha_negative);
        diff = -diff;
    }

    if (diff >= 1000 * 60) {
        iDelay = Math.floor(diff / 1000 / 60);
        time.push(iDelay, " ");
        if (iDelay === 1) {
            time.push(ImportedStrings.mha_minute);
        } else {
            time.push(ImportedStrings.mha_minutes);
        }

        diff -= iDelay * 1000 * 60;
        printedMinutes = true;
    }

    if (printedMinutes && diff) {
        time.push(" ");
    }

    if (!printedMinutes || diff) {
        iDelay = Math.floor(diff / 1000);
        time.push(iDelay, " ");
        if (iDelay === 1) {
            time.push(ImportedStrings.mha_second);
        } else {
            time.push(ImportedStrings.mha_seconds);
        }
    }

    return time.join("");
}<|endoftext|>


<commit_before>'use strict';

// @TODO: Wait to node.js achieve harmony module.
const spawn = require('child_process').spawn;

const chalk = require('chalk');

const fs = require('fs');

const co = require('co');

class PluginChildProcess {
  constructor({isStdout=false,pluginName='',hookName='',hookParams,environment=''}={}) {
    this[Symbol.for('environment')] = environment;
    this[Symbol.for('pluginName')] = pluginName;
    this[Symbol.for('hookParams')] = hookParams;
    this[Symbol.for('hookName')] = hookName;

    this.setStdout(isStdout);
    this[Symbol.for('initPluginProcess')]();
  }

  isStdout() {
    return this[Symbol.for('isStdout')];
  }

  setStdout(isStdout = false, hasError = false) {
    this[Symbol.for('isStdout')]= isStdout;

    if (this.isStdout()) {
      this[Symbol.for('loggerPlugin')](hasError);
    }
  }

  isClose() {
    return this[Symbol.for('isClose')];
  }

  getCwd() {
    return this[Symbol.for('cwd')];
  }

  getInstance() {
    return this[Symbol.for('instance')];
  }

  getProcess() {
    return this[Symbol.for('instance')].getProcess();
  }

  getHookName() {
    return this[Symbol.for('hookName')];
  }

  getHookParams() {
    return this[Symbol.for('hookParams')];
  }

  getNameSpace() {
    const environment = this.getEnvironment();

    const nameSpace = `${environment}-hooks`;

    return nameSpace;
  }

  getPluginName() {
    return this[Symbol.for('pluginName')];
  }

  getEnvironment() {
    return this[Symbol.for('environment')];
  }

  getOutputIterator() {
    let outputQueue = this[Symbol.for('outputQueue')];
    let outputIterator = this[Symbol.for('outputGenerator')](outputQueue);
    return outputIterator;
  }

  getClosePromise() {
    const closePromise = new Promise((resolve, reject) => {
      const pluginProcess = this.getProcess();
      pluginProcess.on('close', (code) => {
        if (code === 0) {
          resolve(true);
          this[Symbol.for('isClose')] = true;
        }
      });
    });
    return closePromise;
  }

  getErrorPromise() {
    const errorPromise = new Promise((resolve, reject) => {
      const pluginChildProcess = this.getProcess();

      pluginChildProcess.stderr.on('data', (buffer) => {
        let name = this.getPluginName();
        let message = buffer.toString();
        reject({ message, name });
      });

      pluginChildProcess.on('close', (code) => {
        if (code !== 0) {
          let text = `plugin child process exited with code ${code}`;
          let name = this.getPluginName();
          reject({ text,  name });
        }
      });
    });
    return errorPromise;
  }

  *[Symbol.for('outputGenerator')](outputQueue = []) {
    while (outputQueue.length) {
      yield outputQueue.shift();
    }
  }

  [Symbol.for('initProcessOutput')]() {
    const pluginChildProcess = this.getProcess();

    this[Symbol.for('outputQueue')] = [];

    pluginChildProcess.stdout.on('data', (buffer) => {
      let text = buffer.toString();

      if (this.isStdout()) {
        process.stdout.write(text);
      }
      else {
        this[Symbol.for('outputQueue')].push(text);
      }
    });
  }

  [Symbol.for('initPluginProcess')]() {
    co(function* () {
      const hookParams = yield this.getHookParams();
      const environment = this.getEnvironment();
      const pluginName = this.getPluginName();
      const nameSpace = this.getNameSpace();
      const hookName = this.getHookName();
      const cwd = this.getCwd();

      const PluginClass = require(`${nameSpace}-${pluginName}`);
      const plugin = new PluginClass({ cwd, hookName, hookParams, environment });
      this[Symbol.for('instance')] = plugin;
    }).then((value) => {
      this[Symbol.for('initProcessOutput')]();
    }, (error) => {
      const reason = `plugin don't exist, please check name or install first.`;
      const error = new PluginDontExistError({ reason });
      throw error;
    });
  }

  [Symbol.for('loggerPlugin')](hasError = false) {
    if (hasError) {
      this[Symbol.for('loggerPluginError')]();
    }
    else {
      this[Symbol.for('loggerPluginSuccess')]();
    }
  }

  [Symbol.for('loggerPluginError')]() {
    const pluginName = chalk.bold(this.getPluginName());

    const error = chalk.bold('error ....');

    const failArrow = chalk.bold.red('==>');

    console.log(`\n${failArrow} ${pluginName} ${error}\n`);
  }

  [Symbol.for('loggerPluginSuccess')]() {
    const pluginName = chalk.bold(this.getPluginName());

    const starting = chalk.bold('starting ...');

    const sucessArrow = chalk.bold.green('==>');

    console.log(`\n${sucessArrow} ${pluginName} ${starting}\n`);
  }
}

class PluginDontExistError extends Error {
  constructor({ reason = '' } = {}) {
    super(reason);
  }
}

module.exports = PluginChildProcess;
<commit_msg>bugfix<commit_after>'use strict';

// @TODO: Wait to node.js achieve harmony module.
const spawn = require('child_process').spawn;

const chalk = require('chalk');

const fs = require('fs');

const co = require('co');

class PluginChildProcess {
  constructor({isStdout=false,pluginName='',hookName='',hookParams,environment=''}={}) {
    this[Symbol.for('environment')] = environment;
    this[Symbol.for('pluginName')] = pluginName;
    this[Symbol.for('hookParams')] = hookParams;
    this[Symbol.for('hookName')] = hookName;

    this.setStdout(isStdout);
    this[Symbol.for('initPluginProcess')]();
  }

  isStdout() {
    return this[Symbol.for('isStdout')];
  }

  setStdout(isStdout = false, hasError = false) {
    this[Symbol.for('isStdout')]= isStdout;

    if (this.isStdout()) {
      this[Symbol.for('loggerPlugin')](hasError);
    }
  }

  isClose() {
    return this[Symbol.for('isClose')];
  }

  getCwd() {
    return this[Symbol.for('cwd')];
  }

  getInstance() {
    return this[Symbol.for('instance')];
  }

  getProcess() {
    return this[Symbol.for('instance')].getProcess();
  }

  getHookName() {
    return this[Symbol.for('hookName')];
  }

  getHookParams() {
    return this[Symbol.for('hookParams')];
  }

  getNameSpace() {
    const environment = this.getEnvironment();

    const nameSpace = `${environment}-hooks`;

    return nameSpace;
  }

  getPluginName() {
    return this[Symbol.for('pluginName')];
  }

  getEnvironment() {
    return this[Symbol.for('environment')];
  }

  getOutputIterator() {
    let outputQueue = this[Symbol.for('outputQueue')];
    let outputIterator = this[Symbol.for('outputGenerator')](outputQueue);
    return outputIterator;
  }

  getClosePromise() {
    const closePromise = new Promise((resolve, reject) => {
      const pluginProcess = this.getProcess();
      pluginProcess.on('close', (code) => {
        if (code === 0) {
          resolve(true);
          this[Symbol.for('isClose')] = true;
        }
      });
    });
    return closePromise;
  }

  getErrorPromise() {
    const errorPromise = new Promise((resolve, reject) => {
      const pluginChildProcess = this.getProcess();

      pluginChildProcess.stderr.on('data', (buffer) => {
        let name = this.getPluginName();
        let message = buffer.toString();
        reject({ message, name });
      });

      pluginChildProcess.on('close', (code) => {
        if (code !== 0) {
          let text = `plugin child process exited with code ${code}`;
          let name = this.getPluginName();
          reject({ text,  name });
        }
      });
    });
    return errorPromise;
  }

  *[Symbol.for('outputGenerator')](outputQueue = []) {
    while (outputQueue.length) {
      yield outputQueue.shift();
    }
  }

  [Symbol.for('initProcessOutput')]() {
    const pluginChildProcess = this.getProcess();

    this[Symbol.for('outputQueue')] = [];

    pluginChildProcess.stdout.on('data', (buffer) => {
      let text = buffer.toString();

      if (this.isStdout()) {
        process.stdout.write(text);
      }
      else {
        this[Symbol.for('outputQueue')].push(text);
      }
    });
  }

  [Symbol.for('initPluginProcess')]() {
    co(function* () {
      const hookParams = yield this.getHookParams();
      const environment = this.getEnvironment();
      const pluginName = this.getPluginName();
      const nameSpace = this.getNameSpace();
      const hookName = this.getHookName();
      const cwd = this.getCwd();

      const PluginClass = require(`${nameSpace}-${pluginName}`);
      const plugin = new PluginClass({ cwd, hookName, hookParams, environment });

      this[Symbol.for('instance')] = plugin;
    }).then((value) => {
      this[Symbol.for('initProcessOutput')]();
    }, () => {
      const reason = `plugin don't exist, please check name or install first.`;
      const error = new PluginDontExistError({ reason });
      throw error;
    });
  }

  [Symbol.for('loggerPlugin')](hasError = false) {
    if (hasError) {
      this[Symbol.for('loggerPluginError')]();
    }
    else {
      this[Symbol.for('loggerPluginSuccess')]();
    }
  }

  [Symbol.for('loggerPluginError')]() {
    const pluginName = chalk.bold(this.getPluginName());

    const error = chalk.bold('error ....');

    const failArrow = chalk.bold.red('==>');

    console.log(`\n${failArrow} ${pluginName} ${error}\n`);
  }

  [Symbol.for('loggerPluginSuccess')]() {
    const pluginName = chalk.bold(this.getPluginName());

    const starting = chalk.bold('starting ...');

    const sucessArrow = chalk.bold.green('==>');

    console.log(`\n${sucessArrow} ${pluginName} ${starting}\n`);
  }
}

class PluginDontExistError extends Error {
  constructor({ reason = '' } = {}) {
    super(reason);
  }
}

module.exports = PluginChildProcess;
<|endoftext|>


<commit_before>'use strict';

var Cesium = require('cesium');
var fs = require('fs');
var handlebars = require('handlebars');
var path = require('path');

var loadRepoConfig = require('./loadRepoConfig');

var defaultValue = Cesium.defaultValue;

var defaultInitialStalePullRequest = fs.readFileSync(path.join(__dirname, 'templates', 'initialStalePullRequest.hbs')).toString();
var defaultIssueClosed = fs.readFileSync(path.join(__dirname, 'templates', 'issueClosed.hbs')).toString();
var defaultPullRequestOpened = fs.readFileSync(path.join(__dirname, 'templates', 'pullRequestOpened.hbs')).toString();
var defaultSecondaryStalePullRequest = fs.readFileSync(path.join(__dirname, 'templates', 'secondaryStalePullRequest.hbs')).toString();
var defaultSignatureTemplate = fs.readFileSync(path.join(__dirname, 'templates', 'signature.hbs')).toString();
var defaultMaxDaysSinceUpdate = 30;

/**
 * Encapsulate available options for each repository.
 * @param {Object} [options] The repository options, see below for full details.
 * @constructor
 */
function RepositorySettings(options) {
    options = defaultValue(options, defaultValue.EMPTY_OBJECT);

    this._name = options.name;
    this._gitHubToken = options.gitHubToken;
    this._headers = {
        'User-Agent': 'cesium-concierge',
        Authorization: 'token ' + this.gitHubToken
    };

    this.thirdPartyFolders = defaultValue(options.thirdPartyFolders, []);

    this.issueClosedTemplate = defaultValue(options.issueClosedTemplate, defaultIssueClosed);
    this.pullRequestOpenedTemplate = defaultValue(options.pullRequestOpenedTemplate, defaultPullRequestOpened);
    this.initialStalePullRequestTemplate = defaultValue(options.initialStalePullRequestTemplate, defaultInitialStalePullRequest);
    this.secondaryStalePullRequestTemplate = defaultValue(options.secondaryStalePullRequestTemplate, defaultSecondaryStalePullRequest);

    /**
     * Gets the raw template for the signature at the bottom of each comment template.
     * @type {String}
     */
    this.signatureTemplate = defaultValue(options.signatureTemplate, defaultSignatureTemplate);

    /**
     * Gets the URL of the CLA to use for this repository.
     */
    this.claUrl = options.claUrl;

    /**
     * Gets the URL of the CONTRIBUTORS markdown file for this repository.
     */
    this.contributorsUrl =  `https://api.github.com/repos/${options.name}/contents/` + options.contributorsUrl;

    /**
     * Gets the amount of days before a pull request is considered stale.
     */
    this.maxDaysSinceUpdate = defaultValue(options.maxDaysSinceUpdate, defaultMaxDaysSinceUpdate);

    // Exposed for testing
    this._loadRepoConfig = loadRepoConfig;
}

Object.defineProperties(RepositorySettings.prototype, {
    /**
     * Gets the name of the repository
     * @type {String}
     */
    name: {
        get: function () {
            return this._name;
        }
    },

    /**
     * Gets the headers to use for authenticated GitHub requests.
     * @type {Object}
     */
    headers: {
        get: function () {
            return this._headers;
        }
    },

    /**
     * Gets the access token for this repository.
     * @type {String}
     */
    gitHubToken: {
        get: function () {
            return this._gitHubToken;
        }
    },

    /**
     * Gets the array of third party source files.
     * @type {String[]}
     */
    thirdPartyFolders: {
        get: function () {
            return this._thirdPartyFolders;
        },
        set: function (value) {
            var thirdPartyFolders = value;
            if (typeof value === 'string') {
                var thirdPartyFoldersArray = value.split(',');
                for (var j = 0; j < thirdPartyFoldersArray.length; j++) {
                    var folder = thirdPartyFoldersArray[j];
                    if (folder.startsWith('/')) {
                        thirdPartyFoldersArray[j] = folder.slice(1);
                    }
                    if (!folder.endsWith('/')) {
                        thirdPartyFoldersArray[j] = folder + '/';
                    }
                }
                thirdPartyFolders = thirdPartyFoldersArray;
            }
            this._thirdPartyFolders = thirdPartyFolders;
        }
    },

    /**
     * Gets the handlebars template to use when commenting on a closed issue.
     * @type {String}
     */
    issueClosedTemplate: {
        get: function () {
            return handlebars.compile(this._issueClosedTemplate + this.signatureTemplate);
        },
        set: function (value) {
            this._issueClosedTemplate = value;
        }
    },

    /**
     * Gets the handlebars template to use when commenting on a new pull request.
     * @type {String}
     */
    pullRequestOpenedTemplate: {
        get: function () {
            return handlebars.compile(this._pullRequestOpenedTemplate + this.signatureTemplate);
        },
        set: function (value) {
            this._pullRequestOpenedTemplate = value;
        }
    },

    /**
     * Gets the handlebars template to use when commenting on a stale pull request for the first time.
     * @type {String}
     */
    initialStalePullRequestTemplate: {
        get: function () {
            return handlebars.compile(this._initialStalePullRequestTemplate + this.signatureTemplate);
        },
        set: function (value) {
            this._initialStalePullRequestTemplate = value;
        }
    },

    /**
     * Gets the handlebars template to use when commenting on a stale pull request for the second time.
     * @type {String}
     */
    secondaryStalePullRequestTemplate: {
        get: function () {
            return handlebars.compile(this._secondaryStalePullRequestTemplate + this.signatureTemplate);
        },
        set: function (value) {
            this._secondaryStalePullRequestTemplate = value;
        }
    }
});

/**
 * Requests the latest version of the configuration settings from the repository.
 * @returns {Promise<Object|String>} A Promise that resolves with the new configuration, or the error message if the Promise fails.
 */
RepositorySettings.prototype.fetchSettings = function () {
    return this._loadRepoConfig(this.name, this.headers, this);
};

module.exports = RepositorySettings;<commit_msg>Linter doesn't like JS template strings<commit_after>'use strict';

var Cesium = require('cesium');
var fs = require('fs');
var handlebars = require('handlebars');
var path = require('path');

var loadRepoConfig = require('./loadRepoConfig');

var defaultValue = Cesium.defaultValue;

var defaultInitialStalePullRequest = fs.readFileSync(path.join(__dirname, 'templates', 'initialStalePullRequest.hbs')).toString();
var defaultIssueClosed = fs.readFileSync(path.join(__dirname, 'templates', 'issueClosed.hbs')).toString();
var defaultPullRequestOpened = fs.readFileSync(path.join(__dirname, 'templates', 'pullRequestOpened.hbs')).toString();
var defaultSecondaryStalePullRequest = fs.readFileSync(path.join(__dirname, 'templates', 'secondaryStalePullRequest.hbs')).toString();
var defaultSignatureTemplate = fs.readFileSync(path.join(__dirname, 'templates', 'signature.hbs')).toString();
var defaultMaxDaysSinceUpdate = 30;

/**
 * Encapsulate available options for each repository.
 * @param {Object} [options] The repository options, see below for full details.
 * @constructor
 */
function RepositorySettings(options) {
    options = defaultValue(options, defaultValue.EMPTY_OBJECT);

    this._name = options.name;
    this._gitHubToken = options.gitHubToken;
    this._headers = {
        'User-Agent': 'cesium-concierge',
        Authorization: 'token ' + this.gitHubToken
    };

    this.thirdPartyFolders = defaultValue(options.thirdPartyFolders, []);

    this.issueClosedTemplate = defaultValue(options.issueClosedTemplate, defaultIssueClosed);
    this.pullRequestOpenedTemplate = defaultValue(options.pullRequestOpenedTemplate, defaultPullRequestOpened);
    this.initialStalePullRequestTemplate = defaultValue(options.initialStalePullRequestTemplate, defaultInitialStalePullRequest);
    this.secondaryStalePullRequestTemplate = defaultValue(options.secondaryStalePullRequestTemplate, defaultSecondaryStalePullRequest);

    /**
     * Gets the raw template for the signature at the bottom of each comment template.
     * @type {String}
     */
    this.signatureTemplate = defaultValue(options.signatureTemplate, defaultSignatureTemplate);

    /**
     * Gets the URL of the CLA to use for this repository.
     */
    this.claUrl = options.claUrl;

    /**
     * Gets the URL of the CONTRIBUTORS markdown file for this repository.
     */
    this.contributorsUrl =  'https://api.github.com/repos/' + options.name + '/contents/' + options.contributorsUrl;

    /**
     * Gets the amount of days before a pull request is considered stale.
     */
    this.maxDaysSinceUpdate = defaultValue(options.maxDaysSinceUpdate, defaultMaxDaysSinceUpdate);

    // Exposed for testing
    this._loadRepoConfig = loadRepoConfig;
}

Object.defineProperties(RepositorySettings.prototype, {
    /**
     * Gets the name of the repository
     * @type {String}
     */
    name: {
        get: function () {
            return this._name;
        }
    },

    /**
     * Gets the headers to use for authenticated GitHub requests.
     * @type {Object}
     */
    headers: {
        get: function () {
            return this._headers;
        }
    },

    /**
     * Gets the access token for this repository.
     * @type {String}
     */
    gitHubToken: {
        get: function () {
            return this._gitHubToken;
        }
    },

    /**
     * Gets the array of third party source files.
     * @type {String[]}
     */
    thirdPartyFolders: {
        get: function () {
            return this._thirdPartyFolders;
        },
        set: function (value) {
            var thirdPartyFolders = value;
            if (typeof value === 'string') {
                var thirdPartyFoldersArray = value.split(',');
                for (var j = 0; j < thirdPartyFoldersArray.length; j++) {
                    var folder = thirdPartyFoldersArray[j];
                    if (folder.startsWith('/')) {
                        thirdPartyFoldersArray[j] = folder.slice(1);
                    }
                    if (!folder.endsWith('/')) {
                        thirdPartyFoldersArray[j] = folder + '/';
                    }
                }
                thirdPartyFolders = thirdPartyFoldersArray;
            }
            this._thirdPartyFolders = thirdPartyFolders;
        }
    },

    /**
     * Gets the handlebars template to use when commenting on a closed issue.
     * @type {String}
     */
    issueClosedTemplate: {
        get: function () {
            return handlebars.compile(this._issueClosedTemplate + this.signatureTemplate);
        },
        set: function (value) {
            this._issueClosedTemplate = value;
        }
    },

    /**
     * Gets the handlebars template to use when commenting on a new pull request.
     * @type {String}
     */
    pullRequestOpenedTemplate: {
        get: function () {
            return handlebars.compile(this._pullRequestOpenedTemplate + this.signatureTemplate);
        },
        set: function (value) {
            this._pullRequestOpenedTemplate = value;
        }
    },

    /**
     * Gets the handlebars template to use when commenting on a stale pull request for the first time.
     * @type {String}
     */
    initialStalePullRequestTemplate: {
        get: function () {
            return handlebars.compile(this._initialStalePullRequestTemplate + this.signatureTemplate);
        },
        set: function (value) {
            this._initialStalePullRequestTemplate = value;
        }
    },

    /**
     * Gets the handlebars template to use when commenting on a stale pull request for the second time.
     * @type {String}
     */
    secondaryStalePullRequestTemplate: {
        get: function () {
            return handlebars.compile(this._secondaryStalePullRequestTemplate + this.signatureTemplate);
        },
        set: function (value) {
            this._secondaryStalePullRequestTemplate = value;
        }
    }
});

/**
 * Requests the latest version of the configuration settings from the repository.
 * @returns {Promise<Object|String>} A Promise that resolves with the new configuration, or the error message if the Promise fails.
 */
RepositorySettings.prototype.fetchSettings = function () {
    return this._loadRepoConfig(this.name, this.headers, this);
};

module.exports = RepositorySettings;<|endoftext|>