<commit_before>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var assert = require('assert');
var fs = require('fs');
var TypedError = require('error/typed');
var WrappedError = require('error/wrapped');
var timers = require('timers');
var util = require('util');
var EventEmitter = require('events').EventEmitter;
var safeJsonParse = require('safe-json-parse/tuple');

var Reporter = require('./tcollector/reporter.js');
var TChannelJSON = require('./as/json.js');

var AdvertisementTimeoutError = WrappedError({
    type: 'hyperbahn-client.advertisement-timeout',
    message: 'Hyperbahn advertisement timed out after {time} ms!.\n' +
        '{origMessage}.\n'
});

var AlreadyDestroyed = TypedError({
    type: 'hyperbahn-client.already-destroyed',
    message: 'HyperbahnClient was already destroyed.\n' +
        'Cannot invoke {method}.',
    method: null
});

var States = {
    UNADVERTISED: 'UNADVERTISED',
    ADVERTISED: 'ADVERTISED'
};
var DEFAULT_TTL = 60 * 1000;
var DEFAULT_ERROR_RETRY_TIMES = [
    200, // One fast retry

    1000, // Fibonacci backoff
    1000,
    2000,
    3000,
    5000,
    8000,

    10000 // Max out at 10 seconds
];
var DEFAULT_TIMEOUT = 500;

module.exports = HyperbahnClient;

// # HyperbahnClient
// options.
//   * (required) tchannel: tchannel instance
//   * (required) serviceName: string service name
//   * (required) hostPortList: array of initial hyperbahn nodes
//   * callerName: the caller name
//   * logger: logtron instance
//   * reportTracing: Whether to report tracing
//   * hardFail: boolean; default false; whether or not to fail hard when we
//     can't advertise or on unexpected hyperbahn errors
//   * registrationTimeout: deprecated
//   * advertisementTimeout: integer. In hardFail mode we default to 5000. If
//     not in hardFail mode we don't time out advertisements.
function HyperbahnClient(options) {
    /*eslint max-statements: [2, 25] complexity: [2, 20] */
    if (!(this instanceof HyperbahnClient)) {
        return new HyperbahnClient(options);
    }

    var self = this;

    EventEmitter.call(this);

    assert(options && options.tchannel && !options.tchannel.topChannel,
        'Must pass in top level tchannel');
    assert(options.tchannel.tracer, 'Top channel must have trace enabled');
    assert(options.serviceName, 'must pass in serviceName');
    if (Array.isArray(options.hostPortList)) {
        self.hostPortList = options.hostPortList;
    } else if (typeof options.hostPortFile === 'string') {
        var tuple = safeSyncRead(options.hostPortFile);
        assert(!tuple[0], 'Read host port list failed with ' + tuple[0]);
        tuple = safeJsonParse(tuple[1]);
        assert(!tuple[0], 'Parse host port list failed with ' + tuple[0]);
        assert(Array.isArray(tuple[1]), 'Host port list in the file is not array');
        self.hostPortList = tuple[1];
    } else {
        assert(false, 'Must pass in hostPortList as array or hostPortFile as string');
    }

    self.serviceName = options.serviceName;
    self.callerName = options.callerName || options.serviceName;
    self.tchannel = options.tchannel;
    self.reportTracing = 'reportTracing' in options ?
        options.reportTracing : true;
    self.hardFail = !!options.hardFail;
    self.advertiseInterval = options.advertiseInterval || DEFAULT_TTL;
    self.timeoutFuzz = self.advertiseInterval;
    self.errorRetryTimes = options.errorRetryTimes || DEFAULT_ERROR_RETRY_TIMES;

    self.logger = options.logger || self.tchannel.logger;
    self.statsd = options.statsd;

    self.reporter = Reporter({
        channel: self.getClientChannel({
            serviceName: 'tcollector',
            trace: false
        }),
        logger: self.logger,
        callerName: self.callerName,
        logWarnings: options.logTraceWarnings
    });
    self.tchannel.tracer.reporter = function report(span) {
        if (self.reportTracing) {
            self.reporter.report(span);
        }
    };

    if (!self.reportTracing) {
        self.logger.warn('HyperbahnClient tcollector tracing is OFF', {
            service: self.serviceName
        });
    }

    self.hyperbahnChannel = self.tchannel.subChannels.hyperbahn ||
        self.tchannel.makeSubChannel({
            serviceName: 'hyperbahn',
            peers: self.hostPortList,
            preferConnectionDirection: 'in'
        });
    self.tchannelJSON = TChannelJSON();

    self.lastError = null;
    self.latestAdvertisementResult = null;
    self.attemptCounter = 0;
    self.state = States.UNADVERTISED;
    self._destroyed = false;
    self.defaultTimeout = options.defaultTimeout || DEFAULT_TIMEOUT;

    var advertisementTimeout = options.advertisementTimeout ||
        options.registrationTimeout;
    if (self.hardFail) {
        self.advertisementTimeoutTime = advertisementTimeout || 5000;
    } else {
        self.advertisementTimeoutTime = 0;
        self.logger.info('HyperbahnClient advertisement timeout disabled', {
            service: self.serviceName
        });
    }
}

util.inherits(HyperbahnClient, EventEmitter);

HyperbahnClient.prototype.setReportTracing = function setReportTracing(bool) {
    var self = this;

    self.reportTracing = bool;
};

// Gets the subchannel for hitting a particular service.
HyperbahnClient.prototype.getClientChannel =
function getClientChannel(options) {
    var self = this;

    assert(options && options.serviceName, 'must pass serviceName');

    if (self._destroyed) {
        self.emit('error', AlreadyDestroyed({
            method: 'getClientChannel'
        }));
        return null;
    }

    if (self.tchannel.subChannels[options.serviceName]) {
        return self.tchannel.subChannels[options.serviceName];
    }

    var channelOptions = {
        peers: self.hostPortList,
        serviceName: options.serviceName,
        preferConnectionDirection: 'in',
        requestDefaults: {
            serviceName: options.serviceName,
            headers: {
                cn: self.callerName
            }
        }
    };
    if ('trace' in options) {
        channelOptions.trace = options.trace;
    }
    if ('timeout' in options) {
        channelOptions.requestDefaults.timeout = options.timeout;
    }
    if ('retryLimit' in options) {
        channelOptions.requestDefaults.retryLimit = options.retryLimit;
    }

    return self.tchannel.makeSubChannel(channelOptions);
};

// ## advertisementTimeout
// Called after a certain amount of time to have a fatal error when reg fails.
// Will not be called if options.advertisementTimeout isn't passed in
HyperbahnClient.prototype.advertisementTimeout =
function advertisementTimeout() {
    var self = this;

    if (self.state === States.UNADVERTISED) {
        var lastError = self.lastError ||
            new Error('advertisement timeout!');
        var err = AdvertisementTimeoutError(lastError, {
            time: self.advertisementTimeoutTime
        });

        self.logger.fatal('HyperbahnClient: advertisement timed out', {
            timeout: self.advertisementTimeout,
            error: err
        });

        self.advertisementFailure(err);
    }
    // TODO else warn
};

HyperbahnClient.prototype.advertisementFailure =
function advertisementFailure(err) {
    var self = this;

    self.destroy();
    self.emit('error', err);

    if (self.statsd) {
        self.statsd.increment(
            'hyperbahn-client.' + self.serviceName + '.advertisement.failure'
        );
    }
};

HyperbahnClient.prototype.sendRequest =
function sendRequest(opts, endpoint, cb) {
    var self = this;

    var req = self.hyperbahnChannel.request({
        serviceName: 'hyperbahn',
        timeout: (opts && opts.timeout) || self.defaultTimeout,
        hasNoParent: true,
        trace: false,
        retryLimit: 1,
        headers: {
            cn: self.callerName
        }
    });
    self.tchannelJSON.send(req, endpoint, null, {
        services: [{
            cost: 0,
            serviceName: self.serviceName
        }]
    }, cb);
};

// ## advertise
// Advertise with Hyperbahn. If called with a callback, the callback will not be
// called until there has been a successful advertisement. This function
// attempts a advertise and retries until there are no healthy servers left; it
// will then repeatedly choose random servers to try until it finds one that
// works.
// register is **deprecated**
HyperbahnClient.prototype.register =
HyperbahnClient.prototype.advertise =
function advertise(opts) {
    var self = this;
    // Attempt a advertisement. If it succeeds, setTimeout to re-advertise with
    // the same server after the TTL.

    assert(self.tchannel.hostPort,
        'must call tchannel.listen() before advertise()');

    if (self._destroyed) {
        self.emit('error', AlreadyDestroyed({
            method: 'advertise'
        }));
        return;
    }

    if (self.advertisementTimeoutTime) {
        // Start the timeout timer
        self.advertisementTimeoutTimer = timers.setTimeout(
            function advertisementTimeoutTimer() {
                self.advertisementTimeout();
            },
            self.advertisementTimeoutTime
        );
    }

    self.attemptCounter++;
    self.sendRequest(opts, 'ad', advertiseInternalCb);
    self.emit('advertise-attempt');

    function advertiseInternalCb(err, result) {
        /*eslint max-statements: [2, 40] */
        if (err) {
            self.logger[self.hardFail ? 'error' : 'warn'](
                'HyperbahnClient: advertisement failure, ' +
                'marking server as sick', {
                error: err,
                serviceName: self.serviceName,
                hostPort: self.hostPort
            });
            self.lastError = err;

            self.advertiseAgain(self.getErrorRetryTime());
            return;
        }

        if (result.ok === false) {
            err = result.body;
            var errInfo2 = {
                error: err,
                serviceName: self.serviceName,
                hostPort: self.hostPort
            };

            self.logger[self.hardFail ? 'fatal' : 'warn'](
                'HyperbahnClient: unexpected failure (from Hyperbahn)',
                errInfo2
            );

            if (self.hardFail) {
                self.advertisementFailure(err);
            } else {
                self.advertiseAgain(self.getErrorRetryTime());
            }

            return;
        }

        if (self.statsd) {
            self.statsd.increment(
                'hyperbahn-client.' + self.serviceName +
                    '.advertisement.success'
            );
        }

        self.latestAdvertisementResult = result;
        self.attemptCounter = 0;
        self.state = States.ADVERTISED;
        timers.clearTimeout(self.advertisementTimeoutTimer);

        self.advertiseAgain(self.getHealthyRetryTime());

        // registered event is deprecated
        self.emit('registered');
        self.emit('advertised');
    }
};

HyperbahnClient.prototype.unadvertise =
function unadvertise(opts) {
    var self = this;
    self.sendRequest(opts, 'unad', unadvertiseInternalCb);
    timers.clearTimeout(self._advertisementTimer);
    timers.clearTimeout(self.advertisementTimeoutTimer);
    self.latestAdvertisementResult = null;
    self.state = States.UNADVERTISED;
    self.emit('unadvertised');
    function unadvertiseInternalCb(error, result) {
        if (error) {
            self.logger.warn('HyperbahnClient: unadvertisement failure', {
                error: error,
                serviceName: self.serviceName,
                hostPort: self.hostPort
            });
            return;
        }
    }
};

HyperbahnClient.prototype.getErrorRetryTime = function getErrorRetryTime() {
    var self = this;

    return self.errorRetryTimes[self.attemptCounter - 1] ||
        self.errorRetryTimes[self.errorRetryTimes.length - 1];
};

HyperbahnClient.prototype.getHealthyRetryTime = function getHealthyRetryTime() {
    var self = this;

    var fuzz = self.timeoutFuzz;
    var delay = Math.round(Math.floor(Math.random() * fuzz)) - (fuzz / 2);

    return self.advertiseInterval + delay;
};

HyperbahnClient.prototype.advertiseAgain =
function advertiseAgain(delay) {
    var self = this;

    if (self._destroyed) {
        return;
    }

    self._advertisementTimer = timers.setTimeout(
        function advertiseTimeout() {
            self.advertise();
        },
        delay
    );
};

// ## destroy
HyperbahnClient.prototype.destroy = function destroy() {
    var self = this;
    if (self._destroyed) {
        return;
    }

    self._destroyed = true;
    timers.clearTimeout(self._advertisementTimer);
    timers.clearTimeout(self.advertisementTimeoutTimer);
};

function safeSyncRead(filePath) {
    var fileContents = null;
    var error;

    try {
        fileContents = fs.readFileSync(filePath, 'utf8');
    } catch (err) {
        error = err;
    }

    return [error, fileContents];
}
<commit_msg>Add state change and event emit to callback<commit_after>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var assert = require('assert');
var fs = require('fs');
var TypedError = require('error/typed');
var WrappedError = require('error/wrapped');
var timers = require('timers');
var util = require('util');
var EventEmitter = require('events').EventEmitter;
var safeJsonParse = require('safe-json-parse/tuple');

var Reporter = require('./tcollector/reporter.js');
var TChannelJSON = require('./as/json.js');

var AdvertisementTimeoutError = WrappedError({
    type: 'hyperbahn-client.advertisement-timeout',
    message: 'Hyperbahn advertisement timed out after {time} ms!.\n' +
        '{origMessage}.\n'
});

var AlreadyDestroyed = TypedError({
    type: 'hyperbahn-client.already-destroyed',
    message: 'HyperbahnClient was already destroyed.\n' +
        'Cannot invoke {method}.',
    method: null
});

var States = {
    UNADVERTISED: 'UNADVERTISED',
    ADVERTISED: 'ADVERTISED'
};
var DEFAULT_TTL = 60 * 1000;
var DEFAULT_ERROR_RETRY_TIMES = [
    200, // One fast retry

    1000, // Fibonacci backoff
    1000,
    2000,
    3000,
    5000,
    8000,

    10000 // Max out at 10 seconds
];
var DEFAULT_TIMEOUT = 500;

module.exports = HyperbahnClient;

// # HyperbahnClient
// options.
//   * (required) tchannel: tchannel instance
//   * (required) serviceName: string service name
//   * (required) hostPortList: array of initial hyperbahn nodes
//   * callerName: the caller name
//   * logger: logtron instance
//   * reportTracing: Whether to report tracing
//   * hardFail: boolean; default false; whether or not to fail hard when we
//     can't advertise or on unexpected hyperbahn errors
//   * registrationTimeout: deprecated
//   * advertisementTimeout: integer. In hardFail mode we default to 5000. If
//     not in hardFail mode we don't time out advertisements.
function HyperbahnClient(options) {
    /*eslint max-statements: [2, 25] complexity: [2, 20] */
    if (!(this instanceof HyperbahnClient)) {
        return new HyperbahnClient(options);
    }

    var self = this;

    EventEmitter.call(this);

    assert(options && options.tchannel && !options.tchannel.topChannel,
        'Must pass in top level tchannel');
    assert(options.tchannel.tracer, 'Top channel must have trace enabled');
    assert(options.serviceName, 'must pass in serviceName');
    if (Array.isArray(options.hostPortList)) {
        self.hostPortList = options.hostPortList;
    } else if (typeof options.hostPortFile === 'string') {
        var tuple = safeSyncRead(options.hostPortFile);
        assert(!tuple[0], 'Read host port list failed with ' + tuple[0]);
        tuple = safeJsonParse(tuple[1]);
        assert(!tuple[0], 'Parse host port list failed with ' + tuple[0]);
        assert(Array.isArray(tuple[1]), 'Host port list in the file is not array');
        self.hostPortList = tuple[1];
    } else {
        assert(false, 'Must pass in hostPortList as array or hostPortFile as string');
    }

    self.serviceName = options.serviceName;
    self.callerName = options.callerName || options.serviceName;
    self.tchannel = options.tchannel;
    self.reportTracing = 'reportTracing' in options ?
        options.reportTracing : true;
    self.hardFail = !!options.hardFail;
    self.advertiseInterval = options.advertiseInterval || DEFAULT_TTL;
    self.timeoutFuzz = self.advertiseInterval;
    self.errorRetryTimes = options.errorRetryTimes || DEFAULT_ERROR_RETRY_TIMES;

    self.logger = options.logger || self.tchannel.logger;
    self.statsd = options.statsd;

    self.reporter = Reporter({
        channel: self.getClientChannel({
            serviceName: 'tcollector',
            trace: false
        }),
        logger: self.logger,
        callerName: self.callerName,
        logWarnings: options.logTraceWarnings
    });
    self.tchannel.tracer.reporter = function report(span) {
        if (self.reportTracing) {
            self.reporter.report(span);
        }
    };

    if (!self.reportTracing) {
        self.logger.warn('HyperbahnClient tcollector tracing is OFF', {
            service: self.serviceName
        });
    }

    self.hyperbahnChannel = self.tchannel.subChannels.hyperbahn ||
        self.tchannel.makeSubChannel({
            serviceName: 'hyperbahn',
            peers: self.hostPortList,
            preferConnectionDirection: 'in'
        });
    self.tchannelJSON = TChannelJSON();

    self.lastError = null;
    self.latestAdvertisementResult = null;
    self.attemptCounter = 0;
    self.state = States.UNADVERTISED;
    self._destroyed = false;
    self.defaultTimeout = options.defaultTimeout || DEFAULT_TIMEOUT;

    var advertisementTimeout = options.advertisementTimeout ||
        options.registrationTimeout;
    if (self.hardFail) {
        self.advertisementTimeoutTime = advertisementTimeout || 5000;
    } else {
        self.advertisementTimeoutTime = 0;
        self.logger.info('HyperbahnClient advertisement timeout disabled', {
            service: self.serviceName
        });
    }
}

util.inherits(HyperbahnClient, EventEmitter);

HyperbahnClient.prototype.setReportTracing = function setReportTracing(bool) {
    var self = this;

    self.reportTracing = bool;
};

// Gets the subchannel for hitting a particular service.
HyperbahnClient.prototype.getClientChannel =
function getClientChannel(options) {
    var self = this;

    assert(options && options.serviceName, 'must pass serviceName');

    if (self._destroyed) {
        self.emit('error', AlreadyDestroyed({
            method: 'getClientChannel'
        }));
        return null;
    }

    if (self.tchannel.subChannels[options.serviceName]) {
        return self.tchannel.subChannels[options.serviceName];
    }

    var channelOptions = {
        peers: self.hostPortList,
        serviceName: options.serviceName,
        preferConnectionDirection: 'in',
        requestDefaults: {
            serviceName: options.serviceName,
            headers: {
                cn: self.callerName
            }
        }
    };
    if ('trace' in options) {
        channelOptions.trace = options.trace;
    }
    if ('timeout' in options) {
        channelOptions.requestDefaults.timeout = options.timeout;
    }
    if ('retryLimit' in options) {
        channelOptions.requestDefaults.retryLimit = options.retryLimit;
    }

    return self.tchannel.makeSubChannel(channelOptions);
};

// ## advertisementTimeout
// Called after a certain amount of time to have a fatal error when reg fails.
// Will not be called if options.advertisementTimeout isn't passed in
HyperbahnClient.prototype.advertisementTimeout =
function advertisementTimeout() {
    var self = this;

    if (self.state === States.UNADVERTISED) {
        var lastError = self.lastError ||
            new Error('advertisement timeout!');
        var err = AdvertisementTimeoutError(lastError, {
            time: self.advertisementTimeoutTime
        });

        self.logger.fatal('HyperbahnClient: advertisement timed out', {
            timeout: self.advertisementTimeout,
            error: err
        });

        self.advertisementFailure(err);
    }
    // TODO else warn
};

HyperbahnClient.prototype.advertisementFailure =
function advertisementFailure(err) {
    var self = this;

    self.destroy();
    self.emit('error', err);

    if (self.statsd) {
        self.statsd.increment(
            'hyperbahn-client.' + self.serviceName + '.advertisement.failure'
        );
    }
};

HyperbahnClient.prototype.sendRequest =
function sendRequest(opts, endpoint, cb) {
    var self = this;

    var req = self.hyperbahnChannel.request({
        serviceName: 'hyperbahn',
        timeout: (opts && opts.timeout) || self.defaultTimeout,
        hasNoParent: true,
        trace: false,
        retryLimit: 1,
        headers: {
            cn: self.callerName
        }
    });
    self.tchannelJSON.send(req, endpoint, null, {
        services: [{
            cost: 0,
            serviceName: self.serviceName
        }]
    }, cb);
};

// ## advertise
// Advertise with Hyperbahn. If called with a callback, the callback will not be
// called until there has been a successful advertisement. This function
// attempts a advertise and retries until there are no healthy servers left; it
// will then repeatedly choose random servers to try until it finds one that
// works.
// register is **deprecated**
HyperbahnClient.prototype.register =
HyperbahnClient.prototype.advertise =
function advertise(opts) {
    var self = this;
    // Attempt a advertisement. If it succeeds, setTimeout to re-advertise with
    // the same server after the TTL.

    assert(self.tchannel.hostPort,
        'must call tchannel.listen() before advertise()');

    if (self._destroyed) {
        self.emit('error', AlreadyDestroyed({
            method: 'advertise'
        }));
        return;
    }

    if (self.advertisementTimeoutTime) {
        // Start the timeout timer
        self.advertisementTimeoutTimer = timers.setTimeout(
            function advertisementTimeoutTimer() {
                self.advertisementTimeout();
            },
            self.advertisementTimeoutTime
        );
    }

    self.attemptCounter++;
    self.sendRequest(opts, 'ad', advertiseInternalCb);
    self.emit('advertise-attempt');

    function advertiseInternalCb(err, result) {
        /*eslint max-statements: [2, 40] */
        if (err) {
            self.logger[self.hardFail ? 'error' : 'warn'](
                'HyperbahnClient: advertisement failure, ' +
                'marking server as sick', {
                error: err,
                serviceName: self.serviceName,
                hostPort: self.hostPort
            });
            self.lastError = err;

            self.advertiseAgain(self.getErrorRetryTime());
            return;
        }

        if (result.ok === false) {
            err = result.body;
            var errInfo2 = {
                error: err,
                serviceName: self.serviceName,
                hostPort: self.hostPort
            };

            self.logger[self.hardFail ? 'fatal' : 'warn'](
                'HyperbahnClient: unexpected failure (from Hyperbahn)',
                errInfo2
            );

            if (self.hardFail) {
                self.advertisementFailure(err);
            } else {
                self.advertiseAgain(self.getErrorRetryTime());
            }

            return;
        }

        if (self.statsd) {
            self.statsd.increment(
                'hyperbahn-client.' + self.serviceName +
                    '.advertisement.success'
            );
        }

        self.latestAdvertisementResult = result;
        self.attemptCounter = 0;
        self.state = States.ADVERTISED;
        timers.clearTimeout(self.advertisementTimeoutTimer);

        self.advertiseAgain(self.getHealthyRetryTime());

        // registered event is deprecated
        self.emit('registered');
        self.emit('advertised');
    }
};

HyperbahnClient.prototype.unadvertise =
function unadvertise(opts) {
    var self = this;
    self.sendRequest(opts, 'unad', unadvertiseInternalCb);
    timers.clearTimeout(self._advertisementTimer);
    timers.clearTimeout(self.advertisementTimeoutTimer);
    self.latestAdvertisementResult = null;
    function unadvertiseInternalCb(error, result) {
        if (error) {
            self.logger.warn('HyperbahnClient: unadvertisement failure', {
                error: error,
                serviceName: self.serviceName,
                hostPort: self.hostPort
            });
            return;
        }
        self.state = States.UNADVERTISED;
        self.emit('unadvertised');
    }
};

HyperbahnClient.prototype.getErrorRetryTime = function getErrorRetryTime() {
    var self = this;

    return self.errorRetryTimes[self.attemptCounter - 1] ||
        self.errorRetryTimes[self.errorRetryTimes.length - 1];
};

HyperbahnClient.prototype.getHealthyRetryTime = function getHealthyRetryTime() {
    var self = this;

    var fuzz = self.timeoutFuzz;
    var delay = Math.round(Math.floor(Math.random() * fuzz)) - (fuzz / 2);

    return self.advertiseInterval + delay;
};

HyperbahnClient.prototype.advertiseAgain =
function advertiseAgain(delay) {
    var self = this;

    if (self._destroyed) {
        return;
    }

    self._advertisementTimer = timers.setTimeout(
        function advertiseTimeout() {
            self.advertise();
        },
        delay
    );
};

// ## destroy
HyperbahnClient.prototype.destroy = function destroy() {
    var self = this;
    if (self._destroyed) {
        return;
    }

    self._destroyed = true;
    timers.clearTimeout(self._advertisementTimer);
    timers.clearTimeout(self.advertisementTimeoutTimer);
};

function safeSyncRead(filePath) {
    var fileContents = null;
    var error;

    try {
        fileContents = fs.readFileSync(filePath, 'utf8');
    } catch (err) {
        error = err;
    }

    return [error, fileContents];
}
<|endoftext|>


<commit_before>/**
 * Retrieve stock market figures from google finance.
 *
 * @module stockGet
 *
 * @param  {String} html
 * @return {String}

	name
	tickerSymbol
	exchange
	exchangeTimezone
	price
	priceChange
	priceChangePercent
	quoteTime
*/

var cheerio = require('cheerio');
var https = require('https');

var debugMode=0;
var baseurl="https://www.google.co.uk/finance?q=";

//var query = "NASDAQ%3AAAPL";

//debug logging to console
function dlog(data){
	if(debugMode==1)
	{
		console.log(data);
	}
	return;
}

var Fin = function() {};

//***********************************************************************
Fin.prototype.url = function (query,callback) {
	dlog('url to query is '+ baseurl + query);
	callback(baseurl+query);
	return;
};
//***********************************************************************

//***********************************************************************
Fin.prototype.query = function(symbol,attri,callback) {

	dlog("\n-----\nsymbol:"+symbol+"\nattri:"+attri+"\n-----\n");

	var req = https.request((baseurl+symbol), function(res){

		var body = "";

		dlog(res.sockets);
	//***********************************************
		res.setTimeout(4500, function(e){
			console.log("timed out");
			callback("error");
			return;
		});
	//***********************************************
		res.on('data', function (htmldata) {
			body += htmldata;
			dlog("data..");

		});
	//***********************************************
		res.on('error', function() {
			console.log("sdfasfaf");
			req.abort();
			req.destroy();

			req.end();
			callback(new Error(e),null);

		});
	//***********************************************
//		res.on("socket", function (socket) {
//		  socket.emit("agentRemove");
//		  socket.emit("end");

//		});
	//***********************************************
	  	res.on('end', function() {
			//need statusCode of 200 otherwise we have an issue
			 //clearTimeout( timeout );
			if(res.statusCode !=200)
			{
				dlog("error statusCode: " + res.statusCode);
				callback(new Error("error statusCode: " + res.statusCode + ""), null);
				return;
			}

	    	dlog('No more data in response.');

	  		//manipulate DOM
			manDom(body);
		});
	});

function manDom(htmlbody)
{
	var $ = cheerio.load(htmlbody);
	var stockSymbol= {};
	if(attri=="all"){
		var k="";
		$(htmlbody).find('meta[itemprop]').each (function(){
		 	attrib = ($(this).attr('itemprop'));
		 	valuu = ($(this).attr('content'));
		 	stockSymbol[attrib] = valuu;
		 });
		callback(null,stockSymbol);
		return;
	}
	else
	{

		if($("meta[itemprop="+attri+"]").attr("content") === undefined)
		{
			console.log("Error: missing parameters, attribute is " +attri+".");
			dlog("error ´"+attri+"´ not found - callbacking with error");
			if(callback){
				callback(new Error("´"+attri + "´ not found."),null);
			}
			else
			{
				return;
			}

		}
		else
		{
			var stockSymbol= {};
			stockSymbol[attri] = $("meta[itemprop="+attri+"]").attr("content");

			if(stockSymbol[attri] == ""){
				dlog("error - "+attri+" doesn´t seem to exist!");
				callback(new Error(attri + " doesn´t exist"),null);
				return;
			}
			else
			{
				//success - pass back data to callback.
				callback(null,stockSymbol);
				return;
			}
		}
	}
} //end function
//*****************************************************

	req.on('error', function(e) {
	  //console.error(e);
	  req.abort();
	  req.destroy();

	  req.end();
	  callback(new Error(e),null);

	});

	req.on('timeout', function () {
	  // Timeout happend. Server received request, but not handled it
	  // (i.e. doesn't send any response or it took to long).
	  // You don't know what happend.
	  // It will emit 'error' message as well (with ECONNRESET code).

	  console.log('timeout');
	  req.abort();
	  req.destroy();
	  req.end();
	  //req.finish();
	});

	req.on("socket", function (socket) {
	  dlog("socket start");
	//  socket.emit("agentRemove");
	 // socket.emit("close");

	});

	req.end();

} //close function

module.exports = new Fin();
<commit_msg>updated comments<commit_after>/**
 * Retrieve stock market figures from google finance.
 *
 * @module stockGet
 *
 * @param  {String} stocksymbol
 * @return {object}

	name
	tickerSymbol
	exchange
	exchangeTimezone
	price
	priceChange
	priceChangePercent
	quoteTime
*/

var cheerio = require('cheerio');
var https = require('https');

var debugMode=0;
var baseurl="https://www.google.co.uk/finance?q=";

//var query = "NASDAQ%3AAAPL";

//debug logging to console
function dlog(data){
	if(debugMode==1)
	{
		console.log(data);
	}
	return;
}

var Fin = function() {};

//***********************************************************************
Fin.prototype.url = function (query,callback) {
	dlog('url to query is '+ baseurl + query);
	callback(baseurl+query);
	return;
};
//***********************************************************************

//***********************************************************************
Fin.prototype.query = function(symbol,attri,callback) {

	dlog("\n-----\nsymbol:"+symbol+"\nattri:"+attri+"\n-----\n");

	var req = https.request((baseurl+symbol), function(res){

		var body = "";

		dlog(res.sockets);
	//***********************************************
		res.setTimeout(4500, function(e){
			console.log("timed out");
			callback("error");
			return;
		});
	//***********************************************
		res.on('data', function (htmldata) {
			body += htmldata;
			dlog("data..");

		});
	//***********************************************
		res.on('error', function() {
			console.log("sdfasfaf");
			req.abort();
			req.destroy();

			req.end();
			callback(new Error(e),null);

		});
	//***********************************************
//		res.on("socket", function (socket) {
//		  socket.emit("agentRemove");
//		  socket.emit("end");

//		});
	//***********************************************
	  	res.on('end', function() {
			//need statusCode of 200 otherwise we have an issue
			 //clearTimeout( timeout );
			if(res.statusCode !=200)
			{
				dlog("error statusCode: " + res.statusCode);
				callback(new Error("error statusCode: " + res.statusCode + ""), null);
				return;
			}

	    	dlog('No more data in response.');

	  		//manipulate DOM
			manDom(body);
		});
	});

function manDom(htmlbody)
{
	var $ = cheerio.load(htmlbody);
	var stockSymbol= {};
	if(attri=="all"){
		var k="";
		$(htmlbody).find('meta[itemprop]').each (function(){
		 	attrib = ($(this).attr('itemprop'));
		 	valuu = ($(this).attr('content'));
		 	stockSymbol[attrib] = valuu;
		 });
		callback(null,stockSymbol);
		return;
	}
	else
	{

		if($("meta[itemprop="+attri+"]").attr("content") === undefined)
		{
			console.log("Error: missing parameters, attribute is " +attri+".");
			dlog("error ´"+attri+"´ not found - callbacking with error");
			if(callback){
				callback(new Error("´"+attri + "´ not found."),null);
			}
			else
			{
				return;
			}

		}
		else
		{
			var stockSymbol= {};
			stockSymbol[attri] = $("meta[itemprop="+attri+"]").attr("content");

			if(stockSymbol[attri] == ""){
				dlog("error - "+attri+" doesn´t seem to exist!");
				callback(new Error(attri + " doesn´t exist"),null);
				return;
			}
			else
			{
				//success - pass back data to callback.
				callback(null,stockSymbol);
				return;
			}
		}
	}
} //end function
//*****************************************************

	req.on('error', function(e) {
	  //console.error(e);
	  req.abort();
	  req.destroy();

	  req.end();
	  callback(new Error(e),null);

	});

	req.on('timeout', function () {
	  // Timeout happend. Server received request, but not handled it
	  // (i.e. doesn't send any response or it took to long).
	  // You don't know what happend.
	  // It will emit 'error' message as well (with ECONNRESET code).

	  console.log('timeout');
	  req.abort();
	  req.destroy();
	  req.end();
	  //req.finish();
	});

	req.on("socket", function (socket) {
	  dlog("socket start");
	//  socket.emit("agentRemove");
	 // socket.emit("close");

	});

	req.end();

} //close function

module.exports = new Fin();
<|endoftext|>


<commit_before>define({
    "postbox-text": "Nhập bình luận tại đây (tối thiểu 3 ký tự)",
    "postbox-author": "Tên (tùy chọn)",
    "postbox-email": "E-mail (tùy chọn)",
    "postbox-website": "Website (tùy chọn)",
    "postbox-submit": "Gửi",

    "num-comments": "Một bình luận\n{{ n }} bình luận",
    "no-comments": "Chưa có bình luận nào",

    "comment-reply": "Trả lời",
    "comment-edit": "Sửa",
    "comment-save": "Lưu",
    "comment-delete": "Xóa",
    "comment-confirm": "Xác nhận",
    "comment-close": "Đóng",
    "comment-cancel": "Hủy",
    "comment-deleted": "Đã xóa bình luận.",
    "comment-queued": "Bình luận đang chờ duyệt",
    "comment-anonymous": "Nặc danh",
    "comment-hidden": "{{ n }} đã ẩn",

    "date-now": "vừa mới",
    "date-minute": "một phút trước\n{{ n }} phút trước",
    "date-hour": "một giờ trước\n{{ n }} giờ trước",
    "date-day": "Hôm qua\n{{ n }} ngày trước",
    "date-week": "Tuần qua\n{{ n }} tuần trướ",
    "date-month": "Tháng trước\n{{ n }} tháng trước",
    "date-year": "Năm trước\n{{ n }} năm trước"
});
<commit_msg>Fix typo: s/trướ/trước<commit_after>define({
    "postbox-text": "Nhập bình luận tại đây (tối thiểu 3 ký tự)",
    "postbox-author": "Tên (tùy chọn)",
    "postbox-email": "E-mail (tùy chọn)",
    "postbox-website": "Website (tùy chọn)",
    "postbox-submit": "Gửi",

    "num-comments": "Một bình luận\n{{ n }} bình luận",
    "no-comments": "Chưa có bình luận nào",

    "comment-reply": "Trả lời",
    "comment-edit": "Sửa",
    "comment-save": "Lưu",
    "comment-delete": "Xóa",
    "comment-confirm": "Xác nhận",
    "comment-close": "Đóng",
    "comment-cancel": "Hủy",
    "comment-deleted": "Đã xóa bình luận.",
    "comment-queued": "Bình luận đang chờ duyệt",
    "comment-anonymous": "Nặc danh",
    "comment-hidden": "{{ n }} đã ẩn",

    "date-now": "vừa mới",
    "date-minute": "một phút trước\n{{ n }} phút trước",
    "date-hour": "một giờ trước\n{{ n }} giờ trước",
    "date-day": "Hôm qua\n{{ n }} ngày trước",
    "date-week": "Tuần qua\n{{ n }} tuần trước",
    "date-month": "Tháng trước\n{{ n }} tháng trước",
    "date-year": "Năm trước\n{{ n }} năm trước"
});
<|endoftext|>


<commit_before>#!/usr/bin/env node

const path = require('path')
const Metalsmith = require('metalsmith')
const debug = require('metalsmith-debug')
const collections = require('metalsmith-collections')
const layouts = require('metalsmith-layouts')
const nunjucks = require('nunjucks')
const markdown = require('metalsmith-markdown')
const permalinks = require('metalsmith-permalinks')
const writemetadata = require('metalsmith-writemetadata')
const htmlMinifier = require('metalsmith-html-minifier')
const cleanCSS = require('metalsmith-clean-css')
const contenthash = require('metalsmith-contenthash')
const markdownFilter = require('nunjucks-markdown-filter')
const sass = require('metalsmith-sass')
const marked = require('marked')
const { shuffle } = require('lodash')
const metadataDir = require('../plugins/metalsmith-metadata-dir')
const svgOptimizer = require('../plugins/metalsmith-svg-optimizer')
const nunjucksRenderer = require('../plugins/metalsmith-nunjucks-renderer')

const markdownRenderer = new marked.Renderer()
markdownRenderer.image = function (href, title, text) {
  if (href.charAt(0) === '/') {
    href = href.substring(1)
  }
  return `<img src="/{{ hashes['${href}'] }}" alt="${title}" title="${title}" />`
}

const source = path.resolve(
  process.argv[2] || path.join(__dirname, '..', 'website')
)
const dest = path.resolve(
  process.argv[3] || path.join(__dirname, '..', '..', 'build')
)

console.log(`Building website from ${source} into ${dest}`)

const env = nunjucks.configure(path.join(source, 'layouts'), {
  watch: false,
  noCache: true
})
let first = true
env.addGlobal('getContext', function () {
  if (first) {
    console.log(Object.keys(this.env.globals))
    first = false
  }
})
env.addGlobal('currentYear', new Date().getFullYear())
env.addFilter('md', markdownFilter)
env.addFilter('shuffle', arr => shuffle(arr))

Metalsmith(source)
  .source(path.join(source, 'content'))
  .destination(dest)
  .clean(true)
  .metadata(require(path.join(source, 'metadata.json')))
  .use(debug())
  .use(sass({
    outputDir: 'css/'
  }))
  .use(
    writemetadata({
      childIgnorekeys: ['next', 'previous', 'content']
    })
  )
  .use(
    metadataDir({
      directory: path.join(source, 'data')
    })
  )
  .use(
    collections({
      docs: 'docs/**/*.md'
    })
  )
  .use(
    contenthash({
      pattern: ['**/*.{js,css,png,jpg,svg}']
    })
  )
  .use(markdown({ renderer: markdownRenderer }))
  .use(nunjucksRenderer())
  .use(
    permalinks({
      relative: false
    })
  )
  .use(
    layouts({
      engine: 'nunjucks',
      pattern: '**/*.html',
      directory: 'layouts',
      rename: true
    })
  )
  .use(
    svgOptimizer({
      plugins: [{ removeScriptElement: true }],
      pattern: ['**/*.svg', '!**/resources/*.*']
    })
  )
  .use(htmlMinifier())
  .use(cleanCSS())
  .build(err => {
    if (err) throw err
  })
<commit_msg>fix: use pattern to process only right files<commit_after>#!/usr/bin/env node

const path = require('path')
const Metalsmith = require('metalsmith')
const debug = require('metalsmith-debug')
const collections = require('metalsmith-collections')
const layouts = require('metalsmith-layouts')
const nunjucks = require('nunjucks')
const markdown = require('metalsmith-markdown')
const permalinks = require('metalsmith-permalinks')
const writemetadata = require('metalsmith-writemetadata')
const htmlMinifier = require('metalsmith-html-minifier')
const cleanCSS = require('metalsmith-clean-css')
const contenthash = require('metalsmith-contenthash')
const markdownFilter = require('nunjucks-markdown-filter')
const sass = require('metalsmith-sass')
const marked = require('marked')
const { shuffle } = require('lodash')
const metadataDir = require('../plugins/metalsmith-metadata-dir')
const svgOptimizer = require('../plugins/metalsmith-svg-optimizer')
const nunjucksRenderer = require('../plugins/metalsmith-nunjucks-renderer')

const markdownRenderer = new marked.Renderer()
markdownRenderer.image = function (href, title, text) {
  if (href.charAt(0) === '/') {
    href = href.substring(1)
  }
  return `<img src="/{{ hashes['${href}'] }}" alt="${title}" title="${title}" />`
}

const source = path.resolve(
  process.argv[2] || path.join(__dirname, '..', 'website')
)
const dest = path.resolve(
  process.argv[3] || path.join(__dirname, '..', '..', 'build')
)

console.log(`Building website from ${source} into ${dest}`)

const env = nunjucks.configure(path.join(source, 'layouts'), {
  watch: false,
  noCache: true
})
let first = true
env.addGlobal('getContext', function () {
  if (first) {
    console.log(Object.keys(this.env.globals))
    first = false
  }
})
env.addGlobal('currentYear', new Date().getFullYear())
env.addFilter('md', markdownFilter)
env.addFilter('shuffle', arr => shuffle(arr))

Metalsmith(source)
  .source(path.join(source, 'content'))
  .destination(dest)
  .clean(true)
  .metadata(require(path.join(source, 'metadata.json')))
  .use(debug())
  .use(sass({
    outputDir: 'css/'
  }))
  .use(
    writemetadata({
      childIgnorekeys: ['next', 'previous', 'content']
    })
  )
  .use(
    metadataDir({
      directory: path.join(source, 'data')
    })
  )
  .use(
    collections({
      docs: 'docs/**/*.md'
    })
  )
  .use(
    contenthash({
      pattern: ['**/*.{js,css,png,jpg,svg}']
    })
  )
  .use(markdown({ renderer: markdownRenderer }))
  .use(nunjucksRenderer({
    pattern: 'docs/**/*.html'
  }))
  .use(
    permalinks({
      relative: false
    })
  )
  .use(
    layouts({
      engine: 'nunjucks',
      pattern: '**/*.html',
      directory: 'layouts',
      rename: true
    })
  )
  .use(
    svgOptimizer({
      plugins: [{ removeScriptElement: true }],
      pattern: ['!**/resources/*.*']
    })
  )
  .use(htmlMinifier())
  .use(cleanCSS())
  .build(err => {
    if (err) throw err
  })
<|endoftext|>


<commit_before>/**
 * @fileOverview 表示一个包含文本内容的控件。
 * @author xuld
 */

/**
 * 表示一个有内置呈现的控件。
 * @abstract
 * @class ContentControl
 * @extends Control
 *
 *

 * 这个控件同时允许在子控件上显示一个图标。
 *

 *
 *

 * ContentControl 的外元素是一个根据内容自动改变大小的元素。它自身没有设置大小，全部的大小依赖子元素而自动决定。
 * 因此，外元素必须满足下列条件的任何一个:
 *

 *
外元素的 position 是 absolute

 *
外元素的 float 是 left或 right

 *
外元素的 display 是  inline-block (在 IE6 下，使用 inline + zoom模拟)

 *

 *

 */
var ContentControl = Control.extend({

	/**
	 * 获取当前显示的图标。
	 * @getter {Control} icon
	 * @proected
	 */

	icon: function(){
		return this.find('.x-icon');
	},

	/**
	 * 获取当前控件中显示文字的主 DOM 对象。
	 */
	content: function(){
		return this.last(true) || this;
	},

	/**
	 * 当被子类改写时，实现创建添加和返回一个图标节点。
	 * @protected
	 * @virtual
	 */
	createIcon: function(){
		return this.content().prepend(Dom.create('i', 'x-icon'));
	},

	getIcon: function(){
		var icon = this.icon();
		return icon ? (/x-icon-(.*?)\b/.test(icon.node.className) || [0, ""])[1] : null;
	},

	/**
	 * 设置图标。
	 * @param {String} icon 图标。
	 * @return {Panel} this
	 */
	setIcon: function(icon) {

		if(icon != null){
			(this.icon() || this.createIcon()).node.className = "x-icon x-icon-" + icon;
		} else if(icon = this.icon()) {
			icon.remove();
		}

		return this;
	},

	setText: function(value){
		Dom.prototype.setText.call(this.content(), value);
		return this;
	},

	getText: function(){
		return Dom.getText(this.content().node);
	}

});
<commit_msg>icon  <commit_after>/**
 * @fileOverview 表示一个包含文本内容的控件。
 * @author xuld
 */

/**
 * 表示一个有内置呈现的控件。
 * @abstract
 * @class ContentControl
 * @extends Control
 *
 *

 * 这个控件同时允许在子控件上显示一个图标。
 *

 *
 *

 * ContentControl 的外元素是一个根据内容自动改变大小的元素。它自身没有设置大小，全部的大小依赖子元素而自动决定。
 * 因此，外元素必须满足下列条件的任何一个:
 *

 *
外元素的 position 是 absolute

 *
外元素的 float 是 left或 right

 *
外元素的 display 是  inline-block (在 IE6 下，使用 inline + zoom模拟)

 *

 *

 */
var ContentControl = Control.extend({

	/**
	 * 获取当前显示的图标。
	 * @getter {Control} icon
	 * @proected
	 */

	icon: function(){
		return this.find('.x-icon');
	},

	/**
	 * 获取当前控件中显示文字的主 DOM 对象。
	 */
	content: function(){
		return this.last(true) || this;
	},

	/**
	 * 当被子类改写时，实现创建添加和返回一个图标节点。
	 * @protected
	 * @virtual
	 */
	createIcon: function(){
		return this.content().before(Dom.create('i', 'x-icon'));
	},

	getIcon: function(){
		var icon = this.icon();
		return icon ? (/x-icon-(.*?)\b/.test(icon.node.className) || [0, ""])[1] : null;
	},

	/**
	 * 设置图标。
	 * @param {String} icon 图标。
	 * @return {Panel} this
	 */
	setIcon: function(icon) {

		if(icon != null){
			(this.icon() || this.createIcon()).node.className = "x-icon x-icon-" + icon;
		} else if(icon = this.icon()) {
			icon.remove();
		}

		return this;
	},

	setText: function(value){
		Dom.prototype.setText.call(this.content(), value);
		return this;
	},

	getText: function(){
		return Dom.getText(this.content().node);
	}

});
<|endoftext|>


<commit_before>var Comparison = (function (JSON, undefined) {

    var isString, isObject, toString, compare, getValues,
        COMPARE_EQUAL = '==', COMPARE_STRICTEQUAL = '===';

    getValues = function () {
        return [true, false, 1, 0, -1, 'true', 'false', '1', '0', '-1', '', null, undefined, [], {}, [[]], [0], [1], parseFloat('nan')];
    };

    isString = function (o) {
        return Object.prototype.toString.call(o) === "[object String]";
    };

    isObject = function (o) {
        return Object.prototype.toString.call(o) === "[object Object]";
    };

    toString = function (o) {
        if (isString(o)) {
            return '"' + o + '"';
        }
        if (isObject(o)) {
            return JSON.stringify(o);
        }
        if (o === null) {
            return 'null';
        }
        if (o === undefined) {
            return 'undefined';
        }
        if (isNaN(o)) {
            return 'NaN';
        }
        return JSON.stringify(o);
    };

    compare = function (val1, val2, comparator) {

        if (comparator === COMPARE_EQUAL) {
            return (val1 == val2);
        }

        return (val1 === val2);

    };

    return {
        'getValues': getValues,
        'toString': toString,
        'compare': compare,
        'EQUAL': COMPARE_EQUAL,
        'STRICTEQUAL': COMPARE_STRICTEQUAL
    };

}(window.JSON));

export default Comparison;<commit_msg>jshint<commit_after>var Comparison = (function (JSON, undefined) {

    var isString, isObject, toString, compare, getValues,
        COMPARE_EQUAL = '==', COMPARE_STRICTEQUAL = '===';

    getValues = function () {
        return [true, false, 1, 0, -1, 'true', 'false', '1', '0', '-1', '', null, undefined, [], {}, [[]], [0], [1], parseFloat('nan')];
    };

    isString = function (o) {
        return Object.prototype.toString.call(o) === '[object String]';
    };

    isObject = function (o) {
        return Object.prototype.toString.call(o) === '[object Object]';
    };

    toString = function (o) {
        if (isString(o)) {
            return '"' + o + '"';
        }
        if (isObject(o)) {
            return JSON.stringify(o);
        }
        if (o === null) {
            return 'null';
        }
        if (o === undefined) {
            return 'undefined';
        }
        if (isNaN(o)) {
            return 'NaN';
        }
        return JSON.stringify(o);
    };

    compare = function (val1, val2, comparator) {

        if (comparator === COMPARE_EQUAL) {
            /* jshint ignore:start */
            return (val1 == val2);
            /* jshint ignore:end */
        }

        return (val1 === val2);

    };

    return {
        'getValues': getValues,
        'toString': toString,
        'compare': compare,
        'EQUAL': COMPARE_EQUAL,
        'STRICTEQUAL': COMPARE_STRICTEQUAL
    };

}(window.JSON));

export default Comparison;<|endoftext|>


<commit_before>import React, { PropTypes } from 'react'
import { Tabs, FormGroup, InputGroup,
  FormControl, Button } from 'react-bootstrap'
import Icon from '../../components/Icon'
import { connect } from 'react-redux'
import { isEmpty, isUndefined } from 'lodash'
import { FormattedDate, FormattedTime } from 'react-intl'
import GlossaryTab from './GlossaryTab'

// TODO change SidebarContent to just choose between the current
//      content (moved to a different component) and SidebarSettings.
// TODO split this into separate components for each tab and panel
class SidebarContent extends React.Component {
  static propTypes = {
    /* close the sidebar */
    close: PropTypes.func.isRequired,
    glossaryCount: PropTypes.number.isRequired,
    hasSelectedPhrase: PropTypes.bool.isRequired,
    selectedPhrase: PropTypes.shape({
      msgctxt: PropTypes.string,
      resId: PropTypes.string.isRequired,
      sourceComment: PropTypes.string,
      sourceFlags: PropTypes.string,
      sourceReferences: PropTypes.string,
      lastModifiedBy: PropTypes.string,
      lastModifiedTime: PropTypes.instanceOf(Date)
    })
  }

  sidebarDetails = () => {
    if (!this.props.hasSelectedPhrase) {
      return Select a phrase to see details.
    }
    const {
      msgctxt,
      resId,
      sourceComment,
      sourceFlags,
      sourceReferences,
      lastModifiedBy,
      lastModifiedTime
    } = this.props.selectedPhrase

    return (

        {this.detailItem('Resource ID', resId)}
        {this.detailItem('Message Context', msgctxt)}
        {this.detailItem('Reference', sourceReferences)}
        {this.detailItem('Flags', sourceFlags)}
        {this.detailItem('Source Comment', sourceComment)}
        {this.detailItem('Last Modified',
            this.lastModifiedDisplay(lastModifiedBy, lastModifiedTime))}

    )
  }

  detailItem = (label, value) => {
    const valueDisplay = isEmpty(value)
        ? No content
        : {value}
    return (

        {label} {valueDisplay}

    )
  }

  lastModifiedDisplay = (lastModifiedBy, lastModifiedTime) => {
    if (isUndefined(lastModifiedBy) && isUndefined(lastModifiedTime)) {
      return undefined
    }

    const modifiedByIcon = isUndefined(lastModifiedBy) ? undefined
        : <Icon name="user" className="n1" />
    const modifiedTimeIcon = isUndefined(lastModifiedTime) ? undefined
        : <Icon name="clock" className="n1" />
    const modifiedDate = isUndefined(lastModifiedTime) ? undefined
        : <FormattedDate value={lastModifiedTime} format="medium" />
    const modifiedTime = isUndefined(lastModifiedTime) ? undefined
        : <FormattedTime value={lastModifiedTime} />
    return (

        {modifiedByIcon} {lastModifiedBy} {modifiedTimeIcon
        } {modifiedDate} {modifiedTime}

    )
  }

  /* URL of the selected phrase, with copy button. */
  phraseLink = () => {
    // TODO need to set up phrase ID in the URL first
    return (
      <FormGroup className="trans-link">
        <InputGroup>
          <InputGroup.Addon><Icon name="copy"
            className="s1" />
          </InputGroup.Addon>
          <FormControl type="text" />
        </InputGroup>
      </FormGroup>
    )
  }

  render () {
    const { glossaryCount } = this.props
    const glossaryCountDisplay = glossaryCount > 0
      // TODO kgough display as a badge instead of text in parens
      ?  ({this.props.glossaryCount})
      : undefined
    const glossaryTitle = (

        <Icon name="glossary" className="s1 gloss-tab-svg" />
        Glossary{glossaryCountDisplay}

    )

    return (

          <Icon name="info" className="details-svg s1" />
          Details

            <Button bsStyle="link" onClick={this.props.close}>
              <Icon name="cross" />
            </Button>

          {this.sidebarDetails()}

        <Tabs id="sidebartabs" defaultActiveKey={1}>
          { /* <Tab eventKey={2} title={activityTitle}>

              Tab 1 content

          </Tab> */ }
          <GlossaryTab eventKey={1} title={glossaryTitle} />
        </Tabs>

    )
  }
}

function mapStateToProps (state) {
  const { glossary, phrases } = state
  const { detail, selectedPhraseId } = phrases
  const selectedPhrase = detail[selectedPhraseId]

  const { results, searchText } = glossary
  const glossaryResults = results.get(searchText)
  const glossaryCount = glossaryResults ? glossaryResults.length : 0

  // Need to check whether phrase itself is undefined since the detail may not
  // yet have been fetched from the server.
  const hasSelectedPhrase = !isUndefined(selectedPhraseId) &&
      !isUndefined(selectedPhrase)

  const newProps = {
    glossaryCount,
    hasSelectedPhrase
  }

  if (hasSelectedPhrase) {
    newProps.selectedPhrase = selectedPhrase
  }

  return newProps
}

export default connect(mapStateToProps)(SidebarContent)
<commit_msg>feat(ZNTA-1736): move TODO comments that are causing a merge conflict<commit_after>// TODO change SidebarContent to just choose between the current
//      content (moved to a different component) and SidebarSettings.
// TODO split this into separate components for each tab and panel
import React, { PropTypes } from 'react'
import { Tabs, FormGroup, InputGroup,
  FormControl, Button } from 'react-bootstrap'
import Icon from '../../components/Icon'
import { connect } from 'react-redux'
import { isEmpty, isUndefined } from 'lodash'
import { FormattedDate, FormattedTime } from 'react-intl'
import GlossaryTab from './GlossaryTab'

class SidebarContent extends React.Component {
  static propTypes = {
    /* close the sidebar */
    close: PropTypes.func.isRequired,
    glossaryCount: PropTypes.number.isRequired,
    hasSelectedPhrase: PropTypes.bool.isRequired,
    selectedPhrase: PropTypes.shape({
      msgctxt: PropTypes.string,
      resId: PropTypes.string.isRequired,
      sourceComment: PropTypes.string,
      sourceFlags: PropTypes.string,
      sourceReferences: PropTypes.string,
      lastModifiedBy: PropTypes.string,
      lastModifiedTime: PropTypes.instanceOf(Date)
    })
  }

  sidebarDetails = () => {
    if (!this.props.hasSelectedPhrase) {
      return Select a phrase to see details.
    }
    const {
      msgctxt,
      resId,
      sourceComment,
      sourceFlags,
      sourceReferences,
      lastModifiedBy,
      lastModifiedTime
    } = this.props.selectedPhrase

    return (

        {this.detailItem('Resource ID', resId)}
        {this.detailItem('Message Context', msgctxt)}
        {this.detailItem('Reference', sourceReferences)}
        {this.detailItem('Flags', sourceFlags)}
        {this.detailItem('Source Comment', sourceComment)}
        {this.detailItem('Last Modified',
            this.lastModifiedDisplay(lastModifiedBy, lastModifiedTime))}

    )
  }

  detailItem = (label, value) => {
    const valueDisplay = isEmpty(value)
        ? No content
        : {value}
    return (

        {label} {valueDisplay}

    )
  }

  lastModifiedDisplay = (lastModifiedBy, lastModifiedTime) => {
    if (isUndefined(lastModifiedBy) && isUndefined(lastModifiedTime)) {
      return undefined
    }

    const modifiedByIcon = isUndefined(lastModifiedBy) ? undefined
        : <Icon name="user" className="n1" />
    const modifiedTimeIcon = isUndefined(lastModifiedTime) ? undefined
        : <Icon name="clock" className="n1" />
    const modifiedDate = isUndefined(lastModifiedTime) ? undefined
        : <FormattedDate value={lastModifiedTime} format="medium" />
    const modifiedTime = isUndefined(lastModifiedTime) ? undefined
        : <FormattedTime value={lastModifiedTime} />
    return (

        {modifiedByIcon} {lastModifiedBy} {modifiedTimeIcon
        } {modifiedDate} {modifiedTime}

    )
  }

  /* URL of the selected phrase, with copy button. */
  phraseLink = () => {
    // TODO need to set up phrase ID in the URL first
    return (
      <FormGroup className="trans-link">
        <InputGroup>
          <InputGroup.Addon><Icon name="copy"
            className="s1" />
          </InputGroup.Addon>
          <FormControl type="text" />
        </InputGroup>
      </FormGroup>
    )
  }

  render () {
    const { glossaryCount } = this.props
    const glossaryCountDisplay = glossaryCount > 0
      // TODO kgough display as a badge instead of text in parens
      ?  ({this.props.glossaryCount})
      : undefined
    const glossaryTitle = (

        <Icon name="glossary" className="s1 gloss-tab-svg" />
        Glossary{glossaryCountDisplay}

    )

    return (

          <Icon name="info" className="details-svg s1" />
          Details

            <Button bsStyle="link" onClick={this.props.close}>
              <Icon name="cross" />
            </Button>

          {this.sidebarDetails()}

        <Tabs id="sidebartabs" defaultActiveKey={1}>
          { /* <Tab eventKey={2} title={activityTitle}>

              Tab 1 content

          </Tab> */ }
          <GlossaryTab eventKey={1} title={glossaryTitle} />
        </Tabs>

    )
  }
}

function mapStateToProps (state) {
  const { glossary, phrases } = state
  const { detail, selectedPhraseId } = phrases
  const selectedPhrase = detail[selectedPhraseId]

  const { results, searchText } = glossary
  const glossaryResults = results.get(searchText)
  const glossaryCount = glossaryResults ? glossaryResults.length : 0

  // Need to check whether phrase itself is undefined since the detail may not
  // yet have been fetched from the server.
  const hasSelectedPhrase = !isUndefined(selectedPhraseId) &&
      !isUndefined(selectedPhrase)

  const newProps = {
    glossaryCount,
    hasSelectedPhrase
  }

  if (hasSelectedPhrase) {
    newProps.selectedPhrase = selectedPhrase
  }

  return newProps
}

export default connect(mapStateToProps)(SidebarContent)
<|endoftext|>


<commit_before>(function($)
/*
 *  Project: opensong.js
 *  Description: displays OpenSong files nicely on a web page
 *  Author: Andreas Boehrnsen
 *  License: LGPL 2.1
 */
{
  // jQuery wrapper around openSongLyrics function
  $.fn.openSongLyrics = function(lyrics) {
    try {
      openSongLyrics(this, lyrics);
    } catch(e) {
      alert(e);
    }
  }
  // displays Opensong
  function openSongLyrics(domElem, lyrics) {
    // clear Html Element and add opensong class
    $(domElem).html("").addClass("opensong");

    var lyricsLines = lyrics.split("\n");

    while(lyricsLines.length > 0) {
      var line = lyricsLines.shift();

      switch(line[0]) {
        case "[":
          var header = line.match(/\[(.*)\]/)[1]
          // replace first char (e.g. V -> Verse)
          header = header.replace(header[0], replaceHeader(header[0]));

          $(domElem).append("
" + header + "
");
          break
        case ".":
          var chordsLine = line.substr(1);

          var chordArr = new Array();
          // split cords
          while (chordsLine.length > 0) {
            var m = /^(\S*\s*)(.*)$/.exec(chordsLine);
            chordArr.push(m[1]);
            chordsLine = m[2];
          }
          // add an item if it is an empty line
          if (chordArr.length == 0) {
            chordArr.push(chordsLine);
          }

          // write html table row for the chords
          var htmlTableRows = "
	" + chordArr.join("	") + "
\n";

          var textLine = "", m = null, cleanRegExp = /_|\||---|-!!/g;

          // while we have lines that match a textLine create an html table row
          while ((textLine = lyricsLines.shift()) && (m = textLine.match(/^([ 1-9])(.*)/))) {
            var textArr = new Array();
            var textLineNr = m[1];
            textLine = m[2];

            // split lyrics line based on chord length
            for (var i in chordArr) {
              if (i < chordArr.length - 1) {
                var chordLength = chordArr[i].length;
                // split String with RegExp (is there a better way?)
                var m = textLine.match(new RegExp("(.{0,"+ chordLength +"})(.*)"));

                textArr.push(m[1].replace(cleanRegExp, ""));
                textLine = m[2];
              } else {
                // add the whole string if at the end of the chord arr
                textArr.push(textLine.replace(cleanRegExp, ""));
              }
            }
            // write html table row for the text (lyrics)
            htmlTableRows = htmlTableRows + "
" + textLineNr + "	" + textArr.join("	") + "
\n";
          }
          // attach the line again in front (we cut it off in the while loop)
          if(textLine !== undefined) lyricsLines.unshift(textLine);

          $(domElem).append("
" + htmlTableRows + "
");
          break;
        case " ":
          $(domElem).append("
" + line.substr(1) + "
");
          break;
        case ";":
          $(domElem).append("
" + line.substr(1) + "
");
          break;
        default:
          console.log("no support for :" + line);
      };
    }

    function replaceHeader(abbr) {
      switch(abbr) {
        case "C":
          return "Chorus ";
        case "V":
          return "Verse ";
        case "B":
          return "Bridge ";
        case "T":
          return "Tag ";
        case "P":
          return "Pre-Chorus ";
        default:
          return abbr;
        }
    }
  }
})(jQuery);
<commit_msg>Dev: semicolon at beginning of plugin<commit_after>/*
 *  Project: opensong.js
 *  Description: displays OpenSong files nicely on a web page
 *  Author: Andreas Boehrnsen
 *  License: LGPL 2.1
 */

// the semi-colon before function invocation is a safety net against concatenated
// scripts and/or other plugins which may not be closed properly.
;(function($)
{
  // jQuery wrapper around openSongLyrics function
  $.fn.openSongLyrics = function(lyrics) {
    try {
      openSongLyrics(this, lyrics);
    } catch(e) {
      alert(e);
    }
  }

  // displays Opensong
  function openSongLyrics(domElem, lyrics) {
    // clear Html Element and add opensong class
    $(domElem).html("").addClass("opensong");

    var lyricsLines = lyrics.split("\n");

    while(lyricsLines.length > 0) {
      var line = lyricsLines.shift();

      switch(line[0]) {
        case "[":
          var header = line.match(/\[(.*)\]/)[1]
          // replace first char (e.g. V -> Verse)
          header = header.replace(header[0], replaceHeader(header[0]));

          $(domElem).append("
" + header + "
");
          break
        case ".":
          var chordsLine = line.substr(1);

          var chordArr = new Array();
          // split cords
          while (chordsLine.length > 0) {
            var m = /^(\S*\s*)(.*)$/.exec(chordsLine);
            chordArr.push(m[1]);
            chordsLine = m[2];
          }
          // add an item if it is an empty line
          if (chordArr.length == 0) {
            chordArr.push(chordsLine);
          }

          // write html table row for the chords
          var htmlTableRows = "
	" + chordArr.join("	") + "
\n";

          var textLine = "", m = null, cleanRegExp = /_|\||---|-!!/g;

          // while we have lines that match a textLine create an html table row
          while ((textLine = lyricsLines.shift()) && (m = textLine.match(/^([ 1-9])(.*)/))) {
            var textArr = new Array();
            var textLineNr = m[1];
            textLine = m[2];

            // split lyrics line based on chord length
            for (var i in chordArr) {
              if (i < chordArr.length - 1) {
                var chordLength = chordArr[i].length;
                // split String with RegExp (is there a better way?)
                var m = textLine.match(new RegExp("(.{0,"+ chordLength +"})(.*)"));

                textArr.push(m[1].replace(cleanRegExp, ""));
                textLine = m[2];
              } else {
                // add the whole string if at the end of the chord arr
                textArr.push(textLine.replace(cleanRegExp, ""));
              }
            }
            // write html table row for the text (lyrics)
            htmlTableRows = htmlTableRows + "
" + textLineNr + "	" + textArr.join("	") + "
\n";
          }
          // attach the line again in front (we cut it off in the while loop)
          if(textLine !== undefined) lyricsLines.unshift(textLine);

          $(domElem).append("
" + htmlTableRows + "
");
          break;
        case " ":
          $(domElem).append("
" + line.substr(1) + "
");
          break;
        case ";":
          $(domElem).append("
" + line.substr(1) + "
");
          break;
        default:
          console.log("no support for :" + line);
      };
    }

    function replaceHeader(abbr) {
      switch(abbr) {
        case "C":
          return "Chorus ";
        case "V":
          return "Verse ";
        case "B":
          return "Bridge ";
        case "T":
          return "Tag ";
        case "P":
          return "Pre-Chorus ";
        default:
          return abbr;
        }
    }
  }
})(jQuery);
<|endoftext|>


<commit_before>define(
  'ephox.alloy.spec.ToolbarGroupSpec',

  [
    'ephox.alloy.spec.SpecSchema',
    'ephox.alloy.spec.UiSubstitutes',
    'ephox.boulder.api.FieldPresence',
    'ephox.boulder.api.FieldSchema',
    'ephox.boulder.api.ValueSchema',
    'ephox.compass.Arr',
    'ephox.highway.Merger',
    'ephox.perhaps.Option'
  ],

  function (SpecSchema, UiSubstitutes, FieldPresence, FieldSchema, ValueSchema, Arr, Merger, Option) {
    var schema = [
      FieldSchema.strict('dom'),
      FieldSchema.strict('items'),

      FieldSchema.field(
        'members',
        'members',
        FieldPresence.strict(),
        ValueSchema.objOf([
          FieldSchema.strict('item')
        ])
      ),

      FieldSchema.field(
        'markers',
        'markers',
        FieldPresence.strict(),
        ValueSchema.objOf([
          FieldSchema.strict('itemClass')
        ])
      )
    ];

    var make = function (spec) {
      var detail = SpecSchema.asStructOrDie('toolbar.group.spec', schema, spec, [ ]);

      var components = UiSubstitutes.substitutePlaces(
        Option.some('toolbar-group'),
        detail,
        detail.components(),
        {
          '<alloy.toolbar.group.items>': UiSubstitutes.multiple(
            Arr.map(detail.items(), function (item) {
              return detail.members().item().munge(item);
            })
          )
        },
        {

        }
      );

      return {
        uid: detail.uid(),
        uiType: 'custom',
        dom: detail.dom(),
        components: components,
        keying: {
          mode: 'flow',
          selector: detail.markers().itemClass()
        },
        tabstopping: true,
        focusing: undefined
      };
    };

    return {
      make: make
    };
  }
);<commit_msg>VAN-44-wheel-hackery-apis: toolbar role for toolbar-group<commit_after>define(
  'ephox.alloy.spec.ToolbarGroupSpec',

  [
    'ephox.alloy.spec.SpecSchema',
    'ephox.alloy.spec.UiSubstitutes',
    'ephox.boulder.api.FieldPresence',
    'ephox.boulder.api.FieldSchema',
    'ephox.boulder.api.ValueSchema',
    'ephox.compass.Arr',
    'ephox.highway.Merger',
    'ephox.perhaps.Option'
  ],

  function (SpecSchema, UiSubstitutes, FieldPresence, FieldSchema, ValueSchema, Arr, Merger, Option) {
    var schema = [
      FieldSchema.strict('dom'),
      FieldSchema.strict('items'),

      FieldSchema.field(
        'members',
        'members',
        FieldPresence.strict(),
        ValueSchema.objOf([
          FieldSchema.strict('item')
        ])
      ),

      FieldSchema.field(
        'markers',
        'markers',
        FieldPresence.strict(),
        ValueSchema.objOf([
          FieldSchema.strict('itemClass')
        ])
      )
    ];

    var make = function (spec) {
      var detail = SpecSchema.asStructOrDie('toolbar.group.spec', schema, spec, [ ]);

      var components = UiSubstitutes.substitutePlaces(
        Option.some('toolbar-group'),
        detail,
        detail.components(),
        {
          '<alloy.toolbar.group.items>': UiSubstitutes.multiple(
            Arr.map(detail.items(), function (item) {
              return Merger.deepMerge(
                detail.members().item().munge(item),
                {
                  dom: {
                    attributes: {
                      role: 'toolbar'
                    }
                  }
                }
              );
            })
          )
        },
        {

        }
      );

      return {
        uid: detail.uid(),
        uiType: 'custom',
        dom: detail.dom(),
        components: components,
        keying: {
          mode: 'flow',
          selector: detail.markers().itemClass()
        },
        tabstopping: true,
        focusing: undefined
      };
    };

    return {
      make: make
    };
  }
);<|endoftext|>


<commit_before>/* global bootbox, PasswordSecurityTranslation, MenuTranslation, zenscroll, vanillaToast, GlobalTranslation */

// Internet Exploder caches AJAX requests by default
$(document).ready(() => {
    $.ajaxSetup({cache: false});

    const token = $("body").data("csrf-token");

    $.ajaxPrefilter(function (options, originalOptions, jqXHR) {
        if (options.type.toLowerCase() === "post") {
            // initialize `data` to empty string if it does not exist
            options.data = options.data || "";

            // add leading ampersand if `data` is non-empty
            options.data += options.data ? "&" : "";

            // add _token entry
            options.data += "csrf_token=" + encodeURIComponent(token);
        }
    });
});

$(() => {
    const language = $("html").data("select2-language");
    bootbox.setDefaults({
        locale: $("html").data("select2-language")
    });

    // toggle hamburger menu
    $("#menu-toggle").click(() =>
        $(".mainmenu").slideToggle()
    );

    $(".mainmenu a.is-not-ajax").click((event) => {
        $(".mainmenu").hide();

        if(event.target.target === "_blank") {
            return
        }

        $("#main-backend-content, #message").hide();
        $("#main-content-loadspinner").show();
    });

    $("a.is-ajax").click((event) => {
        event.preventDefault();
        event.stopPropagation();
        const target = $(event.target);
        const url = target.attr("href");

        const mainMenu = $(".mainmenu");
        const isMenuEntry = mainMenu.contains(target);

        $(".mainmenu").hide();

        $("#main-backend-content, #message").hide();
        $("#main-content-loadspinner").show();

        $("#content-container").load(url, (response, status, xhr) => {
            $("#main-backend-content").show();
            $("#main-content-loadspinner").hide();
            if (status === "error") {
                const msg = `${xhr.status} ${xhr.statusText}`;
                bootbox.alert(
                        $('<div/>').text(msg).html()
                        );
            } else if(isMenuEntry) {
               mainMenu.find("a").removeClass("active");
                target.addClass("active");
             }
        });
    });

    // clear-cache shortcut icon
    $("#menu-clear-cache").click((event) => {
        event.preventDefault();
        event.stopPropagation();

        $("#menu-clear-cache").hide();
        $("#menu-clear-cache-loading").show();
        const url = $("#menu-clear-cache").data("url");
        $.get(url, () => {
            $("#menu-clear-cache").show();
            $("#menu-clear-cache-loading").hide();
        });
    });

    // Add bootstrap css class to tablesorter
    $.extend($.fn.dataTableExt.oStdClasses, {
        sFilterInput: "form-control",
        sLengthSelect: "form-control"
    });

    // copy text from input to clipboard on click
    $(".select-on-click").click((event) => {
        const target = $(event.target);
        copyTextToClipboard(
                target.value,
                () => vanillaToast.success(GlobalTranslation.CopiedToClipboardSuccess),
                () => {
            vanillaToast.error(GlobalTranslation.CopiedToClipboardFailed);
            target.select();
        }
        );
    });

    // Disabled a link-buttons must not be clickable
    $("a").click((event) => {
        const target = $(event.currentTarget);
        if ((target.hasClass("disabled") || target.attr("disabled")) && target.attr("href").length > 1) {
            event.preventDefault();
        }
    });

    initDataTables("body");

    // password security check
    if (typeof $(".password-security-check").password !== "undefined") {
        $(".password-security-check").password({
            shortPass: PasswordSecurityTranslation.ShortPass,
            badPass: PasswordSecurityTranslation.BadPass,
            goodPass: PasswordSecurityTranslation.GoodPass,
            strongPass: PasswordSecurityTranslation.StrongPass,
            containsUsername: PasswordSecurityTranslation.ContainsUsername,
            enterPass: PasswordSecurityTranslation.EnterPass,
            showPercent: false,
            showText: true, // shows the text tips
            animate: true, // whether or not to animate the progress bar on input blur/focus
            animateSpeed: "fast", // the above animation speed
            username: $("[name=username]").length ? $("[name=username]") : false, // select the username field (selector or jQuery instance) for better password checks
            usernamePartialMatch: true, // whether to check for username partials
            minimumLength: 4 // minimum password length (below this threshold, the score is 0)
        });
    }
    // Links to upcoming features

    $(".coming-soon").click((event) => {
        event.preventDefault();
        bootbox.alert("Coming Soon!");
    });
    // Showing a link in an alert box
    initRemoteAlerts("body");
    // There is a bug in iOS Safari's implementation of datetime-local
    // Safari appends a timezone to value on change while the
    // validation only accepts value without timezone
    // remove the timezone from the datetime value
    // https://www.reddit.com/r/webdev/comments/6pxfn3/ios_datetimelocal_inputs_broken_universally/

    const isSafari = !!navigator.userAgent.match(/Version\/[\d\.]+.*Safari/);
    if (isSafari) {
        $("input[type='datetime-local']").map((element) =>
            $(element).val($(element).val().substr(0, 16))
        );

        $("input[type='datetime-local']").change((event) =>
            event.target.value = event.target.value.substr(0, 16)
        );
    }

// dynamically add class form-control to all form elements to
// make inputs prettier
    $("input, select, textarea")
            .not("input[type=checkbox]")
            .not("input[type=radio]")
            .not("input[type=button]")
            .not("input[type=submit]")
            .not("input[type=reset]")
            .not("input[type=image]")
            .addClass("form-control");
    // override save shortcut to trigger submit button
    if ($("form button[type=submit], form input[type=submit]").length) {
        document.addEventListener(
                "keydown",
                (e) => {
            if ((window.navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey)
                    && e.keyCode === 83) {
                e.preventDefault();
                $("form button[type=submit], form input[type=submit]")
                        .last()
                        .click();
                // Process the event here (such as click on submit button)
            }
        }, false);
    }

// prettier select-boxes
    $("select").select2({
        width: "100%",
        language: language
    });

    // Toggle switches for some checkboxes
    $(".js-switch").bootstrapToggle({
        on: MenuTranslation.On,
        off: MenuTranslation.Off
    });

    // bootstrap-toggle doesn't react to click on the label of a toggle switch
    // This is a long standing issue that is still not fixed.
    // https://github.com/minhur/bootstrap-toggle/issues/23
    // just wrap the clickable text in an element with this css class
    // to make it clickable
    $(".js-switch-label").click((event) => {
        const target = $(event.target);
        const theSwitch = target.closest('.checkbox').find(".js-switch");
        if (theSwitch && theSwitch.length) {
            theSwitch.bootstrapToggle('toggle');
        }

    });

    $.datetimepicker.setLocale(language);
    $(".datepicker").datetimepicker({
        format: "Y-m-d",
        timepicker: false
    });

    // User has to confirm logout
    $("a.backend-menu-item-logout").click((event) => {
        event.preventDefault();
            $(".mainmenu").hide();
        const url = $(event.target).attr("href");
        bootbox.confirm(`${MenuTranslation.Logout}?`, (result) => {
            if (result) {
                location.href = url;
            }
        });
    });

    // show a scroll-to-top arrow
    // if the scroll viewport isn't at top of the page
    $(window).scroll(() => {
        if ($(window).scrollTop() > 0) {
            $("#scroll-to-top").fadeIn();
        } else {
            $("#scroll-to-top").fadeOut();
        }
    });

    // scroll to top arrow at bottom right
    $("#scroll-to-top").click((event) => {
        event.preventDefault();
        event.stopPropagation();

        zenscroll.toY(0);
    });

    $(".more-options-toggle").click((event) => {
        const target = $(event.target);
        const toggleTarget = $(target.data("target"));

        toggleTarget.slideToggle()

    });
});
<commit_msg>Update global.js<commit_after>/* global bootbox, PasswordSecurityTranslation, MenuTranslation, zenscroll, vanillaToast, GlobalTranslation */

// Internet Exploder caches AJAX requests by default
$(document).ready(() => {
    $.ajaxSetup({cache: false});

    const token = $("body").data("csrf-token");

    $.ajaxPrefilter(function (options, originalOptions, jqXHR) {
        if (options.type.toLowerCase() === "post") {
            // initialize `data` to empty string if it does not exist
            options.data = options.data || "";

            // add leading ampersand if `data` is non-empty
            options.data += options.data ? "&" : "";

            // add _token entry
            options.data += "csrf_token=" + encodeURIComponent(token);
        }
    });
});

$(() => {
    const language = $("html").data("select2-language");
    bootbox.setDefaults({
        locale: $("html").data("select2-language")
    });

    // toggle hamburger menu
    $("#menu-toggle").click(() =>
        $(".mainmenu").slideToggle()
    );

    $(".mainmenu a.is-not-ajax").click((event) => {
        $(".mainmenu").hide();

        if(event.target.target === "_blank") {
            return
        }

        $("#main-backend-content, #message").hide();
        $("#main-content-loadspinner").show();
    });

    $("a.is-ajax").click((event) => {
        event.preventDefault();
        event.stopPropagation();
        const target = $(event.target);
        const url = target.attr("href");

        const mainMenu = $(".mainmenu");
        const isMenuEntry = mainMenu.has(target);

        $(".mainmenu").hide();

        $("#main-backend-content, #message").hide();
        $("#main-content-loadspinner").show();

        $("#content-container").load(url, (response, status, xhr) => {
            $("#main-backend-content").show();
            $("#main-content-loadspinner").hide();
            if (status === "error") {
                const msg = `${xhr.status} ${xhr.statusText}`;
                bootbox.alert(
                        $('<div/>').text(msg).html()
                        );
            } else if(isMenuEntry) {
               mainMenu.find("a").removeClass("active");
                target.addClass("active");
             }
        });
    });

    // clear-cache shortcut icon
    $("#menu-clear-cache").click((event) => {
        event.preventDefault();
        event.stopPropagation();

        $("#menu-clear-cache").hide();
        $("#menu-clear-cache-loading").show();
        const url = $("#menu-clear-cache").data("url");
        $.get(url, () => {
            $("#menu-clear-cache").show();
            $("#menu-clear-cache-loading").hide();
        });
    });

    // Add bootstrap css class to tablesorter
    $.extend($.fn.dataTableExt.oStdClasses, {
        sFilterInput: "form-control",
        sLengthSelect: "form-control"
    });

    // copy text from input to clipboard on click
    $(".select-on-click").click((event) => {
        const target = $(event.target);
        copyTextToClipboard(
                target.value,
                () => vanillaToast.success(GlobalTranslation.CopiedToClipboardSuccess),
                () => {
            vanillaToast.error(GlobalTranslation.CopiedToClipboardFailed);
            target.select();
        }
        );
    });

    // Disabled a link-buttons must not be clickable
    $("a").click((event) => {
        const target = $(event.currentTarget);
        if ((target.hasClass("disabled") || target.attr("disabled")) && target.attr("href").length > 1) {
            event.preventDefault();
        }
    });

    initDataTables("body");

    // password security check
    if (typeof $(".password-security-check").password !== "undefined") {
        $(".password-security-check").password({
            shortPass: PasswordSecurityTranslation.ShortPass,
            badPass: PasswordSecurityTranslation.BadPass,
            goodPass: PasswordSecurityTranslation.GoodPass,
            strongPass: PasswordSecurityTranslation.StrongPass,
            containsUsername: PasswordSecurityTranslation.ContainsUsername,
            enterPass: PasswordSecurityTranslation.EnterPass,
            showPercent: false,
            showText: true, // shows the text tips
            animate: true, // whether or not to animate the progress bar on input blur/focus
            animateSpeed: "fast", // the above animation speed
            username: $("[name=username]").length ? $("[name=username]") : false, // select the username field (selector or jQuery instance) for better password checks
            usernamePartialMatch: true, // whether to check for username partials
            minimumLength: 4 // minimum password length (below this threshold, the score is 0)
        });
    }
    // Links to upcoming features

    $(".coming-soon").click((event) => {
        event.preventDefault();
        bootbox.alert("Coming Soon!");
    });
    // Showing a link in an alert box
    initRemoteAlerts("body");
    // There is a bug in iOS Safari's implementation of datetime-local
    // Safari appends a timezone to value on change while the
    // validation only accepts value without timezone
    // remove the timezone from the datetime value
    // https://www.reddit.com/r/webdev/comments/6pxfn3/ios_datetimelocal_inputs_broken_universally/

    const isSafari = !!navigator.userAgent.match(/Version\/[\d\.]+.*Safari/);
    if (isSafari) {
        $("input[type='datetime-local']").map((element) =>
            $(element).val($(element).val().substr(0, 16))
        );

        $("input[type='datetime-local']").change((event) =>
            event.target.value = event.target.value.substr(0, 16)
        );
    }

// dynamically add class form-control to all form elements to
// make inputs prettier
    $("input, select, textarea")
            .not("input[type=checkbox]")
            .not("input[type=radio]")
            .not("input[type=button]")
            .not("input[type=submit]")
            .not("input[type=reset]")
            .not("input[type=image]")
            .addClass("form-control");
    // override save shortcut to trigger submit button
    if ($("form button[type=submit], form input[type=submit]").length) {
        document.addEventListener(
                "keydown",
                (e) => {
            if ((window.navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey)
                    && e.keyCode === 83) {
                e.preventDefault();
                $("form button[type=submit], form input[type=submit]")
                        .last()
                        .click();
                // Process the event here (such as click on submit button)
            }
        }, false);
    }

// prettier select-boxes
    $("select").select2({
        width: "100%",
        language: language
    });

    // Toggle switches for some checkboxes
    $(".js-switch").bootstrapToggle({
        on: MenuTranslation.On,
        off: MenuTranslation.Off
    });

    // bootstrap-toggle doesn't react to click on the label of a toggle switch
    // This is a long standing issue that is still not fixed.
    // https://github.com/minhur/bootstrap-toggle/issues/23
    // just wrap the clickable text in an element with this css class
    // to make it clickable
    $(".js-switch-label").click((event) => {
        const target = $(event.target);
        const theSwitch = target.closest('.checkbox').find(".js-switch");
        if (theSwitch && theSwitch.length) {
            theSwitch.bootstrapToggle('toggle');
        }

    });

    $.datetimepicker.setLocale(language);
    $(".datepicker").datetimepicker({
        format: "Y-m-d",
        timepicker: false
    });

    // User has to confirm logout
    $("a.backend-menu-item-logout").click((event) => {
        event.preventDefault();
            $(".mainmenu").hide();
        const url = $(event.target).attr("href");
        bootbox.confirm(`${MenuTranslation.Logout}?`, (result) => {
            if (result) {
                location.href = url;
            }
        });
    });

    // show a scroll-to-top arrow
    // if the scroll viewport isn't at top of the page
    $(window).scroll(() => {
        if ($(window).scrollTop() > 0) {
            $("#scroll-to-top").fadeIn();
        } else {
            $("#scroll-to-top").fadeOut();
        }
    });

    // scroll to top arrow at bottom right
    $("#scroll-to-top").click((event) => {
        event.preventDefault();
        event.stopPropagation();

        zenscroll.toY(0);
    });

    $(".more-options-toggle").click((event) => {
        const target = $(event.target);
        const toggleTarget = $(target.data("target"));

        toggleTarget.slideToggle()

    });
});
<|endoftext|>


<commit_before>var fs = require('fs');
var path = require('path');
var semver = require('semver');

module.exports = function(bump, filename, nosave) {
  filename = filename || 'package.json';
  filename = path.join(process.cwd(), filename);
  var package_file = JSON.parse(fs.readFileSync(filename));
  var version = semver.valid(package_file.version);
  if (version) {
    if (bump) {
      if (bump !== 'none') {
        package_file.version = semver.inc(version, bump);
      }
      if (!nosave && bump !== 'none') {
        fs.writeFileSync(filename, JSON.stringify(package_file, null, '  '));
      }
      return { from: version, to: package_file.version, bump: bump };
    } else {
      return version;
    }
  } else {
    return '';
  }

};
<commit_msg>remove nosave option and add 'bump' option<commit_after>var fs = require('fs');
var path = require('path');
var semver = require('semver');

module.exports = function(bump, filename) {
  filename = filename || 'package.json';
  filename = path.join(process.cwd(), filename);
  var package_file = JSON.parse(fs.readFileSync(filename));
  var version = semver.valid(package_file.version);
  if (version) {
    if (bump) {
      if (bump === 'major' || bump === 'minor' || bump === 'patch') {
        package_file.version = semver.inc(version, bump);
      }
      if (bump === 'major' || bump === 'minor' || bump === 'patch') {
        fs.writeFileSync(filename, JSON.stringify(package_file, null, '  '));
      } else if (bump === 'bump') {
        var stat = fs.statSync(filename);
        var time = (new Date()).getTime();
        fs.utimesSync(filename, stat.atime, time);
      }
      return { from: version, to: package_file.version, bump: bump };
    } else {
      return version;
    }
  } else {
    return '';
  }
};
<|endoftext|>