--- /dev/null
+!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.eio=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){
+
+module.exports = _dereq_('./lib/');
+
+},{"./lib/":2}],2:[function(_dereq_,module,exports){
+
+module.exports = _dereq_('./socket');
+
+/**
+ * Exports parser
+ *
+ * @api public
+ *
+ */
+module.exports.parser = _dereq_('engine.io-parser');
+
+},{"./socket":3,"engine.io-parser":15}],3:[function(_dereq_,module,exports){
+(function (global){
+/**
+ * Module dependencies.
+ */
+
+var transports = _dereq_('./transports');
+var Emitter = _dereq_('component-emitter');
+var debug = _dereq_('debug')('engine.io-client:socket');
+var index = _dereq_('indexof');
+var parser = _dereq_('engine.io-parser');
+var parseuri = _dereq_('parseuri');
+var parsejson = _dereq_('parsejson');
+var parseqs = _dereq_('parseqs');
+
+/**
+ * Module exports.
+ */
+
+module.exports = Socket;
+
+/**
+ * Noop function.
+ *
+ * @api private
+ */
+
+function noop(){}
+
+/**
+ * Socket constructor.
+ *
+ * @param {String|Object} uri or options
+ * @param {Object} options
+ * @api public
+ */
+
+function Socket(uri, opts){
+ if (!(this instanceof Socket)) return new Socket(uri, opts);
+
+ opts = opts || {};
+
+ if (uri && 'object' == typeof uri) {
+ opts = uri;
+ uri = null;
+ }
+
+ if (uri) {
+ uri = parseuri(uri);
+ opts.host = uri.host;
+ opts.secure = uri.protocol == 'https' || uri.protocol == 'wss';
+ opts.port = uri.port;
+ if (uri.query) opts.query = uri.query;
+ }
+
+ this.secure = null != opts.secure ? opts.secure :
+ (global.location && 'https:' == location.protocol);
+
+ if (opts.host) {
+ var pieces = opts.host.split(':');
+ opts.hostname = pieces.shift();
+ if (pieces.length) opts.port = pieces.pop();
+ }
+
+ this.agent = opts.agent || false;
+ this.hostname = opts.hostname ||
+ (global.location ? location.hostname : 'localhost');
+ this.port = opts.port || (global.location && location.port ?
+ location.port :
+ (this.secure ? 443 : 80));
+ this.query = opts.query || {};
+ if ('string' == typeof this.query) this.query = parseqs.decode(this.query);
+ this.upgrade = false !== opts.upgrade;
+ this.path = (opts.path || '/engine.io').replace(/\/$/, '') + '/';
+ this.forceJSONP = !!opts.forceJSONP;
+ this.jsonp = false !== opts.jsonp;
+ this.forceBase64 = !!opts.forceBase64;
+ this.enablesXDR = !!opts.enablesXDR;
+ this.timestampParam = opts.timestampParam || 't';
+ this.timestampRequests = opts.timestampRequests;
+ this.transports = opts.transports || ['polling', 'websocket'];
+ this.readyState = '';
+ this.writeBuffer = [];
+ this.callbackBuffer = [];
+ this.policyPort = opts.policyPort || 843;
+ this.rememberUpgrade = opts.rememberUpgrade || false;
+ this.open();
+ this.binaryType = null;
+ this.onlyBinaryUpgrades = opts.onlyBinaryUpgrades;
+}
+
+Socket.priorWebsocketSuccess = false;
+
+/**
+ * Mix in `Emitter`.
+ */
+
+Emitter(Socket.prototype);
+
+/**
+ * Protocol version.
+ *
+ * @api public
+ */
+
+Socket.protocol = parser.protocol; // this is an int
+
+/**
+ * Expose deps for legacy compatibility
+ * and standalone browser access.
+ */
+
+Socket.Socket = Socket;
+Socket.Transport = _dereq_('./transport');
+Socket.transports = _dereq_('./transports');
+Socket.parser = _dereq_('engine.io-parser');
+
+/**
+ * Creates transport of the given type.
+ *
+ * @param {String} transport name
+ * @return {Transport}
+ * @api private
+ */
+
+Socket.prototype.createTransport = function (name) {
+ debug('creating transport "%s"', name);
+ var query = clone(this.query);
+
+ // append engine.io protocol identifier
+ query.EIO = parser.protocol;
+
+ // transport name
+ query.transport = name;
+
+ // session id if we already have one
+ if (this.id) query.sid = this.id;
+
+ var transport = new transports[name]({
+ agent: this.agent,
+ hostname: this.hostname,
+ port: this.port,
+ secure: this.secure,
+ path: this.path,
+ query: query,
+ forceJSONP: this.forceJSONP,
+ jsonp: this.jsonp,
+ forceBase64: this.forceBase64,
+ enablesXDR: this.enablesXDR,
+ timestampRequests: this.timestampRequests,
+ timestampParam: this.timestampParam,
+ policyPort: this.policyPort,
+ socket: this
+ });
+
+ return transport;
+};
+
+function clone (obj) {
+ var o = {};
+ for (var i in obj) {
+ if (obj.hasOwnProperty(i)) {
+ o[i] = obj[i];
+ }
+ }
+ return o;
+}
+
+/**
+ * Initializes transport to use and starts probe.
+ *
+ * @api private
+ */
+Socket.prototype.open = function () {
+ var transport;
+ if (this.rememberUpgrade && Socket.priorWebsocketSuccess && this.transports.indexOf('websocket') != -1) {
+ transport = 'websocket';
+ } else if (0 == this.transports.length) {
+ // Emit error on next tick so it can be listened to
+ var self = this;
+ setTimeout(function() {
+ self.emit('error', 'No transports available');
+ }, 0);
+ return;
+ } else {
+ transport = this.transports[0];
+ }
+ this.readyState = 'opening';
+
+ // Retry with the next transport if the transport is disabled (jsonp: false)
+ var transport;
+ try {
+ transport = this.createTransport(transport);
+ } catch (e) {
+ this.transports.shift();
+ this.open();
+ return;
+ }
+
+ transport.open();
+ this.setTransport(transport);
+};
+
+/**
+ * Sets the current transport. Disables the existing one (if any).
+ *
+ * @api private
+ */
+
+Socket.prototype.setTransport = function(transport){
+ debug('setting transport %s', transport.name);
+ var self = this;
+
+ if (this.transport) {
+ debug('clearing existing transport %s', this.transport.name);
+ this.transport.removeAllListeners();
+ }
+
+ // set up transport
+ this.transport = transport;
+
+ // set up transport listeners
+ transport
+ .on('drain', function(){
+ self.onDrain();
+ })
+ .on('packet', function(packet){
+ self.onPacket(packet);
+ })
+ .on('error', function(e){
+ self.onError(e);
+ })
+ .on('close', function(){
+ self.onClose('transport close');
+ });
+};
+
+/**
+ * Probes a transport.
+ *
+ * @param {String} transport name
+ * @api private
+ */
+
+Socket.prototype.probe = function (name) {
+ debug('probing transport "%s"', name);
+ var transport = this.createTransport(name, { probe: 1 })
+ , failed = false
+ , self = this;
+
+ Socket.priorWebsocketSuccess = false;
+
+ function onTransportOpen(){
+ if (self.onlyBinaryUpgrades) {
+ var upgradeLosesBinary = !this.supportsBinary && self.transport.supportsBinary;
+ failed = failed || upgradeLosesBinary;
+ }
+ if (failed) return;
+
+ debug('probe transport "%s" opened', name);
+ transport.send([{ type: 'ping', data: 'probe' }]);
+ transport.once('packet', function (msg) {
+ if (failed) return;
+ if ('pong' == msg.type && 'probe' == msg.data) {
+ debug('probe transport "%s" pong', name);
+ self.upgrading = true;
+ self.emit('upgrading', transport);
+ Socket.priorWebsocketSuccess = 'websocket' == transport.name;
+
+ debug('pausing current transport "%s"', self.transport.name);
+ self.transport.pause(function () {
+ if (failed) return;
+ if ('closed' == self.readyState || 'closing' == self.readyState) {
+ return;
+ }
+ debug('changing transport and sending upgrade packet');
+
+ cleanup();
+
+ self.setTransport(transport);
+ transport.send([{ type: 'upgrade' }]);
+ self.emit('upgrade', transport);
+ transport = null;
+ self.upgrading = false;
+ self.flush();
+ });
+ } else {
+ debug('probe transport "%s" failed', name);
+ var err = new Error('probe error');
+ err.transport = transport.name;
+ self.emit('upgradeError', err);
+ }
+ });
+ }
+
+ function freezeTransport() {
+ if (failed) return;
+
+ // Any callback called by transport should be ignored since now
+ failed = true;
+
+ cleanup();
+
+ transport.close();
+ transport = null;
+ }
+
+ //Handle any error that happens while probing
+ function onerror(err) {
+ var error = new Error('probe error: ' + err);
+ error.transport = transport.name;
+
+ freezeTransport();
+
+ debug('probe transport "%s" failed because of error: %s', name, err);
+
+ self.emit('upgradeError', error);
+ }
+
+ function onTransportClose(){
+ onerror("transport closed");
+ }
+
+ //When the socket is closed while we're probing
+ function onclose(){
+ onerror("socket closed");
+ }
+
+ //When the socket is upgraded while we're probing
+ function onupgrade(to){
+ if (transport && to.name != transport.name) {
+ debug('"%s" works - aborting "%s"', to.name, transport.name);
+ freezeTransport();
+ }
+ }
+
+ //Remove all listeners on the transport and on self
+ function cleanup(){
+ transport.removeListener('open', onTransportOpen);
+ transport.removeListener('error', onerror);
+ transport.removeListener('close', onTransportClose);
+ self.removeListener('close', onclose);
+ self.removeListener('upgrading', onupgrade);
+ }
+
+ transport.once('open', onTransportOpen);
+ transport.once('error', onerror);
+ transport.once('close', onTransportClose);
+
+ this.once('close', onclose);
+ this.once('upgrading', onupgrade);
+
+ transport.open();
+
+};
+
+/**
+ * Called when connection is deemed open.
+ *
+ * @api public
+ */
+
+Socket.prototype.onOpen = function () {
+ debug('socket open');
+ this.readyState = 'open';
+ Socket.priorWebsocketSuccess = 'websocket' == this.transport.name;
+ this.emit('open');
+ this.flush();
+
+ // we check for `readyState` in case an `open`
+ // listener already closed the socket
+ if ('open' == this.readyState && this.upgrade && this.transport.pause) {
+ debug('starting upgrade probes');
+ for (var i = 0, l = this.upgrades.length; i < l; i++) {
+ this.probe(this.upgrades[i]);
+ }
+ }
+};
+
+/**
+ * Handles a packet.
+ *
+ * @api private
+ */
+
+Socket.prototype.onPacket = function (packet) {
+ if ('opening' == this.readyState || 'open' == this.readyState) {
+ debug('socket receive: type "%s", data "%s"', packet.type, packet.data);
+
+ this.emit('packet', packet);
+
+ // Socket is live - any packet counts
+ this.emit('heartbeat');
+
+ switch (packet.type) {
+ case 'open':
+ this.onHandshake(parsejson(packet.data));
+ break;
+
+ case 'pong':
+ this.setPing();
+ break;
+
+ case 'error':
+ var err = new Error('server error');
+ err.code = packet.data;
+ this.emit('error', err);
+ break;
+
+ case 'message':
+ this.emit('data', packet.data);
+ this.emit('message', packet.data);
+ break;
+ }
+ } else {
+ debug('packet received with socket readyState "%s"', this.readyState);
+ }
+};
+
+/**
+ * Called upon handshake completion.
+ *
+ * @param {Object} handshake obj
+ * @api private
+ */
+
+Socket.prototype.onHandshake = function (data) {
+ this.emit('handshake', data);
+ this.id = data.sid;
+ this.transport.query.sid = data.sid;
+ this.upgrades = this.filterUpgrades(data.upgrades);
+ this.pingInterval = data.pingInterval;
+ this.pingTimeout = data.pingTimeout;
+ this.onOpen();
+ // In case open handler closes socket
+ if ('closed' == this.readyState) return;
+ this.setPing();
+
+ // Prolong liveness of socket on heartbeat
+ this.removeListener('heartbeat', this.onHeartbeat);
+ this.on('heartbeat', this.onHeartbeat);
+};
+
+/**
+ * Resets ping timeout.
+ *
+ * @api private
+ */
+
+Socket.prototype.onHeartbeat = function (timeout) {
+ clearTimeout(this.pingTimeoutTimer);
+ var self = this;
+ self.pingTimeoutTimer = setTimeout(function () {
+ if ('closed' == self.readyState) return;
+ self.onClose('ping timeout');
+ }, timeout || (self.pingInterval + self.pingTimeout));
+};
+
+/**
+ * Pings server every `this.pingInterval` and expects response
+ * within `this.pingTimeout` or closes connection.
+ *
+ * @api private
+ */
+
+Socket.prototype.setPing = function () {
+ var self = this;
+ clearTimeout(self.pingIntervalTimer);
+ self.pingIntervalTimer = setTimeout(function () {
+ debug('writing ping packet - expecting pong within %sms', self.pingTimeout);
+ self.ping();
+ self.onHeartbeat(self.pingTimeout);
+ }, self.pingInterval);
+};
+
+/**
+* Sends a ping packet.
+*
+* @api public
+*/
+
+Socket.prototype.ping = function () {
+ this.sendPacket('ping');
+};
+
+/**
+ * Called on `drain` event
+ *
+ * @api private
+ */
+
+Socket.prototype.onDrain = function() {
+ for (var i = 0; i < this.prevBufferLen; i++) {
+ if (this.callbackBuffer[i]) {
+ this.callbackBuffer[i]();
+ }
+ }
+
+ this.writeBuffer.splice(0, this.prevBufferLen);
+ this.callbackBuffer.splice(0, this.prevBufferLen);
+
+ // setting prevBufferLen = 0 is very important
+ // for example, when upgrading, upgrade packet is sent over,
+ // and a nonzero prevBufferLen could cause problems on `drain`
+ this.prevBufferLen = 0;
+
+ if (this.writeBuffer.length == 0) {
+ this.emit('drain');
+ } else {
+ this.flush();
+ }
+};
+
+/**
+ * Flush write buffers.
+ *
+ * @api private
+ */
+
+Socket.prototype.flush = function () {
+ if ('closed' != this.readyState && this.transport.writable &&
+ !this.upgrading && this.writeBuffer.length) {
+ debug('flushing %d packets in socket', this.writeBuffer.length);
+ this.transport.send(this.writeBuffer);
+ // keep track of current length of writeBuffer
+ // splice writeBuffer and callbackBuffer on `drain`
+ this.prevBufferLen = this.writeBuffer.length;
+ this.emit('flush');
+ }
+};
+
+/**
+ * Sends a message.
+ *
+ * @param {String} message.
+ * @param {Function} callback function.
+ * @return {Socket} for chaining.
+ * @api public
+ */
+
+Socket.prototype.write =
+Socket.prototype.send = function (msg, fn) {
+ this.sendPacket('message', msg, fn);
+ return this;
+};
+
+/**
+ * Sends a packet.
+ *
+ * @param {String} packet type.
+ * @param {String} data.
+ * @param {Function} callback function.
+ * @api private
+ */
+
+Socket.prototype.sendPacket = function (type, data, fn) {
+ var packet = { type: type, data: data };
+ this.emit('packetCreate', packet);
+ this.writeBuffer.push(packet);
+ this.callbackBuffer.push(fn);
+ this.flush();
+};
+
+/**
+ * Closes the connection.
+ *
+ * @api private
+ */
+
+Socket.prototype.close = function () {
+ if ('opening' == this.readyState || 'open' == this.readyState) {
+ this.onClose('forced close');
+ debug('socket closing - telling transport to close');
+ this.transport.close();
+ }
+
+ return this;
+};
+
+/**
+ * Called upon transport error
+ *
+ * @api private
+ */
+
+Socket.prototype.onError = function (err) {
+ debug('socket error %j', err);
+ Socket.priorWebsocketSuccess = false;
+ this.emit('error', err);
+ this.onClose('transport error', err);
+};
+
+/**
+ * Called upon transport close.
+ *
+ * @api private
+ */
+
+Socket.prototype.onClose = function (reason, desc) {
+ if ('opening' == this.readyState || 'open' == this.readyState) {
+ debug('socket close with reason: "%s"', reason);
+ var self = this;
+
+ // clear timers
+ clearTimeout(this.pingIntervalTimer);
+ clearTimeout(this.pingTimeoutTimer);
+
+ // clean buffers in next tick, so developers can still
+ // grab the buffers on `close` event
+ setTimeout(function() {
+ self.writeBuffer = [];
+ self.callbackBuffer = [];
+ self.prevBufferLen = 0;
+ }, 0);
+
+ // stop event from firing again for transport
+ this.transport.removeAllListeners('close');
+
+ // ensure transport won't stay open
+ this.transport.close();
+
+ // ignore further transport communication
+ this.transport.removeAllListeners();
+
+ // set ready state
+ this.readyState = 'closed';
+
+ // clear session id
+ this.id = null;
+
+ // emit close event
+ this.emit('close', reason, desc);
+ }
+};
+
+/**
+ * Filters upgrades, returning only those matching client transports.
+ *
+ * @param {Array} server upgrades
+ * @api private
+ *
+ */
+
+Socket.prototype.filterUpgrades = function (upgrades) {
+ var filteredUpgrades = [];
+ for (var i = 0, j = upgrades.length; i<j; i++) {
+ if (~index(this.transports, upgrades[i])) filteredUpgrades.push(upgrades[i]);
+ }
+ return filteredUpgrades;
+};
+
+}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./transport":4,"./transports":5,"component-emitter":12,"debug":14,"engine.io-parser":15,"indexof":23,"parsejson":24,"parseqs":25,"parseuri":26}],4:[function(_dereq_,module,exports){
+/**
+ * Module dependencies.
+ */
+
+var parser = _dereq_('engine.io-parser');
+var Emitter = _dereq_('component-emitter');
+
+/**
+ * Module exports.
+ */
+
+module.exports = Transport;
+
+/**
+ * Transport abstract constructor.
+ *
+ * @param {Object} options.
+ * @api private
+ */
+
+function Transport (opts) {
+ this.path = opts.path;
+ this.hostname = opts.hostname;
+ this.port = opts.port;
+ this.secure = opts.secure;
+ this.query = opts.query;
+ this.timestampParam = opts.timestampParam;
+ this.timestampRequests = opts.timestampRequests;
+ this.readyState = '';
+ this.agent = opts.agent || false;
+ this.socket = opts.socket;
+ this.enablesXDR = opts.enablesXDR;
+}
+
+/**
+ * Mix in `Emitter`.
+ */
+
+Emitter(Transport.prototype);
+
+/**
+ * A counter used to prevent collisions in the timestamps used
+ * for cache busting.
+ */
+
+Transport.timestamps = 0;
+
+/**
+ * Emits an error.
+ *
+ * @param {String} str
+ * @return {Transport} for chaining
+ * @api public
+ */
+
+Transport.prototype.onError = function (msg, desc) {
+ var err = new Error(msg);
+ err.type = 'TransportError';
+ err.description = desc;
+ this.emit('error', err);
+ return this;
+};
+
+/**
+ * Opens the transport.
+ *
+ * @api public
+ */
+
+Transport.prototype.open = function () {
+ if ('closed' == this.readyState || '' == this.readyState) {
+ this.readyState = 'opening';
+ this.doOpen();
+ }
+
+ return this;
+};
+
+/**
+ * Closes the transport.
+ *
+ * @api private
+ */
+
+Transport.prototype.close = function () {
+ if ('opening' == this.readyState || 'open' == this.readyState) {
+ this.doClose();
+ this.onClose();
+ }
+
+ return this;
+};
+
+/**
+ * Sends multiple packets.
+ *
+ * @param {Array} packets
+ * @api private
+ */
+
+Transport.prototype.send = function(packets){
+ if ('open' == this.readyState) {
+ this.write(packets);
+ } else {
+ throw new Error('Transport not open');
+ }
+};
+
+/**
+ * Called upon open
+ *
+ * @api private
+ */
+
+Transport.prototype.onOpen = function () {
+ this.readyState = 'open';
+ this.writable = true;
+ this.emit('open');
+};
+
+/**
+ * Called with data.
+ *
+ * @param {String} data
+ * @api private
+ */
+
+Transport.prototype.onData = function(data){
+ var packet = parser.decodePacket(data, this.socket.binaryType);
+ this.onPacket(packet);
+};
+
+/**
+ * Called with a decoded packet.
+ */
+
+Transport.prototype.onPacket = function (packet) {
+ this.emit('packet', packet);
+};
+
+/**
+ * Called upon close.
+ *
+ * @api private
+ */
+
+Transport.prototype.onClose = function () {
+ this.readyState = 'closed';
+ this.emit('close');
+};
+
+},{"component-emitter":12,"engine.io-parser":15}],5:[function(_dereq_,module,exports){
+(function (global){
+/**
+ * Module dependencies
+ */
+
+var XMLHttpRequest = _dereq_('xmlhttprequest');
+var XHR = _dereq_('./polling-xhr');
+var JSONP = _dereq_('./polling-jsonp');
+var websocket = _dereq_('./websocket');
+
+/**
+ * Export transports.
+ */
+
+exports.polling = polling;
+exports.websocket = websocket;
+
+/**
+ * Polling transport polymorphic constructor.
+ * Decides on xhr vs jsonp based on feature detection.
+ *
+ * @api private
+ */
+
+function polling(opts){
+ var xhr;
+ var xd = false;
+ var xs = false;
+ var jsonp = false !== opts.jsonp;
+
+ if (global.location) {
+ var isSSL = 'https:' == location.protocol;
+ var port = location.port;
+
+ // some user agents have empty `location.port`
+ if (!port) {
+ port = isSSL ? 443 : 80;
+ }
+
+ xd = opts.hostname != location.hostname || port != opts.port;
+ xs = opts.secure != isSSL;
+ }
+
+ opts.xdomain = xd;
+ opts.xscheme = xs;
+ xhr = new XMLHttpRequest(opts);
+
+ if ('open' in xhr && !opts.forceJSONP) {
+ return new XHR(opts);
+ } else {
+ if (!jsonp) throw new Error('JSONP disabled');
+ return new JSONP(opts);
+ }
+}
+
+}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./polling-jsonp":6,"./polling-xhr":7,"./websocket":9,"xmlhttprequest":10}],6:[function(_dereq_,module,exports){
+(function (global){
+
+/**
+ * Module requirements.
+ */
+
+var Polling = _dereq_('./polling');
+var inherit = _dereq_('component-inherit');
+
+/**
+ * Module exports.
+ */
+
+module.exports = JSONPPolling;
+
+/**
+ * Cached regular expressions.
+ */
+
+var rNewline = /\n/g;
+var rEscapedNewline = /\\n/g;
+
+/**
+ * Global JSONP callbacks.
+ */
+
+var callbacks;
+
+/**
+ * Callbacks count.
+ */
+
+var index = 0;
+
+/**
+ * Noop.
+ */
+
+function empty () { }
+
+/**
+ * JSONP Polling constructor.
+ *
+ * @param {Object} opts.
+ * @api public
+ */
+
+function JSONPPolling (opts) {
+ Polling.call(this, opts);
+
+ this.query = this.query || {};
+
+ // define global callbacks array if not present
+ // we do this here (lazily) to avoid unneeded global pollution
+ if (!callbacks) {
+ // we need to consider multiple engines in the same page
+ if (!global.___eio) global.___eio = [];
+ callbacks = global.___eio;
+ }
+
+ // callback identifier
+ this.index = callbacks.length;
+
+ // add callback to jsonp global
+ var self = this;
+ callbacks.push(function (msg) {
+ self.onData(msg);
+ });
+
+ // append to query string
+ this.query.j = this.index;
+
+ // prevent spurious errors from being emitted when the window is unloaded
+ if (global.document && global.addEventListener) {
+ global.addEventListener('beforeunload', function () {
+ if (self.script) self.script.onerror = empty;
+ });
+ }
+}
+
+/**
+ * Inherits from Polling.
+ */
+
+inherit(JSONPPolling, Polling);
+
+/*
+ * JSONP only supports binary as base64 encoded strings
+ */
+
+JSONPPolling.prototype.supportsBinary = false;
+
+/**
+ * Closes the socket.
+ *
+ * @api private
+ */
+
+JSONPPolling.prototype.doClose = function () {
+ if (this.script) {
+ this.script.parentNode.removeChild(this.script);
+ this.script = null;
+ }
+
+ if (this.form) {
+ this.form.parentNode.removeChild(this.form);
+ this.form = null;
+ }
+
+ Polling.prototype.doClose.call(this);
+};
+
+/**
+ * Starts a poll cycle.
+ *
+ * @api private
+ */
+
+JSONPPolling.prototype.doPoll = function () {
+ var self = this;
+ var script = document.createElement('script');
+
+ if (this.script) {
+ this.script.parentNode.removeChild(this.script);
+ this.script = null;
+ }
+
+ script.async = true;
+ script.src = this.uri();
+ script.onerror = function(e){
+ self.onError('jsonp poll error',e);
+ };
+
+ var insertAt = document.getElementsByTagName('script')[0];
+ insertAt.parentNode.insertBefore(script, insertAt);
+ this.script = script;
+
+ var isUAgecko = 'undefined' != typeof navigator && /gecko/i.test(navigator.userAgent);
+
+ if (isUAgecko) {
+ setTimeout(function () {
+ var iframe = document.createElement('iframe');
+ document.body.appendChild(iframe);
+ document.body.removeChild(iframe);
+ }, 100);
+ }
+};
+
+/**
+ * Writes with a hidden iframe.
+ *
+ * @param {String} data to send
+ * @param {Function} called upon flush.
+ * @api private
+ */
+
+JSONPPolling.prototype.doWrite = function (data, fn) {
+ var self = this;
+
+ if (!this.form) {
+ var form = document.createElement('form');
+ var area = document.createElement('textarea');
+ var id = this.iframeId = 'eio_iframe_' + this.index;
+ var iframe;
+
+ form.className = 'socketio';
+ form.style.position = 'absolute';
+ form.style.top = '-1000px';
+ form.style.left = '-1000px';
+ form.target = id;
+ form.method = 'POST';
+ form.setAttribute('accept-charset', 'utf-8');
+ area.name = 'd';
+ form.appendChild(area);
+ document.body.appendChild(form);
+
+ this.form = form;
+ this.area = area;
+ }
+
+ this.form.action = this.uri();
+
+ function complete () {
+ initIframe();
+ fn();
+ }
+
+ function initIframe () {
+ if (self.iframe) {
+ try {
+ self.form.removeChild(self.iframe);
+ } catch (e) {
+ self.onError('jsonp polling iframe removal error', e);
+ }
+ }
+
+ try {
+ // ie6 dynamic iframes with target="" support (thanks Chris Lambacher)
+ var html = '<iframe src="javascript:0" name="'+ self.iframeId +'">';
+ iframe = document.createElement(html);
+ } catch (e) {
+ iframe = document.createElement('iframe');
+ iframe.name = self.iframeId;
+ iframe.src = 'javascript:0';
+ }
+
+ iframe.id = self.iframeId;
+
+ self.form.appendChild(iframe);
+ self.iframe = iframe;
+ }
+
+ initIframe();
+
+ // escape \n to prevent it from being converted into \r\n by some UAs
+ // double escaping is required for escaped new lines because unescaping of new lines can be done safely on server-side
+ data = data.replace(rEscapedNewline, '\\\n');
+ this.area.value = data.replace(rNewline, '\\n');
+
+ try {
+ this.form.submit();
+ } catch(e) {}
+
+ if (this.iframe.attachEvent) {
+ this.iframe.onreadystatechange = function(){
+ if (self.iframe.readyState == 'complete') {
+ complete();
+ }
+ };
+ } else {
+ this.iframe.onload = complete;
+ }
+};
+
+}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./polling":8,"component-inherit":13}],7:[function(_dereq_,module,exports){
+(function (global){
+/**
+ * Module requirements.
+ */
+
+var XMLHttpRequest = _dereq_('xmlhttprequest');
+var Polling = _dereq_('./polling');
+var Emitter = _dereq_('component-emitter');
+var inherit = _dereq_('component-inherit');
+var debug = _dereq_('debug')('engine.io-client:polling-xhr');
+
+/**
+ * Module exports.
+ */
+
+module.exports = XHR;
+module.exports.Request = Request;
+
+/**
+ * Empty function
+ */
+
+function empty(){}
+
+/**
+ * XHR Polling constructor.
+ *
+ * @param {Object} opts
+ * @api public
+ */
+
+function XHR(opts){
+ Polling.call(this, opts);
+
+ if (global.location) {
+ var isSSL = 'https:' == location.protocol;
+ var port = location.port;
+
+ // some user agents have empty `location.port`
+ if (!port) {
+ port = isSSL ? 443 : 80;
+ }
+
+ this.xd = opts.hostname != global.location.hostname ||
+ port != opts.port;
+ this.xs = opts.secure != isSSL;
+ }
+}
+
+/**
+ * Inherits from Polling.
+ */
+
+inherit(XHR, Polling);
+
+/**
+ * XHR supports binary
+ */
+
+XHR.prototype.supportsBinary = true;
+
+/**
+ * Creates a request.
+ *
+ * @param {String} method
+ * @api private
+ */
+
+XHR.prototype.request = function(opts){
+ opts = opts || {};
+ opts.uri = this.uri();
+ opts.xd = this.xd;
+ opts.xs = this.xs;
+ opts.agent = this.agent || false;
+ opts.supportsBinary = this.supportsBinary;
+ opts.enablesXDR = this.enablesXDR;
+ return new Request(opts);
+};
+
+/**
+ * Sends data.
+ *
+ * @param {String} data to send.
+ * @param {Function} called upon flush.
+ * @api private
+ */
+
+XHR.prototype.doWrite = function(data, fn){
+ var isBinary = typeof data !== 'string' && data !== undefined;
+ var req = this.request({ method: 'POST', data: data, isBinary: isBinary });
+ var self = this;
+ req.on('success', fn);
+ req.on('error', function(err){
+ self.onError('xhr post error', err);
+ });
+ this.sendXhr = req;
+};
+
+/**
+ * Starts a poll cycle.
+ *
+ * @api private
+ */
+
+XHR.prototype.doPoll = function(){
+ debug('xhr poll');
+ var req = this.request();
+ var self = this;
+ req.on('data', function(data){
+ self.onData(data);
+ });
+ req.on('error', function(err){
+ self.onError('xhr poll error', err);
+ });
+ this.pollXhr = req;
+};
+
+/**
+ * Request constructor
+ *
+ * @param {Object} options
+ * @api public
+ */
+
+function Request(opts){
+ this.method = opts.method || 'GET';
+ this.uri = opts.uri;
+ this.xd = !!opts.xd;
+ this.xs = !!opts.xs;
+ this.async = false !== opts.async;
+ this.data = undefined != opts.data ? opts.data : null;
+ this.agent = opts.agent;
+ this.isBinary = opts.isBinary;
+ this.supportsBinary = opts.supportsBinary;
+ this.enablesXDR = opts.enablesXDR;
+ this.create();
+}
+
+/**
+ * Mix in `Emitter`.
+ */
+
+Emitter(Request.prototype);
+
+/**
+ * Creates the XHR object and sends the request.
+ *
+ * @api private
+ */
+
+Request.prototype.create = function(){
+ var xhr = this.xhr = new XMLHttpRequest({ agent: this.agent, xdomain: this.xd, xscheme: this.xs, enablesXDR: this.enablesXDR });
+ var self = this;
+
+ try {
+ debug('xhr open %s: %s', this.method, this.uri);
+ xhr.open(this.method, this.uri, this.async);
+ if (this.supportsBinary) {
+ // This has to be done after open because Firefox is stupid
+ // http://stackoverflow.com/questions/13216903/get-binary-data-with-xmlhttprequest-in-a-firefox-extension
+ xhr.responseType = 'arraybuffer';
+ }
+
+ if ('POST' == this.method) {
+ try {
+ if (this.isBinary) {
+ xhr.setRequestHeader('Content-type', 'application/octet-stream');
+ } else {
+ xhr.setRequestHeader('Content-type', 'text/plain;charset=UTF-8');
+ }
+ } catch (e) {}
+ }
+
+ // ie6 check
+ if ('withCredentials' in xhr) {
+ xhr.withCredentials = true;
+ }
+
+ if (this.hasXDR()) {
+ xhr.onload = function(){
+ self.onLoad();
+ };
+ xhr.onerror = function(){
+ self.onError(xhr.responseText);
+ };
+ } else {
+ xhr.onreadystatechange = function(){
+ if (4 != xhr.readyState) return;
+ if (200 == xhr.status || 1223 == xhr.status) {
+ self.onLoad();
+ } else {
+ // make sure the `error` event handler that's user-set
+ // does not throw in the same tick and gets caught here
+ setTimeout(function(){
+ self.onError(xhr.status);
+ }, 0);
+ }
+ };
+ }
+
+ debug('xhr data %s', this.data);
+ xhr.send(this.data);
+ } catch (e) {
+ // Need to defer since .create() is called directly fhrom the constructor
+ // and thus the 'error' event can only be only bound *after* this exception
+ // occurs. Therefore, also, we cannot throw here at all.
+ setTimeout(function() {
+ self.onError(e);
+ }, 0);
+ return;
+ }
+
+ if (global.document) {
+ this.index = Request.requestsCount++;
+ Request.requests[this.index] = this;
+ }
+};
+
+/**
+ * Called upon successful response.
+ *
+ * @api private
+ */
+
+Request.prototype.onSuccess = function(){
+ this.emit('success');
+ this.cleanup();
+};
+
+/**
+ * Called if we have data.
+ *
+ * @api private
+ */
+
+Request.prototype.onData = function(data){
+ this.emit('data', data);
+ this.onSuccess();
+};
+
+/**
+ * Called upon error.
+ *
+ * @api private
+ */
+
+Request.prototype.onError = function(err){
+ this.emit('error', err);
+ this.cleanup();
+};
+
+/**
+ * Cleans up house.
+ *
+ * @api private
+ */
+
+Request.prototype.cleanup = function(){
+ if ('undefined' == typeof this.xhr || null === this.xhr) {
+ return;
+ }
+ // xmlhttprequest
+ if (this.hasXDR()) {
+ this.xhr.onload = this.xhr.onerror = empty;
+ } else {
+ this.xhr.onreadystatechange = empty;
+ }
+
+ try {
+ this.xhr.abort();
+ } catch(e) {}
+
+ if (global.document) {
+ delete Request.requests[this.index];
+ }
+
+ this.xhr = null;
+};
+
+/**
+ * Called upon load.
+ *
+ * @api private
+ */
+
+Request.prototype.onLoad = function(){
+ var data;
+ try {
+ var contentType;
+ try {
+ contentType = this.xhr.getResponseHeader('Content-Type');
+ } catch (e) {}
+ if (contentType === 'application/octet-stream') {
+ data = this.xhr.response;
+ } else {
+ if (!this.supportsBinary) {
+ data = this.xhr.responseText;
+ } else {
+ data = 'ok';
+ }
+ }
+ } catch (e) {
+ this.onError(e);
+ }
+ if (null != data) {
+ this.onData(data);
+ }
+};
+
+/**
+ * Check if it has XDomainRequest.
+ *
+ * @api private
+ */
+
+Request.prototype.hasXDR = function(){
+ return 'undefined' !== typeof global.XDomainRequest && !this.xs && this.enablesXDR;
+};
+
+/**
+ * Aborts the request.
+ *
+ * @api public
+ */
+
+Request.prototype.abort = function(){
+ this.cleanup();
+};
+
+/**
+ * Aborts pending requests when unloading the window. This is needed to prevent
+ * memory leaks (e.g. when using IE) and to ensure that no spurious error is
+ * emitted.
+ */
+
+if (global.document) {
+ Request.requestsCount = 0;
+ Request.requests = {};
+ if (global.attachEvent) {
+ global.attachEvent('onunload', unloadHandler);
+ } else if (global.addEventListener) {
+ global.addEventListener('beforeunload', unloadHandler);
+ }
+}
+
+function unloadHandler() {
+ for (var i in Request.requests) {
+ if (Request.requests.hasOwnProperty(i)) {
+ Request.requests[i].abort();
+ }
+ }
+}
+
+}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./polling":8,"component-emitter":12,"component-inherit":13,"debug":14,"xmlhttprequest":10}],8:[function(_dereq_,module,exports){
+/**
+ * Module dependencies.
+ */
+
+var Transport = _dereq_('../transport');
+var parseqs = _dereq_('parseqs');
+var parser = _dereq_('engine.io-parser');
+var inherit = _dereq_('component-inherit');
+var debug = _dereq_('debug')('engine.io-client:polling');
+
+/**
+ * Module exports.
+ */
+
+module.exports = Polling;
+
+/**
+ * Is XHR2 supported?
+ */
+
+var hasXHR2 = (function() {
+ var XMLHttpRequest = _dereq_('xmlhttprequest');
+ var xhr = new XMLHttpRequest({ agent: this.agent, xdomain: false });
+ return null != xhr.responseType;
+})();
+
+/**
+ * Polling interface.
+ *
+ * @param {Object} opts
+ * @api private
+ */
+
+function Polling(opts){
+ var forceBase64 = (opts && opts.forceBase64);
+ if (!hasXHR2 || forceBase64) {
+ this.supportsBinary = false;
+ }
+ Transport.call(this, opts);
+}
+
+/**
+ * Inherits from Transport.
+ */
+
+inherit(Polling, Transport);
+
+/**
+ * Transport name.
+ */
+
+Polling.prototype.name = 'polling';
+
+/**
+ * Opens the socket (triggers polling). We write a PING message to determine
+ * when the transport is open.
+ *
+ * @api private
+ */
+
+Polling.prototype.doOpen = function(){
+ this.poll();
+};
+
+/**
+ * Pauses polling.
+ *
+ * @param {Function} callback upon buffers are flushed and transport is paused
+ * @api private
+ */
+
+Polling.prototype.pause = function(onPause){
+ var pending = 0;
+ var self = this;
+
+ this.readyState = 'pausing';
+
+ function pause(){
+ debug('paused');
+ self.readyState = 'paused';
+ onPause();
+ }
+
+ if (this.polling || !this.writable) {
+ var total = 0;
+
+ if (this.polling) {
+ debug('we are currently polling - waiting to pause');
+ total++;
+ this.once('pollComplete', function(){
+ debug('pre-pause polling complete');
+ --total || pause();
+ });
+ }
+
+ if (!this.writable) {
+ debug('we are currently writing - waiting to pause');
+ total++;
+ this.once('drain', function(){
+ debug('pre-pause writing complete');
+ --total || pause();
+ });
+ }
+ } else {
+ pause();
+ }
+};
+
+/**
+ * Starts polling cycle.
+ *
+ * @api public
+ */
+
+Polling.prototype.poll = function(){
+ debug('polling');
+ this.polling = true;
+ this.doPoll();
+ this.emit('poll');
+};
+
+/**
+ * Overloads onData to detect payloads.
+ *
+ * @api private
+ */
+
+Polling.prototype.onData = function(data){
+ var self = this;
+ debug('polling got data %s', data);
+ var callback = function(packet, index, total) {
+ // if its the first message we consider the transport open
+ if ('opening' == self.readyState) {
+ self.onOpen();
+ }
+
+ // if its a close packet, we close the ongoing requests
+ if ('close' == packet.type) {
+ self.onClose();
+ return false;
+ }
+
+ // otherwise bypass onData and handle the message
+ self.onPacket(packet);
+ };
+
+ // decode payload
+ parser.decodePayload(data, this.socket.binaryType, callback);
+
+ // if an event did not trigger closing
+ if ('closed' != this.readyState) {
+ // if we got data we're not polling
+ this.polling = false;
+ this.emit('pollComplete');
+
+ if ('open' == this.readyState) {
+ this.poll();
+ } else {
+ debug('ignoring poll - transport state "%s"', this.readyState);
+ }
+ }
+};
+
+/**
+ * For polling, send a close packet.
+ *
+ * @api private
+ */
+
+Polling.prototype.doClose = function(){
+ var self = this;
+
+ function close(){
+ debug('writing close packet');
+ self.write([{ type: 'close' }]);
+ }
+
+ if ('open' == this.readyState) {
+ debug('transport open - closing');
+ close();
+ } else {
+ // in case we're trying to close while
+ // handshaking is in progress (GH-164)
+ debug('transport not open - deferring close');
+ this.once('open', close);
+ }
+};
+
+/**
+ * Writes a packets payload.
+ *
+ * @param {Array} data packets
+ * @param {Function} drain callback
+ * @api private
+ */
+
+Polling.prototype.write = function(packets){
+ var self = this;
+ this.writable = false;
+ var callbackfn = function() {
+ self.writable = true;
+ self.emit('drain');
+ };
+
+ var self = this;
+ parser.encodePayload(packets, this.supportsBinary, function(data) {
+ self.doWrite(data, callbackfn);
+ });
+};
+
+/**
+ * Generates uri for connection.
+ *
+ * @api private
+ */
+
+Polling.prototype.uri = function(){
+ var query = this.query || {};
+ var schema = this.secure ? 'https' : 'http';
+ var port = '';
+
+ // cache busting is forced
+ if (false !== this.timestampRequests) {
+ query[this.timestampParam] = +new Date + '-' + Transport.timestamps++;
+ }
+
+ if (!this.supportsBinary && !query.sid) {
+ query.b64 = 1;
+ }
+
+ query = parseqs.encode(query);
+
+ // avoid port if default for schema
+ if (this.port && (('https' == schema && this.port != 443) ||
+ ('http' == schema && this.port != 80))) {
+ port = ':' + this.port;
+ }
+
+ // prepend ? to query
+ if (query.length) {
+ query = '?' + query;
+ }
+
+ return schema + '://' + this.hostname + port + this.path + query;
+};
+
+},{"../transport":4,"component-inherit":13,"debug":14,"engine.io-parser":15,"parseqs":25,"xmlhttprequest":10}],9:[function(_dereq_,module,exports){
+/**
+ * Module dependencies.
+ */
+
+var Transport = _dereq_('../transport');
+var parser = _dereq_('engine.io-parser');
+var parseqs = _dereq_('parseqs');
+var inherit = _dereq_('component-inherit');
+var debug = _dereq_('debug')('engine.io-client:websocket');
+
+/**
+ * `ws` exposes a WebSocket-compatible interface in
+ * Node, or the `WebSocket` or `MozWebSocket` globals
+ * in the browser.
+ */
+
+var WebSocket = _dereq_('ws');
+
+/**
+ * Module exports.
+ */
+
+module.exports = WS;
+
+/**
+ * WebSocket transport constructor.
+ *
+ * @api {Object} connection options
+ * @api public
+ */
+
+function WS(opts){
+ var forceBase64 = (opts && opts.forceBase64);
+ if (forceBase64) {
+ this.supportsBinary = false;
+ }
+ Transport.call(this, opts);
+}
+
+/**
+ * Inherits from Transport.
+ */
+
+inherit(WS, Transport);
+
+/**
+ * Transport name.
+ *
+ * @api public
+ */
+
+WS.prototype.name = 'websocket';
+
+/*
+ * WebSockets support binary
+ */
+
+WS.prototype.supportsBinary = true;
+
+/**
+ * Opens socket.
+ *
+ * @api private
+ */
+
+WS.prototype.doOpen = function(){
+ if (!this.check()) {
+ // let probe timeout
+ return;
+ }
+
+ var self = this;
+ var uri = this.uri();
+ var protocols = void(0);
+ var opts = { agent: this.agent };
+
+ this.ws = new WebSocket(uri, protocols, opts);
+
+ if (this.ws.binaryType === undefined) {
+ this.supportsBinary = false;
+ }
+
+ this.ws.binaryType = 'arraybuffer';
+ this.addEventListeners();
+};
+
+/**
+ * Adds event listeners to the socket
+ *
+ * @api private
+ */
+
+WS.prototype.addEventListeners = function(){
+ var self = this;
+
+ this.ws.onopen = function(){
+ self.onOpen();
+ };
+ this.ws.onclose = function(){
+ self.onClose();
+ };
+ this.ws.onmessage = function(ev){
+ self.onData(ev.data);
+ };
+ this.ws.onerror = function(e){
+ self.onError('websocket error', e);
+ };
+};
+
+/**
+ * Override `onData` to use a timer on iOS.
+ * See: https://gist.github.com/mloughran/2052006
+ *
+ * @api private
+ */
+
+if ('undefined' != typeof navigator
+ && /iPad|iPhone|iPod/i.test(navigator.userAgent)) {
+ WS.prototype.onData = function(data){
+ var self = this;
+ setTimeout(function(){
+ Transport.prototype.onData.call(self, data);
+ }, 0);
+ };
+}
+
+/**
+ * Writes data to socket.
+ *
+ * @param {Array} array of packets.
+ * @api private
+ */
+
+WS.prototype.write = function(packets){
+ var self = this;
+ this.writable = false;
+ // encodePacket efficient as it uses WS framing
+ // no need for encodePayload
+ for (var i = 0, l = packets.length; i < l; i++) {
+ parser.encodePacket(packets[i], this.supportsBinary, function(data) {
+ //Sometimes the websocket has already been closed but the browser didn't
+ //have a chance of informing us about it yet, in that case send will
+ //throw an error
+ try {
+ self.ws.send(data);
+ } catch (e){
+ debug('websocket closed before onclose event');
+ }
+ });
+ }
+
+ function ondrain() {
+ self.writable = true;
+ self.emit('drain');
+ }
+ // fake drain
+ // defer to next tick to allow Socket to clear writeBuffer
+ setTimeout(ondrain, 0);
+};
+
+/**
+ * Called upon close
+ *
+ * @api private
+ */
+
+WS.prototype.onClose = function(){
+ Transport.prototype.onClose.call(this);
+};
+
+/**
+ * Closes socket.
+ *
+ * @api private
+ */
+
+WS.prototype.doClose = function(){
+ if (typeof this.ws !== 'undefined') {
+ this.ws.close();
+ }
+};
+
+/**
+ * Generates uri for connection.
+ *
+ * @api private
+ */
+
+WS.prototype.uri = function(){
+ var query = this.query || {};
+ var schema = this.secure ? 'wss' : 'ws';
+ var port = '';
+
+ // avoid port if default for schema
+ if (this.port && (('wss' == schema && this.port != 443)
+ || ('ws' == schema && this.port != 80))) {
+ port = ':' + this.port;
+ }
+
+ // append timestamp to URI
+ if (this.timestampRequests) {
+ query[this.timestampParam] = +new Date;
+ }
+
+ // communicate binary support capabilities
+ if (!this.supportsBinary) {
+ query.b64 = 1;
+ }
+
+ query = parseqs.encode(query);
+
+ // prepend ? to query
+ if (query.length) {
+ query = '?' + query;
+ }
+
+ return schema + '://' + this.hostname + port + this.path + query;
+};
+
+/**
+ * Feature detection for WebSocket.
+ *
+ * @return {Boolean} whether this transport is available.
+ * @api public
+ */
+
+WS.prototype.check = function(){
+ return !!WebSocket && !('__initialize' in WebSocket && this.name === WS.prototype.name);
+};
+
+},{"../transport":4,"component-inherit":13,"debug":14,"engine.io-parser":15,"parseqs":25,"ws":27}],10:[function(_dereq_,module,exports){
+// browser shim for xmlhttprequest module
+var hasCORS = _dereq_('has-cors');
+
+module.exports = function(opts) {
+ var xdomain = opts.xdomain;
+
+ // scheme must be same when usign XDomainRequest
+ // http://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx
+ var xscheme = opts.xscheme;
+
+ // XDomainRequest has a flow of not sending cookie, therefore it should be disabled as a default.
+ // https://github.com/Automattic/engine.io-client/pull/217
+ var enablesXDR = opts.enablesXDR;
+
+ // Use XDomainRequest for IE8 if enablesXDR is true
+ // because loading bar keeps flashing when using jsonp-polling
+ // https://github.com/yujiosaka/socke.io-ie8-loading-example
+ try {
+ if ('undefined' != typeof XDomainRequest && !xscheme && enablesXDR) {
+ return new XDomainRequest();
+ }
+ } catch (e) { }
+
+ // XMLHttpRequest can be disabled on IE
+ try {
+ if ('undefined' != typeof XMLHttpRequest && (!xdomain || hasCORS)) {
+ return new XMLHttpRequest();
+ }
+ } catch (e) { }
+
+ if (!xdomain) {
+ try {
+ return new ActiveXObject('Microsoft.XMLHTTP');
+ } catch(e) { }
+ }
+}
+
+},{"has-cors":21}],11:[function(_dereq_,module,exports){
+(function (global){
+/**
+ * Create a blob builder even when vendor prefixes exist
+ */
+
+var BlobBuilder = global.BlobBuilder
+ || global.WebKitBlobBuilder
+ || global.MSBlobBuilder
+ || global.MozBlobBuilder;
+
+/**
+ * Check if Blob constructor is supported
+ */
+
+var blobSupported = (function() {
+ try {
+ var b = new Blob(['hi']);
+ return b.size == 2;
+ } catch(e) {
+ return false;
+ }
+})();
+
+/**
+ * Check if BlobBuilder is supported
+ */
+
+var blobBuilderSupported = BlobBuilder
+ && BlobBuilder.prototype.append
+ && BlobBuilder.prototype.getBlob;
+
+function BlobBuilderConstructor(ary, options) {
+ options = options || {};
+
+ var bb = new BlobBuilder();
+ for (var i = 0; i < ary.length; i++) {
+ bb.append(ary[i]);
+ }
+ return (options.type) ? bb.getBlob(options.type) : bb.getBlob();
+};
+
+module.exports = (function() {
+ if (blobSupported) {
+ return global.Blob;
+ } else if (blobBuilderSupported) {
+ return BlobBuilderConstructor;
+ } else {
+ return undefined;
+ }
+})();
+
+}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{}],12:[function(_dereq_,module,exports){
+
+/**
+ * Expose `Emitter`.
+ */
+
+module.exports = Emitter;
+
+/**
+ * Initialize a new `Emitter`.
+ *
+ * @api public
+ */
+
+function Emitter(obj) {
+ if (obj) return mixin(obj);
+};
+
+/**
+ * Mixin the emitter properties.
+ *
+ * @param {Object} obj
+ * @return {Object}
+ * @api private
+ */
+
+function mixin(obj) {
+ for (var key in Emitter.prototype) {
+ obj[key] = Emitter.prototype[key];
+ }
+ return obj;
+}
+
+/**
+ * Listen on the given `event` with `fn`.
+ *
+ * @param {String} event
+ * @param {Function} fn
+ * @return {Emitter}
+ * @api public
+ */
+
+Emitter.prototype.on =
+Emitter.prototype.addEventListener = function(event, fn){
+ this._callbacks = this._callbacks || {};
+ (this._callbacks[event] = this._callbacks[event] || [])
+ .push(fn);
+ return this;
+};
+
+/**
+ * Adds an `event` listener that will be invoked a single
+ * time then automatically removed.
+ *
+ * @param {String} event
+ * @param {Function} fn
+ * @return {Emitter}
+ * @api public
+ */
+
+Emitter.prototype.once = function(event, fn){
+ var self = this;
+ this._callbacks = this._callbacks || {};
+
+ function on() {
+ self.off(event, on);
+ fn.apply(this, arguments);
+ }
+
+ on.fn = fn;
+ this.on(event, on);
+ return this;
+};
+
+/**
+ * Remove the given callback for `event` or all
+ * registered callbacks.
+ *
+ * @param {String} event
+ * @param {Function} fn
+ * @return {Emitter}
+ * @api public
+ */
+
+Emitter.prototype.off =
+Emitter.prototype.removeListener =
+Emitter.prototype.removeAllListeners =
+Emitter.prototype.removeEventListener = function(event, fn){
+ this._callbacks = this._callbacks || {};
+
+ // all
+ if (0 == arguments.length) {
+ this._callbacks = {};
+ return this;
+ }
+
+ // specific event
+ var callbacks = this._callbacks[event];
+ if (!callbacks) return this;
+
+ // remove all handlers
+ if (1 == arguments.length) {
+ delete this._callbacks[event];
+ return this;
+ }
+
+ // remove specific handler
+ var cb;
+ for (var i = 0; i < callbacks.length; i++) {
+ cb = callbacks[i];
+ if (cb === fn || cb.fn === fn) {
+ callbacks.splice(i, 1);
+ break;
+ }
+ }
+ return this;
+};
+
+/**
+ * Emit `event` with the given args.
+ *
+ * @param {String} event
+ * @param {Mixed} ...
+ * @return {Emitter}
+ */
+
+Emitter.prototype.emit = function(event){
+ this._callbacks = this._callbacks || {};
+ var args = [].slice.call(arguments, 1)
+ , callbacks = this._callbacks[event];
+
+ if (callbacks) {
+ callbacks = callbacks.slice(0);
+ for (var i = 0, len = callbacks.length; i < len; ++i) {
+ callbacks[i].apply(this, args);
+ }
+ }
+
+ return this;
+};
+
+/**
+ * Return array of callbacks for `event`.
+ *
+ * @param {String} event
+ * @return {Array}
+ * @api public
+ */
+
+Emitter.prototype.listeners = function(event){
+ this._callbacks = this._callbacks || {};
+ return this._callbacks[event] || [];
+};
+
+/**
+ * Check if this emitter has `event` handlers.
+ *
+ * @param {String} event
+ * @return {Boolean}
+ * @api public
+ */
+
+Emitter.prototype.hasListeners = function(event){
+ return !! this.listeners(event).length;
+};
+
+},{}],13:[function(_dereq_,module,exports){
+
+module.exports = function(a, b){
+ var fn = function(){};
+ fn.prototype = b.prototype;
+ a.prototype = new fn;
+ a.prototype.constructor = a;
+};
+},{}],14:[function(_dereq_,module,exports){
+
+/**
+ * Expose `debug()` as the module.
+ */
+
+module.exports = debug;
+
+/**
+ * Create a debugger with the given `name`.
+ *
+ * @param {String} name
+ * @return {Type}
+ * @api public
+ */
+
+function debug(name) {
+ if (!debug.enabled(name)) return function(){};
+
+ return function(fmt){
+ fmt = coerce(fmt);
+
+ var curr = new Date;
+ var ms = curr - (debug[name] || curr);
+ debug[name] = curr;
+
+ fmt = name
+ + ' '
+ + fmt
+ + ' +' + debug.humanize(ms);
+
+ // This hackery is required for IE8
+ // where `console.log` doesn't have 'apply'
+ window.console
+ && console.log
+ && Function.prototype.apply.call(console.log, console, arguments);
+ }
+}
+
+/**
+ * The currently active debug mode names.
+ */
+
+debug.names = [];
+debug.skips = [];
+
+/**
+ * Enables a debug mode by name. This can include modes
+ * separated by a colon and wildcards.
+ *
+ * @param {String} name
+ * @api public
+ */
+
+debug.enable = function(name) {
+ try {
+ localStorage.debug = name;
+ } catch(e){}
+
+ var split = (name || '').split(/[\s,]+/)
+ , len = split.length;
+
+ for (var i = 0; i < len; i++) {
+ name = split[i].replace('*', '.*?');
+ if (name[0] === '-') {
+ debug.skips.push(new RegExp('^' + name.substr(1) + '$'));
+ }
+ else {
+ debug.names.push(new RegExp('^' + name + '$'));
+ }
+ }
+};
+
+/**
+ * Disable debug output.
+ *
+ * @api public
+ */
+
+debug.disable = function(){
+ debug.enable('');
+};
+
+/**
+ * Humanize the given `ms`.
+ *
+ * @param {Number} m
+ * @return {String}
+ * @api private
+ */
+
+debug.humanize = function(ms) {
+ var sec = 1000
+ , min = 60 * 1000
+ , hour = 60 * min;
+
+ if (ms >= hour) return (ms / hour).toFixed(1) + 'h';
+ if (ms >= min) return (ms / min).toFixed(1) + 'm';
+ if (ms >= sec) return (ms / sec | 0) + 's';
+ return ms + 'ms';
+};
+
+/**
+ * Returns true if the given mode name is enabled, false otherwise.
+ *
+ * @param {String} name
+ * @return {Boolean}
+ * @api public
+ */
+
+debug.enabled = function(name) {
+ for (var i = 0, len = debug.skips.length; i < len; i++) {
+ if (debug.skips[i].test(name)) {
+ return false;
+ }
+ }
+ for (var i = 0, len = debug.names.length; i < len; i++) {
+ if (debug.names[i].test(name)) {
+ return true;
+ }
+ }
+ return false;
+};
+
+/**
+ * Coerce `val`.
+ */
+
+function coerce(val) {
+ if (val instanceof Error) return val.stack || val.message;
+ return val;
+}
+
+// persist
+
+try {
+ if (window.localStorage) debug.enable(localStorage.debug);
+} catch(e){}
+
+},{}],15:[function(_dereq_,module,exports){
+(function (global){
+/**
+ * Module dependencies.
+ */
+
+var keys = _dereq_('./keys');
+var sliceBuffer = _dereq_('arraybuffer.slice');
+var base64encoder = _dereq_('base64-arraybuffer');
+var after = _dereq_('after');
+var utf8 = _dereq_('utf8');
+
+/**
+ * Check if we are running an android browser. That requires us to use
+ * ArrayBuffer with polling transports...
+ *
+ * http://ghinda.net/jpeg-blob-ajax-android/
+ */
+
+var isAndroid = navigator.userAgent.match(/Android/i);
+
+/**
+ * Current protocol version.
+ */
+
+exports.protocol = 3;
+
+/**
+ * Packet types.
+ */
+
+var packets = exports.packets = {
+ open: 0 // non-ws
+ , close: 1 // non-ws
+ , ping: 2
+ , pong: 3
+ , message: 4
+ , upgrade: 5
+ , noop: 6
+};
+
+var packetslist = keys(packets);
+
+/**
+ * Premade error packet.
+ */
+
+var err = { type: 'error', data: 'parser error' };
+
+/**
+ * Create a blob api even for blob builder when vendor prefixes exist
+ */
+
+var Blob = _dereq_('blob');
+
+/**
+ * Encodes a packet.
+ *
+ * <packet type id> [ <data> ]
+ *
+ * Example:
+ *
+ * 5hello world
+ * 3
+ * 4
+ *
+ * Binary is encoded in an identical principle
+ *
+ * @api private
+ */
+
+exports.encodePacket = function (packet, supportsBinary, utf8encode, callback) {
+ if ('function' == typeof supportsBinary) {
+ callback = supportsBinary;
+ supportsBinary = false;
+ }
+
+ if ('function' == typeof utf8encode) {
+ callback = utf8encode;
+ utf8encode = null;
+ }
+
+ var data = (packet.data === undefined)
+ ? undefined
+ : packet.data.buffer || packet.data;
+
+ if (global.ArrayBuffer && data instanceof ArrayBuffer) {
+ return encodeArrayBuffer(packet, supportsBinary, callback);
+ } else if (Blob && data instanceof global.Blob) {
+ return encodeBlob(packet, supportsBinary, callback);
+ }
+
+ // Sending data as a utf-8 string
+ var encoded = packets[packet.type];
+
+ // data fragment is optional
+ if (undefined !== packet.data) {
+ encoded += utf8encode ? utf8.encode(String(packet.data)) : String(packet.data);
+ }
+
+ return callback('' + encoded);
+
+};
+
+/**
+ * Encode packet helpers for binary types
+ */
+
+function encodeArrayBuffer(packet, supportsBinary, callback) {
+ if (!supportsBinary) {
+ return exports.encodeBase64Packet(packet, callback);
+ }
+
+ var data = packet.data;
+ var contentArray = new Uint8Array(data);
+ var resultBuffer = new Uint8Array(1 + data.byteLength);
+
+ resultBuffer[0] = packets[packet.type];
+ for (var i = 0; i < contentArray.length; i++) {
+ resultBuffer[i+1] = contentArray[i];
+ }
+
+ return callback(resultBuffer.buffer);
+}
+
+function encodeBlobAsArrayBuffer(packet, supportsBinary, callback) {
+ if (!supportsBinary) {
+ return exports.encodeBase64Packet(packet, callback);
+ }
+
+ var fr = new FileReader();
+ fr.onload = function() {
+ packet.data = fr.result;
+ exports.encodePacket(packet, supportsBinary, true, callback);
+ };
+ return fr.readAsArrayBuffer(packet.data);
+}
+
+function encodeBlob(packet, supportsBinary, callback) {
+ if (!supportsBinary) {
+ return exports.encodeBase64Packet(packet, callback);
+ }
+
+ if (isAndroid) {
+ return encodeBlobAsArrayBuffer(packet, supportsBinary, callback);
+ }
+
+ var length = new Uint8Array(1);
+ length[0] = packets[packet.type];
+ var blob = new Blob([length.buffer, packet.data]);
+
+ return callback(blob);
+}
+
+/**
+ * Encodes a packet with binary data in a base64 string
+ *
+ * @param {Object} packet, has `type` and `data`
+ * @return {String} base64 encoded message
+ */
+
+exports.encodeBase64Packet = function(packet, callback) {
+ var message = 'b' + exports.packets[packet.type];
+ if (Blob && packet.data instanceof Blob) {
+ var fr = new FileReader();
+ fr.onload = function() {
+ var b64 = fr.result.split(',')[1];
+ callback(message + b64);
+ };
+ return fr.readAsDataURL(packet.data);
+ }
+
+ var b64data;
+ try {
+ b64data = String.fromCharCode.apply(null, new Uint8Array(packet.data));
+ } catch (e) {
+ // iPhone Safari doesn't let you apply with typed arrays
+ var typed = new Uint8Array(packet.data);
+ var basic = new Array(typed.length);
+ for (var i = 0; i < typed.length; i++) {
+ basic[i] = typed[i];
+ }
+ b64data = String.fromCharCode.apply(null, basic);
+ }
+ message += global.btoa(b64data);
+ return callback(message);
+};
+
+/**
+ * Decodes a packet. Changes format to Blob if requested.
+ *
+ * @return {Object} with `type` and `data` (if any)
+ * @api private
+ */
+
+exports.decodePacket = function (data, binaryType, utf8decode) {
+ // String data
+ if (typeof data == 'string' || data === undefined) {
+ if (data.charAt(0) == 'b') {
+ return exports.decodeBase64Packet(data.substr(1), binaryType);
+ }
+
+ if (utf8decode) {
+ try {
+ data = utf8.decode(data);
+ } catch (e) {
+ return err;
+ }
+ }
+ var type = data.charAt(0);
+
+ if (Number(type) != type || !packetslist[type]) {
+ return err;
+ }
+
+ if (data.length > 1) {
+ return { type: packetslist[type], data: data.substring(1) };
+ } else {
+ return { type: packetslist[type] };
+ }
+ }
+
+ var asArray = new Uint8Array(data);
+ var type = asArray[0];
+ var rest = sliceBuffer(data, 1);
+ if (Blob && binaryType === 'blob') {
+ rest = new Blob([rest]);
+ }
+ return { type: packetslist[type], data: rest };
+};
+
+/**
+ * Decodes a packet encoded in a base64 string
+ *
+ * @param {String} base64 encoded message
+ * @return {Object} with `type` and `data` (if any)
+ */
+
+exports.decodeBase64Packet = function(msg, binaryType) {
+ var type = packetslist[msg.charAt(0)];
+ if (!global.ArrayBuffer) {
+ return { type: type, data: { base64: true, data: msg.substr(1) } };
+ }
+
+ var data = base64encoder.decode(msg.substr(1));
+
+ if (binaryType === 'blob' && Blob) {
+ data = new Blob([data]);
+ }
+
+ return { type: type, data: data };
+};
+
+/**
+ * Encodes multiple messages (payload).
+ *
+ * <length>:data
+ *
+ * Example:
+ *
+ * 11:hello world2:hi
+ *
+ * If any contents are binary, they will be encoded as base64 strings. Base64
+ * encoded strings are marked with a b before the length specifier
+ *
+ * @param {Array} packets
+ * @api private
+ */
+
+exports.encodePayload = function (packets, supportsBinary, callback) {
+ if (typeof supportsBinary == 'function') {
+ callback = supportsBinary;
+ supportsBinary = null;
+ }
+
+ if (supportsBinary) {
+ if (Blob && !isAndroid) {
+ return exports.encodePayloadAsBlob(packets, callback);
+ }
+
+ return exports.encodePayloadAsArrayBuffer(packets, callback);
+ }
+
+ if (!packets.length) {
+ return callback('0:');
+ }
+
+ function setLengthHeader(message) {
+ return message.length + ':' + message;
+ }
+
+ function encodeOne(packet, doneCallback) {
+ exports.encodePacket(packet, supportsBinary, true, function(message) {
+ doneCallback(null, setLengthHeader(message));
+ });
+ }
+
+ map(packets, encodeOne, function(err, results) {
+ return callback(results.join(''));
+ });
+};
+
+/**
+ * Async array map using after
+ */
+
+function map(ary, each, done) {
+ var result = new Array(ary.length);
+ var next = after(ary.length, done);
+
+ var eachWithIndex = function(i, el, cb) {
+ each(el, function(error, msg) {
+ result[i] = msg;
+ cb(error, result);
+ });
+ };
+
+ for (var i = 0; i < ary.length; i++) {
+ eachWithIndex(i, ary[i], next);
+ }
+}
+
+/*
+ * Decodes data when a payload is maybe expected. Possible binary contents are
+ * decoded from their base64 representation
+ *
+ * @param {String} data, callback method
+ * @api public
+ */
+
+exports.decodePayload = function (data, binaryType, callback) {
+ if (typeof data != 'string') {
+ return exports.decodePayloadAsBinary(data, binaryType, callback);
+ }
+
+ if (typeof binaryType === 'function') {
+ callback = binaryType;
+ binaryType = null;
+ }
+
+ var packet;
+ if (data == '') {
+ // parser error - ignoring payload
+ return callback(err, 0, 1);
+ }
+
+ var length = ''
+ , n, msg;
+
+ for (var i = 0, l = data.length; i < l; i++) {
+ var chr = data.charAt(i);
+
+ if (':' != chr) {
+ length += chr;
+ } else {
+ if ('' == length || (length != (n = Number(length)))) {
+ // parser error - ignoring payload
+ return callback(err, 0, 1);
+ }
+
+ msg = data.substr(i + 1, n);
+
+ if (length != msg.length) {
+ // parser error - ignoring payload
+ return callback(err, 0, 1);
+ }
+
+ if (msg.length) {
+ packet = exports.decodePacket(msg, binaryType, true);
+
+ if (err.type == packet.type && err.data == packet.data) {
+ // parser error in individual packet - ignoring payload
+ return callback(err, 0, 1);
+ }
+
+ var ret = callback(packet, i + n, l);
+ if (false === ret) return;
+ }
+
+ // advance cursor
+ i += n;
+ length = '';
+ }
+ }
+
+ if (length != '') {
+ // parser error - ignoring payload
+ return callback(err, 0, 1);
+ }
+
+};
+
+/**
+ * Encodes multiple messages (payload) as binary.
+ *
+ * <1 = binary, 0 = string><number from 0-9><number from 0-9>[...]<number
+ * 255><data>
+ *
+ * Example:
+ * 1 3 255 1 2 3, if the binary contents are interpreted as 8 bit integers
+ *
+ * @param {Array} packets
+ * @return {ArrayBuffer} encoded payload
+ * @api private
+ */
+
+exports.encodePayloadAsArrayBuffer = function(packets, callback) {
+ if (!packets.length) {
+ return callback(new ArrayBuffer(0));
+ }
+
+ function encodeOne(packet, doneCallback) {
+ exports.encodePacket(packet, true, true, function(data) {
+ return doneCallback(null, data);
+ });
+ }
+
+ map(packets, encodeOne, function(err, encodedPackets) {
+ var totalLength = encodedPackets.reduce(function(acc, p) {
+ var len;
+ if (typeof p === 'string'){
+ len = p.length;
+ } else {
+ len = p.byteLength;
+ }
+ return acc + len.toString().length + len + 2; // string/binary identifier + separator = 2
+ }, 0);
+
+ var resultArray = new Uint8Array(totalLength);
+
+ var bufferIndex = 0;
+ encodedPackets.forEach(function(p) {
+ var isString = typeof p === 'string';
+ var ab = p;
+ if (isString) {
+ var view = new Uint8Array(p.length);
+ for (var i = 0; i < p.length; i++) {
+ view[i] = p.charCodeAt(i);
+ }
+ ab = view.buffer;
+ }
+
+ if (isString) { // not true binary
+ resultArray[bufferIndex++] = 0;
+ } else { // true binary
+ resultArray[bufferIndex++] = 1;
+ }
+
+ var lenStr = ab.byteLength.toString();
+ for (var i = 0; i < lenStr.length; i++) {
+ resultArray[bufferIndex++] = parseInt(lenStr[i]);
+ }
+ resultArray[bufferIndex++] = 255;
+
+ var view = new Uint8Array(ab);
+ for (var i = 0; i < view.length; i++) {
+ resultArray[bufferIndex++] = view[i];
+ }
+ });
+
+ return callback(resultArray.buffer);
+ });
+};
+
+/**
+ * Encode as Blob
+ */
+
+exports.encodePayloadAsBlob = function(packets, callback) {
+ function encodeOne(packet, doneCallback) {
+ exports.encodePacket(packet, true, true, function(encoded) {
+ var binaryIdentifier = new Uint8Array(1);
+ binaryIdentifier[0] = 1;
+ if (typeof encoded === 'string') {
+ var view = new Uint8Array(encoded.length);
+ for (var i = 0; i < encoded.length; i++) {
+ view[i] = encoded.charCodeAt(i);
+ }
+ encoded = view.buffer;
+ binaryIdentifier[0] = 0;
+ }
+
+ var len = (encoded instanceof ArrayBuffer)
+ ? encoded.byteLength
+ : encoded.size;
+
+ var lenStr = len.toString();
+ var lengthAry = new Uint8Array(lenStr.length + 1);
+ for (var i = 0; i < lenStr.length; i++) {
+ lengthAry[i] = parseInt(lenStr[i]);
+ }
+ lengthAry[lenStr.length] = 255;
+
+ if (Blob) {
+ var blob = new Blob([binaryIdentifier.buffer, lengthAry.buffer, encoded]);
+ doneCallback(null, blob);
+ }
+ });
+ }
+
+ map(packets, encodeOne, function(err, results) {
+ return callback(new Blob(results));
+ });
+};
+
+/*
+ * Decodes data when a payload is maybe expected. Strings are decoded by
+ * interpreting each byte as a key code for entries marked to start with 0. See
+ * description of encodePayloadAsBinary
+ *
+ * @param {ArrayBuffer} data, callback method
+ * @api public
+ */
+
+exports.decodePayloadAsBinary = function (data, binaryType, callback) {
+ if (typeof binaryType === 'function') {
+ callback = binaryType;
+ binaryType = null;
+ }
+
+ var bufferTail = data;
+ var buffers = [];
+
+ var numberTooLong = false;
+ while (bufferTail.byteLength > 0) {
+ var tailArray = new Uint8Array(bufferTail);
+ var isString = tailArray[0] === 0;
+ var msgLength = '';
+
+ for (var i = 1; ; i++) {
+ if (tailArray[i] == 255) break;
+
+ if (msgLength.length > 310) {
+ numberTooLong = true;
+ break;
+ }
+
+ msgLength += tailArray[i];
+ }
+
+ if(numberTooLong) return callback(err, 0, 1);
+
+ bufferTail = sliceBuffer(bufferTail, 2 + msgLength.length);
+ msgLength = parseInt(msgLength);
+
+ var msg = sliceBuffer(bufferTail, 0, msgLength);
+ if (isString) {
+ try {
+ msg = String.fromCharCode.apply(null, new Uint8Array(msg));
+ } catch (e) {
+ // iPhone Safari doesn't let you apply to typed arrays
+ var typed = new Uint8Array(msg);
+ msg = '';
+ for (var i = 0; i < typed.length; i++) {
+ msg += String.fromCharCode(typed[i]);
+ }
+ }
+ }
+
+ buffers.push(msg);
+ bufferTail = sliceBuffer(bufferTail, msgLength);
+ }
+
+ var total = buffers.length;
+ buffers.forEach(function(buffer, i) {
+ callback(exports.decodePacket(buffer, binaryType, true), i, total);
+ });
+};
+
+}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./keys":16,"after":17,"arraybuffer.slice":18,"base64-arraybuffer":19,"blob":11,"utf8":20}],16:[function(_dereq_,module,exports){
+
+/**
+ * Gets the keys for an object.
+ *
+ * @return {Array} keys
+ * @api private
+ */
+
+module.exports = Object.keys || function keys (obj){
+ var arr = [];
+ var has = Object.prototype.hasOwnProperty;
+
+ for (var i in obj) {
+ if (has.call(obj, i)) {
+ arr.push(i);
+ }
+ }
+ return arr;
+};
+
+},{}],17:[function(_dereq_,module,exports){
+module.exports = after
+
+function after(count, callback, err_cb) {
+ var bail = false
+ err_cb = err_cb || noop
+ proxy.count = count
+
+ return (count === 0) ? callback() : proxy
+
+ function proxy(err, result) {
+ if (proxy.count <= 0) {
+ throw new Error('after called too many times')
+ }
+ --proxy.count
+
+ // after first error, rest are passed to err_cb
+ if (err) {
+ bail = true
+ callback(err)
+ // future error callbacks will go to error handler
+ callback = err_cb
+ } else if (proxy.count === 0 && !bail) {
+ callback(null, result)
+ }
+ }
+}
+
+function noop() {}
+
+},{}],18:[function(_dereq_,module,exports){
+/**
+ * An abstraction for slicing an arraybuffer even when
+ * ArrayBuffer.prototype.slice is not supported
+ *
+ * @api public
+ */
+
+module.exports = function(arraybuffer, start, end) {
+ var bytes = arraybuffer.byteLength;
+ start = start || 0;
+ end = end || bytes;
+
+ if (arraybuffer.slice) { return arraybuffer.slice(start, end); }
+
+ if (start < 0) { start += bytes; }
+ if (end < 0) { end += bytes; }
+ if (end > bytes) { end = bytes; }
+
+ if (start >= bytes || start >= end || bytes === 0) {
+ return new ArrayBuffer(0);
+ }
+
+ var abv = new Uint8Array(arraybuffer);
+ var result = new Uint8Array(end - start);
+ for (var i = start, ii = 0; i < end; i++, ii++) {
+ result[ii] = abv[i];
+ }
+ return result.buffer;
+};
+
+},{}],19:[function(_dereq_,module,exports){
+/*
+ * base64-arraybuffer
+ * https://github.com/niklasvh/base64-arraybuffer
+ *
+ * Copyright (c) 2012 Niklas von Hertzen
+ * Licensed under the MIT license.
+ */
+(function(chars){
+ "use strict";
+
+ exports.encode = function(arraybuffer) {
+ var bytes = new Uint8Array(arraybuffer),
+ i, len = bytes.length, base64 = "";
+
+ for (i = 0; i < len; i+=3) {
+ base64 += chars[bytes[i] >> 2];
+ base64 += chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)];
+ base64 += chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)];
+ base64 += chars[bytes[i + 2] & 63];
+ }
+
+ if ((len % 3) === 2) {
+ base64 = base64.substring(0, base64.length - 1) + "=";
+ } else if (len % 3 === 1) {
+ base64 = base64.substring(0, base64.length - 2) + "==";
+ }
+
+ return base64;
+ };
+
+ exports.decode = function(base64) {
+ var bufferLength = base64.length * 0.75,
+ len = base64.length, i, p = 0,
+ encoded1, encoded2, encoded3, encoded4;
+
+ if (base64[base64.length - 1] === "=") {
+ bufferLength--;
+ if (base64[base64.length - 2] === "=") {
+ bufferLength--;
+ }
+ }
+
+ var arraybuffer = new ArrayBuffer(bufferLength),
+ bytes = new Uint8Array(arraybuffer);
+
+ for (i = 0; i < len; i+=4) {
+ encoded1 = chars.indexOf(base64[i]);
+ encoded2 = chars.indexOf(base64[i+1]);
+ encoded3 = chars.indexOf(base64[i+2]);
+ encoded4 = chars.indexOf(base64[i+3]);
+
+ bytes[p++] = (encoded1 << 2) | (encoded2 >> 4);
+ bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);
+ bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63);
+ }
+
+ return arraybuffer;
+ };
+})("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
+
+},{}],20:[function(_dereq_,module,exports){
+(function (global){
+/*! http://mths.be/utf8js v2.0.0 by @mathias */
+;(function(root) {
+
+ // Detect free variables `exports`
+ var freeExports = typeof exports == 'object' && exports;
+
+ // Detect free variable `module`
+ var freeModule = typeof module == 'object' && module &&
+ module.exports == freeExports && module;
+
+ // Detect free variable `global`, from Node.js or Browserified code,
+ // and use it as `root`
+ var freeGlobal = typeof global == 'object' && global;
+ if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) {
+ root = freeGlobal;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ var stringFromCharCode = String.fromCharCode;
+
+ // Taken from http://mths.be/punycode
+ function ucs2decode(string) {
+ var output = [];
+ var counter = 0;
+ var length = string.length;
+ var value;
+ var extra;
+ while (counter < length) {
+ value = string.charCodeAt(counter++);
+ if (value >= 0xD800 && value <= 0xDBFF && counter < length) {
+ // high surrogate, and there is a next character
+ extra = string.charCodeAt(counter++);
+ if ((extra & 0xFC00) == 0xDC00) { // low surrogate
+ output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000);
+ } else {
+ // unmatched surrogate; only append this code unit, in case the next
+ // code unit is the high surrogate of a surrogate pair
+ output.push(value);
+ counter--;
+ }
+ } else {
+ output.push(value);
+ }
+ }
+ return output;
+ }
+
+ // Taken from http://mths.be/punycode
+ function ucs2encode(array) {
+ var length = array.length;
+ var index = -1;
+ var value;
+ var output = '';
+ while (++index < length) {
+ value = array[index];
+ if (value > 0xFFFF) {
+ value -= 0x10000;
+ output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800);
+ value = 0xDC00 | value & 0x3FF;
+ }
+ output += stringFromCharCode(value);
+ }
+ return output;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ function createByte(codePoint, shift) {
+ return stringFromCharCode(((codePoint >> shift) & 0x3F) | 0x80);
+ }
+
+ function encodeCodePoint(codePoint) {
+ if ((codePoint & 0xFFFFFF80) == 0) { // 1-byte sequence
+ return stringFromCharCode(codePoint);
+ }
+ var symbol = '';
+ if ((codePoint & 0xFFFFF800) == 0) { // 2-byte sequence
+ symbol = stringFromCharCode(((codePoint >> 6) & 0x1F) | 0xC0);
+ }
+ else if ((codePoint & 0xFFFF0000) == 0) { // 3-byte sequence
+ symbol = stringFromCharCode(((codePoint >> 12) & 0x0F) | 0xE0);
+ symbol += createByte(codePoint, 6);
+ }
+ else if ((codePoint & 0xFFE00000) == 0) { // 4-byte sequence
+ symbol = stringFromCharCode(((codePoint >> 18) & 0x07) | 0xF0);
+ symbol += createByte(codePoint, 12);
+ symbol += createByte(codePoint, 6);
+ }
+ symbol += stringFromCharCode((codePoint & 0x3F) | 0x80);
+ return symbol;
+ }
+
+ function utf8encode(string) {
+ var codePoints = ucs2decode(string);
+
+ // console.log(JSON.stringify(codePoints.map(function(x) {
+ // return 'U+' + x.toString(16).toUpperCase();
+ // })));
+
+ var length = codePoints.length;
+ var index = -1;
+ var codePoint;
+ var byteString = '';
+ while (++index < length) {
+ codePoint = codePoints[index];
+ byteString += encodeCodePoint(codePoint);
+ }
+ return byteString;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ function readContinuationByte() {
+ if (byteIndex >= byteCount) {
+ throw Error('Invalid byte index');
+ }
+
+ var continuationByte = byteArray[byteIndex] & 0xFF;
+ byteIndex++;
+
+ if ((continuationByte & 0xC0) == 0x80) {
+ return continuationByte & 0x3F;
+ }
+
+ // If we end up here, it’s not a continuation byte
+ throw Error('Invalid continuation byte');
+ }
+
+ function decodeSymbol() {
+ var byte1;
+ var byte2;
+ var byte3;
+ var byte4;
+ var codePoint;
+
+ if (byteIndex > byteCount) {
+ throw Error('Invalid byte index');
+ }
+
+ if (byteIndex == byteCount) {
+ return false;
+ }
+
+ // Read first byte
+ byte1 = byteArray[byteIndex] & 0xFF;
+ byteIndex++;
+
+ // 1-byte sequence (no continuation bytes)
+ if ((byte1 & 0x80) == 0) {
+ return byte1;
+ }
+
+ // 2-byte sequence
+ if ((byte1 & 0xE0) == 0xC0) {
+ var byte2 = readContinuationByte();
+ codePoint = ((byte1 & 0x1F) << 6) | byte2;
+ if (codePoint >= 0x80) {
+ return codePoint;
+ } else {
+ throw Error('Invalid continuation byte');
+ }
+ }
+
+ // 3-byte sequence (may include unpaired surrogates)
+ if ((byte1 & 0xF0) == 0xE0) {
+ byte2 = readContinuationByte();
+ byte3 = readContinuationByte();
+ codePoint = ((byte1 & 0x0F) << 12) | (byte2 << 6) | byte3;
+ if (codePoint >= 0x0800) {
+ return codePoint;
+ } else {
+ throw Error('Invalid continuation byte');
+ }
+ }
+
+ // 4-byte sequence
+ if ((byte1 & 0xF8) == 0xF0) {
+ byte2 = readContinuationByte();
+ byte3 = readContinuationByte();
+ byte4 = readContinuationByte();
+ codePoint = ((byte1 & 0x0F) << 0x12) | (byte2 << 0x0C) |
+ (byte3 << 0x06) | byte4;
+ if (codePoint >= 0x010000 && codePoint <= 0x10FFFF) {
+ return codePoint;
+ }
+ }
+
+ throw Error('Invalid UTF-8 detected');
+ }
+
+ var byteArray;
+ var byteCount;
+ var byteIndex;
+ function utf8decode(byteString) {
+ byteArray = ucs2decode(byteString);
+ byteCount = byteArray.length;
+ byteIndex = 0;
+ var codePoints = [];
+ var tmp;
+ while ((tmp = decodeSymbol()) !== false) {
+ codePoints.push(tmp);
+ }
+ return ucs2encode(codePoints);
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ var utf8 = {
+ 'version': '2.0.0',
+ 'encode': utf8encode,
+ 'decode': utf8decode
+ };
+
+ // Some AMD build optimizers, like r.js, check for specific condition patterns
+ // like the following:
+ if (
+ typeof define == 'function' &&
+ typeof define.amd == 'object' &&
+ define.amd
+ ) {
+ define(function() {
+ return utf8;
+ });
+ } else if (freeExports && !freeExports.nodeType) {
+ if (freeModule) { // in Node.js or RingoJS v0.8.0+
+ freeModule.exports = utf8;
+ } else { // in Narwhal or RingoJS v0.7.0-
+ var object = {};
+ var hasOwnProperty = object.hasOwnProperty;
+ for (var key in utf8) {
+ hasOwnProperty.call(utf8, key) && (freeExports[key] = utf8[key]);
+ }
+ }
+ } else { // in Rhino or a web browser
+ root.utf8 = utf8;
+ }
+
+}(this));
+
+}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{}],21:[function(_dereq_,module,exports){
+
+/**
+ * Module dependencies.
+ */
+
+var global = _dereq_('global');
+
+/**
+ * Module exports.
+ *
+ * Logic borrowed from Modernizr:
+ *
+ * - https://github.com/Modernizr/Modernizr/blob/master/feature-detects/cors.js
+ */
+
+try {
+ module.exports = 'XMLHttpRequest' in global &&
+ 'withCredentials' in new global.XMLHttpRequest();
+} catch (err) {
+ // if XMLHttp support is disabled in IE then it will throw
+ // when trying to create
+ module.exports = false;
+}
+
+},{"global":22}],22:[function(_dereq_,module,exports){
+
+/**
+ * Returns `this`. Execute this without a "context" (i.e. without it being
+ * attached to an object of the left-hand side), and `this` points to the
+ * "global" scope of the current JS execution.
+ */
+
+module.exports = (function () { return this; })();
+
+},{}],23:[function(_dereq_,module,exports){
+
+var indexOf = [].indexOf;
+
+module.exports = function(arr, obj){
+ if (indexOf) return arr.indexOf(obj);
+ for (var i = 0; i < arr.length; ++i) {
+ if (arr[i] === obj) return i;
+ }
+ return -1;
+};
+},{}],24:[function(_dereq_,module,exports){
+(function (global){
+/**
+ * JSON parse.
+ *
+ * @see Based on jQuery#parseJSON (MIT) and JSON2
+ * @api private
+ */
+
+var rvalidchars = /^[\],:{}\s]*$/;
+var rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g;
+var rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;
+var rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g;
+var rtrimLeft = /^\s+/;
+var rtrimRight = /\s+$/;
+
+module.exports = function parsejson(data) {
+ if ('string' != typeof data || !data) {
+ return null;
+ }
+
+ data = data.replace(rtrimLeft, '').replace(rtrimRight, '');
+
+ // Attempt to parse using the native JSON parser first
+ if (global.JSON && JSON.parse) {
+ return JSON.parse(data);
+ }
+
+ if (rvalidchars.test(data.replace(rvalidescape, '@')
+ .replace(rvalidtokens, ']')
+ .replace(rvalidbraces, ''))) {
+ return (new Function('return ' + data))();
+ }
+};
+}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{}],25:[function(_dereq_,module,exports){
+/**
+ * Compiles a querystring
+ * Returns string representation of the object
+ *
+ * @param {Object}
+ * @api private
+ */
+
+exports.encode = function (obj) {
+ var str = '';
+
+ for (var i in obj) {
+ if (obj.hasOwnProperty(i)) {
+ if (str.length) str += '&';
+ str += encodeURIComponent(i) + '=' + encodeURIComponent(obj[i]);
+ }
+ }
+
+ return str;
+};
+
+/**
+ * Parses a simple querystring into an object
+ *
+ * @param {String} qs
+ * @api private
+ */
+
+exports.decode = function(qs){
+ var qry = {};
+ var pairs = qs.split('&');
+ for (var i = 0, l = pairs.length; i < l; i++) {
+ var pair = pairs[i].split('=');
+ qry[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
+ }
+ return qry;
+};
+
+},{}],26:[function(_dereq_,module,exports){
+/**
+ * Parses an URI
+ *
+ * @author Steven Levithan <stevenlevithan.com> (MIT license)
+ * @api private
+ */
+
+var re = /^(?:(?![^:@]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/;
+
+var parts = [
+ 'source', 'protocol', 'authority', 'userInfo', 'user', 'password', 'host', 'port', 'relative', 'path', 'directory', 'file', 'query', 'anchor'
+];
+
+module.exports = function parseuri(str) {
+ var src = str,
+ b = str.indexOf('['),
+ e = str.indexOf(']');
+
+ if (b != -1 && e != -1) {
+ str = str.substring(0, b) + str.substring(b, e).replace(/:/g, ';') + str.substring(e, str.length);
+ }
+
+ var m = re.exec(str || ''),
+ uri = {},
+ i = 14;
+
+ while (i--) {
+ uri[parts[i]] = m[i] || '';
+ }
+
+ if (b != -1 && e != -1) {
+ uri.source = src;
+ uri.host = uri.host.substring(1, uri.host.length - 1).replace(/;/g, ':');
+ uri.authority = uri.authority.replace('[', '').replace(']', '').replace(/;/g, ':');
+ uri.ipv6uri = true;
+ }
+
+ return uri;
+};
+
+},{}],27:[function(_dereq_,module,exports){
+
+/**
+ * Module dependencies.
+ */
+
+var global = (function() { return this; })();
+
+/**
+ * WebSocket constructor.
+ */
+
+var WebSocket = global.WebSocket || global.MozWebSocket;
+
+/**
+ * Module exports.
+ */
+
+module.exports = WebSocket ? ws : null;
+
+/**
+ * WebSocket constructor.
+ *
+ * The third `opts` options object gets ignored in web browsers, since it's
+ * non-standard, and throws a TypeError if passed to the constructor.
+ * See: https://github.com/einaros/ws/issues/227
+ *
+ * @param {String} uri
+ * @param {Array} protocols (optional)
+ * @param {Object) opts (optional)
+ * @api public
+ */
+
+function ws(uri, protocols, opts) {
+ var instance;
+ if (protocols) {
+ instance = new WebSocket(uri, protocols);
+ } else {
+ instance = new WebSocket(uri);
+ }
+ return instance;
+}
+
+if (WebSocket) ws.prototype = WebSocket.prototype;
+
+},{}]},{},[1])
+(1)
+});
+
+
+
+var EngineioTools = {
+ ReconnectingSocket: function ReconnectingSocket(server_uri, socket_options) {
+ var connected = false;
+ var is_reconnecting = false;
+
+ var reconnect_delay = 4000;
+ var reconnect_last_delay = 0;
+ var reconnect_delay_exponential = true;
+ var reconnect_max_attempts = 5;
+ var reconnect_step = 0;
+ var reconnect_tmr = null;
+
+ var original_disconnect;
+ var planned_disconnect = false;
+
+ var socket = eio.apply(eio, arguments);
+ socket.on('open', onOpen);
+ socket.on('close', onClose);
+ socket.on('error', onError);
+
+ original_disconnect = socket.close;
+ socket.close = close;
+
+ // Apply any custom reconnection config
+ if (socket_options) {
+ if (typeof socket_options.reconnect_delay === 'number')
+ reconnect_delay = socket_options.reconnect_delay;
+
+ if (typeof socket_options.reconnect_max_attempts === 'number')
+ reconnect_max_attempts = socket_options.reconnect_max_attempts;
+
+ if (typeof socket_options.reconnect_delay_exponential !== 'undefined')
+ reconnect_delay_exponential = !!socket_options.reconnect_delay_exponential;
+ }
+
+
+ function onOpen() {
+ connected = true;
+ is_reconnecting = false;
+ planned_disconnect = false;
+
+ reconnect_step = 0;
+ reconnect_last_delay = 0;
+
+ clearTimeout(reconnect_tmr);
+ }
+
+
+ function onClose() {
+ connected = false;
+
+ if (!planned_disconnect && !is_reconnecting)
+ reconnect();
+ }
+
+
+ function onError() {
+ // This will be called when a reconnect fails
+ if (is_reconnecting)
+ reconnect();
+ }
+
+
+ function close() {
+ planned_disconnect = true;
+ original_disconnect.call(socket);
+ }
+
+
+ function reconnect() {
+ if (reconnect_step >= reconnect_max_attempts) {
+ socket.emit('reconnecting_failed');
+ return;
+ }
+
+ var delay = reconnect_delay_exponential ?
+ (reconnect_last_delay || reconnect_delay / 2) * 2 :
+ reconnect_delay * reconnect_step;
+
+ is_reconnecting = true;
+
+ reconnect_tmr = setTimeout(function() {
+ socket.open();
+ }, delay);
+
+ reconnect_last_delay = delay;
+
+ socket.emit('reconnecting', {
+ attempt: reconnect_step + 1,
+ max_attempts: reconnect_max_attempts,
+ delay: delay
+ });
+
+ reconnect_step++;
+ }
+
+ return socket;
+ },
+
+
+
+
+ Rpc: (function(){
+ /*
+ TODO:
+ Create a document explaining the protocol
+ Some way to expire unused callbacks? TTL? expireCallback() function?
+ */
+
+ /**
+ * Wrapper around creating a new WebsocketRpcCaller
+ * This lets us use the WebsocketRpc object as a function
+ */
+ function WebsocketRpc(eio_socket) {
+ var caller = new WebsocketRpcCaller(eio_socket);
+ var ret = function WebsocketRpcInstance() {
+ return ret.makeCall.apply(ret, arguments);
+ };
+
+ for(var prop in caller){
+ ret[prop] = caller[prop];
+ }
+
+ ret._mixinEmitter();
+ ret._bindSocketListeners();
+
+ // Keep a reference to the main Rpc object so namespaces can find calling functions
+ ret._rpc = ret;
+
+ return ret;
+ }
+
+
+ function WebsocketRpcCaller(eio_socket) {
+ this._next_id = 0;
+ this._rpc_callbacks = {};
+ this._socket = eio_socket;
+
+ this._rpc = this;
+ this._namespace = '';
+ this._namespaces = [];
+ }
+
+
+ WebsocketRpcCaller.prototype._bindSocketListeners = function() {
+ var self = this;
+
+ // Proxy the onMessage listener
+ this._onMessageProxy = function rpcOnMessageBoundFunction(){
+ self._onMessage.apply(self, arguments);
+ };
+ this._socket.on('message', this._onMessageProxy);
+ };
+
+
+
+ WebsocketRpcCaller.prototype.dispose = function() {
+ if (this._onMessageProxy) {
+ this._socket.removeListener('message', this._onMessageProxy);
+ delete this._onMessageProxy;
+ }
+
+ // Clean up any namespaces
+ for (var idx in this._namespaces) {
+ this._namespaces[idx].dispose();
+ }
+
+ this.removeAllListeners();
+ };
+
+
+
+ WebsocketRpcCaller.prototype.namespace = function(namespace_name) {
+ var complete_namespace, namespace;
+
+ if (this._namespace) {
+ complete_namespace = this._namespace + '.' + namespace_name;
+ } else {
+ complete_namespace = namespace_name;
+ }
+
+ namespace = new this._rpc.Namespace(this._rpc, complete_namespace);
+ this._rpc._namespaces.push(namespace);
+
+ return namespace;
+ };
+
+
+
+ // Find all namespaces that either matches or starts with namespace_name
+ WebsocketRpcCaller.prototype._findRelevantNamespaces = function(namespace_name) {
+ var found_namespaces = [];
+
+ for(var idx in this._namespaces) {
+ if (this._namespaces[idx]._namespace === namespace_name) {
+ found_namespaces.push(this._namespaces[idx]);
+ }
+
+ if (this._namespaces[idx]._namespace.indexOf(namespace_name + '.') === 0) {
+ found_namespaces.push(this._namespaces[idx]);
+ }
+ }
+
+ return found_namespaces;
+ };
+
+
+
+ /**
+ * The engine.io socket already has an emitter mixin so steal it from there
+ */
+ WebsocketRpcCaller.prototype._mixinEmitter = function(target_obj) {
+ var funcs = ['on', 'once', 'off', 'removeListener', 'removeAllListeners', 'emit', 'listeners', 'hasListeners'];
+
+ target_obj = target_obj || this;
+
+ for (var i=0; i<funcs.length; i++) {
+ if (typeof this._socket[funcs[i]] === 'function')
+ target_obj[funcs[i]] = this._socket[funcs[i]];
+ }
+ };
+
+
+ /**
+ * Check if a packet is a valid RPC call
+ */
+ WebsocketRpcCaller.prototype._isCall = function(packet) {
+ return (typeof packet.method !== 'undefined' &&
+ typeof packet.params !== 'undefined');
+ };
+
+
+ /**
+ * Check if a packet is a valid RPC response
+ */
+ WebsocketRpcCaller.prototype._isResponse = function(packet) {
+ return (typeof packet.id !== 'undefined' &&
+ typeof packet.response !== 'undefined');
+ };
+
+
+
+ /**
+ * Make an RPC call
+ * First argument must be the method name to call
+ * If the last argument is a function, it is used as a callback
+ * All other arguments are passed to the RPC method
+ * Eg. Rpc.makeCall('namespace.method_name', 1, 2, 3, callbackFn)
+ */
+ WebsocketRpcCaller.prototype.makeCall = function(method) {
+ var params, callback, packet;
+
+ // Get a normal array of passed in arguments
+ params = Array.prototype.slice.call(arguments, 1, arguments.length);
+
+ // If the last argument is a function, take it as a callback and strip it out
+ if (typeof params[params.length-1] === 'function') {
+ callback = params[params.length-1];
+ params = params.slice(0, params.length-1);
+ }
+
+ packet = {
+ method: method,
+ params: params
+ };
+
+ if (typeof callback === 'function') {
+ packet.id = this._next_id;
+
+ this._next_id++;
+ this._rpc_callbacks[packet.id] = callback;
+ }
+
+ this.send(packet);
+ };
+
+
+ /**
+ * Encode the packet into JSON and send it over the websocket
+ */
+ WebsocketRpcCaller.prototype.send = function(packet) {
+ if (this._socket)
+ this._socket.send(JSON.stringify(packet));
+ };
+
+
+ /**
+ * Handler for the websocket `message` event
+ */
+ WebsocketRpcCaller.prototype._onMessage = function(message_raw) {
+ var self = this,
+ packet,
+ returnFn,
+ callback,
+ namespace, namespaces, idx;
+
+ try {
+ packet = JSON.parse(message_raw);
+ if (!packet) throw 'Corrupt packet';
+ } catch(err) {
+ return;
+ }
+
+ if (this._isResponse(packet)) {
+ // If we have no callback waiting for this response, don't do anything
+ if (typeof this._rpc_callbacks[packet.id] !== 'function')
+ return;
+
+ // Delete the callback before calling it. If any exceptions accur within the callback
+ // we don't have to worry about the delete not happening
+ callback = this._rpc_callbacks[packet.id];
+ delete this._rpc_callbacks[packet.id];
+
+ callback.apply(this, packet.response);
+
+ } else if (this._isCall(packet)) {
+ // Calls with an ID may be responded to
+ if (typeof packet.id !== 'undefined') {
+ returnFn = this._createReturnCallFn(packet.id);
+ } else {
+ returnFn = this._noop;
+ }
+
+ this.emit.apply(this, ['all', packet.method, returnFn].concat(packet.params));
+ this.emit.apply(this, [packet.method, returnFn].concat(packet.params));
+
+ if (packet.method.indexOf('.') > 0) {
+ namespace = packet.method.substring(0, packet.method.lastIndexOf('.'));
+ namespaces = this._findRelevantNamespaces(namespace);
+ for(idx in namespaces){
+ packet.method = packet.method.replace(namespaces[idx]._namespace + '.', '');
+ namespaces[idx].emit.apply(namespaces[idx], [packet.method, returnFn].concat(packet.params));
+ }
+ }
+ }
+ };
+
+
+ /**
+ * Returns a function used as a callback when responding to a call
+ */
+ WebsocketRpcCaller.prototype._createReturnCallFn = function(packet_id) {
+ var self = this;
+
+ return function returnCallFn() {
+ var value = Array.prototype.slice.call(arguments, 0);
+
+ var ret_packet = {
+ id: packet_id,
+ response: value
+ };
+
+ self.send(ret_packet);
+ };
+ };
+
+
+
+ WebsocketRpcCaller.prototype._noop = function() {};
+
+
+
+ WebsocketRpcCaller.prototype.Namespace = function(rpc, namespace) {
+ var ret = function WebsocketRpcNamespaceInstance() {
+ if (typeof arguments[0] === 'undefined') {
+ return;
+ }
+
+ arguments[0] = ret._namespace + '.' + arguments[0];
+ return ret._rpc.apply(ret._rpc, arguments);
+ };
+
+ ret._rpc = rpc;
+ ret._namespace = namespace;
+
+ ret.dispose = function() {
+ ret.removeAllListeners();
+ ret._rpc = null;
+ };
+
+ rpc._mixinEmitter(ret);
+
+ return ret;
+ };
+
+
+ return WebsocketRpc;
+
+ }())
+};
+
+
--- /dev/null
+!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var t;"undefined"!=typeof window?t=window:"undefined"!=typeof global?t=global:"undefined"!=typeof self&&(t=self),t.eio=e()}}(function(){var e;return function t(e,n,i){function s(o,r){if(!n[o]){if(!e[o]){var c="function"==typeof require&&require;if(!r&&c)return c(o,!0);if(a)return a(o,!0);throw new Error("Cannot find module '"+o+"'")}var l=n[o]={exports:{}};e[o][0].call(l.exports,function(t){var n=e[o][1][t];return s(n?n:t)},l,l.exports,t,e,n,i)}return n[o].exports}for(var a="function"==typeof require&&require,o=0;o<i.length;o++)s(i[o]);return s}({1:[function(e,t){t.exports=e("./lib/")},{"./lib/":2}],2:[function(e,t){t.exports=e("./socket"),t.exports.parser=e("engine.io-parser")},{"./socket":3,"engine.io-parser":15}],3:[function(e,t){(function(n){function i(e,t){if(!(this instanceof i))return new i(e,t);if(t=t||{},e&&"object"==typeof e&&(t=e,e=null),e&&(e=h(e),t.host=e.host,t.secure="https"==e.protocol||"wss"==e.protocol,t.port=e.port,e.query&&(t.query=e.query)),this.secure=null!=t.secure?t.secure:n.location&&"https:"==location.protocol,t.host){var s=t.host.split(":");t.hostname=s.shift(),s.length&&(t.port=s.pop())}this.agent=t.agent||!1,this.hostname=t.hostname||(n.location?location.hostname:"localhost"),this.port=t.port||(n.location&&location.port?location.port:this.secure?443:80),this.query=t.query||{},"string"==typeof this.query&&(this.query=d.decode(this.query)),this.upgrade=!1!==t.upgrade,this.path=(t.path||"/engine.io").replace(/\/$/,"")+"/",this.forceJSONP=!!t.forceJSONP,this.jsonp=!1!==t.jsonp,this.forceBase64=!!t.forceBase64,this.enablesXDR=!!t.enablesXDR,this.timestampParam=t.timestampParam||"t",this.timestampRequests=t.timestampRequests,this.transports=t.transports||["polling","websocket"],this.readyState="",this.writeBuffer=[],this.callbackBuffer=[],this.policyPort=t.policyPort||843,this.rememberUpgrade=t.rememberUpgrade||!1,this.open(),this.binaryType=null,this.onlyBinaryUpgrades=t.onlyBinaryUpgrades}function s(e){var t={};for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t}var a=e("./transports"),o=e("component-emitter"),r=e("debug")("engine.io-client:socket"),c=e("indexof"),l=e("engine.io-parser"),h=e("parseuri"),p=e("parsejson"),d=e("parseqs");t.exports=i,i.priorWebsocketSuccess=!1,o(i.prototype),i.protocol=l.protocol,i.Socket=i,i.Transport=e("./transport"),i.transports=e("./transports"),i.parser=e("engine.io-parser"),i.prototype.createTransport=function(e){r('creating transport "%s"',e);var t=s(this.query);t.EIO=l.protocol,t.transport=e,this.id&&(t.sid=this.id);var n=new a[e]({agent:this.agent,hostname:this.hostname,port:this.port,secure:this.secure,path:this.path,query:t,forceJSONP:this.forceJSONP,jsonp:this.jsonp,forceBase64:this.forceBase64,enablesXDR:this.enablesXDR,timestampRequests:this.timestampRequests,timestampParam:this.timestampParam,policyPort:this.policyPort,socket:this});return n},i.prototype.open=function(){var e;if(this.rememberUpgrade&&i.priorWebsocketSuccess&&-1!=this.transports.indexOf("websocket"))e="websocket";else{if(0==this.transports.length){var t=this;return void setTimeout(function(){t.emit("error","No transports available")},0)}e=this.transports[0]}this.readyState="opening";var e;try{e=this.createTransport(e)}catch(n){return this.transports.shift(),void this.open()}e.open(),this.setTransport(e)},i.prototype.setTransport=function(e){r("setting transport %s",e.name);var t=this;this.transport&&(r("clearing existing transport %s",this.transport.name),this.transport.removeAllListeners()),this.transport=e,e.on("drain",function(){t.onDrain()}).on("packet",function(e){t.onPacket(e)}).on("error",function(e){t.onError(e)}).on("close",function(){t.onClose("transport close")})},i.prototype.probe=function(e){function t(){if(d.onlyBinaryUpgrades){var t=!this.supportsBinary&&d.transport.supportsBinary;p=p||t}p||(r('probe transport "%s" opened',e),h.send([{type:"ping",data:"probe"}]),h.once("packet",function(t){if(!p)if("pong"==t.type&&"probe"==t.data)r('probe transport "%s" pong',e),d.upgrading=!0,d.emit("upgrading",h),i.priorWebsocketSuccess="websocket"==h.name,r('pausing current transport "%s"',d.transport.name),d.transport.pause(function(){p||"closed"!=d.readyState&&"closing"!=d.readyState&&(r("changing transport and sending upgrade packet"),l(),d.setTransport(h),h.send([{type:"upgrade"}]),d.emit("upgrade",h),h=null,d.upgrading=!1,d.flush())});else{r('probe transport "%s" failed',e);var n=new Error("probe error");n.transport=h.name,d.emit("upgradeError",n)}}))}function n(){p||(p=!0,l(),h.close(),h=null)}function s(t){var i=new Error("probe error: "+t);i.transport=h.name,n(),r('probe transport "%s" failed because of error: %s',e,t),d.emit("upgradeError",i)}function a(){s("transport closed")}function o(){s("socket closed")}function c(e){h&&e.name!=h.name&&(r('"%s" works - aborting "%s"',e.name,h.name),n())}function l(){h.removeListener("open",t),h.removeListener("error",s),h.removeListener("close",a),d.removeListener("close",o),d.removeListener("upgrading",c)}r('probing transport "%s"',e);var h=this.createTransport(e,{probe:1}),p=!1,d=this;i.priorWebsocketSuccess=!1,h.once("open",t),h.once("error",s),h.once("close",a),this.once("close",o),this.once("upgrading",c),h.open()},i.prototype.onOpen=function(){if(r("socket open"),this.readyState="open",i.priorWebsocketSuccess="websocket"==this.transport.name,this.emit("open"),this.flush(),"open"==this.readyState&&this.upgrade&&this.transport.pause){r("starting upgrade probes");for(var e=0,t=this.upgrades.length;t>e;e++)this.probe(this.upgrades[e])}},i.prototype.onPacket=function(e){if("opening"==this.readyState||"open"==this.readyState)switch(r('socket receive: type "%s", data "%s"',e.type,e.data),this.emit("packet",e),this.emit("heartbeat"),e.type){case"open":this.onHandshake(p(e.data));break;case"pong":this.setPing();break;case"error":var t=new Error("server error");t.code=e.data,this.emit("error",t);break;case"message":this.emit("data",e.data),this.emit("message",e.data)}else r('packet received with socket readyState "%s"',this.readyState)},i.prototype.onHandshake=function(e){this.emit("handshake",e),this.id=e.sid,this.transport.query.sid=e.sid,this.upgrades=this.filterUpgrades(e.upgrades),this.pingInterval=e.pingInterval,this.pingTimeout=e.pingTimeout,this.onOpen(),"closed"!=this.readyState&&(this.setPing(),this.removeListener("heartbeat",this.onHeartbeat),this.on("heartbeat",this.onHeartbeat))},i.prototype.onHeartbeat=function(e){clearTimeout(this.pingTimeoutTimer);var t=this;t.pingTimeoutTimer=setTimeout(function(){"closed"!=t.readyState&&t.onClose("ping timeout")},e||t.pingInterval+t.pingTimeout)},i.prototype.setPing=function(){var e=this;clearTimeout(e.pingIntervalTimer),e.pingIntervalTimer=setTimeout(function(){r("writing ping packet - expecting pong within %sms",e.pingTimeout),e.ping(),e.onHeartbeat(e.pingTimeout)},e.pingInterval)},i.prototype.ping=function(){this.sendPacket("ping")},i.prototype.onDrain=function(){for(var e=0;e<this.prevBufferLen;e++)this.callbackBuffer[e]&&this.callbackBuffer[e]();this.writeBuffer.splice(0,this.prevBufferLen),this.callbackBuffer.splice(0,this.prevBufferLen),this.prevBufferLen=0,0==this.writeBuffer.length?this.emit("drain"):this.flush()},i.prototype.flush=function(){"closed"!=this.readyState&&this.transport.writable&&!this.upgrading&&this.writeBuffer.length&&(r("flushing %d packets in socket",this.writeBuffer.length),this.transport.send(this.writeBuffer),this.prevBufferLen=this.writeBuffer.length,this.emit("flush"))},i.prototype.write=i.prototype.send=function(e,t){return this.sendPacket("message",e,t),this},i.prototype.sendPacket=function(e,t,n){var i={type:e,data:t};this.emit("packetCreate",i),this.writeBuffer.push(i),this.callbackBuffer.push(n),this.flush()},i.prototype.close=function(){return("opening"==this.readyState||"open"==this.readyState)&&(this.onClose("forced close"),r("socket closing - telling transport to close"),this.transport.close()),this},i.prototype.onError=function(e){r("socket error %j",e),i.priorWebsocketSuccess=!1,this.emit("error",e),this.onClose("transport error",e)},i.prototype.onClose=function(e,t){if("opening"==this.readyState||"open"==this.readyState){r('socket close with reason: "%s"',e);var n=this;clearTimeout(this.pingIntervalTimer),clearTimeout(this.pingTimeoutTimer),setTimeout(function(){n.writeBuffer=[],n.callbackBuffer=[],n.prevBufferLen=0},0),this.transport.removeAllListeners("close"),this.transport.close(),this.transport.removeAllListeners(),this.readyState="closed",this.id=null,this.emit("close",e,t)}},i.prototype.filterUpgrades=function(e){for(var t=[],n=0,i=e.length;i>n;n++)~c(this.transports,e[n])&&t.push(e[n]);return t}}).call(this,"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./transport":4,"./transports":5,"component-emitter":12,debug:14,"engine.io-parser":15,indexof:23,parsejson:24,parseqs:25,parseuri:26}],4:[function(e,t){function n(e){this.path=e.path,this.hostname=e.hostname,this.port=e.port,this.secure=e.secure,this.query=e.query,this.timestampParam=e.timestampParam,this.timestampRequests=e.timestampRequests,this.readyState="",this.agent=e.agent||!1,this.socket=e.socket,this.enablesXDR=e.enablesXDR}var i=e("engine.io-parser"),s=e("component-emitter");t.exports=n,s(n.prototype),n.timestamps=0,n.prototype.onError=function(e,t){var n=new Error(e);return n.type="TransportError",n.description=t,this.emit("error",n),this},n.prototype.open=function(){return("closed"==this.readyState||""==this.readyState)&&(this.readyState="opening",this.doOpen()),this},n.prototype.close=function(){return("opening"==this.readyState||"open"==this.readyState)&&(this.doClose(),this.onClose()),this},n.prototype.send=function(e){if("open"!=this.readyState)throw new Error("Transport not open");this.write(e)},n.prototype.onOpen=function(){this.readyState="open",this.writable=!0,this.emit("open")},n.prototype.onData=function(e){var t=i.decodePacket(e,this.socket.binaryType);this.onPacket(t)},n.prototype.onPacket=function(e){this.emit("packet",e)},n.prototype.onClose=function(){this.readyState="closed",this.emit("close")}},{"component-emitter":12,"engine.io-parser":15}],5:[function(e,t,n){(function(t){function i(e){var n,i=!1,r=!1,c=!1!==e.jsonp;if(t.location){var l="https:"==location.protocol,h=location.port;h||(h=l?443:80),i=e.hostname!=location.hostname||h!=e.port,r=e.secure!=l}if(e.xdomain=i,e.xscheme=r,n=new s(e),"open"in n&&!e.forceJSONP)return new a(e);if(!c)throw new Error("JSONP disabled");return new o(e)}var s=e("xmlhttprequest"),a=e("./polling-xhr"),o=e("./polling-jsonp"),r=e("./websocket");n.polling=i,n.websocket=r}).call(this,"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./polling-jsonp":6,"./polling-xhr":7,"./websocket":9,xmlhttprequest:10}],6:[function(e,t){(function(n){function i(){}function s(e){a.call(this,e),this.query=this.query||{},r||(n.___eio||(n.___eio=[]),r=n.___eio),this.index=r.length;var t=this;r.push(function(e){t.onData(e)}),this.query.j=this.index,n.document&&n.addEventListener&&n.addEventListener("beforeunload",function(){t.script&&(t.script.onerror=i)})}var a=e("./polling"),o=e("component-inherit");t.exports=s;var r,c=/\n/g,l=/\\n/g;o(s,a),s.prototype.supportsBinary=!1,s.prototype.doClose=function(){this.script&&(this.script.parentNode.removeChild(this.script),this.script=null),this.form&&(this.form.parentNode.removeChild(this.form),this.form=null),a.prototype.doClose.call(this)},s.prototype.doPoll=function(){var e=this,t=document.createElement("script");this.script&&(this.script.parentNode.removeChild(this.script),this.script=null),t.async=!0,t.src=this.uri(),t.onerror=function(t){e.onError("jsonp poll error",t)};var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(t,n),this.script=t;var i="undefined"!=typeof navigator&&/gecko/i.test(navigator.userAgent);i&&setTimeout(function(){var e=document.createElement("iframe");document.body.appendChild(e),document.body.removeChild(e)},100)},s.prototype.doWrite=function(e,t){function n(){i(),t()}function i(){if(s.iframe)try{s.form.removeChild(s.iframe)}catch(e){s.onError("jsonp polling iframe removal error",e)}try{var t='<iframe src="javascript:0" name="'+s.iframeId+'">';a=document.createElement(t)}catch(e){a=document.createElement("iframe"),a.name=s.iframeId,a.src="javascript:0"}a.id=s.iframeId,s.form.appendChild(a),s.iframe=a}var s=this;if(!this.form){var a,o=document.createElement("form"),r=document.createElement("textarea"),h=this.iframeId="eio_iframe_"+this.index;o.className="socketio",o.style.position="absolute",o.style.top="-1000px",o.style.left="-1000px",o.target=h,o.method="POST",o.setAttribute("accept-charset","utf-8"),r.name="d",o.appendChild(r),document.body.appendChild(o),this.form=o,this.area=r}this.form.action=this.uri(),i(),e=e.replace(l,"\\\n"),this.area.value=e.replace(c,"\\n");try{this.form.submit()}catch(p){}this.iframe.attachEvent?this.iframe.onreadystatechange=function(){"complete"==s.iframe.readyState&&n()}:this.iframe.onload=n}}).call(this,"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./polling":8,"component-inherit":13}],7:[function(e,t){(function(n){function i(){}function s(e){if(c.call(this,e),n.location){var t="https:"==location.protocol,i=location.port;i||(i=t?443:80),this.xd=e.hostname!=n.location.hostname||i!=e.port,this.xs=e.secure!=t}}function a(e){this.method=e.method||"GET",this.uri=e.uri,this.xd=!!e.xd,this.xs=!!e.xs,this.async=!1!==e.async,this.data=void 0!=e.data?e.data:null,this.agent=e.agent,this.isBinary=e.isBinary,this.supportsBinary=e.supportsBinary,this.enablesXDR=e.enablesXDR,this.create()}function o(){for(var e in a.requests)a.requests.hasOwnProperty(e)&&a.requests[e].abort()}var r=e("xmlhttprequest"),c=e("./polling"),l=e("component-emitter"),h=e("component-inherit"),p=e("debug")("engine.io-client:polling-xhr");t.exports=s,t.exports.Request=a,h(s,c),s.prototype.supportsBinary=!0,s.prototype.request=function(e){return e=e||{},e.uri=this.uri(),e.xd=this.xd,e.xs=this.xs,e.agent=this.agent||!1,e.supportsBinary=this.supportsBinary,e.enablesXDR=this.enablesXDR,new a(e)},s.prototype.doWrite=function(e,t){var n="string"!=typeof e&&void 0!==e,i=this.request({method:"POST",data:e,isBinary:n}),s=this;i.on("success",t),i.on("error",function(e){s.onError("xhr post error",e)}),this.sendXhr=i},s.prototype.doPoll=function(){p("xhr poll");var e=this.request(),t=this;e.on("data",function(e){t.onData(e)}),e.on("error",function(e){t.onError("xhr poll error",e)}),this.pollXhr=e},l(a.prototype),a.prototype.create=function(){var e=this.xhr=new r({agent:this.agent,xdomain:this.xd,xscheme:this.xs,enablesXDR:this.enablesXDR}),t=this;try{if(p("xhr open %s: %s",this.method,this.uri),e.open(this.method,this.uri,this.async),this.supportsBinary&&(e.responseType="arraybuffer"),"POST"==this.method)try{this.isBinary?e.setRequestHeader("Content-type","application/octet-stream"):e.setRequestHeader("Content-type","text/plain;charset=UTF-8")}catch(i){}"withCredentials"in e&&(e.withCredentials=!0),this.hasXDR()?(e.onload=function(){t.onLoad()},e.onerror=function(){t.onError(e.responseText)}):e.onreadystatechange=function(){4==e.readyState&&(200==e.status||1223==e.status?t.onLoad():setTimeout(function(){t.onError(e.status)},0))},p("xhr data %s",this.data),e.send(this.data)}catch(i){return void setTimeout(function(){t.onError(i)},0)}n.document&&(this.index=a.requestsCount++,a.requests[this.index]=this)},a.prototype.onSuccess=function(){this.emit("success"),this.cleanup()},a.prototype.onData=function(e){this.emit("data",e),this.onSuccess()},a.prototype.onError=function(e){this.emit("error",e),this.cleanup()},a.prototype.cleanup=function(){if("undefined"!=typeof this.xhr&&null!==this.xhr){this.hasXDR()?this.xhr.onload=this.xhr.onerror=i:this.xhr.onreadystatechange=i;try{this.xhr.abort()}catch(e){}n.document&&delete a.requests[this.index],this.xhr=null}},a.prototype.onLoad=function(){var e;try{var t;try{t=this.xhr.getResponseHeader("Content-Type")}catch(n){}e="application/octet-stream"===t?this.xhr.response:this.supportsBinary?"ok":this.xhr.responseText}catch(n){this.onError(n)}null!=e&&this.onData(e)},a.prototype.hasXDR=function(){return"undefined"!=typeof n.XDomainRequest&&!this.xs&&this.enablesXDR},a.prototype.abort=function(){this.cleanup()},n.document&&(a.requestsCount=0,a.requests={},n.attachEvent?n.attachEvent("onunload",o):n.addEventListener&&n.addEventListener("beforeunload",o))}).call(this,"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./polling":8,"component-emitter":12,"component-inherit":13,debug:14,xmlhttprequest:10}],8:[function(e,t){function n(e){var t=e&&e.forceBase64;(!c||t)&&(this.supportsBinary=!1),i.call(this,e)}var i=e("../transport"),s=e("parseqs"),a=e("engine.io-parser"),o=e("component-inherit"),r=e("debug")("engine.io-client:polling");t.exports=n;var c=function(){var t=e("xmlhttprequest"),n=new t({agent:this.agent,xdomain:!1});return null!=n.responseType}();o(n,i),n.prototype.name="polling",n.prototype.doOpen=function(){this.poll()},n.prototype.pause=function(e){function t(){r("paused"),n.readyState="paused",e()}var n=this;if(this.readyState="pausing",this.polling||!this.writable){var i=0;this.polling&&(r("we are currently polling - waiting to pause"),i++,this.once("pollComplete",function(){r("pre-pause polling complete"),--i||t()})),this.writable||(r("we are currently writing - waiting to pause"),i++,this.once("drain",function(){r("pre-pause writing complete"),--i||t()}))}else t()},n.prototype.poll=function(){r("polling"),this.polling=!0,this.doPoll(),this.emit("poll")},n.prototype.onData=function(e){var t=this;r("polling got data %s",e);var n=function(e){return"opening"==t.readyState&&t.onOpen(),"close"==e.type?(t.onClose(),!1):void t.onPacket(e)};a.decodePayload(e,this.socket.binaryType,n),"closed"!=this.readyState&&(this.polling=!1,this.emit("pollComplete"),"open"==this.readyState?this.poll():r('ignoring poll - transport state "%s"',this.readyState))},n.prototype.doClose=function(){function e(){r("writing close packet"),t.write([{type:"close"}])}var t=this;"open"==this.readyState?(r("transport open - closing"),e()):(r("transport not open - deferring close"),this.once("open",e))},n.prototype.write=function(e){var t=this;this.writable=!1;var n=function(){t.writable=!0,t.emit("drain")},t=this;a.encodePayload(e,this.supportsBinary,function(e){t.doWrite(e,n)})},n.prototype.uri=function(){var e=this.query||{},t=this.secure?"https":"http",n="";return!1!==this.timestampRequests&&(e[this.timestampParam]=+new Date+"-"+i.timestamps++),this.supportsBinary||e.sid||(e.b64=1),e=s.encode(e),this.port&&("https"==t&&443!=this.port||"http"==t&&80!=this.port)&&(n=":"+this.port),e.length&&(e="?"+e),t+"://"+this.hostname+n+this.path+e}},{"../transport":4,"component-inherit":13,debug:14,"engine.io-parser":15,parseqs:25,xmlhttprequest:10}],9:[function(e,t){function n(e){var t=e&&e.forceBase64;t&&(this.supportsBinary=!1),i.call(this,e)}var i=e("../transport"),s=e("engine.io-parser"),a=e("parseqs"),o=e("component-inherit"),r=e("debug")("engine.io-client:websocket"),c=e("ws");t.exports=n,o(n,i),n.prototype.name="websocket",n.prototype.supportsBinary=!0,n.prototype.doOpen=function(){if(this.check()){var e=this.uri(),t=void 0,n={agent:this.agent};this.ws=new c(e,t,n),void 0===this.ws.binaryType&&(this.supportsBinary=!1),this.ws.binaryType="arraybuffer",this.addEventListeners()}},n.prototype.addEventListeners=function(){var e=this;this.ws.onopen=function(){e.onOpen()},this.ws.onclose=function(){e.onClose()},this.ws.onmessage=function(t){e.onData(t.data)},this.ws.onerror=function(t){e.onError("websocket error",t)}},"undefined"!=typeof navigator&&/iPad|iPhone|iPod/i.test(navigator.userAgent)&&(n.prototype.onData=function(e){var t=this;setTimeout(function(){i.prototype.onData.call(t,e)},0)}),n.prototype.write=function(e){function t(){n.writable=!0,n.emit("drain")}var n=this;this.writable=!1;for(var i=0,a=e.length;a>i;i++)s.encodePacket(e[i],this.supportsBinary,function(e){try{n.ws.send(e)}catch(t){r("websocket closed before onclose event")}});setTimeout(t,0)},n.prototype.onClose=function(){i.prototype.onClose.call(this)},n.prototype.doClose=function(){"undefined"!=typeof this.ws&&this.ws.close()},n.prototype.uri=function(){var e=this.query||{},t=this.secure?"wss":"ws",n="";return this.port&&("wss"==t&&443!=this.port||"ws"==t&&80!=this.port)&&(n=":"+this.port),this.timestampRequests&&(e[this.timestampParam]=+new Date),this.supportsBinary||(e.b64=1),e=a.encode(e),e.length&&(e="?"+e),t+"://"+this.hostname+n+this.path+e},n.prototype.check=function(){return!(!c||"__initialize"in c&&this.name===n.prototype.name)}},{"../transport":4,"component-inherit":13,debug:14,"engine.io-parser":15,parseqs:25,ws:27}],10:[function(e,t){var n=e("has-cors");t.exports=function(e){var t=e.xdomain,i=e.xscheme,s=e.enablesXDR;try{if("undefined"!=typeof XDomainRequest&&!i&&s)return new XDomainRequest}catch(a){}try{if("undefined"!=typeof XMLHttpRequest&&(!t||n))return new XMLHttpRequest}catch(a){}if(!t)try{return new ActiveXObject("Microsoft.XMLHTTP")}catch(a){}}},{"has-cors":21}],11:[function(e,t){(function(e){function n(e,t){t=t||{};for(var n=new i,s=0;s<e.length;s++)n.append(e[s]);return t.type?n.getBlob(t.type):n.getBlob()}var i=e.BlobBuilder||e.WebKitBlobBuilder||e.MSBlobBuilder||e.MozBlobBuilder,s=function(){try{var e=new Blob(["hi"]);return 2==e.size}catch(t){return!1}}(),a=i&&i.prototype.append&&i.prototype.getBlob;t.exports=function(){return s?e.Blob:a?n:void 0}()}).call(this,"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],12:[function(e,t){function n(e){return e?i(e):void 0}function i(e){for(var t in n.prototype)e[t]=n.prototype[t];return e}t.exports=n,n.prototype.on=n.prototype.addEventListener=function(e,t){return this._callbacks=this._callbacks||{},(this._callbacks[e]=this._callbacks[e]||[]).push(t),this},n.prototype.once=function(e,t){function n(){i.off(e,n),t.apply(this,arguments)}var i=this;return this._callbacks=this._callbacks||{},n.fn=t,this.on(e,n),this},n.prototype.off=n.prototype.removeListener=n.prototype.removeAllListeners=n.prototype.removeEventListener=function(e,t){if(this._callbacks=this._callbacks||{},0==arguments.length)return this._callbacks={},this;var n=this._callbacks[e];if(!n)return this;if(1==arguments.length)return delete this._callbacks[e],this;for(var i,s=0;s<n.length;s++)if(i=n[s],i===t||i.fn===t){n.splice(s,1);break}return this},n.prototype.emit=function(e){this._callbacks=this._callbacks||{};var t=[].slice.call(arguments,1),n=this._callbacks[e];if(n){n=n.slice(0);for(var i=0,s=n.length;s>i;++i)n[i].apply(this,t)}return this},n.prototype.listeners=function(e){return this._callbacks=this._callbacks||{},this._callbacks[e]||[]},n.prototype.hasListeners=function(e){return!!this.listeners(e).length}},{}],13:[function(e,t){t.exports=function(e,t){var n=function(){};n.prototype=t.prototype,e.prototype=new n,e.prototype.constructor=e}},{}],14:[function(e,t){function n(e){return n.enabled(e)?function(t){t=i(t);var s=new Date,a=s-(n[e]||s);n[e]=s,t=e+" "+t+" +"+n.humanize(a),window.console&&console.log&&Function.prototype.apply.call(console.log,console,arguments)}:function(){}}function i(e){return e instanceof Error?e.stack||e.message:e}t.exports=n,n.names=[],n.skips=[],n.enable=function(e){try{localStorage.debug=e}catch(t){}for(var i=(e||"").split(/[\s,]+/),s=i.length,a=0;s>a;a++)e=i[a].replace("*",".*?"),"-"===e[0]?n.skips.push(new RegExp("^"+e.substr(1)+"$")):n.names.push(new RegExp("^"+e+"$"))},n.disable=function(){n.enable("")},n.humanize=function(e){var t=1e3,n=6e4,i=60*n;return e>=i?(e/i).toFixed(1)+"h":e>=n?(e/n).toFixed(1)+"m":e>=t?(e/t|0)+"s":e+"ms"},n.enabled=function(e){for(var t=0,i=n.skips.length;i>t;t++)if(n.skips[t].test(e))return!1;for(var t=0,i=n.names.length;i>t;t++)if(n.names[t].test(e))return!0;return!1};try{window.localStorage&&n.enable(localStorage.debug)}catch(s){}},{}],15:[function(e,t,n){(function(t){function i(e,t,i){if(!t)return n.encodeBase64Packet(e,i);var s=e.data,a=new Uint8Array(s),o=new Uint8Array(1+s.byteLength);o[0]=u[e.type];for(var r=0;r<a.length;r++)o[r+1]=a[r];return i(o.buffer)}function s(e,t,i){if(!t)return n.encodeBase64Packet(e,i);var s=new FileReader;return s.onload=function(){e.data=s.result,n.encodePacket(e,t,!0,i)},s.readAsArrayBuffer(e.data)}function a(e,t,i){if(!t)return n.encodeBase64Packet(e,i);if(d)return s(e,t,i);var a=new Uint8Array(1);a[0]=u[e.type];var o=new g([a.buffer,e.data]);return i(o)}function o(e,t,n){for(var i=new Array(e.length),s=h(e.length,n),a=function(e,n,s){t(n,function(t,n){i[e]=n,s(t,i)})},o=0;o<e.length;o++)a(o,e[o],s)}var r=e("./keys"),c=e("arraybuffer.slice"),l=e("base64-arraybuffer"),h=e("after"),p=e("utf8"),d=navigator.userAgent.match(/Android/i);n.protocol=3;var u=n.packets={open:0,close:1,ping:2,pong:3,message:4,upgrade:5,noop:6},f=r(u),m={type:"error",data:"parser error"},g=e("blob");n.encodePacket=function(e,n,s,o){"function"==typeof n&&(o=n,n=!1),"function"==typeof s&&(o=s,s=null);var r=void 0===e.data?void 0:e.data.buffer||e.data;if(t.ArrayBuffer&&r instanceof ArrayBuffer)return i(e,n,o);if(g&&r instanceof t.Blob)return a(e,n,o);var c=u[e.type];return void 0!==e.data&&(c+=s?p.encode(String(e.data)):String(e.data)),o(""+c)},n.encodeBase64Packet=function(e,i){var s="b"+n.packets[e.type];if(g&&e.data instanceof g){var a=new FileReader;return a.onload=function(){var e=a.result.split(",")[1];i(s+e)},a.readAsDataURL(e.data)}var o;try{o=String.fromCharCode.apply(null,new Uint8Array(e.data))}catch(r){for(var c=new Uint8Array(e.data),l=new Array(c.length),h=0;h<c.length;h++)l[h]=c[h];o=String.fromCharCode.apply(null,l)}return s+=t.btoa(o),i(s)},n.decodePacket=function(e,t,i){if("string"==typeof e||void 0===e){if("b"==e.charAt(0))return n.decodeBase64Packet(e.substr(1),t);if(i)try{e=p.decode(e)}catch(s){return m}var a=e.charAt(0);return Number(a)==a&&f[a]?e.length>1?{type:f[a],data:e.substring(1)}:{type:f[a]}:m}var o=new Uint8Array(e),a=o[0],r=c(e,1);return g&&"blob"===t&&(r=new g([r])),{type:f[a],data:r}},n.decodeBase64Packet=function(e,n){var i=f[e.charAt(0)];if(!t.ArrayBuffer)return{type:i,data:{base64:!0,data:e.substr(1)}};var s=l.decode(e.substr(1));return"blob"===n&&g&&(s=new g([s])),{type:i,data:s}},n.encodePayload=function(e,t,i){function s(e){return e.length+":"+e}function a(e,i){n.encodePacket(e,t,!0,function(e){i(null,s(e))})}return"function"==typeof t&&(i=t,t=null),t?g&&!d?n.encodePayloadAsBlob(e,i):n.encodePayloadAsArrayBuffer(e,i):e.length?void o(e,a,function(e,t){return i(t.join(""))}):i("0:")},n.decodePayload=function(e,t,i){if("string"!=typeof e)return n.decodePayloadAsBinary(e,t,i);"function"==typeof t&&(i=t,t=null);var s;if(""==e)return i(m,0,1);for(var a,o,r="",c=0,l=e.length;l>c;c++){var h=e.charAt(c);if(":"!=h)r+=h;else{if(""==r||r!=(a=Number(r)))return i(m,0,1);if(o=e.substr(c+1,a),r!=o.length)return i(m,0,1);if(o.length){if(s=n.decodePacket(o,t,!0),m.type==s.type&&m.data==s.data)return i(m,0,1);var p=i(s,c+a,l);if(!1===p)return}c+=a,r=""}}return""!=r?i(m,0,1):void 0},n.encodePayloadAsArrayBuffer=function(e,t){function i(e,t){n.encodePacket(e,!0,!0,function(e){return t(null,e)})}return e.length?void o(e,i,function(e,n){var i=n.reduce(function(e,t){var n;return n="string"==typeof t?t.length:t.byteLength,e+n.toString().length+n+2},0),s=new Uint8Array(i),a=0;return n.forEach(function(e){var t="string"==typeof e,n=e;if(t){for(var i=new Uint8Array(e.length),o=0;o<e.length;o++)i[o]=e.charCodeAt(o);n=i.buffer}s[a++]=t?0:1;for(var r=n.byteLength.toString(),o=0;o<r.length;o++)s[a++]=parseInt(r[o]);s[a++]=255;for(var i=new Uint8Array(n),o=0;o<i.length;o++)s[a++]=i[o]}),t(s.buffer)}):t(new ArrayBuffer(0))},n.encodePayloadAsBlob=function(e,t){function i(e,t){n.encodePacket(e,!0,!0,function(e){var n=new Uint8Array(1);if(n[0]=1,"string"==typeof e){for(var i=new Uint8Array(e.length),s=0;s<e.length;s++)i[s]=e.charCodeAt(s);e=i.buffer,n[0]=0}for(var a=e instanceof ArrayBuffer?e.byteLength:e.size,o=a.toString(),r=new Uint8Array(o.length+1),s=0;s<o.length;s++)r[s]=parseInt(o[s]);if(r[o.length]=255,g){var c=new g([n.buffer,r.buffer,e]);t(null,c)}})}o(e,i,function(e,n){return t(new g(n))})},n.decodePayloadAsBinary=function(e,t,i){"function"==typeof t&&(i=t,t=null);for(var s=e,a=[],o=!1;s.byteLength>0;){for(var r=new Uint8Array(s),l=0===r[0],h="",p=1;255!=r[p];p++){if(h.length>310){o=!0;break}h+=r[p]}if(o)return i(m,0,1);s=c(s,2+h.length),h=parseInt(h);var d=c(s,0,h);if(l)try{d=String.fromCharCode.apply(null,new Uint8Array(d))}catch(u){var f=new Uint8Array(d);d="";for(var p=0;p<f.length;p++)d+=String.fromCharCode(f[p])}a.push(d),s=c(s,h)}var g=a.length;a.forEach(function(e,s){i(n.decodePacket(e,t,!0),s,g)})}}).call(this,"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./keys":16,after:17,"arraybuffer.slice":18,"base64-arraybuffer":19,blob:11,utf8:20}],16:[function(e,t){t.exports=Object.keys||function(e){var t=[],n=Object.prototype.hasOwnProperty;for(var i in e)n.call(e,i)&&t.push(i);return t}},{}],17:[function(e,t){function n(e,t,n){function s(e,i){if(s.count<=0)throw new Error("after called too many times");--s.count,e?(a=!0,t(e),t=n):0!==s.count||a||t(null,i)}var a=!1;return n=n||i,s.count=e,0===e?t():s}function i(){}t.exports=n},{}],18:[function(e,t){t.exports=function(e,t,n){var i=e.byteLength;if(t=t||0,n=n||i,e.slice)return e.slice(t,n);if(0>t&&(t+=i),0>n&&(n+=i),n>i&&(n=i),t>=i||t>=n||0===i)return new ArrayBuffer(0);for(var s=new Uint8Array(e),a=new Uint8Array(n-t),o=t,r=0;n>o;o++,r++)a[r]=s[o];return a.buffer}},{}],19:[function(e,t,n){!function(e){"use strict";n.encode=function(t){var n,i=new Uint8Array(t),s=i.length,a="";for(n=0;s>n;n+=3)a+=e[i[n]>>2],a+=e[(3&i[n])<<4|i[n+1]>>4],a+=e[(15&i[n+1])<<2|i[n+2]>>6],a+=e[63&i[n+2]];return s%3===2?a=a.substring(0,a.length-1)+"=":s%3===1&&(a=a.substring(0,a.length-2)+"=="),a},n.decode=function(t){var n,i,s,a,o,r=.75*t.length,c=t.length,l=0;"="===t[t.length-1]&&(r--,"="===t[t.length-2]&&r--);var h=new ArrayBuffer(r),p=new Uint8Array(h);for(n=0;c>n;n+=4)i=e.indexOf(t[n]),s=e.indexOf(t[n+1]),a=e.indexOf(t[n+2]),o=e.indexOf(t[n+3]),p[l++]=i<<2|s>>4,p[l++]=(15&s)<<4|a>>2,p[l++]=(3&a)<<6|63&o;return h}}("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")},{}],20:[function(t,n,i){(function(t){!function(s){function a(e){for(var t,n,i=[],s=0,a=e.length;a>s;)t=e.charCodeAt(s++),t>=55296&&56319>=t&&a>s?(n=e.charCodeAt(s++),56320==(64512&n)?i.push(((1023&t)<<10)+(1023&n)+65536):(i.push(t),s--)):i.push(t);return i}function o(e){for(var t,n=e.length,i=-1,s="";++i<n;)t=e[i],t>65535&&(t-=65536,s+=w(t>>>10&1023|55296),t=56320|1023&t),s+=w(t);return s}function r(e,t){return w(e>>t&63|128)}function c(e){if(0==(4294967168&e))return w(e);var t="";return 0==(4294965248&e)?t=w(e>>6&31|192):0==(4294901760&e)?(t=w(e>>12&15|224),t+=r(e,6)):0==(4292870144&e)&&(t=w(e>>18&7|240),t+=r(e,12),t+=r(e,6)),t+=w(63&e|128)}function l(e){for(var t,n=a(e),i=n.length,s=-1,o="";++s<i;)t=n[s],o+=c(t);return o}function h(){if(v>=_)throw Error("Invalid byte index");var e=255&g[v];if(v++,128==(192&e))return 63&e;throw Error("Invalid continuation byte")}function p(){var e,t,n,i,s;if(v>_)throw Error("Invalid byte index");if(v==_)return!1;if(e=255&g[v],v++,0==(128&e))return e;if(192==(224&e)){var t=h();if(s=(31&e)<<6|t,s>=128)return s;throw Error("Invalid continuation byte")}if(224==(240&e)){if(t=h(),n=h(),s=(15&e)<<12|t<<6|n,s>=2048)return s;throw Error("Invalid continuation byte")}if(240==(248&e)&&(t=h(),n=h(),i=h(),s=(15&e)<<18|t<<12|n<<6|i,s>=65536&&1114111>=s))return s;throw Error("Invalid UTF-8 detected")}function d(e){g=a(e),_=g.length,v=0;for(var t,n=[];(t=p())!==!1;)n.push(t);return o(n)}var u="object"==typeof i&&i,f="object"==typeof n&&n&&n.exports==u&&n,m="object"==typeof t&&t;(m.global===m||m.window===m)&&(s=m);var g,_,v,w=String.fromCharCode,b={version:"2.0.0",encode:l,decode:d};
+if("function"==typeof e&&"object"==typeof e.amd&&e.amd)e(function(){return b});else if(u&&!u.nodeType)if(f)f.exports=b;else{var k={},y=k.hasOwnProperty;for(var $ in b)y.call(b,$)&&(u[$]=b[$])}else s.utf8=b}(this)}).call(this,"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],21:[function(e,t){var n=e("global");try{t.exports="XMLHttpRequest"in n&&"withCredentials"in new n.XMLHttpRequest}catch(i){t.exports=!1}},{global:22}],22:[function(e,t){t.exports=function(){return this}()},{}],23:[function(e,t){var n=[].indexOf;t.exports=function(e,t){if(n)return e.indexOf(t);for(var i=0;i<e.length;++i)if(e[i]===t)return i;return-1}},{}],24:[function(e,t){(function(e){var n=/^[\],:{}\s]*$/,i=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,s=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,a=/(?:^|:|,)(?:\s*\[)+/g,o=/^\s+/,r=/\s+$/;t.exports=function(t){return"string"==typeof t&&t?(t=t.replace(o,"").replace(r,""),e.JSON&&JSON.parse?JSON.parse(t):n.test(t.replace(i,"@").replace(s,"]").replace(a,""))?new Function("return "+t)():void 0):null}}).call(this,"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],25:[function(e,t,n){n.encode=function(e){var t="";for(var n in e)e.hasOwnProperty(n)&&(t.length&&(t+="&"),t+=encodeURIComponent(n)+"="+encodeURIComponent(e[n]));return t},n.decode=function(e){for(var t={},n=e.split("&"),i=0,s=n.length;s>i;i++){var a=n[i].split("=");t[decodeURIComponent(a[0])]=decodeURIComponent(a[1])}return t}},{}],26:[function(e,t){var n=/^(?:(?![^:@]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/,i=["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"];t.exports=function(e){var t=e,s=e.indexOf("["),a=e.indexOf("]");-1!=s&&-1!=a&&(e=e.substring(0,s)+e.substring(s,a).replace(/:/g,";")+e.substring(a,e.length));for(var o=n.exec(e||""),r={},c=14;c--;)r[i[c]]=o[c]||"";return-1!=s&&-1!=a&&(r.source=t,r.host=r.host.substring(1,r.host.length-1).replace(/;/g,":"),r.authority=r.authority.replace("[","").replace("]","").replace(/;/g,":"),r.ipv6uri=!0),r}},{}],27:[function(e,t){function n(e,t){var n;return n=t?new s(e,t):new s(e)}var i=function(){return this}(),s=i.WebSocket||i.MozWebSocket;t.exports=s?n:null,s&&(n.prototype=s.prototype)},{}]},{},[1])(1)});var EngineioTools={ReconnectingSocket:function(e,t){function n(){c=!0,l=!1,g=!1,f=0,p=0,clearTimeout(m)}function i(){c=!1,g||l||o()}function s(){l&&o()}function a(){g=!0,r.call(_)}function o(){if(f>=u)return void _.emit("reconnecting_failed");var e=d?2*(p||h/2):h*f;l=!0,m=setTimeout(function(){_.open()},e),p=e,_.emit("reconnecting",{attempt:f+1,max_attempts:u,delay:e}),f++}var r,c=!1,l=!1,h=4e3,p=0,d=!0,u=5,f=0,m=null,g=!1,_=eio.apply(eio,arguments);return _.on("open",n),_.on("close",i),_.on("error",s),r=_.close,_.close=a,t&&("number"==typeof t.reconnect_delay&&(h=t.reconnect_delay),"number"==typeof t.reconnect_max_attempts&&(u=t.reconnect_max_attempts),"undefined"!=typeof t.reconnect_delay_exponential&&(d=!!t.reconnect_delay_exponential)),_},Rpc:function(){function e(e){var n=new t(e),i=function(){return i.makeCall.apply(i,arguments)};for(var s in n)i[s]=n[s];return i._mixinEmitter(),i._bindSocketListeners(),i._rpc=i,i}function t(e){this._next_id=0,this._rpc_callbacks={},this._socket=e,this._rpc=this,this._namespace="",this._namespaces=[]}return t.prototype._bindSocketListeners=function(){var e=this;this._onMessageProxy=function(){e._onMessage.apply(e,arguments)},this._socket.on("message",this._onMessageProxy)},t.prototype.dispose=function(){this._onMessageProxy&&(this._socket.removeListener("message",this._onMessageProxy),delete this._onMessageProxy);for(var e in this._namespaces)this._namespaces[e].dispose();this.removeAllListeners()},t.prototype.namespace=function(e){var t,n;return t=this._namespace?this._namespace+"."+e:e,n=new this._rpc.Namespace(this._rpc,t),this._rpc._namespaces.push(n),n},t.prototype._findRelevantNamespaces=function(e){var t=[];for(var n in this._namespaces)this._namespaces[n]._namespace===e&&t.push(this._namespaces[n]),0===this._namespaces[n]._namespace.indexOf(e+".")&&t.push(this._namespaces[n]);return t},t.prototype._mixinEmitter=function(e){var t=["on","once","off","removeListener","removeAllListeners","emit","listeners","hasListeners"];e=e||this;for(var n=0;n<t.length;n++)"function"==typeof this._socket[t[n]]&&(e[t[n]]=this._socket[t[n]])},t.prototype._isCall=function(e){return"undefined"!=typeof e.method&&"undefined"!=typeof e.params},t.prototype._isResponse=function(e){return"undefined"!=typeof e.id&&"undefined"!=typeof e.response},t.prototype.makeCall=function(e){var t,n,i;t=Array.prototype.slice.call(arguments,1,arguments.length),"function"==typeof t[t.length-1]&&(n=t[t.length-1],t=t.slice(0,t.length-1)),i={method:e,params:t},"function"==typeof n&&(i.id=this._next_id,this._next_id++,this._rpc_callbacks[i.id]=n),this.send(i)},t.prototype.send=function(e){this._socket&&this._socket.send(JSON.stringify(e))},t.prototype._onMessage=function(e){var t,n,i,s,a,o;try{if(t=JSON.parse(e),!t)throw"Corrupt packet"}catch(r){return}if(this._isResponse(t)){if("function"!=typeof this._rpc_callbacks[t.id])return;i=this._rpc_callbacks[t.id],delete this._rpc_callbacks[t.id],i.apply(this,t.response)}else if(this._isCall(t)&&(n="undefined"!=typeof t.id?this._createReturnCallFn(t.id):this._noop,this.emit.apply(this,["all",t.method,n].concat(t.params)),this.emit.apply(this,[t.method,n].concat(t.params)),t.method.indexOf(".")>0)){s=t.method.substring(0,t.method.lastIndexOf(".")),a=this._findRelevantNamespaces(s);for(o in a)t.method=t.method.replace(a[o]._namespace+".",""),a[o].emit.apply(a[o],[t.method,n].concat(t.params))}},t.prototype._createReturnCallFn=function(e){var t=this;return function(){var n=Array.prototype.slice.call(arguments,0),i={id:e,response:n};t.send(i)}},t.prototype._noop=function(){},t.prototype.Namespace=function(e,t){var n=function(){return"undefined"!=typeof arguments[0]?(arguments[0]=n._namespace+"."+arguments[0],n._rpc.apply(n._rpc,arguments)):void 0};return n._rpc=e,n._namespace=t,n.dispose=function(){n.removeAllListeners(),n._rpc=null},e._mixinEmitter(n),n},e}()};
\ No newline at end of file
--- /dev/null
+ /*
+ @licstart The following is the entire license notice for the
+ JavaScript code in this page.
+
+ Copyright (C) 2014 Loic J. Duros
+
+ The JavaScript code in this page is free software: you can
+ redistribute it and/or modify it under the terms of the GNU
+ General Public License (GNU GPL) as published by the Free Software
+ Foundation, either version 3 of the License, or (at your option)
+ any later version. The code is distributed WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS
+ FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
+
+ As additional permission under GNU GPL version 3 section 7, you
+ may distribute non-source (e.g., minimized or compacted) forms of
+ that code without the copy of the GNU GPL normally required by
+ section 4, provided you include this license notice and a URL
+ through which recipients can access the Corresponding Source.
+
+
+ @licend The above is the entire license notice
+ for the JavaScript code in this page.
+ */
+
+
+(function (global, undefined) {
+
+// Holds anything kiwi client specific (ie. front, gateway, _kiwi.plugs..)\r
+/**\r
+* @namespace\r
+*/\r
+var _kiwi = {};\r
+\r
+_kiwi.misc = {};\r
+_kiwi.model = {};\r
+_kiwi.view = {};\r
+_kiwi.applets = {};\r
+_kiwi.utils = {};\r
+\r
+\r
+/**\r
+ * A global container for third party access\r
+ * Will be used to access a limited subset of kiwi functionality\r
+ * and data (think: plugins)\r
+ */\r
+_kiwi.global = {\r
+ build_version: '', // Kiwi IRC version this is built from (Set from index.html)\r
+ settings: undefined, // Instance of _kiwi.model.DataStore\r
+ plugins: undefined, // Instance of _kiwi.model.PluginManager\r
+ events: undefined, // Instance of PluginInterface\r
+ rpc: undefined, // Instance of WebsocketRpc\r
+ utils: {}, // References to misc. re-usable helpers / functions\r
+\r
+ // Make public some internal utils for plugins to make use of\r
+ initUtils: function() {\r
+ this.utils.randomString = randomString;\r
+ this.utils.secondsToTime = secondsToTime;\r
+ this.utils.parseISO8601 = parseISO8601;\r
+ this.utils.escapeRegex = escapeRegex;\r
+ this.utils.formatIRCMsg = formatIRCMsg;\r
+ this.utils.styleText = styleText;\r
+ this.utils.hsl2rgb = hsl2rgb;\r
+\r
+ this.utils.notifications = _kiwi.utils.notifications;\r
+ this.utils.formatDate = _kiwi.utils.formatDate;\r
+ },\r
+\r
+ addMediaMessageType: function(match, buildHtml) {\r
+ _kiwi.view.MediaMessage.addType(match, buildHtml);\r
+ },\r
+\r
+ // Event managers for plugins\r
+ components: {\r
+ EventComponent: function(event_source, proxy_event_name) {\r
+ /*\r
+ * proxyEvent() listens for events then re-triggers them on its own\r
+ * event emitter. Why? So we can .off() on this emitter without\r
+ * effecting the source of events. Handy for plugins that we don't\r
+ * trust meddling with the core events.\r
+ *\r
+ * If listening for 'all' events the arguments are as follows:\r
+ * 1. Name of the triggered event\r
+ * 2. The event data\r
+ * For all other events, we only have one argument:\r
+ * 1. The event data\r
+ *\r
+ * When this is used via `new kiwi.components.Network()`, this listens\r
+ * for 'all' events so the first argument is the event name which is\r
+ * the connection ID. We don't want to re-trigger this event name so\r
+ * we need to juggle the arguments to find the real event name we want\r
+ * to emit.\r
+ */\r
+ function proxyEvent(event_name, event_data) {\r
+ if (proxy_event_name == 'all') {\r
+ } else {\r
+ event_data = event_name.event_data;\r
+ event_name = event_name.event_name;\r
+ }\r
+\r
+ this.trigger(event_name, event_data);\r
+ }\r
+\r
+ // The event we are to proxy\r
+ proxy_event_name = proxy_event_name || 'all';\r
+\r
+ _.extend(this, Backbone.Events);\r
+ this._source = event_source;\r
+\r
+ // Proxy the events to this dispatcher\r
+ event_source.on(proxy_event_name, proxyEvent, this);\r
+\r
+ // Clean up this object\r
+ this.dispose = function () {\r
+ event_source.off(proxy_event_name, proxyEvent);\r
+ this.off();\r
+ delete this.event_source;\r
+ };\r
+ },\r
+\r
+ Network: function(connection_id) {\r
+ var connection_event;\r
+\r
+ // If no connection id given, use all connections\r
+ if (typeof connection_id !== 'undefined') {\r
+ connection_event = 'connection:' + connection_id.toString();\r
+ } else {\r
+ connection_event = 'connection';\r
+ }\r
+\r
+ // Helper to get the network object\r
+ var getNetwork = function() {\r
+ var network = typeof connection_id === 'undefined' ?\r
+ _kiwi.app.connections.active_connection :\r
+ _kiwi.app.connections.getByConnectionId(connection_id);\r
+\r
+ return network ?\r
+ network :\r
+ undefined;\r
+ };\r
+\r
+ // Create the return object (events proxy from the gateway)\r
+ var obj = new this.EventComponent(_kiwi.gateway, connection_event);\r
+\r
+ // Proxy several gateway functions onto the return object\r
+ var funcs = {\r
+ kiwi: 'kiwi', raw: 'raw', kick: 'kick', topic: 'topic',\r
+ part: 'part', join: 'join', action: 'action', ctcp: 'ctcp',\r
+ ctcpRequest: 'ctcpRequest', ctcpResponse: 'ctcpResponse',\r
+ notice: 'notice', msg: 'privmsg', say: 'privmsg',\r
+ changeNick: 'changeNick', channelInfo: 'channelInfo',\r
+ mode: 'mode', quit: 'quit'\r
+ };\r
+\r
+ _.each(funcs, function(gateway_fn, func_name) {\r
+ obj[func_name] = function() {\r
+ var fn_name = gateway_fn;\r
+\r
+ // Add connection_id to the argument list\r
+ var args = Array.prototype.slice.call(arguments, 0);\r
+ args.unshift(connection_id);\r
+\r
+ // Call the gateway function on behalf of this connection\r
+ return _kiwi.gateway[fn_name].apply(_kiwi.gateway, args);\r
+ };\r
+ });\r
+\r
+ // Now for some network related functions...\r
+ obj.createQuery = function(nick) {\r
+ var network, restricted_keys;\r
+\r
+ network = getNetwork();\r
+ if (!network) {\r
+ return;\r
+ }\r
+\r
+ return network.createQuery(nick);\r
+ };\r
+\r
+ // Add the networks getters/setters\r
+ obj.get = function(name) {\r
+ var network, restricted_keys;\r
+\r
+ network = getNetwork();\r
+ if (!network) {\r
+ return;\r
+ }\r
+\r
+ restricted_keys = [\r
+ 'password'\r
+ ];\r
+ if (restricted_keys.indexOf(name) > -1) {\r
+ return undefined;\r
+ }\r
+\r
+ return network.get(name);\r
+ };\r
+\r
+ obj.set = function() {\r
+ var network = getNetwork();\r
+ if (!network) {\r
+ return;\r
+ }\r
+\r
+ return network.set.apply(network, arguments);\r
+ };\r
+\r
+ return obj;\r
+ },\r
+\r
+ ControlInput: function() {\r
+ var obj = new this.EventComponent(_kiwi.app.controlbox);\r
+ var funcs = {\r
+ run: 'processInput', addPluginIcon: 'addPluginIcon'\r
+ };\r
+\r
+ _.each(funcs, function(controlbox_fn, func_name) {\r
+ obj[func_name] = function() {\r
+ var fn_name = controlbox_fn;\r
+ return _kiwi.app.controlbox[fn_name].apply(_kiwi.app.controlbox, arguments);\r
+ };\r
+ });\r
+\r
+ // Give access to the control input textarea\r
+ obj.input = _kiwi.app.controlbox.$('.inp');\r
+\r
+ return obj;\r
+ }\r
+ },\r
+\r
+ // Entry point to start the kiwi application\r
+ init: function (opts, callback) {\r
+ var locale_promise, theme_promise,\r
+ that = this;\r
+\r
+ opts = opts || {};\r
+\r
+ this.initUtils();\r
+\r
+ // Set up the settings datastore\r
+ _kiwi.global.settings = _kiwi.model.DataStore.instance('kiwi.settings');\r
+ _kiwi.global.settings.load();\r
+\r
+ // Set the window title\r
+ window.document.title = opts.server_settings.client.window_title || 'Kiwi IRC';\r
+\r
+ locale_promise = new Promise(function (resolve) {\r
+ var locale = _kiwi.global.settings.get('locale') || 'magic';\r
+ $.getJSON(opts.base_path + '/assets/locales/' + locale + '.json', function (locale) {\r
+ if (locale) {\r
+ that.i18n = new Jed(locale);\r
+ } else {\r
+ that.i18n = new Jed();\r
+ }\r
+ resolve();\r
+ });\r
+ });\r
+\r
+ theme_promise = new Promise(function (resolve) {\r
+ var text_theme = opts.server_settings.client.settings.text_theme || 'default';\r
+ $.getJSON(opts.base_path + '/assets/text_themes/' + text_theme + '.json', function(text_theme) {\r
+ opts.text_theme = text_theme;\r
+ resolve();\r
+ });\r
+ });\r
+\r
+\r
+ Promise.all([locale_promise, theme_promise]).then(function () {\r
+ _kiwi.app = new _kiwi.model.Application(opts);\r
+\r
+ // Start the client up\r
+ _kiwi.app.initializeInterfaces();\r
+\r
+ // Event emitter to let plugins interface with parts of kiwi\r
+ _kiwi.global.events = new PluginInterface();\r
+\r
+ // Now everything has started up, load the plugin manager for third party plugins\r
+ _kiwi.global.plugins = new _kiwi.model.PluginManager();\r
+\r
+ callback();\r
+\r
+ }).then(null, function(err) {\r
+ console.error(err.stack);\r
+ });\r
+ },\r
+\r
+ start: function() {\r
+ _kiwi.app.showStartup();\r
+ },\r
+\r
+ // Allow plugins to change the startup applet\r
+ registerStartupApplet: function(startup_applet_name) {\r
+ _kiwi.app.startup_applet_name = startup_applet_name;\r
+ },\r
+\r
+ /**\r
+ * Open a new IRC connection\r
+ * @param {Object} connection_details {nick, host, port, ssl, password, options}\r
+ * @param {Function} callback function(err, network){}\r
+ */\r
+ newIrcConnection: function(connection_details, callback) {\r
+ _kiwi.gateway.newConnection(connection_details, callback);\r
+ },\r
+\r
+\r
+ /**\r
+ * Taking settings from the server and URL, extract the default server/channel/nick settings\r
+ */\r
+ defaultServerSettings: function () {\r
+ var parts;\r
+ var defaults = {\r
+ nick: '',\r
+ server: '',\r
+ port: 6667,\r
+ ssl: false,\r
+ channel: '',\r
+ channel_key: ''\r
+ };\r
+ var uricheck;\r
+\r
+\r
+ /**\r
+ * Get any settings set by the server\r
+ * These settings may be changed in the server selection dialog or via URL parameters\r
+ */\r
+ if (_kiwi.app.server_settings.client) {\r
+ if (_kiwi.app.server_settings.client.nick)\r
+ defaults.nick = _kiwi.app.server_settings.client.nick;\r
+\r
+ if (_kiwi.app.server_settings.client.server)\r
+ defaults.server = _kiwi.app.server_settings.client.server;\r
+\r
+ if (_kiwi.app.server_settings.client.port)\r
+ defaults.port = _kiwi.app.server_settings.client.port;\r
+\r
+ if (_kiwi.app.server_settings.client.ssl)\r
+ defaults.ssl = _kiwi.app.server_settings.client.ssl;\r
+\r
+ if (_kiwi.app.server_settings.client.channel)\r
+ defaults.channel = _kiwi.app.server_settings.client.channel;\r
+\r
+ if (_kiwi.app.server_settings.client.channel_key)\r
+ defaults.channel_key = _kiwi.app.server_settings.client.channel_key;\r
+ }\r
+\r
+\r
+\r
+ /**\r
+ * Get any settings passed in the URL\r
+ * These settings may be changed in the server selection dialog\r
+ */\r
+\r
+ // Any query parameters first\r
+ if (getQueryVariable('nick'))\r
+ defaults.nick = getQueryVariable('nick');\r
+\r
+ if (window.location.hash)\r
+ defaults.channel = window.location.hash;\r
+\r
+\r
+ // Process the URL part by part, extracting as we go\r
+ parts = window.location.pathname.toString().replace(_kiwi.app.get('base_path'), '').split('/');\r
+\r
+ if (parts.length > 0) {\r
+ parts.shift();\r
+\r
+ if (parts.length > 0 && parts[0]) {\r
+ // Check to see if we're dealing with an irc: uri, or whether we need to extract the server/channel info from the HTTP URL path.\r
+ uricheck = parts[0].substr(0, 7).toLowerCase();\r
+ if ((uricheck === 'ircs%3a') || (uricheck.substr(0,6) === 'irc%3a')) {\r
+ parts[0] = decodeURIComponent(parts[0]);\r
+ // irc[s]://<host>[:<port>]/[<channel>[?<password>]]\r
+ uricheck = /^irc(s)?:(?:\/\/?)?([^:\/]+)(?::([0-9]+))?(?:(?:\/)([^\?]*)(?:(?:\?)(.*))?)?$/.exec(parts[0]);\r
+ /*\r
+ uricheck[1] = ssl (optional)\r
+ uricheck[2] = host\r
+ uricheck[3] = port (optional)\r
+ uricheck[4] = channel (optional)\r
+ uricheck[5] = channel key (optional, channel must also be set)\r
+ */\r
+ if (uricheck) {\r
+ if (typeof uricheck[1] !== 'undefined') {\r
+ defaults.ssl = true;\r
+ if (defaults.port === 6667) {\r
+ defaults.port = 6697;\r
+ }\r
+ }\r
+ defaults.server = uricheck[2];\r
+ if (typeof uricheck[3] !== 'undefined') {\r
+ defaults.port = uricheck[3];\r
+ }\r
+ if (typeof uricheck[4] !== 'undefined') {\r
+ defaults.channel = '#' + uricheck[4];\r
+ if (typeof uricheck[5] !== 'undefined') {\r
+ defaults.channel_key = uricheck[5];\r
+ }\r
+ }\r
+ }\r
+ parts = [];\r
+ } else {\r
+ // Extract the port+ssl if we find one\r
+ if (parts[0].search(/:/) > 0) {\r
+ defaults.port = parts[0].substring(parts[0].search(/:/) + 1);\r
+ defaults.server = parts[0].substring(0, parts[0].search(/:/));\r
+ if (defaults.port[0] === '+') {\r
+ defaults.port = parseInt(defaults.port.substring(1), 10);\r
+ defaults.ssl = true;\r
+ } else {\r
+ defaults.ssl = false;\r
+ }\r
+\r
+ } else {\r
+ defaults.server = parts[0];\r
+ }\r
+\r
+ parts.shift();\r
+ }\r
+ }\r
+\r
+ if (parts.length > 0 && parts[0]) {\r
+ defaults.channel = '#' + parts[0];\r
+ parts.shift();\r
+ }\r
+ }\r
+\r
+ // If any settings have been given by the server.. override any auto detected settings\r
+ /**\r
+ * Get any server restrictions as set in the server config\r
+ * These settings can not be changed in the server selection dialog\r
+ */\r
+ if (_kiwi.app.server_settings && _kiwi.app.server_settings.connection) {\r
+ if (_kiwi.app.server_settings.connection.server) {\r
+ defaults.server = _kiwi.app.server_settings.connection.server;\r
+ }\r
+\r
+ if (_kiwi.app.server_settings.connection.port) {\r
+ defaults.port = _kiwi.app.server_settings.connection.port;\r
+ }\r
+\r
+ if (_kiwi.app.server_settings.connection.ssl) {\r
+ defaults.ssl = _kiwi.app.server_settings.connection.ssl;\r
+ }\r
+\r
+ if (_kiwi.app.server_settings.connection.channel) {\r
+ defaults.channel = _kiwi.app.server_settings.connection.channel;\r
+ }\r
+\r
+ if (_kiwi.app.server_settings.connection.channel_key) {\r
+ defaults.channel_key = _kiwi.app.server_settings.connection.channel_key;\r
+ }\r
+\r
+ if (_kiwi.app.server_settings.connection.nick) {\r
+ defaults.nick = _kiwi.app.server_settings.connection.nick;\r
+ }\r
+ }\r
+\r
+ // Set any random numbers if needed\r
+ defaults.nick = defaults.nick.replace('?', Math.floor(Math.random() * 100000).toString());\r
+\r
+ if (getQueryVariable('encoding'))\r
+ defaults.encoding = getQueryVariable('encoding');\r
+\r
+ return defaults;\r
+ },\r
+};\r
+\r
+\r
+\r
+// If within a closure, expose the kiwi globals\r
+if (typeof global !== 'undefined') {\r
+ global.kiwi = _kiwi.global;\r
+} else {\r
+ // Not within a closure so set a var in the current scope\r
+ var kiwi = _kiwi.global;\r
+}\r
+
+
+
+(function () {\r
+\r
+ _kiwi.model.Application = Backbone.Model.extend({\r
+ /** _kiwi.view.Application */\r
+ view: null,\r
+\r
+ /** _kiwi.view.StatusMessage */\r
+ message: null,\r
+\r
+ initialize: function (options) {\r
+ this.app_options = options;\r
+\r
+ if (options.container) {\r
+ this.set('container', options.container);\r
+ }\r
+\r
+ // The base url to the kiwi server\r
+ this.set('base_path', options.base_path ? options.base_path : '');\r
+\r
+ // Path for the settings.json file\r
+ this.set('settings_path', options.settings_path ?\r
+ options.settings_path :\r
+ this.get('base_path') + '/assets/settings.json'\r
+ );\r
+\r
+ // Any options sent down from the server\r
+ this.server_settings = options.server_settings || {};\r
+ this.translations = options.translations || {};\r
+ this.themes = options.themes || [];\r
+ this.text_theme = options.text_theme || {};\r
+\r
+ // The applet to initially load\r
+ this.startup_applet_name = options.startup || 'kiwi_startup';\r
+\r
+ // Set any default settings before anything else is applied\r
+ if (this.server_settings && this.server_settings.client && this.server_settings.client.settings) {\r
+ this.applyDefaultClientSettings(this.server_settings.client.settings);\r
+ }\r
+ },\r
+\r
+\r
+ initializeInterfaces: function () {\r
+ // Best guess at where the kiwi server is if not already specified\r
+ var kiwi_server = this.app_options.kiwi_server || this.detectKiwiServer();\r
+\r
+ // Set the gateway up\r
+ _kiwi.gateway = new _kiwi.model.Gateway({kiwi_server: kiwi_server});\r
+ this.bindGatewayCommands(_kiwi.gateway);\r
+\r
+ this.initializeClient();\r
+ this.initializeGlobals();\r
+\r
+ this.view.barsHide(true);\r
+ },\r
+\r
+\r
+ detectKiwiServer: function () {\r
+ // If running from file, default to localhost:7777 by default\r
+ if (window.location.protocol === 'file:') {\r
+ return 'http://localhost:7778';\r
+ } else {\r
+ // Assume the kiwi server is on the same server\r
+ return window.location.protocol + '//' + window.location.host;\r
+ }\r
+ },\r
+\r
+\r
+ showStartup: function() {\r
+ this.startup_applet = _kiwi.model.Applet.load(this.startup_applet_name, {no_tab: true});\r
+ this.startup_applet.tab = this.view.$('.console');\r
+ this.startup_applet.view.show();\r
+\r
+ _kiwi.global.events.emit('loaded');\r
+ },\r
+\r
+\r
+ initializeClient: function () {\r
+ this.view = new _kiwi.view.Application({model: this, el: this.get('container')});\r
+\r
+ // Takes instances of model_network\r
+ this.connections = new _kiwi.model.NetworkPanelList();\r
+\r
+ // If all connections are removed at some point, hide the bars\r
+ this.connections.on('remove', _.bind(function() {\r
+ if (this.connections.length === 0) {\r
+ this.view.barsHide();\r
+ }\r
+ }, this));\r
+\r
+ // Applets panel list\r
+ this.applet_panels = new _kiwi.model.PanelList();\r
+ this.applet_panels.view.$el.addClass('panellist applets');\r
+ this.view.$el.find('.tabs').append(this.applet_panels.view.$el);\r
+\r
+ /**\r
+ * Set the UI components up\r
+ */\r
+ this.controlbox = (new _kiwi.view.ControlBox({el: $('#kiwi .controlbox')[0]})).render();\r
+ this.client_ui_commands = new _kiwi.misc.ClientUiCommands(this, this.controlbox);\r
+\r
+ this.rightbar = new _kiwi.view.RightBar({el: this.view.$('.right_bar')[0]});\r
+ this.topicbar = new _kiwi.view.TopicBar({el: this.view.$el.find('.topic')[0]});\r
+\r
+ new _kiwi.view.AppToolbar({el: _kiwi.app.view.$el.find('.toolbar .app_tools')[0]});\r
+ new _kiwi.view.ChannelTools({el: _kiwi.app.view.$el.find('.channel_tools')[0]});\r
+\r
+ this.message = new _kiwi.view.StatusMessage({el: this.view.$el.find('.status_message')[0]});\r
+\r
+ this.resize_handle = new _kiwi.view.ResizeHandler({el: this.view.$el.find('.memberlists_resize_handle')[0]});\r
+\r
+ // Rejigg the UI sizes\r
+ this.view.doLayout();\r
+ },\r
+\r
+\r
+ initializeGlobals: function () {\r
+ _kiwi.global.connections = this.connections;\r
+\r
+ _kiwi.global.panels = this.panels;\r
+ _kiwi.global.panels.applets = this.applet_panels;\r
+\r
+ _kiwi.global.components.Applet = _kiwi.model.Applet;\r
+ _kiwi.global.components.Panel =_kiwi.model.Panel;\r
+ _kiwi.global.components.MenuBox = _kiwi.view.MenuBox;\r
+ _kiwi.global.components.DataStore = _kiwi.model.DataStore;\r
+ _kiwi.global.components.Notification = _kiwi.view.Notification;\r
+ _kiwi.global.components.Events = function() {\r
+ return kiwi.events.createProxy();\r
+ };\r
+ },\r
+\r
+\r
+ applyDefaultClientSettings: function (settings) {\r
+ _.each(settings, function (value, setting) {\r
+ if (typeof _kiwi.global.settings.get(setting) === 'undefined') {\r
+ _kiwi.global.settings.set(setting, value);\r
+ }\r
+ });\r
+ },\r
+\r
+\r
+ panels: (function() {\r
+ var active_panel;\r
+\r
+ var fn = function(panel_type) {\r
+ var app = _kiwi.app,\r
+ panels;\r
+\r
+ // Default panel type\r
+ panel_type = panel_type || 'connections';\r
+\r
+ switch (panel_type) {\r
+ case 'connections':\r
+ panels = app.connections.panels();\r
+ break;\r
+ case 'applets':\r
+ panels = app.applet_panels.models;\r
+ break;\r
+ }\r
+\r
+ // Active panels / server\r
+ panels.active = active_panel;\r
+ panels.server = app.connections.active_connection ?\r
+ app.connections.active_connection.panels.server :\r
+ null;\r
+\r
+ return panels;\r
+ };\r
+\r
+ _.extend(fn, Backbone.Events);\r
+\r
+ // Keep track of the active panel. Channel/query/server or applet\r
+ fn.bind('active', function (new_active_panel) {\r
+ var previous_panel = active_panel;\r
+ active_panel = new_active_panel;\r
+\r
+ _kiwi.global.events.emit('panel:active', {previous: previous_panel, active: active_panel});\r
+ });\r
+\r
+ return fn;\r
+ })(),\r
+\r
+\r
+ bindGatewayCommands: function (gw) {\r
+ var that = this;\r
+\r
+ // As soon as an IRC connection is made, show the full client UI\r
+ gw.on('connection:connect', function (event) {\r
+ that.view.barsShow();\r
+ });\r
+\r
+\r
+ /**\r
+ * Handle the reconnections to the kiwi server\r
+ */\r
+ (function () {\r
+ // 0 = non-reconnecting state. 1 = reconnecting state.\r
+ var gw_stat = 0;\r
+\r
+ gw.on('disconnect', function (event) {\r
+ that.view.$el.removeClass('connected');\r
+\r
+ // Reconnection phase will start to kick in\r
+ gw_stat = 1;\r
+ });\r
+\r
+\r
+ gw.on('reconnecting', function (event) {\r
+ var msg = translateText('client_models_application_reconnect_in_x_seconds', [event.delay/1000]) + '...';\r
+\r
+ // Only need to mention the repeating re-connection messages on server panels\r
+ _kiwi.app.connections.forEach(function(connection) {\r
+ connection.panels.server.addMsg('', styleText('quit', {text: msg}), 'action quit');\r
+ });\r
+ });\r
+\r
+\r
+ // After the socket has connected, kiwi handshakes and then triggers a kiwi:connected event\r
+ gw.on('kiwi:connected', function (event) {\r
+ var msg;\r
+\r
+ that.view.$el.addClass('connected');\r
+\r
+ // Make the rpc globally available for plugins\r
+ _kiwi.global.rpc = _kiwi.gateway.rpc;\r
+\r
+ _kiwi.global.events.emit('connected');\r
+\r
+ // If we were reconnecting, show some messages we have connected back OK\r
+ if (gw_stat === 1) {\r
+\r
+ // No longer in the reconnection state\r
+ gw_stat = 0;\r
+\r
+ msg = translateText('client_models_application_reconnect_successfully') + ' :)';\r
+ that.message.text(msg, {timeout: 5000});\r
+\r
+ // Mention the re-connection on every channel\r
+ _kiwi.app.connections.forEach(function(connection) {\r
+ connection.reconnect();\r
+\r
+ connection.panels.server.addMsg('', styleText('rejoin', {text: msg}), 'action join');\r
+\r
+ connection.panels.forEach(function(panel) {\r
+ if (!panel.isChannel())\r
+ return;\r
+\r
+ panel.addMsg('', styleText('rejoin', {text: msg}), 'action join');\r
+ });\r
+ });\r
+ }\r
+\r
+ });\r
+ })();\r
+\r
+\r
+ gw.on('kiwi:reconfig', function () {\r
+ $.getJSON(that.get('settings_path'), function (data) {\r
+ that.server_settings = data.server_settings || {};\r
+ that.translations = data.translations || {};\r
+ });\r
+ });\r
+\r
+\r
+ gw.on('kiwi:jumpserver', function (data) {\r
+ var serv;\r
+ // No server set? Then nowhere to jump to.\r
+ if (typeof data.kiwi_server === 'undefined')\r
+ return;\r
+\r
+ serv = data.kiwi_server;\r
+\r
+ // Strip any trailing slash from the end\r
+ if (serv[serv.length-1] === '/')\r
+ serv = serv.substring(0, serv.length-1);\r
+\r
+ // Force the jumpserver now?\r
+ if (data.force) {\r
+ // Get an interval between 5 and 6 minutes so everyone doesn't reconnect it all at once\r
+ var jump_server_interval = Math.random() * (360 - 300) + 300;\r
+ jump_server_interval = 1;\r
+\r
+ // Tell the user we are going to disconnect, wait 5 minutes then do the actual reconnect\r
+ var msg = _kiwi.global.i18n.translate('client_models_application_jumpserver_prepare').fetch();\r
+ that.message.text(msg, {timeout: 10000});\r
+\r
+ setTimeout(function forcedReconnect() {\r
+ var msg = _kiwi.global.i18n.translate('client_models_application_jumpserver_reconnect').fetch();\r
+ that.message.text(msg, {timeout: 8000});\r
+\r
+ setTimeout(function forcedReconnectPartTwo() {\r
+ _kiwi.gateway.set('kiwi_server', serv);\r
+\r
+ _kiwi.gateway.reconnect(function() {\r
+ // Reconnect all the IRC connections\r
+ that.connections.forEach(function(con){ con.reconnect(); });\r
+ });\r
+ }, 5000);\r
+\r
+ }, jump_server_interval * 1000);\r
+ }\r
+ });\r
+ }\r
+\r
+ });\r
+\r
+})();\r
+
+
+
+_kiwi.model.Gateway = Backbone.Model.extend({\r
+\r
+ initialize: function () {\r
+\r
+ // For ease of access. The socket.io object\r
+ this.socket = this.get('socket');\r
+\r
+ // Used to check if a disconnection was unplanned\r
+ this.disconnect_requested = false;\r
+ },\r
+\r
+\r
+\r
+ reconnect: function (callback) {\r
+ this.disconnect_requested = true;\r
+ this.socket.close();\r
+\r
+ this.socket = null;\r
+ this.connect(callback);\r
+ },\r
+\r
+\r
+\r
+ /**\r
+ * Connects to the server\r
+ * @param {Function} callback A callback function to be invoked once Kiwi's server has connected to the IRC server\r
+ */\r
+ connect: function (callback) {\r
+ var that = this;\r
+\r
+ this.connect_callback = callback;\r
+\r
+ this.socket = new EngineioTools.ReconnectingSocket(this.get('kiwi_server'), {\r
+ transports: _kiwi.app.server_settings.transports || ['polling', 'websocket'],\r
+ path: _kiwi.app.get('base_path') + '/transport',\r
+ reconnect_max_attempts: 5,\r
+ reconnect_delay: 2000\r
+ });\r
+\r
+ // If we have an existing RPC object, clean it up before replacing it\r
+ if (this.rpc) {\r
+ rpc.dispose();\r
+ }\r
+ this.rpc = new EngineioTools.Rpc(this.socket);\r
+\r
+ this.socket.on('connect_failed', function (reason) {\r
+ this.socket.disconnect();\r
+ this.trigger("connect_fail", {reason: reason});\r
+ });\r
+\r
+ this.socket.on('error', function (e) {\r
+ console.log("_kiwi.gateway.socket.on('error')", {reason: e});\r
+ if (that.connect_callback) {\r
+ that.connect_callback(e);\r
+ delete that.connect_callback;\r
+ }\r
+\r
+ that.trigger("connect_fail", {reason: e});\r
+ });\r
+\r
+ this.socket.on('connecting', function (transport_type) {\r
+ console.log("_kiwi.gateway.socket.on('connecting')");\r
+ that.trigger("connecting");\r
+ });\r
+\r
+ /**\r
+ * Once connected to the kiwi server send the IRC connect command along\r
+ * with the IRC server details.\r
+ * A `connect` event is sent from the kiwi server once connected to the\r
+ * IRCD and the nick has been accepted.\r
+ */\r
+ this.socket.on('open', function () {\r
+ // Reset the disconnect_requested flag\r
+ that.disconnect_requested = false;\r
+\r
+ // Each minute we need to trigger a heartbeat. Server expects 2min, but to be safe we do it every 1min\r
+ var heartbeat = function() {\r
+ if (!that.rpc) return;\r
+\r
+ that.rpc('kiwi.heartbeat');\r
+ that._heartbeat_tmr = setTimeout(heartbeat, 60000);\r
+ };\r
+\r
+ heartbeat();\r
+\r
+ console.log("_kiwi.gateway.socket.on('open')");\r
+ });\r
+\r
+ this.rpc.on('too_many_connections', function () {\r
+ that.trigger("connect_fail", {reason: 'too_many_connections'});\r
+ });\r
+\r
+ this.rpc.on('irc', function (response, data) {\r
+ that.parse(data.command, data.data);\r
+ });\r
+\r
+ this.rpc.on('kiwi', function (response, data) {\r
+ that.parseKiwi(data.command, data.data);\r
+ });\r
+\r
+ this.socket.on('close', function () {\r
+ that.trigger("disconnect", {});\r
+ console.log("_kiwi.gateway.socket.on('close')");\r
+ });\r
+\r
+ this.socket.on('reconnecting', function (status) {\r
+ console.log("_kiwi.gateway.socket.on('reconnecting')");\r
+ that.trigger("reconnecting", {delay: status.delay, attempts: status.attempts});\r
+ });\r
+\r
+ this.socket.on('reconnecting_failed', function () {\r
+ console.log("_kiwi.gateway.socket.on('reconnect_failed')");\r
+ });\r
+ },\r
+\r
+\r
+ /**\r
+ * Return a new network object with the new connection details\r
+ */\r
+ newConnection: function(connection_info, callback_fn) {\r
+ var that = this;\r
+\r
+ // If not connected, connect first then re-call this function\r
+ if (!this.isConnected()) {\r
+ this.connect(function(err) {\r
+ if (err) {\r
+ callback_fn(err);\r
+ return;\r
+ }\r
+\r
+ that.newConnection(connection_info, callback_fn);\r
+ });\r
+\r
+ return;\r
+ }\r
+\r
+ this.makeIrcConnection(connection_info, function(err, server_num) {\r
+ var connection;\r
+\r
+ if (!err) {\r
+ if (!_kiwi.app.connections.getByConnectionId(server_num)){\r
+ var inf = {\r
+ connection_id: server_num,\r
+ nick: connection_info.nick,\r
+ address: connection_info.host,\r
+ port: connection_info.port,\r
+ ssl: connection_info.ssl,\r
+ password: connection_info.password\r
+ };\r
+ connection = new _kiwi.model.Network(inf);\r
+ _kiwi.app.connections.add(connection);\r
+ }\r
+\r
+ console.log("_kiwi.gateway.socket.on('connect')", connection);\r
+ callback_fn && callback_fn(err, connection);\r
+\r
+ } else {\r
+ console.log("_kiwi.gateway.socket.on('error')", {reason: err});\r
+ callback_fn && callback_fn(err);\r
+ }\r
+ });\r
+ },\r
+\r
+\r
+ /**\r
+ * Make a new IRC connection and return its connection ID\r
+ */\r
+ makeIrcConnection: function(connection_info, callback_fn) {\r
+ var server_info = {\r
+ nick: connection_info.nick,\r
+ hostname: connection_info.host,\r
+ port: connection_info.port,\r
+ ssl: connection_info.ssl,\r
+ password: connection_info.password\r
+ };\r
+\r
+ connection_info.options = connection_info.options || {};\r
+\r
+ // A few optional parameters\r
+ if (connection_info.options.encoding)\r
+ server_info.encoding = connection_info.options.encoding;\r
+\r
+ this.rpc('kiwi.connect_irc', server_info, function (err, server_num) {\r
+ if (!err) {\r
+ callback_fn && callback_fn(err, server_num);\r
+\r
+ } else {\r
+ callback_fn && callback_fn(err);\r
+ }\r
+ });\r
+ },\r
+\r
+\r
+ isConnected: function () {\r
+ // TODO: Check this. Might want to use .readyState\r
+ return this.socket;\r
+ },\r
+\r
+\r
+\r
+ parseKiwi: function (command, data) {\r
+ var args;\r
+\r
+ switch (command) {\r
+ case 'connected':\r
+ // Send some info on this client to the server\r
+ args = {\r
+ build_version: _kiwi.global.build_version\r
+ };\r
+ this.rpc('kiwi.client_info', args);\r
+\r
+ this.connect_callback && this.connect_callback();\r
+ delete this.connect_callback;\r
+\r
+ break;\r
+ }\r
+\r
+ this.trigger('kiwi:' + command, data);\r
+ this.trigger('kiwi', data);\r
+ },\r
+\r
+ /**\r
+ * Parses the response from the server\r
+ */\r
+ parse: function (command, data) {\r
+ var network_trigger = '';\r
+\r
+ // Trigger the connection specific events (used by Network objects)\r
+ if (typeof data.connection_id !== 'undefined') {\r
+ network_trigger = 'connection:' + data.connection_id.toString();\r
+\r
+ this.trigger(network_trigger, {\r
+ event_name: command,\r
+ event_data: data\r
+ });\r
+\r
+ // Some events trigger a more in-depth event name\r
+ if (command == 'message' && data.type) {\r
+ this.trigger('connection ' + network_trigger, {\r
+ event_name: 'message:' + data.type,\r
+ event_data: data\r
+ });\r
+ }\r
+\r
+ if (command == 'channel' && data.type) {\r
+ this.trigger('connection ' + network_trigger, {\r
+ event_name: 'channel:' + data.type,\r
+ event_data: data\r
+ });\r
+ }\r
+ }\r
+\r
+ // Trigger the global events\r
+ this.trigger('connection', {event_name: command, event_data: data});\r
+ this.trigger('connection:' + command, data);\r
+ },\r
+\r
+ /**\r
+ * Make an RPC call with the connection_id as the first argument\r
+ * @param {String} method RPC method name\r
+ * @param {Number} connection_id Connection ID this call relates to\r
+ */\r
+ rpcCall: function(method, connection_id) {\r
+ var args = Array.prototype.slice.call(arguments, 0);\r
+\r
+ if (typeof args[1] === 'undefined' || args[1] === null)\r
+ args[1] = _kiwi.app.connections.active_connection.get('connection_id');\r
+\r
+ return this.rpc.apply(this.rpc, args);\r
+ },\r
+\r
+ /**\r
+ * Sends a PRIVMSG message\r
+ * @param {String} target The target of the message (e.g. a channel or nick)\r
+ * @param {String} msg The message to send\r
+ * @param {Function} callback A callback function\r
+ */\r
+ privmsg: function (connection_id, target, msg, callback) {\r
+ var args = {\r
+ target: target,\r
+ msg: msg\r
+ };\r
+\r
+ this.rpcCall('irc.privmsg', connection_id, args, callback);\r
+ },\r
+\r
+ /**\r
+ * Sends a NOTICE message\r
+ * @param {String} target The target of the message (e.g. a channel or nick)\r
+ * @param {String} msg The message to send\r
+ * @param {Function} callback A callback function\r
+ */\r
+ notice: function (connection_id, target, msg, callback) {\r
+ var args = {\r
+ target: target,\r
+ msg: msg\r
+ };\r
+\r
+ this.rpcCall('irc.notice', connection_id, args, callback);\r
+ },\r
+\r
+ /**\r
+ * Sends a CTCP message\r
+ * @param {Boolean} request Indicates whether this is a CTCP request (true) or reply (false)\r
+ * @param {String} type The type of CTCP message, e.g. 'VERSION', 'TIME', 'PING' etc.\r
+ * @param {String} target The target of the message, e.g a channel or nick\r
+ * @param {String} params Additional paramaters\r
+ * @param {Function} callback A callback function\r
+ */\r
+ ctcp: function (connection_id, is_request, type, target, params, callback) {\r
+ var args = {\r
+ is_request: is_request,\r
+ type: type,\r
+ target: target,\r
+ params: params\r
+ };\r
+\r
+ this.rpcCall('irc.ctcp', connection_id, args, callback);\r
+ },\r
+\r
+ ctcpRequest: function (connection_id, type, target, params, callback) {\r
+ this.ctcp(connection_id, true, type, target, params, callback);\r
+ },\r
+ ctcpResponse: function (connection_id, type, target, params, callback) {\r
+ this.ctcp(connection_id, false, type, target, params, callback);\r
+ },\r
+\r
+ /**\r
+ * @param {String} target The target of the message (e.g. a channel or nick)\r
+ * @param {String} msg The message to send\r
+ * @param {Function} callback A callback function\r
+ */\r
+ action: function (connection_id, target, msg, callback) {\r
+ this.ctcp(connection_id, true, 'ACTION', target, msg, callback);\r
+ },\r
+\r
+ /**\r
+ * Joins a channel\r
+ * @param {String} channel The channel to join\r
+ * @param {String} key The key to the channel\r
+ * @param {Function} callback A callback function\r
+ */\r
+ join: function (connection_id, channel, key, callback) {\r
+ var args = {\r
+ channel: channel,\r
+ key: key\r
+ };\r
+\r
+ this.rpcCall('irc.join', connection_id, args, callback);\r
+ },\r
+\r
+ /**\r
+ * Retrieves channel information\r
+ */\r
+ channelInfo: function (connection_id, channel, callback) {\r
+ var args = {\r
+ channel: channel\r
+ };\r
+\r
+ this.rpcCall('irc.channel_info', connection_id, args, callback);\r
+ },\r
+\r
+ /**\r
+ * Leaves a channel\r
+ * @param {String} channel The channel to part\r
+ * @param {String} message Optional part message\r
+ * @param {Function} callback A callback function\r
+ */\r
+ part: function (connection_id, channel, message, callback) {\r
+ "use strict";\r
+\r
+ // The message param is optional, so juggle args if it is missing\r
+ if (typeof arguments[2] === 'function') {\r
+ callback = arguments[2];\r
+ message = undefined;\r
+ }\r
+ var args = {\r
+ channel: channel,\r
+ message: message\r
+ };\r
+\r
+ this.rpcCall('irc.part', connection_id, args, callback);\r
+ },\r
+\r
+ /**\r
+ * Queries or modifies a channell topic\r
+ * @param {String} channel The channel to query or modify\r
+ * @param {String} new_topic The new topic to set\r
+ * @param {Function} callback A callback function\r
+ */\r
+ topic: function (connection_id, channel, new_topic, callback) {\r
+ var args = {\r
+ channel: channel,\r
+ topic: new_topic\r
+ };\r
+\r
+ this.rpcCall('irc.topic', connection_id, args, callback);\r
+ },\r
+\r
+ /**\r
+ * Kicks a user from a channel\r
+ * @param {String} channel The channel to kick the user from\r
+ * @param {String} nick The nick of the user to kick\r
+ * @param {String} reason The reason for kicking the user\r
+ * @param {Function} callback A callback function\r
+ */\r
+ kick: function (connection_id, channel, nick, reason, callback) {\r
+ var args = {\r
+ channel: channel,\r
+ nick: nick,\r
+ reason: reason\r
+ };\r
+\r
+ this.rpcCall('irc.kick', connection_id, args, callback);\r
+ },\r
+\r
+ /**\r
+ * Disconnects us from the server\r
+ * @param {String} msg The quit message to send to the IRC server\r
+ * @param {Function} callback A callback function\r
+ */\r
+ quit: function (connection_id, msg, callback) {\r
+ msg = msg || "";\r
+\r
+ var args = {\r
+ message: msg\r
+ };\r
+\r
+ this.rpcCall('irc.quit', connection_id, args, callback);\r
+ },\r
+\r
+ /**\r
+ * Sends a string unmodified to the IRC server\r
+ * @param {String} data The data to send to the IRC server\r
+ * @param {Function} callback A callback function\r
+ */\r
+ raw: function (connection_id, data, callback) {\r
+ var args = {\r
+ data: data\r
+ };\r
+\r
+ this.rpcCall('irc.raw', connection_id, args, callback);\r
+ },\r
+\r
+ /**\r
+ * Changes our nickname\r
+ * @param {String} new_nick Our new nickname\r
+ * @param {Function} callback A callback function\r
+ */\r
+ changeNick: function (connection_id, new_nick, callback) {\r
+ var args = {\r
+ nick: new_nick\r
+ };\r
+\r
+ this.rpcCall('irc.nick', connection_id, args, callback);\r
+ },\r
+\r
+ /**\r
+ * Sets a mode for a target\r
+ */\r
+ mode: function (connection_id, target, mode_string, callback) {\r
+ var args = {\r
+ data: 'MODE ' + target + ' ' + mode_string\r
+ };\r
+\r
+ this.rpcCall('irc.raw', connection_id, args, callback);\r
+ },\r
+\r
+ /**\r
+ * Sends ENCODING change request to server.\r
+ * @param {String} new_encoding The new proposed encode\r
+ * @param {Fucntion} callback A callback function\r
+ */\r
+ setEncoding: function (connection_id, new_encoding, callback) {\r
+ var args = {\r
+ encoding: new_encoding\r
+ };\r
+\r
+ this.rpcCall('irc.encoding', connection_id, args, callback);\r
+ }\r
+});\r
+
+
+
+(function () {
+
+ _kiwi.model.Network = Backbone.Model.extend({
+ defaults: {
+ connection_id: 0,
+ /**
+ * The name of the network
+ * @type String
+ */
+ name: 'Network',
+
+ /**
+ * The address (URL) of the network
+ * @type String
+ */
+ address: '',
+
+ /**
+ * The port for the network
+ * @type Int
+ */
+ port: 6667,
+
+ /**
+ * If this network uses SSL
+ * @type Bool
+ */
+ ssl: false,
+
+ /**
+ * The password to connect to this network
+ * @type String
+ */
+ password: '',
+
+ /**
+ * The current nickname
+ * @type String
+ */
+ nick: '',
+
+ /**
+ * The channel prefix for this network
+ * @type String
+ */
+ channel_prefix: '#',
+
+ /**
+ * The user prefixes for channel owner/admin/op/voice etc. on this network
+ * @type Array
+ */
+ user_prefixes: [
+ {symbol: '~', mode: 'q'},
+ {symbol: '&', mode: 'a'},
+ {symbol: '@', mode: 'o'},
+ {symbol: '%', mode: 'h'},
+ {symbol: '+', mode: 'v'}
+ ],
+
+ /**
+ * List of nicks we are ignoring
+ * @type Array
+ */
+ ignore_list: []
+ },
+
+
+ initialize: function () {
+ // If we already have a connection, bind our events
+ if (typeof this.get('connection_id') !== 'undefined') {
+ this.gateway = _kiwi.global.components.Network(this.get('connection_id'));
+ this.bindGatewayEvents();
+ }
+
+ // Create our panel list (tabs)
+ this.panels = new _kiwi.model.PanelList([], this);
+ //this.panels.network = this;
+
+ // Automatically create a server tab
+ var server_panel = new _kiwi.model.Server({name: 'Server', network: this});
+ this.panels.add(server_panel);
+ this.panels.server = this.panels.active = server_panel;
+ },
+
+
+ reconnect: function(callback_fn) {
+ var that = this,
+ server_info = {
+ nick: this.get('nick'),
+ host: this.get('address'),
+ port: this.get('port'),
+ ssl: this.get('ssl'),
+ password: this.get('password')
+ };
+
+ _kiwi.gateway.makeIrcConnection(server_info, function(err, connection_id) {
+ if (!err) {
+ that.gateway.dispose();
+
+ that.set('connection_id', connection_id);
+ that.gateway = _kiwi.global.components.Network(that.get('connection_id'));
+ that.bindGatewayEvents();
+
+ // Reset each of the panels connection ID
+ that.panels.forEach(function(panel) {
+ panel.set('connection_id', connection_id);
+ });
+
+ callback_fn && callback_fn(err);
+
+ } else {
+ console.log("_kiwi.gateway.socket.on('error')", {reason: err});
+ callback_fn && callback_fn(err);
+ }
+ });
+ },
+
+
+ bindGatewayEvents: function () {
+ //this.gateway.on('all', function() {console.log('ALL', this.get('connection_id'), arguments);});
+
+ this.gateway.on('connect', onConnect, this);
+ this.gateway.on('disconnect', onDisconnect, this);
+
+ this.gateway.on('nick', function(event) {
+ if (event.nick === this.get('nick')) {
+ this.set('nick', event.newnick);
+ }
+ }, this);
+
+ this.gateway.on('options', onOptions, this);
+ this.gateway.on('motd', onMotd, this);
+ this.gateway.on('channel:join', onJoin, this);
+ this.gateway.on('channel:part', onPart, this);
+ this.gateway.on('channel:kick', onKick, this);
+ this.gateway.on('quit', onQuit, this);
+ this.gateway.on('message', onMessage, this);
+ this.gateway.on('nick', onNick, this);
+ this.gateway.on('ctcp_request', onCtcpRequest, this);
+ this.gateway.on('ctcp_response', onCtcpResponse, this);
+ this.gateway.on('topic', onTopic, this);
+ this.gateway.on('topicsetby', onTopicSetBy, this);
+ this.gateway.on('userlist', onUserlist, this);
+ this.gateway.on('userlist_end', onUserlistEnd, this);
+ this.gateway.on('banlist', onBanlist, this);
+ this.gateway.on('mode', onMode, this);
+ this.gateway.on('whois', onWhois, this);
+ this.gateway.on('whowas', onWhowas, this);
+ this.gateway.on('away', onAway, this);
+ this.gateway.on('list_start', onListStart, this);
+ this.gateway.on('irc_error', onIrcError, this);
+ this.gateway.on('unknown_command', onUnknownCommand, this);
+ this.gateway.on('channel_info', onChannelInfo, this);
+ this.gateway.on('wallops', onWallops, this);
+ },
+
+
+ /**
+ * Create panels and join the channel
+ * This will not wait for the join event to create a panel. This
+ * increases responsiveness in case of network lag
+ */
+ createAndJoinChannels: function (channels) {
+ var that = this,
+ panels = [];
+
+ // Multiple channels may come as comma-delimited
+ if (typeof channels === 'string') {
+ channels = channels.split(',');
+ }
+
+ $.each(channels, function (index, channel_name_key) {
+ // We may have a channel key so split it off
+ var spli = channel_name_key.trim().split(' '),
+ channel_name = spli[0],
+ channel_key = spli[1] || '';
+
+ // Trim any whitespace off the name
+ channel_name = channel_name.trim();
+
+ // Add channel_prefix in front of the first channel if missing
+ if (that.get('channel_prefix').indexOf(channel_name[0]) === -1) {
+ // Could be many prefixes but '#' is highly likely the required one
+ channel_name = '#' + channel_name;
+ }
+
+ // Check if we have the panel already. If not, create it
+ channel = that.panels.getByName(channel_name);
+ if (!channel) {
+ channel = new _kiwi.model.Channel({name: channel_name, network: that});
+ that.panels.add(channel);
+ }
+
+ panels.push(channel);
+
+ that.gateway.join(channel_name, channel_key);
+ });
+
+
+ return panels;
+ },
+
+
+ /**
+ * Join all the open channels we have open
+ * Reconnecting to a network would typically call this.
+ */
+ rejoinAllChannels: function() {
+ var that = this;
+
+ this.panels.forEach(function(panel) {
+ if (!panel.isChannel())
+ return;
+
+ that.gateway.join(panel.get('name'));
+ });
+ },
+
+ isChannelName: function (channel_name) {
+ var channel_prefix = this.get('channel_prefix');
+
+ if (!channel_name || !channel_name.length) return false;
+ return (channel_prefix.indexOf(channel_name[0]) > -1);
+ },
+
+ // Check a nick alongside our ignore list
+ isNickIgnored: function (nick) {
+ var idx, list = this.get('ignore_list');
+ var pattern, regex;
+
+ for (idx = 0; idx < list.length; idx++) {
+ pattern = list[idx].replace(/([.+^$[\]\\(){}|-])/g, "\\$1")
+ .replace('*', '.*')
+ .replace('?', '.');
+
+ regex = new RegExp(pattern, 'i');
+ if (regex.test(nick)) return true;
+ }
+
+ return false;
+ },
+
+ // Create a new query panel
+ createQuery: function (nick) {
+ var that = this,
+ query;
+
+ // Check if we have the panel already. If not, create it
+ query = that.panels.getByName(nick);
+ if (!query) {
+ query = new _kiwi.model.Query({name: nick});
+ that.panels.add(query);
+ }
+
+ // In all cases, show the demanded query
+ query.view.show();
+
+ return query;
+ }
+ });
+
+
+
+ function onDisconnect(event) {
+ this.set('connected', false);
+
+ $.each(this.panels.models, function (index, panel) {
+ if (!panel.isApplet()) {
+ panel.addMsg('', styleText('network_disconnected', {text: translateText('client_models_network_disconnected', [])}), 'action quit');
+ }
+ });
+ }
+
+
+
+ function onConnect(event) {
+ var panels, channel_names;
+
+ // Update our nick with what the network gave us
+ this.set('nick', event.nick);
+
+ this.set('connected', true);
+
+ // If this is a re-connection then we may have some channels to re-join
+ this.rejoinAllChannels();
+
+ // Auto joining channels
+ if (this.auto_join && this.auto_join.channel) {
+ panels = this.createAndJoinChannels(this.auto_join.channel + ' ' + (this.auto_join.key || ''));
+
+ // Show the last channel if we have one
+ if (panels)
+ panels[panels.length - 1].view.show();
+
+ delete this.auto_join;
+ }
+ }
+
+
+
+ function onOptions(event) {
+ var that = this;
+
+ $.each(event.options, function (name, value) {
+ switch (name) {
+ case 'CHANTYPES':
+ that.set('channel_prefix', value.join(''));
+ break;
+ case 'NETWORK':
+ that.set('name', value);
+ break;
+ case 'PREFIX':
+ that.set('user_prefixes', value);
+ break;
+ }
+ });
+
+ this.set('cap', event.cap);
+ }
+
+
+
+ function onMotd(event) {
+ this.panels.server.addMsg(this.get('name'), styleText('motd', {text: event.msg}), 'motd');
+ }
+
+
+
+ function onJoin(event) {
+ var c, members, user;
+ c = this.panels.getByName(event.channel);
+ if (!c) {
+ c = new _kiwi.model.Channel({name: event.channel, network: this});
+ this.panels.add(c);
+ }
+
+ members = c.get('members');
+ if (!members) return;
+
+ // Do we already have this member?
+ if (members.getByNick(event.nick)) {
+ return;
+ }
+
+ user = new _kiwi.model.Member({
+ nick: event.nick,
+ ident: event.ident,
+ hostname: event.hostname,
+ user_prefixes: this.get('user_prefixes')
+ });
+
+ _kiwi.global.events.emit('channel:join', {channel: event.channel, user: user, network: this.gateway})
+ .then(function() {
+ members.add(user, {kiwi: event});
+ });
+ }
+
+
+
+ function onPart(event) {
+ var channel, members, user,
+ part_options = {};
+
+ part_options.type = 'part';
+ part_options.message = event.message || '';
+ part_options.time = event.time;
+
+ channel = this.panels.getByName(event.channel);
+ if (!channel) return;
+
+ // If this is us, close the panel
+ if (event.nick === this.get('nick')) {
+ channel.close();
+ return;
+ }
+
+ members = channel.get('members');
+ if (!members) return;
+
+ user = members.getByNick(event.nick);
+ if (!user) return;
+
+ _kiwi.global.events.emit('channel:leave', {channel: event.channel, user: user, type: 'part', message: part_options.message, network: this.gateway})
+ .then(function() {
+ members.remove(user, {kiwi: part_options});
+ });
+ }
+
+
+
+ function onQuit(event) {
+ var member, members,
+ quit_options = {};
+
+ quit_options.type = 'quit';
+ quit_options.message = event.message || '';
+ quit_options.time = event.time;
+
+ $.each(this.panels.models, function (index, panel) {
+ // Let any query panels know they quit
+ if (panel.isQuery() && panel.get('name').toLowerCase() === event.nick.toLowerCase()) {
+ panel.addMsg(' ', styleText('channel_quit', {
+ nick: event.nick,
+ text: translateText('client_models_channel_quit', [quit_options.message])
+ }), 'action quit', {time: quit_options.time});
+ }
+
+ // Remove the nick from any channels
+ if (panel.isChannel()) {
+ member = panel.get('members').getByNick(event.nick);
+ if (member) {
+ _kiwi.global.events.emit('channel:leave', {channel: panel.get('name'), user: member, type: 'quit', message: quit_options.message, network: this.gateway})
+ .then(function() {
+ panel.get('members').remove(member, {kiwi: quit_options});
+ });
+ }
+ }
+ });
+ }
+
+
+
+ function onKick(event) {
+ var channel, members, user,
+ part_options = {};
+
+ part_options.type = 'kick';
+ part_options.by = event.nick;
+ part_options.message = event.message || '';
+ part_options.current_user_kicked = (event.kicked == this.get('nick'));
+ part_options.current_user_initiated = (event.nick == this.get('nick'));
+ part_options.time = event.time;
+
+ channel = this.panels.getByName(event.channel);
+ if (!channel) return;
+
+ members = channel.get('members');
+ if (!members) return;
+
+ user = members.getByNick(event.kicked);
+ if (!user) return;
+
+
+ _kiwi.global.events.emit('channel:leave', {channel: event.channel, user: user, type: 'kick', message: part_options.message, network: this.gateway})
+ .then(function() {
+ members.remove(user, {kiwi: part_options});
+
+ if (part_options.current_user_kicked) {
+ members.reset([]);
+ }
+ });
+ }
+
+
+
+ function onMessage(event) {
+ _kiwi.global.events.emit('message:new', {network: this.gateway, message: event})
+ .then(_.bind(function() {
+ var panel,
+ is_pm = ((event.target || '').toLowerCase() == this.get('nick').toLowerCase());
+
+ // An ignored user? don't do anything with it
+ if (this.isNickIgnored(event.nick)) {
+ return;
+ }
+
+ if (event.type == 'notice') {
+ if (event.from_server) {
+ panel = this.panels.server;
+
+ } else {
+ panel = this.panels.getByName(event.target) || this.panels.getByName(event.nick);
+
+ // Forward ChanServ messages to its associated channel
+ if (event.nick && event.nick.toLowerCase() == 'chanserv' && event.msg.charAt(0) == '[') {
+ channel_name = /\[([^ \]]+)\]/gi.exec(event.msg);
+ if (channel_name && channel_name[1]) {
+ channel_name = channel_name[1];
+
+ panel = this.panels.getByName(channel_name);
+ }
+ }
+
+ }
+
+ if (!panel) {
+ panel = this.panels.server;
+ }
+
+ } else if (is_pm) {
+ // If a panel isn't found for this PM, create one
+ panel = this.panels.getByName(event.nick);
+ if (!panel) {
+ panel = new _kiwi.model.Query({name: event.nick, network: this});
+ this.panels.add(panel);
+ }
+
+ } else {
+ // If a panel isn't found for this target, reroute to the
+ // server panel
+ panel = this.panels.getByName(event.target);
+ if (!panel) {
+ panel = this.panels.server;
+ }
+ }
+
+ switch (event.type){
+ case 'message':
+ panel.addMsg(event.nick, styleText('privmsg', {text: event.msg}), 'privmsg', {time: event.time});
+ break;
+
+ case 'action':
+ panel.addMsg('', styleText('action', {nick: event.nick, text: event.msg}), 'action', {time: event.time});
+ break;
+
+ case 'notice':
+ panel.addMsg('[' + (event.nick||'') + ']', styleText('notice', {text: event.msg}), 'notice', {time: event.time});
+
+ // Show this notice to the active panel if it didn't have a set target, but only in an active channel or query window
+ active_panel = _kiwi.app.panels().active;
+
+ if (!event.from_server && panel === this.panels.server && active_panel !== this.panels.server) {
+ if (active_panel.get('network') === this && (active_panel.isChannel() || active_panel.isQuery()))
+ active_panel.addMsg('[' + (event.nick||'') + ']', styleText('notice', {text: event.msg}), 'notice', {time: event.time});
+ }
+ break;
+ }
+ }, this));
+ }
+
+
+
+ function onNick(event) {
+ var member;
+
+ $.each(this.panels.models, function (index, panel) {
+ if (panel.get('name') == event.nick)
+ panel.set('name', event.newnick);
+
+ if (!panel.isChannel()) return;
+
+ member = panel.get('members').getByNick(event.nick);
+ if (member) {
+ member.set('nick', event.newnick);
+ panel.addMsg('', styleText('nick_changed', {nick: event.nick, text: translateText('client_models_network_nickname_changed', [event.newnick]), channel: name}), 'action nick', {time: event.time});
+ }
+ });
+ }
+
+
+
+ function onCtcpRequest(event) {
+ // An ignored user? don't do anything with it
+ if (this.isNickIgnored(event.nick)) {
+ return;
+ }
+
+ // Reply to a TIME ctcp
+ if (event.msg.toUpperCase() === 'TIME') {
+ this.gateway.ctcpResponse(event.type, event.nick, (new Date()).toString());
+ } else if(event.type.toUpperCase() === 'PING') { // CTCP PING reply
+ this.gateway.ctcpResponse(event.type, event.nick, event.msg.substr(5));
+ }
+ }
+
+
+
+ function onCtcpResponse(event) {
+ // An ignored user? don't do anything with it
+ if (this.isNickIgnored(event.nick)) {
+ return;
+ }
+
+ this.panels.server.addMsg('[' + event.nick + ']', styleText('ctcp', {text: event.msg}), 'ctcp', {time: event.time});
+ }
+
+
+
+ function onTopic(event) {
+ var c;
+ c = this.panels.getByName(event.channel);
+ if (!c) return;
+
+ // Set the channels topic
+ c.set('topic', event.topic);
+
+ // If this is the active channel, update the topic bar too
+ if (c.get('name') === this.panels.active.get('name')) {
+ _kiwi.app.topicbar.setCurrentTopic(event.topic);
+ }
+ }
+
+
+
+ function onTopicSetBy(event) {
+ var c, when;
+ c = this.panels.getByName(event.channel);
+ if (!c) return;
+
+ when = new Date(event.when * 1000);
+ c.set('topic_set_by', {nick: event.nick, when: when});
+ }
+
+
+
+ function onChannelInfo(event) {
+ var channel = this.panels.getByName(event.channel);
+ if (!channel) return;
+
+ if (event.url) {
+ channel.set('info_url', event.url);
+ } else if (event.modes) {
+ channel.set('info_modes', event.modes);
+ }
+ }
+
+
+
+ function onUserlist(event) {
+ var that = this,
+ channel = this.panels.getByName(event.channel);
+
+ // If we didn't find a channel for this, may aswell leave
+ if (!channel) return;
+
+ channel.temp_userlist = channel.temp_userlist || [];
+ _.each(event.users, function (item) {
+ var user = new _kiwi.model.Member({
+ nick: item.nick,
+ modes: item.modes,
+ user_prefixes: that.get('user_prefixes')
+ });
+ channel.temp_userlist.push(user);
+ });
+ }
+
+
+
+ function onUserlistEnd(event) {
+ var channel;
+ channel = this.panels.getByName(event.channel);
+
+ // If we didn't find a channel for this, may aswell leave
+ if (!channel) return;
+
+ // Update the members list with the new list
+ channel.get('members').reset(channel.temp_userlist || []);
+
+ // Clear the temporary userlist
+ delete channel.temp_userlist;
+ }
+
+
+
+ function onBanlist(event) {
+ var channel = this.panels.getByName(event.channel);
+ if (!channel)
+ return;
+
+ channel.set('banlist', event.bans || []);
+ }
+
+
+
+ function onMode(event) {
+ var channel, i, prefixes, members, member, find_prefix,
+ request_updated_banlist = false;
+
+ // Build a nicely formatted string to be displayed to a regular human
+ function friendlyModeString (event_modes, alt_target) {
+ var modes = {}, return_string;
+
+ // If no default given, use the main event info
+ if (!event_modes) {
+ event_modes = event.modes;
+ alt_target = event.target;
+ }
+
+ // Reformat the mode object to make it easier to work with
+ _.each(event_modes, function (mode){
+ var param = mode.param || alt_target || '';
+
+ // Make sure we have some modes for this param
+ if (!modes[param]) {
+ modes[param] = {'+':'', '-':''};
+ }
+
+ modes[param][mode.mode[0]] += mode.mode.substr(1);
+ });
+
+ // Put the string together from each mode
+ return_string = [];
+ _.each(modes, function (modeset, param) {
+ var str = '';
+ if (modeset['+']) str += '+' + modeset['+'];
+ if (modeset['-']) str += '-' + modeset['-'];
+ return_string.push(str + ' ' + param);
+ });
+ return_string = return_string.join(', ');
+
+ return return_string;
+ }
+
+
+ channel = this.panels.getByName(event.target);
+ if (channel) {
+ prefixes = this.get('user_prefixes');
+ find_prefix = function (p) {
+ return event.modes[i].mode[1] === p.mode;
+ };
+ for (i = 0; i < event.modes.length; i++) {
+ if (_.any(prefixes, find_prefix)) {
+ if (!members) {
+ members = channel.get('members');
+ }
+ member = members.getByNick(event.modes[i].param);
+ if (!member) {
+ console.log('MODE command recieved for unknown member %s on channel %s', event.modes[i].param, event.target);
+ return;
+ } else {
+ if (event.modes[i].mode[0] === '+') {
+ member.addMode(event.modes[i].mode[1]);
+ } else if (event.modes[i].mode[0] === '-') {
+ member.removeMode(event.modes[i].mode[1]);
+ }
+ members.sort();
+ }
+ } else {
+ // Channel mode being set
+ // TODO: Store this somewhere?
+ //channel.addMsg('', 'CHANNEL === ' + event.nick + ' set mode ' + event.modes[i].mode + ' on ' + event.target, 'action mode');
+ }
+
+ // TODO: Be smart, remove this specific ban from the banlist rather than request a whole banlist
+ if (event.modes[i].mode[1] == 'b')
+ request_updated_banlist = true;
+ }
+
+ channel.addMsg('', styleText('mode', {nick: event.nick, text: translateText('client_models_network_mode', [friendlyModeString()]), channel: event.target}), 'action mode', {time: event.time});
+
+ // TODO: Be smart, remove the specific ban from the banlist rather than request a whole banlist
+ if (request_updated_banlist)
+ this.gateway.raw('MODE ' + channel.get('name') + ' +b');
+
+ } else {
+ // This is probably a mode being set on us.
+ if (event.target.toLowerCase() === this.get("nick").toLowerCase()) {
+ this.panels.server.addMsg('', styleText('selfmode', {nick: event.nick, text: translateText('client_models_network_mode', [friendlyModeString()]), channel: event.target}), 'action mode');
+ } else {
+ console.log('MODE command recieved for unknown target %s: ', event.target, event);
+ }
+ }
+ }
+
+
+
+ function onWhois(event) {
+ var logon_date, idle_time = '', panel;
+
+ if (event.end)
+ return;
+
+ if (typeof event.idle !== 'undefined') {
+ idle_time = secondsToTime(parseInt(event.idle, 10));
+ idle_time = idle_time.h.toString().lpad(2, "0") + ':' + idle_time.m.toString().lpad(2, "0") + ':' + idle_time.s.toString().lpad(2, "0");
+ }
+
+ panel = _kiwi.app.panels().active;
+ if (event.ident) {
+ panel.addMsg(event.nick, styleText('whois_ident', {nick: event.nick, ident: event.ident, host: event.hostname, text: event.msg}), 'whois');
+
+ } else if (event.chans) {
+ panel.addMsg(event.nick, styleText('whois_channels', {nick: event.nick, text: translateText('client_models_network_channels', [event.chans])}), 'whois');
+ } else if (event.irc_server) {
+ panel.addMsg(event.nick, styleText('whois_server', {nick: event.nick, text: translateText('client_models_network_server', [event.irc_server, event.server_info])}), 'whois');
+ } else if (event.msg) {
+ panel.addMsg(event.nick, styleText('whois', {text: event.msg}), 'whois');
+ } else if (event.logon) {
+ logon_date = new Date();
+ logon_date.setTime(event.logon * 1000);
+ logon_date = _kiwi.utils.formatDate(logon_date);
+
+ panel.addMsg(event.nick, styleText('whois_idle_and_signon', {nick: event.nick, text: translateText('client_models_network_idle_and_signon', [idle_time, logon_date])}), 'whois');
+ } else if (event.away_reason) {
+ panel.addMsg(event.nick, styleText('whois_away', {nick: event.nick, text: translateText('client_models_network_away', [event.away_reason])}), 'whois');
+ } else {
+ panel.addMsg(event.nick, styleText('whois_idle', {nick: event.nick, text: translateText('client_models_network_idle', [idle_time])}), 'whois');
+ }
+ }
+
+ function onWhowas(event) {
+ var panel;
+
+ if (event.end)
+ return;
+
+ panel = _kiwi.app.panels().active;
+ if (event.hostname) {
+ panel.addMsg(event.nick, styleText('who', {nick: event.nick, ident: event.ident, host: event.hostname, realname: event.real_name, text: event.msg}), 'whois');
+ } else {
+ panel.addMsg(event.nick, styleText('whois_notfound', {nick: event.nick, text: translateText('client_models_network_nickname_notfound', [])}), 'whois');
+ }
+ }
+
+
+ function onAway(event) {
+ $.each(this.panels.models, function (index, panel) {
+ if (!panel.isChannel()) return;
+
+ member = panel.get('members').getByNick(event.nick);
+ if (member) {
+ member.set('away', !(!event.reason));
+ }
+ });
+ }
+
+
+
+ function onListStart(event) {
+ var chanlist = _kiwi.model.Applet.loadOnce('kiwi_chanlist');
+ chanlist.view.show();
+ }
+
+
+
+ function onIrcError(event) {
+ var panel, tmp;
+
+ if (event.channel !== undefined && !(panel = this.panels.getByName(event.channel))) {
+ panel = this.panels.server;
+ }
+
+ switch (event.error) {
+ case 'banned_from_channel':
+ panel.addMsg(' ', styleText('channel_banned', {nick: event.nick, text: translateText('client_models_network_banned', [event.channel, event.reason]), channel: event.channel}), 'status');
+ _kiwi.app.message.text(_kiwi.global.i18n.translate('client_models_network_banned').fetch(event.channel, event.reason));
+ break;
+ case 'bad_channel_key':
+ panel.addMsg(' ', styleText('channel_badkey', {nick: event.nick, text: translateText('client_models_network_channel_badkey', [event.channel]), channel: event.channel}), 'status');
+ _kiwi.app.message.text(_kiwi.global.i18n.translate('client_models_network_channel_badkey').fetch(event.channel));
+ break;
+ case 'invite_only_channel':
+ panel.addMsg(' ', styleText('channel_inviteonly', {nick: event.nick, text: translateText('client_models_network_channel_inviteonly', [event.nick, event.channel]), channel: event.channel}), 'status');
+ _kiwi.app.message.text(event.channel + ' ' + _kiwi.global.i18n.translate('client_models_network_channel_inviteonly').fetch());
+ break;
+ case 'user_on_channel':
+ panel.addMsg(' ', styleText('channel_alreadyin', {nick: event.nick, text: translateText('client_models_network_channel_alreadyin'), channel: event.channel}));
+ break;
+ case 'channel_is_full':
+ panel.addMsg(' ', styleText('channel_limitreached', {nick: event.nick, text: translateText('client_models_network_channel_limitreached', [event.channel]), channel: event.channel}), 'status');
+ _kiwi.app.message.text(event.channel + ' ' + _kiwi.global.i18n.translate('client_models_network_channel_limitreached').fetch(event.channel));
+ break;
+ case 'chanop_privs_needed':
+ panel.addMsg(' ', styleText('chanop_privs_needed', {text: event.reason, channel: event.channel}), 'status');
+ _kiwi.app.message.text(event.reason + ' (' + event.channel + ')');
+ break;
+ case 'cannot_send_to_channel':
+ panel.addMsg(' ', '== ' + _kiwi.global.i18n.translate('Cannot send message to channel, you are not voiced').fetch(event.channel, event.reason), 'status');
+ break;
+ case 'no_such_nick':
+ tmp = this.panels.getByName(event.nick);
+ if (tmp) {
+ tmp.addMsg(' ', styleText('no_such_nick', {nick: event.nick, text: event.reason, channel: event.channel}), 'status');
+ } else {
+ this.panels.server.addMsg(' ', styleText('no_such_nick', {nick: event.nick, text: event.reason, channel: event.channel}), 'status');
+ }
+ break;
+ case 'nickname_in_use':
+ this.panels.server.addMsg(' ', styleText('nickname_alreadyinuse', {nick: event.nick, text: translateText('client_models_network_nickname_alreadyinuse', [event.nick]), channel: event.channel}), 'status');
+ if (this.panels.server !== this.panels.active) {
+ _kiwi.app.message.text(_kiwi.global.i18n.translate('client_models_network_nickname_alreadyinuse').fetch(event.nick));
+ }
+
+ // Only show the nickchange component if the controlbox is open
+ if (_kiwi.app.controlbox.$el.css('display') !== 'none') {
+ (new _kiwi.view.NickChangeBox()).render();
+ }
+
+ break;
+
+ case 'password_mismatch':
+ this.panels.server.addMsg(' ', styleText('channel_badpassword', {nick: event.nick, text: translateText('client_models_network_badpassword', []), channel: event.channel}), 'status');
+ break;
+
+ case 'error':
+ if (event.reason) {
+ this.panels.server.addMsg(' ', styleText('general_error', {text: event.reason}), 'status');
+ }
+ break;
+
+ default:
+ // We don't know what data contains, so don't do anything with it.
+ //_kiwi.front.tabviews.server.addMsg(null, ' ', '== ' + data, 'status');
+ }
+ }
+
+
+ function onUnknownCommand(event) {
+ var display_params = _.clone(event.params);
+
+ // A lot of commands have our nick as the first parameter. This is redundant for us
+ if (display_params[0] && display_params[0] == this.get('nick')) {
+ display_params.shift();
+ }
+
+ this.panels.server.addMsg('', styleText('unknown_command', {text: '[' + event.command + '] ' + display_params.join(', ', '')}));
+ }
+
+
+ function onWallops(event) {
+ var active_panel = _kiwi.app.panels().active;
+
+ // Send to server panel
+ this.panels.server.addMsg('[' + (event.nick||'') + ']', styleText('wallops', {text: event.msg}), 'wallops', {time: event.time});
+
+ // Send to active panel if its a channel/query *and* it's related to this network
+ if (active_panel !== this.panels.server && (active_panel.isChannel() || active_panel.isQuery()) && active_panel.get('network') === this)
+ active_panel.addMsg('[' + (event.nick||'') + ']', styleText('wallops', {text: event.msg}), 'wallops', {time: event.time});
+ }
+
+}
+
+)();
+
+
+
+_kiwi.model.Member = Backbone.Model.extend({\r
+ initialize: function (attributes) {\r
+ var nick, modes, prefix;\r
+\r
+ // The nick may have a mode prefix, we don't want this\r
+ nick = this.stripPrefix(this.get("nick"));\r
+\r
+ // Make sure we have a mode array, and that it's sorted\r
+ modes = this.get("modes");\r
+ modes = modes || [];\r
+ this.sortModes(modes);\r
+\r
+ this.set({"nick": nick, "modes": modes, "prefix": this.getPrefix(modes)}, {silent: true});\r
+\r
+ this.updateOpStatus();\r
+\r
+ this.view = new _kiwi.view.Member({"model": this});\r
+ },\r
+\r
+\r
+ /**\r
+ * Sort modes in order of importance\r
+ */\r
+ sortModes: function (modes) {\r
+ var that = this;\r
+\r
+ return modes.sort(function (a, b) {\r
+ var a_idx, b_idx, i;\r
+ var user_prefixes = that.get('user_prefixes');\r
+\r
+ for (i = 0; i < user_prefixes.length; i++) {\r
+ if (user_prefixes[i].mode === a) {\r
+ a_idx = i;\r
+ }\r
+ }\r
+\r
+ for (i = 0; i < user_prefixes.length; i++) {\r
+ if (user_prefixes[i].mode === b) {\r
+ b_idx = i;\r
+ }\r
+ }\r
+\r
+ if (a_idx < b_idx) {\r
+ return -1;\r
+ } else if (a_idx > b_idx) {\r
+ return 1;\r
+ } else {\r
+ return 0;\r
+ }\r
+ });\r
+ },\r
+\r
+\r
+ addMode: function (mode) {\r
+ var modes_to_add = mode.split(''),\r
+ modes, prefix;\r
+\r
+ modes = this.get("modes");\r
+ $.each(modes_to_add, function (index, item) {\r
+ modes.push(item);\r
+ });\r
+\r
+ modes = this.sortModes(modes);\r
+ this.set({"prefix": this.getPrefix(modes), "modes": modes});\r
+\r
+ this.updateOpStatus();\r
+\r
+ this.view.render();\r
+ },\r
+\r
+\r
+ removeMode: function (mode) {\r
+ var modes_to_remove = mode.split(''),\r
+ modes, prefix;\r
+\r
+ modes = this.get("modes");\r
+ modes = _.reject(modes, function (m) {\r
+ return (_.indexOf(modes_to_remove, m) !== -1);\r
+ });\r
+\r
+ this.set({"prefix": this.getPrefix(modes), "modes": modes});\r
+\r
+ this.updateOpStatus();\r
+\r
+ this.view.render();\r
+ },\r
+\r
+\r
+ /**\r
+ * Figure out a valid prefix given modes.\r
+ * If a user is an op but also has voice, the prefix\r
+ * should be the op as it is more important.\r
+ */\r
+ getPrefix: function (modes) {\r
+ var prefix = '';\r
+ var user_prefixes = this.get('user_prefixes');\r
+\r
+ if (typeof modes[0] !== 'undefined') {\r
+ prefix = _.detect(user_prefixes, function (prefix) {\r
+ return prefix.mode === modes[0];\r
+ });\r
+\r
+ prefix = (prefix) ? prefix.symbol : '';\r
+ }\r
+\r
+ return prefix;\r
+ },\r
+\r
+\r
+ /**\r
+ * Remove any recognised prefix from a nick\r
+ */\r
+ stripPrefix: function (nick) {\r
+ var tmp = nick, i, j, k, nick_char;\r
+ var user_prefixes = this.get('user_prefixes');\r
+\r
+ i = 0;\r
+\r
+ nick_character_loop:\r
+ for (j = 0; j < nick.length; j++) {\r
+ nick_char = nick.charAt(j);\r
+\r
+ for (k = 0; k < user_prefixes.length; k++) {\r
+ if (nick_char === user_prefixes[k].symbol) {\r
+ i++;\r
+ continue nick_character_loop;\r
+ }\r
+ }\r
+\r
+ break;\r
+ }\r
+\r
+ return tmp.substr(i);\r
+ },\r
+\r
+\r
+\r
+ /**\r
+ * Format this nick into readable format (eg. nick [ident@hostname])\r
+ */\r
+ displayNick: function (full) {\r
+ var display = this.get('nick');\r
+\r
+ if (full) {\r
+ if (this.get("ident")) {\r
+ display += ' [' + this.get("ident") + '@' + this.get("hostname") + ']';\r
+ }\r
+ }\r
+\r
+ return display;\r
+ },\r
+\r
+\r
+ // Helper to quickly get user mask details\r
+ getMaskParts: function () {\r
+ return {\r
+ nick: this.get('nick') || '',\r
+ ident: this.get('ident') || '',\r
+ hostname: this.get('hostname') || ''\r
+ };\r
+ },\r
+\r
+\r
+ /**\r
+ * With the modes set on the user, make note if we have some sort of op status\r
+ */\r
+ updateOpStatus: function () {\r
+ var user_prefixes = this.get('user_prefixes'),\r
+ modes = this.get('modes'),\r
+ o, max_mode;\r
+\r
+ if (modes.length > 0) {\r
+ o = _.indexOf(user_prefixes, _.find(user_prefixes, function (prefix) {\r
+ return prefix.mode === 'o';\r
+ }));\r
+\r
+ max_mode = _.indexOf(user_prefixes, _.find(user_prefixes, function (prefix) {\r
+ return prefix.mode === modes[0];\r
+ }));\r
+\r
+ if ((max_mode === -1) || (max_mode > o)) {\r
+ this.set({"is_op": false}, {silent: true});\r
+ } else {\r
+ this.set({"is_op": true}, {silent: true});\r
+ }\r
+\r
+ } else {\r
+ this.set({"is_op": false}, {silent: true});\r
+ }\r
+ }\r
+});
+
+
+_kiwi.model.MemberList = Backbone.Collection.extend({\r
+ model: _kiwi.model.Member,\r
+ comparator: function (a, b) {\r
+ var i, a_modes, b_modes, a_idx, b_idx, a_nick, b_nick;\r
+ var user_prefixes = this.channel.get('network').get('user_prefixes');\r
+\r
+ a_modes = a.get("modes");\r
+ b_modes = b.get("modes");\r
+\r
+ // Try to sort by modes first\r
+ if (a_modes.length > 0) {\r
+ // a has modes, but b doesn't so a should appear first\r
+ if (b_modes.length === 0) {\r
+ return -1;\r
+ }\r
+ a_idx = b_idx = -1;\r
+ // Compare the first (highest) mode\r
+ for (i = 0; i < user_prefixes.length; i++) {\r
+ if (user_prefixes[i].mode === a_modes[0]) {\r
+ a_idx = i;\r
+ }\r
+ }\r
+ for (i = 0; i < user_prefixes.length; i++) {\r
+ if (user_prefixes[i].mode === b_modes[0]) {\r
+ b_idx = i;\r
+ }\r
+ }\r
+ if (a_idx < b_idx) {\r
+ return -1;\r
+ } else if (a_idx > b_idx) {\r
+ return 1;\r
+ }\r
+ // If we get to here both a and b have the same highest mode so have to resort to lexicographical sorting\r
+\r
+ } else if (b_modes.length > 0) {\r
+ // b has modes but a doesn't so b should appear first\r
+ return 1;\r
+ }\r
+ a_nick = a.get("nick").toLocaleUpperCase();\r
+ b_nick = b.get("nick").toLocaleUpperCase();\r
+ // Lexicographical sorting\r
+ if (a_nick < b_nick) {\r
+ return -1;\r
+ } else if (a_nick > b_nick) {\r
+ return 1;\r
+ } else {\r
+ return 0;\r
+ }\r
+ },\r
+\r
+\r
+ initialize: function (options) {\r
+ this.view = new _kiwi.view.MemberList({"model": this});\r
+ this.initNickCache();\r
+ },\r
+\r
+\r
+ /*\r
+ * Keep a reference to each member by the nick. Speeds up .getByNick()\r
+ * so it doesn't need to loop over every model for each nick lookup\r
+ */\r
+ initNickCache: function() {\r
+ var that = this;\r
+\r
+ this.nick_cache = Object.create(null);\r
+\r
+ this.on('reset', function() {\r
+ this.nick_cache = Object.create(null);\r
+\r
+ this.models.forEach(function(member) {\r
+ that.nick_cache[member.get('nick').toLowerCase()] = member;\r
+ });\r
+ });\r
+\r
+ this.on('add', function(member) {\r
+ that.nick_cache[member.get('nick').toLowerCase()] = member;\r
+ });\r
+\r
+ this.on('remove', function(member) {\r
+ delete that.nick_cache[member.get('nick').toLowerCase()];\r
+ });\r
+\r
+ this.on('change:nick', function(member) {\r
+ that.nick_cache[member.get('nick').toLowerCase()] = member;\r
+ delete that.nick_cache[member.previous('nick').toLowerCase()];\r
+ });\r
+ },\r
+\r
+\r
+ getByNick: function (nick) {\r
+ if (typeof nick !== 'string') return;\r
+ return this.nick_cache[nick.toLowerCase()];\r
+ }\r
+});
+
+
+_kiwi.model.NewConnection = Backbone.Collection.extend({
+ initialize: function() {
+ this.view = new _kiwi.view.ServerSelect({model: this});
+
+ this.view.bind('server_connect', this.onMakeConnection, this);
+
+ },
+
+
+ populateDefaultServerSettings: function() {
+ var defaults = _kiwi.global.defaultServerSettings();
+ this.view.populateFields(defaults);
+ },
+
+
+ onMakeConnection: function(new_connection_event) {
+ var that = this;
+
+ this.connect_details = new_connection_event;
+
+ this.view.networkConnecting();
+
+ _kiwi.gateway.newConnection({
+ nick: new_connection_event.nick,
+ host: new_connection_event.server,
+ port: new_connection_event.port,
+ ssl: new_connection_event.ssl,
+ password: new_connection_event.password,
+ options: new_connection_event.options
+ }, function(err, network) {
+ that.onNewNetwork(err, network);
+ });
+ },
+
+
+ onNewNetwork: function(err, network) {
+ // Show any errors if given
+ if (err) {
+ this.view.showError(err);
+ }
+
+ if (network && this.connect_details) {
+ network.auto_join = {
+ channel: this.connect_details.channel,
+ key: this.connect_details.channel_key
+ };
+
+ this.trigger('new_network', network);
+ }
+ }
+});
+
+
+_kiwi.model.Panel = Backbone.Model.extend({\r
+ initialize: function (attributes) {\r
+ var name = this.get("name") || "";\r
+ this.view = new _kiwi.view.Panel({"model": this, "name": name});\r
+ this.set({\r
+ "scrollback": [],\r
+ "name": name\r
+ }, {"silent": true});\r
+\r
+ _kiwi.global.events.emit('panel:created', {panel: this});\r
+ },\r
+\r
+ close: function () {\r
+ _kiwi.app.panels.trigger('close', this);\r
+ _kiwi.global.events.emit('panel:close', {panel: this});\r
+\r
+ if (this.view) {\r
+ this.view.unbind();\r
+ this.view.remove();\r
+ this.view = undefined;\r
+ delete this.view;\r
+ }\r
+\r
+ var members = this.get('members');\r
+ if (members) {\r
+ members.reset([]);\r
+ this.unset('members');\r
+ }\r
+\r
+ this.get('panel_list').remove(this);\r
+\r
+ this.unbind();\r
+ this.destroy();\r
+ },\r
+\r
+ isChannel: function () {\r
+ return false;\r
+ },\r
+\r
+ isQuery: function () {\r
+ return false;\r
+ },\r
+\r
+ isApplet: function () {\r
+ return false;\r
+ },\r
+\r
+ isServer: function () {\r
+ return false;\r
+ },\r
+\r
+ isActive: function () {\r
+ return (_kiwi.app.panels().active === this);\r
+ }\r
+});
+
+
+_kiwi.model.PanelList = Backbone.Collection.extend({\r
+ model: _kiwi.model.Panel,\r
+\r
+ comparator: function (chan) {\r
+ return chan.get('name');\r
+ },\r
+ initialize: function (elements, network) {\r
+ var that = this;\r
+\r
+ // If this PanelList is associated with a network/connection\r
+ if (network) {\r
+ this.network = network;\r
+ }\r
+\r
+ this.view = new _kiwi.view.Tabs({model: this});\r
+\r
+ // Holds the active panel\r
+ this.active = null;\r
+\r
+ // Keep a tab on the active panel\r
+ this.bind('active', function (active_panel) {\r
+ this.active = active_panel;\r
+ }, this);\r
+\r
+ this.bind('add', function(panel) {\r
+ panel.set('panel_list', this);\r
+ });\r
+ },\r
+\r
+\r
+\r
+ getByCid: function (cid) {\r
+ if (typeof name !== 'string') return;\r
+\r
+ return this.find(function (c) {\r
+ return cid === c.cid;\r
+ });\r
+ },\r
+\r
+\r
+\r
+ getByName: function (name) {\r
+ if (typeof name !== 'string') return;\r
+\r
+ return this.find(function (c) {\r
+ return name.toLowerCase() === c.get('name').toLowerCase();\r
+ });\r
+ }\r
+});\r
+
+
+
+_kiwi.model.NetworkPanelList = Backbone.Collection.extend({
+ model: _kiwi.model.Network,
+
+ initialize: function() {
+ this.view = new _kiwi.view.NetworkTabs({model: this});
+
+ this.on('add', this.onNetworkAdd, this);
+ this.on('remove', this.onNetworkRemove, this);
+
+ // Current active connection / panel
+ this.active_connection = undefined;
+ this.active_panel = undefined;
+
+ // TODO: Remove this - legacy
+ this.active = undefined;
+ },
+
+ getByConnectionId: function(id) {
+ return this.find(function(connection){
+ return connection.get('connection_id') == id;
+ });
+ },
+
+ panels: function() {
+ var panels = [];
+
+ this.each(function(network) {
+ panels = panels.concat(network.panels.models);
+ });
+
+ return panels;
+ },
+
+
+ onNetworkAdd: function(network) {
+ network.panels.on('active', this.onPanelActive, this);
+
+ // if it's our first connection, set it active
+ if (this.models.length === 1) {
+ this.active_connection = network;
+ this.active_panel = network.panels.server;
+
+ // TODO: Remove this - legacy
+ this.active = this.active_panel;
+ }
+ },
+
+ onNetworkRemove: function(network) {
+ network.panels.off('active', this.onPanelActive, this);
+ },
+
+ onPanelActive: function(panel) {
+ var connection = this.getByConnectionId(panel.tab.data('connection_id'));
+ this.trigger('active', panel, connection);
+
+ this.active_connection = connection;
+ this.active_panel = panel;
+
+ // TODO: Remove this - legacy
+ this.active = panel;
+ }
+});
+
+
+// TODO: Channel modes\r
+// TODO: Listen to gateway events for anythign related to this channel\r
+_kiwi.model.Channel = _kiwi.model.Panel.extend({\r
+ initialize: function (attributes) {\r
+ var name = this.get("name") || "",\r
+ members;\r
+\r
+ this.set({\r
+ "members": new _kiwi.model.MemberList(),\r
+ "name": name,\r
+ "scrollback": [],\r
+ "topic": ""\r
+ }, {"silent": true});\r
+\r
+ this.view = new _kiwi.view.Channel({"model": this, "name": name});\r
+\r
+ members = this.get("members");\r
+ members.channel = this;\r
+ members.bind("add", function (member, members, options) {\r
+ var show_message = _kiwi.global.settings.get('show_joins_parts');\r
+ if (show_message === false) {\r
+ return;\r
+ }\r
+\r
+ this.addMsg(' ', styleText('channel_join', {member: member.getMaskParts(), text: translateText('client_models_channel_join'), channel: name}), 'action join', {time: options.kiwi.time});\r
+ }, this);\r
+\r
+ members.bind("remove", function (member, members, options) {\r
+ var show_message = _kiwi.global.settings.get('show_joins_parts');\r
+ var msg = (options.kiwi.message) ? '(' + options.kiwi.message + ')' : '';\r
+\r
+ if (options.kiwi.type === 'quit' && show_message) {\r
+ this.addMsg(' ', styleText('channel_quit', {member: member.getMaskParts(), text: translateText('client_models_channel_quit', [msg]), channel: name}), 'action quit', {time: options.kiwi.time});\r
+\r
+ } else if (options.kiwi.type === 'kick') {\r
+\r
+ if (!options.kiwi.current_user_kicked) {\r
+ //If user kicked someone, show the message regardless of settings.\r
+ if (show_message || options.kiwi.current_user_initiated) {\r
+ this.addMsg(' ', styleText('channel_kicked', {member: member.getMaskParts(), text: translateText('client_models_channel_kicked', [options.kiwi.by, msg]), channel: name}), 'action kick', {time: options.kiwi.time});\r
+ }\r
+ } else {\r
+ this.addMsg(' ', styleText('channel_selfkick', {text: translateText('client_models_channel_selfkick', [options.kiwi.by, msg]), channel: name}), 'action kick', {time: options.kiwi.time});\r
+ }\r
+ } else if (show_message) {\r
+ this.addMsg(' ', styleText('channel_part', {member: member.getMaskParts(), text: translateText('client_models_channel_part', [msg]), channel: name}), 'action part', {time: options.kiwi.time});\r
+\r
+ }\r
+ }, this);\r
+\r
+ _kiwi.global.events.emit('panel:created', {panel: this});\r
+ },\r
+\r
+\r
+ addMsg: function (nick, msg, type, opts) {\r
+ var message_obj, bs, d, members, member,\r
+ scrollback = (parseInt(_kiwi.global.settings.get('scrollback'), 10) || 250);\r
+\r
+ opts = opts || {};\r
+\r
+ // Time defaults to now\r
+ if (typeof opts.time === 'number') {\r
+ opts.time = new Date(opts.time);\r
+ } else {\r
+ opts.time = new Date();\r
+ }\r
+\r
+ // CSS style defaults to empty string\r
+ if (!opts || typeof opts.style === 'undefined') {\r
+ opts.style = '';\r
+ }\r
+\r
+ // Create a message object\r
+ message_obj = {"msg": msg, "date": opts.date, "time": opts.time, "nick": nick, "chan": this.get("name"), "type": type, "style": opts.style};\r
+\r
+ // If this user has one, get its prefix\r
+ members = this.get('members');\r
+ if (members) {\r
+ member = members.getByNick(message_obj.nick);\r
+ if (member) {\r
+ message_obj.nick_prefix = member.get('prefix');\r
+ }\r
+ }\r
+\r
+ // The CSS class (action, topic, notice, etc)\r
+ if (typeof message_obj.type !== "string") {\r
+ message_obj.type = '';\r
+ }\r
+\r
+ // Make sure we don't have NaN or something\r
+ if (typeof message_obj.msg !== "string") {\r
+ message_obj.msg = '';\r
+ }\r
+\r
+ // Update the scrollback\r
+ bs = this.get("scrollback");\r
+ if (bs) {\r
+ bs.push(message_obj);\r
+\r
+ // Keep the scrolback limited\r
+ if (bs.length > scrollback) {\r
+ bs = _.last(bs, scrollback);\r
+ }\r
+ this.set({"scrollback": bs}, {silent: true});\r
+ }\r
+\r
+ this.trigger("msg", message_obj);\r
+ },\r
+\r
+\r
+ clearMessages: function () {\r
+ this.set({'scrollback': []}, {silent: true});\r
+ this.addMsg('', 'Window cleared');\r
+\r
+ this.view.render();\r
+ },\r
+\r
+\r
+ setMode: function(mode_string) {\r
+ this.get('network').gateway.mode(this.get('name'), mode_string);\r
+ },\r
+\r
+ isChannel: function() {\r
+ return true;\r
+ }\r
+});\r
+
+
+
+_kiwi.model.Query = _kiwi.model.Channel.extend({\r
+ initialize: function (attributes) {\r
+ var name = this.get("name") || "",\r
+ members;\r
+\r
+ this.view = new _kiwi.view.Channel({"model": this, "name": name});\r
+ this.set({\r
+ "name": name,\r
+ "scrollback": []\r
+ }, {"silent": true});\r
+\r
+ _kiwi.global.events.emit('panel:created', {panel: this});\r
+ },\r
+\r
+ isChannel: function () {\r
+ return false;\r
+ },\r
+\r
+ isQuery: function () {\r
+ return true;\r
+ }\r
+});
+
+
+_kiwi.model.Server = _kiwi.model.Channel.extend({\r
+ initialize: function (attributes) {\r
+ var name = "Server";\r
+ this.view = new _kiwi.view.Channel({"model": this, "name": name});\r
+ this.set({\r
+ "scrollback": [],\r
+ "name": name\r
+ }, {"silent": true});\r
+\r
+ _kiwi.global.events.emit('panel:created', {panel: this});\r
+ },\r
+\r
+ isServer: function () {\r
+ return true;\r
+ },\r
+\r
+ isChannel: function () {\r
+ return false;\r
+ }\r
+});
+
+
+_kiwi.model.Applet = _kiwi.model.Panel.extend({\r
+ initialize: function (attributes) {\r
+ // Temporary name\r
+ var name = "applet_"+(new Date().getTime().toString()) + Math.ceil(Math.random()*100).toString();\r
+ this.view = new _kiwi.view.Applet({model: this, name: name});\r
+\r
+ this.set({\r
+ "name": name\r
+ }, {"silent": true});\r
+\r
+ // Holds the loaded applet\r
+ this.loaded_applet = null;\r
+ },\r
+\r
+\r
+ // Load an applet within this panel\r
+ load: function (applet_object, applet_name) {\r
+ if (typeof applet_object === 'object') {\r
+ // Make sure this is a valid Applet\r
+ if (applet_object.get || applet_object.extend) {\r
+\r
+ // Try find a title for the applet\r
+ this.set('title', applet_object.get('title') || _kiwi.global.i18n.translate('client_models_applet_unknown').fetch());\r
+\r
+ // Update the tabs title if the applet changes it\r
+ applet_object.bind('change:title', function (obj, new_value) {\r
+ this.set('title', new_value);\r
+ }, this);\r
+\r
+ // If this applet has a UI, add it now\r
+ this.view.$el.html('');\r
+ if (applet_object.view) {\r
+ this.view.$el.append(applet_object.view.$el);\r
+ }\r
+\r
+ // Keep a reference to this applet\r
+ this.loaded_applet = applet_object;\r
+\r
+ this.loaded_applet.trigger('applet_loaded');\r
+ }\r
+\r
+ } else if (typeof applet_object === 'string') {\r
+ // Treat this as a URL to an applet script and load it\r
+ this.loadFromUrl(applet_object, applet_name);\r
+ }\r
+\r
+ return this;\r
+ },\r
+\r
+\r
+ loadFromUrl: function(applet_url, applet_name) {\r
+ var that = this;\r
+\r
+ this.view.$el.html(_kiwi.global.i18n.translate('client_models_applet_loading').fetch());\r
+ $script(applet_url, function () {\r
+ // Check if the applet loaded OK\r
+ if (!_kiwi.applets[applet_name]) {\r
+ that.view.$el.html(_kiwi.global.i18n.translate('client_models_applet_notfound').fetch());\r
+ return;\r
+ }\r
+\r
+ // Load a new instance of this applet\r
+ that.load(new _kiwi.applets[applet_name]());\r
+ });\r
+ },\r
+\r
+\r
+ close: function () {\r
+ this.view.$el.remove();\r
+ this.destroy();\r
+\r
+ this.view = undefined;\r
+\r
+ // Call the applets dispose method if it has one\r
+ if (this.loaded_applet && this.loaded_applet.dispose) {\r
+ this.loaded_applet.dispose();\r
+ }\r
+\r
+ // Call the inherited close()\r
+ this.constructor.__super__.close.apply(this, arguments);\r
+ },\r
+\r
+ isApplet: function () {\r
+ return true;\r
+ }\r
+},\r
+\r
+\r
+{\r
+ // Load an applet type once only. If it already exists, return that\r
+ loadOnce: function (applet_name) {\r
+\r
+ // See if we have an instance loaded already\r
+ var applet = _.find(_kiwi.app.panels('applets'), function(panel) {\r
+ // Ignore if it's not an applet\r
+ if (!panel.isApplet()) return;\r
+\r
+ // Ignore if it doesn't have an applet loaded\r
+ if (!panel.loaded_applet) return;\r
+\r
+ if (panel.loaded_applet.get('_applet_name') === applet_name) {\r
+ return true;\r
+ }\r
+ });\r
+\r
+ if (applet) return applet;\r
+\r
+\r
+ // If we didn't find an instance, load a new one up\r
+ return this.load(applet_name);\r
+ },\r
+\r
+\r
+ load: function (applet_name, options) {\r
+ var applet, applet_obj;\r
+\r
+ options = options || {};\r
+\r
+ applet_obj = this.getApplet(applet_name);\r
+\r
+ if (!applet_obj)\r
+ return;\r
+\r
+ // Create the applet and load the content\r
+ applet = new _kiwi.model.Applet();\r
+ applet.load(new applet_obj({_applet_name: applet_name}));\r
+\r
+ // Add it into the tab list if needed (default)\r
+ if (!options.no_tab)\r
+ _kiwi.app.applet_panels.add(applet);\r
+\r
+\r
+ return applet;\r
+ },\r
+\r
+\r
+ getApplet: function (applet_name) {\r
+ return _kiwi.applets[applet_name] || null;\r
+ },\r
+\r
+\r
+ register: function (applet_name, applet) {\r
+ _kiwi.applets[applet_name] = applet;\r
+ }\r
+});
+
+
+_kiwi.model.PluginManager = Backbone.Model.extend({\r
+ initialize: function () {\r
+ this.$plugin_holder = $('<div id="kiwi_plugins" style="display:none;"></div>')\r
+ .appendTo(_kiwi.app.view.$el);\r
+\r
+ this.loading_plugins = 0;\r
+ this.loaded_plugins = {};\r
+ },\r
+\r
+ // Load an applet within this panel\r
+ load: function (url) {\r
+ var that = this;\r
+\r
+ if (this.loaded_plugins[url]) {\r
+ this.unload(url);\r
+ }\r
+\r
+ this.loading_plugins++;\r
+\r
+ this.loaded_plugins[url] = $('<div></div>');\r
+ this.loaded_plugins[url].appendTo(this.$plugin_holder)\r
+ .load(url, _.bind(that.pluginLoaded, that));\r
+ },\r
+\r
+\r
+ unload: function (url) {\r
+ if (!this.loaded_plugins[url]) {\r
+ return;\r
+ }\r
+\r
+ this.loaded_plugins[url].remove();\r
+ delete this.loaded_plugins[url];\r
+ },\r
+\r
+\r
+ // Called after each plugin is loaded\r
+ pluginLoaded: function() {\r
+ this.loading_plugins--;\r
+\r
+ if (this.loading_plugins === 0) {\r
+ this.trigger('loaded');\r
+ }\r
+ },\r
+});
+
+
+_kiwi.model.DataStore = Backbone.Model.extend({
+ initialize: function () {
+ this._namespace = '';
+ this.new_data = {};
+ },
+
+ namespace: function (new_namespace) {
+ if (new_namespace) this._namespace = new_namespace;
+ return this._namespace;
+ },
+
+ // Overload the original save() method
+ save: function () {
+ localStorage.setItem(this._namespace, JSON.stringify(this.attributes));
+ },
+
+ // Overload the original load() method
+ load: function () {
+ if (!localStorage) return;
+
+ var data;
+
+ try {
+ data = JSON.parse(localStorage.getItem(this._namespace)) || {};
+ } catch (error) {
+ data = {};
+ }
+
+ this.attributes = data;
+ }
+},
+
+{
+ // Generates a new instance of DataStore with a set namespace
+ instance: function (namespace, attributes) {
+ var datastore = new _kiwi.model.DataStore(attributes);
+ datastore.namespace(namespace);
+ return datastore;
+ }
+});
+
+
+_kiwi.model.ChannelInfo = Backbone.Model.extend({
+ initialize: function () {
+ this.view = new _kiwi.view.ChannelInfo({"model": this});
+ }
+});
+
+
+_kiwi.view.Panel = Backbone.View.extend({
+ tagName: "div",
+ className: "panel",
+
+ events: {
+ },
+
+ initialize: function (options) {
+ this.initializePanel(options);
+ },
+
+ initializePanel: function (options) {
+ this.$el.css('display', 'none');
+ options = options || {};
+
+ // Containing element for this panel
+ if (options.container) {
+ this.$container = $(options.container);
+ } else {
+ this.$container = $('#kiwi .panels .container1');
+ }
+
+ this.$el.appendTo(this.$container);
+
+ this.alert_level = 0;
+
+ this.model.set({"view": this}, {"silent": true});
+
+ this.listenTo(this.model, 'change:activity_counter', function(model, new_count) {
+ var $act = this.model.tab.find('.activity');
+
+ if (new_count > 999) {
+ $act.text('999+');
+ } else {
+ $act.text(new_count);
+ }
+
+ if (new_count === 0) {
+ $act.addClass('zero');
+ } else {
+ $act.removeClass('zero');
+ }
+ });
+ },
+
+ render: function () {
+ },
+
+
+ show: function () {
+ var $this = this.$el;
+
+ // Hide all other panels and show this one
+ this.$container.children('.panel').css('display', 'none');
+ $this.css('display', 'block');
+
+ // Show this panels memberlist
+ var members = this.model.get("members");
+ if (members) {
+ _kiwi.app.rightbar.show();
+ members.view.show();
+ } else {
+ _kiwi.app.rightbar.hide();
+ }
+
+ // Remove any alerts and activity counters for this panel
+ this.alert('none');
+ this.model.set('activity_counter', 0);
+
+ _kiwi.app.panels.trigger('active', this.model, _kiwi.app.panels().active);
+ this.model.trigger('active', this.model);
+
+ _kiwi.app.view.doLayout();
+
+ if (!this.model.isApplet())
+ this.scrollToBottom(true);
+ },
+
+
+ alert: function (level) {
+ // No need to highlight if this si the active panel
+ if (this.model == _kiwi.app.panels().active) return;
+
+ var types, type_idx;
+ types = ['none', 'action', 'activity', 'highlight'];
+
+ // Default alert level
+ level = level || 'none';
+
+ // If this alert level does not exist, assume clearing current level
+ type_idx = _.indexOf(types, level);
+ if (!type_idx) {
+ level = 'none';
+ type_idx = 0;
+ }
+
+ // Only 'upgrade' the alert. Never down (unless clearing)
+ if (type_idx !== 0 && type_idx <= this.alert_level) {
+ return;
+ }
+
+ // Clear any existing levels
+ this.model.tab.removeClass(function (i, css) {
+ return (css.match(/\balert_\S+/g) || []).join(' ');
+ });
+
+ // Add the new level if there is one
+ if (level !== 'none') {
+ this.model.tab.addClass('alert_' + level);
+ }
+
+ this.alert_level = type_idx;
+ },
+
+
+ // Scroll to the bottom of the panel
+ scrollToBottom: function (force_down) {
+ // If this isn't the active panel, don't scroll
+ if (this.model !== _kiwi.app.panels().active) return;
+
+ // Don't scroll down if we're scrolled up the panel a little
+ if (force_down || this.$container.scrollTop() + this.$container.height() > this.$el.outerHeight() - 150) {
+ this.$container[0].scrollTop = this.$container[0].scrollHeight;
+ }
+ }
+});
+
+
+_kiwi.view.Channel = _kiwi.view.Panel.extend({
+ events: function(){
+ var parent_events = this.constructor.__super__.events;
+
+ if(_.isFunction(parent_events)){
+ parent_events = parent_events();
+ }
+ return _.extend({}, parent_events, {
+ 'click .msg .nick' : 'nickClick',
+ 'click .msg .inline-nick' : 'nickClick',
+ "click .chan": "chanClick",
+ 'click .media .open': 'mediaClick',
+ 'mouseenter .msg .nick': 'msgEnter',
+ 'mouseleave .msg .nick': 'msgLeave'
+ });
+ },
+
+ initialize: function (options) {
+ this.initializePanel(options);
+
+ // Container for all the messages
+ this.$messages = $('<div class="messages"></div>');
+ this.$el.append(this.$messages);
+
+ this.model.bind('change:topic', this.topic, this);
+ this.model.bind('change:topic_set_by', this.topicSetBy, this);
+
+ if (this.model.get('members')) {
+ // When we join the memberlist, we have officially joined the channel
+ this.model.get('members').bind('add', function (member) {
+ if (member.get('nick') === this.model.collection.network.get('nick')) {
+ this.$el.find('.initial_loader').slideUp(function () {
+ $(this).remove();
+ });
+ }
+ }, this);
+
+ // Memberlist reset with a new nicklist? Consider we have joined
+ this.model.get('members').bind('reset', function(members) {
+ if (members.getByNick(this.model.collection.network.get('nick'))) {
+ this.$el.find('.initial_loader').slideUp(function () {
+ $(this).remove();
+ });
+ }
+ }, this);
+ }
+
+ // Only show the loader if this is a channel (ie. not a query)
+ if (this.model.isChannel()) {
+ this.$el.append('<div class="initial_loader" style="margin:1em;text-align:center;"> ' + _kiwi.global.i18n.translate('client_views_channel_joining').fetch() + ' <span class="loader"></span></div>');
+ }
+
+ this.model.bind('msg', this.newMsg, this);
+ this.msg_count = 0;
+ },
+
+
+ render: function () {
+ var that = this;
+
+ this.$messages.empty();
+ _.each(this.model.get('scrollback'), function (msg) {
+ that.newMsg(msg);
+ });
+ },
+
+
+ newMsg: function(msg) {
+
+ // Parse the msg object into properties fit for displaying
+ msg = this.generateMessageDisplayObj(msg);
+
+ _kiwi.global.events.emit('message:display', {panel: this.model, message: msg})
+ .then(_.bind(function() {
+ var line_msg;
+
+ // Format the nick to the config defined format
+ var display_obj = _.clone(msg);
+ display_obj.nick = styleText('message_nick', {nick: msg.nick, prefix: msg.nick_prefix || ''});
+
+ line_msg = '<div class="msg <%= type %> <%= css_classes %>"><div class="time"><%- time_string %></div><div class="nick" style="<%= nick_style %>"><%- nick %></div><div class="text" style="<%= style %>"><%= msg %> </div></div>';
+ this.$messages.append($(_.template(line_msg, display_obj)).data('message', msg));
+
+ // Activity/alerts based on the type of new message
+ if (msg.type.match(/^action /)) {
+ this.alert('action');
+
+ } else if (msg.is_highlight) {
+ _kiwi.app.view.alertWindow('* ' + _kiwi.global.i18n.translate('client_views_panel_activity').fetch());
+ _kiwi.app.view.favicon.newHighlight();
+ _kiwi.app.view.playSound('highlight');
+ _kiwi.app.view.showNotification(this.model.get('name'), msg.unparsed_msg);
+ this.alert('highlight');
+
+ } else {
+ // If this is the active panel, send an alert out
+ if (this.model.isActive()) {
+ _kiwi.app.view.alertWindow('* ' + _kiwi.global.i18n.translate('client_views_panel_activity').fetch());
+ }
+ this.alert('activity');
+ }
+
+ if (this.model.isQuery() && !this.model.isActive()) {
+ _kiwi.app.view.alertWindow('* ' + _kiwi.global.i18n.translate('client_views_panel_activity').fetch());
+
+ // Highlights have already been dealt with above
+ if (!msg.is_highlight) {
+ _kiwi.app.view.favicon.newHighlight();
+ }
+
+ _kiwi.app.view.showNotification(this.model.get('name'), msg.unparsed_msg);
+ _kiwi.app.view.playSound('highlight');
+ }
+
+ // Update the activity counters
+ (function () {
+ // Only inrement the counters if we're not the active panel
+ if (this.model.isActive()) return;
+
+ var count_all_activity = _kiwi.global.settings.get('count_all_activity'),
+ exclude_message_types, new_count;
+
+ // Set the default config value
+ if (typeof count_all_activity === 'undefined') {
+ count_all_activity = false;
+ }
+
+ // Do not increment the counter for these message types
+ exclude_message_types = [
+ 'action join',
+ 'action quit',
+ 'action part',
+ 'action kick',
+ 'action nick',
+ 'action mode'
+ ];
+
+ if (count_all_activity || _.indexOf(exclude_message_types, msg.type) === -1) {
+ new_count = this.model.get('activity_counter') || 0;
+ new_count++;
+ this.model.set('activity_counter', new_count);
+ }
+
+ }).apply(this);
+
+ if(this.model.isActive()) this.scrollToBottom();
+
+ // Make sure our DOM isn't getting too large (Acts as scrollback)
+ this.msg_count++;
+ if (this.msg_count > (parseInt(_kiwi.global.settings.get('scrollback'), 10) || 250)) {
+ $('.msg:first', this.$messages).remove();
+ this.msg_count--;
+ }
+ }, this));
+ },
+
+
+ // Let nicks be clickable + colourise within messages
+ parseMessageNicks: function(word, colourise) {
+ var members, member, style = '';
+
+ members = this.model.get('members');
+ if (!members) {
+ return;
+ }
+
+ member = members.getByNick(word);
+ if (!member) {
+ return;
+ }
+
+ if (colourise !== false) {
+ // Use the nick from the member object so the style matches the letter casing
+ style = this.getNickStyles(member.get('nick')).asCssString();
+ }
+
+ return _.template('<span class="inline-nick" style="<%- style %>;cursor:pointer;" data-nick="<%- nick %>"><%- nick %></span>', {
+ nick: word,
+ style: style
+ });
+
+ },
+
+
+ // Make channels clickable
+ parseMessageChannels: function(word) {
+ var re,
+ parsed = false,
+ network = this.model.get('network');
+
+ if (!network) {
+ return;
+ }
+
+ re = new RegExp('(^|\\s)([' + escapeRegex(network.get('channel_prefix')) + '][^ ,\\007]+)', 'g');
+
+ if (!word.match(re)) {
+ return parsed;
+ }
+
+ parsed = word.replace(re, function (m1, m2) {
+ return m2 + '<a class="chan" data-channel="' + _.escape(m1.trim()) + '">' + _.escape(m1.trim()) + '</a>';
+ });
+
+ return parsed;
+ },
+
+
+ parseMessageUrls: function(word) {
+ var found_a_url = false,
+ parsed_url;
+
+ parsed_url = word.replace(/^(([A-Za-z][A-Za-z0-9\-]*\:\/\/)|(www\.))([\w.\-]+)([a-zA-Z]{2,6})(:[0-9]+)?(\/[\w!:.?$'()[\]*,;~+=&%@!\-\/]*)?(#.*)?$/gi, function (url) {
+ var nice = url,
+ extra_html = '';
+
+ // Don't allow javascript execution
+ if (url.match(/^javascript:/)) {
+ return url;
+ }
+
+ found_a_url = true;
+
+ // Add the http if no protoocol was found
+ if (url.match(/^www\./)) {
+ url = 'http://' + url;
+ }
+
+ // Shorten the displayed URL if it's going to be too long
+ if (nice.length > 100) {
+ nice = nice.substr(0, 100) + '...';
+ }
+
+ // Get any media HTML if supported
+ extra_html = _kiwi.view.MediaMessage.buildHtml(url);
+
+ // Make the link clickable
+ return '<a class="link_ext" target="_blank" rel="nofollow" href="' + url.replace(/"/g, '%22') + '">' + _.escape(nice) + '</a>' + extra_html;
+ });
+
+ return found_a_url ? parsed_url : false;
+ },
+
+
+ // Sgnerate a css style for a nick
+ getNickStyles: function(nick) {
+ var ret, colour, nick_int = 0, rgb, nick_lightness;
+
+ // Get a colour from a nick (Method based on IRSSIs nickcolor.pl)
+ _.map(nick.split(''), function (i) { nick_int += i.charCodeAt(0); });
+
+ nick_lightness = (_.find(_kiwi.app.themes, function (theme) {
+ return theme.name.toLowerCase() === _kiwi.global.settings.get('theme').toLowerCase();
+ }) || {}).nick_lightness;
+
+ if (typeof nick_lightness !== 'number') {
+ nick_lightness = 35;
+ } else {
+ nick_lightness = Math.max(0, Math.min(100, nick_lightness));
+ }
+
+ rgb = hsl2rgb(nick_int % 255, 70, nick_lightness);
+ rgb = rgb[2] | (rgb[1] << 8) | (rgb[0] << 16);
+ colour = '#' + rgb.toString(16);
+
+ ret = {color: colour};
+ ret.asCssString = function() {
+ return _.reduce(this, function(result, item, key){
+ return result + key + ':' + item + ';';
+ }, '');
+ };
+
+ return ret;
+ },
+
+
+ // Takes an IRC message object and parses it for displaying
+ generateMessageDisplayObj: function(msg) {
+ var nick_hex, time_difference,
+ message_words,
+ sb = this.model.get('scrollback'),
+ prev_msg = sb[sb.length-2],
+ hour, pm, am_pm_locale_key;
+
+ // Clone the msg object so we dont modify the original
+ msg = _.clone(msg);
+
+ // Defaults
+ msg.css_classes = '';
+ msg.nick_style = '';
+ msg.is_highlight = false;
+ msg.time_string = '';
+
+
+ // Nick highlight detecting
+ var nick = _kiwi.app.connections.active_connection.get('nick');
+ if ((new RegExp('(^|\\W)(' + escapeRegex(nick) + ')(\\W|$)', 'i')).test(msg.msg)) {
+ // Do not highlight the user's own input
+ if (msg.nick.localeCompare(nick) !== 0) {
+ msg.is_highlight = true;
+ msg.css_classes += ' highlight';
+ }
+ }
+
+ message_words = msg.msg.split(' ');
+ message_words = _.map(message_words, function(word) {
+ var parsed_word;
+
+ parsed_word = this.parseMessageUrls(word);
+ if (typeof parsed_word === 'string') return parsed_word;
+
+ parsed_word = this.parseMessageChannels(word);
+ if (typeof parsed_word === 'string') return parsed_word;
+
+ parsed_word = this.parseMessageNicks(word, (msg.type === 'privmsg'));
+ if (typeof parsed_word === 'string') return parsed_word;
+
+ parsed_word = _.escape(word);
+
+ // Replace text emoticons with images
+ if (_kiwi.global.settings.get('show_emoticons')) {
+ parsed_word = emoticonFromText(parsed_word);
+ }
+
+ return parsed_word;
+ }, this);
+
+ msg.unparsed_msg = msg.msg;
+ msg.msg = message_words.join(' ');
+
+ // Convert IRC formatting into HTML formatting
+ msg.msg = formatIRCMsg(msg.msg);
+
+ // Add some style to the nick
+ msg.nick_style = this.getNickStyles(msg.nick).asCssString();
+
+ // Generate a hex string from the nick to be used as a CSS class name
+ nick_hex = '';
+ if (msg.nick) {
+ _.map(msg.nick.split(''), function (char) {
+ nick_hex += char.charCodeAt(0).toString(16);
+ });
+ msg.css_classes += ' nick_' + nick_hex;
+ }
+
+ if (prev_msg) {
+ // Time difference between this message and the last (in minutes)
+ time_difference = (msg.time.getTime() - prev_msg.time.getTime())/1000/60;
+ if (prev_msg.nick === msg.nick && time_difference < 1) {
+ msg.css_classes += ' repeated_nick';
+ }
+ }
+
+ // Build up and add the line
+ if (_kiwi.global.settings.get('use_24_hour_timestamps')) {
+ msg.time_string = msg.time.getHours().toString().lpad(2, "0") + ":" + msg.time.getMinutes().toString().lpad(2, "0") + ":" + msg.time.getSeconds().toString().lpad(2, "0");
+ } else {
+ hour = msg.time.getHours();
+ pm = hour > 11;
+
+ hour = hour % 12;
+ if (hour === 0)
+ hour = 12;
+
+ am_pm_locale_key = pm ?
+ 'client_views_panel_timestamp_pm' :
+ 'client_views_panel_timestamp_am';
+
+ msg.time_string = translateText(am_pm_locale_key, hour + ":" + msg.time.getMinutes().toString().lpad(2, "0") + ":" + msg.time.getSeconds().toString().lpad(2, "0"));
+ }
+
+ return msg;
+ },
+
+
+ topic: function (topic) {
+ if (typeof topic !== 'string' || !topic) {
+ topic = this.model.get("topic");
+ }
+
+ this.model.addMsg('', styleText('channel_topic', {text: topic, channel: this.model.get('name')}), 'topic');
+
+ // If this is the active channel then update the topic bar
+ if (_kiwi.app.panels().active === this.model) {
+ _kiwi.app.topicbar.setCurrentTopicFromChannel(this.model);
+ }
+ },
+
+ topicSetBy: function (topic) {
+ // If this is the active channel then update the topic bar
+ if (_kiwi.app.panels().active === this.model) {
+ _kiwi.app.topicbar.setCurrentTopicFromChannel(this.model);
+ }
+ },
+
+ // Click on a nickname
+ nickClick: function (event) {
+ var $target = $(event.currentTarget),
+ nick,
+ members = this.model.get('members'),
+ member;
+
+ event.stopPropagation();
+
+ // Check this current element for a nick before resorting to the main message
+ // (eg. inline nicks has the nick on its own element within the message)
+ nick = $target.data('nick');
+ if (!nick) {
+ nick = $target.parent('.msg').data('message').nick;
+ }
+
+ // Make sure this nick is still in the channel
+ member = members ? members.getByNick(nick) : null;
+ if (!member) {
+ return;
+ }
+
+ _kiwi.global.events.emit('nick:select', {target: $target, member: member, source: 'message'})
+ .then(_.bind(this.openUserMenuForNick, this, $target, member));
+ },
+
+
+ updateLastSeenMarker: function() {
+ if (this.model.isActive()) {
+ // Remove the previous last seen classes
+ this.$(".last_seen").removeClass("last_seen");
+
+ // Mark the last message the user saw
+ this.$messages.children().last().addClass("last_seen");
+ }
+ },
+
+
+ openUserMenuForNick: function ($target, member) {
+ var members = this.model.get('members'),
+ are_we_an_op = !!members.getByNick(_kiwi.app.connections.active_connection.get('nick')).get('is_op'),
+ userbox, menubox;
+
+ userbox = new _kiwi.view.UserBox();
+ userbox.setTargets(member, this.model);
+ userbox.displayOpItems(are_we_an_op);
+
+ menubox = new _kiwi.view.MenuBox(member.get('nick') || 'User');
+ menubox.addItem('userbox', userbox.$el);
+ menubox.showFooter(false);
+
+ _kiwi.global.events.emit('usermenu:created', {menu: menubox, userbox: userbox, user: member})
+ .then(_.bind(function() {
+ menubox.show();
+
+ // Position the userbox + menubox
+ var target_offset = $target.offset(),
+ t = target_offset.top,
+ m_bottom = t + menubox.$el.outerHeight(), // Where the bottom of menu will be
+ memberlist_bottom = this.$el.parent().offset().top + this.$el.parent().outerHeight();
+
+ // If the bottom of the userbox is going to be too low.. raise it
+ if (m_bottom > memberlist_bottom){
+ t = memberlist_bottom - menubox.$el.outerHeight();
+ }
+
+ // Set the new positon
+ menubox.$el.offset({
+ left: target_offset.left,
+ top: t
+ });
+ }, this))
+ .catch(_.bind(function() {
+ userbox = null;
+
+ menu.dispose();
+ menu = null;
+ }, this));
+ },
+
+
+ chanClick: function (event) {
+ var target = (event.target) ? $(event.target).data('channel') : $(event.srcElement).data('channel');
+
+ _kiwi.app.connections.active_connection.gateway.join(target);
+ },
+
+
+ mediaClick: function (event) {
+ var $media = $(event.target).parents('.media');
+ var media_message;
+
+ if ($media.data('media')) {
+ media_message = $media.data('media');
+ } else {
+ media_message = new _kiwi.view.MediaMessage({el: $media[0]});
+
+ // Cache this MediaMessage instance for when it's opened again
+ $media.data('media', media_message);
+ }
+
+ media_message.toggle();
+ },
+
+
+ // Cursor hovers over a message
+ msgEnter: function (event) {
+ var nick_class;
+
+ // Find a valid class that this element has
+ _.each($(event.currentTarget).parent('.msg').attr('class').split(' '), function (css_class) {
+ if (css_class.match(/^nick_[a-z0-9]+/i)) {
+ nick_class = css_class;
+ }
+ });
+
+ // If no class was found..
+ if (!nick_class) return;
+
+ $('.'+nick_class).addClass('global_nick_highlight');
+ },
+
+
+ // Cursor leaves message
+ msgLeave: function (event) {
+ var nick_class;
+
+ // Find a valid class that this element has
+ _.each($(event.currentTarget).parent('.msg').attr('class').split(' '), function (css_class) {
+ if (css_class.match(/^nick_[a-z0-9]+/i)) {
+ nick_class = css_class;
+ }
+ });
+
+ // If no class was found..
+ if (!nick_class) return;
+
+ $('.'+nick_class).removeClass('global_nick_highlight');
+ }
+});
+
+
+
+_kiwi.view.Applet = _kiwi.view.Panel.extend({
+ className: 'panel applet',
+ initialize: function (options) {
+ this.initializePanel(options);
+ }
+});
+
+
+_kiwi.view.Application = Backbone.View.extend({
+ initialize: function () {
+ var that = this;
+
+ this.$el = $($('#tmpl_application').html().trim());
+ this.el = this.$el[0];
+
+ $(this.model.get('container') || 'body').append(this.$el);
+
+ this.elements = {
+ panels: this.$el.find('.panels'),
+ right_bar: this.$el.find('.right_bar'),
+ toolbar: this.$el.find('.toolbar'),
+ controlbox: this.$el.find('.controlbox'),
+ resize_handle: this.$el.find('.memberlists_resize_handle')
+ };
+
+ $(window).resize(function() { that.doLayout.apply(that); });
+ this.elements.toolbar.resize(function() { that.doLayout.apply(that); });
+ this.elements.controlbox.resize(function() { that.doLayout.apply(that); });
+
+ // Change the theme when the config is changed
+ _kiwi.global.settings.on('change:theme', this.updateTheme, this);
+ this.updateTheme(getQueryVariable('theme'));
+
+ _kiwi.global.settings.on('change:channel_list_style', this.setTabLayout, this);
+ this.setTabLayout(_kiwi.global.settings.get('channel_list_style'));
+
+ _kiwi.global.settings.on('change:show_timestamps', this.displayTimestamps, this);
+ this.displayTimestamps(_kiwi.global.settings.get('show_timestamps'));
+
+ this.$el.appendTo($('body'));
+ this.doLayout();
+
+ $(document).keydown(this.setKeyFocus);
+
+ // Confirmation require to leave the page
+ window.onbeforeunload = function () {
+ if (_kiwi.gateway.isConnected()) {
+ return _kiwi.global.i18n.translate('client_views_application_close_notice').fetch();
+ }
+ };
+
+ // Keep tabs on the browser having focus
+ this.has_focus = true;
+
+ $(window).on('focus', function windowOnFocus() {
+ that.has_focus = true;
+ });
+
+ $(window).on('blur', function windowOnBlur() {
+ var active_panel = that.model.panels().active;
+ if (active_panel && active_panel.view.updateLastSeenMarker) {
+ active_panel.view.updateLastSeenMarker();
+ }
+
+ that.has_focus = false;
+ });
+
+ // If we get a touchstart event, make note of it so we know we're using a touchscreen
+ $(window).on('touchstart', function windowOnTouchstart() {
+ that.$el.addClass('touch');
+ $(window).off('touchstart', windowOnTouchstart);
+ });
+
+
+ this.favicon = new _kiwi.view.Favicon();
+ this.initSound();
+
+ this.monitorPanelFallback();
+ },
+
+
+
+ updateTheme: function (theme_name) {
+ // If called by the settings callback, get the correct new_value
+ if (theme_name === _kiwi.global.settings) {
+ theme_name = arguments[1];
+ }
+
+ // If we have no theme specified, get it from the settings
+ if (!theme_name) theme_name = _kiwi.global.settings.get('theme') || 'relaxed';
+
+ theme_name = theme_name.toLowerCase();
+
+ // Clear any current theme
+ $('[data-theme]:not([disabled])').each(function (idx, link) {
+ var $link = $(link);
+ $link.attr('rel', 'alternate ' + $link.attr('rel')).attr('disabled', true)[0].disabled = true;
+ });
+
+ // Apply the new theme
+ var link = $('[data-theme][title=' + theme_name + ']');
+ if (link.length > 0) {
+ link.attr('rel', 'stylesheet').attr('disabled', false)[0].disabled = false;
+ }
+
+ this.doLayout();
+ },
+
+
+ setTabLayout: function (layout_style) {
+ // If called by the settings callback, get the correct new_value
+ if (layout_style === _kiwi.global.settings) {
+ layout_style = arguments[1];
+ }
+
+ if (layout_style == 'list') {
+ this.$el.addClass('chanlist_treeview');
+ } else {
+ this.$el.removeClass('chanlist_treeview');
+ }
+
+ this.doLayout();
+ },
+
+
+ displayTimestamps: function (show_timestamps) {
+ // If called by the settings callback, get the correct new_value
+ if (show_timestamps === _kiwi.global.settings) {
+ show_timestamps = arguments[1];
+ }
+
+ if (show_timestamps) {
+ this.$el.addClass('timestamps');
+ } else {
+ this.$el.removeClass('timestamps');
+ }
+ },
+
+
+ // Globally shift focus to the command input box on a keypress
+ setKeyFocus: function (ev) {
+ // If we're copying text, don't shift focus
+ if (ev.ctrlKey || ev.altKey || ev.metaKey) {
+ return;
+ }
+
+ // If we're typing into an input box somewhere, ignore
+ if ((ev.target.tagName.toLowerCase() === 'input') || (ev.target.tagName.toLowerCase() === 'textarea') || $(ev.target).attr('contenteditable')) {
+ return;
+ }
+
+ $('#kiwi .controlbox .inp').focus();
+ },
+
+
+ doLayout: function () {
+ var $kiwi = this.$el;
+ var $panels = this.elements.panels;
+ var $right_bar = this.elements.right_bar;
+ var $toolbar = this.elements.toolbar;
+ var $controlbox = this.elements.controlbox;
+ var $resize_handle = this.elements.resize_handle;
+
+ if (!$kiwi.is(':visible')) {
+ return;
+ }
+
+ var css_heights = {
+ top: $toolbar.outerHeight(true),
+ bottom: $controlbox.outerHeight(true)
+ };
+
+
+ // If any elements are not visible, full size the panals instead
+ if (!$toolbar.is(':visible')) {
+ css_heights.top = 0;
+ }
+
+ if (!$controlbox.is(':visible')) {
+ css_heights.bottom = 0;
+ }
+
+ // Apply the CSS sizes
+ $panels.css(css_heights);
+ $right_bar.css(css_heights);
+ $resize_handle.css(css_heights);
+
+ // If we have channel tabs on the side, adjust the height
+ if ($kiwi.hasClass('chanlist_treeview')) {
+ this.$el.find('.tabs', $kiwi).css(css_heights);
+ }
+
+ // Determine if we have a narrow window (mobile/tablet/or even small desktop window)
+ if ($kiwi.outerWidth() < 420) {
+ $kiwi.addClass('narrow');
+ if (this.model.rightbar && this.model.rightbar.keep_hidden !== true)
+ this.model.rightbar.toggle(true);
+ } else {
+ $kiwi.removeClass('narrow');
+ if (this.model.rightbar && this.model.rightbar.keep_hidden !== false)
+ this.model.rightbar.toggle(false);
+ }
+
+ // Set the panels width depending on the memberlist visibility
+ if (!$right_bar.hasClass('disabled')) {
+ // Panels to the side of the memberlist
+ $panels.css('right', $right_bar.outerWidth(true));
+ // The resize handle sits overlapping the panels and memberlist
+ $resize_handle.css('left', $right_bar.position().left - ($resize_handle.outerWidth(true) / 2));
+ } else {
+ // Memberlist is hidden so panels to the right edge
+ $panels.css('right', 0);
+ // And move the handle just out of sight to the right
+ $resize_handle.css('left', $panels.outerWidth(true));
+ }
+
+ var input_wrap_width = parseInt($controlbox.find('.input_tools').outerWidth(), 10);
+ $controlbox.find('.input_wrap').css('right', input_wrap_width + 7);
+ },
+
+
+ alertWindow: function (title) {
+ if (!this.alertWindowTimer) {
+ this.alertWindowTimer = new (function () {
+ var that = this;
+ var tmr;
+ var has_focus = true;
+ var state = 0;
+ var default_title = _kiwi.app.server_settings.client.window_title || 'Kiwi IRC';
+ var title = 'Kiwi IRC';
+
+ this.setTitle = function (new_title) {
+ new_title = new_title || default_title;
+ window.document.title = new_title;
+ return new_title;
+ };
+
+ this.start = function (new_title) {
+ // Don't alert if we already have focus
+ if (has_focus) return;
+
+ title = new_title;
+ if (tmr) return;
+ tmr = setInterval(this.update, 1000);
+ };
+
+ this.stop = function () {
+ // Stop the timer and clear the title
+ if (tmr) clearInterval(tmr);
+ tmr = null;
+ this.setTitle();
+
+ // Some browsers don't always update the last title correctly
+ // Wait a few seconds and then reset
+ setTimeout(this.reset, 2000);
+ };
+
+ this.reset = function () {
+ if (tmr) return;
+ that.setTitle();
+ };
+
+
+ this.update = function () {
+ if (state === 0) {
+ that.setTitle(title);
+ state = 1;
+ } else {
+ that.setTitle();
+ state = 0;
+ }
+ };
+
+ $(window).focus(function (event) {
+ has_focus = true;
+ that.stop();
+
+ // Some browsers don't always update the last title correctly
+ // Wait a few seconds and then reset
+ setTimeout(that.reset, 2000);
+ });
+
+ $(window).blur(function (event) {
+ has_focus = false;
+ });
+ })();
+ }
+
+ this.alertWindowTimer.start(title);
+ },
+
+
+ barsHide: function (instant) {
+ var that = this;
+
+ if (!instant) {
+ this.$el.find('.toolbar').slideUp({queue: false, duration: 400, step: $.proxy(this.doLayout, this)});
+ $('#kiwi .controlbox').slideUp({queue: false, duration: 400, step: $.proxy(this.doLayout, this)});
+ } else {
+ this.$el.find('.toolbar').slideUp(0);
+ $('#kiwi .controlbox').slideUp(0);
+ this.doLayout();
+ }
+ },
+
+ barsShow: function (instant) {
+ var that = this;
+
+ if (!instant) {
+ this.$el.find('.toolbar').slideDown({queue: false, duration: 400, step: $.proxy(this.doLayout, this)});
+ $('#kiwi .controlbox').slideDown({queue: false, duration: 400, step: $.proxy(this.doLayout, this)});
+ } else {
+ this.$el.find('.toolbar').slideDown(0);
+ $('#kiwi .controlbox').slideDown(0);
+ this.doLayout();
+ }
+ },
+
+
+ initSound: function () {
+ var that = this,
+ base_path = this.model.get('base_path');
+
+ $script(base_path + '/assets/libs/soundmanager2/soundmanager2-nodebug-jsmin.js', function() {
+ if (typeof soundManager === 'undefined')
+ return;
+
+ soundManager.setup({
+ url: base_path + '/assets/libs/soundmanager2/',
+ flashVersion: 9, // optional: shiny features (default = 8)// optional: ignore Flash where possible, use 100% HTML5 mode
+ preferFlash: true,
+
+ onready: function() {
+ that.sound_object = soundManager.createSound({
+ id: 'highlight',
+ url: base_path + '/assets/sound/highlight.mp3'
+ });
+ }
+ });
+ });
+ },
+
+
+ playSound: function (sound_id) {
+ if (!this.sound_object) return;
+
+ if (_kiwi.global.settings.get('mute_sounds'))
+ return;
+
+ soundManager.play(sound_id);
+ },
+
+
+ showNotification: function(title, message) {
+ var icon = this.model.get('base_path') + '/assets/img/ico.png',
+ notifications = _kiwi.utils.notifications;
+
+ if (!this.has_focus && notifications.allowed()) {
+ notifications
+ .create(title, { icon: icon, body: message })
+ .closeAfter(5000)
+ .on('click', _.bind(window.focus, window));
+ }
+ },
+
+ monitorPanelFallback: function() {
+ var panel_access = [];
+
+ this.model.panels.on('active', function() {
+ var panel = _kiwi.app.panels().active,
+ panel_index;
+
+ // If the panel is already open, remove it so we can put it back in first place
+ panel_index = _.indexOf(panel_access, panel.cid);
+
+ if (panel_index > -1) {
+ panel_access.splice(panel_index, 1);
+ }
+
+ //Make this panel the most recently accessed
+ panel_access.unshift(panel.cid);
+ });
+
+ this.model.panels.on('remove', function(panel) {
+ // If closing the active panel, switch to the last-accessed panel
+ if (panel_access[0] === panel.cid) {
+ panel_access.shift();
+
+ //Get the last-accessed panel model now that we removed the closed one
+ var model = _.find(_kiwi.app.panels('applets').concat(_kiwi.app.panels('connections')), {cid: panel_access[0]});
+
+ if (model) {
+ model.view.show();
+ }
+ }
+ });
+ }
+});
+
+
+
+_kiwi.view.AppToolbar = Backbone.View.extend({
+ events: {
+ 'click .settings': 'clickSettings',
+ 'click .startup': 'clickStartup'
+ },
+
+ initialize: function () {
+ // Remove the new connection/startup link if the server has disabled server changing
+ if (_kiwi.app.server_settings.connection && !_kiwi.app.server_settings.connection.allow_change) {
+ this.$('.startup').css('display', 'none');
+ }
+ },
+
+ clickSettings: function (event) {
+ event.preventDefault();
+ _kiwi.app.controlbox.processInput('/settings');
+ },
+
+ clickStartup: function (event) {
+ event.preventDefault();
+ _kiwi.app.startup_applet.view.show();
+ }
+});
+
+
+
+_kiwi.view.ControlBox = Backbone.View.extend({
+ events: {
+ 'keydown .inp': 'process',
+ 'click .nick': 'showNickChange'
+ },
+
+ initialize: function () {
+ var that = this;
+
+ this.buffer = []; // Stores previously run commands
+ this.buffer_pos = 0; // The current position in the buffer
+
+ this.preprocessor = new InputPreProcessor();
+ this.preprocessor.recursive_depth = 5;
+
+ // Hold tab autocomplete data
+ this.tabcomplete = {active: false, data: [], prefix: ''};
+
+ // Keep the nick view updated with nick changes
+ _kiwi.app.connections.on('change:nick', function(connection) {
+ // Only update the nick view if it's the active connection
+ if (connection !== _kiwi.app.connections.active_connection)
+ return;
+
+ $('.nick', that.$el).text(connection.get('nick'));
+ });
+
+ // Update our nick view as we flick between connections
+ _kiwi.app.connections.on('active', function(panel, connection) {
+ $('.nick', that.$el).text(connection.get('nick'));
+ });
+
+ // Keep focus on the input box as we flick between panels
+ _kiwi.app.panels.bind('active', function (active_panel) {
+ if (active_panel.isChannel() || active_panel.isServer() || active_panel.isQuery()) {
+ that.$('.inp').focus();
+ }
+ });
+ },
+
+ render: function() {
+ var send_message_text = translateText('client_views_controlbox_message');
+ this.$('.inp').attr('placeholder', send_message_text);
+
+ return this;
+ },
+
+ showNickChange: function (ev) {
+ // Nick box already open? Don't do it again
+ if (this.nick_change)
+ return;
+
+ this.nick_change = new _kiwi.view.NickChangeBox();
+ this.nick_change.render();
+
+ this.listenTo(this.nick_change, 'close', function() {
+ delete this.nick_change;
+ });
+ },
+
+ process: function (ev) {
+ var that = this,
+ inp = $(ev.currentTarget),
+ inp_val = inp.val(),
+ meta;
+
+ if (navigator.appVersion.indexOf("Mac") !== -1) {
+ meta = ev.metaKey;
+ } else {
+ meta = ev.altKey;
+ }
+
+ // If not a tab key, reset the tabcomplete data
+ if (this.tabcomplete.active && ev.keyCode !== 9) {
+ this.tabcomplete.active = false;
+ this.tabcomplete.data = [];
+ this.tabcomplete.prefix = '';
+ }
+
+ switch (true) {
+ case (ev.keyCode === 13): // return
+ inp_val = inp_val.trim();
+
+ if (inp_val) {
+ $.each(inp_val.split('\n'), function (idx, line) {
+ that.processInput(line);
+ });
+
+ this.buffer.push(inp_val);
+ this.buffer_pos = this.buffer.length;
+ }
+
+ inp.val('');
+ return false;
+
+ break;
+
+ case (ev.keyCode === 38): // up
+ if (this.buffer_pos > 0) {
+ this.buffer_pos--;
+ inp.val(this.buffer[this.buffer_pos]);
+ }
+ //suppress browsers default behavior as it would set the cursor at the beginning
+ return false;
+
+ case (ev.keyCode === 40): // down
+ if (this.buffer_pos < this.buffer.length) {
+ this.buffer_pos++;
+ inp.val(this.buffer[this.buffer_pos]);
+ }
+ break;
+
+ case (ev.keyCode === 219 && meta): // [ + meta
+ // Find all the tab elements and get the index of the active tab
+ var $tabs = $('#kiwi .tabs').find('li[class!=connection]');
+ var cur_tab_ind = (function() {
+ for (var idx=0; idx<$tabs.length; idx++){
+ if ($($tabs[idx]).hasClass('active'))
+ return idx;
+ }
+ })();
+
+ // Work out the previous tab along. Wrap around if needed
+ if (cur_tab_ind === 0) {
+ $prev_tab = $($tabs[$tabs.length - 1]);
+ } else {
+ $prev_tab = $($tabs[cur_tab_ind - 1]);
+ }
+
+ $prev_tab.click();
+ return false;
+
+ case (ev.keyCode === 221 && meta): // ] + meta
+ // Find all the tab elements and get the index of the active tab
+ var $tabs = $('#kiwi .tabs').find('li[class!=connection]');
+ var cur_tab_ind = (function() {
+ for (var idx=0; idx<$tabs.length; idx++){
+ if ($($tabs[idx]).hasClass('active'))
+ return idx;
+ }
+ })();
+
+ // Work out the next tab along. Wrap around if needed
+ if (cur_tab_ind === $tabs.length - 1) {
+ $next_tab = $($tabs[0]);
+ } else {
+ $next_tab = $($tabs[cur_tab_ind + 1]);
+ }
+
+ $next_tab.click();
+ return false;
+
+ case (ev.keyCode === 9 //Check if ONLY tab is pressed
+ && !ev.shiftKey //(user could be using some browser
+ && !ev.altKey //keyboard shortcut)
+ && !ev.metaKey
+ && !ev.ctrlKey):
+ this.tabcomplete.active = true;
+ if (_.isEqual(this.tabcomplete.data, [])) {
+ // Get possible autocompletions
+ var ac_data = [],
+ members = _kiwi.app.panels().active.get('members');
+
+ // If we have a members list, get the models. Otherwise empty array
+ members = members ? members.models : [];
+
+ $.each(members, function (i, member) {
+ if (!member) return;
+ ac_data.push(member.get('nick'));
+ });
+
+ ac_data.push(_kiwi.app.panels().active.get('name'));
+
+ ac_data = _.sortBy(ac_data, function (nick) {
+ return nick.toLowerCase();
+ });
+ this.tabcomplete.data = ac_data;
+ }
+
+ if (inp_val[inp[0].selectionStart - 1] === ' ') {
+ return false;
+ }
+
+ (function () {
+ var tokens, // Words before the cursor position
+ val, // New value being built up
+ p1, // Position in the value just before the nick
+ newnick, // New nick to be displayed (cycles through)
+ range, // TextRange for setting new text cursor position
+ nick, // Current nick in the value
+ trailing = ': '; // Text to be inserted after a tabbed nick
+
+ tokens = inp_val.substring(0, inp[0].selectionStart).split(' ');
+ if (tokens[tokens.length-1] == ':')
+ tokens.pop();
+
+ // Only add the trailing text if not at the beginning of the line
+ if (tokens.length > 1)
+ trailing = '';
+
+ nick = tokens[tokens.length - 1];
+
+ if (this.tabcomplete.prefix === '') {
+ this.tabcomplete.prefix = nick;
+ }
+
+ this.tabcomplete.data = _.select(this.tabcomplete.data, function (n) {
+ return (n.toLowerCase().indexOf(that.tabcomplete.prefix.toLowerCase()) === 0);
+ });
+
+ if (this.tabcomplete.data.length > 0) {
+ // Get the current value before cursor position
+ p1 = inp[0].selectionStart - (nick.length);
+ val = inp_val.substr(0, p1);
+
+ // Include the current selected nick
+ newnick = this.tabcomplete.data.shift();
+ this.tabcomplete.data.push(newnick);
+ val += newnick;
+
+ if (inp_val.substr(inp[0].selectionStart, 2) !== trailing)
+ val += trailing;
+
+ // Now include the rest of the current value
+ val += inp_val.substr(inp[0].selectionStart);
+
+ inp.val(val);
+
+ // Move the cursor position to the end of the nick
+ if (inp[0].setSelectionRange) {
+ inp[0].setSelectionRange(p1 + newnick.length + trailing.length, p1 + newnick.length + trailing.length);
+ } else if (inp[0].createTextRange) { // not sure if this bit is actually needed....
+ range = inp[0].createTextRange();
+ range.collapse(true);
+ range.moveEnd('character', p1 + newnick.length + trailing.length);
+ range.moveStart('character', p1 + newnick.length + trailing.length);
+ range.select();
+ }
+ }
+ }).apply(this);
+ return false;
+ }
+ },
+
+
+ processInput: function (command_raw) {
+ var that = this,
+ command, params, events_data,
+ pre_processed;
+
+ // If sending a message when not in a channel or query window, automatically
+ // convert it into a command
+ if (command_raw[0] !== '/' && !_kiwi.app.panels().active.isChannel() && !_kiwi.app.panels().active.isQuery()) {
+ command_raw = '/' + command_raw;
+ }
+
+ // The default command
+ if (command_raw[0] !== '/' || command_raw.substr(0, 2) === '//') {
+ // Remove any slash escaping at the start (ie. //)
+ command_raw = command_raw.replace(/^\/\//, '/');
+
+ // Prepend the default command
+ command_raw = '/msg ' + _kiwi.app.panels().active.get('name') + ' ' + command_raw;
+ }
+
+ // Process the raw command for any aliases
+ this.preprocessor.vars.server = _kiwi.app.connections.active_connection.get('name');
+ this.preprocessor.vars.channel = _kiwi.app.panels().active.get('name');
+ this.preprocessor.vars.destination = this.preprocessor.vars.channel;
+ command_raw = this.preprocessor.process(command_raw);
+
+ // Extract the command and parameters
+ params = command_raw.split(/\s/);
+ if (params[0][0] === '/') {
+ command = params[0].substr(1).toLowerCase();
+ params = params.splice(1, params.length - 1);
+ } else {
+ // Default command
+ command = 'msg';
+ params.unshift(_kiwi.app.panels().active.get('name'));
+ }
+
+ // Emit a plugin event for any modifications
+ events_data = {command: command, params: params};
+
+ _kiwi.global.events.emit('command', events_data)
+ .then(function() {
+ // Trigger the command events
+ that.trigger('command', {command: events_data.command, params: events_data.params});
+ that.trigger('command:' + events_data.command, {command: events_data.command, params: events_data.params});
+
+ // If we didn't have any listeners for this event, fire a special case
+ // TODO: This feels dirty. Should this really be done..?
+ if (!that._events['command:' + events_data.command]) {
+ that.trigger('unknown_command', {command: events_data.command, params: events_data.params});
+ }
+ });
+ },
+
+
+ addPluginIcon: function ($icon) {
+ var $tool = $('<div class="tool"></div>').append($icon);
+ this.$el.find('.input_tools').append($tool);
+ _kiwi.app.view.doLayout();
+ }
+});
+
+
+
+_kiwi.view.Favicon = Backbone.View.extend({
+ initialize: function () {
+ var that = this,
+ $win = $(window);
+
+ this.has_focus = true;
+ this.highlight_count = 0;
+ // Check for html5 canvas support
+ this.has_canvas_support = !!window.CanvasRenderingContext2D;
+
+ // Store the original favicon
+ this.original_favicon = $('link[rel~="icon"]')[0].href;
+
+ // Create our favicon canvas
+ this._createCanvas();
+
+ // Reset favicon notifications when user focuses window
+ $win.on('focus', function () {
+ that.has_focus = true;
+ that._resetHighlights();
+ });
+ $win.on('blur', function () {
+ that.has_focus = false;
+ });
+ },
+
+ newHighlight: function () {
+ var that = this;
+ if (!this.has_focus) {
+ this.highlight_count++;
+ if (this.has_canvas_support) {
+ this._drawFavicon(function() {
+ that._drawBubble(that.highlight_count.toString());
+ that._refreshFavicon(that.canvas.toDataURL());
+ });
+ }
+ }
+ },
+
+ _resetHighlights: function () {
+ var that = this;
+ this.highlight_count = 0;
+ this._refreshFavicon(this.original_favicon);
+ },
+
+ _drawFavicon: function (callback) {
+ var that = this,
+ canvas = this.canvas,
+ context = canvas.getContext('2d'),
+ favicon_image = new Image();
+
+ // Allow cross origin resource requests
+ favicon_image.crossOrigin = 'anonymous';
+ // Trigger the load event
+ favicon_image.src = this.original_favicon;
+
+ favicon_image.onload = function() {
+ // Clear canvas from prevous iteration
+ context.clearRect(0, 0, canvas.width, canvas.height);
+ // Draw the favicon itself
+ context.drawImage(favicon_image, 0, 0, canvas.width, canvas.height);
+ callback();
+ };
+ },
+
+ _drawBubble: function (label) {
+ var letter_spacing,
+ bubble_width = 0, bubble_height = 0,
+ canvas = this.canvas,
+ context = test_context = canvas.getContext('2d'),
+ canvas_width = canvas.width,
+ canvas_height = canvas.height;
+
+ // Different letter spacing for MacOS
+ if (navigator.appVersion.indexOf("Mac") !== -1) {
+ letter_spacing = -1.5;
+ }
+ else {
+ letter_spacing = -1;
+ }
+
+ // Setup a test canvas to get text width
+ test_context.font = context.font = 'bold 10px Arial';
+ test_context.textAlign = 'right';
+ this._renderText(test_context, label, 0, 0, letter_spacing);
+
+ // Calculate bubble width based on letter spacing and padding
+ bubble_width = test_context.measureText(label).width + letter_spacing * (label.length - 1) + 2;
+ // Canvas does not have any way of measuring text height, so we just do it manually and add 1px top/bottom padding
+ bubble_height = 9;
+
+ // Set bubble coordinates
+ bubbleX = canvas_width - bubble_width;
+ bubbleY = canvas_height - bubble_height;
+
+ // Draw bubble background
+ context.fillStyle = 'red';
+ context.fillRect(bubbleX, bubbleY, bubble_width, bubble_height);
+
+ // Draw the text
+ context.fillStyle = 'white';
+ this._renderText(context, label, canvas_width - 1, canvas_height - 1, letter_spacing);
+ },
+
+ _refreshFavicon: function (url) {
+ $('link[rel~="icon"]').remove();
+ $('<link rel="shortcut icon" href="' + url + '">').appendTo($('head'));
+ },
+
+ _createCanvas: function () {
+ var canvas = document.createElement('canvas');
+ canvas.width = 16;
+ canvas.height = 16;
+
+ this.canvas = canvas;
+ },
+
+ _renderText: function (context, text, x, y, letter_spacing) {
+ // A hacky solution for letter-spacing, but works well with small favicon text
+ // Modified from http://jsfiddle.net/davidhong/hKbJ4/
+ var current,
+ characters = text.split('').reverse(),
+ index = 0,
+ currentPosition = x;
+
+ while (index < text.length) {
+ current = characters[index++];
+ context.fillText(current, currentPosition, y);
+ currentPosition += (-1 * (context.measureText(current).width + letter_spacing));
+ }
+
+ return context;
+ }
+});
+
+
+
+_kiwi.view.MediaMessage = Backbone.View.extend({
+ events: {
+ 'click .media_close': 'close'
+ },
+
+ initialize: function () {
+ // Get the URL from the data
+ this.url = this.$el.data('url');
+ },
+
+ toggle: function () {
+ if (!this.$content || !this.$content.is(':visible')) {
+ this.open();
+ } else {
+ this.close();
+ }
+ },
+
+ // Close the media content and remove it from display
+ close: function () {
+ var that = this;
+ this.$content.slideUp('fast', function () {
+ that.$content.remove();
+ });
+ },
+
+ // Open the media content within its wrapper
+ open: function () {
+ // Create the content div if we haven't already
+ if (!this.$content) {
+ this.$content = $('<div class="media_content"><a class="media_close"><i class="fa fa-chevron-up"></i> ' + _kiwi.global.i18n.translate('client_views_mediamessage_close').fetch() + '</a><br /><div class="content"></div></div>');
+ this.$content.find('.content').append(this.mediaTypes[this.$el.data('type')].apply(this, []) || _kiwi.global.i18n.translate('client_views_mediamessage_notfound').fetch() + ' :(');
+ }
+
+ // Now show the content if not already
+ if (!this.$content.is(':visible')) {
+ // Hide it first so the slideDown always plays
+ this.$content.hide();
+
+ // Add the media content and slide it into view
+ this.$el.append(this.$content);
+ this.$content.slideDown();
+ }
+ },
+
+
+
+ // Generate the media content for each recognised type
+ mediaTypes: {
+ twitter: function () {
+ var tweet_id = this.$el.data('tweetid');
+ var that = this;
+
+ $.getJSON('https://api.twitter.com/1/statuses/oembed.json?id=' + tweet_id + '&callback=?', function (data) {
+ that.$content.find('.content').html(data.html);
+ });
+
+ return $('<div>' + _kiwi.global.i18n.translate('client_views_mediamessage_load_tweet').fetch() + '...</div>');
+ },
+
+
+ image: function () {
+ return $('<a href="' + this.url + '" target="_blank"><img height="100" src="' + this.url + '" /></a>');
+ },
+
+
+ imgur: function () {
+ var that = this;
+
+ $.getJSON('http://api.imgur.com/oembed?url=' + this.url, function (data) {
+ var img_html = '<a href="' + data.url + '" target="_blank"><img height="100" src="' + data.url + '" /></a>';
+ that.$content.find('.content').html(img_html);
+ });
+
+ return $('<div>' + _kiwi.global.i18n.translate('client_views_mediamessage_load_image').fetch() + '...</div>');
+ },
+
+
+ reddit: function () {
+ var that = this;
+ var matches = (/reddit\.com\/r\/([a-zA-Z0-9_\-]+)\/comments\/([a-z0-9]+)\/([^\/]+)?/gi).exec(this.url);
+
+ $.getJSON('http://www.' + matches[0] + '.json?jsonp=?', function (data) {
+ console.log('Loaded reddit data', data);
+ var post = data[0].data.children[0].data;
+ var thumb = '';
+
+ // Show a thumbnail if there is one
+ if (post.thumbnail) {
+ //post.thumbnail = 'http://www.eurotunnel.com/uploadedImages/commercial/back-steps-icon-arrow.png';
+
+ // Hide the thumbnail if an over_18 image
+ if (post.over_18) {
+ thumb = '<span class="thumbnail_nsfw" onclick="$(this).find(\'p\').remove(); $(this).find(\'img\').css(\'visibility\', \'visible\');">';
+ thumb += '<p style="font-size:0.9em;line-height:1.2em;cursor:pointer;">Show<br />NSFW</p>';
+ thumb += '<img src="' + post.thumbnail + '" class="thumbnail" style="visibility:hidden;" />';
+ thumb += '</span>';
+ } else {
+ thumb = '<img src="' + post.thumbnail + '" class="thumbnail" />';
+ }
+ }
+
+ // Build the template string up
+ var tmpl = '<div>' + thumb + '<b><%- title %></b><br />Posted by <%- author %>. ';
+ tmpl += '<i class="fa fa-arrow-up"></i> <%- ups %> <i class="fa fa-arrow-down"></i> <%- downs %><br />';
+ tmpl += '<%- num_comments %> comments made. <a href="http://www.reddit.com<%- permalink %>">View post</a></div>';
+
+ that.$content.find('.content').html(_.template(tmpl, post));
+ });
+
+ return $('<div>' + _kiwi.global.i18n.translate('client_views_mediamessage_load_reddit').fetch() + '...</div>');
+ },
+
+
+ youtube: function () {
+ var ytid = this.$el.data('ytid');
+ var that = this;
+ var yt_html = '<iframe width="480" height="270" src="https://www.youtube.com/embed/'+ ytid +'?feature=oembed" frameborder="0" allowfullscreen=""></iframe>';
+ that.$content.find('.content').html(yt_html);
+
+ return $('');
+ },
+
+
+ gist: function () {
+ var that = this,
+ matches = (/https?:\/\/gist\.github\.com\/(?:[a-z0-9-]*\/)?([a-z0-9]+)(\#(.+))?$/i).exec(this.url);
+
+ $.getJSON('https://gist.github.com/'+matches[1]+'.json?callback=?' + (matches[2] || ''), function (data) {
+ $('body').append('<link rel="stylesheet" href="' + data.stylesheet + '" type="text/css" />');
+ that.$content.find('.content').html(data.div);
+ });
+
+ return $('<div>' + _kiwi.global.i18n.translate('client_views_mediamessage_load_gist').fetch() + '...</div>');
+ },
+
+ spotify: function () {
+ var uri = this.$el.data('uri'),
+ method = this.$el.data('method'),
+ spot, html;
+
+ switch (method) {
+ case "track":
+ case "album":
+ spot = {
+ url: 'https://embed.spotify.com/?uri=' + uri,
+ width: 300,
+ height: 80
+ };
+ break;
+ case "artist":
+ spot = {
+ url: 'https://embed.spotify.com/follow/1/?uri=' + uri +'&size=detail&theme=dark',
+ width: 300,
+ height: 56
+ };
+ break;
+ }
+
+ html = '<iframe src="' + spot.url + '" width="' + spot.width + '" height="' + spot.height + '" frameborder="0" allowtransparency="true"></iframe>';
+
+ return $(html);
+ },
+
+ soundcloud: function () {
+ var url = this.$el.data('url'),
+ $content = $('<div></div>').text(_kiwi.global.i18n.translate('client_models_applet_loading').fetch());
+
+ $.getJSON('https://soundcloud.com/oembed', { url: url })
+ .then(function (data) {
+ $content.empty().append(
+ $(data.html).attr('height', data.height - 100)
+ );
+ }, function () {
+ $content.text(_kiwi.global.i18n.translate('client_views_mediamessage_notfound').fetch());
+ });
+
+ return $content;
+ },
+
+ custom: function() {
+ var type = this.constructor.types[this.$el.data('index')];
+
+ if (!type)
+ return;
+
+ return $(type.buildHtml(this.$el.data('url')));
+ }
+
+ }
+ }, {
+
+ /**
+ * Add a media message type to append HTML after a matching URL
+ * match() should return a truthy value if it wants to handle this URL
+ * buildHtml() should return the HTML string to be used within the drop down
+ */
+ addType: function(match, buildHtml) {
+ if (typeof match !== 'function' || typeof buildHtml !== 'function')
+ return;
+
+ this.types = this.types || [];
+ this.types.push({match: match, buildHtml: buildHtml});
+ },
+
+
+ // Build the closed media HTML from a URL
+ buildHtml: function (url) {
+ var html = '', matches;
+
+ _.each(this.types || [], function(type, type_idx) {
+ if (!type.match(url))
+ return;
+
+ // Add which media type should handle this media message. Will be read when it's clicked on
+ html += '<span class="media" title="Open" data-type="custom" data-index="'+type_idx+'" data-url="' + _.escape(url) + '"><a class="open"><i class="fa fa-chevron-right"></i></a></span>';
+ });
+
+ // Is it an image?
+ if (url.match(/(\.jpe?g|\.gif|\.bmp|\.png)\??$/i)) {
+ html += '<span class="media image" data-type="image" data-url="' + url + '" title="Open Image"><a class="open"><i class="fa fa-chevron-right"></i></a></span>';
+ }
+
+ // Is this an imgur link not picked up by the images regex?
+ matches = (/imgur\.com\/[^/]*(?!=\.[^!.]+($|\?))/ig).exec(url);
+ if (matches && !url.match(/(\.jpe?g|\.gif|\.bmp|\.png)\??$/i)) {
+ html += '<span class="media imgur" data-type="imgur" data-url="' + url + '" title="Open Image"><a class="open"><i class="fa fa-chevron-right"></i></a></span>';
+ }
+
+ // Is it a tweet?
+ matches = (/https?:\/\/twitter.com\/([a-zA-Z0-9_]+)\/status\/([0-9]+)/ig).exec(url);
+ if (matches) {
+ html += '<span class="media twitter" data-type="twitter" data-url="' + url + '" data-tweetid="' + matches[2] + '" title="Show tweet information"><a class="open"><i class="fa fa-chevron-right"></i></a></span>';
+ }
+
+ // Is reddit?
+ matches = (/reddit\.com\/r\/([a-zA-Z0-9_\-]+)\/comments\/([a-z0-9]+)\/([^\/]+)?/gi).exec(url);
+ if (matches) {
+ html += '<span class="media reddit" data-type="reddit" data-url="' + url + '" title="Reddit thread"><a class="open"><i class="fa fa-chevron-right"></i></a></span>';
+ }
+
+ // Is youtube?
+ matches = (/(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/ ]{11})/gi).exec(url);
+ if (matches) {
+ html += '<span class="media youtube" data-type="youtube" data-url="' + url + '" data-ytid="' + matches[1] + '" title="YouTube Video"><a class="open"><i class="fa fa-chevron-right"></i></a></span>';
+ }
+
+ // Is a github gist?
+ matches = (/https?:\/\/gist\.github\.com\/(?:[a-z0-9-]*\/)?([a-z0-9]+)(\#(.+))?$/i).exec(url);
+ if (matches) {
+ html += '<span class="media gist" data-type="gist" data-url="' + url + '" data-gist_id="' + matches[1] + '" title="GitHub Gist"><a class="open"><i class="fa fa-chevron-right"></i></a></span>';
+ }
+
+ // Is this a spotify link?
+ matches = (/http:\/\/(?:play|open\.)?spotify.com\/(album|track|artist)\/([a-zA-Z0-9]+)\/?/i).exec(url);
+ if (matches) {
+ // Make it a Spotify URI! (spotify:<type>:<id>)
+ var method = matches[1],
+ uri = "spotify:" + matches[1] + ":" + matches[2];
+ html += '<span class="media spotify" data-type="spotify" data-uri="' + uri + '" data-method="' + method + '" title="Spotify ' + method + '"><a class="open"><i class="fa fa-chevron-right"></i></a></span>';
+ }
+
+ matches = (/(?:m\.)?(soundcloud\.com(?:\/.+))/i).exec(url);
+ if (matches) {
+ html += '<span class="media soundcloud" data-type="soundcloud" data-url="http://' + matches[1] + '" title="SoundCloud player"><a class="open"><i class="fa fa-chevron-right"></i></a></span>';
+ }
+
+ return html;
+ }
+});
+
+
+
+_kiwi.view.Member = Backbone.View.extend({
+ tagName: "li",
+ initialize: function (options) {
+ this.model.bind('change', this.render, this);
+ this.render();
+ },
+ render: function () {
+ var $this = this.$el,
+ prefix_css_class = (this.model.get('modes') || []).join(' ');
+
+ $this.attr('class', 'mode ' + prefix_css_class);
+ $this.html('<a class="nick"><span class="prefix">' + this.model.get("prefix") + '</span>' + this.model.get("nick") + '</a>');
+
+ return this;
+ }
+});
+
+
+_kiwi.view.MemberList = Backbone.View.extend({
+ tagName: "div",
+ events: {
+ "click .nick": "nickClick",
+ "click .channel_info": "channelInfoClick"
+ },
+
+ initialize: function (options) {
+ this.model.bind('all', this.render, this);
+ this.$el.appendTo('#kiwi .memberlists');
+
+ // Holds meta data. User counts, etc
+ this.$meta = $('<div class="meta"></div>').appendTo(this.$el);
+
+ // The list for holding the nicks
+ this.$list = $('<ul></ul>').appendTo(this.$el);
+ },
+ render: function () {
+ var that = this;
+
+ this.$list.empty();
+ this.model.forEach(function (member) {
+ member.view.$el.data('member', member);
+ that.$list.append(member.view.$el);
+ });
+
+ // User count
+ if(this.model.channel.isActive()) {
+ this.renderMeta();
+ }
+
+ return this;
+ },
+
+ renderMeta: function() {
+ var members_count = this.model.length + ' ' + translateText('client_applets_chanlist_users');
+ this.$meta.text(members_count);
+ },
+
+ nickClick: function (event) {
+ var $target = $(event.currentTarget).parent('li'),
+ member = $target.data('member');
+
+ _kiwi.global.events.emit('nick:select', {target: $target, member: member, source: 'nicklist'})
+ .then(_.bind(this.openUserMenuForItem, this, $target));
+ },
+
+
+ // Open a user menu for the given userlist item (<li>)
+ openUserMenuForItem: function($target) {
+ var member = $target.data('member'),
+ userbox,
+ are_we_an_op = !!this.model.getByNick(_kiwi.app.connections.active_connection.get('nick')).get('is_op');
+
+ userbox = new _kiwi.view.UserBox();
+ userbox.setTargets(member, this.model.channel);
+ userbox.displayOpItems(are_we_an_op);
+
+ var menu = new _kiwi.view.MenuBox(member.get('nick') || 'User');
+ menu.addItem('userbox', userbox.$el);
+ menu.showFooter(false);
+
+ _kiwi.global.events.emit('usermenu:created', {menu: menu, userbox: userbox, user: member})
+ .then(_.bind(function() {
+ menu.show();
+
+ var target_offset = $target.offset(),
+ t = target_offset.top,
+ m_bottom = t + menu.$el.outerHeight(), // Where the bottom of menu will be
+ memberlist_bottom = this.$el.parent().offset().top + this.$el.parent().outerHeight(),
+ l = target_offset.left,
+ m_right = l + menu.$el.outerWidth(), // Where the left of menu will be
+ memberlist_right = this.$el.parent().offset().left + this.$el.parent().outerWidth();
+
+ // If the bottom of the userbox is going to be too low.. raise it
+ if (m_bottom > memberlist_bottom){
+ t = memberlist_bottom - menu.$el.outerHeight();
+ }
+
+ // If the top of the userbox is going to be too high.. lower it
+ if (t < 0){
+ t = 0;
+ }
+
+ // If the right of the userbox is going off screen.. bring it in
+ if (m_right > memberlist_right){
+ l = memberlist_right - menu.$el.outerWidth();
+ }
+
+ // Set the new positon
+ menu.$el.offset({
+ left: l,
+ top: t
+ });
+
+ }, this))
+ .catch(_.bind(function() {
+ userbox = null;
+
+ menu.dispose();
+ menu = null;
+ }, this));
+ },
+
+
+ channelInfoClick: function(event) {
+ new _kiwi.model.ChannelInfo({channel: this.model.channel});
+ },
+
+
+ show: function () {
+ $('#kiwi .memberlists').children().removeClass('active');
+ $(this.el).addClass('active');
+
+ this.renderMeta();
+ }
+});
+
+
+_kiwi.view.MenuBox = Backbone.View.extend({
+ events: {
+ 'click .ui_menu_foot .close, a.close_menu': 'dispose'
+ },
+
+ initialize: function(title) {
+ var that = this;
+
+ this.$el = $('<div class="ui_menu"><div class="items"></div></div>');
+
+ this._title = title || '';
+ this._items = {};
+ this._display_footer = true;
+ this._close_on_blur = true;
+ },
+
+
+ render: function() {
+ var that = this,
+ $title,
+ $items = that.$el.find('.items');
+
+ $items.find('*').remove();
+
+ if (this._title) {
+ $title = $('<div class="ui_menu_title"></div>')
+ .text(this._title);
+
+ this.$el.prepend($title);
+ }
+
+ _.each(this._items, function(item) {
+ var $item = $('<div class="ui_menu_content hover"></div>')
+ .append(item);
+
+ $items.append($item);
+ });
+
+ if (this._display_footer)
+ this.$el.append('<div class="ui_menu_foot"><a class="close" onclick="">Close <i class="fa fa-times"></i></a></div>');
+
+ },
+
+
+ setTitle: function(new_title) {
+ this._title = new_title;
+
+ if (!this._title)
+ return;
+
+ this.$el.find('.ui_menu_title').text(this._title);
+ },
+
+
+ onDocumentClick: function(event) {
+ var $target = $(event.target);
+
+ if (!this._close_on_blur)
+ return;
+
+ // If this is not itself AND we don't contain this element, dispose $el
+ if ($target[0] != this.$el[0] && this.$el.has($target).length === 0)
+ this.dispose();
+ },
+
+
+ dispose: function() {
+ _.each(this._items, function(item) {
+ item.dispose && item.dispose();
+ item.remove && item.remove();
+ });
+
+ this._items = null;
+ this.remove();
+
+ if (this._close_proxy)
+ $(document).off('click', this._close_proxy);
+ },
+
+
+ addItem: function(item_name, $item) {
+ if ($item.is('a')) $item.addClass('fa fa-chevron-right');
+ this._items[item_name] = $item;
+ },
+
+
+ removeItem: function(item_name) {
+ delete this._items[item_name];
+ },
+
+
+ showFooter: function(show) {
+ this._display_footer = show;
+ },
+
+
+ closeOnBlur: function(close_it) {
+ this._close_on_blur = close_it;
+ },
+
+
+ show: function() {
+ var that = this,
+ $controlbox, menu_height;
+
+ this.render();
+ this.$el.appendTo(_kiwi.app.view.$el);
+
+ // Ensure the menu doesn't get too tall to overlap the input bar at the bottom
+ $controlbox = _kiwi.app.view.$el.find('.controlbox');
+ $items = this.$el.find('.items');
+ menu_height = this.$el.outerHeight() - $items.outerHeight();
+
+ $items.css({
+ 'overflow-y': 'auto',
+ 'max-height': $controlbox.offset().top - this.$el.offset().top - menu_height
+ });
+
+ // We add this document click listener on the next javascript tick.
+ // If the current tick is handling an existing click event (such as the nicklist click handler),
+ // the click event bubbles up and hits the document therefore calling this callback to
+ // remove this menubox before it's even shown.
+ setTimeout(function() {
+ that._close_proxy = function(event) {
+ that.onDocumentClick(event);
+ };
+ $(document).on('click', that._close_proxy);
+ }, 0);
+ }
+});
+
+
+
+// Model for this = _kiwi.model.NetworkPanelList
+_kiwi.view.NetworkTabs = Backbone.View.extend({
+ tagName: 'ul',
+ className: 'connections',
+
+ initialize: function() {
+ this.model.on('add', this.networkAdded, this);
+ this.model.on('remove', this.networkRemoved, this);
+
+ this.$el.appendTo(_kiwi.app.view.$el.find('.tabs'));
+ },
+
+ networkAdded: function(network) {
+ $('<li class="connection"></li>')
+ .append(network.panels.view.$el)
+ .appendTo(this.$el);
+ },
+
+ networkRemoved: function(network) {
+ // Remove the containing list element
+ network.panels.view.$el.parent().remove();
+
+ network.panels.view.remove();
+
+ _kiwi.app.view.doLayout();
+ }
+});
+
+
+_kiwi.view.NickChangeBox = Backbone.View.extend({
+ events: {
+ 'submit': 'changeNick',
+ 'click .cancel': 'close'
+ },
+
+ initialize: function () {
+ var text = {
+ new_nick: _kiwi.global.i18n.translate('client_views_nickchangebox_new').fetch(),
+ change: _kiwi.global.i18n.translate('client_views_nickchangebox_change').fetch(),
+ cancel: _kiwi.global.i18n.translate('client_views_nickchangebox_cancel').fetch()
+ };
+ this.$el = $(_.template($('#tmpl_nickchange').html().trim(), text));
+ },
+
+ render: function () {
+ // Add the UI component and give it focus
+ _kiwi.app.controlbox.$el.prepend(this.$el);
+ this.$el.find('input').focus();
+
+ this.$el.css('bottom', _kiwi.app.controlbox.$el.outerHeight(true));
+ },
+
+ close: function () {
+ this.$el.remove();
+ this.trigger('close');
+ },
+
+ changeNick: function (event) {
+ event.preventDefault();
+
+ var connection = _kiwi.app.connections.active_connection;
+ this.listenTo(connection, 'change:nick', function() {
+ this.close();
+ });
+
+ connection.gateway.changeNick(this.$('input').val());
+ }
+});
+
+
+_kiwi.view.ResizeHandler = Backbone.View.extend({
+ events: {
+ 'mousedown': 'startDrag',
+ 'mouseup': 'stopDrag'
+ },
+
+ initialize: function () {
+ this.dragging = false;
+ this.starting_width = {};
+
+ $(window).on('mousemove', $.proxy(this.onDrag, this));
+ },
+
+ startDrag: function (event) {
+ this.dragging = true;
+ },
+
+ stopDrag: function (event) {
+ this.dragging = false;
+ },
+
+ onDrag: function (event) {
+ if (!this.dragging) return;
+
+ var offset = $('#kiwi').offset().left;
+
+ this.$el.css('left', event.clientX - (this.$el.outerWidth(true) / 2) - offset);
+ $('#kiwi .right_bar').css('width', this.$el.parent().width() - (this.$el.position().left + this.$el.outerWidth()));
+ _kiwi.app.view.doLayout();
+ }
+});
+
+
+_kiwi.view.ServerSelect = Backbone.View.extend({
+ events: {
+ 'submit form': 'submitForm',
+ 'click .show_more': 'showMore',
+ 'change .have_pass input': 'showPass',
+ 'change .have_key input': 'showKey',
+ 'click .fa-key': 'channelKeyIconClick',
+ 'click .show_server': 'showServer'
+ },
+
+ initialize: function () {
+ var that = this,
+ text = {
+ think_nick: _kiwi.global.i18n.translate('client_views_serverselect_form_title').fetch(),
+ nickname: _kiwi.global.i18n.translate('client_views_serverselect_nickname').fetch(),
+ have_password: _kiwi.global.i18n.translate('client_views_serverselect_enable_password').fetch(),
+ password: _kiwi.global.i18n.translate('client_views_serverselect_password').fetch(),
+ channel: _kiwi.global.i18n.translate('client_views_serverselect_channel').fetch(),
+ channel_key: _kiwi.global.i18n.translate('client_views_serverselect_channelkey').fetch(),
+ require_key: _kiwi.global.i18n.translate('client_views_serverselect_channelkey_required').fetch(),
+ key: _kiwi.global.i18n.translate('client_views_serverselect_key').fetch(),
+ start: _kiwi.global.i18n.translate('client_views_serverselect_connection_start').fetch(),
+ server_network: _kiwi.global.i18n.translate('client_views_serverselect_server_and_network').fetch(),
+ server: _kiwi.global.i18n.translate('client_views_serverselect_server').fetch(),
+ port: _kiwi.global.i18n.translate('client_views_serverselect_port').fetch(),
+ powered_by: _kiwi.global.i18n.translate('client_views_serverselect_poweredby').fetch()
+ };
+
+ this.$el = $(_.template($('#tmpl_server_select').html().trim(), text));
+
+ // Remove the 'more' link if the server has disabled server changing
+ if (_kiwi.app.server_settings && _kiwi.app.server_settings.connection) {
+ if (!_kiwi.app.server_settings.connection.allow_change) {
+ this.$el.find('.show_more').remove();
+ this.$el.addClass('single_server');
+ }
+ }
+
+ // Are currently showing all the controlls or just a nick_change box?
+ this.state = 'all';
+
+ this.more_shown = false;
+
+ this.model.bind('new_network', this.newNetwork, this);
+
+ this.gateway = _kiwi.global.components.Network();
+ this.gateway.on('connect', this.networkConnected, this);
+ this.gateway.on('connecting', this.networkConnecting, this);
+ this.gateway.on('disconnect', this.networkDisconnected, this);
+ this.gateway.on('irc_error', this.onIrcError, this);
+ },
+
+ dispose: function() {
+ this.model.off('new_network', this.newNetwork, this);
+ this.gateway.off();
+
+ this.remove();
+ },
+
+ submitForm: function (event) {
+ event.preventDefault();
+
+ // Make sure a nick is chosen
+ if (!$('input.nick', this.$el).val().trim()) {
+ this.setStatus(_kiwi.global.i18n.translate('client_views_serverselect_nickname_error_empty').fetch());
+ $('input.nick', this.$el).select();
+ return;
+ }
+
+ if (this.state === 'nick_change') {
+ this.submitNickChange(event);
+ } else {
+ this.submitLogin(event);
+ }
+
+ $('button', this.$el).attr('disabled', 1);
+ return;
+ },
+
+ submitLogin: function (event) {
+ // If submitting is disabled, don't do anything
+ if ($('button', this.$el).attr('disabled')) return;
+
+ var values = {
+ nick: $('input.nick', this.$el).val(),
+ server: $('input.server', this.$el).val(),
+ port: $('input.port', this.$el).val(),
+ ssl: $('input.ssl', this.$el).prop('checked'),
+ password: $('input.password', this.$el).val(),
+ channel: $('input.channel', this.$el).val(),
+ channel_key: $('input.channel_key', this.$el).val(),
+ options: this.server_options
+ };
+
+ this.trigger('server_connect', values);
+ },
+
+ submitNickChange: function (event) {
+ _kiwi.gateway.changeNick(null, $('input.nick', this.$el).val());
+ this.networkConnecting();
+ },
+
+ showPass: function (event) {
+ if (this.$el.find('tr.have_pass input').is(':checked')) {
+ this.$el.find('tr.pass').show().find('input').focus();
+ } else {
+ this.$el.find('tr.pass').hide().find('input').val('');
+ }
+ },
+
+ channelKeyIconClick: function (event) {
+ this.$el.find('tr.have_key input').click();
+ },
+
+ showKey: function (event) {
+ if (this.$el.find('tr.have_key input').is(':checked')) {
+ this.$el.find('tr.key').show().find('input').focus();
+ } else {
+ this.$el.find('tr.key').hide().find('input').val('');
+ }
+ },
+
+ showMore: function (event) {
+ if (!this.more_shown) {
+ $('.more', this.$el).slideDown('fast');
+ $('.show_more', this.$el)
+ .children('.fa-caret-down')
+ .removeClass('fa-caret-down')
+ .addClass('fa-caret-up');
+ $('input.server', this.$el).select();
+ this.more_shown = true;
+ } else {
+ $('.more', this.$el).slideUp('fast');
+ $('.show_more', this.$el)
+ .children('.fs-caret-up')
+ .removeClass('fa-caret-up')
+ .addClass('fa-caret-down');
+ $('input.nick', this.$el).select();
+ this.more_shown = false;
+ }
+ },
+
+ populateFields: function (defaults) {
+ var nick, server, port, channel, channel_key, ssl, password;
+
+ defaults = defaults || {};
+
+ nick = defaults.nick || '';
+ server = defaults.server || '';
+ port = defaults.port || 6667;
+ ssl = defaults.ssl || 0;
+ password = defaults.password || '';
+ channel = defaults.channel || '';
+ channel_key = defaults.channel_key || '';
+
+ $('input.nick', this.$el).val(nick);
+ $('input.server', this.$el).val(server);
+ $('input.port', this.$el).val(port);
+ $('input.ssl', this.$el).prop('checked', ssl);
+ $('input#server_select_show_pass', this.$el).prop('checked', !(!password));
+ $('input.password', this.$el).val(password);
+ if (!(!password)) {
+ $('tr.pass', this.$el).show();
+ }
+ $('input.channel', this.$el).val(channel);
+ $('input#server_select_show_channel_key', this.$el).prop('checked', !(!channel_key));
+ $('input.channel_key', this.$el).val(channel_key);
+ if (!(!channel_key)) {
+ $('tr.key', this.$el).show();
+ }
+
+ // Temporary values
+ this.server_options = {};
+
+ if (defaults.encoding)
+ this.server_options.encoding = defaults.encoding;
+ },
+
+ hide: function () {
+ this.$el.slideUp();
+ },
+
+ show: function (new_state) {
+ new_state = new_state || 'all';
+
+ this.$el.show();
+
+ if (new_state === 'all') {
+ $('.show_more', this.$el).show();
+
+ } else if (new_state === 'more') {
+ $('.more', this.$el).slideDown('fast');
+
+ } else if (new_state === 'nick_change') {
+ $('.more', this.$el).hide();
+ $('.show_more', this.$el).hide();
+ $('input.nick', this.$el).select();
+
+ } else if (new_state === 'enter_password') {
+ $('.more', this.$el).hide();
+ $('.show_more', this.$el).hide();
+ $('input.password', this.$el).select();
+ }
+
+ this.state = new_state;
+ },
+
+ infoBoxShow: function() {
+ var $side_panel = this.$el.find('.side_panel');
+
+ // Some theme may hide the info panel so check before we
+ // resize ourselves
+ if (!$side_panel.is(':visible'))
+ return;
+
+ this.$el.animate({
+ width: parseInt($side_panel.css('left'), 10) + $side_panel.find('.content:first').outerWidth()
+ });
+ },
+
+ infoBoxHide: function() {
+ var $side_panel = this.$el.find('.side_panel');
+ this.$el.animate({
+ width: parseInt($side_panel.css('left'), 10)
+ });
+ },
+
+ infoBoxSet: function($info_view) {
+ this.$el.find('.side_panel .content')
+ .empty()
+ .append($info_view);
+ },
+
+ setStatus: function (text, class_name) {
+ $('.status', this.$el)
+ .text(text)
+ .attr('class', 'status')
+ .addClass(class_name||'')
+ .show();
+ },
+ clearStatus: function () {
+ $('.status', this.$el).hide();
+ },
+
+ reset: function() {
+ this.populateFields();
+ this.clearStatus();
+
+ this.$('button').attr('disabled', null);
+ },
+
+ newNetwork: function(network) {
+ // Keep a reference to this network so we can interact with it
+ this.model.current_connecting_network = network;
+ },
+
+ networkConnected: function (event) {
+ this.model.trigger('connected', _kiwi.app.connections.getByConnectionId(event.server));
+ this.model.current_connecting_network = null;
+ },
+
+ networkDisconnected: function () {
+ this.model.current_connecting_network = null;
+ this.state = 'all';
+ },
+
+ networkConnecting: function (event) {
+ this.model.trigger('connecting');
+ this.setStatus(_kiwi.global.i18n.translate('client_views_serverselect_connection_trying').fetch(), 'ok');
+
+ this.$('.status').append('<a class="show_server"><i class="fa fa-info-circle"></i></a>');
+ },
+
+ showServer: function() {
+ // If we don't have a current connection in the making then we have nothing to show
+ if (!this.model.current_connecting_network)
+ return;
+
+ _kiwi.app.view.barsShow();
+ this.model.current_connecting_network.panels.server.view.show();
+ },
+
+ onIrcError: function (data) {
+ $('button', this.$el).attr('disabled', null);
+
+ switch(data.error) {
+ case 'nickname_in_use':
+ this.setStatus(_kiwi.global.i18n.translate('client_views_serverselect_nickname_error_alreadyinuse').fetch());
+ this.show('nick_change');
+ this.$el.find('.nick').select();
+ break;
+ case 'erroneus_nickname':
+ if (data.reason) {
+ this.setStatus(data.reason);
+ } else {
+ this.setStatus(_kiwi.global.i18n.translate('client_views_serverselect_nickname_invalid').fetch());
+ }
+ this.show('nick_change');
+ this.$el.find('.nick').select();
+ break;
+ case 'password_mismatch':
+ this.setStatus(_kiwi.global.i18n.translate('client_views_serverselect_password_incorrect').fetch());
+ this.show('enter_password');
+ this.$el.find('.password').select();
+ break;
+ default:
+ this.showError(data.reason || '');
+ break;
+ }
+ },
+
+ showError: function (error_reason) {
+ var err_text = _kiwi.global.i18n.translate('client_views_serverselect_connection_error').fetch();
+
+ if (error_reason) {
+ switch (error_reason) {
+ case 'ENOTFOUND':
+ err_text = _kiwi.global.i18n.translate('client_views_serverselect_server_notfound').fetch();
+ break;
+
+ case 'ECONNREFUSED':
+ err_text += ' (' + _kiwi.global.i18n.translate('client_views_serverselect_connection_refused').fetch() + ')';
+ break;
+
+ default:
+ err_text += ' (' + error_reason + ')';
+ }
+ }
+
+ this.setStatus(err_text, 'error');
+ $('button', this.$el).attr('disabled', null);
+ this.show();
+ }
+});
+
+
+_kiwi.view.StatusMessage = Backbone.View.extend({
+ initialize: function () {
+ this.$el.hide();
+
+ // Timer for hiding the message after X seconds
+ this.tmr = null;
+ },
+
+ text: function (text, opt) {
+ // Defaults
+ opt = opt || {};
+ opt.type = opt.type || '';
+ opt.timeout = opt.timeout || 5000;
+
+ this.$el.text(text).addClass(opt.type);
+ this.$el.slideDown($.proxy(_kiwi.app.view.doLayout, _kiwi.app.view));
+
+ if (opt.timeout) this.doTimeout(opt.timeout);
+ },
+
+ html: function (html, opt) {
+ // Defaults
+ opt = opt || {};
+ opt.type = opt.type || '';
+ opt.timeout = opt.timeout || 5000;
+
+ this.$el.html(html).addClass(opt.type);
+ this.$el.slideDown($.proxy(_kiwi.app.view.doLayout, _kiwi.app.view));
+
+ if (opt.timeout) this.doTimeout(opt.timeout);
+ },
+
+ hide: function () {
+ this.$el.slideUp($.proxy(_kiwi.app.view.doLayout, _kiwi.app.view));
+ },
+
+ doTimeout: function (length) {
+ if (this.tmr) clearTimeout(this.tmr);
+ var that = this;
+ this.tmr = setTimeout(function () { that.hide(); }, length);
+ }
+});
+
+
+// Model for this = _kiwi.model.PanelList
+_kiwi.view.Tabs = Backbone.View.extend({
+ tagName: 'ul',
+ className: 'panellist',
+
+ events: {
+ 'click li': 'tabClick',
+ 'click li .part': 'partClick'
+ },
+
+ initialize: function () {
+ this.model.on("add", this.panelAdded, this);
+ this.model.on("remove", this.panelRemoved, this);
+ this.model.on("reset", this.render, this);
+
+ this.model.on('active', this.panelActive, this);
+
+ // Network tabs start with a server, so determine what we are now
+ this.is_network = false;
+
+ if (this.model.network) {
+ this.is_network = true;
+
+ this.model.network.on('change:name', function (network, new_val) {
+ $('span', this.model.server.tab).text(new_val);
+ }, this);
+
+ this.model.network.on('change:connection_id', function (network, new_val) {
+ this.model.forEach(function(panel) {
+ panel.tab.data('connection_id', new_val);
+ });
+ }, this);
+ }
+ },
+
+ render: function () {
+ var that = this;
+
+ this.$el.empty();
+
+ if (this.is_network) {
+ // Add the server tab first
+ this.model.server.tab
+ .data('panel', this.model.server)
+ .data('connection_id', this.model.network.get('connection_id'))
+ .appendTo(this.$el);
+ }
+
+ // Go through each panel adding its tab
+ this.model.forEach(function (panel) {
+ // If this is the server panel, ignore as it's already added
+ if (this.is_network && panel == that.model.server)
+ return;
+
+ panel.tab.data('panel', panel);
+
+ if (this.is_network)
+ panel.tab.data('connection_id', this.model.network.get('connection_id'));
+
+ panel.tab.appendTo(that.$el);
+ });
+
+ _kiwi.app.view.doLayout();
+ },
+
+ updateTabTitle: function (panel, new_title) {
+ $('span', panel.tab).text(new_title);
+ },
+
+ panelAdded: function (panel) {
+ // Add a tab to the panel
+ panel.tab = $('<li><span></span><div class="activity"></div></li>');
+ panel.tab.find('span').text(panel.get('title') || panel.get('name'));
+
+ if (panel.isServer()) {
+ panel.tab.addClass('server');
+ panel.tab.addClass('fa');
+ panel.tab.addClass('fa-nonexistant');
+ }
+
+ panel.tab.data('panel', panel);
+
+ if (this.is_network)
+ panel.tab.data('connection_id', this.model.network.get('connection_id'));
+
+ this.sortTabs();
+
+ panel.bind('change:title', this.updateTabTitle);
+ panel.bind('change:name', this.updateTabTitle);
+
+ _kiwi.app.view.doLayout();
+ },
+ panelRemoved: function (panel) {
+ var connection = _kiwi.app.connections.active_connection;
+
+ panel.tab.remove();
+ delete panel.tab;
+
+ _kiwi.app.panels.trigger('remove', panel);
+
+ _kiwi.app.view.doLayout();
+ },
+
+ panelActive: function (panel, previously_active_panel) {
+ // Remove any existing tabs or part images
+ _kiwi.app.view.$el.find('.panellist .part').remove();
+ _kiwi.app.view.$el.find('.panellist .active').removeClass('active');
+
+ panel.tab.addClass('active');
+
+ panel.tab.append('<span class="part fa fa-nonexistant"></span>');
+ },
+
+ tabClick: function (e) {
+ var tab = $(e.currentTarget);
+
+ var panel = tab.data('panel');
+ if (!panel) {
+ // A panel wasn't found for this tab... wadda fuck
+ return;
+ }
+
+ panel.view.show();
+ },
+
+ partClick: function (e) {
+ var tab = $(e.currentTarget).parent();
+ var panel = tab.data('panel');
+
+ if (!panel) return;
+
+ // If the nicklist is empty, we haven't joined the channel as yet
+ // If we part a server, then we need to disconnect from server, close channel tabs,
+ // close server tab, then bring client back to homepage
+ if (panel.isChannel() && panel.get('members').models.length > 0) {
+ this.model.network.gateway.part(panel.get('name'));
+
+ } else if(panel.isServer()) {
+ if (!this.model.network.get('connected') || confirm(translateText('disconnect_from_server'))) {
+ this.model.network.gateway.quit("Leaving");
+ _kiwi.app.connections.remove(this.model.network);
+ _kiwi.app.startup_applet.view.show();
+ }
+
+ } else {
+ panel.close();
+ }
+ },
+
+ sortTabs: function() {
+ var that = this,
+ panels = [];
+
+ this.model.forEach(function (panel) {
+ // Ignore the server tab, so all others get added after it
+ if (that.is_network && panel == that.model.server)
+ return;
+
+ panels.push([panel.get('title') || panel.get('name'), panel]);
+ });
+
+ // Sort by the panel name..
+ panels.sort(function(a, b) {
+ if (a[0].toLowerCase() > b[0].toLowerCase()) {
+ return 1;
+ } else if (a[0].toLowerCase() < b[0].toLowerCase()) {
+ return -1;
+ } else {
+ return 0;
+ }
+ });
+
+ // And add them all back in order.
+ _.each(panels, function(panel) {
+ panel[1].tab.appendTo(that.$el);
+ });
+ }
+});
+
+
+_kiwi.view.TopicBar = Backbone.View.extend({
+ events: {
+ 'keydown div': 'process'
+ },
+
+ initialize: function () {
+ _kiwi.app.panels.bind('active', function (active_panel) {
+ // If it's a channel topic, update and make editable
+ if (active_panel.isChannel()) {
+ this.setCurrentTopicFromChannel(active_panel);
+ this.$el.find('div').attr('contentEditable', true);
+
+ } else {
+ // Not a channel topic.. clear and make uneditable
+ this.$el.find('div').attr('contentEditable', false)
+ .text('');
+ }
+ }, this);
+ },
+
+ process: function (ev) {
+ var inp = $(ev.currentTarget),
+ inp_val = inp.text();
+
+ // Only allow topic editing if this is a channel panel
+ if (!_kiwi.app.panels().active.isChannel()) {
+ return false;
+ }
+
+ // If hit return key, update the current topic
+ if (ev.keyCode === 13) {
+ _kiwi.app.connections.active_connection.gateway.topic(_kiwi.app.panels().active.get('name'), inp_val);
+ return false;
+ }
+ },
+
+ setCurrentTopic: function (new_topic) {
+ new_topic = new_topic || '';
+
+ // We only want a plain text version
+ $('div', this.$el).html(formatIRCMsg(_.escape(new_topic)));
+ },
+
+ setCurrentTopicFromChannel: function(channel) {
+ var set_by = channel.get('topic_set_by'),
+ set_by_text = '';
+
+ this.setCurrentTopic(channel.get("topic"));
+
+ if (set_by) {
+ set_by_text += translateText('client_models_network_topic', [set_by.nick, _kiwi.utils.formatDate(set_by.when)]);
+ this.$el.attr('title', set_by_text);
+ } else {
+ this.$el.attr('title', '');
+ }
+ }
+});
+
+
+_kiwi.view.UserBox = Backbone.View.extend({
+ events: {
+ 'click .query': 'queryClick',
+ 'click .info': 'infoClick',
+ 'change .ignore': 'ignoreChange',
+ 'click .ignore': 'ignoreClick',
+ 'click .op': 'opClick',
+ 'click .deop': 'deopClick',
+ 'click .voice': 'voiceClick',
+ 'click .devoice': 'devoiceClick',
+ 'click .kick': 'kickClick',
+ 'click .ban': 'banClick'
+ },
+
+ initialize: function () {
+ var text = {
+ op: _kiwi.global.i18n.translate('client_views_userbox_op').fetch(),
+ de_op: _kiwi.global.i18n.translate('client_views_userbox_deop').fetch(),
+ voice: _kiwi.global.i18n.translate('client_views_userbox_voice').fetch(),
+ de_voice: _kiwi.global.i18n.translate('client_views_userbox_devoice').fetch(),
+ kick: _kiwi.global.i18n.translate('client_views_userbox_kick').fetch(),
+ ban: _kiwi.global.i18n.translate('client_views_userbox_ban').fetch(),
+ message: _kiwi.global.i18n.translate('client_views_userbox_query').fetch(),
+ info: _kiwi.global.i18n.translate('client_views_userbox_whois').fetch(),
+ ignore: _kiwi.global.i18n.translate('client_views_userbox_ignore').fetch()
+ };
+ this.$el = $(_.template($('#tmpl_userbox').html().trim(), text));
+ },
+
+ setTargets: function (user, channel) {
+ this.user = user;
+ this.channel = channel;
+
+ var is_ignored = _kiwi.app.connections.active_connection.isNickIgnored(this.user.get('nick'));
+ this.$('.ignore input').attr('checked', is_ignored ? 'checked' : false);
+ },
+
+ displayOpItems: function(display_items) {
+ if (display_items) {
+ this.$el.find('.if_op').css('display', 'block');
+ } else {
+ this.$el.find('.if_op').css('display', 'none');
+ }
+ },
+
+ queryClick: function (event) {
+ var nick = this.user.get('nick');
+ _kiwi.app.connections.active_connection.createQuery(nick);
+ },
+
+ infoClick: function (event) {
+ _kiwi.app.controlbox.processInput('/whois ' + this.user.get('nick'));
+ },
+
+ ignoreClick: function (event) {
+ // Stop the menubox from closing since it will not update the checkbox otherwise
+ event.stopPropagation();
+ },
+
+ ignoreChange: function (event) {
+ if ($(event.currentTarget).find('input').is(':checked')) {
+ _kiwi.app.controlbox.processInput('/ignore ' + this.user.get('nick'));
+ } else {
+ _kiwi.app.controlbox.processInput('/unignore ' + this.user.get('nick'));
+ }
+ },
+
+ opClick: function (event) {
+ _kiwi.app.controlbox.processInput('/mode ' + this.channel.get('name') + ' +o ' + this.user.get('nick'));
+ },
+
+ deopClick: function (event) {
+ _kiwi.app.controlbox.processInput('/mode ' + this.channel.get('name') + ' -o ' + this.user.get('nick'));
+ },
+
+ voiceClick: function (event) {
+ _kiwi.app.controlbox.processInput('/mode ' + this.channel.get('name') + ' +v ' + this.user.get('nick'));
+ },
+
+ devoiceClick: function (event) {
+ _kiwi.app.controlbox.processInput('/mode ' + this.channel.get('name') + ' -v ' + this.user.get('nick'));
+ },
+
+ kickClick: function (event) {
+ // TODO: Enable the use of a custom kick message
+ _kiwi.app.controlbox.processInput('/kick ' + this.user.get('nick') + ' Bye!');
+ },
+
+ banClick: function (event) {
+ // TODO: Set ban on host, not just on nick
+ _kiwi.app.controlbox.processInput('/mode ' + this.channel.get('name') + ' +b ' + this.user.get('nick') + '!*');
+ }
+});
+
+
+_kiwi.view.ChannelTools = Backbone.View.extend({
+ events: {
+ 'click .channel_info': 'infoClick',
+ 'click .channel_part': 'partClick'
+ },
+
+ initialize: function () {},
+
+ infoClick: function (event) {
+ new _kiwi.model.ChannelInfo({channel: _kiwi.app.panels().active});
+ },
+
+ partClick: function (event) {
+ _kiwi.app.connections.active_connection.gateway.part(_kiwi.app.panels().active.get('name'));
+ }
+});
+
+
+// var f = new _kiwi.model.ChannelInfo({channel: _kiwi.app.panels().active});
+
+_kiwi.view.ChannelInfo = Backbone.View.extend({
+ events: {
+ 'click .toggle_banlist': 'toggleBanList',
+ 'change .channel-mode': 'onModeChange',
+ 'click .remove-ban': 'onRemoveBanClick'
+ },
+
+
+ initialize: function () {
+ var that = this,
+ network,
+ channel = this.model.get('channel'),
+ text;
+
+ text = {
+ moderated_chat: translateText('client_views_channelinfo_moderated'),
+ invite_only: translateText('client_views_channelinfo_inviteonly'),
+ ops_change_topic: translateText('client_views_channelinfo_opschangechannel'),
+ external_messages: translateText('client_views_channelinfo_externalmessages'),
+ toggle_banlist: translateText('client_views_channelinfo_togglebanlist'),
+ channel_name: channel.get('name')
+ };
+
+ this.$el = $(_.template($('#tmpl_channel_info').html().trim(), text));
+
+ // Create the menu box this view will sit inside
+ this.menu = new _kiwi.view.MenuBox(channel.get('name'));
+ this.menu.addItem('channel_info', this.$el);
+ this.menu.$el.appendTo(channel.view.$container);
+ this.menu.show();
+
+ this.menu.$el.offset({top: _kiwi.app.view.$el.find('.panels').offset().top});
+
+ // Menu box will call this destroy on closing
+ this.$el.dispose = _.bind(this.dispose, this);
+
+ // Display the info we have, then listen for further changes
+ this.updateInfo(channel);
+ channel.on('change:info_modes change:info_url change:banlist', this.updateInfo, this);
+
+ // Request the latest info for ths channel from the network
+ channel.get('network').gateway.channelInfo(channel.get('name'));
+ },
+
+
+ render: function () {
+ },
+
+
+ onModeChange: function(event) {
+ var $this = $(event.currentTarget),
+ channel = this.model.get('channel'),
+ mode = $this.data('mode'),
+ mode_string = '';
+
+ if ($this.attr('type') == 'checkbox') {
+ mode_string = $this.is(':checked') ? '+' : '-';
+ mode_string += mode;
+ channel.setMode(mode_string);
+
+ return;
+ }
+
+ if ($this.attr('type') == 'text') {
+ mode_string = $this.val() ?
+ '+' + mode + ' ' + $this.val() :
+ '-' + mode;
+
+ channel.setMode(mode_string);
+
+ return;
+ }
+ },
+
+
+ onRemoveBanClick: function (event) {
+ event.preventDefault();
+ event.stopPropagation();
+
+ var $this = $(event.currentTarget),
+ $tr = $this.parents('tr:first'),
+ ban = $tr.data('ban');
+
+ if (!ban)
+ return;
+
+ var channel = this.model.get('channel');
+ channel.setMode('-b ' + ban.banned);
+
+ $tr.remove();
+ },
+
+
+ updateInfo: function (channel, new_val) {
+ var that = this,
+ title, modes, url, banlist;
+
+ modes = channel.get('info_modes');
+ if (modes) {
+ _.each(modes, function(mode, idx) {
+ mode.mode = mode.mode.toLowerCase();
+
+ if (mode.mode == '+k') {
+ that.$el.find('[name="channel_key"]').val(mode.param);
+ } else if (mode.mode == '+m') {
+ that.$el.find('[name="channel_mute"]').attr('checked', 'checked');
+ } else if (mode.mode == '+i') {
+ that.$el.find('[name="channel_invite"]').attr('checked', 'checked');
+ } else if (mode.mode == '+n') {
+ that.$el.find('[name="channel_external_messages"]').attr('checked', 'checked');
+ } else if (mode.mode == '+t') {
+ that.$el.find('[name="channel_topic"]').attr('checked', 'checked');
+ }
+ });
+ }
+
+ url = channel.get('info_url');
+ if (url) {
+ this.$el.find('.channel_url')
+ .text(url)
+ .attr('href', url);
+
+ this.$el.find('.channel_url').slideDown();
+ }
+
+ banlist = channel.get('banlist');
+ if (banlist && banlist.length) {
+ var $table = this.$el.find('.channel-banlist table tbody');
+
+ this.$el.find('.banlist-status').text('');
+
+ $table.empty();
+ _.each(banlist, function(ban) {
+ var $tr = $('<tr></tr>').data('ban', ban);
+
+ $('<td></td>').text(ban.banned).appendTo($tr);
+ $('<td></td>').text(ban.banned_by.split(/[!@]/)[0]).appendTo($tr);
+ $('<td></td>').text(_kiwi.utils.formatDate(new Date(parseInt(ban.banned_at, 10) * 1000))).appendTo($tr);
+ $('<td><i class="fa fa-rtimes remove-ban"></i></td>').appendTo($tr);
+
+ $table.append($tr);
+ });
+
+ this.$el.find('.channel-banlist table').slideDown();
+ } else {
+ this.$el.find('.banlist-status').text('Banlist empty');
+ this.$el.find('.channel-banlist table').hide();
+ }
+ },
+
+ toggleBanList: function (event) {
+ event.preventDefault();
+ this.$el.find('.channel-banlist table').toggle();
+
+ if(!this.$el.find('.channel-banlist table').is(':visible'))
+ return;
+
+ var channel = this.model.get('channel'),
+ network = channel.get('network');
+
+ network.gateway.raw('MODE ' + channel.get('name') + ' +b');
+ },
+
+ dispose: function () {
+ this.model.get('channel').off('change:info_modes change:info_url change:banlist', this.updateInfo, this);
+
+ this.$el.remove();
+ }
+});
+
+
+
+_kiwi.view.RightBar = Backbone.View.extend({
+ events: {
+ 'click .right-bar-toggle': 'onClickToggle',
+ 'click .right-bar-toggle-inner': 'onClickToggle'
+ },
+
+ initialize: function() {
+ this.keep_hidden = false;
+ this.hidden = this.$el.hasClass('disabled');
+
+ this.updateIcon();
+ },
+
+
+ hide: function() {
+ this.hidden = true;
+ this.$el.addClass('disabled');
+
+ this.updateIcon();
+ },
+
+
+ show: function() {
+ this.hidden = false;
+
+ if (!this.keep_hidden)
+ this.$el.removeClass('disabled');
+
+ this.updateIcon();
+ },
+
+
+ // Toggle if the rightbar should be shown or not
+ toggle: function(keep_hidden) {
+ // Hacky, but we need to ignore the toggle() call from doLayout() as we are overriding it
+ if (this.ignore_layout)
+ return true;
+
+ if (typeof keep_hidden === 'undefined') {
+ this.keep_hidden = !this.keep_hidden;
+ } else {
+ this.keep_hidden = keep_hidden;
+ }
+
+ if (this.keep_hidden || this.hidden) {
+ this.$el.addClass('disabled');
+ } else {
+ this.$el.removeClass('disabled');
+ }
+
+ this.updateIcon();
+ },
+
+
+ updateIcon: function() {
+ var $toggle = this.$('.right-bar-toggle'),
+ $icon = $toggle.find('i');
+
+ if (!this.hidden && this.keep_hidden) {
+ $toggle.show();
+ } else {
+ $toggle.hide();
+ }
+
+ if (this.keep_hidden) {
+ $icon.removeClass('fa fa-angle-double-right').addClass('fa fa-users');
+ } else {
+ $icon.removeClass('fa fa-users').addClass('fa fa-angle-double-right');
+ }
+ },
+
+
+ onClickToggle: function(event) {
+ this.toggle();
+
+ // Hacky, but we need to ignore the toggle() call from doLayout() as we are overriding it
+ this.ignore_layout = true;
+ _kiwi.app.view.doLayout();
+
+ // No longer ignoring the toggle() call from doLayout()
+ delete this.ignore_layout;
+ }
+});
+
+
+_kiwi.view.Notification = Backbone.View.extend({
+ className: 'notification',
+
+ events: {
+ 'click .close': 'close'
+ },
+
+ initialize: function(title, content) {
+ this.title = title;
+ this.content = content;
+ },
+
+ render: function() {
+ this.$el.html($('#tmpl_notifications').html());
+ this.$('h6').text(this.title);
+
+ // HTML string or jquery object
+ if (typeof this.content === 'string') {
+ this.$('.content').html(this.content);
+ } else if (typeof this.content === 'object') {
+ this.$('.content').empty().append(this.content);
+ }
+
+ return this;
+ },
+
+ show: function() {
+ var that = this;
+
+ this.render().$el.appendTo(_kiwi.app.view.$el);
+
+ // The element won't have any CSS transitions applied
+ // until after a tick + paint.
+ _.defer(function() {
+ that.$el.addClass('show');
+ });
+ },
+
+ close: function() {
+ this.remove();
+ }
+});
+
+
+(function() {
+
+ function ClientUiCommands(app, controlbox) {
+ this.app = app;
+ this.controlbox = controlbox;
+
+ this.addDefaultAliases();
+ this.bindCommand(fn_to_bind);
+ }
+
+ _kiwi.misc.ClientUiCommands = ClientUiCommands;
+
+
+ // Add the default user command aliases
+ ClientUiCommands.prototype.addDefaultAliases = function() {
+ $.extend(this.controlbox.preprocessor.aliases, {
+ // General aliases
+ '/p': '/part $1+',
+ '/me': '/action $1+',
+ '/j': '/join $1+',
+ '/q': '/query $1+',
+ '/w': '/whois $1+',
+ '/raw': '/quote $1+',
+ '/connect': '/server $1+',
+
+ // Op related aliases
+ '/op': '/quote mode $channel +o $1+',
+ '/deop': '/quote mode $channel -o $1+',
+ '/hop': '/quote mode $channel +h $1+',
+ '/dehop': '/quote mode $channel -h $1+',
+ '/voice': '/quote mode $channel +v $1+',
+ '/devoice': '/quote mode $channel -v $1+',
+ '/k': '/kick $channel $1+',
+ '/ban': '/quote mode $channel +b $1+',
+ '/unban': '/quote mode $channel -b $1+',
+
+ // Misc aliases
+ '/slap': '/me slaps $1 around a bit with a large trout',
+ '/tick': '/msg $channel ✔'
+ });
+ };
+
+
+ /**
+ * Add a new command action
+ * @var command Object {'command:the_command': fn}
+ */
+ ClientUiCommands.prototype.bindCommand = function(command) {
+ var that = this;
+
+ _.each(command, function(fn, event_name) {
+ that.controlbox.on(event_name, _.bind(fn, that));
+ });
+ };
+
+
+
+
+ /**
+ * Default functions to bind to controlbox events
+ **/
+
+ var fn_to_bind = {
+ 'unknown_command': unknownCommand,
+ 'command': allCommands,
+ 'command:msg': msgCommand,
+ 'command:action': actionCommand,
+ 'command:join': joinCommand,
+ 'command:part': partCommand,
+ 'command:cycle': cycleCommand,
+ 'command:nick': nickCommand,
+ 'command:query': queryCommand,
+ 'command:invite': inviteCommand,
+ 'command:topic': topicCommand,
+ 'command:notice': noticeCommand,
+ 'command:quote': quoteCommand,
+ 'command:kick': kickCommand,
+ 'command:clear': clearCommand,
+ 'command:ctcp': ctcpCommand,
+ 'command:quit': quitCommand,
+ 'command:server': serverCommand,
+ 'command:whois': whoisCommand,
+ 'command:whowas': whowasCommand,
+ 'command:away': awayCommand,
+ 'command:encoding': encodingCommand,
+ 'command:channel': channelCommand,
+ 'command:applet': appletCommand,
+ 'command:settings': settingsCommand,
+ 'command:script': scriptCommand
+ };
+
+
+ fn_to_bind['command:css'] = function (ev) {
+ var queryString = '?reload=' + new Date().getTime();
+ $('link[rel="stylesheet"]').each(function () {
+ this.href = this.href.replace(/\?.*|$/, queryString);
+ });
+ };
+
+
+ fn_to_bind['command:js'] = function (ev) {
+ if (!ev.params[0]) return;
+ $script(ev.params[0] + '?' + (new Date().getTime()));
+ };
+
+
+ fn_to_bind['command:set'] = function (ev) {
+ if (!ev.params[0]) return;
+
+ var setting = ev.params[0],
+ value;
+
+ // Do we have a second param to set a value?
+ if (ev.params[1]) {
+ ev.params.shift();
+
+ value = ev.params.join(' ');
+
+ // If we're setting a true boolean value..
+ if (value === 'true')
+ value = true;
+
+ // If we're setting a false boolean value..
+ if (value === 'false')
+ value = false;
+
+ // If we're setting a number..
+ if (parseInt(value, 10).toString() === value)
+ value = parseInt(value, 10);
+
+ _kiwi.global.settings.set(setting, value);
+ }
+
+ // Read the value to the user
+ this.app.panels().active.addMsg('', styleText('set_setting', {text: setting + ' = ' + _kiwi.global.settings.get(setting).toString()}));
+ };
+
+
+ fn_to_bind['command:save'] = function (ev) {
+ _kiwi.global.settings.save();
+ this.app.panels().active.addMsg('', styleText('settings_saved', {text: translateText('client_models_application_settings_saved')}));
+ };
+
+
+ fn_to_bind['command:alias'] = function (ev) {
+ var that = this,
+ name, rule;
+
+ // No parameters passed so list them
+ if (!ev.params[1]) {
+ $.each(this.controlbox.preprocessor.aliases, function (name, rule) {
+ that.app.panels().server.addMsg(' ', styleText('list_aliases', {text: name + ' => ' + rule}));
+ });
+ return;
+ }
+
+ // Deleting an alias?
+ if (ev.params[0] === 'del' || ev.params[0] === 'delete') {
+ name = ev.params[1];
+ if (name[0] !== '/') name = '/' + name;
+ delete this.controlbox.preprocessor.aliases[name];
+ return;
+ }
+
+ // Add the alias
+ name = ev.params[0];
+ ev.params.shift();
+ rule = ev.params.join(' ');
+
+ // Make sure the name starts with a slash
+ if (name[0] !== '/') name = '/' + name;
+
+ // Now actually add the alias
+ this.controlbox.preprocessor.aliases[name] = rule;
+ };
+
+
+ fn_to_bind['command:ignore'] = function (ev) {
+ var that = this,
+ list = this.app.connections.active_connection.get('ignore_list');
+
+ // No parameters passed so list them
+ if (!ev.params[0]) {
+ if (list.length > 0) {
+ this.app.panels().active.addMsg(' ', styleText('ignore_title', {text: translateText('client_models_application_ignore_title')}));
+ $.each(list, function (idx, ignored_pattern) {
+ that.app.panels().active.addMsg(' ', styleText('ignored_pattern', {text: ignored_pattern}));
+ });
+ } else {
+ this.app.panels().active.addMsg(' ', styleText('ignore_none', {text: translateText('client_models_application_ignore_none')}));
+ }
+ return;
+ }
+
+ // We have a parameter, so add it
+ list.push(ev.params[0]);
+ this.app.connections.active_connection.set('ignore_list', list);
+ this.app.panels().active.addMsg(' ', styleText('ignore_nick', {text: translateText('client_models_application_ignore_nick', [ev.params[0]])}));
+ };
+
+
+ fn_to_bind['command:unignore'] = function (ev) {
+ var list = this.app.connections.active_connection.get('ignore_list');
+
+ if (!ev.params[0]) {
+ this.app.panels().active.addMsg(' ', styleText('ignore_stop_notice', {text: translateText('client_models_application_ignore_stop_notice')}));
+ return;
+ }
+
+ list = _.reject(list, function(pattern) {
+ return pattern === ev.params[0];
+ });
+
+ this.app.connections.active_connection.set('ignore_list', list);
+
+ this.app.panels().active.addMsg(' ', styleText('ignore_stopped', {text: translateText('client_models_application_ignore_stopped', [ev.params[0]])}));
+ };
+
+
+
+
+ // A fallback action. Send a raw command to the server
+ function unknownCommand (ev) {
+ var raw_cmd = ev.command + ' ' + ev.params.join(' ');
+ this.app.connections.active_connection.gateway.raw(raw_cmd);
+ }
+
+
+ function allCommands (ev) {}
+
+
+ function joinCommand (ev) {
+ var panels, channel_names;
+
+ channel_names = ev.params.join(' ').split(',');
+ panels = this.app.connections.active_connection.createAndJoinChannels(channel_names);
+
+ // Show the last channel if we have one
+ if (panels.length)
+ panels[panels.length - 1].view.show();
+ }
+
+
+ function queryCommand (ev) {
+ var destination, message, panel;
+
+ destination = ev.params[0];
+ ev.params.shift();
+
+ message = ev.params.join(' ');
+
+ // Check if we have the panel already. If not, create it
+ panel = this.app.connections.active_connection.panels.getByName(destination);
+ if (!panel) {
+ panel = new _kiwi.model.Query({name: destination});
+ this.app.connections.active_connection.panels.add(panel);
+ }
+
+ if (panel) panel.view.show();
+
+ if (message) {
+ this.app.connections.active_connection.gateway.msg(panel.get('name'), message);
+ panel.addMsg(this.app.connections.active_connection.get('nick'), styleText('privmsg', {text: message}), 'privmsg');
+ }
+
+ }
+
+
+ function msgCommand (ev) {
+ var message,
+ destination = ev.params[0],
+ panel = this.app.connections.active_connection.panels.getByName(destination) || this.app.panels().server;
+
+ ev.params.shift();
+ message = ev.params.join(' ');
+
+ panel.addMsg(this.app.connections.active_connection.get('nick'), styleText('privmsg', {text: message}), 'privmsg');
+ this.app.connections.active_connection.gateway.msg(destination, message);
+ }
+
+
+ function actionCommand (ev) {
+ if (this.app.panels().active.isServer()) {
+ return;
+ }
+
+ var panel = this.app.panels().active;
+ panel.addMsg('', styleText('action', {nick: this.app.connections.active_connection.get('nick'), text: ev.params.join(' ')}), 'action');
+ this.app.connections.active_connection.gateway.action(panel.get('name'), ev.params.join(' '));
+ }
+
+
+ function partCommand (ev) {
+ var that = this,
+ chans,
+ msg;
+ if (ev.params.length === 0) {
+ this.app.connections.active_connection.gateway.part(this.app.panels().active.get('name'));
+ } else {
+ chans = ev.params[0].split(',');
+ msg = ev.params[1];
+ _.each(chans, function (channel) {
+ that.connections.active_connection.gateway.part(channel, msg);
+ });
+ }
+ }
+
+
+ function cycleCommand (ev) {
+ var that = this,
+ chan_name;
+
+ if (ev.params.length === 0) {
+ chan_name = this.app.panels().active.get('name');
+ } else {
+ chan_name = ev.params[0];
+ }
+
+ this.app.connections.active_connection.gateway.part(chan_name);
+
+ // Wait for a second to give the network time to register the part command
+ setTimeout(function() {
+ // Use createAndJoinChannels() here as it auto-creates panels instead of waiting for the network
+ that.app.connections.active_connection.createAndJoinChannels(chan_name);
+ that.app.connections.active_connection.panels.getByName(chan_name).show();
+ }, 1000);
+ }
+
+
+ function nickCommand (ev) {
+ this.app.connections.active_connection.gateway.changeNick(ev.params[0]);
+ }
+
+
+ function topicCommand (ev) {
+ var channel_name;
+
+ if (ev.params.length === 0) return;
+
+ if (this.app.connections.active_connection.isChannelName(ev.params[0])) {
+ channel_name = ev.params[0];
+ ev.params.shift();
+ } else {
+ channel_name = this.app.panels().active.get('name');
+ }
+
+ this.app.connections.active_connection.gateway.topic(channel_name, ev.params.join(' '));
+ }
+
+
+ function noticeCommand (ev) {
+ var destination;
+
+ // Make sure we have a destination and some sort of message
+ if (ev.params.length <= 1) return;
+
+ destination = ev.params[0];
+ ev.params.shift();
+
+ this.app.connections.active_connection.gateway.notice(destination, ev.params.join(' '));
+ }
+
+
+ function quoteCommand (ev) {
+ var raw = ev.params.join(' ');
+ this.app.connections.active_connection.gateway.raw(raw);
+ }
+
+
+ function kickCommand (ev) {
+ var nick, panel = this.app.panels().active;
+
+ if (!panel.isChannel()) return;
+
+ // Make sure we have a nick
+ if (ev.params.length === 0) return;
+
+ nick = ev.params[0];
+ ev.params.shift();
+
+ this.app.connections.active_connection.gateway.kick(panel.get('name'), nick, ev.params.join(' '));
+ }
+
+
+ function clearCommand (ev) {
+ // Can't clear a server or applet panel
+ if (this.app.panels().active.isServer() || this.app.panels().active.isApplet()) {
+ return;
+ }
+
+ if (this.app.panels().active.clearMessages) {
+ this.app.panels().active.clearMessages();
+ }
+ }
+
+
+ function ctcpCommand(ev) {
+ var target, type;
+
+ // Make sure we have a target and a ctcp type (eg. version, time)
+ if (ev.params.length < 2) return;
+
+ target = ev.params[0];
+ ev.params.shift();
+
+ type = ev.params[0];
+ ev.params.shift();
+
+ this.app.connections.active_connection.gateway.ctcpRequest(type, target, ev.params.join(' '));
+ }
+
+
+ function settingsCommand (ev) {
+ var settings = _kiwi.model.Applet.loadOnce('kiwi_settings');
+ settings.view.show();
+ }
+
+
+ function scriptCommand (ev) {
+ var editor = _kiwi.model.Applet.loadOnce('kiwi_script_editor');
+ editor.view.show();
+ }
+
+
+ function appletCommand (ev) {
+ if (!ev.params[0]) return;
+
+ var panel = new _kiwi.model.Applet();
+
+ if (ev.params[1]) {
+ // Url and name given
+ panel.load(ev.params[0], ev.params[1]);
+ } else {
+ // Load a pre-loaded applet
+ if (this.applets[ev.params[0]]) {
+ panel.load(new this.applets[ev.params[0]]());
+ } else {
+ this.app.panels().server.addMsg('', styleText('applet_notfound', {text: translateText('client_models_application_applet_notfound', [ev.params[0]])}));
+ return;
+ }
+ }
+
+ this.app.connections.active_connection.panels.add(panel);
+ panel.view.show();
+ }
+
+
+ function inviteCommand (ev) {
+ var nick, channel;
+
+ // A nick must be specified
+ if (!ev.params[0])
+ return;
+
+ // Can only invite into channels
+ if (!this.app.panels().active.isChannel())
+ return;
+
+ nick = ev.params[0];
+ channel = this.app.panels().active.get('name');
+
+ this.app.connections.active_connection.gateway.raw('INVITE ' + nick + ' ' + channel);
+
+ this.app.panels().active.addMsg('', styleText('channel_has_been_invited', {nick: nick, text: translateText('client_models_application_has_been_invited', [channel])}), 'action');
+ }
+
+
+ function whoisCommand (ev) {
+ var nick;
+
+ if (ev.params[0]) {
+ nick = ev.params[0];
+ } else if (this.app.panels().active.isQuery()) {
+ nick = this.app.panels().active.get('name');
+ }
+
+ if (nick)
+ this.app.connections.active_connection.gateway.raw('WHOIS ' + nick + ' ' + nick);
+ }
+
+
+ function whowasCommand (ev) {
+ var nick;
+
+ if (ev.params[0]) {
+ nick = ev.params[0];
+ } else if (this.app.panels().active.isQuery()) {
+ nick = this.app.panels().active.get('name');
+ }
+
+ if (nick)
+ this.app.connections.active_connection.gateway.raw('WHOWAS ' + nick);
+ }
+
+
+ function awayCommand (ev) {
+ this.app.connections.active_connection.gateway.raw('AWAY :' + ev.params.join(' '));
+ }
+
+
+ function encodingCommand (ev) {
+ var that = this;
+
+ if (ev.params[0]) {
+ _kiwi.gateway.setEncoding(null, ev.params[0], function (success) {
+ if (success) {
+ that.app.panels().active.addMsg('', styleText('encoding_changed', {text: translateText('client_models_application_encoding_changed', [ev.params[0]])}));
+ } else {
+ that.app.panels().active.addMsg('', styleText('encoding_invalid', {text: translateText('client_models_application_encoding_invalid', [ev.params[0]])}));
+ }
+ });
+ } else {
+ this.app.panels().active.addMsg('', styleText('client_models_application_encoding_notspecified', {text: translateText('client_models_application_encoding_notspecified')}));
+ this.app.panels().active.addMsg('', styleText('client_models_application_encoding_usage', {text: translateText('client_models_application_encoding_usage')}));
+ }
+ }
+
+
+ function channelCommand (ev) {
+ var active_panel = this.app.panels().active;
+
+ if (!active_panel.isChannel())
+ return;
+
+ new _kiwi.model.ChannelInfo({channel: this.app.panels().active});
+ }
+
+
+ function quitCommand (ev) {
+ var network = this.app.connections.active_connection;
+
+ if (!network)
+ return;
+
+ network.gateway.quit(ev.params.join(' '));
+ }
+
+
+ function serverCommand (ev) {
+ var that = this,
+ server, port, ssl, password, nick,
+ tmp;
+
+ // If no server address given, show the new connection dialog
+ if (!ev.params[0]) {
+ tmp = new _kiwi.view.MenuBox(_kiwi.global.i18n.translate('client_models_application_connection_create').fetch());
+ tmp.addItem('new_connection', new _kiwi.model.NewConnection().view.$el);
+ tmp.show();
+
+ // Center screen the dialog
+ tmp.$el.offset({
+ top: (this.app.view.$el.height() / 2) - (tmp.$el.height() / 2),
+ left: (this.app.view.$el.width() / 2) - (tmp.$el.width() / 2)
+ });
+
+ return;
+ }
+
+ // Port given in 'host:port' format and no specific port given after a space
+ if (ev.params[0].indexOf(':') > 0) {
+ tmp = ev.params[0].split(':');
+ server = tmp[0];
+ port = tmp[1];
+
+ password = ev.params[1] || undefined;
+
+ } else {
+ // Server + port given as 'host port'
+ server = ev.params[0];
+ port = ev.params[1] || 6667;
+
+ password = ev.params[2] || undefined;
+ }
+
+ // + in the port means SSL
+ if (port.toString()[0] === '+') {
+ ssl = true;
+ port = parseInt(port.substring(1), 10);
+ } else {
+ ssl = false;
+ }
+
+ // Default port if one wasn't found
+ port = port || 6667;
+
+ // Use the same nick as we currently have
+ nick = this.app.connections.active_connection.get('nick');
+
+ this.app.panels().active.addMsg('', styleText('server_connecting', {text: translateText('client_models_application_connection_connecting', [server, port.toString()])}));
+
+ _kiwi.gateway.newConnection({
+ nick: nick,
+ host: server,
+ port: port,
+ ssl: ssl,
+ password: password
+ }, function(err, new_connection) {
+ var translated_err;
+
+ if (err) {
+ translated_err = translateText('client_models_application_connection_error', [server, port.toString(), err.toString()]);
+ that.app.panels().active.addMsg('', styleText('server_connecting_error', {text: translated_err}));
+ }
+ });
+ }
+
+})();
+
+
+(function () {\r
+ var View = Backbone.View.extend({\r
+ events: {\r
+ 'change [data-setting]': 'saveSettings',\r
+ 'click [data-setting="theme"]': 'selectTheme',\r
+ 'click .register_protocol': 'registerProtocol',\r
+ 'click .enable_notifications': 'enableNotifications'\r
+ },\r
+\r
+ initialize: function (options) {\r
+ var text = {\r
+ tabs : translateText('client_applets_settings_channelview_tabs'),\r
+ list : translateText('client_applets_settings_channelview_list'),\r
+ large_amounts_of_chans: translateText('client_applets_settings_channelview_list_notice'),\r
+ join_part : translateText('client_applets_settings_notification_joinpart'),\r
+ count_all_activity : translateText('client_applets_settings_notification_count_all_activity'),\r
+ timestamps : translateText('client_applets_settings_timestamp'),\r
+ timestamp_24 : translateText('client_applets_settings_timestamp_24_hour'),\r
+ mute : translateText('client_applets_settings_notification_sound'),\r
+ emoticons : translateText('client_applets_settings_emoticons'),\r
+ scroll_history : translateText('client_applets_settings_history_length'),\r
+ languages : _kiwi.app.translations,\r
+ default_client : translateText('client_applets_settings_default_client'),\r
+ make_default : translateText('client_applets_settings_default_client_enable'),\r
+ locale_restart_needed : translateText('client_applets_settings_locale_restart_needed'),\r
+ default_note : translateText('client_applets_settings_default_client_notice', '<a href="chrome://settings/handlers">chrome://settings/handlers</a>'),\r
+ html5_notifications : translateText('client_applets_settings_html5_notifications'),\r
+ enable_notifications : translateText('client_applets_settings_enable_notifications'),\r
+ theme_thumbnails: _.map(_kiwi.app.themes, function (theme) {\r
+ return _.template($('#tmpl_theme_thumbnail').html().trim(), theme);\r
+ })\r
+ };\r
+ this.$el = $(_.template($('#tmpl_applet_settings').html().trim(), text));\r
+\r
+ if (!navigator.registerProtocolHandler) {\r
+ this.$('.protocol_handler').remove();\r
+ }\r
+\r
+ if (_kiwi.utils.notifications.allowed() !== null) {\r
+ this.$('.notification_enabler').remove();\r
+ }\r
+\r
+ // Incase any settings change while we have this open, update them\r
+ _kiwi.global.settings.on('change', this.loadSettings, this);\r
+\r
+ // Now actually show the current settings\r
+ this.loadSettings();\r
+\r
+ },\r
+\r
+ loadSettings: function () {\r
+\r
+ _.each(_kiwi.global.settings.attributes, function(value, key) {\r
+\r
+ var $el = this.$('[data-setting="' + key + '"]');\r
+\r
+ // Only deal with settings we have a UI element for\r
+ if (!$el.length)\r
+ return;\r
+\r
+ switch ($el.prop('type')) {\r
+ case 'checkbox':\r
+ $el.prop('checked', value);\r
+ break;\r
+ case 'radio':\r
+ this.$('[data-setting="' + key + '"][value="' + value + '"]').prop('checked', true);\r
+ break;\r
+ case 'text':\r
+ $el.val(value);\r
+ break;\r
+ case 'select-one':\r
+ this.$('[value="' + value + '"]').prop('selected', true);\r
+ break;\r
+ default:\r
+ this.$('[data-setting="' + key + '"][data-value="' + value + '"]').addClass('active');\r
+ break;\r
+ }\r
+ }, this);\r
+ },\r
+\r
+ saveSettings: function (event) {\r
+ var value,\r
+ settings = _kiwi.global.settings,\r
+ $setting = $(event.currentTarget);\r
+\r
+ switch (event.currentTarget.type) {\r
+ case 'checkbox':\r
+ value = $setting.is(':checked');\r
+ break;\r
+ case 'radio':\r
+ case 'text':\r
+ value = $setting.val();\r
+ break;\r
+ case 'select-one':\r
+ value = $(event.currentTarget[$setting.prop('selectedIndex')]).val();\r
+ break;\r
+ default:\r
+ value = $setting.data('value');\r
+ break;\r
+ }\r
+\r
+ // Stop settings being updated while we're saving one by one\r
+ _kiwi.global.settings.off('change', this.loadSettings, this);\r
+ settings.set($setting.data('setting'), value);\r
+ settings.save();\r
+\r
+ // Continue listening for setting changes\r
+ _kiwi.global.settings.on('change', this.loadSettings, this);\r
+ },\r
+\r
+ selectTheme: function(event) {\r
+ event.preventDefault();\r
+\r
+ this.$('[data-setting="theme"].active').removeClass('active');\r
+ $(event.currentTarget).addClass('active').trigger('change');\r
+ },\r
+\r
+ registerProtocol: function (event) {\r
+ event.preventDefault();\r
+\r
+ navigator.registerProtocolHandler('irc', document.location.origin + _kiwi.app.get('base_path') + '/%s', 'Kiwi IRC');\r
+ navigator.registerProtocolHandler('ircs', document.location.origin + _kiwi.app.get('base_path') + '/%s', 'Kiwi IRC');\r
+ },\r
+\r
+ enableNotifications: function(event){\r
+ event.preventDefault();\r
+ var notifications = _kiwi.utils.notifications;\r
+\r
+ notifications.requestPermission().always(_.bind(function () {\r
+ if (notifications.allowed() !== null) {\r
+ this.$('.notification_enabler').remove();\r
+ }\r
+ }, this));\r
+ }\r
+\r
+ });\r
+\r
+\r
+ var Applet = Backbone.Model.extend({\r
+ initialize: function () {\r
+ this.set('title', translateText('client_applets_settings_title'));\r
+ this.view = new View();\r
+ }\r
+ });\r
+\r
+\r
+ _kiwi.model.Applet.register('kiwi_settings', Applet);\r
+})();\r
+
+
+
+(function () {\r
+\r
+ var View = Backbone.View.extend({\r
+ events: {\r
+ "click .chan": "chanClick",\r
+ "click .channel_name_title": "sortChannelsByNameClick",\r
+ "click .users_title": "sortChannelsByUsersClick"\r
+ },\r
+\r
+\r
+\r
+ initialize: function (options) {\r
+ var text = {\r
+ channel_name: _kiwi.global.i18n.translate('client_applets_chanlist_channelname').fetch(),\r
+ users: _kiwi.global.i18n.translate('client_applets_chanlist_users').fetch(),\r
+ topic: _kiwi.global.i18n.translate('client_applets_chanlist_topic').fetch()\r
+ };\r
+ this.$el = $(_.template($('#tmpl_channel_list').html().trim(), text));\r
+\r
+ this.channels = [];\r
+\r
+ // Sort the table\r
+ this.order = '';\r
+\r
+ // Waiting to add the table back into the DOM?\r
+ this.waiting = false;\r
+ },\r
+\r
+ render: function () {\r
+ var table = $('table', this.$el),\r
+ tbody = table.children('tbody:first').detach(),\r
+ that = this,\r
+ i;\r
+\r
+ // Create the sort icon container and clean previous any previous ones\r
+ if($('.applet_chanlist .users_title').find('span.chanlist_sort_users').length == 0) {\r
+ this.$('.users_title').append('<span class="chanlist_sort_users"> </span>');\r
+ } else {\r
+ this.$('.users_title span.chanlist_sort_users').removeClass('fa fa-sort-desc');\r
+ this.$('.users_title span.chanlist_sort_users').removeClass('fa fa-sort-asc');\r
+ }\r
+ if ($('.applet_chanlist .channel_name_title').find('span.chanlist_sort_names').length == 0) {\r
+ this.$('.channel_name_title').append('<span class="chanlist_sort_names"> </span>');\r
+ } else {\r
+ this.$('.channel_name_title span.chanlist_sort_names').removeClass('fa fa-sort-desc');\r
+ this.$('.channel_name_title span.chanlist_sort_names').removeClass('fa fa-sort-asc');\r
+ }\r
+\r
+ // Push the new sort icon\r
+ switch (this.order) {\r
+ case 'user_desc':\r
+ default:\r
+ this.$('.users_title span.chanlist_sort_users').addClass('fa fa-sort-asc');\r
+ break;\r
+ case 'user_asc':\r
+ this.$('.users_title span.chanlist_sort_users').addClass('fa fa-sort-desc');\r
+ break;\r
+ case 'name_asc':\r
+ this.$('.channel_name_title span.chanlist_sort_names').addClass('fa fa-sort-desc');\r
+ break;\r
+ case 'name_desc':\r
+ this.$('.channel_name_title span.chanlist_sort_names').addClass('fa fa-sort-asc');\r
+ break;\r
+ }\r
+\r
+ this.channels = this.sortChannels(this.channels, this.order);\r
+\r
+ // Make sure all the channel DOM nodes are inserted in order\r
+ for (i = 0; i < this.channels.length; i++) {\r
+ tbody[0].appendChild(this.channels[i].dom);\r
+ }\r
+\r
+ table[0].appendChild(tbody[0]);\r
+ },\r
+\r
+\r
+ chanClick: function (event) {\r
+ if (event.target) {\r
+ _kiwi.gateway.join(null, $(event.target).data('channel'));\r
+ } else {\r
+ // IE...\r
+ _kiwi.gateway.join(null, $(event.srcElement).data('channel'));\r
+ }\r
+ },\r
+\r
+ sortChannelsByNameClick: function (event) {\r
+ // Revert the sorting to switch between orders\r
+ this.order = (this.order == 'name_asc') ? 'name_desc' : 'name_asc';\r
+\r
+ this.sortChannelsClick();\r
+ },\r
+\r
+ sortChannelsByUsersClick: function (event) {\r
+ // Revert the sorting to switch between orders\r
+ this.order = (this.order == 'user_desc' || this.order == '') ? 'user_asc' : 'user_desc';\r
+\r
+ this.sortChannelsClick();\r
+ },\r
+\r
+ sortChannelsClick: function() {\r
+ this.render();\r
+ },\r
+\r
+ sortChannels: function (channels, order) {\r
+ var sort_channels = [],\r
+ new_channels = [];\r
+\r
+\r
+ // First we create a light copy of the channels object to do the sorting\r
+ _.each(channels, function (chan, chan_idx) {\r
+ sort_channels.push({'chan_idx': chan_idx, 'num_users': chan.num_users, 'channel': chan.channel});\r
+ });\r
+\r
+ // Second, we apply the sorting\r
+ sort_channels.sort(function (a, b) {\r
+ switch (order) {\r
+ case 'user_asc':\r
+ return a.num_users - b.num_users;\r
+ case 'user_desc':\r
+ return b.num_users - a.num_users;\r
+ case 'name_asc':\r
+ if (a.channel.toLowerCase() > b.channel.toLowerCase()) return 1;\r
+ if (a.channel.toLowerCase() < b.channel.toLowerCase()) return -1;\r
+ case 'name_desc':\r
+ if (a.channel.toLowerCase() < b.channel.toLowerCase()) return 1;\r
+ if (a.channel.toLowerCase() > b.channel.toLowerCase()) return -1;\r
+ default:\r
+ return b.num_users - a.num_users;\r
+ }\r
+ return 0;\r
+ });\r
+\r
+ // Third, we re-shuffle the chanlist according to the sort order\r
+ _.each(sort_channels, function (chan) {\r
+ new_channels.push(channels[chan.chan_idx]);\r
+ });\r
+\r
+ return new_channels;\r
+ }\r
+ });\r
+\r
+\r
+\r
+ var Applet = Backbone.Model.extend({\r
+ initialize: function () {\r
+ this.set('title', _kiwi.global.i18n.translate('client_applets_chanlist_channellist').fetch());\r
+ this.view = new View();\r
+\r
+ this.network = _kiwi.global.components.Network();\r
+ this.network.on('list_channel', this.onListChannel, this);\r
+ this.network.on('list_start', this.onListStart, this);\r
+ },\r
+\r
+\r
+ // New channels to add to our list\r
+ onListChannel: function (event) {\r
+ this.addChannel(event.chans);\r
+ },\r
+\r
+ // A new, fresh channel list starting\r
+ onListStart: function (event) {\r
+ // TODO: clear out our existing list\r
+ },\r
+\r
+ addChannel: function (channels) {\r
+ var that = this;\r
+\r
+ if (!_.isArray(channels)) {\r
+ channels = [channels];\r
+ }\r
+ _.each(channels, function (chan) {\r
+ var row;\r
+ row = document.createElement("tr");\r
+ row.innerHTML = '<td class="chanlist_name"><a class="chan" data-channel="' + chan.channel + '">' + _.escape(chan.channel) + '</a></td><td class="chanlist_num_users" style="text-align: center;">' + chan.num_users + '</td><td style="padding-left: 2em;" class="chanlist_topic">' + formatIRCMsg(_.escape(chan.topic)) + '</td>';\r
+ chan.dom = row;\r
+ that.view.channels.push(chan);\r
+ });\r
+\r
+ if (!that.view.waiting) {\r
+ that.view.waiting = true;\r
+ _.defer(function () {\r
+ that.view.render();\r
+ that.view.waiting = false;\r
+ });\r
+ }\r
+ },\r
+\r
+\r
+ dispose: function () {\r
+ this.view.channels = null;\r
+ this.view.unbind();\r
+ this.view.$el.html('');\r
+ this.view.remove();\r
+ this.view = null;\r
+\r
+ // Remove any network event bindings\r
+ this.network.off();\r
+ }\r
+ });\r
+\r
+\r
+\r
+ _kiwi.model.Applet.register('kiwi_chanlist', Applet);\r
+})();
+
+
+ (function () {
+ var view = Backbone.View.extend({
+ events: {
+ 'click .btn_save': 'onSave'
+ },
+
+ initialize: function (options) {
+ var that = this,
+ text = {
+ save: _kiwi.global.i18n.translate('client_applets_scripteditor_save').fetch()
+ };
+ this.$el = $(_.template($('#tmpl_script_editor').html().trim(), text));
+
+ this.model.on('applet_loaded', function () {
+ that.$el.parent().css('height', '100%');
+ $script(_kiwi.app.get('base_path') + '/assets/libs/ace/ace.js', function (){ that.createAce(); });
+ });
+ },
+
+
+ createAce: function () {
+ var editor_id = 'editor_' + Math.floor(Math.random()*10000000).toString();
+ this.editor_id = editor_id;
+
+ this.$el.find('.editor').attr('id', editor_id);
+
+ this.editor = ace.edit(editor_id);
+ this.editor.setTheme("ace/theme/monokai");
+ this.editor.getSession().setMode("ace/mode/javascript");
+
+ var script_content = _kiwi.global.settings.get('user_script') || '';
+ this.editor.setValue(script_content);
+ },
+
+
+ onSave: function (event) {
+ var script_content, user_fn;
+
+ // Build the user script up with some pre-defined components
+ script_content = 'var network = kiwi.components.Network();\n';
+ script_content += 'var input = kiwi.components.ControlInput();\n';
+ script_content += 'var events = kiwi.components.Events();\n';
+ script_content += this.editor.getValue() + '\n';
+
+ // Add a dispose method to the user script for cleaning up
+ script_content += 'this._dispose = function(){ network.off(); input.off(); events.dispose(); if(this.dispose) this.dispose(); }';
+
+ // Try to compile the user script
+ try {
+ user_fn = new Function(script_content);
+
+ // Dispose any existing user script
+ if (_kiwi.user_script && _kiwi.user_script._dispose)
+ _kiwi.user_script._dispose();
+
+ // Create and run the new user script
+ _kiwi.user_script = new user_fn();
+
+ } catch (err) {
+ this.setStatus(_kiwi.global.i18n.translate('client_applets_scripteditor_error').fetch(err.toString()));
+ return;
+ }
+
+ // If we're this far, no errors occured. Save the user script
+ _kiwi.global.settings.set('user_script', this.editor.getValue());
+ _kiwi.global.settings.save();
+
+ this.setStatus(_kiwi.global.i18n.translate('client_applets_scripteditor_saved').fetch() + ' :)');
+ },
+
+
+ setStatus: function (status_text) {
+ var $status = this.$el.find('.toolbar .status');
+
+ status_text = status_text || '';
+ $status.slideUp('fast', function() {
+ $status.text(status_text);
+ $status.slideDown();
+ });
+ }
+ });
+
+
+
+ var applet = Backbone.Model.extend({
+ initialize: function () {
+ var that = this;
+
+ this.set('title', _kiwi.global.i18n.translate('client_applets_scripteditor_title').fetch());
+ this.view = new view({model: this});
+
+ }
+ });
+
+
+ _kiwi.model.Applet.register('kiwi_script_editor', applet);
+ //_kiwi.model.Applet.loadOnce('kiwi_script_editor');
+ })();
+
+
+(function () {
+ var view = Backbone.View.extend({
+ events: {},
+
+
+ initialize: function (options) {
+ this.showConnectionDialog();
+ },
+
+
+ showConnectionDialog: function() {
+ var connection_dialog = this.connection_dialog = new _kiwi.model.NewConnection();
+ connection_dialog.populateDefaultServerSettings();
+
+ connection_dialog.view.$el.addClass('initial');
+ this.$el.append(connection_dialog.view.$el);
+
+ var $info = $($('#tmpl_new_connection_info').html().trim());
+
+ if ($info.html()) {
+ connection_dialog.view.infoBoxSet($info);
+ } else {
+ $info = null;
+ }
+
+ this.listenTo(connection_dialog, 'connected', this.newConnectionConnected);
+
+ _.defer(function(){
+ if ($info) {
+ connection_dialog.view.infoBoxShow();
+ }
+
+ // Only set focus if we're not within an iframe. (firefox auto scrolls to the embedded client on page load - bad)
+ if (window == window.top) {
+ connection_dialog.view.$el.find('.nick').select();
+ }
+ });
+ },
+
+
+ newConnectionConnected: function(network) {
+ // Once connected, reset the connection form to be used again in future
+ this.connection_dialog.view.reset();
+ }
+ });
+
+
+
+ var applet = Backbone.Model.extend({
+ initialize: function () {
+ this.view = new view({model: this});
+ }
+ });
+
+
+ _kiwi.model.Applet.register('kiwi_startup', applet);
+})();
+
+
+
+_kiwi.utils.notifications = (function () {
+ if (!window.Notification) {
+ return {
+ allowed: _.constant(false),
+ requestPermission: _.constant($.Deferred().reject())
+ };
+ }
+
+ var notifications = {
+ /**
+ * Check if desktop notifications have been allowed by the user.
+ *
+ * @returns {?Boolean} `true` - they have been allowed.
+ * `false` - they have been blocked.
+ * `null` - the user hasn't answered yet.
+ */
+ allowed: function () {
+ return Notification.permission === 'granted' ? true
+ : Notification.permission === 'denied' ? false
+ : null;
+ },
+
+ /**
+ * Ask the user their permission to display desktop notifications.
+ * This will return a promise which will be resolved if the user allows notifications, or rejected if they blocked
+ * notifictions or simply closed the dialog. If the user had previously given their preference, the promise will be
+ * immediately resolved or rejected with their previous answer.
+ *
+ * @example
+ * notifications.requestPermission().then(function () { 'allowed' }, function () { 'not allowed' });
+ *
+ * @returns {Promise}
+ */
+ requestPermission: function () {
+ var deferred = $.Deferred();
+ Notification.requestPermission(function (permission) {
+ deferred[(permission === 'granted') ? 'resolve' : 'reject']();
+ });
+ return deferred.promise();
+ },
+
+ /**
+ * Create a new notification. If the user has not yet given permission to display notifications, they will be asked
+ * to confirm first. The notification will show afterwards if they allow it.
+ *
+ * Notifications implement Backbone.Events (so you can use `on` and `off`). They trigger four different events:
+ * - 'click'
+ * - 'close'
+ * - 'error'
+ * - 'show'
+ *
+ * @example
+ * notifications
+ * .create('Cool notification', { icon: 'logo.png' })
+ * .on('click', function () {
+ * window.focus();
+ * })
+ * .closeAfter(5000);
+ *
+ * @param {String} title
+ * @param {Object} options
+ * @param {String=} options.body A string representing an extra content to display within the notification
+ * @param {String=} options.dir The direction of the notification; it can be auto, ltr, or rtl
+ * @param {String=} options.lang Specify the lang used within the notification. This string must be a valid BCP
+ * 47 language tag.
+ * @param {String=} options.tag An ID for a given notification that allows to retrieve, replace or remove it if necessary
+ * @param {String=} options.icon The URL of an image to be used as an icon by the notification
+ * @returns {Notifier}
+ */
+ create: function (title, options) {
+ return new Notifier(title, options);
+ }
+ };
+
+ function Notifier(title, options) {
+ createNotification.call(this, title, options);
+ }
+ _.extend(Notifier.prototype, Backbone.Events, {
+ closed: false,
+ _closeTimeout: null,
+
+ /**
+ * Close the notification after a given number of milliseconds.
+ * @param {Number} timeout
+ * @returns {this}
+ */
+ closeAfter: function (timeout) {
+ if (!this.closed) {
+ if (this.notification) {
+ this._closeTimeout = this._closeTimeout || setTimeout(_.bind(this.close, this), timeout);
+ } else {
+ this.once('show', _.bind(this.closeAfter, this, timeout));
+ }
+ }
+ return this;
+ },
+
+ /**
+ * Close the notification immediately.
+ * @returns {this}
+ */
+ close: function () {
+ if (this.notification && !this.closed) {
+ this.notification.close();
+ this.closed = true;
+ }
+ return this;
+ }
+ });
+
+ function createNotification(title, options) {
+ switch (notifications.allowed()) {
+ case true:
+ this.notification = new Notification(title, options);
+ _.each(['click', 'close', 'error', 'show'], function (eventName) {
+ this.notification['on' + eventName] = _.bind(this.trigger, this, eventName);
+ }, this);
+ break;
+ case null:
+ notifications.requestPermission().done(_.bind(createNotification, this, title, options));
+ break;
+ }
+ }
+
+ return notifications;
+}());
+
+
+
+_kiwi.utils.formatDate = (function() {
+ /*
+ Modified version of date.format.js
+ https://github.com/jacwright/date.format
+ */
+ var locale_init = false, // Once the loales have been loaded, this is set to true
+ shortMonths, longMonths, shortDays, longDays;
+
+ // defining patterns
+ var replaceChars = {
+ // Day
+ d: function() { return (this.getDate() < 10 ? '0' : '') + this.getDate(); },
+ D: function() { return Date.shortDays[this.getDay()]; },
+ j: function() { return this.getDate(); },
+ l: function() { return Date.longDays[this.getDay()]; },
+ N: function() { return this.getDay() + 1; },
+ S: function() { return (this.getDate() % 10 == 1 && this.getDate() != 11 ? 'st' : (this.getDate() % 10 == 2 && this.getDate() != 12 ? 'nd' : (this.getDate() % 10 == 3 && this.getDate() != 13 ? 'rd' : 'th'))); },
+ w: function() { return this.getDay(); },
+ z: function() { var d = new Date(this.getFullYear(),0,1); return Math.ceil((this - d) / 86400000); }, // Fixed now
+ // Week
+ W: function() { var d = new Date(this.getFullYear(), 0, 1); return Math.ceil((((this - d) / 86400000) + d.getDay() + 1) / 7); }, // Fixed now
+ // Month
+ F: function() { return Date.longMonths[this.getMonth()]; },
+ m: function() { return (this.getMonth() < 9 ? '0' : '') + (this.getMonth() + 1); },
+ M: function() { return Date.shortMonths[this.getMonth()]; },
+ n: function() { return this.getMonth() + 1; },
+ t: function() { var d = new Date(); return new Date(d.getFullYear(), d.getMonth(), 0).getDate(); }, // Fixed now, gets #days of date
+ // Year
+ L: function() { var year = this.getFullYear(); return (year % 400 == 0 || (year % 100 != 0 && year % 4 == 0)); }, // Fixed now
+ o: function() { var d = new Date(this.valueOf()); d.setDate(d.getDate() - ((this.getDay() + 6) % 7) + 3); return d.getFullYear();}, //Fixed now
+ Y: function() { return this.getFullYear(); },
+ y: function() { return ('' + this.getFullYear()).substr(2); },
+ // Time
+ a: function() { return this.getHours() < 12 ? 'am' : 'pm'; },
+ A: function() { return this.getHours() < 12 ? 'AM' : 'PM'; },
+ B: function() { return Math.floor((((this.getUTCHours() + 1) % 24) + this.getUTCMinutes() / 60 + this.getUTCSeconds() / 3600) * 1000 / 24); }, // Fixed now
+ g: function() { return this.getHours() % 12 || 12; },
+ G: function() { return this.getHours(); },
+ h: function() { return ((this.getHours() % 12 || 12) < 10 ? '0' : '') + (this.getHours() % 12 || 12); },
+ H: function() { return (this.getHours() < 10 ? '0' : '') + this.getHours(); },
+ i: function() { return (this.getMinutes() < 10 ? '0' : '') + this.getMinutes(); },
+ s: function() { return (this.getSeconds() < 10 ? '0' : '') + this.getSeconds(); },
+ u: function() { var m = this.getMilliseconds(); return (m < 10 ? '00' : (m < 100 ? '0' : '')) + m; },
+ // Timezone
+ e: function() { return "Not Yet Supported"; },
+ I: function() {
+ var DST = null;
+ for (var i = 0; i < 12; ++i) {
+ var d = new Date(this.getFullYear(), i, 1);
+ var offset = d.getTimezoneOffset();
+
+ if (DST === null) DST = offset;
+ else if (offset < DST) { DST = offset; break; }
+ else if (offset > DST) break;
+ }
+ return (this.getTimezoneOffset() == DST) | 0;
+ },
+ O: function() { return (-this.getTimezoneOffset() < 0 ? '-' : '+') + (Math.abs(this.getTimezoneOffset() / 60) < 10 ? '0' : '') + (Math.abs(this.getTimezoneOffset() / 60)) + '00'; },
+ P: function() { return (-this.getTimezoneOffset() < 0 ? '-' : '+') + (Math.abs(this.getTimezoneOffset() / 60) < 10 ? '0' : '') + (Math.abs(this.getTimezoneOffset() / 60)) + ':00'; }, // Fixed now
+ T: function() { var m = this.getMonth(); this.setMonth(0); var result = this.toTimeString().replace(/^.+ \(?([^\)]+)\)?$/, '$1'); this.setMonth(m); return result;},
+ Z: function() { return -this.getTimezoneOffset() * 60; },
+ // Full Date/Time
+ c: function() { return this.format("Y-m-d\\TH:i:sP"); }, // Fixed now
+ r: function() { return this.toString(); },
+ U: function() { return this.getTime() / 1000; }
+ };
+
+
+ var initLocaleFormats = function() {
+ shortMonths = [
+ _kiwi.global.i18n.translate('client.libs.date_format.short_months.january').fetch(),
+ _kiwi.global.i18n.translate('client.libs.date_format.short_months.february').fetch(),
+ _kiwi.global.i18n.translate('client.libs.date_format.short_months.march').fetch(),
+ _kiwi.global.i18n.translate('client.libs.date_format.short_months.april').fetch(),
+ _kiwi.global.i18n.translate('client.libs.date_format.short_months.may').fetch(),
+ _kiwi.global.i18n.translate('client.libs.date_format.short_months.june').fetch(),
+ _kiwi.global.i18n.translate('client.libs.date_format.short_months.july').fetch(),
+ _kiwi.global.i18n.translate('client.libs.date_format.short_months.august').fetch(),
+ _kiwi.global.i18n.translate('client.libs.date_format.short_months.september').fetch(),
+ _kiwi.global.i18n.translate('client.libs.date_format.short_months.october').fetch(),
+ _kiwi.global.i18n.translate('client.libs.date_format.short_months.november').fetch(),
+ _kiwi.global.i18n.translate('client.libs.date_format.short_months.december').fetch()
+ ];
+ longMonths = [
+ _kiwi.global.i18n.translate('client.libs.date_format.long_months.january').fetch(),
+ _kiwi.global.i18n.translate('client.libs.date_format.long_months.february').fetch(),
+ _kiwi.global.i18n.translate('client.libs.date_format.long_months.march').fetch(),
+ _kiwi.global.i18n.translate('client.libs.date_format.long_months.april').fetch(),
+ _kiwi.global.i18n.translate('client.libs.date_format.long_months.may').fetch(),
+ _kiwi.global.i18n.translate('client.libs.date_format.long_months.june').fetch(),
+ _kiwi.global.i18n.translate('client.libs.date_format.long_months.july').fetch(),
+ _kiwi.global.i18n.translate('client.libs.date_format.long_months.august').fetch(),
+ _kiwi.global.i18n.translate('client.libs.date_format.long_months.september').fetch(),
+ _kiwi.global.i18n.translate('client.libs.date_format.long_months.october').fetch(),
+ _kiwi.global.i18n.translate('client.libs.date_format.long_months.november').fetch(),
+ _kiwi.global.i18n.translate('client.libs.date_format.long_months.december').fetch()
+ ];
+ shortDays = [
+ _kiwi.global.i18n.translate('client.libs.date_format.short_days.monday').fetch(),
+ _kiwi.global.i18n.translate('client.libs.date_format.short_days.tuesday').fetch(),
+ _kiwi.global.i18n.translate('client.libs.date_format.short_days.wednesday').fetch(),
+ _kiwi.global.i18n.translate('client.libs.date_format.short_days.thursday').fetch(),
+ _kiwi.global.i18n.translate('client.libs.date_format.short_days.friday').fetch(),
+ _kiwi.global.i18n.translate('client.libs.date_format.short_days.saturday').fetch(),
+ _kiwi.global.i18n.translate('client.libs.date_format.short_days.sunday').fetch()
+ ];
+ longDays = [
+ _kiwi.global.i18n.translate('client.libs.date_format.long_days.monday').fetch(),
+ _kiwi.global.i18n.translate('client.libs.date_format.long_days.tuesday').fetch(),
+ _kiwi.global.i18n.translate('client.libs.date_format.long_days.wednesday').fetch(),
+ _kiwi.global.i18n.translate('client.libs.date_format.long_days.thursday').fetch(),
+ _kiwi.global.i18n.translate('client.libs.date_format.long_days.friday').fetch(),
+ _kiwi.global.i18n.translate('client.libs.date_format.long_days.saturday').fetch(),
+ _kiwi.global.i18n.translate('client.libs.date_format.long_days.sunday').fetch()
+ ];
+
+ locale_init = true;
+ };
+ /* End of date.format */
+
+
+ // Finally.. the actuall formatDate function
+ return function(working_date, format) {
+ if (!locale_init)
+ initLocaleFormats();
+
+ working_date = working_date || new Date();
+ format = format || _kiwi.global.i18n.translate('client_date_format').fetch();
+
+ return format.replace(/(\\?)(.)/g, function(_, esc, chr) {
+ return (esc === '' && replaceChars[chr]) ? replaceChars[chr].call(working_date) : chr;
+ });
+ };
+})();
+
+
+/*
+ * The same functionality as EventEmitter but with the inclusion of callbacks
+ */
+
+
+
+function PluginInterface () {
+ // Holder for all the bound listeners by this module
+ this._listeners = {};
+
+ // Event proxies
+ this._parent = null;
+ this._children = [];
+}
+
+
+
+PluginInterface.prototype.on = function (event_name, fn, scope) {
+ this._listeners[event_name] = this._listeners[event_name] || [];
+ this._listeners[event_name].push(['on', fn, scope]);
+};
+
+
+
+PluginInterface.prototype.once = function (event_name, fn, scope) {
+ this._listeners[event_name] = this._listeners[event_name] || [];
+ this._listeners[event_name].push(['once', fn, scope]);
+};
+
+
+
+PluginInterface.prototype.off = function (event_name, fn, scope) {
+ var idx;
+
+ if (typeof event_name === 'undefined') {
+ // Remove all listeners
+ this._listeners = {};
+
+ } else if (typeof fn === 'undefined') {
+ // Remove all of 1 event type
+ delete this._listeners[event_name];
+
+ } else if (typeof scope === 'undefined') {
+ // Remove a single event type + callback
+ for (idx in (this._listeners[event_name] || [])) {
+ if (this._listeners[event_name][idx][1] === fn) {
+ delete this._listeners[event_name][idx];
+ }
+ }
+ } else {
+ // Remove a single event type + callback + scope
+ for (idx in (this._listeners[event_name] || [])) {
+ if (this._listeners[event_name][idx][1] === fn && this._listeners[event_name][idx][2] === scope) {
+ delete this._listeners[event_name][idx];
+ }
+ }
+ }
+};
+
+
+
+PluginInterface.prototype.getListeners = function(event_name) {
+ return this._listeners[event_name] || [];
+};
+
+
+
+PluginInterface.prototype.createProxy = function() {
+ var proxy = new PluginInterface();
+ proxy._parent = this._parent || this;
+ proxy._parent._children.push(proxy);
+
+ return proxy;
+};
+
+
+
+PluginInterface.prototype.dispose = function() {
+ this.off();
+
+ if (this._parent) {
+ var idx = this._parent._children.indexOf(this);
+ if (idx > -1) {
+ this._parent._children.splice(idx, 1);
+ }
+ }
+};
+
+
+
+// Call all the listeners for a certain event, passing them some event data that may be changed
+PluginInterface.prototype.emit = function (event_name, event_data) {
+ var emitter = new this.EmitCall(event_name, event_data),
+ listeners = [],
+ child_idx;
+
+ // Get each childs event listeners in order of last created
+ for(child_idx=this._children.length-1; child_idx>=0; child_idx--) {
+ listeners = listeners.concat(this._children[child_idx].getListeners(event_name));
+ }
+
+ // Now include any listeners directly on this instance
+ listeners = listeners.concat(this.getListeners(event_name));
+
+ // Once emitted, remove any 'once' bound listeners
+ emitter.then(function () {
+ var len = listeners.length,
+ idx;
+
+ for(idx = 0; idx < len; idx++) {
+ if (listeners[idx][0] === 'once') {
+ listeners[idx] = undefined;
+ }
+ }
+ });
+
+ // Emit the event to the listeners and return
+ emitter.callListeners(listeners);
+ return emitter;
+};
+
+
+
+// Promise style object to emit events to listeners
+PluginInterface.prototype.EmitCall = function EmitCall (event_name, event_data) {
+ var that = this,
+ completed = false,
+ completed_fn = [],
+
+ // Has event.preventDefault() been called
+ prevented = false,
+ prevented_fn = [];
+
+
+ // Emit this event to an array of listeners
+ function callListeners(listeners) {
+ var current_event_idx = -1;
+
+ // Make sure we have some data to pass to the listeners
+ event_data = event_data || undefined;
+
+ // If no bound listeners for this event, leave now
+ if (listeners.length === 0) {
+ emitComplete();
+ return;
+ }
+
+
+ // Call the next listener in our array
+ function nextListener() {
+ var listener, event_obj;
+
+ // We want the next listener
+ current_event_idx++;
+
+ // If we've ran out of listeners end this emit call
+ if (!listeners[current_event_idx]) {
+ emitComplete();
+ return;
+ }
+
+ // Object the listener ammends to tell us what it's going to do
+ event_obj = {
+ // If changed to true, expect this listener is going to callback
+ wait: false,
+
+ // If wait is true, this callback must be called to continue running listeners
+ callback: function () {
+ // Invalidate this callback incase a listener decides to call it again
+ event_obj.callback = undefined;
+
+ nextListener.apply(that);
+ },
+
+ // Prevents the default 'done' functions from executing
+ preventDefault: function () {
+ prevented = true;
+ }
+ };
+
+
+ listener = listeners[current_event_idx];
+ listener[1].call(listener[2] || that, event_obj, event_data);
+
+ // If the listener hasn't signalled it's going to wait, proceed to next listener
+ if (!event_obj.wait) {
+ // Invalidate the callback just incase a listener decides to call it anyway
+ event_obj.callback = undefined;
+
+ nextListener();
+ }
+ }
+
+ nextListener();
+ }
+
+
+
+ function emitComplete() {
+ completed = true;
+
+ var funcs = prevented ? prevented_fn : completed_fn;
+ funcs = funcs || [];
+
+ // Call the completed/prevented functions
+ for (var idx = 0; idx < funcs.length; idx++) {
+ if (typeof funcs[idx] === 'function') funcs[idx]();
+ }
+ }
+
+
+
+ function addCompletedFunc(fn) {
+ // Only accept functions
+ if (typeof fn !== 'function') return false;
+
+ completed_fn.push(fn);
+
+ // If we have already completed the emits, call this now
+ if (completed && !prevented) fn();
+
+ return this;
+ }
+
+
+
+ function addPreventedFunc(fn) {
+ // Only accept functions
+ if (typeof fn !== 'function') return false;
+
+ prevented_fn.push(fn);
+
+ // If we have already completed the emits, call this now
+ if (completed && prevented) fn();
+
+ return this;
+ }
+
+
+ return {
+ callListeners: callListeners,
+ then: addCompletedFunc,
+ catch: addPreventedFunc
+ };
+};
+
+
+
+// If running a node module, set the exports
+if (typeof module === 'object' && typeof module.exports !== 'undefined') {
+ module.exports = PluginInterface;
+}
+
+
+
+/*
+ * Example usage
+ */
+
+
+/*
+var modules = new PluginInterface();
+
+
+
+// A plugin
+modules.on('irc message', function (event, data) {
+ //event.wait = true;
+ setTimeout(event.callback, 2000);
+});
+
+
+
+
+// Core code that is being extended by plugins
+var data = {
+ nick: 'prawnsalald',
+ command: '/dothis'
+};
+
+modules.emit('irc message', data).done(function () {
+ console.log('Your command is: ' + data.command);
+});
+*/
+
+
+/*jslint devel: true, browser: true, continue: true, sloppy: true, forin: true, plusplus: true, maxerr: 50, indent: 4, nomen: true, regexp: true*/
+/*globals $, front, gateway, Utilityview */
+
+
+
+/**
+* Generate a random string of given length
+* @param {Number} string_length The length of the random string
+* @returns {String} The random string
+*/
+function randomString(string_length) {
+ var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz",
+ randomstring = '',
+ i,
+ rnum;
+ for (i = 0; i < string_length; i++) {
+ rnum = Math.floor(Math.random() * chars.length);
+ randomstring += chars.substring(rnum, rnum + 1);
+ }
+ return randomstring;
+}
+
+/**
+* String.trim shim
+*/
+if (typeof String.prototype.trim === 'undefined') {
+ String.prototype.trim = function () {
+ return this.replace(/^\s+|\s+$/g, "");
+ };
+}
+
+/**
+* String.lpad shim
+* @param {Number} length The length of padding
+* @param {String} characher The character to pad with
+* @returns {String} The padded string
+*/
+if (typeof String.prototype.lpad === 'undefined') {
+ String.prototype.lpad = function (length, character) {
+ var padding = "",
+ i;
+ for (i = 0; i < length; i++) {
+ padding += character;
+ }
+ return (padding + this).slice(-length);
+ };
+}
+
+
+/**
+* Convert seconds into hours:minutes:seconds
+* @param {Number} secs The number of seconds to converts
+* @returns {Object} An object representing the hours/minutes/second conversion of secs
+*/
+function secondsToTime(secs) {
+ var hours, minutes, seconds, divisor_for_minutes, divisor_for_seconds, obj;
+ hours = Math.floor(secs / (60 * 60));
+
+ divisor_for_minutes = secs % (60 * 60);
+ minutes = Math.floor(divisor_for_minutes / 60);
+
+ divisor_for_seconds = divisor_for_minutes % 60;
+ seconds = Math.ceil(divisor_for_seconds);
+
+ obj = {
+ "h": hours,
+ "m": minutes,
+ "s": seconds
+ };
+ return obj;
+}
+
+
+/* Command input Alias + re-writing */
+function InputPreProcessor () {
+ this.recursive_depth = 3;
+
+ this.aliases = {};
+ this.vars = {version: 1};
+
+ // Current recursive depth
+ var depth = 0;
+
+
+ // Takes an array of words to process!
+ this.processInput = function (input) {
+ var words = input || [],
+ alias = this.aliases[words[0]],
+ alias_len,
+ current_alias_word = '',
+ compiled = [];
+
+ // If an alias wasn't found, return the original input
+ if (!alias) return input;
+
+ // Split the alias up into useable words
+ alias = alias.split(' ');
+ alias_len = alias.length;
+
+ // Iterate over each word and pop them into the final compiled array.
+ // Any $ words are processed with the result ending into the compiled array.
+ for (var i=0; i<alias_len; i++) {
+ current_alias_word = alias[i];
+
+ // Non $ word
+ if (current_alias_word[0] !== '$') {
+ compiled.push(current_alias_word);
+ continue;
+ }
+
+ // Refering to an input word ($N)
+ if (!isNaN(current_alias_word[1])) {
+ var num = current_alias_word.match(/\$(\d+)(\+)?(\d+)?/);
+
+ // Did we find anything or does the word it refers to non-existant?
+ if (!num || !words[num[1]]) continue;
+
+ if (num[2] === '+' && num[3]) {
+ // Add X number of words
+ compiled = compiled.concat(words.slice(parseInt(num[1], 10), parseInt(num[1], 10) + parseInt(num[3], 10)));
+ } else if (num[2] === '+') {
+ // Add the remaining of the words
+ compiled = compiled.concat(words.slice(parseInt(num[1], 10)));
+ } else {
+ // Add a single word
+ compiled.push(words[parseInt(num[1], 10)]);
+ }
+
+ continue;
+ }
+
+
+ // Refering to a variable
+ if (typeof this.vars[current_alias_word.substr(1)] !== 'undefined') {
+
+ // Get the variable
+ compiled.push(this.vars[current_alias_word.substr(1)]);
+
+ continue;
+ }
+
+ }
+
+ return compiled;
+ };
+
+
+ this.process = function (input) {
+ input = input || '';
+
+ var words = input.split(' ');
+
+ depth++;
+ if (depth >= this.recursive_depth) {
+ depth--;
+ return input;
+ }
+
+ if (this.aliases[words[0]]) {
+ words = this.processInput(words);
+
+ if (this.aliases[words[0]]) {
+ words = this.process(words.join(' ')).split(' ');
+ }
+
+ }
+
+ depth--;
+ return words.join(' ');
+ };
+}
+
+
+/**
+ * Convert HSL to RGB formatted colour
+ */
+function hsl2rgb(h, s, l) {
+ var m1, m2, hue;
+ var r, g, b
+ s /=100;
+ l /= 100;
+ if (s == 0)
+ r = g = b = (l * 255);
+ else {
+ function HueToRgb(m1, m2, hue) {
+ var v;
+ if (hue < 0)
+ hue += 1;
+ else if (hue > 1)
+ hue -= 1;
+
+ if (6 * hue < 1)
+ v = m1 + (m2 - m1) * hue * 6;
+ else if (2 * hue < 1)
+ v = m2;
+ else if (3 * hue < 2)
+ v = m1 + (m2 - m1) * (2/3 - hue) * 6;
+ else
+ v = m1;
+
+ return 255 * v;
+ }
+ if (l <= 0.5)
+ m2 = l * (s + 1);
+ else
+ m2 = l + s - l * s;
+ m1 = l * 2 - m2;
+ hue = h / 360;
+ r = HueToRgb(m1, m2, hue + 1/3);
+ g = HueToRgb(m1, m2, hue);
+ b = HueToRgb(m1, m2, hue - 1/3);
+ }
+ return [r,g,b];
+}
+
+
+/**
+ * Formats a kiwi message to IRC format
+ */
+function formatToIrcMsg(message) {
+ // Format any colour codes (eg. $c4)
+ message = message.replace(/%C(\d)/g, function(match, colour_number) {
+ return String.fromCharCode(3) + colour_number.toString();
+ });
+
+ var formatters = {
+ B: '\x02', // Bold
+ I: '\x1D', // Italics
+ U: '\x1F', // Underline
+ O: '\x0F' // Out / Clear formatting
+ };
+ message = message.replace(/%([BIUO])/g, function(match, format_code) {
+ if (typeof formatters[format_code.toUpperCase()] !== 'undefined')
+ return formatters[format_code.toUpperCase()];
+ });
+
+ return message;
+}
+
+
+/**
+* Formats a message. Adds bold, underline and colouring
+* @param {String} msg The message to format
+* @returns {String} The HTML formatted message
+*/
+function formatIRCMsg (msg) {
+ "use strict";
+ var out = '',
+ currentTag = '',
+ openTags = {
+ bold: false,
+ italic: false,
+ underline: false,
+ colour: false
+ },
+ spanFromOpen = function () {
+ var style = '',
+ colours;
+ if (!(openTags.bold || openTags.italic || openTags.underline || openTags.colour)) {
+ return '';
+ } else {
+ style += (openTags.bold) ? 'font-weight: bold; ' : '';
+ style += (openTags.italic) ? 'font-style: italic; ' : '';
+ style += (openTags.underline) ? 'text-decoration: underline; ' : '';
+ if (openTags.colour) {
+ colours = openTags.colour.split(',');
+ style += 'color: ' + colours[0] + ((colours[1]) ? '; background-color: ' + colours[1] + ';' : '');
+ }
+ return '<span class="format_span" style="' + style + '">';
+ }
+ },
+ colourMatch = function (str) {
+ var re = /^\x03(([0-9][0-9]?)(,([0-9][0-9]?))?)/;
+ return re.exec(str);
+ },
+ hexFromNum = function (num) {
+ switch (parseInt(num, 10)) {
+ case 0:
+ return '#FFFFFF';
+ case 1:
+ return '#000000';
+ case 2:
+ return '#000080';
+ case 3:
+ return '#008000';
+ case 4:
+ return '#FF0000';
+ case 5:
+ return '#800040';
+ case 6:
+ return '#800080';
+ case 7:
+ return '#FF8040';
+ case 8:
+ return '#FFFF00';
+ case 9:
+ return '#80FF00';
+ case 10:
+ return '#008080';
+ case 11:
+ return '#00FFFF';
+ case 12:
+ return '#0000FF';
+ case 13:
+ return '#FF55FF';
+ case 14:
+ return '#808080';
+ case 15:
+ return '#C0C0C0';
+ default:
+ return null;
+ }
+ },
+ i = 0,
+ colours = [],
+ match;
+
+ for (i = 0; i < msg.length; i++) {
+ switch (msg[i]) {
+ case '\x02':
+ if ((openTags.bold || openTags.italic || openTags.underline || openTags.colour)) {
+ out += currentTag + '</span>';
+ }
+ openTags.bold = !openTags.bold;
+ currentTag = spanFromOpen();
+ break;
+ case '\x1D':
+ if ((openTags.bold || openTags.italic || openTags.underline || openTags.colour)) {
+ out += currentTag + '</span>';
+ }
+ openTags.italic = !openTags.italic;
+ currentTag = spanFromOpen();
+ break;
+ case '\x1F':
+ if ((openTags.bold || openTags.italic || openTags.underline || openTags.colour)) {
+ out += currentTag + '</span>';
+ }
+ openTags.underline = !openTags.underline;
+ currentTag = spanFromOpen();
+ break;
+ case '\x03':
+ if ((openTags.bold || openTags.italic || openTags.underline || openTags.colour)) {
+ out += currentTag + '</span>';
+ }
+ match = colourMatch(msg.substr(i, 6));
+ if (match) {
+ i += match[1].length;
+ // 2 & 4
+ colours[0] = hexFromNum(match[2]);
+ if (match[4]) {
+ colours[1] = hexFromNum(match[4]);
+ }
+ openTags.colour = colours.join(',');
+ } else {
+ openTags.colour = false;
+ }
+ currentTag = spanFromOpen();
+ break;
+ case '\x0F':
+ if ((openTags.bold || openTags.italic || openTags.underline || openTags.colour)) {
+ out += currentTag + '</span>';
+ }
+ openTags.bold = openTags.italic = openTags.underline = openTags.colour = false;
+ break;
+ default:
+ if ((openTags.bold || openTags.italic || openTags.underline || openTags.colour)) {
+ currentTag += msg[i];
+ } else {
+ out += msg[i];
+ }
+ break;
+ }
+ }
+ if ((openTags.bold || openTags.italic || openTags.underline || openTags.colour)) {
+ out += currentTag + '</span>';
+ }
+ return out;
+}
+
+function escapeRegex (str) {
+ return str.replace(/[\[\\\^\$\.\|\?\*\+\(\)]/g, '\\$&');
+}
+
+function emoticonFromText(str) {
+ var words_in = str.split(' '),
+ words_out = [],
+ i,
+ pushEmoticon = function (alt, emote_name) {
+ words_out.push('<i class="emoticon ' + emote_name + '">' + alt + '</i>');
+ };
+
+ for (i = 0; i < words_in.length; i++) {
+ switch(words_in[i]) {
+ case ':)':
+ pushEmoticon(':)', 'smile');
+ break;
+ case ':(':
+ pushEmoticon(':(', 'sad');
+ break;
+ case ':3':
+ pushEmoticon(':3', 'lion');
+ break;
+ case ';3':
+ pushEmoticon(';3', 'winky_lion');
+ break;
+ case ':s':
+ case ':S':
+ pushEmoticon(':s', 'confused');
+ break;
+ case ';(':
+ case ';_;':
+ pushEmoticon(';(', 'cry');
+ break;
+ case ';)':
+ pushEmoticon(';)', 'wink');
+ break;
+ case ';D':
+ pushEmoticon(';D', 'wink_happy');
+ break;
+ case ':P':
+ case ':p':
+ pushEmoticon(':P', 'tongue');
+ break;
+ case 'xP':
+ pushEmoticon('xP', 'cringe_tongue');
+ break;
+ case ':o':
+ case ':O':
+ case ':0':
+ pushEmoticon(':o', 'shocked');
+ break;
+ case ':D':
+ pushEmoticon(':D', 'happy');
+ break;
+ case '^^':
+ case '^.^':
+ pushEmoticon('^^,', 'eyebrows');
+ break;
+ case '<3':
+ pushEmoticon('<3', 'heart');
+ break;
+ case '>_<':
+ case '>.<':
+ pushEmoticon('>_<', 'doh');
+ break;
+ case 'XD':
+ case 'xD':
+ pushEmoticon('xD', 'big_grin');
+ break;
+ case 'o.0':
+ case 'o.O':
+ pushEmoticon('o.0', 'wide_eye_right');
+ break;
+ case '0.o':
+ case 'O.o':
+ pushEmoticon('0.o', 'wide_eye_left');
+ break;
+ case ':\\':
+ case '=\\':
+ case ':/':
+ case '=/':
+ pushEmoticon(':\\', 'unsure');
+ break;
+ default:
+ words_out.push(words_in[i]);
+ }
+ }
+
+ return words_out.join(' ');
+}
+
+// Code based on http://anentropic.wordpress.com/2009/06/25/javascript-iso8601-parser-and-pretty-dates/#comment-154
+function parseISO8601(str) {
+ if (Date.prototype.toISOString) {
+ return new Date(str);
+ } else {
+ var parts = str.split('T'),
+ dateParts = parts[0].split('-'),
+ timeParts = parts[1].split('Z'),
+ timeSubParts = timeParts[0].split(':'),
+ timeSecParts = timeSubParts[2].split('.'),
+ timeHours = Number(timeSubParts[0]),
+ _date = new Date();
+
+ _date.setUTCFullYear(Number(dateParts[0]));
+ _date.setUTCDate(1);
+ _date.setUTCMonth(Number(dateParts[1])-1);
+ _date.setUTCDate(Number(dateParts[2]));
+ _date.setUTCHours(Number(timeHours));
+ _date.setUTCMinutes(Number(timeSubParts[1]));
+ _date.setUTCSeconds(Number(timeSecParts[0]));
+ if (timeSecParts[1]) {
+ _date.setUTCMilliseconds(Number(timeSecParts[1]));
+ }
+
+ return _date;
+ }
+}
+
+// Simplyfy the translation syntax
+function translateText(string_id, params) {
+ params = params || '';
+
+ return _kiwi.global.i18n.translate(string_id).fetch(params);
+}
+
+/**
+ * Simplyfy the text styling syntax
+ *
+ * Syntax:
+ * %nick: nickname
+ * %channel: channel
+ * %ident: ident
+ * %host: host
+ * %realname: realname
+ * %text: translated text
+ * %C[digit]: color
+ * %B: bold
+ * %I: italic
+ * %U: underline
+ * %O: cancel styles
+ **/
+function styleText(string_id, params) {
+ var style, text;
+
+ //style = formatToIrcMsg(_kiwi.app.text_theme[string_id]);
+ style = _kiwi.app.text_theme[string_id];
+ style = formatToIrcMsg(style);
+
+ // Expand a member mask into its individual parts (nick, ident, hostname)
+ if (params.member) {
+ params.nick = params.member.nick || '';
+ params.ident = params.member.ident || '';
+ params.host = params.member.hostname || '';
+ params.prefix = params.member.prefix || '';
+ }
+
+ // Do the magic. Use the %shorthand syntax to produce output.
+ text = style.replace(/%([A-Z]{2,})/ig, function(match, key) {
+ if (typeof params[key] !== 'undefined')
+ return params[key];
+ });
+
+ return text;
+}
+
+
+
+
+})(window);
--- /dev/null
+!function(e,t){function n(){this._listeners={},this._parent=null,this._children=[]}function i(e){var t,n,i="0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz",s="";for(t=0;e>t;t++)n=Math.floor(Math.random()*i.length),s+=i.substring(n,n+1);return s}function s(e){var t,n,i,s,a,o;return t=Math.floor(e/3600),s=e%3600,n=Math.floor(s/60),a=s%60,i=Math.ceil(a),o={h:t,m:n,s:i}}function a(){this.recursive_depth=3,this.aliases={},this.vars={version:1};var e=0;this.processInput=function(e){var t,n=e||[],i=this.aliases[n[0]],s="",a=[];if(!i)return e;i=i.split(" "),t=i.length;for(var o=0;t>o;o++)if(s=i[o],"$"===s[0])if(isNaN(s[1]))"undefined"==typeof this.vars[s.substr(1)]||a.push(this.vars[s.substr(1)]);else{var c=s.match(/\$(\d+)(\+)?(\d+)?/);if(!c||!n[c[1]])continue;"+"===c[2]&&c[3]?a=a.concat(n.slice(parseInt(c[1],10),parseInt(c[1],10)+parseInt(c[3],10))):"+"===c[2]?a=a.concat(n.slice(parseInt(c[1],10))):a.push(n[parseInt(c[1],10)])}else a.push(s);return a},this.process=function(t){t=t||"";var n=t.split(" ");return e++,e>=this.recursive_depth?(e--,t):(this.aliases[n[0]]&&(n=this.processInput(n),this.aliases[n[0]]&&(n=this.process(n.join(" ")).split(" "))),e--,n.join(" "))}}function o(e,t,n){function i(e,t,n){var i;return 0>n?n+=1:n>1&&(n-=1),i=1>6*n?e+(t-e)*n*6:1>2*n?t:2>3*n?e+(t-e)*(2/3-n)*6:e,255*i}var s,a,o,c,l,r;return t/=100,n/=100,0==t?c=l=r=255*n:(a=.5>=n?n*(t+1):n+t-n*t,s=2*n-a,o=e/360,c=i(s,a,o+1/3),l=i(s,a,o),r=i(s,a,o-1/3)),[c,l,r]}function c(e){e=e.replace(/%C(\d)/g,function(e,t){return String.fromCharCode(3)+t.toString()});var t={B:"\ 2",I:"\1d",U:"\1f",O:"\ f"};return e=e.replace(/%([BIUO])/g,function(e,n){return"undefined"!=typeof t[n.toUpperCase()]?t[n.toUpperCase()]:void 0})}function l(e){"use strict";var t,n="",i="",s={bold:!1,italic:!1,underline:!1,colour:!1},a=function(){var e,t="";return s.bold||s.italic||s.underline||s.colour?(t+=s.bold?"font-weight: bold; ":"",t+=s.italic?"font-style: italic; ":"",t+=s.underline?"text-decoration: underline; ":"",s.colour&&(e=s.colour.split(","),t+="color: "+e[0]+(e[1]?"; background-color: "+e[1]+";":"")),'<span class="format_span" style="'+t+'">'):""},o=function(e){var t=/^\x03(([0-9][0-9]?)(,([0-9][0-9]?))?)/;return t.exec(e)},c=function(e){switch(parseInt(e,10)){case 0:return"#FFFFFF";case 1:return"#000000";case 2:return"#000080";case 3:return"#008000";case 4:return"#FF0000";case 5:return"#800040";case 6:return"#800080";case 7:return"#FF8040";case 8:return"#FFFF00";case 9:return"#80FF00";case 10:return"#008080";case 11:return"#00FFFF";case 12:return"#0000FF";case 13:return"#FF55FF";case 14:return"#808080";case 15:return"#C0C0C0";default:return null}},l=0,r=[];for(l=0;l<e.length;l++)switch(e[l]){case"\ 2":(s.bold||s.italic||s.underline||s.colour)&&(n+=i+"</span>"),s.bold=!s.bold,i=a();break;case"\1d":(s.bold||s.italic||s.underline||s.colour)&&(n+=i+"</span>"),s.italic=!s.italic,i=a();break;case"\1f":(s.bold||s.italic||s.underline||s.colour)&&(n+=i+"</span>"),s.underline=!s.underline,i=a();break;case"\ 3":(s.bold||s.italic||s.underline||s.colour)&&(n+=i+"</span>"),t=o(e.substr(l,6)),t?(l+=t[1].length,r[0]=c(t[2]),t[4]&&(r[1]=c(t[4])),s.colour=r.join(",")):s.colour=!1,i=a();break;case"\ f":(s.bold||s.italic||s.underline||s.colour)&&(n+=i+"</span>"),s.bold=s.italic=s.underline=s.colour=!1;break;default:s.bold||s.italic||s.underline||s.colour?i+=e[l]:n+=e[l]}return(s.bold||s.italic||s.underline||s.colour)&&(n+=i+"</span>"),n}function r(e){return e.replace(/[\[\\\^\$\.\|\?\*\+\(\)]/g,"\\$&")}function h(e){var t,n=e.split(" "),i=[],s=function(e,t){i.push('<i class="emoticon '+t+'">'+e+"</i>")};for(t=0;t<n.length;t++)switch(n[t]){case":)":s(":)","smile");break;case":(":s(":(","sad");break;case":3":s(":3","lion");break;case";3":s(";3","winky_lion");break;case":s":case":S":s(":s","confused");break;case";(":case";_;":s(";(","cry");break;case";)":s(";)","wink");break;case";D":s(";D","wink_happy");break;case":P":case":p":s(":P","tongue");break;case"xP":s("xP","cringe_tongue");break;case":o":case":O":case":0":s(":o","shocked");break;case":D":s(":D","happy");break;case"^^":case"^.^":s("^^,","eyebrows");break;case"<3":s("<3","heart");break;case">_<":case">.<":s(">_<","doh");break;case"XD":case"xD":s("xD","big_grin");break;case"o.0":case"o.O":s("o.0","wide_eye_right");break;case"0.o":case"O.o":s("0.o","wide_eye_left");break;case":\\":case"=\\":case":/":case"=/":s(":\\","unsure");break;default:i.push(n[t])}return i.join(" ")}function p(e){if(Date.prototype.toISOString)return new Date(e);var t=e.split("T"),n=t[0].split("-"),i=t[1].split("Z"),s=i[0].split(":"),a=s[2].split("."),o=Number(s[0]),c=new Date;return c.setUTCFullYear(Number(n[0])),c.setUTCDate(1),c.setUTCMonth(Number(n[1])-1),c.setUTCDate(Number(n[2])),c.setUTCHours(Number(o)),c.setUTCMinutes(Number(s[1])),c.setUTCSeconds(Number(a[0])),a[1]&&c.setUTCMilliseconds(Number(a[1])),c}function d(e,t){return t=t||"",m.global.i18n.translate(e).fetch(t)}function u(e,t){var n,i;return n=m.app.text_theme[e],n=c(n),t.member&&(t.nick=t.member.nick||"",t.ident=t.member.ident||"",t.host=t.member.hostname||"",t.prefix=t.member.prefix||""),i=n.replace(/%([A-Z]{2,})/gi,function(e,n){return"undefined"!=typeof t[n]?t[n]:void 0})}var m={};if(m.misc={},m.model={},m.view={},m.applets={},m.utils={},m.global={build_version:"",settings:t,plugins:t,events:t,rpc:t,utils:{},initUtils:function(){this.utils.randomString=i,this.utils.secondsToTime=s,this.utils.parseISO8601=p,this.utils.escapeRegex=r,this.utils.formatIRCMsg=l,this.utils.styleText=u,this.utils.hsl2rgb=o,this.utils.notifications=m.utils.notifications,this.utils.formatDate=m.utils.formatDate},addMediaMessageType:function(e,t){m.view.MediaMessage.addType(e,t)},components:{EventComponent:function(e,t){function n(e,n){"all"==t||(n=e.event_data,e=e.event_name),this.trigger(e,n)}t=t||"all",_.extend(this,Backbone.Events),this._source=e,e.on(t,n,this),this.dispose=function(){e.off(t,n),this.off(),delete this.event_source}},Network:function(e){var n;n="undefined"!=typeof e?"connection:"+e.toString():"connection";var i=function(){var n="undefined"==typeof e?m.app.connections.active_connection:m.app.connections.getByConnectionId(e);return n?n:t},s=new this.EventComponent(m.gateway,n),a={kiwi:"kiwi",raw:"raw",kick:"kick",topic:"topic",part:"part",join:"join",action:"action",ctcp:"ctcp",ctcpRequest:"ctcpRequest",ctcpResponse:"ctcpResponse",notice:"notice",msg:"privmsg",say:"privmsg",changeNick:"changeNick",channelInfo:"channelInfo",mode:"mode",quit:"quit"};return _.each(a,function(t,n){s[n]=function(){var n=t,i=Array.prototype.slice.call(arguments,0);return i.unshift(e),m.gateway[n].apply(m.gateway,i)}}),s.createQuery=function(e){var t;return(t=i())?t.createQuery(e):void 0},s.get=function(e){var n,s;return(n=i())?(s=["password"],s.indexOf(e)>-1?t:n.get(e)):void 0},s.set=function(){var e=i();if(e)return e.set.apply(e,arguments)},s},ControlInput:function(){var e=new this.EventComponent(m.app.controlbox),t={run:"processInput",addPluginIcon:"addPluginIcon"};return _.each(t,function(t,n){e[n]=function(){var e=t;return m.app.controlbox[e].apply(m.app.controlbox,arguments)}}),e.input=m.app.controlbox.$(".inp"),e}},init:function(e,t){var i,s,a=this;e=e||{},this.initUtils(),m.global.settings=m.model.DataStore.instance("kiwi.settings"),m.global.settings.load(),window.document.title=e.server_settings.client.window_title||"Kiwi IRC",i=new Promise(function(t){var n=m.global.settings.get("locale")||"magic";$.getJSON(e.base_path+"/assets/locales/"+n+".json",function(e){a.i18n=e?new Jed(e):new Jed,t()})}),s=new Promise(function(t){var n=e.server_settings.client.settings.text_theme||"default";$.getJSON(e.base_path+"/assets/text_themes/"+n+".json",function(n){e.text_theme=n,t()})}),Promise.all([i,s]).then(function(){m.app=new m.model.Application(e),m.app.initializeInterfaces(),m.global.events=new n,m.global.plugins=new m.model.PluginManager,t()}).then(null,function(e){console.error(e.stack)})},start:function(){m.app.showStartup()},registerStartupApplet:function(e){m.app.startup_applet_name=e},newIrcConnection:function(e,t){m.gateway.newConnection(e,t)},defaultServerSettings:function(){var e,t,n={nick:"",server:"",port:6667,ssl:!1,channel:"",channel_key:""};return m.app.server_settings.client&&(m.app.server_settings.client.nick&&(n.nick=m.app.server_settings.client.nick),m.app.server_settings.client.server&&(n.server=m.app.server_settings.client.server),m.app.server_settings.client.port&&(n.port=m.app.server_settings.client.port),m.app.server_settings.client.ssl&&(n.ssl=m.app.server_settings.client.ssl),m.app.server_settings.client.channel&&(n.channel=m.app.server_settings.client.channel),m.app.server_settings.client.channel_key&&(n.channel_key=m.app.server_settings.client.channel_key)),getQueryVariable("nick")&&(n.nick=getQueryVariable("nick")),window.location.hash&&(n.channel=window.location.hash),e=window.location.pathname.toString().replace(m.app.get("base_path"),"").split("/"),e.length>0&&(e.shift(),e.length>0&&e[0]&&(t=e[0].substr(0,7).toLowerCase(),"ircs%3a"===t||"irc%3a"===t.substr(0,6)?(e[0]=decodeURIComponent(e[0]),t=/^irc(s)?:(?:\/\/?)?([^:\/]+)(?::([0-9]+))?(?:(?:\/)([^\?]*)(?:(?:\?)(.*))?)?$/.exec(e[0]),t&&("undefined"!=typeof t[1]&&(n.ssl=!0,6667===n.port&&(n.port=6697)),n.server=t[2],"undefined"!=typeof t[3]&&(n.port=t[3]),"undefined"!=typeof t[4]&&(n.channel="#"+t[4],"undefined"!=typeof t[5]&&(n.channel_key=t[5]))),e=[]):(e[0].search(/:/)>0?(n.port=e[0].substring(e[0].search(/:/)+1),n.server=e[0].substring(0,e[0].search(/:/)),"+"===n.port[0]?(n.port=parseInt(n.port.substring(1),10),n.ssl=!0):n.ssl=!1):n.server=e[0],e.shift())),e.length>0&&e[0]&&(n.channel="#"+e[0],e.shift())),m.app.server_settings&&m.app.server_settings.connection&&(m.app.server_settings.connection.server&&(n.server=m.app.server_settings.connection.server),m.app.server_settings.connection.port&&(n.port=m.app.server_settings.connection.port),m.app.server_settings.connection.ssl&&(n.ssl=m.app.server_settings.connection.ssl),m.app.server_settings.connection.channel&&(n.channel=m.app.server_settings.connection.channel),m.app.server_settings.connection.channel_key&&(n.channel_key=m.app.server_settings.connection.channel_key),m.app.server_settings.connection.nick&&(n.nick=m.app.server_settings.connection.nick)),n.nick=n.nick.replace("?",Math.floor(1e5*Math.random()).toString()),getQueryVariable("encoding")&&(n.encoding=getQueryVariable("encoding")),n}},"undefined"!=typeof e)e.kiwi=m.global;else var g=m.global;!function(){m.model.Application=Backbone.Model.extend({view:null,message:null,initialize:function(e){this.app_options=e,e.container&&this.set("container",e.container),this.set("base_path",e.base_path?e.base_path:""),this.set("settings_path",e.settings_path?e.settings_path:this.get("base_path")+"/assets/settings.json"),this.server_settings=e.server_settings||{},this.translations=e.translations||{},this.themes=e.themes||[],this.text_theme=e.text_theme||{},this.startup_applet_name=e.startup||"kiwi_startup",this.server_settings&&this.server_settings.client&&this.server_settings.client.settings&&this.applyDefaultClientSettings(this.server_settings.client.settings)},initializeInterfaces:function(){var e=this.app_options.kiwi_server||this.detectKiwiServer();m.gateway=new m.model.Gateway({kiwi_server:e}),this.bindGatewayCommands(m.gateway),this.initializeClient(),this.initializeGlobals(),this.view.barsHide(!0)},detectKiwiServer:function(){return"file:"===window.location.protocol?"http://localhost:7778":window.location.protocol+"//"+window.location.host},showStartup:function(){this.startup_applet=m.model.Applet.load(this.startup_applet_name,{no_tab:!0}),this.startup_applet.tab=this.view.$(".console"),this.startup_applet.view.show(),m.global.events.emit("loaded")},initializeClient:function(){this.view=new m.view.Application({model:this,el:this.get("container")}),this.connections=new m.model.NetworkPanelList,this.connections.on("remove",_.bind(function(){0===this.connections.length&&this.view.barsHide()},this)),this.applet_panels=new m.model.PanelList,this.applet_panels.view.$el.addClass("panellist applets"),this.view.$el.find(".tabs").append(this.applet_panels.view.$el),this.controlbox=new m.view.ControlBox({el:$("#kiwi .controlbox")[0]}).render(),this.client_ui_commands=new m.misc.ClientUiCommands(this,this.controlbox),this.rightbar=new m.view.RightBar({el:this.view.$(".right_bar")[0]}),this.topicbar=new m.view.TopicBar({el:this.view.$el.find(".topic")[0]}),new m.view.AppToolbar({el:m.app.view.$el.find(".toolbar .app_tools")[0]}),new m.view.ChannelTools({el:m.app.view.$el.find(".channel_tools")[0]}),this.message=new m.view.StatusMessage({el:this.view.$el.find(".status_message")[0]}),this.resize_handle=new m.view.ResizeHandler({el:this.view.$el.find(".memberlists_resize_handle")[0]}),this.view.doLayout()},initializeGlobals:function(){m.global.connections=this.connections,m.global.panels=this.panels,m.global.panels.applets=this.applet_panels,m.global.components.Applet=m.model.Applet,m.global.components.Panel=m.model.Panel,m.global.components.MenuBox=m.view.MenuBox,m.global.components.DataStore=m.model.DataStore,m.global.components.Notification=m.view.Notification,m.global.components.Events=function(){return g.events.createProxy()}},applyDefaultClientSettings:function(e){_.each(e,function(e,t){"undefined"==typeof m.global.settings.get(t)&&m.global.settings.set(t,e)})},panels:function(){var e,t=function(t){var n,i=m.app;switch(t=t||"connections"){case"connections":n=i.connections.panels();break;case"applets":n=i.applet_panels.models}return n.active=e,n.server=i.connections.active_connection?i.connections.active_connection.panels.server:null,n};return _.extend(t,Backbone.Events),t.bind("active",function(t){var n=e;e=t,m.global.events.emit("panel:active",{previous:n,active:e})}),t}(),bindGatewayCommands:function(e){var t=this;e.on("connection:connect",function(){t.view.barsShow()}),function(){var n=0;e.on("disconnect",function(){t.view.$el.removeClass("connected"),n=1}),e.on("reconnecting",function(e){var t=d("client_models_application_reconnect_in_x_seconds",[e.delay/1e3])+"...";m.app.connections.forEach(function(e){e.panels.server.addMsg("",u("quit",{text:t}),"action quit")})}),e.on("kiwi:connected",function(){var e;t.view.$el.addClass("connected"),m.global.rpc=m.gateway.rpc,m.global.events.emit("connected"),1===n&&(n=0,e=d("client_models_application_reconnect_successfully")+" :)",t.message.text(e,{timeout:5e3}),m.app.connections.forEach(function(t){t.reconnect(),t.panels.server.addMsg("",u("rejoin",{text:e}),"action join"),t.panels.forEach(function(t){t.isChannel()&&t.addMsg("",u("rejoin",{text:e}),"action join")})}))})}(),e.on("kiwi:reconfig",function(){$.getJSON(t.get("settings_path"),function(e){t.server_settings=e.server_settings||{},t.translations=e.translations||{}})}),e.on("kiwi:jumpserver",function(e){var n;if("undefined"!=typeof e.kiwi_server&&(n=e.kiwi_server,"/"===n[n.length-1]&&(n=n.substring(0,n.length-1)),e.force)){var i=60*Math.random()+300;i=1;var s=m.global.i18n.translate("client_models_application_jumpserver_prepare").fetch();t.message.text(s,{timeout:1e4}),setTimeout(function(){var e=m.global.i18n.translate("client_models_application_jumpserver_reconnect").fetch();t.message.text(e,{timeout:8e3}),setTimeout(function(){m.gateway.set("kiwi_server",n),m.gateway.reconnect(function(){t.connections.forEach(function(e){e.reconnect()})})},5e3)},1e3*i)}})}})}(),m.model.Gateway=Backbone.Model.extend({initialize:function(){this.socket=this.get("socket"),this.disconnect_requested=!1},reconnect:function(e){this.disconnect_requested=!0,this.socket.close(),this.socket=null,this.connect(e)},connect:function(e){var t=this;this.connect_callback=e,this.socket=new EngineioTools.ReconnectingSocket(this.get("kiwi_server"),{transports:m.app.server_settings.transports||["polling","websocket"],path:m.app.get("base_path")+"/transport",reconnect_max_attempts:5,reconnect_delay:2e3}),this.rpc&&rpc.dispose(),this.rpc=new EngineioTools.Rpc(this.socket),this.socket.on("connect_failed",function(e){this.socket.disconnect(),this.trigger("connect_fail",{reason:e})}),this.socket.on("error",function(e){console.log("_kiwi.gateway.socket.on('error')",{reason:e}),t.connect_callback&&(t.connect_callback(e),delete t.connect_callback),t.trigger("connect_fail",{reason:e})}),this.socket.on("connecting",function(){console.log("_kiwi.gateway.socket.on('connecting')"),t.trigger("connecting")}),this.socket.on("open",function(){t.disconnect_requested=!1;var e=function(){t.rpc&&(t.rpc("kiwi.heartbeat"),t._heartbeat_tmr=setTimeout(e,6e4))};e(),console.log("_kiwi.gateway.socket.on('open')")}),this.rpc.on("too_many_connections",function(){t.trigger("connect_fail",{reason:"too_many_connections"})}),this.rpc.on("irc",function(e,n){t.parse(n.command,n.data)}),this.rpc.on("kiwi",function(e,n){t.parseKiwi(n.command,n.data)}),this.socket.on("close",function(){t.trigger("disconnect",{}),console.log("_kiwi.gateway.socket.on('close')")}),this.socket.on("reconnecting",function(e){console.log("_kiwi.gateway.socket.on('reconnecting')"),t.trigger("reconnecting",{delay:e.delay,attempts:e.attempts})}),this.socket.on("reconnecting_failed",function(){console.log("_kiwi.gateway.socket.on('reconnect_failed')")})},newConnection:function(e,t){var n=this;return this.isConnected()?void this.makeIrcConnection(e,function(n,i){var s;if(n)console.log("_kiwi.gateway.socket.on('error')",{reason:n}),t&&t(n);else{if(!m.app.connections.getByConnectionId(i)){var a={connection_id:i,nick:e.nick,address:e.host,port:e.port,ssl:e.ssl,password:e.password};s=new m.model.Network(a),m.app.connections.add(s)}console.log("_kiwi.gateway.socket.on('connect')",s),t&&t(n,s)}}):void this.connect(function(i){return i?void t(i):void n.newConnection(e,t)})},makeIrcConnection:function(e,t){var n={nick:e.nick,hostname:e.host,port:e.port,ssl:e.ssl,password:e.password};e.options=e.options||{},e.options.encoding&&(n.encoding=e.options.encoding),this.rpc("kiwi.connect_irc",n,function(e,n){e?t&&t(e):t&&t(e,n)})},isConnected:function(){return this.socket},parseKiwi:function(e,t){var n;switch(e){case"connected":n={build_version:m.global.build_version},this.rpc("kiwi.client_info",n),this.connect_callback&&this.connect_callback(),delete this.connect_callback}this.trigger("kiwi:"+e,t),this.trigger("kiwi",t)},parse:function(e,t){var n="";"undefined"!=typeof t.connection_id&&(n="connection:"+t.connection_id.toString(),this.trigger(n,{event_name:e,event_data:t}),"message"==e&&t.type&&this.trigger("connection "+n,{event_name:"message:"+t.type,event_data:t}),"channel"==e&&t.type&&this.trigger("connection "+n,{event_name:"channel:"+t.type,event_data:t})),this.trigger("connection",{event_name:e,event_data:t}),this.trigger("connection:"+e,t)},rpcCall:function(){var e=Array.prototype.slice.call(arguments,0);return("undefined"==typeof e[1]||null===e[1])&&(e[1]=m.app.connections.active_connection.get("connection_id")),this.rpc.apply(this.rpc,e)},privmsg:function(e,t,n,i){var s={target:t,msg:n};this.rpcCall("irc.privmsg",e,s,i)},notice:function(e,t,n,i){var s={target:t,msg:n};this.rpcCall("irc.notice",e,s,i)},ctcp:function(e,t,n,i,s,a){var o={is_request:t,type:n,target:i,params:s};this.rpcCall("irc.ctcp",e,o,a)},ctcpRequest:function(e,t,n,i,s){this.ctcp(e,!0,t,n,i,s)},ctcpResponse:function(e,t,n,i,s){this.ctcp(e,!1,t,n,i,s)},action:function(e,t,n,i){this.ctcp(e,!0,"ACTION",t,n,i)},join:function(e,t,n,i){var s={channel:t,key:n};this.rpcCall("irc.join",e,s,i)},channelInfo:function(e,t,n){var i={channel:t};this.rpcCall("irc.channel_info",e,i,n)},part:function(e,n,i,s){"use strict";"function"==typeof arguments[2]&&(s=arguments[2],i=t);var a={channel:n,message:i};this.rpcCall("irc.part",e,a,s)},topic:function(e,t,n,i){var s={channel:t,topic:n};this.rpcCall("irc.topic",e,s,i)},kick:function(e,t,n,i,s){var a={channel:t,nick:n,reason:i};this.rpcCall("irc.kick",e,a,s)},quit:function(e,t,n){t=t||"";var i={message:t};this.rpcCall("irc.quit",e,i,n)},raw:function(e,t,n){var i={data:t};this.rpcCall("irc.raw",e,i,n)},changeNick:function(e,t,n){var i={nick:t};this.rpcCall("irc.nick",e,i,n)},mode:function(e,t,n,i){var s={data:"MODE "+t+" "+n};this.rpcCall("irc.raw",e,s,i)},setEncoding:function(e,t,n){var i={encoding:t};this.rpcCall("irc.encoding",e,i,n)}}),function(){function e(){this.set("connected",!1),$.each(this.panels.models,function(e,t){t.isApplet()||t.addMsg("",u("network_disconnected",{text:d("client_models_network_disconnected",[])}),"action quit")})}function n(e){var t;this.set("nick",e.nick),this.set("connected",!0),this.rejoinAllChannels(),this.auto_join&&this.auto_join.channel&&(t=this.createAndJoinChannels(this.auto_join.channel+" "+(this.auto_join.key||"")),t&&t[t.length-1].view.show(),delete this.auto_join)}function i(e){var t=this;$.each(e.options,function(e,n){switch(e){case"CHANTYPES":t.set("channel_prefix",n.join(""));break;case"NETWORK":t.set("name",n);break;case"PREFIX":t.set("user_prefixes",n)}}),this.set("cap",e.cap)}function a(e){this.panels.server.addMsg(this.get("name"),u("motd",{text:e.msg}),"motd")}function o(e){var t,n,i;t=this.panels.getByName(e.channel),t||(t=new m.model.Channel({name:e.channel,network:this}),this.panels.add(t)),n=t.get("members"),n&&(n.getByNick(e.nick)||(i=new m.model.Member({nick:e.nick,ident:e.ident,hostname:e.hostname,user_prefixes:this.get("user_prefixes")}),m.global.events.emit("channel:join",{channel:e.channel,user:i,network:this.gateway}).then(function(){n.add(i,{kiwi:e})})))}function c(e){var t,n,i,s={};if(s.type="part",s.message=e.message||"",s.time=e.time,t=this.panels.getByName(e.channel)){if(e.nick===this.get("nick"))return void t.close();n=t.get("members"),n&&(i=n.getByNick(e.nick),i&&m.global.events.emit("channel:leave",{channel:e.channel,user:i,type:"part",message:s.message,network:this.gateway}).then(function(){n.remove(i,{kiwi:s})}))}}function l(e){var t,n={};n.type="quit",n.message=e.message||"",n.time=e.time,$.each(this.panels.models,function(i,s){s.isQuery()&&s.get("name").toLowerCase()===e.nick.toLowerCase()&&s.addMsg(" ",u("channel_quit",{nick:e.nick,text:d("client_models_channel_quit",[n.message])}),"action quit",{time:n.time}),s.isChannel()&&(t=s.get("members").getByNick(e.nick),t&&m.global.events.emit("channel:leave",{channel:s.get("name"),user:t,type:"quit",message:n.message,network:this.gateway}).then(function(){s.get("members").remove(t,{kiwi:n})}))})}function r(e){var t,n,i,s={};s.type="kick",s.by=e.nick,s.message=e.message||"",s.current_user_kicked=e.kicked==this.get("nick"),s.current_user_initiated=e.nick==this.get("nick"),s.time=e.time,t=this.panels.getByName(e.channel),t&&(n=t.get("members"),n&&(i=n.getByNick(e.kicked),i&&m.global.events.emit("channel:leave",{channel:e.channel,user:i,type:"kick",message:s.message,network:this.gateway}).then(function(){n.remove(i,{kiwi:s}),s.current_user_kicked&&n.reset([])})))}function h(e){m.global.events.emit("message:new",{network:this.gateway,message:e}).then(_.bind(function(){var t,n=(e.target||"").toLowerCase()==this.get("nick").toLowerCase();if(!this.isNickIgnored(e.nick))switch("notice"==e.type?(e.from_server?t=this.panels.server:(t=this.panels.getByName(e.target)||this.panels.getByName(e.nick),e.nick&&"chanserv"==e.nick.toLowerCase()&&"["==e.msg.charAt(0)&&(channel_name=/\[([^ \]]+)\]/gi.exec(e.msg),channel_name&&channel_name[1]&&(channel_name=channel_name[1],t=this.panels.getByName(channel_name)))),t||(t=this.panels.server)):n?(t=this.panels.getByName(e.nick),t||(t=new m.model.Query({name:e.nick,network:this}),this.panels.add(t))):(t=this.panels.getByName(e.target),t||(t=this.panels.server)),e.type){case"message":t.addMsg(e.nick,u("privmsg",{text:e.msg}),"privmsg",{time:e.time});break;case"action":t.addMsg("",u("action",{nick:e.nick,text:e.msg}),"action",{time:e.time});break;case"notice":t.addMsg("["+(e.nick||"")+"]",u("notice",{text:e.msg}),"notice",{time:e.time}),active_panel=m.app.panels().active,e.from_server||t!==this.panels.server||active_panel===this.panels.server||active_panel.get("network")===this&&(active_panel.isChannel()||active_panel.isQuery())&&active_panel.addMsg("["+(e.nick||"")+"]",u("notice",{text:e.msg}),"notice",{time:e.time})}},this))}function p(e){var t;$.each(this.panels.models,function(n,i){i.get("name")==e.nick&&i.set("name",e.newnick),i.isChannel()&&(t=i.get("members").getByNick(e.nick),t&&(t.set("nick",e.newnick),i.addMsg("",u("nick_changed",{nick:e.nick,text:d("client_models_network_nickname_changed",[e.newnick]),channel:name}),"action nick",{time:e.time})))})}function g(e){this.isNickIgnored(e.nick)||("TIME"===e.msg.toUpperCase()?this.gateway.ctcpResponse(e.type,e.nick,(new Date).toString()):"PING"===e.type.toUpperCase()&&this.gateway.ctcpResponse(e.type,e.nick,e.msg.substr(5)))}function f(e){this.isNickIgnored(e.nick)||this.panels.server.addMsg("["+e.nick+"]",u("ctcp",{text:e.msg}),"ctcp",{time:e.time})}function v(e){var t;t=this.panels.getByName(e.channel),t&&(t.set("topic",e.topic),t.get("name")===this.panels.active.get("name")&&m.app.topicbar.setCurrentTopic(e.topic))}function w(e){var t,n;t=this.panels.getByName(e.channel),t&&(n=new Date(1e3*e.when),t.set("topic_set_by",{nick:e.nick,when:n}))}function k(e){var t=this.panels.getByName(e.channel);t&&(e.url?t.set("info_url",e.url):e.modes&&t.set("info_modes",e.modes))}function b(e){var t=this,n=this.panels.getByName(e.channel);n&&(n.temp_userlist=n.temp_userlist||[],_.each(e.users,function(e){var i=new m.model.Member({nick:e.nick,modes:e.modes,user_prefixes:t.get("user_prefixes")});n.temp_userlist.push(i)}))}function y(e){var t;t=this.panels.getByName(e.channel),t&&(t.get("members").reset(t.temp_userlist||[]),delete t.temp_userlist)}function C(e){var t=this.panels.getByName(e.channel);t&&t.set("banlist",e.bans||[])}function x(e){function t(t,n){var i,s={};return t||(t=e.modes,n=e.target),_.each(t,function(e){var t=e.param||n||"";s[t]||(s[t]={"+":"","-":""}),s[t][e.mode[0]]+=e.mode.substr(1)}),i=[],_.each(s,function(e,t){var n="";e["+"]&&(n+="+"+e["+"]),e["-"]&&(n+="-"+e["-"]),i.push(n+" "+t)}),i=i.join(", ")}var n,i,s,a,o,c,l=!1;if(n=this.panels.getByName(e.target)){for(s=this.get("user_prefixes"),c=function(t){return e.modes[i].mode[1]===t.mode},i=0;i<e.modes.length;i++){if(_.any(s,c)){if(a||(a=n.get("members")),o=a.getByNick(e.modes[i].param),!o)return void console.log("MODE command recieved for unknown member %s on channel %s",e.modes[i].param,e.target);"+"===e.modes[i].mode[0]?o.addMode(e.modes[i].mode[1]):"-"===e.modes[i].mode[0]&&o.removeMode(e.modes[i].mode[1]),a.sort()}"b"==e.modes[i].mode[1]&&(l=!0)}n.addMsg("",u("mode",{nick:e.nick,text:d("client_models_network_mode",[t()]),channel:e.target}),"action mode",{time:e.time}),l&&this.gateway.raw("MODE "+n.get("name")+" +b")}else e.target.toLowerCase()===this.get("nick").toLowerCase()?this.panels.server.addMsg("",u("selfmode",{nick:e.nick,text:d("client_models_network_mode",[t()]),channel:e.target}),"action mode"):console.log("MODE command recieved for unknown target %s: ",e.target,e)}function M(e){var t,n,i="";e.end||("undefined"!=typeof e.idle&&(i=s(parseInt(e.idle,10)),i=i.h.toString().lpad(2,"0")+":"+i.m.toString().lpad(2,"0")+":"+i.s.toString().lpad(2,"0")),n=m.app.panels().active,e.ident?n.addMsg(e.nick,u("whois_ident",{nick:e.nick,ident:e.ident,host:e.hostname,text:e.msg}),"whois"):e.chans?n.addMsg(e.nick,u("whois_channels",{nick:e.nick,text:d("client_models_network_channels",[e.chans])}),"whois"):e.irc_server?n.addMsg(e.nick,u("whois_server",{nick:e.nick,text:d("client_models_network_server",[e.irc_server,e.server_info])}),"whois"):e.msg?n.addMsg(e.nick,u("whois",{text:e.msg}),"whois"):e.logon?(t=new Date,t.setTime(1e3*e.logon),t=m.utils.formatDate(t),n.addMsg(e.nick,u("whois_idle_and_signon",{nick:e.nick,text:d("client_models_network_idle_and_signon",[i,t])}),"whois")):e.away_reason?n.addMsg(e.nick,u("whois_away",{nick:e.nick,text:d("client_models_network_away",[e.away_reason])}),"whois"):n.addMsg(e.nick,u("whois_idle",{nick:e.nick,text:d("client_models_network_idle",[i])}),"whois"))}function S(e){var t;e.end||(t=m.app.panels().active,e.hostname?t.addMsg(e.nick,u("who",{nick:e.nick,ident:e.ident,host:e.hostname,realname:e.real_name,text:e.msg}),"whois"):t.addMsg(e.nick,u("whois_notfound",{nick:e.nick,text:d("client_models_network_nickname_notfound",[])}),"whois"))}function T(e){$.each(this.panels.models,function(t,n){n.isChannel()&&(member=n.get("members").getByNick(e.nick),member&&member.set("away",!!e.reason))})}function N(){var e=m.model.Applet.loadOnce("kiwi_chanlist");e.view.show()}function B(e){var n,i;switch(e.channel===t||(n=this.panels.getByName(e.channel))||(n=this.panels.server),e.error){case"banned_from_channel":n.addMsg(" ",u("channel_banned",{nick:e.nick,text:d("client_models_network_banned",[e.channel,e.reason]),channel:e.channel}),"status"),m.app.message.text(m.global.i18n.translate("client_models_network_banned").fetch(e.channel,e.reason));break;case"bad_channel_key":n.addMsg(" ",u("channel_badkey",{nick:e.nick,text:d("client_models_network_channel_badkey",[e.channel]),channel:e.channel}),"status"),m.app.message.text(m.global.i18n.translate("client_models_network_channel_badkey").fetch(e.channel));break;case"invite_only_channel":n.addMsg(" ",u("channel_inviteonly",{nick:e.nick,text:d("client_models_network_channel_inviteonly",[e.nick,e.channel]),channel:e.channel}),"status"),m.app.message.text(e.channel+" "+m.global.i18n.translate("client_models_network_channel_inviteonly").fetch());break;case"user_on_channel":n.addMsg(" ",u("channel_alreadyin",{nick:e.nick,text:d("client_models_network_channel_alreadyin"),channel:e.channel}));break;case"channel_is_full":n.addMsg(" ",u("channel_limitreached",{nick:e.nick,text:d("client_models_network_channel_limitreached",[e.channel]),channel:e.channel}),"status"),m.app.message.text(e.channel+" "+m.global.i18n.translate("client_models_network_channel_limitreached").fetch(e.channel));break;case"chanop_privs_needed":n.addMsg(" ",u("chanop_privs_needed",{text:e.reason,channel:e.channel}),"status"),m.app.message.text(e.reason+" ("+e.channel+")");break;case"cannot_send_to_channel":n.addMsg(" ","== "+m.global.i18n.translate("Cannot send message to channel, you are not voiced").fetch(e.channel,e.reason),"status");break;case"no_such_nick":i=this.panels.getByName(e.nick),i?i.addMsg(" ",u("no_such_nick",{nick:e.nick,text:e.reason,channel:e.channel}),"status"):this.panels.server.addMsg(" ",u("no_such_nick",{nick:e.nick,text:e.reason,channel:e.channel}),"status");break;case"nickname_in_use":this.panels.server.addMsg(" ",u("nickname_alreadyinuse",{nick:e.nick,text:d("client_models_network_nickname_alreadyinuse",[e.nick]),channel:e.channel}),"status"),this.panels.server!==this.panels.active&&m.app.message.text(m.global.i18n.translate("client_models_network_nickname_alreadyinuse").fetch(e.nick)),"none"!==m.app.controlbox.$el.css("display")&&(new m.view.NickChangeBox).render();break;case"password_mismatch":this.panels.server.addMsg(" ",u("channel_badpassword",{nick:e.nick,text:d("client_models_network_badpassword",[]),channel:e.channel}),"status");break;case"error":e.reason&&this.panels.server.addMsg(" ",u("general_error",{text:e.reason}),"status")}}function D(e){var t=_.clone(e.params);t[0]&&t[0]==this.get("nick")&&t.shift(),this.panels.server.addMsg("",u("unknown_command",{text:"["+e.command+"] "+t.join(", ","")}))}function I(e){var t=m.app.panels().active;this.panels.server.addMsg("["+(e.nick||"")+"]",u("wallops",{text:e.msg}),"wallops",{time:e.time}),t!==this.panels.server&&(t.isChannel()||t.isQuery())&&t.get("network")===this&&t.addMsg("["+(e.nick||"")+"]",u("wallops",{text:e.msg}),"wallops",{time:e.time})}m.model.Network=Backbone.Model.extend({defaults:{connection_id:0,name:"Network",address:"",port:6667,ssl:!1,password:"",nick:"",channel_prefix:"#",user_prefixes:[{symbol:"~",mode:"q"},{symbol:"&",mode:"a"},{symbol:"@",mode:"o"},{symbol:"%",mode:"h"},{symbol:"+",mode:"v"}],ignore_list:[]},initialize:function(){"undefined"!=typeof this.get("connection_id")&&(this.gateway=m.global.components.Network(this.get("connection_id")),this.bindGatewayEvents()),this.panels=new m.model.PanelList([],this);
+var e=new m.model.Server({name:"Server",network:this});this.panels.add(e),this.panels.server=this.panels.active=e},reconnect:function(e){var t=this,n={nick:this.get("nick"),host:this.get("address"),port:this.get("port"),ssl:this.get("ssl"),password:this.get("password")};m.gateway.makeIrcConnection(n,function(n,i){n?(console.log("_kiwi.gateway.socket.on('error')",{reason:n}),e&&e(n)):(t.gateway.dispose(),t.set("connection_id",i),t.gateway=m.global.components.Network(t.get("connection_id")),t.bindGatewayEvents(),t.panels.forEach(function(e){e.set("connection_id",i)}),e&&e(n))})},bindGatewayEvents:function(){this.gateway.on("connect",n,this),this.gateway.on("disconnect",e,this),this.gateway.on("nick",function(e){e.nick===this.get("nick")&&this.set("nick",e.newnick)},this),this.gateway.on("options",i,this),this.gateway.on("motd",a,this),this.gateway.on("channel:join",o,this),this.gateway.on("channel:part",c,this),this.gateway.on("channel:kick",r,this),this.gateway.on("quit",l,this),this.gateway.on("message",h,this),this.gateway.on("nick",p,this),this.gateway.on("ctcp_request",g,this),this.gateway.on("ctcp_response",f,this),this.gateway.on("topic",v,this),this.gateway.on("topicsetby",w,this),this.gateway.on("userlist",b,this),this.gateway.on("userlist_end",y,this),this.gateway.on("banlist",C,this),this.gateway.on("mode",x,this),this.gateway.on("whois",M,this),this.gateway.on("whowas",S,this),this.gateway.on("away",T,this),this.gateway.on("list_start",N,this),this.gateway.on("irc_error",B,this),this.gateway.on("unknown_command",D,this),this.gateway.on("channel_info",k,this),this.gateway.on("wallops",I,this)},createAndJoinChannels:function(e){var t=this,n=[];return"string"==typeof e&&(e=e.split(",")),$.each(e,function(e,i){var s=i.trim().split(" "),a=s[0],o=s[1]||"";a=a.trim(),-1===t.get("channel_prefix").indexOf(a[0])&&(a="#"+a),channel=t.panels.getByName(a),channel||(channel=new m.model.Channel({name:a,network:t}),t.panels.add(channel)),n.push(channel),t.gateway.join(a,o)}),n},rejoinAllChannels:function(){var e=this;this.panels.forEach(function(t){t.isChannel()&&e.gateway.join(t.get("name"))})},isChannelName:function(e){var t=this.get("channel_prefix");return e&&e.length?t.indexOf(e[0])>-1:!1},isNickIgnored:function(e){var t,n,i,s=this.get("ignore_list");for(t=0;t<s.length;t++)if(n=s[t].replace(/([.+^$[\]\\(){}|-])/g,"\\$1").replace("*",".*").replace("?","."),i=new RegExp(n,"i"),i.test(e))return!0;return!1},createQuery:function(e){var t,n=this;return t=n.panels.getByName(e),t||(t=new m.model.Query({name:e}),n.panels.add(t)),t.view.show(),t}})}(),m.model.Member=Backbone.Model.extend({initialize:function(){var e,t;e=this.stripPrefix(this.get("nick")),t=this.get("modes"),t=t||[],this.sortModes(t),this.set({nick:e,modes:t,prefix:this.getPrefix(t)},{silent:!0}),this.updateOpStatus(),this.view=new m.view.Member({model:this})},sortModes:function(e){var t=this;return e.sort(function(e,n){var i,s,a,o=t.get("user_prefixes");for(a=0;a<o.length;a++)o[a].mode===e&&(i=a);for(a=0;a<o.length;a++)o[a].mode===n&&(s=a);return s>i?-1:i>s?1:0})},addMode:function(e){var t,n=e.split("");t=this.get("modes"),$.each(n,function(e,n){t.push(n)}),t=this.sortModes(t),this.set({prefix:this.getPrefix(t),modes:t}),this.updateOpStatus(),this.view.render()},removeMode:function(e){var t,n=e.split("");t=this.get("modes"),t=_.reject(t,function(e){return-1!==_.indexOf(n,e)}),this.set({prefix:this.getPrefix(t),modes:t}),this.updateOpStatus(),this.view.render()},getPrefix:function(e){var t="",n=this.get("user_prefixes");return"undefined"!=typeof e[0]&&(t=_.detect(n,function(t){return t.mode===e[0]}),t=t?t.symbol:""),t},stripPrefix:function(e){var t,n,i,s,a=e,o=this.get("user_prefixes");t=0;e:for(n=0;n<e.length;n++){for(s=e.charAt(n),i=0;i<o.length;i++)if(s===o[i].symbol){t++;continue e}break}return a.substr(t)},displayNick:function(e){var t=this.get("nick");return e&&this.get("ident")&&(t+=" ["+this.get("ident")+"@"+this.get("hostname")+"]"),t},getMaskParts:function(){return{nick:this.get("nick")||"",ident:this.get("ident")||"",hostname:this.get("hostname")||""}},updateOpStatus:function(){var e,t,n=this.get("user_prefixes"),i=this.get("modes");i.length>0?(e=_.indexOf(n,_.find(n,function(e){return"o"===e.mode})),t=_.indexOf(n,_.find(n,function(e){return e.mode===i[0]})),-1===t||t>e?this.set({is_op:!1},{silent:!0}):this.set({is_op:!0},{silent:!0})):this.set({is_op:!1},{silent:!0})}}),m.model.MemberList=Backbone.Collection.extend({model:m.model.Member,comparator:function(e,t){var n,i,s,a,o,c,l,r=this.channel.get("network").get("user_prefixes");if(i=e.get("modes"),s=t.get("modes"),i.length>0){if(0===s.length)return-1;for(a=o=-1,n=0;n<r.length;n++)r[n].mode===i[0]&&(a=n);for(n=0;n<r.length;n++)r[n].mode===s[0]&&(o=n);if(o>a)return-1;if(a>o)return 1}else if(s.length>0)return 1;return c=e.get("nick").toLocaleUpperCase(),l=t.get("nick").toLocaleUpperCase(),l>c?-1:c>l?1:0},initialize:function(){this.view=new m.view.MemberList({model:this}),this.initNickCache()},initNickCache:function(){var e=this;this.nick_cache=Object.create(null),this.on("reset",function(){this.nick_cache=Object.create(null),this.models.forEach(function(t){e.nick_cache[t.get("nick").toLowerCase()]=t})}),this.on("add",function(t){e.nick_cache[t.get("nick").toLowerCase()]=t}),this.on("remove",function(t){delete e.nick_cache[t.get("nick").toLowerCase()]}),this.on("change:nick",function(t){e.nick_cache[t.get("nick").toLowerCase()]=t,delete e.nick_cache[t.previous("nick").toLowerCase()]})},getByNick:function(e){return"string"==typeof e?this.nick_cache[e.toLowerCase()]:void 0}}),m.model.NewConnection=Backbone.Collection.extend({initialize:function(){this.view=new m.view.ServerSelect({model:this}),this.view.bind("server_connect",this.onMakeConnection,this)},populateDefaultServerSettings:function(){var e=m.global.defaultServerSettings();this.view.populateFields(e)},onMakeConnection:function(e){var t=this;this.connect_details=e,this.view.networkConnecting(),m.gateway.newConnection({nick:e.nick,host:e.server,port:e.port,ssl:e.ssl,password:e.password,options:e.options},function(e,n){t.onNewNetwork(e,n)})},onNewNetwork:function(e,t){e&&this.view.showError(e),t&&this.connect_details&&(t.auto_join={channel:this.connect_details.channel,key:this.connect_details.channel_key},this.trigger("new_network",t))}}),m.model.Panel=Backbone.Model.extend({initialize:function(){var e=this.get("name")||"";this.view=new m.view.Panel({model:this,name:e}),this.set({scrollback:[],name:e},{silent:!0}),m.global.events.emit("panel:created",{panel:this})},close:function(){m.app.panels.trigger("close",this),m.global.events.emit("panel:close",{panel:this}),this.view&&(this.view.unbind(),this.view.remove(),this.view=t,delete this.view);var e=this.get("members");e&&(e.reset([]),this.unset("members")),this.get("panel_list").remove(this),this.unbind(),this.destroy()},isChannel:function(){return!1},isQuery:function(){return!1},isApplet:function(){return!1},isServer:function(){return!1},isActive:function(){return m.app.panels().active===this}}),m.model.PanelList=Backbone.Collection.extend({model:m.model.Panel,comparator:function(e){return e.get("name")},initialize:function(e,t){t&&(this.network=t),this.view=new m.view.Tabs({model:this}),this.active=null,this.bind("active",function(e){this.active=e},this),this.bind("add",function(e){e.set("panel_list",this)})},getByCid:function(e){return"string"==typeof name?this.find(function(t){return e===t.cid}):void 0},getByName:function(e){return"string"==typeof e?this.find(function(t){return e.toLowerCase()===t.get("name").toLowerCase()}):void 0}}),m.model.NetworkPanelList=Backbone.Collection.extend({model:m.model.Network,initialize:function(){this.view=new m.view.NetworkTabs({model:this}),this.on("add",this.onNetworkAdd,this),this.on("remove",this.onNetworkRemove,this),this.active_connection=t,this.active_panel=t,this.active=t},getByConnectionId:function(e){return this.find(function(t){return t.get("connection_id")==e})},panels:function(){var e=[];return this.each(function(t){e=e.concat(t.panels.models)}),e},onNetworkAdd:function(e){e.panels.on("active",this.onPanelActive,this),1===this.models.length&&(this.active_connection=e,this.active_panel=e.panels.server,this.active=this.active_panel)},onNetworkRemove:function(e){e.panels.off("active",this.onPanelActive,this)},onPanelActive:function(e){var t=this.getByConnectionId(e.tab.data("connection_id"));this.trigger("active",e,t),this.active_connection=t,this.active_panel=e,this.active=e}}),m.model.Channel=m.model.Panel.extend({initialize:function(){var e,t=this.get("name")||"";this.set({members:new m.model.MemberList,name:t,scrollback:[],topic:""},{silent:!0}),this.view=new m.view.Channel({model:this,name:t}),e=this.get("members"),e.channel=this,e.bind("add",function(e,n,i){var s=m.global.settings.get("show_joins_parts");s!==!1&&this.addMsg(" ",u("channel_join",{member:e.getMaskParts(),text:d("client_models_channel_join"),channel:t}),"action join",{time:i.kiwi.time})},this),e.bind("remove",function(e,n,i){var s=m.global.settings.get("show_joins_parts"),a=i.kiwi.message?"("+i.kiwi.message+")":"";"quit"===i.kiwi.type&&s?this.addMsg(" ",u("channel_quit",{member:e.getMaskParts(),text:d("client_models_channel_quit",[a]),channel:t}),"action quit",{time:i.kiwi.time}):"kick"===i.kiwi.type?i.kiwi.current_user_kicked?this.addMsg(" ",u("channel_selfkick",{text:d("client_models_channel_selfkick",[i.kiwi.by,a]),channel:t}),"action kick",{time:i.kiwi.time}):(s||i.kiwi.current_user_initiated)&&this.addMsg(" ",u("channel_kicked",{member:e.getMaskParts(),text:d("client_models_channel_kicked",[i.kiwi.by,a]),channel:t}),"action kick",{time:i.kiwi.time}):s&&this.addMsg(" ",u("channel_part",{member:e.getMaskParts(),text:d("client_models_channel_part",[a]),channel:t}),"action part",{time:i.kiwi.time})},this),m.global.events.emit("panel:created",{panel:this})},addMsg:function(e,t,n,i){var s,a,o,c,l=parseInt(m.global.settings.get("scrollback"),10)||250;i=i||{},i.time="number"==typeof i.time?new Date(i.time):new Date,i&&"undefined"!=typeof i.style||(i.style=""),s={msg:t,date:i.date,time:i.time,nick:e,chan:this.get("name"),type:n,style:i.style},o=this.get("members"),o&&(c=o.getByNick(s.nick),c&&(s.nick_prefix=c.get("prefix"))),"string"!=typeof s.type&&(s.type=""),"string"!=typeof s.msg&&(s.msg=""),a=this.get("scrollback"),a&&(a.push(s),a.length>l&&(a=_.last(a,l)),this.set({scrollback:a},{silent:!0})),this.trigger("msg",s)},clearMessages:function(){this.set({scrollback:[]},{silent:!0}),this.addMsg("","Window cleared"),this.view.render()},setMode:function(e){this.get("network").gateway.mode(this.get("name"),e)},isChannel:function(){return!0}}),m.model.Query=m.model.Channel.extend({initialize:function(){var e=this.get("name")||"";this.view=new m.view.Channel({model:this,name:e}),this.set({name:e,scrollback:[]},{silent:!0}),m.global.events.emit("panel:created",{panel:this})},isChannel:function(){return!1},isQuery:function(){return!0}}),m.model.Server=m.model.Channel.extend({initialize:function(){var e="Server";this.view=new m.view.Channel({model:this,name:e}),this.set({scrollback:[],name:e},{silent:!0}),m.global.events.emit("panel:created",{panel:this})},isServer:function(){return!0},isChannel:function(){return!1}}),m.model.Applet=m.model.Panel.extend({initialize:function(){var e="applet_"+(new Date).getTime().toString()+Math.ceil(100*Math.random()).toString();this.view=new m.view.Applet({model:this,name:e}),this.set({name:e},{silent:!0}),this.loaded_applet=null},load:function(e,t){return"object"==typeof e?(e.get||e.extend)&&(this.set("title",e.get("title")||m.global.i18n.translate("client_models_applet_unknown").fetch()),e.bind("change:title",function(e,t){this.set("title",t)},this),this.view.$el.html(""),e.view&&this.view.$el.append(e.view.$el),this.loaded_applet=e,this.loaded_applet.trigger("applet_loaded")):"string"==typeof e&&this.loadFromUrl(e,t),this},loadFromUrl:function(e,t){var n=this;this.view.$el.html(m.global.i18n.translate("client_models_applet_loading").fetch()),$script(e,function(){return m.applets[t]?void n.load(new m.applets[t]):void n.view.$el.html(m.global.i18n.translate("client_models_applet_notfound").fetch())})},close:function(){this.view.$el.remove(),this.destroy(),this.view=t,this.loaded_applet&&this.loaded_applet.dispose&&this.loaded_applet.dispose(),this.constructor.__super__.close.apply(this,arguments)},isApplet:function(){return!0}},{loadOnce:function(e){var t=_.find(m.app.panels("applets"),function(t){return t.isApplet()&&t.loaded_applet?t.loaded_applet.get("_applet_name")===e?!0:void 0:void 0});return t?t:this.load(e)},load:function(e,t){var n,i;return t=t||{},(i=this.getApplet(e))?(n=new m.model.Applet,n.load(new i({_applet_name:e})),t.no_tab||m.app.applet_panels.add(n),n):void 0},getApplet:function(e){return m.applets[e]||null},register:function(e,t){m.applets[e]=t}}),m.model.PluginManager=Backbone.Model.extend({initialize:function(){this.$plugin_holder=$('<div id="kiwi_plugins" style="display:none;"></div>').appendTo(m.app.view.$el),this.loading_plugins=0,this.loaded_plugins={}},load:function(e){var t=this;this.loaded_plugins[e]&&this.unload(e),this.loading_plugins++,this.loaded_plugins[e]=$("<div></div>"),this.loaded_plugins[e].appendTo(this.$plugin_holder).load(e,_.bind(t.pluginLoaded,t))},unload:function(e){this.loaded_plugins[e]&&(this.loaded_plugins[e].remove(),delete this.loaded_plugins[e])},pluginLoaded:function(){this.loading_plugins--,0===this.loading_plugins&&this.trigger("loaded")}}),m.model.DataStore=Backbone.Model.extend({initialize:function(){this._namespace="",this.new_data={}},namespace:function(e){return e&&(this._namespace=e),this._namespace},save:function(){localStorage.setItem(this._namespace,JSON.stringify(this.attributes))},load:function(){if(localStorage){var e;try{e=JSON.parse(localStorage.getItem(this._namespace))||{}}catch(t){e={}}this.attributes=e}}},{instance:function(e,t){var n=new m.model.DataStore(t);return n.namespace(e),n}}),m.model.ChannelInfo=Backbone.Model.extend({initialize:function(){this.view=new m.view.ChannelInfo({model:this})}}),m.view.Panel=Backbone.View.extend({tagName:"div",className:"panel",events:{},initialize:function(e){this.initializePanel(e)},initializePanel:function(e){this.$el.css("display","none"),e=e||{},this.$container=$(e.container?e.container:"#kiwi .panels .container1"),this.$el.appendTo(this.$container),this.alert_level=0,this.model.set({view:this},{silent:!0}),this.listenTo(this.model,"change:activity_counter",function(e,t){var n=this.model.tab.find(".activity");n.text(t>999?"999+":t),0===t?n.addClass("zero"):n.removeClass("zero")})},render:function(){},show:function(){var e=this.$el;this.$container.children(".panel").css("display","none"),e.css("display","block");var t=this.model.get("members");t?(m.app.rightbar.show(),t.view.show()):m.app.rightbar.hide(),this.alert("none"),this.model.set("activity_counter",0),m.app.panels.trigger("active",this.model,m.app.panels().active),this.model.trigger("active",this.model),m.app.view.doLayout(),this.model.isApplet()||this.scrollToBottom(!0)},alert:function(e){if(this.model!=m.app.panels().active){var t,n;t=["none","action","activity","highlight"],e=e||"none",n=_.indexOf(t,e),n||(e="none",n=0),0!==n&&n<=this.alert_level||(this.model.tab.removeClass(function(e,t){return(t.match(/\balert_\S+/g)||[]).join(" ")}),"none"!==e&&this.model.tab.addClass("alert_"+e),this.alert_level=n)}},scrollToBottom:function(e){this.model===m.app.panels().active&&(e||this.$container.scrollTop()+this.$container.height()>this.$el.outerHeight()-150)&&(this.$container[0].scrollTop=this.$container[0].scrollHeight)}}),m.view.Channel=m.view.Panel.extend({events:function(){var e=this.constructor.__super__.events;return _.isFunction(e)&&(e=e()),_.extend({},e,{"click .msg .nick":"nickClick","click .msg .inline-nick":"nickClick","click .chan":"chanClick","click .media .open":"mediaClick","mouseenter .msg .nick":"msgEnter","mouseleave .msg .nick":"msgLeave"})},initialize:function(e){this.initializePanel(e),this.$messages=$('<div class="messages"></div>'),this.$el.append(this.$messages),this.model.bind("change:topic",this.topic,this),this.model.bind("change:topic_set_by",this.topicSetBy,this),this.model.get("members")&&(this.model.get("members").bind("add",function(e){e.get("nick")===this.model.collection.network.get("nick")&&this.$el.find(".initial_loader").slideUp(function(){$(this).remove()})},this),this.model.get("members").bind("reset",function(e){e.getByNick(this.model.collection.network.get("nick"))&&this.$el.find(".initial_loader").slideUp(function(){$(this).remove()})},this)),this.model.isChannel()&&this.$el.append('<div class="initial_loader" style="margin:1em;text-align:center;"> '+m.global.i18n.translate("client_views_channel_joining").fetch()+' <span class="loader"></span></div>'),this.model.bind("msg",this.newMsg,this),this.msg_count=0},render:function(){var e=this;this.$messages.empty(),_.each(this.model.get("scrollback"),function(t){e.newMsg(t)})},newMsg:function(e){e=this.generateMessageDisplayObj(e),m.global.events.emit("message:display",{panel:this.model,message:e}).then(_.bind(function(){var t,n=_.clone(e);n.nick=u("message_nick",{nick:e.nick,prefix:e.nick_prefix||""}),t='<div class="msg <%= type %> <%= css_classes %>"><div class="time"><%- time_string %></div><div class="nick" style="<%= nick_style %>"><%- nick %></div><div class="text" style="<%= style %>"><%= msg %> </div></div>',this.$messages.append($(_.template(t,n)).data("message",e)),e.type.match(/^action /)?this.alert("action"):e.is_highlight?(m.app.view.alertWindow("* "+m.global.i18n.translate("client_views_panel_activity").fetch()),m.app.view.favicon.newHighlight(),m.app.view.playSound("highlight"),m.app.view.showNotification(this.model.get("name"),e.unparsed_msg),this.alert("highlight")):(this.model.isActive()&&m.app.view.alertWindow("* "+m.global.i18n.translate("client_views_panel_activity").fetch()),this.alert("activity")),this.model.isQuery()&&!this.model.isActive()&&(m.app.view.alertWindow("* "+m.global.i18n.translate("client_views_panel_activity").fetch()),e.is_highlight||m.app.view.favicon.newHighlight(),m.app.view.showNotification(this.model.get("name"),e.unparsed_msg),m.app.view.playSound("highlight")),function(){if(!this.model.isActive()){var t,n,i=m.global.settings.get("count_all_activity");"undefined"==typeof i&&(i=!1),t=["action join","action quit","action part","action kick","action nick","action mode"],(i||-1===_.indexOf(t,e.type))&&(n=this.model.get("activity_counter")||0,n++,this.model.set("activity_counter",n))}}.apply(this),this.model.isActive()&&this.scrollToBottom(),this.msg_count++,this.msg_count>(parseInt(m.global.settings.get("scrollback"),10)||250)&&($(".msg:first",this.$messages).remove(),this.msg_count--)},this))},parseMessageNicks:function(e,t){var n,i,s="";return n=this.model.get("members"),n&&(i=n.getByNick(e))?(t!==!1&&(s=this.getNickStyles(i.get("nick")).asCssString()),_.template('<span class="inline-nick" style="<%- style %>;cursor:pointer;" data-nick="<%- nick %>"><%- nick %></span>',{nick:e,style:s})):void 0},parseMessageChannels:function(e){var t,n=!1,i=this.model.get("network");if(i)return t=new RegExp("(^|\\s)(["+r(i.get("channel_prefix"))+"][^ ,\\007]+)","g"),e.match(t)?n=e.replace(t,function(e,t){return t+'<a class="chan" data-channel="'+_.escape(e.trim())+'">'+_.escape(e.trim())+"</a>"}):n},parseMessageUrls:function(e){var t,n=!1;return t=e.replace(/^(([A-Za-z][A-Za-z0-9\-]*\:\/\/)|(www\.))([\w.\-]+)([a-zA-Z]{2,6})(:[0-9]+)?(\/[\w!:.?$'()[\]*,;~+=&%@!\-\/]*)?(#.*)?$/gi,function(e){var t=e,i="";return e.match(/^javascript:/)?e:(n=!0,e.match(/^www\./)&&(e="http://"+e),t.length>100&&(t=t.substr(0,100)+"..."),i=m.view.MediaMessage.buildHtml(e),'<a class="link_ext" target="_blank" rel="nofollow" href="'+e.replace(/"/g,"%22")+'">'+_.escape(t)+"</a>"+i)}),n?t:!1},getNickStyles:function(e){var t,n,i,s,a=0;return _.map(e.split(""),function(e){a+=e.charCodeAt(0)}),s=(_.find(m.app.themes,function(e){return e.name.toLowerCase()===m.global.settings.get("theme").toLowerCase()})||{}).nick_lightness,s="number"!=typeof s?35:Math.max(0,Math.min(100,s)),i=o(a%255,70,s),i=i[2]|i[1]<<8|i[0]<<16,n="#"+i.toString(16),t={color:n},t.asCssString=function(){return _.reduce(this,function(e,t,n){return e+n+":"+t+";"},"")},t},generateMessageDisplayObj:function(e){var t,n,i,s,a,o,c=this.model.get("scrollback"),p=c[c.length-2];e=_.clone(e),e.css_classes="",e.nick_style="",e.is_highlight=!1,e.time_string="";var u=m.app.connections.active_connection.get("nick");return new RegExp("(^|\\W)("+r(u)+")(\\W|$)","i").test(e.msg)&&0!==e.nick.localeCompare(u)&&(e.is_highlight=!0,e.css_classes+=" highlight"),i=e.msg.split(" "),i=_.map(i,function(t){var n;return n=this.parseMessageUrls(t),"string"==typeof n?n:(n=this.parseMessageChannels(t),"string"==typeof n?n:(n=this.parseMessageNicks(t,"privmsg"===e.type),"string"==typeof n?n:(n=_.escape(t),m.global.settings.get("show_emoticons")&&(n=h(n)),n)))},this),e.unparsed_msg=e.msg,e.msg=i.join(" "),e.msg=l(e.msg),e.nick_style=this.getNickStyles(e.nick).asCssString(),t="",e.nick&&(_.map(e.nick.split(""),function(e){t+=e.charCodeAt(0).toString(16)}),e.css_classes+=" nick_"+t),p&&(n=(e.time.getTime()-p.time.getTime())/1e3/60,p.nick===e.nick&&1>n&&(e.css_classes+=" repeated_nick")),m.global.settings.get("use_24_hour_timestamps")?e.time_string=e.time.getHours().toString().lpad(2,"0")+":"+e.time.getMinutes().toString().lpad(2,"0")+":"+e.time.getSeconds().toString().lpad(2,"0"):(s=e.time.getHours(),a=s>11,s%=12,0===s&&(s=12),o=a?"client_views_panel_timestamp_pm":"client_views_panel_timestamp_am",e.time_string=d(o,s+":"+e.time.getMinutes().toString().lpad(2,"0")+":"+e.time.getSeconds().toString().lpad(2,"0"))),e},topic:function(e){"string"==typeof e&&e||(e=this.model.get("topic")),this.model.addMsg("",u("channel_topic",{text:e,channel:this.model.get("name")}),"topic"),m.app.panels().active===this.model&&m.app.topicbar.setCurrentTopicFromChannel(this.model)},topicSetBy:function(){m.app.panels().active===this.model&&m.app.topicbar.setCurrentTopicFromChannel(this.model)},nickClick:function(e){var t,n,i=$(e.currentTarget),s=this.model.get("members");e.stopPropagation(),t=i.data("nick"),t||(t=i.parent(".msg").data("message").nick),n=s?s.getByNick(t):null,n&&m.global.events.emit("nick:select",{target:i,member:n,source:"message"}).then(_.bind(this.openUserMenuForNick,this,i,n))},updateLastSeenMarker:function(){this.model.isActive()&&(this.$(".last_seen").removeClass("last_seen"),this.$messages.children().last().addClass("last_seen"))},openUserMenuForNick:function(e,t){var n,i,s=this.model.get("members"),a=!!s.getByNick(m.app.connections.active_connection.get("nick")).get("is_op");n=new m.view.UserBox,n.setTargets(t,this.model),n.displayOpItems(a),i=new m.view.MenuBox(t.get("nick")||"User"),i.addItem("userbox",n.$el),i.showFooter(!1),m.global.events.emit("usermenu:created",{menu:i,userbox:n,user:t}).then(_.bind(function(){i.show();var t=e.offset(),n=t.top,s=n+i.$el.outerHeight(),a=this.$el.parent().offset().top+this.$el.parent().outerHeight();s>a&&(n=a-i.$el.outerHeight()),i.$el.offset({left:t.left,top:n})},this)).catch(_.bind(function(){n=null,menu.dispose(),menu=null},this))},chanClick:function(e){var t=e.target?$(e.target).data("channel"):$(e.srcElement).data("channel");m.app.connections.active_connection.gateway.join(t)},mediaClick:function(e){var t,n=$(e.target).parents(".media");n.data("media")?t=n.data("media"):(t=new m.view.MediaMessage({el:n[0]}),n.data("media",t)),t.toggle()},msgEnter:function(e){var t;_.each($(e.currentTarget).parent(".msg").attr("class").split(" "),function(e){e.match(/^nick_[a-z0-9]+/i)&&(t=e)}),t&&$("."+t).addClass("global_nick_highlight")},msgLeave:function(e){var t;_.each($(e.currentTarget).parent(".msg").attr("class").split(" "),function(e){e.match(/^nick_[a-z0-9]+/i)&&(t=e)}),t&&$("."+t).removeClass("global_nick_highlight")}}),m.view.Applet=m.view.Panel.extend({className:"panel applet",initialize:function(e){this.initializePanel(e)}}),m.view.Application=Backbone.View.extend({initialize:function(){var e=this;this.$el=$($("#tmpl_application").html().trim()),this.el=this.$el[0],$(this.model.get("container")||"body").append(this.$el),this.elements={panels:this.$el.find(".panels"),right_bar:this.$el.find(".right_bar"),toolbar:this.$el.find(".toolbar"),controlbox:this.$el.find(".controlbox"),resize_handle:this.$el.find(".memberlists_resize_handle")},$(window).resize(function(){e.doLayout.apply(e)}),this.elements.toolbar.resize(function(){e.doLayout.apply(e)}),this.elements.controlbox.resize(function(){e.doLayout.apply(e)}),m.global.settings.on("change:theme",this.updateTheme,this),this.updateTheme(getQueryVariable("theme")),m.global.settings.on("change:channel_list_style",this.setTabLayout,this),this.setTabLayout(m.global.settings.get("channel_list_style")),m.global.settings.on("change:show_timestamps",this.displayTimestamps,this),this.displayTimestamps(m.global.settings.get("show_timestamps")),this.$el.appendTo($("body")),this.doLayout(),$(document).keydown(this.setKeyFocus),window.onbeforeunload=function(){return m.gateway.isConnected()?m.global.i18n.translate("client_views_application_close_notice").fetch():void 0},this.has_focus=!0,$(window).on("focus",function(){e.has_focus=!0}),$(window).on("blur",function(){var t=e.model.panels().active;t&&t.view.updateLastSeenMarker&&t.view.updateLastSeenMarker(),e.has_focus=!1}),$(window).on("touchstart",function t(){e.$el.addClass("touch"),$(window).off("touchstart",t)}),this.favicon=new m.view.Favicon,this.initSound(),this.monitorPanelFallback()},updateTheme:function(e){e===m.global.settings&&(e=arguments[1]),e||(e=m.global.settings.get("theme")||"relaxed"),e=e.toLowerCase(),$("[data-theme]:not([disabled])").each(function(e,t){var n=$(t);n.attr("rel","alternate "+n.attr("rel")).attr("disabled",!0)[0].disabled=!0});var t=$("[data-theme][title="+e+"]");t.length>0&&(t.attr("rel","stylesheet").attr("disabled",!1)[0].disabled=!1),this.doLayout()},setTabLayout:function(e){e===m.global.settings&&(e=arguments[1]),"list"==e?this.$el.addClass("chanlist_treeview"):this.$el.removeClass("chanlist_treeview"),this.doLayout()},displayTimestamps:function(e){e===m.global.settings&&(e=arguments[1]),e?this.$el.addClass("timestamps"):this.$el.removeClass("timestamps")},setKeyFocus:function(e){e.ctrlKey||e.altKey||e.metaKey||"input"===e.target.tagName.toLowerCase()||"textarea"===e.target.tagName.toLowerCase()||$(e.target).attr("contenteditable")||$("#kiwi .controlbox .inp").focus()},doLayout:function(){var e=this.$el,t=this.elements.panels,n=this.elements.right_bar,i=this.elements.toolbar,s=this.elements.controlbox,a=this.elements.resize_handle;if(e.is(":visible")){var o={top:i.outerHeight(!0),bottom:s.outerHeight(!0)};i.is(":visible")||(o.top=0),s.is(":visible")||(o.bottom=0),t.css(o),n.css(o),a.css(o),e.hasClass("chanlist_treeview")&&this.$el.find(".tabs",e).css(o),e.outerWidth()<420?(e.addClass("narrow"),this.model.rightbar&&this.model.rightbar.keep_hidden!==!0&&this.model.rightbar.toggle(!0)):(e.removeClass("narrow"),this.model.rightbar&&this.model.rightbar.keep_hidden!==!1&&this.model.rightbar.toggle(!1)),n.hasClass("disabled")?(t.css("right",0),a.css("left",t.outerWidth(!0))):(t.css("right",n.outerWidth(!0)),a.css("left",n.position().left-a.outerWidth(!0)/2));var c=parseInt(s.find(".input_tools").outerWidth(),10);s.find(".input_wrap").css("right",c+7)}},alertWindow:function(e){this.alertWindowTimer||(this.alertWindowTimer=new function(){var e,t=this,n=!0,i=0,s=m.app.server_settings.client.window_title||"Kiwi IRC",a="Kiwi IRC";this.setTitle=function(e){return e=e||s,window.document.title=e,e},this.start=function(t){n||(a=t,e||(e=setInterval(this.update,1e3)))},this.stop=function(){e&&clearInterval(e),e=null,this.setTitle(),setTimeout(this.reset,2e3)},this.reset=function(){e||t.setTitle()},this.update=function(){0===i?(t.setTitle(a),i=1):(t.setTitle(),i=0)},$(window).focus(function(){n=!0,t.stop(),setTimeout(t.reset,2e3)}),$(window).blur(function(){n=!1})}),this.alertWindowTimer.start(e)},barsHide:function(e){e?(this.$el.find(".toolbar").slideUp(0),$("#kiwi .controlbox").slideUp(0),this.doLayout()):(this.$el.find(".toolbar").slideUp({queue:!1,duration:400,step:$.proxy(this.doLayout,this)}),$("#kiwi .controlbox").slideUp({queue:!1,duration:400,step:$.proxy(this.doLayout,this)}))},barsShow:function(e){e?(this.$el.find(".toolbar").slideDown(0),$("#kiwi .controlbox").slideDown(0),this.doLayout()):(this.$el.find(".toolbar").slideDown({queue:!1,duration:400,step:$.proxy(this.doLayout,this)}),$("#kiwi .controlbox").slideDown({queue:!1,duration:400,step:$.proxy(this.doLayout,this)}))},initSound:function(){var e=this,t=this.model.get("base_path");$script(t+"/assets/libs/soundmanager2/soundmanager2-nodebug-jsmin.js",function(){"undefined"!=typeof soundManager&&soundManager.setup({url:t+"/assets/libs/soundmanager2/",flashVersion:9,preferFlash:!0,onready:function(){e.sound_object=soundManager.createSound({id:"highlight",url:t+"/assets/sound/highlight.mp3"})}})})},playSound:function(e){this.sound_object&&(m.global.settings.get("mute_sounds")||soundManager.play(e))},showNotification:function(e,t){var n=this.model.get("base_path")+"/assets/img/ico.png",i=m.utils.notifications;!this.has_focus&&i.allowed()&&i.create(e,{icon:n,body:t}).closeAfter(5e3).on("click",_.bind(window.focus,window))},monitorPanelFallback:function(){var e=[];this.model.panels.on("active",function(){var t,n=m.app.panels().active;t=_.indexOf(e,n.cid),t>-1&&e.splice(t,1),e.unshift(n.cid)}),this.model.panels.on("remove",function(t){if(e[0]===t.cid){e.shift();var n=_.find(m.app.panels("applets").concat(m.app.panels("connections")),{cid:e[0]});n&&n.view.show()}})}}),m.view.AppToolbar=Backbone.View.extend({events:{"click .settings":"clickSettings","click .startup":"clickStartup"},initialize:function(){m.app.server_settings.connection&&!m.app.server_settings.connection.allow_change&&this.$(".startup").css("display","none")},clickSettings:function(e){e.preventDefault(),m.app.controlbox.processInput("/settings")},clickStartup:function(e){e.preventDefault(),m.app.startup_applet.view.show()}}),m.view.ControlBox=Backbone.View.extend({events:{"keydown .inp":"process","click .nick":"showNickChange"},initialize:function(){var e=this;this.buffer=[],this.buffer_pos=0,this.preprocessor=new a,this.preprocessor.recursive_depth=5,this.tabcomplete={active:!1,data:[],prefix:""},m.app.connections.on("change:nick",function(t){t===m.app.connections.active_connection&&$(".nick",e.$el).text(t.get("nick"))}),m.app.connections.on("active",function(t,n){$(".nick",e.$el).text(n.get("nick"))}),m.app.panels.bind("active",function(t){(t.isChannel()||t.isServer()||t.isQuery())&&e.$(".inp").focus()})},render:function(){var e=d("client_views_controlbox_message");return this.$(".inp").attr("placeholder",e),this},showNickChange:function(){this.nick_change||(this.nick_change=new m.view.NickChangeBox,this.nick_change.render(),this.listenTo(this.nick_change,"close",function(){delete this.nick_change}))},process:function(e){var t,n=this,i=$(e.currentTarget),s=i.val();switch(t=-1!==navigator.appVersion.indexOf("Mac")?e.metaKey:e.altKey,this.tabcomplete.active&&9!==e.keyCode&&(this.tabcomplete.active=!1,this.tabcomplete.data=[],this.tabcomplete.prefix=""),!0){case 13===e.keyCode:return s=s.trim(),s&&($.each(s.split("\n"),function(e,t){n.processInput(t)}),this.buffer.push(s),this.buffer_pos=this.buffer.length),i.val(""),!1;case 38===e.keyCode:return this.buffer_pos>0&&(this.buffer_pos--,i.val(this.buffer[this.buffer_pos])),!1;case 40===e.keyCode:this.buffer_pos<this.buffer.length&&(this.buffer_pos++,i.val(this.buffer[this.buffer_pos]));break;case 219===e.keyCode&&t:var a=$("#kiwi .tabs").find("li[class!=connection]"),o=function(){for(var e=0;e<a.length;e++)if($(a[e]).hasClass("active"))return e}();return $prev_tab=$(0===o?a[a.length-1]:a[o-1]),$prev_tab.click(),!1;case 221===e.keyCode&&t:var a=$("#kiwi .tabs").find("li[class!=connection]"),o=function(){for(var e=0;e<a.length;e++)if($(a[e]).hasClass("active"))return e
+}();return $next_tab=$(o===a.length-1?a[0]:a[o+1]),$next_tab.click(),!1;case!(9!==e.keyCode||e.shiftKey||e.altKey||e.metaKey||e.ctrlKey):if(this.tabcomplete.active=!0,_.isEqual(this.tabcomplete.data,[])){var c=[],l=m.app.panels().active.get("members");l=l?l.models:[],$.each(l,function(e,t){t&&c.push(t.get("nick"))}),c.push(m.app.panels().active.get("name")),c=_.sortBy(c,function(e){return e.toLowerCase()}),this.tabcomplete.data=c}return" "===s[i[0].selectionStart-1]?!1:(function(){var e,t,a,o,c,l,r=": ";e=s.substring(0,i[0].selectionStart).split(" "),":"==e[e.length-1]&&e.pop(),e.length>1&&(r=""),l=e[e.length-1],""===this.tabcomplete.prefix&&(this.tabcomplete.prefix=l),this.tabcomplete.data=_.select(this.tabcomplete.data,function(e){return 0===e.toLowerCase().indexOf(n.tabcomplete.prefix.toLowerCase())}),this.tabcomplete.data.length>0&&(a=i[0].selectionStart-l.length,t=s.substr(0,a),o=this.tabcomplete.data.shift(),this.tabcomplete.data.push(o),t+=o,s.substr(i[0].selectionStart,2)!==r&&(t+=r),t+=s.substr(i[0].selectionStart),i.val(t),i[0].setSelectionRange?i[0].setSelectionRange(a+o.length+r.length,a+o.length+r.length):i[0].createTextRange&&(c=i[0].createTextRange(),c.collapse(!0),c.moveEnd("character",a+o.length+r.length),c.moveStart("character",a+o.length+r.length),c.select()))}.apply(this),!1)}},processInput:function(e){var t,n,i,s=this;"/"===e[0]||m.app.panels().active.isChannel()||m.app.panels().active.isQuery()||(e="/"+e),("/"!==e[0]||"//"===e.substr(0,2))&&(e=e.replace(/^\/\//,"/"),e="/msg "+m.app.panels().active.get("name")+" "+e),this.preprocessor.vars.server=m.app.connections.active_connection.get("name"),this.preprocessor.vars.channel=m.app.panels().active.get("name"),this.preprocessor.vars.destination=this.preprocessor.vars.channel,e=this.preprocessor.process(e),n=e.split(/\s/),"/"===n[0][0]?(t=n[0].substr(1).toLowerCase(),n=n.splice(1,n.length-1)):(t="msg",n.unshift(m.app.panels().active.get("name"))),i={command:t,params:n},m.global.events.emit("command",i).then(function(){s.trigger("command",{command:i.command,params:i.params}),s.trigger("command:"+i.command,{command:i.command,params:i.params}),s._events["command:"+i.command]||s.trigger("unknown_command",{command:i.command,params:i.params})})},addPluginIcon:function(e){var t=$('<div class="tool"></div>').append(e);this.$el.find(".input_tools").append(t),m.app.view.doLayout()}}),m.view.Favicon=Backbone.View.extend({initialize:function(){var e=this,t=$(window);this.has_focus=!0,this.highlight_count=0,this.has_canvas_support=!!window.CanvasRenderingContext2D,this.original_favicon=$('link[rel~="icon"]')[0].href,this._createCanvas(),t.on("focus",function(){e.has_focus=!0,e._resetHighlights()}),t.on("blur",function(){e.has_focus=!1})},newHighlight:function(){var e=this;this.has_focus||(this.highlight_count++,this.has_canvas_support&&this._drawFavicon(function(){e._drawBubble(e.highlight_count.toString()),e._refreshFavicon(e.canvas.toDataURL())}))},_resetHighlights:function(){this.highlight_count=0,this._refreshFavicon(this.original_favicon)},_drawFavicon:function(e){var t=this.canvas,n=t.getContext("2d"),i=new Image;i.crossOrigin="anonymous",i.src=this.original_favicon,i.onload=function(){n.clearRect(0,0,t.width,t.height),n.drawImage(i,0,0,t.width,t.height),e()}},_drawBubble:function(e){var t,n=0,i=0,s=this.canvas,a=test_context=s.getContext("2d"),o=s.width,c=s.height;t=-1!==navigator.appVersion.indexOf("Mac")?-1.5:-1,test_context.font=a.font="bold 10px Arial",test_context.textAlign="right",this._renderText(test_context,e,0,0,t),n=test_context.measureText(e).width+t*(e.length-1)+2,i=9,bubbleX=o-n,bubbleY=c-i,a.fillStyle="red",a.fillRect(bubbleX,bubbleY,n,i),a.fillStyle="white",this._renderText(a,e,o-1,c-1,t)},_refreshFavicon:function(e){$('link[rel~="icon"]').remove(),$('<link rel="shortcut icon" href="'+e+'">').appendTo($("head"))},_createCanvas:function(){var e=document.createElement("canvas");e.width=16,e.height=16,this.canvas=e},_renderText:function(e,t,n,i,s){for(var a,o=t.split("").reverse(),c=0,l=n;c<t.length;)a=o[c++],e.fillText(a,l,i),l+=-1*(e.measureText(a).width+s);return e}}),m.view.MediaMessage=Backbone.View.extend({events:{"click .media_close":"close"},initialize:function(){this.url=this.$el.data("url")},toggle:function(){this.$content&&this.$content.is(":visible")?this.close():this.open()},close:function(){var e=this;this.$content.slideUp("fast",function(){e.$content.remove()})},open:function(){this.$content||(this.$content=$('<div class="media_content"><a class="media_close"><i class="fa fa-chevron-up"></i> '+m.global.i18n.translate("client_views_mediamessage_close").fetch()+'</a><br /><div class="content"></div></div>'),this.$content.find(".content").append(this.mediaTypes[this.$el.data("type")].apply(this,[])||m.global.i18n.translate("client_views_mediamessage_notfound").fetch()+" :(")),this.$content.is(":visible")||(this.$content.hide(),this.$el.append(this.$content),this.$content.slideDown())},mediaTypes:{twitter:function(){var e=this.$el.data("tweetid"),t=this;return $.getJSON("https://api.twitter.com/1/statuses/oembed.json?id="+e+"&callback=?",function(e){t.$content.find(".content").html(e.html)}),$("<div>"+m.global.i18n.translate("client_views_mediamessage_load_tweet").fetch()+"...</div>")},image:function(){return $('<a href="'+this.url+'" target="_blank"><img height="100" src="'+this.url+'" /></a>')},imgur:function(){var e=this;return $.getJSON("http://api.imgur.com/oembed?url="+this.url,function(t){var n='<a href="'+t.url+'" target="_blank"><img height="100" src="'+t.url+'" /></a>';e.$content.find(".content").html(n)}),$("<div>"+m.global.i18n.translate("client_views_mediamessage_load_image").fetch()+"...</div>")},reddit:function(){var e=this,t=/reddit\.com\/r\/([a-zA-Z0-9_\-]+)\/comments\/([a-z0-9]+)\/([^\/]+)?/gi.exec(this.url);return $.getJSON("http://www."+t[0]+".json?jsonp=?",function(t){console.log("Loaded reddit data",t);var n=t[0].data.children[0].data,i="";n.thumbnail&&(n.over_18?(i="<span class=\"thumbnail_nsfw\" onclick=\"$(this).find('p').remove(); $(this).find('img').css('visibility', 'visible');\">",i+='<p style="font-size:0.9em;line-height:1.2em;cursor:pointer;">Show<br />NSFW</p>',i+='<img src="'+n.thumbnail+'" class="thumbnail" style="visibility:hidden;" />',i+="</span>"):i='<img src="'+n.thumbnail+'" class="thumbnail" />');var s="<div>"+i+"<b><%- title %></b><br />Posted by <%- author %>. ";s+='<i class="fa fa-arrow-up"></i> <%- ups %> <i class="fa fa-arrow-down"></i> <%- downs %><br />',s+='<%- num_comments %> comments made. <a href="http://www.reddit.com<%- permalink %>">View post</a></div>',e.$content.find(".content").html(_.template(s,n))}),$("<div>"+m.global.i18n.translate("client_views_mediamessage_load_reddit").fetch()+"...</div>")},youtube:function(){var e=this.$el.data("ytid"),t=this,n='<iframe width="480" height="270" src="https://www.youtube.com/embed/'+e+'?feature=oembed" frameborder="0" allowfullscreen=""></iframe>';return t.$content.find(".content").html(n),$("")},gist:function(){var e=this,t=/https?:\/\/gist\.github\.com\/(?:[a-z0-9-]*\/)?([a-z0-9]+)(\#(.+))?$/i.exec(this.url);return $.getJSON("https://gist.github.com/"+t[1]+".json?callback=?"+(t[2]||""),function(t){$("body").append('<link rel="stylesheet" href="'+t.stylesheet+'" type="text/css" />'),e.$content.find(".content").html(t.div)}),$("<div>"+m.global.i18n.translate("client_views_mediamessage_load_gist").fetch()+"...</div>")},spotify:function(){var e,t,n=this.$el.data("uri"),i=this.$el.data("method");switch(i){case"track":case"album":e={url:"https://embed.spotify.com/?uri="+n,width:300,height:80};break;case"artist":e={url:"https://embed.spotify.com/follow/1/?uri="+n+"&size=detail&theme=dark",width:300,height:56}}return t='<iframe src="'+e.url+'" width="'+e.width+'" height="'+e.height+'" frameborder="0" allowtransparency="true"></iframe>',$(t)},soundcloud:function(){var e=this.$el.data("url"),t=$("<div></div>").text(m.global.i18n.translate("client_models_applet_loading").fetch());return $.getJSON("https://soundcloud.com/oembed",{url:e}).then(function(e){t.empty().append($(e.html).attr("height",e.height-100))},function(){t.text(m.global.i18n.translate("client_views_mediamessage_notfound").fetch())}),t},custom:function(){var e=this.constructor.types[this.$el.data("index")];if(e)return $(e.buildHtml(this.$el.data("url")))}}},{addType:function(e,t){"function"==typeof e&&"function"==typeof t&&(this.types=this.types||[],this.types.push({match:e,buildHtml:t}))},buildHtml:function(e){var t,n="";if(_.each(this.types||[],function(t,i){t.match(e)&&(n+='<span class="media" title="Open" data-type="custom" data-index="'+i+'" data-url="'+_.escape(e)+'"><a class="open"><i class="fa fa-chevron-right"></i></a></span>')}),e.match(/(\.jpe?g|\.gif|\.bmp|\.png)\??$/i)&&(n+='<span class="media image" data-type="image" data-url="'+e+'" title="Open Image"><a class="open"><i class="fa fa-chevron-right"></i></a></span>'),t=/imgur\.com\/[^\/]*(?!=\.[^!.]+($|\?))/gi.exec(e),t&&!e.match(/(\.jpe?g|\.gif|\.bmp|\.png)\??$/i)&&(n+='<span class="media imgur" data-type="imgur" data-url="'+e+'" title="Open Image"><a class="open"><i class="fa fa-chevron-right"></i></a></span>'),t=/https?:\/\/twitter.com\/([a-zA-Z0-9_]+)\/status\/([0-9]+)/gi.exec(e),t&&(n+='<span class="media twitter" data-type="twitter" data-url="'+e+'" data-tweetid="'+t[2]+'" title="Show tweet information"><a class="open"><i class="fa fa-chevron-right"></i></a></span>'),t=/reddit\.com\/r\/([a-zA-Z0-9_\-]+)\/comments\/([a-z0-9]+)\/([^\/]+)?/gi.exec(e),t&&(n+='<span class="media reddit" data-type="reddit" data-url="'+e+'" title="Reddit thread"><a class="open"><i class="fa fa-chevron-right"></i></a></span>'),t=/(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/ ]{11})/gi.exec(e),t&&(n+='<span class="media youtube" data-type="youtube" data-url="'+e+'" data-ytid="'+t[1]+'" title="YouTube Video"><a class="open"><i class="fa fa-chevron-right"></i></a></span>'),t=/https?:\/\/gist\.github\.com\/(?:[a-z0-9-]*\/)?([a-z0-9]+)(\#(.+))?$/i.exec(e),t&&(n+='<span class="media gist" data-type="gist" data-url="'+e+'" data-gist_id="'+t[1]+'" title="GitHub Gist"><a class="open"><i class="fa fa-chevron-right"></i></a></span>'),t=/http:\/\/(?:play|open\.)?spotify.com\/(album|track|artist)\/([a-zA-Z0-9]+)\/?/i.exec(e)){var i=t[1],s="spotify:"+t[1]+":"+t[2];n+='<span class="media spotify" data-type="spotify" data-uri="'+s+'" data-method="'+i+'" title="Spotify '+i+'"><a class="open"><i class="fa fa-chevron-right"></i></a></span>'}return t=/(?:m\.)?(soundcloud\.com(?:\/.+))/i.exec(e),t&&(n+='<span class="media soundcloud" data-type="soundcloud" data-url="http://'+t[1]+'" title="SoundCloud player"><a class="open"><i class="fa fa-chevron-right"></i></a></span>'),n}}),m.view.Member=Backbone.View.extend({tagName:"li",initialize:function(){this.model.bind("change",this.render,this),this.render()},render:function(){var e=this.$el,t=(this.model.get("modes")||[]).join(" ");return e.attr("class","mode "+t),e.html('<a class="nick"><span class="prefix">'+this.model.get("prefix")+"</span>"+this.model.get("nick")+"</a>"),this}}),m.view.MemberList=Backbone.View.extend({tagName:"div",events:{"click .nick":"nickClick","click .channel_info":"channelInfoClick"},initialize:function(){this.model.bind("all",this.render,this),this.$el.appendTo("#kiwi .memberlists"),this.$meta=$('<div class="meta"></div>').appendTo(this.$el),this.$list=$("<ul></ul>").appendTo(this.$el)},render:function(){var e=this;return this.$list.empty(),this.model.forEach(function(t){t.view.$el.data("member",t),e.$list.append(t.view.$el)}),this.model.channel.isActive()&&this.renderMeta(),this},renderMeta:function(){var e=this.model.length+" "+d("client_applets_chanlist_users");this.$meta.text(e)},nickClick:function(e){var t=$(e.currentTarget).parent("li"),n=t.data("member");m.global.events.emit("nick:select",{target:t,member:n,source:"nicklist"}).then(_.bind(this.openUserMenuForItem,this,t))},openUserMenuForItem:function(e){var t,n=e.data("member"),i=!!this.model.getByNick(m.app.connections.active_connection.get("nick")).get("is_op");t=new m.view.UserBox,t.setTargets(n,this.model.channel),t.displayOpItems(i);var s=new m.view.MenuBox(n.get("nick")||"User");s.addItem("userbox",t.$el),s.showFooter(!1),m.global.events.emit("usermenu:created",{menu:s,userbox:t,user:n}).then(_.bind(function(){s.show();var t=e.offset(),n=t.top,i=n+s.$el.outerHeight(),a=this.$el.parent().offset().top+this.$el.parent().outerHeight(),o=t.left,c=o+s.$el.outerWidth(),l=this.$el.parent().offset().left+this.$el.parent().outerWidth();i>a&&(n=a-s.$el.outerHeight()),0>n&&(n=0),c>l&&(o=l-s.$el.outerWidth()),s.$el.offset({left:o,top:n})},this)).catch(_.bind(function(){t=null,s.dispose(),s=null},this))},channelInfoClick:function(){new m.model.ChannelInfo({channel:this.model.channel})},show:function(){$("#kiwi .memberlists").children().removeClass("active"),$(this.el).addClass("active"),this.renderMeta()}}),m.view.MenuBox=Backbone.View.extend({events:{"click .ui_menu_foot .close, a.close_menu":"dispose"},initialize:function(e){this.$el=$('<div class="ui_menu"><div class="items"></div></div>'),this._title=e||"",this._items={},this._display_footer=!0,this._close_on_blur=!0},render:function(){var e,t=this,n=t.$el.find(".items");n.find("*").remove(),this._title&&(e=$('<div class="ui_menu_title"></div>').text(this._title),this.$el.prepend(e)),_.each(this._items,function(e){var t=$('<div class="ui_menu_content hover"></div>').append(e);n.append(t)}),this._display_footer&&this.$el.append('<div class="ui_menu_foot"><a class="close" onclick="">Close <i class="fa fa-times"></i></a></div>')},setTitle:function(e){this._title=e,this._title&&this.$el.find(".ui_menu_title").text(this._title)},onDocumentClick:function(e){var t=$(e.target);this._close_on_blur&&t[0]!=this.$el[0]&&0===this.$el.has(t).length&&this.dispose()},dispose:function(){_.each(this._items,function(e){e.dispose&&e.dispose(),e.remove&&e.remove()}),this._items=null,this.remove(),this._close_proxy&&$(document).off("click",this._close_proxy)},addItem:function(e,t){t.is("a")&&t.addClass("fa fa-chevron-right"),this._items[e]=t},removeItem:function(e){delete this._items[e]},showFooter:function(e){this._display_footer=e},closeOnBlur:function(e){this._close_on_blur=e},show:function(){var e,t,n=this;this.render(),this.$el.appendTo(m.app.view.$el),e=m.app.view.$el.find(".controlbox"),$items=this.$el.find(".items"),t=this.$el.outerHeight()-$items.outerHeight(),$items.css({"overflow-y":"auto","max-height":e.offset().top-this.$el.offset().top-t}),setTimeout(function(){n._close_proxy=function(e){n.onDocumentClick(e)},$(document).on("click",n._close_proxy)},0)}}),m.view.NetworkTabs=Backbone.View.extend({tagName:"ul",className:"connections",initialize:function(){this.model.on("add",this.networkAdded,this),this.model.on("remove",this.networkRemoved,this),this.$el.appendTo(m.app.view.$el.find(".tabs"))},networkAdded:function(e){$('<li class="connection"></li>').append(e.panels.view.$el).appendTo(this.$el)},networkRemoved:function(e){e.panels.view.$el.parent().remove(),e.panels.view.remove(),m.app.view.doLayout()}}),m.view.NickChangeBox=Backbone.View.extend({events:{submit:"changeNick","click .cancel":"close"},initialize:function(){var e={new_nick:m.global.i18n.translate("client_views_nickchangebox_new").fetch(),change:m.global.i18n.translate("client_views_nickchangebox_change").fetch(),cancel:m.global.i18n.translate("client_views_nickchangebox_cancel").fetch()};this.$el=$(_.template($("#tmpl_nickchange").html().trim(),e))},render:function(){m.app.controlbox.$el.prepend(this.$el),this.$el.find("input").focus(),this.$el.css("bottom",m.app.controlbox.$el.outerHeight(!0))},close:function(){this.$el.remove(),this.trigger("close")},changeNick:function(e){e.preventDefault();var t=m.app.connections.active_connection;this.listenTo(t,"change:nick",function(){this.close()}),t.gateway.changeNick(this.$("input").val())}}),m.view.ResizeHandler=Backbone.View.extend({events:{mousedown:"startDrag",mouseup:"stopDrag"},initialize:function(){this.dragging=!1,this.starting_width={},$(window).on("mousemove",$.proxy(this.onDrag,this))},startDrag:function(){this.dragging=!0},stopDrag:function(){this.dragging=!1},onDrag:function(e){if(this.dragging){var t=$("#kiwi").offset().left;this.$el.css("left",e.clientX-this.$el.outerWidth(!0)/2-t),$("#kiwi .right_bar").css("width",this.$el.parent().width()-(this.$el.position().left+this.$el.outerWidth())),m.app.view.doLayout()}}}),m.view.ServerSelect=Backbone.View.extend({events:{"submit form":"submitForm","click .show_more":"showMore","change .have_pass input":"showPass","change .have_key input":"showKey","click .fa-key":"channelKeyIconClick","click .show_server":"showServer"},initialize:function(){var e={think_nick:m.global.i18n.translate("client_views_serverselect_form_title").fetch(),nickname:m.global.i18n.translate("client_views_serverselect_nickname").fetch(),have_password:m.global.i18n.translate("client_views_serverselect_enable_password").fetch(),password:m.global.i18n.translate("client_views_serverselect_password").fetch(),channel:m.global.i18n.translate("client_views_serverselect_channel").fetch(),channel_key:m.global.i18n.translate("client_views_serverselect_channelkey").fetch(),require_key:m.global.i18n.translate("client_views_serverselect_channelkey_required").fetch(),key:m.global.i18n.translate("client_views_serverselect_key").fetch(),start:m.global.i18n.translate("client_views_serverselect_connection_start").fetch(),server_network:m.global.i18n.translate("client_views_serverselect_server_and_network").fetch(),server:m.global.i18n.translate("client_views_serverselect_server").fetch(),port:m.global.i18n.translate("client_views_serverselect_port").fetch(),powered_by:m.global.i18n.translate("client_views_serverselect_poweredby").fetch()};this.$el=$(_.template($("#tmpl_server_select").html().trim(),e)),m.app.server_settings&&m.app.server_settings.connection&&(m.app.server_settings.connection.allow_change||(this.$el.find(".show_more").remove(),this.$el.addClass("single_server"))),this.state="all",this.more_shown=!1,this.model.bind("new_network",this.newNetwork,this),this.gateway=m.global.components.Network(),this.gateway.on("connect",this.networkConnected,this),this.gateway.on("connecting",this.networkConnecting,this),this.gateway.on("disconnect",this.networkDisconnected,this),this.gateway.on("irc_error",this.onIrcError,this)},dispose:function(){this.model.off("new_network",this.newNetwork,this),this.gateway.off(),this.remove()},submitForm:function(e){return e.preventDefault(),$("input.nick",this.$el).val().trim()?("nick_change"===this.state?this.submitNickChange(e):this.submitLogin(e),void $("button",this.$el).attr("disabled",1)):(this.setStatus(m.global.i18n.translate("client_views_serverselect_nickname_error_empty").fetch()),void $("input.nick",this.$el).select())},submitLogin:function(){if(!$("button",this.$el).attr("disabled")){var e={nick:$("input.nick",this.$el).val(),server:$("input.server",this.$el).val(),port:$("input.port",this.$el).val(),ssl:$("input.ssl",this.$el).prop("checked"),password:$("input.password",this.$el).val(),channel:$("input.channel",this.$el).val(),channel_key:$("input.channel_key",this.$el).val(),options:this.server_options};this.trigger("server_connect",e)}},submitNickChange:function(){m.gateway.changeNick(null,$("input.nick",this.$el).val()),this.networkConnecting()},showPass:function(){this.$el.find("tr.have_pass input").is(":checked")?this.$el.find("tr.pass").show().find("input").focus():this.$el.find("tr.pass").hide().find("input").val("")},channelKeyIconClick:function(){this.$el.find("tr.have_key input").click()},showKey:function(){this.$el.find("tr.have_key input").is(":checked")?this.$el.find("tr.key").show().find("input").focus():this.$el.find("tr.key").hide().find("input").val("")},showMore:function(){this.more_shown?($(".more",this.$el).slideUp("fast"),$(".show_more",this.$el).children(".fs-caret-up").removeClass("fa-caret-up").addClass("fa-caret-down"),$("input.nick",this.$el).select(),this.more_shown=!1):($(".more",this.$el).slideDown("fast"),$(".show_more",this.$el).children(".fa-caret-down").removeClass("fa-caret-down").addClass("fa-caret-up"),$("input.server",this.$el).select(),this.more_shown=!0)},populateFields:function(e){var t,n,i,s,a,o,c;e=e||{},t=e.nick||"",n=e.server||"",i=e.port||6667,o=e.ssl||0,c=e.password||"",s=e.channel||"",a=e.channel_key||"",$("input.nick",this.$el).val(t),$("input.server",this.$el).val(n),$("input.port",this.$el).val(i),$("input.ssl",this.$el).prop("checked",o),$("input#server_select_show_pass",this.$el).prop("checked",!!c),$("input.password",this.$el).val(c),c&&$("tr.pass",this.$el).show(),$("input.channel",this.$el).val(s),$("input#server_select_show_channel_key",this.$el).prop("checked",!!a),$("input.channel_key",this.$el).val(a),a&&$("tr.key",this.$el).show(),this.server_options={},e.encoding&&(this.server_options.encoding=e.encoding)},hide:function(){this.$el.slideUp()},show:function(e){e=e||"all",this.$el.show(),"all"===e?$(".show_more",this.$el).show():"more"===e?$(".more",this.$el).slideDown("fast"):"nick_change"===e?($(".more",this.$el).hide(),$(".show_more",this.$el).hide(),$("input.nick",this.$el).select()):"enter_password"===e&&($(".more",this.$el).hide(),$(".show_more",this.$el).hide(),$("input.password",this.$el).select()),this.state=e},infoBoxShow:function(){var e=this.$el.find(".side_panel");e.is(":visible")&&this.$el.animate({width:parseInt(e.css("left"),10)+e.find(".content:first").outerWidth()})},infoBoxHide:function(){var e=this.$el.find(".side_panel");this.$el.animate({width:parseInt(e.css("left"),10)})},infoBoxSet:function(e){this.$el.find(".side_panel .content").empty().append(e)},setStatus:function(e,t){$(".status",this.$el).text(e).attr("class","status").addClass(t||"").show()},clearStatus:function(){$(".status",this.$el).hide()},reset:function(){this.populateFields(),this.clearStatus(),this.$("button").attr("disabled",null)},newNetwork:function(e){this.model.current_connecting_network=e},networkConnected:function(e){this.model.trigger("connected",m.app.connections.getByConnectionId(e.server)),this.model.current_connecting_network=null},networkDisconnected:function(){this.model.current_connecting_network=null,this.state="all"},networkConnecting:function(){this.model.trigger("connecting"),this.setStatus(m.global.i18n.translate("client_views_serverselect_connection_trying").fetch(),"ok"),this.$(".status").append('<a class="show_server"><i class="fa fa-info-circle"></i></a>')},showServer:function(){this.model.current_connecting_network&&(m.app.view.barsShow(),this.model.current_connecting_network.panels.server.view.show())},onIrcError:function(e){switch($("button",this.$el).attr("disabled",null),e.error){case"nickname_in_use":this.setStatus(m.global.i18n.translate("client_views_serverselect_nickname_error_alreadyinuse").fetch()),this.show("nick_change"),this.$el.find(".nick").select();break;case"erroneus_nickname":this.setStatus(e.reason?e.reason:m.global.i18n.translate("client_views_serverselect_nickname_invalid").fetch()),this.show("nick_change"),this.$el.find(".nick").select();break;case"password_mismatch":this.setStatus(m.global.i18n.translate("client_views_serverselect_password_incorrect").fetch()),this.show("enter_password"),this.$el.find(".password").select();break;default:this.showError(e.reason||"")}},showError:function(e){var t=m.global.i18n.translate("client_views_serverselect_connection_error").fetch();if(e)switch(e){case"ENOTFOUND":t=m.global.i18n.translate("client_views_serverselect_server_notfound").fetch();break;case"ECONNREFUSED":t+=" ("+m.global.i18n.translate("client_views_serverselect_connection_refused").fetch()+")";break;default:t+=" ("+e+")"}this.setStatus(t,"error"),$("button",this.$el).attr("disabled",null),this.show()}}),m.view.StatusMessage=Backbone.View.extend({initialize:function(){this.$el.hide(),this.tmr=null},text:function(e,t){t=t||{},t.type=t.type||"",t.timeout=t.timeout||5e3,this.$el.text(e).addClass(t.type),this.$el.slideDown($.proxy(m.app.view.doLayout,m.app.view)),t.timeout&&this.doTimeout(t.timeout)},html:function(e,t){t=t||{},t.type=t.type||"",t.timeout=t.timeout||5e3,this.$el.html(e).addClass(t.type),this.$el.slideDown($.proxy(m.app.view.doLayout,m.app.view)),t.timeout&&this.doTimeout(t.timeout)},hide:function(){this.$el.slideUp($.proxy(m.app.view.doLayout,m.app.view))},doTimeout:function(e){this.tmr&&clearTimeout(this.tmr);var t=this;this.tmr=setTimeout(function(){t.hide()},e)}}),m.view.Tabs=Backbone.View.extend({tagName:"ul",className:"panellist",events:{"click li":"tabClick","click li .part":"partClick"},initialize:function(){this.model.on("add",this.panelAdded,this),this.model.on("remove",this.panelRemoved,this),this.model.on("reset",this.render,this),this.model.on("active",this.panelActive,this),this.is_network=!1,this.model.network&&(this.is_network=!0,this.model.network.on("change:name",function(e,t){$("span",this.model.server.tab).text(t)},this),this.model.network.on("change:connection_id",function(e,t){this.model.forEach(function(e){e.tab.data("connection_id",t)})},this))},render:function(){var e=this;this.$el.empty(),this.is_network&&this.model.server.tab.data("panel",this.model.server).data("connection_id",this.model.network.get("connection_id")).appendTo(this.$el),this.model.forEach(function(t){this.is_network&&t==e.model.server||(t.tab.data("panel",t),this.is_network&&t.tab.data("connection_id",this.model.network.get("connection_id")),t.tab.appendTo(e.$el))}),m.app.view.doLayout()},updateTabTitle:function(e,t){$("span",e.tab).text(t)},panelAdded:function(e){e.tab=$('<li><span></span><div class="activity"></div></li>'),e.tab.find("span").text(e.get("title")||e.get("name")),e.isServer()&&(e.tab.addClass("server"),e.tab.addClass("fa"),e.tab.addClass("fa-nonexistant")),e.tab.data("panel",e),this.is_network&&e.tab.data("connection_id",this.model.network.get("connection_id")),this.sortTabs(),e.bind("change:title",this.updateTabTitle),e.bind("change:name",this.updateTabTitle),m.app.view.doLayout()},panelRemoved:function(e){m.app.connections.active_connection;e.tab.remove(),delete e.tab,m.app.panels.trigger("remove",e),m.app.view.doLayout()},panelActive:function(e){m.app.view.$el.find(".panellist .part").remove(),m.app.view.$el.find(".panellist .active").removeClass("active"),e.tab.addClass("active"),e.tab.append('<span class="part fa fa-nonexistant"></span>')},tabClick:function(e){var t=$(e.currentTarget),n=t.data("panel");n&&n.view.show()},partClick:function(e){var t=$(e.currentTarget).parent(),n=t.data("panel");n&&(n.isChannel()&&n.get("members").models.length>0?this.model.network.gateway.part(n.get("name")):n.isServer()?(!this.model.network.get("connected")||confirm(d("disconnect_from_server")))&&(this.model.network.gateway.quit("Leaving"),m.app.connections.remove(this.model.network),m.app.startup_applet.view.show()):n.close())},sortTabs:function(){var e=this,t=[];this.model.forEach(function(n){e.is_network&&n==e.model.server||t.push([n.get("title")||n.get("name"),n])}),t.sort(function(e,t){return e[0].toLowerCase()>t[0].toLowerCase()?1:e[0].toLowerCase()<t[0].toLowerCase()?-1:0}),_.each(t,function(t){t[1].tab.appendTo(e.$el)})}}),m.view.TopicBar=Backbone.View.extend({events:{"keydown div":"process"},initialize:function(){m.app.panels.bind("active",function(e){e.isChannel()?(this.setCurrentTopicFromChannel(e),this.$el.find("div").attr("contentEditable",!0)):this.$el.find("div").attr("contentEditable",!1).text("")},this)},process:function(e){var t=$(e.currentTarget),n=t.text();return m.app.panels().active.isChannel()?13===e.keyCode?(m.app.connections.active_connection.gateway.topic(m.app.panels().active.get("name"),n),!1):void 0:!1},setCurrentTopic:function(e){e=e||"",$("div",this.$el).html(l(_.escape(e)))},setCurrentTopicFromChannel:function(e){var t=e.get("topic_set_by"),n="";this.setCurrentTopic(e.get("topic")),t?(n+=d("client_models_network_topic",[t.nick,m.utils.formatDate(t.when)]),this.$el.attr("title",n)):this.$el.attr("title","")}}),m.view.UserBox=Backbone.View.extend({events:{"click .query":"queryClick","click .info":"infoClick","change .ignore":"ignoreChange","click .ignore":"ignoreClick","click .op":"opClick","click .deop":"deopClick","click .voice":"voiceClick","click .devoice":"devoiceClick","click .kick":"kickClick","click .ban":"banClick"},initialize:function(){var e={op:m.global.i18n.translate("client_views_userbox_op").fetch(),de_op:m.global.i18n.translate("client_views_userbox_deop").fetch(),voice:m.global.i18n.translate("client_views_userbox_voice").fetch(),de_voice:m.global.i18n.translate("client_views_userbox_devoice").fetch(),kick:m.global.i18n.translate("client_views_userbox_kick").fetch(),ban:m.global.i18n.translate("client_views_userbox_ban").fetch(),message:m.global.i18n.translate("client_views_userbox_query").fetch(),info:m.global.i18n.translate("client_views_userbox_whois").fetch(),ignore:m.global.i18n.translate("client_views_userbox_ignore").fetch()};this.$el=$(_.template($("#tmpl_userbox").html().trim(),e))},setTargets:function(e,t){this.user=e,this.channel=t;var n=m.app.connections.active_connection.isNickIgnored(this.user.get("nick"));this.$(".ignore input").attr("checked",n?"checked":!1)},displayOpItems:function(e){e?this.$el.find(".if_op").css("display","block"):this.$el.find(".if_op").css("display","none")},queryClick:function(){var e=this.user.get("nick");m.app.connections.active_connection.createQuery(e)},infoClick:function(){m.app.controlbox.processInput("/whois "+this.user.get("nick"))},ignoreClick:function(e){e.stopPropagation()},ignoreChange:function(e){m.app.controlbox.processInput($(e.currentTarget).find("input").is(":checked")?"/ignore "+this.user.get("nick"):"/unignore "+this.user.get("nick"))},opClick:function(){m.app.controlbox.processInput("/mode "+this.channel.get("name")+" +o "+this.user.get("nick"))},deopClick:function(){m.app.controlbox.processInput("/mode "+this.channel.get("name")+" -o "+this.user.get("nick"))},voiceClick:function(){m.app.controlbox.processInput("/mode "+this.channel.get("name")+" +v "+this.user.get("nick"))},devoiceClick:function(){m.app.controlbox.processInput("/mode "+this.channel.get("name")+" -v "+this.user.get("nick"))},kickClick:function(){m.app.controlbox.processInput("/kick "+this.user.get("nick")+" Bye!")},banClick:function(){m.app.controlbox.processInput("/mode "+this.channel.get("name")+" +b "+this.user.get("nick")+"!*")}}),m.view.ChannelTools=Backbone.View.extend({events:{"click .channel_info":"infoClick","click .channel_part":"partClick"},initialize:function(){},infoClick:function(){new m.model.ChannelInfo({channel:m.app.panels().active})},partClick:function(){m.app.connections.active_connection.gateway.part(m.app.panels().active.get("name"))}}),m.view.ChannelInfo=Backbone.View.extend({events:{"click .toggle_banlist":"toggleBanList","change .channel-mode":"onModeChange","click .remove-ban":"onRemoveBanClick"},initialize:function(){var e,t=this.model.get("channel");e={moderated_chat:d("client_views_channelinfo_moderated"),invite_only:d("client_views_channelinfo_inviteonly"),ops_change_topic:d("client_views_channelinfo_opschangechannel"),external_messages:d("client_views_channelinfo_externalmessages"),toggle_banlist:d("client_views_channelinfo_togglebanlist"),channel_name:t.get("name")},this.$el=$(_.template($("#tmpl_channel_info").html().trim(),e)),this.menu=new m.view.MenuBox(t.get("name")),this.menu.addItem("channel_info",this.$el),this.menu.$el.appendTo(t.view.$container),this.menu.show(),this.menu.$el.offset({top:m.app.view.$el.find(".panels").offset().top}),this.$el.dispose=_.bind(this.dispose,this),this.updateInfo(t),t.on("change:info_modes change:info_url change:banlist",this.updateInfo,this),t.get("network").gateway.channelInfo(t.get("name"))},render:function(){},onModeChange:function(e){var t=$(e.currentTarget),n=this.model.get("channel"),i=t.data("mode"),s="";
+return"checkbox"==t.attr("type")?(s=t.is(":checked")?"+":"-",s+=i,void n.setMode(s)):"text"==t.attr("type")?(s=t.val()?"+"+i+" "+t.val():"-"+i,void n.setMode(s)):void 0},onRemoveBanClick:function(e){e.preventDefault(),e.stopPropagation();var t=$(e.currentTarget),n=t.parents("tr:first"),i=n.data("ban");if(i){var s=this.model.get("channel");s.setMode("-b "+i.banned),n.remove()}},updateInfo:function(e){var t,n,i,s=this;if(t=e.get("info_modes"),t&&_.each(t,function(e){e.mode=e.mode.toLowerCase(),"+k"==e.mode?s.$el.find('[name="channel_key"]').val(e.param):"+m"==e.mode?s.$el.find('[name="channel_mute"]').attr("checked","checked"):"+i"==e.mode?s.$el.find('[name="channel_invite"]').attr("checked","checked"):"+n"==e.mode?s.$el.find('[name="channel_external_messages"]').attr("checked","checked"):"+t"==e.mode&&s.$el.find('[name="channel_topic"]').attr("checked","checked")}),n=e.get("info_url"),n&&(this.$el.find(".channel_url").text(n).attr("href",n),this.$el.find(".channel_url").slideDown()),i=e.get("banlist"),i&&i.length){var a=this.$el.find(".channel-banlist table tbody");this.$el.find(".banlist-status").text(""),a.empty(),_.each(i,function(e){var t=$("<tr></tr>").data("ban",e);$("<td></td>").text(e.banned).appendTo(t),$("<td></td>").text(e.banned_by.split(/[!@]/)[0]).appendTo(t),$("<td></td>").text(m.utils.formatDate(new Date(1e3*parseInt(e.banned_at,10)))).appendTo(t),$('<td><i class="fa fa-rtimes remove-ban"></i></td>').appendTo(t),a.append(t)}),this.$el.find(".channel-banlist table").slideDown()}else this.$el.find(".banlist-status").text("Banlist empty"),this.$el.find(".channel-banlist table").hide()},toggleBanList:function(e){if(e.preventDefault(),this.$el.find(".channel-banlist table").toggle(),this.$el.find(".channel-banlist table").is(":visible")){var t=this.model.get("channel"),n=t.get("network");n.gateway.raw("MODE "+t.get("name")+" +b")}},dispose:function(){this.model.get("channel").off("change:info_modes change:info_url change:banlist",this.updateInfo,this),this.$el.remove()}}),m.view.RightBar=Backbone.View.extend({events:{"click .right-bar-toggle":"onClickToggle","click .right-bar-toggle-inner":"onClickToggle"},initialize:function(){this.keep_hidden=!1,this.hidden=this.$el.hasClass("disabled"),this.updateIcon()},hide:function(){this.hidden=!0,this.$el.addClass("disabled"),this.updateIcon()},show:function(){this.hidden=!1,this.keep_hidden||this.$el.removeClass("disabled"),this.updateIcon()},toggle:function(e){return this.ignore_layout?!0:(this.keep_hidden="undefined"==typeof e?!this.keep_hidden:e,this.keep_hidden||this.hidden?this.$el.addClass("disabled"):this.$el.removeClass("disabled"),void this.updateIcon())},updateIcon:function(){var e=this.$(".right-bar-toggle"),t=e.find("i");!this.hidden&&this.keep_hidden?e.show():e.hide(),this.keep_hidden?t.removeClass("fa fa-angle-double-right").addClass("fa fa-users"):t.removeClass("fa fa-users").addClass("fa fa-angle-double-right")},onClickToggle:function(){this.toggle(),this.ignore_layout=!0,m.app.view.doLayout(),delete this.ignore_layout}}),m.view.Notification=Backbone.View.extend({className:"notification",events:{"click .close":"close"},initialize:function(e,t){this.title=e,this.content=t},render:function(){return this.$el.html($("#tmpl_notifications").html()),this.$("h6").text(this.title),"string"==typeof this.content?this.$(".content").html(this.content):"object"==typeof this.content&&this.$(".content").empty().append(this.content),this},show:function(){var e=this;this.render().$el.appendTo(m.app.view.$el),_.defer(function(){e.$el.addClass("show")})},close:function(){this.remove()}}),function(){function e(e,t){this.app=e,this.controlbox=t,this.addDefaultAliases(),this.bindCommand(L)}function n(e){var t=e.command+" "+e.params.join(" ");this.app.connections.active_connection.gateway.raw(t)}function i(){}function s(e){var t,n;n=e.params.join(" ").split(","),t=this.app.connections.active_connection.createAndJoinChannels(n),t.length&&t[t.length-1].view.show()}function a(e){var t,n,i;t=e.params[0],e.params.shift(),n=e.params.join(" "),i=this.app.connections.active_connection.panels.getByName(t),i||(i=new m.model.Query({name:t}),this.app.connections.active_connection.panels.add(i)),i&&i.view.show(),n&&(this.app.connections.active_connection.gateway.msg(i.get("name"),n),i.addMsg(this.app.connections.active_connection.get("nick"),u("privmsg",{text:n}),"privmsg"))}function o(e){var t,n=e.params[0],i=this.app.connections.active_connection.panels.getByName(n)||this.app.panels().server;e.params.shift(),t=e.params.join(" "),i.addMsg(this.app.connections.active_connection.get("nick"),u("privmsg",{text:t}),"privmsg"),this.app.connections.active_connection.gateway.msg(n,t)}function c(e){if(!this.app.panels().active.isServer()){var t=this.app.panels().active;t.addMsg("",u("action",{nick:this.app.connections.active_connection.get("nick"),text:e.params.join(" ")}),"action"),this.app.connections.active_connection.gateway.action(t.get("name"),e.params.join(" "))}}function l(e){var t,n,i=this;0===e.params.length?this.app.connections.active_connection.gateway.part(this.app.panels().active.get("name")):(t=e.params[0].split(","),n=e.params[1],_.each(t,function(e){i.connections.active_connection.gateway.part(e,n)}))}function r(e){var t,n=this;t=0===e.params.length?this.app.panels().active.get("name"):e.params[0],this.app.connections.active_connection.gateway.part(t),setTimeout(function(){n.app.connections.active_connection.createAndJoinChannels(t),n.app.connections.active_connection.panels.getByName(t).show()},1e3)}function h(e){this.app.connections.active_connection.gateway.changeNick(e.params[0])}function p(e){var t;0!==e.params.length&&(this.app.connections.active_connection.isChannelName(e.params[0])?(t=e.params[0],e.params.shift()):t=this.app.panels().active.get("name"),this.app.connections.active_connection.gateway.topic(t,e.params.join(" ")))}function g(e){var t;e.params.length<=1||(t=e.params[0],e.params.shift(),this.app.connections.active_connection.gateway.notice(t,e.params.join(" ")))}function f(e){var t=e.params.join(" ");this.app.connections.active_connection.gateway.raw(t)}function v(e){var t,n=this.app.panels().active;n.isChannel()&&0!==e.params.length&&(t=e.params[0],e.params.shift(),this.app.connections.active_connection.gateway.kick(n.get("name"),t,e.params.join(" ")))}function w(){this.app.panels().active.isServer()||this.app.panels().active.isApplet()||this.app.panels().active.clearMessages&&this.app.panels().active.clearMessages()}function k(e){var t,n;e.params.length<2||(t=e.params[0],e.params.shift(),n=e.params[0],e.params.shift(),this.app.connections.active_connection.gateway.ctcpRequest(n,t,e.params.join(" ")))}function b(){var e=m.model.Applet.loadOnce("kiwi_settings");e.view.show()}function y(){var e=m.model.Applet.loadOnce("kiwi_script_editor");e.view.show()}function C(e){if(e.params[0]){var t=new m.model.Applet;if(e.params[1])t.load(e.params[0],e.params[1]);else{if(!this.applets[e.params[0]])return void this.app.panels().server.addMsg("",u("applet_notfound",{text:d("client_models_application_applet_notfound",[e.params[0]])}));t.load(new this.applets[e.params[0]])}this.app.connections.active_connection.panels.add(t),t.view.show()}}function x(e){var t,n;e.params[0]&&this.app.panels().active.isChannel()&&(t=e.params[0],n=this.app.panels().active.get("name"),this.app.connections.active_connection.gateway.raw("INVITE "+t+" "+n),this.app.panels().active.addMsg("",u("channel_has_been_invited",{nick:t,text:d("client_models_application_has_been_invited",[n])}),"action"))}function M(e){var t;e.params[0]?t=e.params[0]:this.app.panels().active.isQuery()&&(t=this.app.panels().active.get("name")),t&&this.app.connections.active_connection.gateway.raw("WHOIS "+t+" "+t)}function S(e){var t;e.params[0]?t=e.params[0]:this.app.panels().active.isQuery()&&(t=this.app.panels().active.get("name")),t&&this.app.connections.active_connection.gateway.raw("WHOWAS "+t)}function T(e){this.app.connections.active_connection.gateway.raw("AWAY :"+e.params.join(" "))}function N(e){var t=this;e.params[0]?m.gateway.setEncoding(null,e.params[0],function(n){n?t.app.panels().active.addMsg("",u("encoding_changed",{text:d("client_models_application_encoding_changed",[e.params[0]])})):t.app.panels().active.addMsg("",u("encoding_invalid",{text:d("client_models_application_encoding_invalid",[e.params[0]])}))}):(this.app.panels().active.addMsg("",u("client_models_application_encoding_notspecified",{text:d("client_models_application_encoding_notspecified")})),this.app.panels().active.addMsg("",u("client_models_application_encoding_usage",{text:d("client_models_application_encoding_usage")})))}function B(){var e=this.app.panels().active;e.isChannel()&&new m.model.ChannelInfo({channel:this.app.panels().active})}function D(e){var t=this.app.connections.active_connection;t&&t.gateway.quit(e.params.join(" "))}function I(e){var n,i,s,a,o,c,l=this;return e.params[0]?(e.params[0].indexOf(":")>0?(c=e.params[0].split(":"),n=c[0],i=c[1],a=e.params[1]||t):(n=e.params[0],i=e.params[1]||6667,a=e.params[2]||t),"+"===i.toString()[0]?(s=!0,i=parseInt(i.substring(1),10)):s=!1,i=i||6667,o=this.app.connections.active_connection.get("nick"),this.app.panels().active.addMsg("",u("server_connecting",{text:d("client_models_application_connection_connecting",[n,i.toString()])})),void m.gateway.newConnection({nick:o,host:n,port:i,ssl:s,password:a},function(e){var t;e&&(t=d("client_models_application_connection_error",[n,i.toString(),e.toString()]),l.app.panels().active.addMsg("",u("server_connecting_error",{text:t})))})):(c=new m.view.MenuBox(m.global.i18n.translate("client_models_application_connection_create").fetch()),c.addItem("new_connection",(new m.model.NewConnection).view.$el),c.show(),void c.$el.offset({top:this.app.view.$el.height()/2-c.$el.height()/2,left:this.app.view.$el.width()/2-c.$el.width()/2}))}m.misc.ClientUiCommands=e,e.prototype.addDefaultAliases=function(){$.extend(this.controlbox.preprocessor.aliases,{"/p":"/part $1+","/me":"/action $1+","/j":"/join $1+","/q":"/query $1+","/w":"/whois $1+","/raw":"/quote $1+","/connect":"/server $1+","/op":"/quote mode $channel +o $1+","/deop":"/quote mode $channel -o $1+","/hop":"/quote mode $channel +h $1+","/dehop":"/quote mode $channel -h $1+","/voice":"/quote mode $channel +v $1+","/devoice":"/quote mode $channel -v $1+","/k":"/kick $channel $1+","/ban":"/quote mode $channel +b $1+","/unban":"/quote mode $channel -b $1+","/slap":"/me slaps $1 around a bit with a large trout","/tick":"/msg $channel ✔"})},e.prototype.bindCommand=function(e){var t=this;_.each(e,function(e,n){t.controlbox.on(n,_.bind(e,t))})};var L={unknown_command:n,command:i,"command:msg":o,"command:action":c,"command:join":s,"command:part":l,"command:cycle":r,"command:nick":h,"command:query":a,"command:invite":x,"command:topic":p,"command:notice":g,"command:quote":f,"command:kick":v,"command:clear":w,"command:ctcp":k,"command:quit":D,"command:server":I,"command:whois":M,"command:whowas":S,"command:away":T,"command:encoding":N,"command:channel":B,"command:applet":C,"command:settings":b,"command:script":y};L["command:css"]=function(){var e="?reload="+(new Date).getTime();$('link[rel="stylesheet"]').each(function(){this.href=this.href.replace(/\?.*|$/,e)})},L["command:js"]=function(e){e.params[0]&&$script(e.params[0]+"?"+(new Date).getTime())},L["command:set"]=function(e){if(e.params[0]){var t,n=e.params[0];e.params[1]&&(e.params.shift(),t=e.params.join(" "),"true"===t&&(t=!0),"false"===t&&(t=!1),parseInt(t,10).toString()===t&&(t=parseInt(t,10)),m.global.settings.set(n,t)),this.app.panels().active.addMsg("",u("set_setting",{text:n+" = "+m.global.settings.get(n).toString()}))}},L["command:save"]=function(){m.global.settings.save(),this.app.panels().active.addMsg("",u("settings_saved",{text:d("client_models_application_settings_saved")}))},L["command:alias"]=function(e){var t,n,i=this;return e.params[1]?"del"===e.params[0]||"delete"===e.params[0]?(t=e.params[1],"/"!==t[0]&&(t="/"+t),void delete this.controlbox.preprocessor.aliases[t]):(t=e.params[0],e.params.shift(),n=e.params.join(" "),"/"!==t[0]&&(t="/"+t),void(this.controlbox.preprocessor.aliases[t]=n)):void $.each(this.controlbox.preprocessor.aliases,function(e,t){i.app.panels().server.addMsg(" ",u("list_aliases",{text:e+" => "+t}))})},L["command:ignore"]=function(e){var t=this,n=this.app.connections.active_connection.get("ignore_list");return e.params[0]?(n.push(e.params[0]),this.app.connections.active_connection.set("ignore_list",n),void this.app.panels().active.addMsg(" ",u("ignore_nick",{text:d("client_models_application_ignore_nick",[e.params[0]])}))):void(n.length>0?(this.app.panels().active.addMsg(" ",u("ignore_title",{text:d("client_models_application_ignore_title")})),$.each(n,function(e,n){t.app.panels().active.addMsg(" ",u("ignored_pattern",{text:n}))})):this.app.panels().active.addMsg(" ",u("ignore_none",{text:d("client_models_application_ignore_none")})))},L["command:unignore"]=function(e){var t=this.app.connections.active_connection.get("ignore_list");return e.params[0]?(t=_.reject(t,function(t){return t===e.params[0]}),this.app.connections.active_connection.set("ignore_list",t),void this.app.panels().active.addMsg(" ",u("ignore_stopped",{text:d("client_models_application_ignore_stopped",[e.params[0]])}))):void this.app.panels().active.addMsg(" ",u("ignore_stop_notice",{text:d("client_models_application_ignore_stop_notice")}))}}(),function(){var e=Backbone.View.extend({events:{"change [data-setting]":"saveSettings",'click [data-setting="theme"]':"selectTheme","click .register_protocol":"registerProtocol","click .enable_notifications":"enableNotifications"},initialize:function(){var e={tabs:d("client_applets_settings_channelview_tabs"),list:d("client_applets_settings_channelview_list"),large_amounts_of_chans:d("client_applets_settings_channelview_list_notice"),join_part:d("client_applets_settings_notification_joinpart"),count_all_activity:d("client_applets_settings_notification_count_all_activity"),timestamps:d("client_applets_settings_timestamp"),timestamp_24:d("client_applets_settings_timestamp_24_hour"),mute:d("client_applets_settings_notification_sound"),emoticons:d("client_applets_settings_emoticons"),scroll_history:d("client_applets_settings_history_length"),languages:m.app.translations,default_client:d("client_applets_settings_default_client"),make_default:d("client_applets_settings_default_client_enable"),locale_restart_needed:d("client_applets_settings_locale_restart_needed"),default_note:d("client_applets_settings_default_client_notice",'<a href="chrome://settings/handlers">chrome://settings/handlers</a>'),html5_notifications:d("client_applets_settings_html5_notifications"),enable_notifications:d("client_applets_settings_enable_notifications"),theme_thumbnails:_.map(m.app.themes,function(e){return _.template($("#tmpl_theme_thumbnail").html().trim(),e)})};this.$el=$(_.template($("#tmpl_applet_settings").html().trim(),e)),navigator.registerProtocolHandler||this.$(".protocol_handler").remove(),null!==m.utils.notifications.allowed()&&this.$(".notification_enabler").remove(),m.global.settings.on("change",this.loadSettings,this),this.loadSettings()},loadSettings:function(){_.each(m.global.settings.attributes,function(e,t){var n=this.$('[data-setting="'+t+'"]');if(n.length)switch(n.prop("type")){case"checkbox":n.prop("checked",e);break;case"radio":this.$('[data-setting="'+t+'"][value="'+e+'"]').prop("checked",!0);break;case"text":n.val(e);break;case"select-one":this.$('[value="'+e+'"]').prop("selected",!0);break;default:this.$('[data-setting="'+t+'"][data-value="'+e+'"]').addClass("active")}},this)},saveSettings:function(e){var t,n=m.global.settings,i=$(e.currentTarget);switch(e.currentTarget.type){case"checkbox":t=i.is(":checked");break;case"radio":case"text":t=i.val();break;case"select-one":t=$(e.currentTarget[i.prop("selectedIndex")]).val();break;default:t=i.data("value")}m.global.settings.off("change",this.loadSettings,this),n.set(i.data("setting"),t),n.save(),m.global.settings.on("change",this.loadSettings,this)},selectTheme:function(e){e.preventDefault(),this.$('[data-setting="theme"].active').removeClass("active"),$(e.currentTarget).addClass("active").trigger("change")},registerProtocol:function(e){e.preventDefault(),navigator.registerProtocolHandler("irc",document.location.origin+m.app.get("base_path")+"/%s","Kiwi IRC"),navigator.registerProtocolHandler("ircs",document.location.origin+m.app.get("base_path")+"/%s","Kiwi IRC")},enableNotifications:function(e){e.preventDefault();var t=m.utils.notifications;t.requestPermission().always(_.bind(function(){null!==t.allowed()&&this.$(".notification_enabler").remove()},this))}}),t=Backbone.Model.extend({initialize:function(){this.set("title",d("client_applets_settings_title")),this.view=new e}});m.model.Applet.register("kiwi_settings",t)}(),function(){var e=Backbone.View.extend({events:{"click .chan":"chanClick","click .channel_name_title":"sortChannelsByNameClick","click .users_title":"sortChannelsByUsersClick"},initialize:function(){var e={channel_name:m.global.i18n.translate("client_applets_chanlist_channelname").fetch(),users:m.global.i18n.translate("client_applets_chanlist_users").fetch(),topic:m.global.i18n.translate("client_applets_chanlist_topic").fetch()};this.$el=$(_.template($("#tmpl_channel_list").html().trim(),e)),this.channels=[],this.order="",this.waiting=!1},render:function(){var e,t=$("table",this.$el),n=t.children("tbody:first").detach();switch(0==$(".applet_chanlist .users_title").find("span.chanlist_sort_users").length?this.$(".users_title").append('<span class="chanlist_sort_users"> </span>'):(this.$(".users_title span.chanlist_sort_users").removeClass("fa fa-sort-desc"),this.$(".users_title span.chanlist_sort_users").removeClass("fa fa-sort-asc")),0==$(".applet_chanlist .channel_name_title").find("span.chanlist_sort_names").length?this.$(".channel_name_title").append('<span class="chanlist_sort_names"> </span>'):(this.$(".channel_name_title span.chanlist_sort_names").removeClass("fa fa-sort-desc"),this.$(".channel_name_title span.chanlist_sort_names").removeClass("fa fa-sort-asc")),this.order){case"user_desc":default:this.$(".users_title span.chanlist_sort_users").addClass("fa fa-sort-asc");break;case"user_asc":this.$(".users_title span.chanlist_sort_users").addClass("fa fa-sort-desc");break;case"name_asc":this.$(".channel_name_title span.chanlist_sort_names").addClass("fa fa-sort-desc");break;case"name_desc":this.$(".channel_name_title span.chanlist_sort_names").addClass("fa fa-sort-asc")}for(this.channels=this.sortChannels(this.channels,this.order),e=0;e<this.channels.length;e++)n[0].appendChild(this.channels[e].dom);t[0].appendChild(n[0])},chanClick:function(e){e.target?m.gateway.join(null,$(e.target).data("channel")):m.gateway.join(null,$(e.srcElement).data("channel"))},sortChannelsByNameClick:function(){this.order="name_asc"==this.order?"name_desc":"name_asc",this.sortChannelsClick()},sortChannelsByUsersClick:function(){this.order="user_desc"==this.order||""==this.order?"user_asc":"user_desc",this.sortChannelsClick()},sortChannelsClick:function(){this.render()},sortChannels:function(e,t){var n=[],i=[];return _.each(e,function(e,t){n.push({chan_idx:t,num_users:e.num_users,channel:e.channel})}),n.sort(function(e,n){switch(t){case"user_asc":return e.num_users-n.num_users;case"user_desc":return n.num_users-e.num_users;case"name_asc":if(e.channel.toLowerCase()>n.channel.toLowerCase())return 1;if(e.channel.toLowerCase()<n.channel.toLowerCase())return-1;case"name_desc":if(e.channel.toLowerCase()<n.channel.toLowerCase())return 1;if(e.channel.toLowerCase()>n.channel.toLowerCase())return-1;default:return n.num_users-e.num_users}return 0}),_.each(n,function(t){i.push(e[t.chan_idx])}),i}}),t=Backbone.Model.extend({initialize:function(){this.set("title",m.global.i18n.translate("client_applets_chanlist_channellist").fetch()),this.view=new e,this.network=m.global.components.Network(),this.network.on("list_channel",this.onListChannel,this),this.network.on("list_start",this.onListStart,this)},onListChannel:function(e){this.addChannel(e.chans)},onListStart:function(){},addChannel:function(e){var t=this;_.isArray(e)||(e=[e]),_.each(e,function(e){var n;n=document.createElement("tr"),n.innerHTML='<td class="chanlist_name"><a class="chan" data-channel="'+e.channel+'">'+_.escape(e.channel)+'</a></td><td class="chanlist_num_users" style="text-align: center;">'+e.num_users+'</td><td style="padding-left: 2em;" class="chanlist_topic">'+l(_.escape(e.topic))+"</td>",e.dom=n,t.view.channels.push(e)}),t.view.waiting||(t.view.waiting=!0,_.defer(function(){t.view.render(),t.view.waiting=!1}))},dispose:function(){this.view.channels=null,this.view.unbind(),this.view.$el.html(""),this.view.remove(),this.view=null,this.network.off()}});m.model.Applet.register("kiwi_chanlist",t)}(),function(){var e=Backbone.View.extend({events:{"click .btn_save":"onSave"},initialize:function(){var e=this,t={save:m.global.i18n.translate("client_applets_scripteditor_save").fetch()};this.$el=$(_.template($("#tmpl_script_editor").html().trim(),t)),this.model.on("applet_loaded",function(){e.$el.parent().css("height","100%"),$script(m.app.get("base_path")+"/assets/libs/ace/ace.js",function(){e.createAce()})})},createAce:function(){var e="editor_"+Math.floor(1e7*Math.random()).toString();this.editor_id=e,this.$el.find(".editor").attr("id",e),this.editor=ace.edit(e),this.editor.setTheme("ace/theme/monokai"),this.editor.getSession().setMode("ace/mode/javascript");var t=m.global.settings.get("user_script")||"";this.editor.setValue(t)},onSave:function(){var e,t;e="var network = kiwi.components.Network();\n",e+="var input = kiwi.components.ControlInput();\n",e+="var events = kiwi.components.Events();\n",e+=this.editor.getValue()+"\n",e+="this._dispose = function(){ network.off(); input.off(); events.dispose(); if(this.dispose) this.dispose(); }";try{t=new Function(e),m.user_script&&m.user_script._dispose&&m.user_script._dispose(),m.user_script=new t}catch(n){return void this.setStatus(m.global.i18n.translate("client_applets_scripteditor_error").fetch(n.toString()))}m.global.settings.set("user_script",this.editor.getValue()),m.global.settings.save(),this.setStatus(m.global.i18n.translate("client_applets_scripteditor_saved").fetch()+" :)")},setStatus:function(e){var t=this.$el.find(".toolbar .status");e=e||"",t.slideUp("fast",function(){t.text(e),t.slideDown()})}}),t=Backbone.Model.extend({initialize:function(){this.set("title",m.global.i18n.translate("client_applets_scripteditor_title").fetch()),this.view=new e({model:this})}});m.model.Applet.register("kiwi_script_editor",t)}(),function(){var e=Backbone.View.extend({events:{},initialize:function(){this.showConnectionDialog()},showConnectionDialog:function(){var e=this.connection_dialog=new m.model.NewConnection;e.populateDefaultServerSettings(),e.view.$el.addClass("initial"),this.$el.append(e.view.$el);var t=$($("#tmpl_new_connection_info").html().trim());t.html()?e.view.infoBoxSet(t):t=null,this.listenTo(e,"connected",this.newConnectionConnected),_.defer(function(){t&&e.view.infoBoxShow(),window==window.top&&e.view.$el.find(".nick").select()})},newConnectionConnected:function(){this.connection_dialog.view.reset()}}),t=Backbone.Model.extend({initialize:function(){this.view=new e({model:this})}});m.model.Applet.register("kiwi_startup",t)}(),m.utils.notifications=function(){function e(e,n){t.call(this,e,n)}function t(e,i){switch(n.allowed()){case!0:this.notification=new Notification(e,i),_.each(["click","close","error","show"],function(e){this.notification["on"+e]=_.bind(this.trigger,this,e)},this);break;case null:n.requestPermission().done(_.bind(t,this,e,i))}}if(!window.Notification)return{allowed:_.constant(!1),requestPermission:_.constant($.Deferred().reject())};var n={allowed:function(){return"granted"===Notification.permission?!0:"denied"===Notification.permission?!1:null},requestPermission:function(){var e=$.Deferred();return Notification.requestPermission(function(t){e["granted"===t?"resolve":"reject"]()}),e.promise()},create:function(t,n){return new e(t,n)}};return _.extend(e.prototype,Backbone.Events,{closed:!1,_closeTimeout:null,closeAfter:function(e){return this.closed||(this.notification?this._closeTimeout=this._closeTimeout||setTimeout(_.bind(this.close,this),e):this.once("show",_.bind(this.closeAfter,this,e))),this},close:function(){return this.notification&&!this.closed&&(this.notification.close(),this.closed=!0),this}}),n}(),m.utils.formatDate=function(){var e,t,n,i,s=!1,a={d:function(){return(this.getDate()<10?"0":"")+this.getDate()},D:function(){return Date.shortDays[this.getDay()]},j:function(){return this.getDate()},l:function(){return Date.longDays[this.getDay()]},N:function(){return this.getDay()+1},S:function(){return this.getDate()%10==1&&11!=this.getDate()?"st":this.getDate()%10==2&&12!=this.getDate()?"nd":this.getDate()%10==3&&13!=this.getDate()?"rd":"th"},w:function(){return this.getDay()},z:function(){var e=new Date(this.getFullYear(),0,1);return Math.ceil((this-e)/864e5)},W:function(){var e=new Date(this.getFullYear(),0,1);return Math.ceil(((this-e)/864e5+e.getDay()+1)/7)},F:function(){return Date.longMonths[this.getMonth()]},m:function(){return(this.getMonth()<9?"0":"")+(this.getMonth()+1)},M:function(){return Date.shortMonths[this.getMonth()]},n:function(){return this.getMonth()+1},t:function(){var e=new Date;return new Date(e.getFullYear(),e.getMonth(),0).getDate()},L:function(){var e=this.getFullYear();return e%400==0||e%100!=0&&e%4==0},o:function(){var e=new Date(this.valueOf());return e.setDate(e.getDate()-(this.getDay()+6)%7+3),e.getFullYear()},Y:function(){return this.getFullYear()},y:function(){return(""+this.getFullYear()).substr(2)},a:function(){return this.getHours()<12?"am":"pm"},A:function(){return this.getHours()<12?"AM":"PM"},B:function(){return Math.floor(1e3*((this.getUTCHours()+1)%24+this.getUTCMinutes()/60+this.getUTCSeconds()/3600)/24)},g:function(){return this.getHours()%12||12},G:function(){return this.getHours()},h:function(){return((this.getHours()%12||12)<10?"0":"")+(this.getHours()%12||12)},H:function(){return(this.getHours()<10?"0":"")+this.getHours()},i:function(){return(this.getMinutes()<10?"0":"")+this.getMinutes()},s:function(){return(this.getSeconds()<10?"0":"")+this.getSeconds()},u:function(){var e=this.getMilliseconds();return(10>e?"00":100>e?"0":"")+e},e:function(){return"Not Yet Supported"},I:function(){for(var e=null,t=0;12>t;++t){var n=new Date(this.getFullYear(),t,1),i=n.getTimezoneOffset();if(null===e)e=i;else{if(e>i){e=i;break}if(i>e)break}}return this.getTimezoneOffset()==e|0},O:function(){return(-this.getTimezoneOffset()<0?"-":"+")+(Math.abs(this.getTimezoneOffset()/60)<10?"0":"")+Math.abs(this.getTimezoneOffset()/60)+"00"},P:function(){return(-this.getTimezoneOffset()<0?"-":"+")+(Math.abs(this.getTimezoneOffset()/60)<10?"0":"")+Math.abs(this.getTimezoneOffset()/60)+":00"},T:function(){var e=this.getMonth();this.setMonth(0);var t=this.toTimeString().replace(/^.+ \(?([^\)]+)\)?$/,"$1");return this.setMonth(e),t},Z:function(){return 60*-this.getTimezoneOffset()},c:function(){return this.format("Y-m-d\\TH:i:sP")},r:function(){return this.toString()},U:function(){return this.getTime()/1e3}},o=function(){e=[m.global.i18n.translate("client.libs.date_format.short_months.january").fetch(),m.global.i18n.translate("client.libs.date_format.short_months.february").fetch(),m.global.i18n.translate("client.libs.date_format.short_months.march").fetch(),m.global.i18n.translate("client.libs.date_format.short_months.april").fetch(),m.global.i18n.translate("client.libs.date_format.short_months.may").fetch(),m.global.i18n.translate("client.libs.date_format.short_months.june").fetch(),m.global.i18n.translate("client.libs.date_format.short_months.july").fetch(),m.global.i18n.translate("client.libs.date_format.short_months.august").fetch(),m.global.i18n.translate("client.libs.date_format.short_months.september").fetch(),m.global.i18n.translate("client.libs.date_format.short_months.october").fetch(),m.global.i18n.translate("client.libs.date_format.short_months.november").fetch(),m.global.i18n.translate("client.libs.date_format.short_months.december").fetch()],t=[m.global.i18n.translate("client.libs.date_format.long_months.january").fetch(),m.global.i18n.translate("client.libs.date_format.long_months.february").fetch(),m.global.i18n.translate("client.libs.date_format.long_months.march").fetch(),m.global.i18n.translate("client.libs.date_format.long_months.april").fetch(),m.global.i18n.translate("client.libs.date_format.long_months.may").fetch(),m.global.i18n.translate("client.libs.date_format.long_months.june").fetch(),m.global.i18n.translate("client.libs.date_format.long_months.july").fetch(),m.global.i18n.translate("client.libs.date_format.long_months.august").fetch(),m.global.i18n.translate("client.libs.date_format.long_months.september").fetch(),m.global.i18n.translate("client.libs.date_format.long_months.october").fetch(),m.global.i18n.translate("client.libs.date_format.long_months.november").fetch(),m.global.i18n.translate("client.libs.date_format.long_months.december").fetch()],n=[m.global.i18n.translate("client.libs.date_format.short_days.monday").fetch(),m.global.i18n.translate("client.libs.date_format.short_days.tuesday").fetch(),m.global.i18n.translate("client.libs.date_format.short_days.wednesday").fetch(),m.global.i18n.translate("client.libs.date_format.short_days.thursday").fetch(),m.global.i18n.translate("client.libs.date_format.short_days.friday").fetch(),m.global.i18n.translate("client.libs.date_format.short_days.saturday").fetch(),m.global.i18n.translate("client.libs.date_format.short_days.sunday").fetch()],i=[m.global.i18n.translate("client.libs.date_format.long_days.monday").fetch(),m.global.i18n.translate("client.libs.date_format.long_days.tuesday").fetch(),m.global.i18n.translate("client.libs.date_format.long_days.wednesday").fetch(),m.global.i18n.translate("client.libs.date_format.long_days.thursday").fetch(),m.global.i18n.translate("client.libs.date_format.long_days.friday").fetch(),m.global.i18n.translate("client.libs.date_format.long_days.saturday").fetch(),m.global.i18n.translate("client.libs.date_format.long_days.sunday").fetch()],s=!0};return function(e,t){return s||o(),e=e||new Date,t=t||m.global.i18n.translate("client_date_format").fetch(),t.replace(/(\\?)(.)/g,function(t,n,i){return""===n&&a[i]?a[i].call(e):i})}}(),n.prototype.on=function(e,t,n){this._listeners[e]=this._listeners[e]||[],this._listeners[e].push(["on",t,n])},n.prototype.once=function(e,t,n){this._listeners[e]=this._listeners[e]||[],this._listeners[e].push(["once",t,n])},n.prototype.off=function(e,t,n){var i;if("undefined"==typeof e)this._listeners={};else if("undefined"==typeof t)delete this._listeners[e];else if("undefined"==typeof n)for(i in this._listeners[e]||[])this._listeners[e][i][1]===t&&delete this._listeners[e][i];else for(i in this._listeners[e]||[])this._listeners[e][i][1]===t&&this._listeners[e][i][2]===n&&delete this._listeners[e][i]},n.prototype.getListeners=function(e){return this._listeners[e]||[]},n.prototype.createProxy=function(){var e=new n;return e._parent=this._parent||this,e._parent._children.push(e),e},n.prototype.dispose=function(){if(this.off(),this._parent){var e=this._parent._children.indexOf(this);e>-1&&this._parent._children.splice(e,1)}},n.prototype.emit=function(e,n){var i,s=new this.EmitCall(e,n),a=[];for(i=this._children.length-1;i>=0;i--)a=a.concat(this._children[i].getListeners(e));return a=a.concat(this.getListeners(e)),s.then(function(){var e,n=a.length;for(e=0;n>e;e++)"once"===a[e][0]&&(a[e]=t)}),s.callListeners(a),s},n.prototype.EmitCall=function(e,n){function i(e){function i(){var o,l;return a++,e[a]?(l={wait:!1,callback:function(){l.callback=t,i.apply(c)},preventDefault:function(){h=!0}},o=e[a],o[1].call(o[2]||c,l,n),void(l.wait||(l.callback=t,i()))):void s()
+}var a=-1;return n=n||t,0===e.length?void s():void i()}function s(){l=!0;var e=h?p:r;e=e||[];for(var t=0;t<e.length;t++)"function"==typeof e[t]&&e[t]()}function a(e){return"function"!=typeof e?!1:(r.push(e),l&&!h&&e(),this)}function o(e){return"function"!=typeof e?!1:(p.push(e),l&&h&&e(),this)}var c=this,l=!1,r=[],h=!1,p=[];return{callListeners:i,then:a,"catch":o}},"object"==typeof module&&"undefined"!=typeof module.exports&&(module.exports=n),"undefined"==typeof String.prototype.trim&&(String.prototype.trim=function(){return this.replace(/^\s+|\s+$/g,"")}),"undefined"==typeof String.prototype.lpad&&(String.prototype.lpad=function(e,t){var n,i="";for(n=0;e>n;n++)i+=t;return(i+this).slice(-e)})}(window);
\ No newline at end of file
--- /dev/null
+/**
+ * @license
+ * lodash 4.6.1 (Custom Build) <https://lodash.com/>
+ * Build: `lodash -o ./dist/lodash.js`
+ * Copyright 2012-2016 The Dojo Foundation <http://dojofoundation.org/>
+ * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
+ * Copyright 2009-2016 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Available under MIT license <https://lodash.com/license>
+ */
+;(function() {
+
+ /** Used as a safe reference for `undefined` in pre-ES5 environments. */
+ var undefined;
+
+ /** Used as the semantic version number. */
+ var VERSION = '4.6.1';
+
+ /** Used as the size to enable large array optimizations. */
+ var LARGE_ARRAY_SIZE = 200;
+
+ /** Used as the `TypeError` message for "Functions" methods. */
+ var FUNC_ERROR_TEXT = 'Expected a function';
+
+ /** Used to stand-in for `undefined` hash values. */
+ var HASH_UNDEFINED = '__lodash_hash_undefined__';
+
+ /** Used as the internal argument placeholder. */
+ var PLACEHOLDER = '__lodash_placeholder__';
+
+ /** Used to compose bitmasks for wrapper metadata. */
+ var BIND_FLAG = 1,
+ BIND_KEY_FLAG = 2,
+ CURRY_BOUND_FLAG = 4,
+ CURRY_FLAG = 8,
+ CURRY_RIGHT_FLAG = 16,
+ PARTIAL_FLAG = 32,
+ PARTIAL_RIGHT_FLAG = 64,
+ ARY_FLAG = 128,
+ REARG_FLAG = 256,
+ FLIP_FLAG = 512;
+
+ /** Used to compose bitmasks for comparison styles. */
+ var UNORDERED_COMPARE_FLAG = 1,
+ PARTIAL_COMPARE_FLAG = 2;
+
+ /** Used as default options for `_.truncate`. */
+ var DEFAULT_TRUNC_LENGTH = 30,
+ DEFAULT_TRUNC_OMISSION = '...';
+
+ /** Used to detect hot functions by number of calls within a span of milliseconds. */
+ var HOT_COUNT = 150,
+ HOT_SPAN = 16;
+
+ /** Used to indicate the type of lazy iteratees. */
+ var LAZY_FILTER_FLAG = 1,
+ LAZY_MAP_FLAG = 2,
+ LAZY_WHILE_FLAG = 3;
+
+ /** Used as references for various `Number` constants. */
+ var INFINITY = 1 / 0,
+ MAX_SAFE_INTEGER = 9007199254740991,
+ MAX_INTEGER = 1.7976931348623157e+308,
+ NAN = 0 / 0;
+
+ /** Used as references for the maximum length and index of an array. */
+ var MAX_ARRAY_LENGTH = 4294967295,
+ MAX_ARRAY_INDEX = MAX_ARRAY_LENGTH - 1,
+ HALF_MAX_ARRAY_LENGTH = MAX_ARRAY_LENGTH >>> 1;
+
+ /** `Object#toString` result references. */
+ var argsTag = '[object Arguments]',
+ arrayTag = '[object Array]',
+ boolTag = '[object Boolean]',
+ dateTag = '[object Date]',
+ errorTag = '[object Error]',
+ funcTag = '[object Function]',
+ genTag = '[object GeneratorFunction]',
+ mapTag = '[object Map]',
+ numberTag = '[object Number]',
+ objectTag = '[object Object]',
+ regexpTag = '[object RegExp]',
+ setTag = '[object Set]',
+ stringTag = '[object String]',
+ symbolTag = '[object Symbol]',
+ weakMapTag = '[object WeakMap]',
+ weakSetTag = '[object WeakSet]';
+
+ var arrayBufferTag = '[object ArrayBuffer]',
+ float32Tag = '[object Float32Array]',
+ float64Tag = '[object Float64Array]',
+ int8Tag = '[object Int8Array]',
+ int16Tag = '[object Int16Array]',
+ int32Tag = '[object Int32Array]',
+ uint8Tag = '[object Uint8Array]',
+ uint8ClampedTag = '[object Uint8ClampedArray]',
+ uint16Tag = '[object Uint16Array]',
+ uint32Tag = '[object Uint32Array]';
+
+ /** Used to match empty string literals in compiled template source. */
+ var reEmptyStringLeading = /\b__p \+= '';/g,
+ reEmptyStringMiddle = /\b(__p \+=) '' \+/g,
+ reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g;
+
+ /** Used to match HTML entities and HTML characters. */
+ var reEscapedHtml = /&(?:amp|lt|gt|quot|#39|#96);/g,
+ reUnescapedHtml = /[&<>"'`]/g,
+ reHasEscapedHtml = RegExp(reEscapedHtml.source),
+ reHasUnescapedHtml = RegExp(reUnescapedHtml.source);
+
+ /** Used to match template delimiters. */
+ var reEscape = /<%-([\s\S]+?)%>/g,
+ reEvaluate = /<%([\s\S]+?)%>/g,
+ reInterpolate = /<%=([\s\S]+?)%>/g;
+
+ /** Used to match property names within property paths. */
+ var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,
+ reIsPlainProp = /^\w*$/,
+ rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]/g;
+
+ /** Used to match `RegExp` [syntax characters](http://ecma-international.org/ecma-262/6.0/#sec-patterns). */
+ var reRegExpChar = /[\\^$.*+?()[\]{}|]/g,
+ reHasRegExpChar = RegExp(reRegExpChar.source);
+
+ /** Used to match leading and trailing whitespace. */
+ var reTrim = /^\s+|\s+$/g,
+ reTrimStart = /^\s+/,
+ reTrimEnd = /\s+$/;
+
+ /** Used to match backslashes in property paths. */
+ var reEscapeChar = /\\(\\)?/g;
+
+ /** Used to match [ES template delimiters](http://ecma-international.org/ecma-262/6.0/#sec-template-literal-lexical-components). */
+ var reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g;
+
+ /** Used to match `RegExp` flags from their coerced string values. */
+ var reFlags = /\w*$/;
+
+ /** Used to detect hexadecimal string values. */
+ var reHasHexPrefix = /^0x/i;
+
+ /** Used to detect bad signed hexadecimal string values. */
+ var reIsBadHex = /^[-+]0x[0-9a-f]+$/i;
+
+ /** Used to detect binary string values. */
+ var reIsBinary = /^0b[01]+$/i;
+
+ /** Used to detect host constructors (Safari > 5). */
+ var reIsHostCtor = /^\[object .+?Constructor\]$/;
+
+ /** Used to detect octal string values. */
+ var reIsOctal = /^0o[0-7]+$/i;
+
+ /** Used to detect unsigned integer values. */
+ var reIsUint = /^(?:0|[1-9]\d*)$/;
+
+ /** Used to match latin-1 supplementary letters (excluding mathematical operators). */
+ var reLatin1 = /[\xc0-\xd6\xd8-\xde\xdf-\xf6\xf8-\xff]/g;
+
+ /** Used to ensure capturing order of template delimiters. */
+ var reNoMatch = /($^)/;
+
+ /** Used to match unescaped characters in compiled string literals. */
+ var reUnescapedString = /['\n\r\u2028\u2029\\]/g;
+
+ /** Used to compose unicode character classes. */
+ var rsAstralRange = '\\ud800-\\udfff',
+ rsComboMarksRange = '\\u0300-\\u036f\\ufe20-\\ufe23',
+ rsComboSymbolsRange = '\\u20d0-\\u20f0',
+ rsDingbatRange = '\\u2700-\\u27bf',
+ rsLowerRange = 'a-z\\xdf-\\xf6\\xf8-\\xff',
+ rsMathOpRange = '\\xac\\xb1\\xd7\\xf7',
+ rsNonCharRange = '\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf',
+ rsQuoteRange = '\\u2018\\u2019\\u201c\\u201d',
+ rsSpaceRange = ' \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000',
+ rsUpperRange = 'A-Z\\xc0-\\xd6\\xd8-\\xde',
+ rsVarRange = '\\ufe0e\\ufe0f',
+ rsBreakRange = rsMathOpRange + rsNonCharRange + rsQuoteRange + rsSpaceRange;
+
+ /** Used to compose unicode capture groups. */
+ var rsAstral = '[' + rsAstralRange + ']',
+ rsBreak = '[' + rsBreakRange + ']',
+ rsCombo = '[' + rsComboMarksRange + rsComboSymbolsRange + ']',
+ rsDigits = '\\d+',
+ rsDingbat = '[' + rsDingbatRange + ']',
+ rsLower = '[' + rsLowerRange + ']',
+ rsMisc = '[^' + rsAstralRange + rsBreakRange + rsDigits + rsDingbatRange + rsLowerRange + rsUpperRange + ']',
+ rsFitz = '\\ud83c[\\udffb-\\udfff]',
+ rsModifier = '(?:' + rsCombo + '|' + rsFitz + ')',
+ rsNonAstral = '[^' + rsAstralRange + ']',
+ rsRegional = '(?:\\ud83c[\\udde6-\\uddff]){2}',
+ rsSurrPair = '[\\ud800-\\udbff][\\udc00-\\udfff]',
+ rsUpper = '[' + rsUpperRange + ']',
+ rsZWJ = '\\u200d';
+
+ /** Used to compose unicode regexes. */
+ var rsLowerMisc = '(?:' + rsLower + '|' + rsMisc + ')',
+ rsUpperMisc = '(?:' + rsUpper + '|' + rsMisc + ')',
+ reOptMod = rsModifier + '?',
+ rsOptVar = '[' + rsVarRange + ']?',
+ rsOptJoin = '(?:' + rsZWJ + '(?:' + [rsNonAstral, rsRegional, rsSurrPair].join('|') + ')' + rsOptVar + reOptMod + ')*',
+ rsSeq = rsOptVar + reOptMod + rsOptJoin,
+ rsEmoji = '(?:' + [rsDingbat, rsRegional, rsSurrPair].join('|') + ')' + rsSeq,
+ rsSymbol = '(?:' + [rsNonAstral + rsCombo + '?', rsCombo, rsRegional, rsSurrPair, rsAstral].join('|') + ')';
+
+ /**
+ * Used to match [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks) and
+ * [combining diacritical marks for symbols](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks_for_Symbols).
+ */
+ var reComboMark = RegExp(rsCombo, 'g');
+
+ /** Used to match [string symbols](https://mathiasbynens.be/notes/javascript-unicode). */
+ var reComplexSymbol = RegExp(rsFitz + '(?=' + rsFitz + ')|' + rsSymbol + rsSeq, 'g');
+
+ /** Used to detect strings with [zero-width joiners or code points from the astral planes](http://eev.ee/blog/2015/09/12/dark-corners-of-unicode/). */
+ var reHasComplexSymbol = RegExp('[' + rsZWJ + rsAstralRange + rsComboMarksRange + rsComboSymbolsRange + rsVarRange + ']');
+
+ /** Used to match non-compound words composed of alphanumeric characters. */
+ var reBasicWord = /[a-zA-Z0-9]+/g;
+
+ /** Used to match complex or compound words. */
+ var reComplexWord = RegExp([
+ rsUpper + '?' + rsLower + '+(?=' + [rsBreak, rsUpper, '$'].join('|') + ')',
+ rsUpperMisc + '+(?=' + [rsBreak, rsUpper + rsLowerMisc, '$'].join('|') + ')',
+ rsUpper + '?' + rsLowerMisc + '+',
+ rsUpper + '+',
+ rsDigits,
+ rsEmoji
+ ].join('|'), 'g');
+
+ /** Used to detect strings that need a more robust regexp to match words. */
+ var reHasComplexWord = /[a-z][A-Z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/;
+
+ /** Used to assign default `context` object properties. */
+ var contextProps = [
+ 'Array', 'Buffer', 'Date', 'Error', 'Float32Array', 'Float64Array',
+ 'Function', 'Int8Array', 'Int16Array', 'Int32Array', 'Map', 'Math', 'Object',
+ 'Reflect', 'RegExp', 'Set', 'String', 'Symbol', 'TypeError', 'Uint8Array',
+ 'Uint8ClampedArray', 'Uint16Array', 'Uint32Array', 'WeakMap', '_',
+ 'clearTimeout', 'isFinite', 'parseInt', 'setTimeout'
+ ];
+
+ /** Used to make template sourceURLs easier to identify. */
+ var templateCounter = -1;
+
+ /** Used to identify `toStringTag` values of typed arrays. */
+ var typedArrayTags = {};
+ typedArrayTags[float32Tag] = typedArrayTags[float64Tag] =
+ typedArrayTags[int8Tag] = typedArrayTags[int16Tag] =
+ typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] =
+ typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] =
+ typedArrayTags[uint32Tag] = true;
+ typedArrayTags[argsTag] = typedArrayTags[arrayTag] =
+ typedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] =
+ typedArrayTags[dateTag] = typedArrayTags[errorTag] =
+ typedArrayTags[funcTag] = typedArrayTags[mapTag] =
+ typedArrayTags[numberTag] = typedArrayTags[objectTag] =
+ typedArrayTags[regexpTag] = typedArrayTags[setTag] =
+ typedArrayTags[stringTag] = typedArrayTags[weakMapTag] = false;
+
+ /** Used to identify `toStringTag` values supported by `_.clone`. */
+ var cloneableTags = {};
+ cloneableTags[argsTag] = cloneableTags[arrayTag] =
+ cloneableTags[arrayBufferTag] = cloneableTags[boolTag] =
+ cloneableTags[dateTag] = cloneableTags[float32Tag] =
+ cloneableTags[float64Tag] = cloneableTags[int8Tag] =
+ cloneableTags[int16Tag] = cloneableTags[int32Tag] =
+ cloneableTags[mapTag] = cloneableTags[numberTag] =
+ cloneableTags[objectTag] = cloneableTags[regexpTag] =
+ cloneableTags[setTag] = cloneableTags[stringTag] =
+ cloneableTags[symbolTag] = cloneableTags[uint8Tag] =
+ cloneableTags[uint8ClampedTag] = cloneableTags[uint16Tag] =
+ cloneableTags[uint32Tag] = true;
+ cloneableTags[errorTag] = cloneableTags[funcTag] =
+ cloneableTags[weakMapTag] = false;
+
+ /** Used to map latin-1 supplementary letters to basic latin letters. */
+ var deburredLetters = {
+ '\xc0': 'A', '\xc1': 'A', '\xc2': 'A', '\xc3': 'A', '\xc4': 'A', '\xc5': 'A',
+ '\xe0': 'a', '\xe1': 'a', '\xe2': 'a', '\xe3': 'a', '\xe4': 'a', '\xe5': 'a',
+ '\xc7': 'C', '\xe7': 'c',
+ '\xd0': 'D', '\xf0': 'd',
+ '\xc8': 'E', '\xc9': 'E', '\xca': 'E', '\xcb': 'E',
+ '\xe8': 'e', '\xe9': 'e', '\xea': 'e', '\xeb': 'e',
+ '\xcC': 'I', '\xcd': 'I', '\xce': 'I', '\xcf': 'I',
+ '\xeC': 'i', '\xed': 'i', '\xee': 'i', '\xef': 'i',
+ '\xd1': 'N', '\xf1': 'n',
+ '\xd2': 'O', '\xd3': 'O', '\xd4': 'O', '\xd5': 'O', '\xd6': 'O', '\xd8': 'O',
+ '\xf2': 'o', '\xf3': 'o', '\xf4': 'o', '\xf5': 'o', '\xf6': 'o', '\xf8': 'o',
+ '\xd9': 'U', '\xda': 'U', '\xdb': 'U', '\xdc': 'U',
+ '\xf9': 'u', '\xfa': 'u', '\xfb': 'u', '\xfc': 'u',
+ '\xdd': 'Y', '\xfd': 'y', '\xff': 'y',
+ '\xc6': 'Ae', '\xe6': 'ae',
+ '\xde': 'Th', '\xfe': 'th',
+ '\xdf': 'ss'
+ };
+
+ /** Used to map characters to HTML entities. */
+ var htmlEscapes = {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ "'": ''',
+ '`': '`'
+ };
+
+ /** Used to map HTML entities to characters. */
+ var htmlUnescapes = {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ ''': "'",
+ '`': '`'
+ };
+
+ /** Used to determine if values are of the language type `Object`. */
+ var objectTypes = {
+ 'function': true,
+ 'object': true
+ };
+
+ /** Used to escape characters for inclusion in compiled string literals. */
+ var stringEscapes = {
+ '\\': '\\',
+ "'": "'",
+ '\n': 'n',
+ '\r': 'r',
+ '\u2028': 'u2028',
+ '\u2029': 'u2029'
+ };
+
+ /** Built-in method references without a dependency on `root`. */
+ var freeParseFloat = parseFloat,
+ freeParseInt = parseInt;
+
+ /** Detect free variable `exports`. */
+ var freeExports = (objectTypes[typeof exports] && exports && !exports.nodeType)
+ ? exports
+ : undefined;
+
+ /** Detect free variable `module`. */
+ var freeModule = (objectTypes[typeof module] && module && !module.nodeType)
+ ? module
+ : undefined;
+
+ /** Detect the popular CommonJS extension `module.exports`. */
+ var moduleExports = (freeModule && freeModule.exports === freeExports)
+ ? freeExports
+ : undefined;
+
+ /** Detect free variable `global` from Node.js. */
+ var freeGlobal = checkGlobal(freeExports && freeModule && typeof global == 'object' && global);
+
+ /** Detect free variable `self`. */
+ var freeSelf = checkGlobal(objectTypes[typeof self] && self);
+
+ /** Detect free variable `window`. */
+ var freeWindow = checkGlobal(objectTypes[typeof window] && window);
+
+ /** Detect `this` as the global object. */
+ var thisGlobal = checkGlobal(objectTypes[typeof this] && this);
+
+ /**
+ * Used as a reference to the global object.
+ *
+ * The `this` value is used if it's the global object to avoid Greasemonkey's
+ * restricted `window` object, otherwise the `window` object is used.
+ */
+ var root = freeGlobal ||
+ ((freeWindow !== (thisGlobal && thisGlobal.window)) && freeWindow) ||
+ freeSelf || thisGlobal || Function('return this')();
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Adds the key-value `pair` to `map`.
+ *
+ * @private
+ * @param {Object} map The map to modify.
+ * @param {Array} pair The key-value pair to add.
+ * @returns {Object} Returns `map`.
+ */
+ function addMapEntry(map, pair) {
+ // Don't return `Map#set` because it doesn't return the map instance in IE 11.
+ map.set(pair[0], pair[1]);
+ return map;
+ }
+
+ /**
+ * Adds `value` to `set`.
+ *
+ * @private
+ * @param {Object} set The set to modify.
+ * @param {*} value The value to add.
+ * @returns {Object} Returns `set`.
+ */
+ function addSetEntry(set, value) {
+ set.add(value);
+ return set;
+ }
+
+ /**
+ * A faster alternative to `Function#apply`, this function invokes `func`
+ * with the `this` binding of `thisArg` and the arguments of `args`.
+ *
+ * @private
+ * @param {Function} func The function to invoke.
+ * @param {*} thisArg The `this` binding of `func`.
+ * @param {...*} args The arguments to invoke `func` with.
+ * @returns {*} Returns the result of `func`.
+ */
+ function apply(func, thisArg, args) {
+ var length = args.length;
+ switch (length) {
+ case 0: return func.call(thisArg);
+ case 1: return func.call(thisArg, args[0]);
+ case 2: return func.call(thisArg, args[0], args[1]);
+ case 3: return func.call(thisArg, args[0], args[1], args[2]);
+ }
+ return func.apply(thisArg, args);
+ }
+
+ /**
+ * A specialized version of `baseAggregator` for arrays.
+ *
+ * @private
+ * @param {Array} array The array to iterate over.
+ * @param {Function} setter The function to set `accumulator` values.
+ * @param {Function} iteratee The iteratee to transform keys.
+ * @param {Object} accumulator The initial aggregated object.
+ * @returns {Function} Returns `accumulator`.
+ */
+ function arrayAggregator(array, setter, iteratee, accumulator) {
+ var index = -1,
+ length = array.length;
+
+ while (++index < length) {
+ var value = array[index];
+ setter(accumulator, value, iteratee(value), array);
+ }
+ return accumulator;
+ }
+
+ /**
+ * Creates a new array concatenating `array` with `other`.
+ *
+ * @private
+ * @param {Array} array The first array to concatenate.
+ * @param {Array} other The second array to concatenate.
+ * @returns {Array} Returns the new concatenated array.
+ */
+ function arrayConcat(array, other) {
+ var index = -1,
+ length = array.length,
+ othIndex = -1,
+ othLength = other.length,
+ result = Array(length + othLength);
+
+ while (++index < length) {
+ result[index] = array[index];
+ }
+ while (++othIndex < othLength) {
+ result[index++] = other[othIndex];
+ }
+ return result;
+ }
+
+ /**
+ * A specialized version of `_.forEach` for arrays without support for
+ * iteratee shorthands.
+ *
+ * @private
+ * @param {Array} array The array to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @returns {Array} Returns `array`.
+ */
+ function arrayEach(array, iteratee) {
+ var index = -1,
+ length = array.length;
+
+ while (++index < length) {
+ if (iteratee(array[index], index, array) === false) {
+ break;
+ }
+ }
+ return array;
+ }
+
+ /**
+ * A specialized version of `_.forEachRight` for arrays without support for
+ * iteratee shorthands.
+ *
+ * @private
+ * @param {Array} array The array to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @returns {Array} Returns `array`.
+ */
+ function arrayEachRight(array, iteratee) {
+ var length = array.length;
+
+ while (length--) {
+ if (iteratee(array[length], length, array) === false) {
+ break;
+ }
+ }
+ return array;
+ }
+
+ /**
+ * A specialized version of `_.every` for arrays without support for
+ * iteratee shorthands.
+ *
+ * @private
+ * @param {Array} array The array to iterate over.
+ * @param {Function} predicate The function invoked per iteration.
+ * @returns {boolean} Returns `true` if all elements pass the predicate check, else `false`.
+ */
+ function arrayEvery(array, predicate) {
+ var index = -1,
+ length = array.length;
+
+ while (++index < length) {
+ if (!predicate(array[index], index, array)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * A specialized version of `_.filter` for arrays without support for
+ * iteratee shorthands.
+ *
+ * @private
+ * @param {Array} array The array to iterate over.
+ * @param {Function} predicate The function invoked per iteration.
+ * @returns {Array} Returns the new filtered array.
+ */
+ function arrayFilter(array, predicate) {
+ var index = -1,
+ length = array.length,
+ resIndex = 0,
+ result = [];
+
+ while (++index < length) {
+ var value = array[index];
+ if (predicate(value, index, array)) {
+ result[resIndex++] = value;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * A specialized version of `_.includes` for arrays without support for
+ * specifying an index to search from.
+ *
+ * @private
+ * @param {Array} array The array to search.
+ * @param {*} target The value to search for.
+ * @returns {boolean} Returns `true` if `target` is found, else `false`.
+ */
+ function arrayIncludes(array, value) {
+ return !!array.length && baseIndexOf(array, value, 0) > -1;
+ }
+
+ /**
+ * This function is like `arrayIncludes` except that it accepts a comparator.
+ *
+ * @private
+ * @param {Array} array The array to search.
+ * @param {*} target The value to search for.
+ * @param {Function} comparator The comparator invoked per element.
+ * @returns {boolean} Returns `true` if `target` is found, else `false`.
+ */
+ function arrayIncludesWith(array, value, comparator) {
+ var index = -1,
+ length = array.length;
+
+ while (++index < length) {
+ if (comparator(value, array[index])) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * A specialized version of `_.map` for arrays without support for iteratee
+ * shorthands.
+ *
+ * @private
+ * @param {Array} array The array to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @returns {Array} Returns the new mapped array.
+ */
+ function arrayMap(array, iteratee) {
+ var index = -1,
+ length = array.length,
+ result = Array(length);
+
+ while (++index < length) {
+ result[index] = iteratee(array[index], index, array);
+ }
+ return result;
+ }
+
+ /**
+ * Appends the elements of `values` to `array`.
+ *
+ * @private
+ * @param {Array} array The array to modify.
+ * @param {Array} values The values to append.
+ * @returns {Array} Returns `array`.
+ */
+ function arrayPush(array, values) {
+ var index = -1,
+ length = values.length,
+ offset = array.length;
+
+ while (++index < length) {
+ array[offset + index] = values[index];
+ }
+ return array;
+ }
+
+ /**
+ * A specialized version of `_.reduce` for arrays without support for
+ * iteratee shorthands.
+ *
+ * @private
+ * @param {Array} array The array to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @param {*} [accumulator] The initial value.
+ * @param {boolean} [initAccum] Specify using the first element of `array` as the initial value.
+ * @returns {*} Returns the accumulated value.
+ */
+ function arrayReduce(array, iteratee, accumulator, initAccum) {
+ var index = -1,
+ length = array.length;
+
+ if (initAccum && length) {
+ accumulator = array[++index];
+ }
+ while (++index < length) {
+ accumulator = iteratee(accumulator, array[index], index, array);
+ }
+ return accumulator;
+ }
+
+ /**
+ * A specialized version of `_.reduceRight` for arrays without support for
+ * iteratee shorthands.
+ *
+ * @private
+ * @param {Array} array The array to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @param {*} [accumulator] The initial value.
+ * @param {boolean} [initAccum] Specify using the last element of `array` as the initial value.
+ * @returns {*} Returns the accumulated value.
+ */
+ function arrayReduceRight(array, iteratee, accumulator, initAccum) {
+ var length = array.length;
+ if (initAccum && length) {
+ accumulator = array[--length];
+ }
+ while (length--) {
+ accumulator = iteratee(accumulator, array[length], length, array);
+ }
+ return accumulator;
+ }
+
+ /**
+ * A specialized version of `_.some` for arrays without support for iteratee
+ * shorthands.
+ *
+ * @private
+ * @param {Array} array The array to iterate over.
+ * @param {Function} predicate The function invoked per iteration.
+ * @returns {boolean} Returns `true` if any element passes the predicate check, else `false`.
+ */
+ function arraySome(array, predicate) {
+ var index = -1,
+ length = array.length;
+
+ while (++index < length) {
+ if (predicate(array[index], index, array)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * The base implementation of methods like `_.max` and `_.min` which accepts a
+ * `comparator` to determine the extremum value.
+ *
+ * @private
+ * @param {Array} array The array to iterate over.
+ * @param {Function} iteratee The iteratee invoked per iteration.
+ * @param {Function} comparator The comparator used to compare values.
+ * @returns {*} Returns the extremum value.
+ */
+ function baseExtremum(array, iteratee, comparator) {
+ var index = -1,
+ length = array.length;
+
+ while (++index < length) {
+ var value = array[index],
+ current = iteratee(value);
+
+ if (current != null && (computed === undefined
+ ? current === current
+ : comparator(current, computed)
+ )) {
+ var computed = current,
+ result = value;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * The base implementation of methods like `_.find` and `_.findKey`, without
+ * support for iteratee shorthands, which iterates over `collection` using
+ * `eachFunc`.
+ *
+ * @private
+ * @param {Array|Object} collection The collection to search.
+ * @param {Function} predicate The function invoked per iteration.
+ * @param {Function} eachFunc The function to iterate over `collection`.
+ * @param {boolean} [retKey] Specify returning the key of the found element instead of the element itself.
+ * @returns {*} Returns the found element or its key, else `undefined`.
+ */
+ function baseFind(collection, predicate, eachFunc, retKey) {
+ var result;
+ eachFunc(collection, function(value, key, collection) {
+ if (predicate(value, key, collection)) {
+ result = retKey ? key : value;
+ return false;
+ }
+ });
+ return result;
+ }
+
+ /**
+ * The base implementation of `_.findIndex` and `_.findLastIndex` without
+ * support for iteratee shorthands.
+ *
+ * @private
+ * @param {Array} array The array to search.
+ * @param {Function} predicate The function invoked per iteration.
+ * @param {boolean} [fromRight] Specify iterating from right to left.
+ * @returns {number} Returns the index of the matched value, else `-1`.
+ */
+ function baseFindIndex(array, predicate, fromRight) {
+ var length = array.length,
+ index = fromRight ? length : -1;
+
+ while ((fromRight ? index-- : ++index < length)) {
+ if (predicate(array[index], index, array)) {
+ return index;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * The base implementation of `_.indexOf` without `fromIndex` bounds checks.
+ *
+ * @private
+ * @param {Array} array The array to search.
+ * @param {*} value The value to search for.
+ * @param {number} fromIndex The index to search from.
+ * @returns {number} Returns the index of the matched value, else `-1`.
+ */
+ function baseIndexOf(array, value, fromIndex) {
+ if (value !== value) {
+ return indexOfNaN(array, fromIndex);
+ }
+ var index = fromIndex - 1,
+ length = array.length;
+
+ while (++index < length) {
+ if (array[index] === value) {
+ return index;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * This function is like `baseIndexOf` except that it accepts a comparator.
+ *
+ * @private
+ * @param {Array} array The array to search.
+ * @param {*} value The value to search for.
+ * @param {number} fromIndex The index to search from.
+ * @param {Function} comparator The comparator invoked per element.
+ * @returns {number} Returns the index of the matched value, else `-1`.
+ */
+ function baseIndexOfWith(array, value, fromIndex, comparator) {
+ var index = fromIndex - 1,
+ length = array.length;
+
+ while (++index < length) {
+ if (comparator(array[index], value)) {
+ return index;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * The base implementation of `_.reduce` and `_.reduceRight`, without support
+ * for iteratee shorthands, which iterates over `collection` using `eachFunc`.
+ *
+ * @private
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @param {*} accumulator The initial value.
+ * @param {boolean} initAccum Specify using the first or last element of `collection` as the initial value.
+ * @param {Function} eachFunc The function to iterate over `collection`.
+ * @returns {*} Returns the accumulated value.
+ */
+ function baseReduce(collection, iteratee, accumulator, initAccum, eachFunc) {
+ eachFunc(collection, function(value, index, collection) {
+ accumulator = initAccum
+ ? (initAccum = false, value)
+ : iteratee(accumulator, value, index, collection);
+ });
+ return accumulator;
+ }
+
+ /**
+ * The base implementation of `_.sortBy` which uses `comparer` to define the
+ * sort order of `array` and replaces criteria objects with their corresponding
+ * values.
+ *
+ * @private
+ * @param {Array} array The array to sort.
+ * @param {Function} comparer The function to define sort order.
+ * @returns {Array} Returns `array`.
+ */
+ function baseSortBy(array, comparer) {
+ var length = array.length;
+
+ array.sort(comparer);
+ while (length--) {
+ array[length] = array[length].value;
+ }
+ return array;
+ }
+
+ /**
+ * The base implementation of `_.sum` without support for iteratee shorthands.
+ *
+ * @private
+ * @param {Array} array The array to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @returns {number} Returns the sum.
+ */
+ function baseSum(array, iteratee) {
+ var result,
+ index = -1,
+ length = array.length;
+
+ while (++index < length) {
+ var current = iteratee(array[index]);
+ if (current !== undefined) {
+ result = result === undefined ? current : (result + current);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * The base implementation of `_.times` without support for iteratee shorthands
+ * or max array length checks.
+ *
+ * @private
+ * @param {number} n The number of times to invoke `iteratee`.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @returns {Array} Returns the array of results.
+ */
+ function baseTimes(n, iteratee) {
+ var index = -1,
+ result = Array(n);
+
+ while (++index < n) {
+ result[index] = iteratee(index);
+ }
+ return result;
+ }
+
+ /**
+ * The base implementation of `_.toPairs` and `_.toPairsIn` which creates an array
+ * of key-value pairs for `object` corresponding to the property names of `props`.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @param {Array} props The property names to get values for.
+ * @returns {Object} Returns the new array of key-value pairs.
+ */
+ function baseToPairs(object, props) {
+ return arrayMap(props, function(key) {
+ return [key, object[key]];
+ });
+ }
+
+ /**
+ * The base implementation of `_.unary` without support for storing wrapper metadata.
+ *
+ * @private
+ * @param {Function} func The function to cap arguments for.
+ * @returns {Function} Returns the new function.
+ */
+ function baseUnary(func) {
+ return function(value) {
+ return func(value);
+ };
+ }
+
+ /**
+ * The base implementation of `_.values` and `_.valuesIn` which creates an
+ * array of `object` property values corresponding to the property names
+ * of `props`.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @param {Array} props The property names to get values for.
+ * @returns {Object} Returns the array of property values.
+ */
+ function baseValues(object, props) {
+ return arrayMap(props, function(key) {
+ return object[key];
+ });
+ }
+
+ /**
+ * Used by `_.trim` and `_.trimStart` to get the index of the first string symbol
+ * that is not found in the character symbols.
+ *
+ * @private
+ * @param {Array} strSymbols The string symbols to inspect.
+ * @param {Array} chrSymbols The character symbols to find.
+ * @returns {number} Returns the index of the first unmatched string symbol.
+ */
+ function charsStartIndex(strSymbols, chrSymbols) {
+ var index = -1,
+ length = strSymbols.length;
+
+ while (++index < length && baseIndexOf(chrSymbols, strSymbols[index], 0) > -1) {}
+ return index;
+ }
+
+ /**
+ * Used by `_.trim` and `_.trimEnd` to get the index of the last string symbol
+ * that is not found in the character symbols.
+ *
+ * @private
+ * @param {Array} strSymbols The string symbols to inspect.
+ * @param {Array} chrSymbols The character symbols to find.
+ * @returns {number} Returns the index of the last unmatched string symbol.
+ */
+ function charsEndIndex(strSymbols, chrSymbols) {
+ var index = strSymbols.length;
+
+ while (index-- && baseIndexOf(chrSymbols, strSymbols[index], 0) > -1) {}
+ return index;
+ }
+
+ /**
+ * Checks if `value` is a global object.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {null|Object} Returns `value` if it's a global object, else `null`.
+ */
+ function checkGlobal(value) {
+ return (value && value.Object === Object) ? value : null;
+ }
+
+ /**
+ * Compares values to sort them in ascending order.
+ *
+ * @private
+ * @param {*} value The value to compare.
+ * @param {*} other The other value to compare.
+ * @returns {number} Returns the sort order indicator for `value`.
+ */
+ function compareAscending(value, other) {
+ if (value !== other) {
+ var valIsNull = value === null,
+ valIsUndef = value === undefined,
+ valIsReflexive = value === value;
+
+ var othIsNull = other === null,
+ othIsUndef = other === undefined,
+ othIsReflexive = other === other;
+
+ if ((value > other && !othIsNull) || !valIsReflexive ||
+ (valIsNull && !othIsUndef && othIsReflexive) ||
+ (valIsUndef && othIsReflexive)) {
+ return 1;
+ }
+ if ((value < other && !valIsNull) || !othIsReflexive ||
+ (othIsNull && !valIsUndef && valIsReflexive) ||
+ (othIsUndef && valIsReflexive)) {
+ return -1;
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * Used by `_.orderBy` to compare multiple properties of a value to another
+ * and stable sort them.
+ *
+ * If `orders` is unspecified, all values are sorted in ascending order. Otherwise,
+ * specify an order of "desc" for descending or "asc" for ascending sort order
+ * of corresponding values.
+ *
+ * @private
+ * @param {Object} object The object to compare.
+ * @param {Object} other The other object to compare.
+ * @param {boolean[]|string[]} orders The order to sort by for each property.
+ * @returns {number} Returns the sort order indicator for `object`.
+ */
+ function compareMultiple(object, other, orders) {
+ var index = -1,
+ objCriteria = object.criteria,
+ othCriteria = other.criteria,
+ length = objCriteria.length,
+ ordersLength = orders.length;
+
+ while (++index < length) {
+ var result = compareAscending(objCriteria[index], othCriteria[index]);
+ if (result) {
+ if (index >= ordersLength) {
+ return result;
+ }
+ var order = orders[index];
+ return result * (order == 'desc' ? -1 : 1);
+ }
+ }
+ // Fixes an `Array#sort` bug in the JS engine embedded in Adobe applications
+ // that causes it, under certain circumstances, to provide the same value for
+ // `object` and `other`. See https://github.com/jashkenas/underscore/pull/1247
+ // for more details.
+ //
+ // This also ensures a stable sort in V8 and other engines.
+ // See https://code.google.com/p/v8/issues/detail?id=90 for more details.
+ return object.index - other.index;
+ }
+
+ /**
+ * Gets the number of `placeholder` occurrences in `array`.
+ *
+ * @private
+ * @param {Array} array The array to inspect.
+ * @param {*} placeholder The placeholder to search for.
+ * @returns {number} Returns the placeholder count.
+ */
+ function countHolders(array, placeholder) {
+ var length = array.length,
+ result = 0;
+
+ while (length--) {
+ if (array[length] === placeholder) {
+ result++;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Used by `_.deburr` to convert latin-1 supplementary letters to basic latin letters.
+ *
+ * @private
+ * @param {string} letter The matched letter to deburr.
+ * @returns {string} Returns the deburred letter.
+ */
+ function deburrLetter(letter) {
+ return deburredLetters[letter];
+ }
+
+ /**
+ * Used by `_.escape` to convert characters to HTML entities.
+ *
+ * @private
+ * @param {string} chr The matched character to escape.
+ * @returns {string} Returns the escaped character.
+ */
+ function escapeHtmlChar(chr) {
+ return htmlEscapes[chr];
+ }
+
+ /**
+ * Used by `_.template` to escape characters for inclusion in compiled string literals.
+ *
+ * @private
+ * @param {string} chr The matched character to escape.
+ * @returns {string} Returns the escaped character.
+ */
+ function escapeStringChar(chr) {
+ return '\\' + stringEscapes[chr];
+ }
+
+ /**
+ * Gets the index at which the first occurrence of `NaN` is found in `array`.
+ *
+ * @private
+ * @param {Array} array The array to search.
+ * @param {number} fromIndex The index to search from.
+ * @param {boolean} [fromRight] Specify iterating from right to left.
+ * @returns {number} Returns the index of the matched `NaN`, else `-1`.
+ */
+ function indexOfNaN(array, fromIndex, fromRight) {
+ var length = array.length,
+ index = fromIndex + (fromRight ? 0 : -1);
+
+ while ((fromRight ? index-- : ++index < length)) {
+ var other = array[index];
+ if (other !== other) {
+ return index;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Checks if `value` is a host object in IE < 9.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a host object, else `false`.
+ */
+ function isHostObject(value) {
+ // Many host objects are `Object` objects that can coerce to strings
+ // despite having improperly defined `toString` methods.
+ var result = false;
+ if (value != null && typeof value.toString != 'function') {
+ try {
+ result = !!(value + '');
+ } catch (e) {}
+ }
+ return result;
+ }
+
+ /**
+ * Checks if `value` is a valid array-like index.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index.
+ * @returns {boolean} Returns `true` if `value` is a valid index, else `false`.
+ */
+ function isIndex(value, length) {
+ value = (typeof value == 'number' || reIsUint.test(value)) ? +value : -1;
+ length = length == null ? MAX_SAFE_INTEGER : length;
+ return value > -1 && value % 1 == 0 && value < length;
+ }
+
+ /**
+ * Converts `iterator` to an array.
+ *
+ * @private
+ * @param {Object} iterator The iterator to convert.
+ * @returns {Array} Returns the converted array.
+ */
+ function iteratorToArray(iterator) {
+ var data,
+ result = [];
+
+ while (!(data = iterator.next()).done) {
+ result.push(data.value);
+ }
+ return result;
+ }
+
+ /**
+ * Converts `map` to an array.
+ *
+ * @private
+ * @param {Object} map The map to convert.
+ * @returns {Array} Returns the converted array.
+ */
+ function mapToArray(map) {
+ var index = -1,
+ result = Array(map.size);
+
+ map.forEach(function(value, key) {
+ result[++index] = [key, value];
+ });
+ return result;
+ }
+
+ /**
+ * Replaces all `placeholder` elements in `array` with an internal placeholder
+ * and returns an array of their indexes.
+ *
+ * @private
+ * @param {Array} array The array to modify.
+ * @param {*} placeholder The placeholder to replace.
+ * @returns {Array} Returns the new array of placeholder indexes.
+ */
+ function replaceHolders(array, placeholder) {
+ var index = -1,
+ length = array.length,
+ resIndex = 0,
+ result = [];
+
+ while (++index < length) {
+ var value = array[index];
+ if (value === placeholder || value === PLACEHOLDER) {
+ array[index] = PLACEHOLDER;
+ result[resIndex++] = index;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Converts `set` to an array.
+ *
+ * @private
+ * @param {Object} set The set to convert.
+ * @returns {Array} Returns the converted array.
+ */
+ function setToArray(set) {
+ var index = -1,
+ result = Array(set.size);
+
+ set.forEach(function(value) {
+ result[++index] = value;
+ });
+ return result;
+ }
+
+ /**
+ * Gets the number of symbols in `string`.
+ *
+ * @private
+ * @param {string} string The string to inspect.
+ * @returns {number} Returns the string size.
+ */
+ function stringSize(string) {
+ if (!(string && reHasComplexSymbol.test(string))) {
+ return string.length;
+ }
+ var result = reComplexSymbol.lastIndex = 0;
+ while (reComplexSymbol.test(string)) {
+ result++;
+ }
+ return result;
+ }
+
+ /**
+ * Converts `string` to an array.
+ *
+ * @private
+ * @param {string} string The string to convert.
+ * @returns {Array} Returns the converted array.
+ */
+ function stringToArray(string) {
+ return string.match(reComplexSymbol);
+ }
+
+ /**
+ * Used by `_.unescape` to convert HTML entities to characters.
+ *
+ * @private
+ * @param {string} chr The matched character to unescape.
+ * @returns {string} Returns the unescaped character.
+ */
+ function unescapeHtmlChar(chr) {
+ return htmlUnescapes[chr];
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Create a new pristine `lodash` function using the `context` object.
+ *
+ * @static
+ * @memberOf _
+ * @category Util
+ * @param {Object} [context=root] The context object.
+ * @returns {Function} Returns a new `lodash` function.
+ * @example
+ *
+ * _.mixin({ 'foo': _.constant('foo') });
+ *
+ * var lodash = _.runInContext();
+ * lodash.mixin({ 'bar': lodash.constant('bar') });
+ *
+ * _.isFunction(_.foo);
+ * // => true
+ * _.isFunction(_.bar);
+ * // => false
+ *
+ * lodash.isFunction(lodash.foo);
+ * // => false
+ * lodash.isFunction(lodash.bar);
+ * // => true
+ *
+ * // Use `context` to mock `Date#getTime` use in `_.now`.
+ * var mock = _.runInContext({
+ * 'Date': function() {
+ * return { 'getTime': getTimeMock };
+ * }
+ * });
+ *
+ * // Create a suped-up `defer` in Node.js.
+ * var defer = _.runInContext({ 'setTimeout': setImmediate }).defer;
+ */
+ function runInContext(context) {
+ context = context ? _.defaults({}, context, _.pick(root, contextProps)) : root;
+
+ /** Built-in constructor references. */
+ var Date = context.Date,
+ Error = context.Error,
+ Math = context.Math,
+ RegExp = context.RegExp,
+ TypeError = context.TypeError;
+
+ /** Used for built-in method references. */
+ var arrayProto = context.Array.prototype,
+ objectProto = context.Object.prototype;
+
+ /** Used to resolve the decompiled source of functions. */
+ var funcToString = context.Function.prototype.toString;
+
+ /** Used to check objects for own properties. */
+ var hasOwnProperty = objectProto.hasOwnProperty;
+
+ /** Used to generate unique IDs. */
+ var idCounter = 0;
+
+ /** Used to infer the `Object` constructor. */
+ var objectCtorString = funcToString.call(Object);
+
+ /**
+ * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)
+ * of values.
+ */
+ var objectToString = objectProto.toString;
+
+ /** Used to restore the original `_` reference in `_.noConflict`. */
+ var oldDash = root._;
+
+ /** Used to detect if a method is native. */
+ var reIsNative = RegExp('^' +
+ funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\$&')
+ .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$'
+ );
+
+ /** Built-in value references. */
+ var Buffer = moduleExports ? context.Buffer : undefined,
+ Reflect = context.Reflect,
+ Symbol = context.Symbol,
+ Uint8Array = context.Uint8Array,
+ clearTimeout = context.clearTimeout,
+ enumerate = Reflect ? Reflect.enumerate : undefined,
+ getPrototypeOf = Object.getPrototypeOf,
+ getOwnPropertySymbols = Object.getOwnPropertySymbols,
+ iteratorSymbol = typeof (iteratorSymbol = Symbol && Symbol.iterator) == 'symbol' ? iteratorSymbol : undefined,
+ objectCreate = Object.create,
+ propertyIsEnumerable = objectProto.propertyIsEnumerable,
+ setTimeout = context.setTimeout,
+ splice = arrayProto.splice;
+
+ /* Built-in method references for those with the same name as other `lodash` methods. */
+ var nativeCeil = Math.ceil,
+ nativeFloor = Math.floor,
+ nativeIsFinite = context.isFinite,
+ nativeJoin = arrayProto.join,
+ nativeKeys = Object.keys,
+ nativeMax = Math.max,
+ nativeMin = Math.min,
+ nativeParseInt = context.parseInt,
+ nativeRandom = Math.random,
+ nativeReverse = arrayProto.reverse;
+
+ /* Built-in method references that are verified to be native. */
+ var Map = getNative(context, 'Map'),
+ Set = getNative(context, 'Set'),
+ WeakMap = getNative(context, 'WeakMap'),
+ nativeCreate = getNative(Object, 'create');
+
+ /** Used to store function metadata. */
+ var metaMap = WeakMap && new WeakMap;
+
+ /** Detect if properties shadowing those on `Object.prototype` are non-enumerable. */
+ var nonEnumShadows = !propertyIsEnumerable.call({ 'valueOf': 1 }, 'valueOf');
+
+ /** Used to lookup unminified function names. */
+ var realNames = {};
+
+ /** Used to detect maps, sets, and weakmaps. */
+ var mapCtorString = Map ? funcToString.call(Map) : '',
+ setCtorString = Set ? funcToString.call(Set) : '',
+ weakMapCtorString = WeakMap ? funcToString.call(WeakMap) : '';
+
+ /** Used to convert symbols to primitives and strings. */
+ var symbolProto = Symbol ? Symbol.prototype : undefined,
+ symbolValueOf = symbolProto ? symbolProto.valueOf : undefined,
+ symbolToString = symbolProto ? symbolProto.toString : undefined;
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Creates a `lodash` object which wraps `value` to enable implicit method
+ * chaining. Methods that operate on and return arrays, collections, and
+ * functions can be chained together. Methods that retrieve a single value or
+ * may return a primitive value will automatically end the chain sequence and
+ * return the unwrapped value. Otherwise, the value must be unwrapped with
+ * `_#value`.
+ *
+ * Explicit chaining, which must be unwrapped with `_#value` in all cases,
+ * may be enabled using `_.chain`.
+ *
+ * The execution of chained methods is lazy, that is, it's deferred until
+ * `_#value` is implicitly or explicitly called.
+ *
+ * Lazy evaluation allows several methods to support shortcut fusion. Shortcut
+ * fusion is an optimization to merge iteratee calls; this avoids the creation
+ * of intermediate arrays and can greatly reduce the number of iteratee executions.
+ * Sections of a chain sequence qualify for shortcut fusion if the section is
+ * applied to an array of at least two hundred elements and any iteratees
+ * accept only one argument. The heuristic for whether a section qualifies
+ * for shortcut fusion is subject to change.
+ *
+ * Chaining is supported in custom builds as long as the `_#value` method is
+ * directly or indirectly included in the build.
+ *
+ * In addition to lodash methods, wrappers have `Array` and `String` methods.
+ *
+ * The wrapper `Array` methods are:
+ * `concat`, `join`, `pop`, `push`, `shift`, `sort`, `splice`, and `unshift`
+ *
+ * The wrapper `String` methods are:
+ * `replace` and `split`
+ *
+ * The wrapper methods that support shortcut fusion are:
+ * `at`, `compact`, `drop`, `dropRight`, `dropWhile`, `filter`, `find`,
+ * `findLast`, `head`, `initial`, `last`, `map`, `reject`, `reverse`, `slice`,
+ * `tail`, `take`, `takeRight`, `takeRightWhile`, `takeWhile`, and `toArray`
+ *
+ * The chainable wrapper methods are:
+ * `after`, `ary`, `assign`, `assignIn`, `assignInWith`, `assignWith`, `at`,
+ * `before`, `bind`, `bindAll`, `bindKey`, `castArray`, `chain`, `chunk`,
+ * `commit`, `compact`, `concat`, `conforms`, `constant`, `countBy`, `create`,
+ * `curry`, `debounce`, `defaults`, `defaultsDeep`, `defer`, `delay`,
+ * `difference`, `differenceBy`, `differenceWith`, `drop`, `dropRight`,
+ * `dropRightWhile`, `dropWhile`, `extend`, `extendWith`, `fill`, `filter`,
+ * `flatten`, `flattenDeep`, `flattenDepth`, `flip`, `flow`, `flowRight`,
+ * `fromPairs`, `functions`, `functionsIn`, `groupBy`, `initial`, `intersection`,
+ * `intersectionBy`, `intersectionWith`, `invert`, `invertBy`, `invokeMap`,
+ * `iteratee`, `keyBy`, `keys`, `keysIn`, `map`, `mapKeys`, `mapValues`,
+ * `matches`, `matchesProperty`, `memoize`, `merge`, `mergeWith`, `method`,
+ * `methodOf`, `mixin`, `negate`, `nthArg`, `omit`, `omitBy`, `once`, `orderBy`,
+ * `over`, `overArgs`, `overEvery`, `overSome`, `partial`, `partialRight`,
+ * `partition`, `pick`, `pickBy`, `plant`, `property`, `propertyOf`, `pull`,
+ * `pullAll`, `pullAllBy`, `pullAllWith`, `pullAt`, `push`, `range`,
+ * `rangeRight`, `rearg`, `reject`, `remove`, `rest`, `reverse`, `sampleSize`,
+ * `set`, `setWith`, `shuffle`, `slice`, `sort`, `sortBy`, `splice`, `spread`,
+ * `tail`, `take`, `takeRight`, `takeRightWhile`, `takeWhile`, `tap`, `throttle`,
+ * `thru`, `toArray`, `toPairs`, `toPairsIn`, `toPath`, `toPlainObject`,
+ * `transform`, `unary`, `union`, `unionBy`, `unionWith`, `uniq`, `uniqBy`,
+ * `uniqWith`, `unset`, `unshift`, `unzip`, `unzipWith`, `update`, `values`,
+ * `valuesIn`, `without`, `wrap`, `xor`, `xorBy`, `xorWith`, `zip`, `zipObject`,
+ * `zipObjectDeep`, and `zipWith`
+ *
+ * The wrapper methods that are **not** chainable by default are:
+ * `add`, `attempt`, `camelCase`, `capitalize`, `ceil`, `clamp`, `clone`,
+ * `cloneDeep`, `cloneDeepWith`, `cloneWith`, `deburr`, `each`, `eachRight`,
+ * `endsWith`, `eq`, `escape`, `escapeRegExp`, `every`, `find`, `findIndex`,
+ * `findKey`, `findLast`, `findLastIndex`, `findLastKey`, `first`, `floor`,
+ * `forEach`, `forEachRight`, `forIn`, `forInRight`, `forOwn`, `forOwnRight`,
+ * `get`, `gt`, `gte`, `has`, `hasIn`, `head`, `identity`, `includes`,
+ * `indexOf`, `inRange`, `invoke`, `isArguments`, `isArray`, `isArrayBuffer`,
+ * `isArrayLike`, `isArrayLikeObject`, `isBoolean`, `isBuffer`, `isDate`,
+ * `isElement`, `isEmpty`, `isEqual`, `isEqualWith`, `isError`, `isFinite`,
+ * `isFunction`, `isInteger`, `isLength`, `isMap`, `isMatch`, `isMatchWith`,
+ * `isNaN`, `isNative`, `isNil`, `isNull`, `isNumber`, `isObject`, `isObjectLike`,
+ * `isPlainObject`, `isRegExp`, `isSafeInteger`, `isSet`, `isString`,
+ * `isUndefined`, `isTypedArray`, `isWeakMap`, `isWeakSet`, `join`, `kebabCase`,
+ * `last`, `lastIndexOf`, `lowerCase`, `lowerFirst`, `lt`, `lte`, `max`,
+ * `maxBy`, `mean`, `min`, `minBy`, `noConflict`, `noop`, `now`, `pad`,
+ * `padEnd`, `padStart`, `parseInt`, `pop`, `random`, `reduce`, `reduceRight`,
+ * `repeat`, `result`, `round`, `runInContext`, `sample`, `shift`, `size`,
+ * `snakeCase`, `some`, `sortedIndex`, `sortedIndexBy`, `sortedLastIndex`,
+ * `sortedLastIndexBy`, `startCase`, `startsWith`, `subtract`, `sum`, `sumBy`,
+ * `template`, `times`, `toInteger`, `toJSON`, `toLength`, `toLower`,
+ * `toNumber`, `toSafeInteger`, `toString`, `toUpper`, `trim`, `trimEnd`,
+ * `trimStart`, `truncate`, `unescape`, `uniqueId`, `upperCase`, `upperFirst`,
+ * `value`, and `words`
+ *
+ * @name _
+ * @constructor
+ * @category Seq
+ * @param {*} value The value to wrap in a `lodash` instance.
+ * @returns {Object} Returns the new `lodash` wrapper instance.
+ * @example
+ *
+ * function square(n) {
+ * return n * n;
+ * }
+ *
+ * var wrapped = _([1, 2, 3]);
+ *
+ * // Returns an unwrapped value.
+ * wrapped.reduce(_.add);
+ * // => 6
+ *
+ * // Returns a wrapped value.
+ * var squares = wrapped.map(square);
+ *
+ * _.isArray(squares);
+ * // => false
+ *
+ * _.isArray(squares.value());
+ * // => true
+ */
+ function lodash(value) {
+ if (isObjectLike(value) && !isArray(value) && !(value instanceof LazyWrapper)) {
+ if (value instanceof LodashWrapper) {
+ return value;
+ }
+ if (hasOwnProperty.call(value, '__wrapped__')) {
+ return wrapperClone(value);
+ }
+ }
+ return new LodashWrapper(value);
+ }
+
+ /**
+ * The function whose prototype all chaining wrappers inherit from.
+ *
+ * @private
+ */
+ function baseLodash() {
+ // No operation performed.
+ }
+
+ /**
+ * The base constructor for creating `lodash` wrapper objects.
+ *
+ * @private
+ * @param {*} value The value to wrap.
+ * @param {boolean} [chainAll] Enable chaining for all wrapper methods.
+ */
+ function LodashWrapper(value, chainAll) {
+ this.__wrapped__ = value;
+ this.__actions__ = [];
+ this.__chain__ = !!chainAll;
+ this.__index__ = 0;
+ this.__values__ = undefined;
+ }
+
+ /**
+ * By default, the template delimiters used by lodash are like those in
+ * embedded Ruby (ERB). Change the following template settings to use
+ * alternative delimiters.
+ *
+ * @static
+ * @memberOf _
+ * @type {Object}
+ */
+ lodash.templateSettings = {
+
+ /**
+ * Used to detect `data` property values to be HTML-escaped.
+ *
+ * @memberOf _.templateSettings
+ * @type {RegExp}
+ */
+ 'escape': reEscape,
+
+ /**
+ * Used to detect code to be evaluated.
+ *
+ * @memberOf _.templateSettings
+ * @type {RegExp}
+ */
+ 'evaluate': reEvaluate,
+
+ /**
+ * Used to detect `data` property values to inject.
+ *
+ * @memberOf _.templateSettings
+ * @type {RegExp}
+ */
+ 'interpolate': reInterpolate,
+
+ /**
+ * Used to reference the data object in the template text.
+ *
+ * @memberOf _.templateSettings
+ * @type {string}
+ */
+ 'variable': '',
+
+ /**
+ * Used to import variables into the compiled template.
+ *
+ * @memberOf _.templateSettings
+ * @type {Object}
+ */
+ 'imports': {
+
+ /**
+ * A reference to the `lodash` function.
+ *
+ * @memberOf _.templateSettings.imports
+ * @type {Function}
+ */
+ '_': lodash
+ }
+ };
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Creates a lazy wrapper object which wraps `value` to enable lazy evaluation.
+ *
+ * @private
+ * @constructor
+ * @param {*} value The value to wrap.
+ */
+ function LazyWrapper(value) {
+ this.__wrapped__ = value;
+ this.__actions__ = [];
+ this.__dir__ = 1;
+ this.__filtered__ = false;
+ this.__iteratees__ = [];
+ this.__takeCount__ = MAX_ARRAY_LENGTH;
+ this.__views__ = [];
+ }
+
+ /**
+ * Creates a clone of the lazy wrapper object.
+ *
+ * @private
+ * @name clone
+ * @memberOf LazyWrapper
+ * @returns {Object} Returns the cloned `LazyWrapper` object.
+ */
+ function lazyClone() {
+ var result = new LazyWrapper(this.__wrapped__);
+ result.__actions__ = copyArray(this.__actions__);
+ result.__dir__ = this.__dir__;
+ result.__filtered__ = this.__filtered__;
+ result.__iteratees__ = copyArray(this.__iteratees__);
+ result.__takeCount__ = this.__takeCount__;
+ result.__views__ = copyArray(this.__views__);
+ return result;
+ }
+
+ /**
+ * Reverses the direction of lazy iteration.
+ *
+ * @private
+ * @name reverse
+ * @memberOf LazyWrapper
+ * @returns {Object} Returns the new reversed `LazyWrapper` object.
+ */
+ function lazyReverse() {
+ if (this.__filtered__) {
+ var result = new LazyWrapper(this);
+ result.__dir__ = -1;
+ result.__filtered__ = true;
+ } else {
+ result = this.clone();
+ result.__dir__ *= -1;
+ }
+ return result;
+ }
+
+ /**
+ * Extracts the unwrapped value from its lazy wrapper.
+ *
+ * @private
+ * @name value
+ * @memberOf LazyWrapper
+ * @returns {*} Returns the unwrapped value.
+ */
+ function lazyValue() {
+ var array = this.__wrapped__.value(),
+ dir = this.__dir__,
+ isArr = isArray(array),
+ isRight = dir < 0,
+ arrLength = isArr ? array.length : 0,
+ view = getView(0, arrLength, this.__views__),
+ start = view.start,
+ end = view.end,
+ length = end - start,
+ index = isRight ? end : (start - 1),
+ iteratees = this.__iteratees__,
+ iterLength = iteratees.length,
+ resIndex = 0,
+ takeCount = nativeMin(length, this.__takeCount__);
+
+ if (!isArr || arrLength < LARGE_ARRAY_SIZE ||
+ (arrLength == length && takeCount == length)) {
+ return baseWrapperValue(array, this.__actions__);
+ }
+ var result = [];
+
+ outer:
+ while (length-- && resIndex < takeCount) {
+ index += dir;
+
+ var iterIndex = -1,
+ value = array[index];
+
+ while (++iterIndex < iterLength) {
+ var data = iteratees[iterIndex],
+ iteratee = data.iteratee,
+ type = data.type,
+ computed = iteratee(value);
+
+ if (type == LAZY_MAP_FLAG) {
+ value = computed;
+ } else if (!computed) {
+ if (type == LAZY_FILTER_FLAG) {
+ continue outer;
+ } else {
+ break outer;
+ }
+ }
+ }
+ result[resIndex++] = value;
+ }
+ return result;
+ }
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Creates an hash object.
+ *
+ * @private
+ * @constructor
+ * @returns {Object} Returns the new hash object.
+ */
+ function Hash() {}
+
+ /**
+ * Removes `key` and its value from the hash.
+ *
+ * @private
+ * @param {Object} hash The hash to modify.
+ * @param {string} key The key of the value to remove.
+ * @returns {boolean} Returns `true` if the entry was removed, else `false`.
+ */
+ function hashDelete(hash, key) {
+ return hashHas(hash, key) && delete hash[key];
+ }
+
+ /**
+ * Gets the hash value for `key`.
+ *
+ * @private
+ * @param {Object} hash The hash to query.
+ * @param {string} key The key of the value to get.
+ * @returns {*} Returns the entry value.
+ */
+ function hashGet(hash, key) {
+ if (nativeCreate) {
+ var result = hash[key];
+ return result === HASH_UNDEFINED ? undefined : result;
+ }
+ return hasOwnProperty.call(hash, key) ? hash[key] : undefined;
+ }
+
+ /**
+ * Checks if a hash value for `key` exists.
+ *
+ * @private
+ * @param {Object} hash The hash to query.
+ * @param {string} key The key of the entry to check.
+ * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
+ */
+ function hashHas(hash, key) {
+ return nativeCreate ? hash[key] !== undefined : hasOwnProperty.call(hash, key);
+ }
+
+ /**
+ * Sets the hash `key` to `value`.
+ *
+ * @private
+ * @param {Object} hash The hash to modify.
+ * @param {string} key The key of the value to set.
+ * @param {*} value The value to set.
+ */
+ function hashSet(hash, key, value) {
+ hash[key] = (nativeCreate && value === undefined) ? HASH_UNDEFINED : value;
+ }
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Creates a map cache object to store key-value pairs.
+ *
+ * @private
+ * @constructor
+ * @param {Array} [values] The values to cache.
+ */
+ function MapCache(values) {
+ var index = -1,
+ length = values ? values.length : 0;
+
+ this.clear();
+ while (++index < length) {
+ var entry = values[index];
+ this.set(entry[0], entry[1]);
+ }
+ }
+
+ /**
+ * Removes all key-value entries from the map.
+ *
+ * @private
+ * @name clear
+ * @memberOf MapCache
+ */
+ function mapClear() {
+ this.__data__ = {
+ 'hash': new Hash,
+ 'map': Map ? new Map : [],
+ 'string': new Hash
+ };
+ }
+
+ /**
+ * Removes `key` and its value from the map.
+ *
+ * @private
+ * @name delete
+ * @memberOf MapCache
+ * @param {string} key The key of the value to remove.
+ * @returns {boolean} Returns `true` if the entry was removed, else `false`.
+ */
+ function mapDelete(key) {
+ var data = this.__data__;
+ if (isKeyable(key)) {
+ return hashDelete(typeof key == 'string' ? data.string : data.hash, key);
+ }
+ return Map ? data.map['delete'](key) : assocDelete(data.map, key);
+ }
+
+ /**
+ * Gets the map value for `key`.
+ *
+ * @private
+ * @name get
+ * @memberOf MapCache
+ * @param {string} key The key of the value to get.
+ * @returns {*} Returns the entry value.
+ */
+ function mapGet(key) {
+ var data = this.__data__;
+ if (isKeyable(key)) {
+ return hashGet(typeof key == 'string' ? data.string : data.hash, key);
+ }
+ return Map ? data.map.get(key) : assocGet(data.map, key);
+ }
+
+ /**
+ * Checks if a map value for `key` exists.
+ *
+ * @private
+ * @name has
+ * @memberOf MapCache
+ * @param {string} key The key of the entry to check.
+ * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
+ */
+ function mapHas(key) {
+ var data = this.__data__;
+ if (isKeyable(key)) {
+ return hashHas(typeof key == 'string' ? data.string : data.hash, key);
+ }
+ return Map ? data.map.has(key) : assocHas(data.map, key);
+ }
+
+ /**
+ * Sets the map `key` to `value`.
+ *
+ * @private
+ * @name set
+ * @memberOf MapCache
+ * @param {string} key The key of the value to set.
+ * @param {*} value The value to set.
+ * @returns {Object} Returns the map cache object.
+ */
+ function mapSet(key, value) {
+ var data = this.__data__;
+ if (isKeyable(key)) {
+ hashSet(typeof key == 'string' ? data.string : data.hash, key, value);
+ } else if (Map) {
+ data.map.set(key, value);
+ } else {
+ assocSet(data.map, key, value);
+ }
+ return this;
+ }
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ *
+ * Creates a set cache object to store unique values.
+ *
+ * @private
+ * @constructor
+ * @param {Array} [values] The values to cache.
+ */
+ function SetCache(values) {
+ var index = -1,
+ length = values ? values.length : 0;
+
+ this.__data__ = new MapCache;
+ while (++index < length) {
+ this.push(values[index]);
+ }
+ }
+
+ /**
+ * Checks if `value` is in `cache`.
+ *
+ * @private
+ * @param {Object} cache The set cache to search.
+ * @param {*} value The value to search for.
+ * @returns {number} Returns `true` if `value` is found, else `false`.
+ */
+ function cacheHas(cache, value) {
+ var map = cache.__data__;
+ if (isKeyable(value)) {
+ var data = map.__data__,
+ hash = typeof value == 'string' ? data.string : data.hash;
+
+ return hash[value] === HASH_UNDEFINED;
+ }
+ return map.has(value);
+ }
+
+ /**
+ * Adds `value` to the set cache.
+ *
+ * @private
+ * @name push
+ * @memberOf SetCache
+ * @param {*} value The value to cache.
+ */
+ function cachePush(value) {
+ var map = this.__data__;
+ if (isKeyable(value)) {
+ var data = map.__data__,
+ hash = typeof value == 'string' ? data.string : data.hash;
+
+ hash[value] = HASH_UNDEFINED;
+ }
+ else {
+ map.set(value, HASH_UNDEFINED);
+ }
+ }
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Creates a stack cache object to store key-value pairs.
+ *
+ * @private
+ * @constructor
+ * @param {Array} [values] The values to cache.
+ */
+ function Stack(values) {
+ var index = -1,
+ length = values ? values.length : 0;
+
+ this.clear();
+ while (++index < length) {
+ var entry = values[index];
+ this.set(entry[0], entry[1]);
+ }
+ }
+
+ /**
+ * Removes all key-value entries from the stack.
+ *
+ * @private
+ * @name clear
+ * @memberOf Stack
+ */
+ function stackClear() {
+ this.__data__ = { 'array': [], 'map': null };
+ }
+
+ /**
+ * Removes `key` and its value from the stack.
+ *
+ * @private
+ * @name delete
+ * @memberOf Stack
+ * @param {string} key The key of the value to remove.
+ * @returns {boolean} Returns `true` if the entry was removed, else `false`.
+ */
+ function stackDelete(key) {
+ var data = this.__data__,
+ array = data.array;
+
+ return array ? assocDelete(array, key) : data.map['delete'](key);
+ }
+
+ /**
+ * Gets the stack value for `key`.
+ *
+ * @private
+ * @name get
+ * @memberOf Stack
+ * @param {string} key The key of the value to get.
+ * @returns {*} Returns the entry value.
+ */
+ function stackGet(key) {
+ var data = this.__data__,
+ array = data.array;
+
+ return array ? assocGet(array, key) : data.map.get(key);
+ }
+
+ /**
+ * Checks if a stack value for `key` exists.
+ *
+ * @private
+ * @name has
+ * @memberOf Stack
+ * @param {string} key The key of the entry to check.
+ * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
+ */
+ function stackHas(key) {
+ var data = this.__data__,
+ array = data.array;
+
+ return array ? assocHas(array, key) : data.map.has(key);
+ }
+
+ /**
+ * Sets the stack `key` to `value`.
+ *
+ * @private
+ * @name set
+ * @memberOf Stack
+ * @param {string} key The key of the value to set.
+ * @param {*} value The value to set.
+ * @returns {Object} Returns the stack cache object.
+ */
+ function stackSet(key, value) {
+ var data = this.__data__,
+ array = data.array;
+
+ if (array) {
+ if (array.length < (LARGE_ARRAY_SIZE - 1)) {
+ assocSet(array, key, value);
+ } else {
+ data.array = null;
+ data.map = new MapCache(array);
+ }
+ }
+ var map = data.map;
+ if (map) {
+ map.set(key, value);
+ }
+ return this;
+ }
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Removes `key` and its value from the associative array.
+ *
+ * @private
+ * @param {Array} array The array to query.
+ * @param {string} key The key of the value to remove.
+ * @returns {boolean} Returns `true` if the entry was removed, else `false`.
+ */
+ function assocDelete(array, key) {
+ var index = assocIndexOf(array, key);
+ if (index < 0) {
+ return false;
+ }
+ var lastIndex = array.length - 1;
+ if (index == lastIndex) {
+ array.pop();
+ } else {
+ splice.call(array, index, 1);
+ }
+ return true;
+ }
+
+ /**
+ * Gets the associative array value for `key`.
+ *
+ * @private
+ * @param {Array} array The array to query.
+ * @param {string} key The key of the value to get.
+ * @returns {*} Returns the entry value.
+ */
+ function assocGet(array, key) {
+ var index = assocIndexOf(array, key);
+ return index < 0 ? undefined : array[index][1];
+ }
+
+ /**
+ * Checks if an associative array value for `key` exists.
+ *
+ * @private
+ * @param {Array} array The array to query.
+ * @param {string} key The key of the entry to check.
+ * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
+ */
+ function assocHas(array, key) {
+ return assocIndexOf(array, key) > -1;
+ }
+
+ /**
+ * Gets the index at which the first occurrence of `key` is found in `array`
+ * of key-value pairs.
+ *
+ * @private
+ * @param {Array} array The array to search.
+ * @param {*} key The key to search for.
+ * @returns {number} Returns the index of the matched value, else `-1`.
+ */
+ function assocIndexOf(array, key) {
+ var length = array.length;
+ while (length--) {
+ if (eq(array[length][0], key)) {
+ return length;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Sets the associative array `key` to `value`.
+ *
+ * @private
+ * @param {Array} array The array to modify.
+ * @param {string} key The key of the value to set.
+ * @param {*} value The value to set.
+ */
+ function assocSet(array, key, value) {
+ var index = assocIndexOf(array, key);
+ if (index < 0) {
+ array.push([key, value]);
+ } else {
+ array[index][1] = value;
+ }
+ }
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Used by `_.defaults` to customize its `_.assignIn` use.
+ *
+ * @private
+ * @param {*} objValue The destination value.
+ * @param {*} srcValue The source value.
+ * @param {string} key The key of the property to assign.
+ * @param {Object} object The parent object of `objValue`.
+ * @returns {*} Returns the value to assign.
+ */
+ function assignInDefaults(objValue, srcValue, key, object) {
+ if (objValue === undefined ||
+ (eq(objValue, objectProto[key]) && !hasOwnProperty.call(object, key))) {
+ return srcValue;
+ }
+ return objValue;
+ }
+
+ /**
+ * This function is like `assignValue` except that it doesn't assign
+ * `undefined` values.
+ *
+ * @private
+ * @param {Object} object The object to modify.
+ * @param {string} key The key of the property to assign.
+ * @param {*} value The value to assign.
+ */
+ function assignMergeValue(object, key, value) {
+ if ((value !== undefined && !eq(object[key], value)) ||
+ (typeof key == 'number' && value === undefined && !(key in object))) {
+ object[key] = value;
+ }
+ }
+
+ /**
+ * Assigns `value` to `key` of `object` if the existing value is not equivalent
+ * using [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+ * for equality comparisons.
+ *
+ * @private
+ * @param {Object} object The object to modify.
+ * @param {string} key The key of the property to assign.
+ * @param {*} value The value to assign.
+ */
+ function assignValue(object, key, value) {
+ var objValue = object[key];
+ if (!(hasOwnProperty.call(object, key) && eq(objValue, value)) ||
+ (value === undefined && !(key in object))) {
+ object[key] = value;
+ }
+ }
+
+ /**
+ * Aggregates elements of `collection` on `accumulator` with keys transformed
+ * by `iteratee` and values set by `setter`.
+ *
+ * @private
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} setter The function to set `accumulator` values.
+ * @param {Function} iteratee The iteratee to transform keys.
+ * @param {Object} accumulator The initial aggregated object.
+ * @returns {Function} Returns `accumulator`.
+ */
+ function baseAggregator(collection, setter, iteratee, accumulator) {
+ baseEach(collection, function(value, key, collection) {
+ setter(accumulator, value, iteratee(value), collection);
+ });
+ return accumulator;
+ }
+
+ /**
+ * The base implementation of `_.assign` without support for multiple sources
+ * or `customizer` functions.
+ *
+ * @private
+ * @param {Object} object The destination object.
+ * @param {Object} source The source object.
+ * @returns {Object} Returns `object`.
+ */
+ function baseAssign(object, source) {
+ return object && copyObject(source, keys(source), object);
+ }
+
+ /**
+ * The base implementation of `_.at` without support for individual paths.
+ *
+ * @private
+ * @param {Object} object The object to iterate over.
+ * @param {string[]} paths The property paths of elements to pick.
+ * @returns {Array} Returns the new array of picked elements.
+ */
+ function baseAt(object, paths) {
+ var index = -1,
+ isNil = object == null,
+ length = paths.length,
+ result = Array(length);
+
+ while (++index < length) {
+ result[index] = isNil ? undefined : get(object, paths[index]);
+ }
+ return result;
+ }
+
+ /**
+ * Casts `value` to an empty array if it's not an array like object.
+ *
+ * @private
+ * @param {*} value The value to inspect.
+ * @returns {Array} Returns the array-like object.
+ */
+ function baseCastArrayLikeObject(value) {
+ return isArrayLikeObject(value) ? value : [];
+ }
+
+ /**
+ * Casts `value` to `identity` if it's not a function.
+ *
+ * @private
+ * @param {*} value The value to inspect.
+ * @returns {Array} Returns the array-like object.
+ */
+ function baseCastFunction(value) {
+ return typeof value == 'function' ? value : identity;
+ }
+
+ /**
+ * Casts `value` to a path array if it's not one.
+ *
+ * @private
+ * @param {*} value The value to inspect.
+ * @returns {Array} Returns the cast property path array.
+ */
+ function baseCastPath(value) {
+ return isArray(value) ? value : stringToPath(value);
+ }
+
+ /**
+ * The base implementation of `_.clamp` which doesn't coerce arguments to numbers.
+ *
+ * @private
+ * @param {number} number The number to clamp.
+ * @param {number} [lower] The lower bound.
+ * @param {number} upper The upper bound.
+ * @returns {number} Returns the clamped number.
+ */
+ function baseClamp(number, lower, upper) {
+ if (number === number) {
+ if (upper !== undefined) {
+ number = number <= upper ? number : upper;
+ }
+ if (lower !== undefined) {
+ number = number >= lower ? number : lower;
+ }
+ }
+ return number;
+ }
+
+ /**
+ * The base implementation of `_.clone` and `_.cloneDeep` which tracks
+ * traversed objects.
+ *
+ * @private
+ * @param {*} value The value to clone.
+ * @param {boolean} [isDeep] Specify a deep clone.
+ * @param {boolean} [isFull] Specify a clone including symbols.
+ * @param {Function} [customizer] The function to customize cloning.
+ * @param {string} [key] The key of `value`.
+ * @param {Object} [object] The parent object of `value`.
+ * @param {Object} [stack] Tracks traversed objects and their clone counterparts.
+ * @returns {*} Returns the cloned value.
+ */
+ function baseClone(value, isDeep, isFull, customizer, key, object, stack) {
+ var result;
+ if (customizer) {
+ result = object ? customizer(value, key, object, stack) : customizer(value);
+ }
+ if (result !== undefined) {
+ return result;
+ }
+ if (!isObject(value)) {
+ return value;
+ }
+ var isArr = isArray(value);
+ if (isArr) {
+ result = initCloneArray(value);
+ if (!isDeep) {
+ return copyArray(value, result);
+ }
+ } else {
+ var tag = getTag(value),
+ isFunc = tag == funcTag || tag == genTag;
+
+ if (isBuffer(value)) {
+ return cloneBuffer(value, isDeep);
+ }
+ if (tag == objectTag || tag == argsTag || (isFunc && !object)) {
+ if (isHostObject(value)) {
+ return object ? value : {};
+ }
+ result = initCloneObject(isFunc ? {} : value);
+ if (!isDeep) {
+ result = baseAssign(result, value);
+ return isFull ? copySymbols(value, result) : result;
+ }
+ } else {
+ if (!cloneableTags[tag]) {
+ return object ? value : {};
+ }
+ result = initCloneByTag(value, tag, isDeep);
+ }
+ }
+ // Check for circular references and return its corresponding clone.
+ stack || (stack = new Stack);
+ var stacked = stack.get(value);
+ if (stacked) {
+ return stacked;
+ }
+ stack.set(value, result);
+
+ // Recursively populate clone (susceptible to call stack limits).
+ (isArr ? arrayEach : baseForOwn)(value, function(subValue, key) {
+ assignValue(result, key, baseClone(subValue, isDeep, isFull, customizer, key, value, stack));
+ });
+ return (isFull && !isArr) ? copySymbols(value, result) : result;
+ }
+
+ /**
+ * The base implementation of `_.conforms` which doesn't clone `source`.
+ *
+ * @private
+ * @param {Object} source The object of property predicates to conform to.
+ * @returns {Function} Returns the new function.
+ */
+ function baseConforms(source) {
+ var props = keys(source),
+ length = props.length;
+
+ return function(object) {
+ if (object == null) {
+ return !length;
+ }
+ var index = length;
+ while (index--) {
+ var key = props[index],
+ predicate = source[key],
+ value = object[key];
+
+ if ((value === undefined && !(key in Object(object))) || !predicate(value)) {
+ return false;
+ }
+ }
+ return true;
+ };
+ }
+
+ /**
+ * The base implementation of `_.create` without support for assigning
+ * properties to the created object.
+ *
+ * @private
+ * @param {Object} prototype The object to inherit from.
+ * @returns {Object} Returns the new object.
+ */
+ function baseCreate(proto) {
+ return isObject(proto) ? objectCreate(proto) : {};
+ }
+
+ /**
+ * The base implementation of `_.delay` and `_.defer` which accepts an array
+ * of `func` arguments.
+ *
+ * @private
+ * @param {Function} func The function to delay.
+ * @param {number} wait The number of milliseconds to delay invocation.
+ * @param {Object} args The arguments to provide to `func`.
+ * @returns {number} Returns the timer id.
+ */
+ function baseDelay(func, wait, args) {
+ if (typeof func != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
+ return setTimeout(function() { func.apply(undefined, args); }, wait);
+ }
+
+ /**
+ * The base implementation of methods like `_.difference` without support for
+ * excluding multiple arrays or iteratee shorthands.
+ *
+ * @private
+ * @param {Array} array The array to inspect.
+ * @param {Array} values The values to exclude.
+ * @param {Function} [iteratee] The iteratee invoked per element.
+ * @param {Function} [comparator] The comparator invoked per element.
+ * @returns {Array} Returns the new array of filtered values.
+ */
+ function baseDifference(array, values, iteratee, comparator) {
+ var index = -1,
+ includes = arrayIncludes,
+ isCommon = true,
+ length = array.length,
+ result = [],
+ valuesLength = values.length;
+
+ if (!length) {
+ return result;
+ }
+ if (iteratee) {
+ values = arrayMap(values, baseUnary(iteratee));
+ }
+ if (comparator) {
+ includes = arrayIncludesWith;
+ isCommon = false;
+ }
+ else if (values.length >= LARGE_ARRAY_SIZE) {
+ includes = cacheHas;
+ isCommon = false;
+ values = new SetCache(values);
+ }
+ outer:
+ while (++index < length) {
+ var value = array[index],
+ computed = iteratee ? iteratee(value) : value;
+
+ if (isCommon && computed === computed) {
+ var valuesIndex = valuesLength;
+ while (valuesIndex--) {
+ if (values[valuesIndex] === computed) {
+ continue outer;
+ }
+ }
+ result.push(value);
+ }
+ else if (!includes(values, computed, comparator)) {
+ result.push(value);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * The base implementation of `_.forEach` without support for iteratee shorthands.
+ *
+ * @private
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @returns {Array|Object} Returns `collection`.
+ */
+ var baseEach = createBaseEach(baseForOwn);
+
+ /**
+ * The base implementation of `_.forEachRight` without support for iteratee shorthands.
+ *
+ * @private
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @returns {Array|Object} Returns `collection`.
+ */
+ var baseEachRight = createBaseEach(baseForOwnRight, true);
+
+ /**
+ * The base implementation of `_.every` without support for iteratee shorthands.
+ *
+ * @private
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} predicate The function invoked per iteration.
+ * @returns {boolean} Returns `true` if all elements pass the predicate check, else `false`
+ */
+ function baseEvery(collection, predicate) {
+ var result = true;
+ baseEach(collection, function(value, index, collection) {
+ result = !!predicate(value, index, collection);
+ return result;
+ });
+ return result;
+ }
+
+ /**
+ * The base implementation of `_.fill` without an iteratee call guard.
+ *
+ * @private
+ * @param {Array} array The array to fill.
+ * @param {*} value The value to fill `array` with.
+ * @param {number} [start=0] The start position.
+ * @param {number} [end=array.length] The end position.
+ * @returns {Array} Returns `array`.
+ */
+ function baseFill(array, value, start, end) {
+ var length = array.length;
+
+ start = toInteger(start);
+ if (start < 0) {
+ start = -start > length ? 0 : (length + start);
+ }
+ end = (end === undefined || end > length) ? length : toInteger(end);
+ if (end < 0) {
+ end += length;
+ }
+ end = start > end ? 0 : toLength(end);
+ while (start < end) {
+ array[start++] = value;
+ }
+ return array;
+ }
+
+ /**
+ * The base implementation of `_.filter` without support for iteratee shorthands.
+ *
+ * @private
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} predicate The function invoked per iteration.
+ * @returns {Array} Returns the new filtered array.
+ */
+ function baseFilter(collection, predicate) {
+ var result = [];
+ baseEach(collection, function(value, index, collection) {
+ if (predicate(value, index, collection)) {
+ result.push(value);
+ }
+ });
+ return result;
+ }
+
+ /**
+ * The base implementation of `_.flatten` with support for restricting flattening.
+ *
+ * @private
+ * @param {Array} array The array to flatten.
+ * @param {number} depth The maximum recursion depth.
+ * @param {boolean} [isStrict] Restrict flattening to arrays-like objects.
+ * @param {Array} [result=[]] The initial result value.
+ * @returns {Array} Returns the new flattened array.
+ */
+ function baseFlatten(array, depth, isStrict, result) {
+ result || (result = []);
+
+ var index = -1,
+ length = array.length;
+
+ while (++index < length) {
+ var value = array[index];
+ if (depth > 0 && isArrayLikeObject(value) &&
+ (isStrict || isArray(value) || isArguments(value))) {
+ if (depth > 1) {
+ // Recursively flatten arrays (susceptible to call stack limits).
+ baseFlatten(value, depth - 1, isStrict, result);
+ } else {
+ arrayPush(result, value);
+ }
+ } else if (!isStrict) {
+ result[result.length] = value;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * The base implementation of `baseForIn` and `baseForOwn` which iterates
+ * over `object` properties returned by `keysFunc` invoking `iteratee` for
+ * each property. Iteratee functions may exit iteration early by explicitly
+ * returning `false`.
+ *
+ * @private
+ * @param {Object} object The object to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @param {Function} keysFunc The function to get the keys of `object`.
+ * @returns {Object} Returns `object`.
+ */
+ var baseFor = createBaseFor();
+
+ /**
+ * This function is like `baseFor` except that it iterates over properties
+ * in the opposite order.
+ *
+ * @private
+ * @param {Object} object The object to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @param {Function} keysFunc The function to get the keys of `object`.
+ * @returns {Object} Returns `object`.
+ */
+ var baseForRight = createBaseFor(true);
+
+ /**
+ * The base implementation of `_.forIn` without support for iteratee shorthands.
+ *
+ * @private
+ * @param {Object} object The object to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @returns {Object} Returns `object`.
+ */
+ function baseForIn(object, iteratee) {
+ return object == null ? object : baseFor(object, iteratee, keysIn);
+ }
+
+ /**
+ * The base implementation of `_.forOwn` without support for iteratee shorthands.
+ *
+ * @private
+ * @param {Object} object The object to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @returns {Object} Returns `object`.
+ */
+ function baseForOwn(object, iteratee) {
+ return object && baseFor(object, iteratee, keys);
+ }
+
+ /**
+ * The base implementation of `_.forOwnRight` without support for iteratee shorthands.
+ *
+ * @private
+ * @param {Object} object The object to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @returns {Object} Returns `object`.
+ */
+ function baseForOwnRight(object, iteratee) {
+ return object && baseForRight(object, iteratee, keys);
+ }
+
+ /**
+ * The base implementation of `_.functions` which creates an array of
+ * `object` function property names filtered from `props`.
+ *
+ * @private
+ * @param {Object} object The object to inspect.
+ * @param {Array} props The property names to filter.
+ * @returns {Array} Returns the new array of filtered property names.
+ */
+ function baseFunctions(object, props) {
+ return arrayFilter(props, function(key) {
+ return isFunction(object[key]);
+ });
+ }
+
+ /**
+ * The base implementation of `_.get` without support for default values.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @param {Array|string} path The path of the property to get.
+ * @returns {*} Returns the resolved value.
+ */
+ function baseGet(object, path) {
+ path = isKey(path, object) ? [path + ''] : baseCastPath(path);
+
+ var index = 0,
+ length = path.length;
+
+ while (object != null && index < length) {
+ object = object[path[index++]];
+ }
+ return (index && index == length) ? object : undefined;
+ }
+
+ /**
+ * The base implementation of `_.has` without support for deep paths.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @param {Array|string} key The key to check.
+ * @returns {boolean} Returns `true` if `key` exists, else `false`.
+ */
+ function baseHas(object, key) {
+ // Avoid a bug in IE 10-11 where objects with a [[Prototype]] of `null`,
+ // that are composed entirely of index properties, return `false` for
+ // `hasOwnProperty` checks of them.
+ return hasOwnProperty.call(object, key) ||
+ (typeof object == 'object' && key in object && getPrototypeOf(object) === null);
+ }
+
+ /**
+ * The base implementation of `_.hasIn` without support for deep paths.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @param {Array|string} key The key to check.
+ * @returns {boolean} Returns `true` if `key` exists, else `false`.
+ */
+ function baseHasIn(object, key) {
+ return key in Object(object);
+ }
+
+ /**
+ * The base implementation of `_.inRange` which doesn't coerce arguments to numbers.
+ *
+ * @private
+ * @param {number} number The number to check.
+ * @param {number} start The start of the range.
+ * @param {number} end The end of the range.
+ * @returns {boolean} Returns `true` if `number` is in the range, else `false`.
+ */
+ function baseInRange(number, start, end) {
+ return number >= nativeMin(start, end) && number < nativeMax(start, end);
+ }
+
+ /**
+ * The base implementation of methods like `_.intersection`, without support
+ * for iteratee shorthands, that accepts an array of arrays to inspect.
+ *
+ * @private
+ * @param {Array} arrays The arrays to inspect.
+ * @param {Function} [iteratee] The iteratee invoked per element.
+ * @param {Function} [comparator] The comparator invoked per element.
+ * @returns {Array} Returns the new array of shared values.
+ */
+ function baseIntersection(arrays, iteratee, comparator) {
+ var includes = comparator ? arrayIncludesWith : arrayIncludes,
+ length = arrays[0].length,
+ othLength = arrays.length,
+ othIndex = othLength,
+ caches = Array(othLength),
+ maxLength = Infinity,
+ result = [];
+
+ while (othIndex--) {
+ var array = arrays[othIndex];
+ if (othIndex && iteratee) {
+ array = arrayMap(array, baseUnary(iteratee));
+ }
+ maxLength = nativeMin(array.length, maxLength);
+ caches[othIndex] = !comparator && (iteratee || (length >= 120 && array.length >= 120))
+ ? new SetCache(othIndex && array)
+ : undefined;
+ }
+ array = arrays[0];
+
+ var index = -1,
+ seen = caches[0];
+
+ outer:
+ while (++index < length && result.length < maxLength) {
+ var value = array[index],
+ computed = iteratee ? iteratee(value) : value;
+
+ if (!(seen
+ ? cacheHas(seen, computed)
+ : includes(result, computed, comparator)
+ )) {
+ othIndex = othLength;
+ while (--othIndex) {
+ var cache = caches[othIndex];
+ if (!(cache
+ ? cacheHas(cache, computed)
+ : includes(arrays[othIndex], computed, comparator))
+ ) {
+ continue outer;
+ }
+ }
+ if (seen) {
+ seen.push(computed);
+ }
+ result.push(value);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * The base implementation of `_.invert` and `_.invertBy` which inverts
+ * `object` with values transformed by `iteratee` and set by `setter`.
+ *
+ * @private
+ * @param {Object} object The object to iterate over.
+ * @param {Function} setter The function to set `accumulator` values.
+ * @param {Function} iteratee The iteratee to transform values.
+ * @param {Object} accumulator The initial inverted object.
+ * @returns {Function} Returns `accumulator`.
+ */
+ function baseInverter(object, setter, iteratee, accumulator) {
+ baseForOwn(object, function(value, key, object) {
+ setter(accumulator, iteratee(value), key, object);
+ });
+ return accumulator;
+ }
+
+ /**
+ * The base implementation of `_.invoke` without support for individual
+ * method arguments.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @param {Array|string} path The path of the method to invoke.
+ * @param {Array} args The arguments to invoke the method with.
+ * @returns {*} Returns the result of the invoked method.
+ */
+ function baseInvoke(object, path, args) {
+ if (!isKey(path, object)) {
+ path = baseCastPath(path);
+ object = parent(object, path);
+ path = last(path);
+ }
+ var func = object == null ? object : object[path];
+ return func == null ? undefined : apply(func, object, args);
+ }
+
+ /**
+ * The base implementation of `_.isEqual` which supports partial comparisons
+ * and tracks traversed objects.
+ *
+ * @private
+ * @param {*} value The value to compare.
+ * @param {*} other The other value to compare.
+ * @param {Function} [customizer] The function to customize comparisons.
+ * @param {boolean} [bitmask] The bitmask of comparison flags.
+ * The bitmask may be composed of the following flags:
+ * 1 - Unordered comparison
+ * 2 - Partial comparison
+ * @param {Object} [stack] Tracks traversed `value` and `other` objects.
+ * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
+ */
+ function baseIsEqual(value, other, customizer, bitmask, stack) {
+ if (value === other) {
+ return true;
+ }
+ if (value == null || other == null || (!isObject(value) && !isObjectLike(other))) {
+ return value !== value && other !== other;
+ }
+ return baseIsEqualDeep(value, other, baseIsEqual, customizer, bitmask, stack);
+ }
+
+ /**
+ * A specialized version of `baseIsEqual` for arrays and objects which performs
+ * deep comparisons and tracks traversed objects enabling objects with circular
+ * references to be compared.
+ *
+ * @private
+ * @param {Object} object The object to compare.
+ * @param {Object} other The other object to compare.
+ * @param {Function} equalFunc The function to determine equivalents of values.
+ * @param {Function} [customizer] The function to customize comparisons.
+ * @param {number} [bitmask] The bitmask of comparison flags. See `baseIsEqual` for more details.
+ * @param {Object} [stack] Tracks traversed `object` and `other` objects.
+ * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
+ */
+ function baseIsEqualDeep(object, other, equalFunc, customizer, bitmask, stack) {
+ var objIsArr = isArray(object),
+ othIsArr = isArray(other),
+ objTag = arrayTag,
+ othTag = arrayTag;
+
+ if (!objIsArr) {
+ objTag = getTag(object);
+ objTag = objTag == argsTag ? objectTag : objTag;
+ }
+ if (!othIsArr) {
+ othTag = getTag(other);
+ othTag = othTag == argsTag ? objectTag : othTag;
+ }
+ var objIsObj = objTag == objectTag && !isHostObject(object),
+ othIsObj = othTag == objectTag && !isHostObject(other),
+ isSameTag = objTag == othTag;
+
+ if (isSameTag && !objIsObj) {
+ stack || (stack = new Stack);
+ return (objIsArr || isTypedArray(object))
+ ? equalArrays(object, other, equalFunc, customizer, bitmask, stack)
+ : equalByTag(object, other, objTag, equalFunc, customizer, bitmask, stack);
+ }
+ if (!(bitmask & PARTIAL_COMPARE_FLAG)) {
+ var objIsWrapped = objIsObj && hasOwnProperty.call(object, '__wrapped__'),
+ othIsWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__');
+
+ if (objIsWrapped || othIsWrapped) {
+ stack || (stack = new Stack);
+ return equalFunc(objIsWrapped ? object.value() : object, othIsWrapped ? other.value() : other, customizer, bitmask, stack);
+ }
+ }
+ if (!isSameTag) {
+ return false;
+ }
+ stack || (stack = new Stack);
+ return equalObjects(object, other, equalFunc, customizer, bitmask, stack);
+ }
+
+ /**
+ * The base implementation of `_.isMatch` without support for iteratee shorthands.
+ *
+ * @private
+ * @param {Object} object The object to inspect.
+ * @param {Object} source The object of property values to match.
+ * @param {Array} matchData The property names, values, and compare flags to match.
+ * @param {Function} [customizer] The function to customize comparisons.
+ * @returns {boolean} Returns `true` if `object` is a match, else `false`.
+ */
+ function baseIsMatch(object, source, matchData, customizer) {
+ var index = matchData.length,
+ length = index,
+ noCustomizer = !customizer;
+
+ if (object == null) {
+ return !length;
+ }
+ object = Object(object);
+ while (index--) {
+ var data = matchData[index];
+ if ((noCustomizer && data[2])
+ ? data[1] !== object[data[0]]
+ : !(data[0] in object)
+ ) {
+ return false;
+ }
+ }
+ while (++index < length) {
+ data = matchData[index];
+ var key = data[0],
+ objValue = object[key],
+ srcValue = data[1];
+
+ if (noCustomizer && data[2]) {
+ if (objValue === undefined && !(key in object)) {
+ return false;
+ }
+ } else {
+ var stack = new Stack,
+ result = customizer ? customizer(objValue, srcValue, key, object, source, stack) : undefined;
+
+ if (!(result === undefined
+ ? baseIsEqual(srcValue, objValue, customizer, UNORDERED_COMPARE_FLAG | PARTIAL_COMPARE_FLAG, stack)
+ : result
+ )) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * The base implementation of `_.iteratee`.
+ *
+ * @private
+ * @param {*} [value=_.identity] The value to convert to an iteratee.
+ * @returns {Function} Returns the iteratee.
+ */
+ function baseIteratee(value) {
+ var type = typeof value;
+ if (type == 'function') {
+ return value;
+ }
+ if (value == null) {
+ return identity;
+ }
+ if (type == 'object') {
+ return isArray(value)
+ ? baseMatchesProperty(value[0], value[1])
+ : baseMatches(value);
+ }
+ return property(value);
+ }
+
+ /**
+ * The base implementation of `_.keys` which doesn't skip the constructor
+ * property of prototypes or treat sparse arrays as dense.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the array of property names.
+ */
+ function baseKeys(object) {
+ return nativeKeys(Object(object));
+ }
+
+ /**
+ * The base implementation of `_.keysIn` which doesn't skip the constructor
+ * property of prototypes or treat sparse arrays as dense.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the array of property names.
+ */
+ function baseKeysIn(object) {
+ object = object == null ? object : Object(object);
+
+ var result = [];
+ for (var key in object) {
+ result.push(key);
+ }
+ return result;
+ }
+
+ // Fallback for IE < 9 with es6-shim.
+ if (enumerate && !propertyIsEnumerable.call({ 'valueOf': 1 }, 'valueOf')) {
+ baseKeysIn = function(object) {
+ return iteratorToArray(enumerate(object));
+ };
+ }
+
+ /**
+ * The base implementation of `_.map` without support for iteratee shorthands.
+ *
+ * @private
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @returns {Array} Returns the new mapped array.
+ */
+ function baseMap(collection, iteratee) {
+ var index = -1,
+ result = isArrayLike(collection) ? Array(collection.length) : [];
+
+ baseEach(collection, function(value, key, collection) {
+ result[++index] = iteratee(value, key, collection);
+ });
+ return result;
+ }
+
+ /**
+ * The base implementation of `_.matches` which doesn't clone `source`.
+ *
+ * @private
+ * @param {Object} source The object of property values to match.
+ * @returns {Function} Returns the new function.
+ */
+ function baseMatches(source) {
+ var matchData = getMatchData(source);
+ if (matchData.length == 1 && matchData[0][2]) {
+ var key = matchData[0][0],
+ value = matchData[0][1];
+
+ return function(object) {
+ if (object == null) {
+ return false;
+ }
+ return object[key] === value &&
+ (value !== undefined || (key in Object(object)));
+ };
+ }
+ return function(object) {
+ return object === source || baseIsMatch(object, source, matchData);
+ };
+ }
+
+ /**
+ * The base implementation of `_.matchesProperty` which doesn't clone `srcValue`.
+ *
+ * @private
+ * @param {string} path The path of the property to get.
+ * @param {*} srcValue The value to match.
+ * @returns {Function} Returns the new function.
+ */
+ function baseMatchesProperty(path, srcValue) {
+ return function(object) {
+ var objValue = get(object, path);
+ return (objValue === undefined && objValue === srcValue)
+ ? hasIn(object, path)
+ : baseIsEqual(srcValue, objValue, undefined, UNORDERED_COMPARE_FLAG | PARTIAL_COMPARE_FLAG);
+ };
+ }
+
+ /**
+ * The base implementation of `_.merge` without support for multiple sources.
+ *
+ * @private
+ * @param {Object} object The destination object.
+ * @param {Object} source The source object.
+ * @param {number} srcIndex The index of `source`.
+ * @param {Function} [customizer] The function to customize merged values.
+ * @param {Object} [stack] Tracks traversed source values and their merged counterparts.
+ */
+ function baseMerge(object, source, srcIndex, customizer, stack) {
+ if (object === source) {
+ return;
+ }
+ var props = (isArray(source) || isTypedArray(source))
+ ? undefined
+ : keysIn(source);
+
+ arrayEach(props || source, function(srcValue, key) {
+ if (props) {
+ key = srcValue;
+ srcValue = source[key];
+ }
+ if (isObject(srcValue)) {
+ stack || (stack = new Stack);
+ baseMergeDeep(object, source, key, srcIndex, baseMerge, customizer, stack);
+ }
+ else {
+ var newValue = customizer
+ ? customizer(object[key], srcValue, (key + ''), object, source, stack)
+ : undefined;
+
+ if (newValue === undefined) {
+ newValue = srcValue;
+ }
+ assignMergeValue(object, key, newValue);
+ }
+ });
+ }
+
+ /**
+ * A specialized version of `baseMerge` for arrays and objects which performs
+ * deep merges and tracks traversed objects enabling objects with circular
+ * references to be merged.
+ *
+ * @private
+ * @param {Object} object The destination object.
+ * @param {Object} source The source object.
+ * @param {string} key The key of the value to merge.
+ * @param {number} srcIndex The index of `source`.
+ * @param {Function} mergeFunc The function to merge values.
+ * @param {Function} [customizer] The function to customize assigned values.
+ * @param {Object} [stack] Tracks traversed source values and their merged counterparts.
+ */
+ function baseMergeDeep(object, source, key, srcIndex, mergeFunc, customizer, stack) {
+ var objValue = object[key],
+ srcValue = source[key],
+ stacked = stack.get(srcValue);
+
+ if (stacked) {
+ assignMergeValue(object, key, stacked);
+ return;
+ }
+ var newValue = customizer
+ ? customizer(objValue, srcValue, (key + ''), object, source, stack)
+ : undefined;
+
+ var isCommon = newValue === undefined;
+
+ if (isCommon) {
+ newValue = srcValue;
+ if (isArray(srcValue) || isTypedArray(srcValue)) {
+ if (isArray(objValue)) {
+ newValue = objValue;
+ }
+ else if (isArrayLikeObject(objValue)) {
+ newValue = copyArray(objValue);
+ }
+ else {
+ isCommon = false;
+ newValue = baseClone(srcValue, !customizer);
+ }
+ }
+ else if (isPlainObject(srcValue) || isArguments(srcValue)) {
+ if (isArguments(objValue)) {
+ newValue = toPlainObject(objValue);
+ }
+ else if (!isObject(objValue) || (srcIndex && isFunction(objValue))) {
+ isCommon = false;
+ newValue = baseClone(srcValue, !customizer);
+ }
+ else {
+ newValue = objValue;
+ }
+ }
+ else {
+ isCommon = false;
+ }
+ }
+ stack.set(srcValue, newValue);
+
+ if (isCommon) {
+ // Recursively merge objects and arrays (susceptible to call stack limits).
+ mergeFunc(newValue, srcValue, srcIndex, customizer, stack);
+ }
+ stack['delete'](srcValue);
+ assignMergeValue(object, key, newValue);
+ }
+
+ /**
+ * The base implementation of `_.orderBy` without param guards.
+ *
+ * @private
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function[]|Object[]|string[]} iteratees The iteratees to sort by.
+ * @param {string[]} orders The sort orders of `iteratees`.
+ * @returns {Array} Returns the new sorted array.
+ */
+ function baseOrderBy(collection, iteratees, orders) {
+ var index = -1;
+ iteratees = arrayMap(iteratees.length ? iteratees : Array(1), getIteratee());
+
+ var result = baseMap(collection, function(value, key, collection) {
+ var criteria = arrayMap(iteratees, function(iteratee) {
+ return iteratee(value);
+ });
+ return { 'criteria': criteria, 'index': ++index, 'value': value };
+ });
+
+ return baseSortBy(result, function(object, other) {
+ return compareMultiple(object, other, orders);
+ });
+ }
+
+ /**
+ * The base implementation of `_.pick` without support for individual
+ * property names.
+ *
+ * @private
+ * @param {Object} object The source object.
+ * @param {string[]} props The property names to pick.
+ * @returns {Object} Returns the new object.
+ */
+ function basePick(object, props) {
+ object = Object(object);
+ return arrayReduce(props, function(result, key) {
+ if (key in object) {
+ result[key] = object[key];
+ }
+ return result;
+ }, {});
+ }
+
+ /**
+ * The base implementation of `_.pickBy` without support for iteratee shorthands.
+ *
+ * @private
+ * @param {Object} object The source object.
+ * @param {Function} predicate The function invoked per property.
+ * @returns {Object} Returns the new object.
+ */
+ function basePickBy(object, predicate) {
+ var result = {};
+ baseForIn(object, function(value, key) {
+ if (predicate(value, key)) {
+ result[key] = value;
+ }
+ });
+ return result;
+ }
+
+ /**
+ * The base implementation of `_.property` without support for deep paths.
+ *
+ * @private
+ * @param {string} key The key of the property to get.
+ * @returns {Function} Returns the new function.
+ */
+ function baseProperty(key) {
+ return function(object) {
+ return object == null ? undefined : object[key];
+ };
+ }
+
+ /**
+ * A specialized version of `baseProperty` which supports deep paths.
+ *
+ * @private
+ * @param {Array|string} path The path of the property to get.
+ * @returns {Function} Returns the new function.
+ */
+ function basePropertyDeep(path) {
+ return function(object) {
+ return baseGet(object, path);
+ };
+ }
+
+ /**
+ * The base implementation of `_.pullAllBy` without support for iteratee
+ * shorthands.
+ *
+ * @private
+ * @param {Array} array The array to modify.
+ * @param {Array} values The values to remove.
+ * @param {Function} [iteratee] The iteratee invoked per element.
+ * @param {Function} [comparator] The comparator invoked per element.
+ * @returns {Array} Returns `array`.
+ */
+ function basePullAll(array, values, iteratee, comparator) {
+ var indexOf = comparator ? baseIndexOfWith : baseIndexOf,
+ index = -1,
+ length = values.length,
+ seen = array;
+
+ if (iteratee) {
+ seen = arrayMap(array, baseUnary(iteratee));
+ }
+ while (++index < length) {
+ var fromIndex = 0,
+ value = values[index],
+ computed = iteratee ? iteratee(value) : value;
+
+ while ((fromIndex = indexOf(seen, computed, fromIndex, comparator)) > -1) {
+ if (seen !== array) {
+ splice.call(seen, fromIndex, 1);
+ }
+ splice.call(array, fromIndex, 1);
+ }
+ }
+ return array;
+ }
+
+ /**
+ * The base implementation of `_.pullAt` without support for individual
+ * indexes or capturing the removed elements.
+ *
+ * @private
+ * @param {Array} array The array to modify.
+ * @param {number[]} indexes The indexes of elements to remove.
+ * @returns {Array} Returns `array`.
+ */
+ function basePullAt(array, indexes) {
+ var length = array ? indexes.length : 0,
+ lastIndex = length - 1;
+
+ while (length--) {
+ var index = indexes[length];
+ if (lastIndex == length || index != previous) {
+ var previous = index;
+ if (isIndex(index)) {
+ splice.call(array, index, 1);
+ }
+ else if (!isKey(index, array)) {
+ var path = baseCastPath(index),
+ object = parent(array, path);
+
+ if (object != null) {
+ delete object[last(path)];
+ }
+ }
+ else {
+ delete array[index];
+ }
+ }
+ }
+ return array;
+ }
+
+ /**
+ * The base implementation of `_.random` without support for returning
+ * floating-point numbers.
+ *
+ * @private
+ * @param {number} lower The lower bound.
+ * @param {number} upper The upper bound.
+ * @returns {number} Returns the random number.
+ */
+ function baseRandom(lower, upper) {
+ return lower + nativeFloor(nativeRandom() * (upper - lower + 1));
+ }
+
+ /**
+ * The base implementation of `_.range` and `_.rangeRight` which doesn't
+ * coerce arguments to numbers.
+ *
+ * @private
+ * @param {number} start The start of the range.
+ * @param {number} end The end of the range.
+ * @param {number} step The value to increment or decrement by.
+ * @param {boolean} [fromRight] Specify iterating from right to left.
+ * @returns {Array} Returns the new array of numbers.
+ */
+ function baseRange(start, end, step, fromRight) {
+ var index = -1,
+ length = nativeMax(nativeCeil((end - start) / (step || 1)), 0),
+ result = Array(length);
+
+ while (length--) {
+ result[fromRight ? length : ++index] = start;
+ start += step;
+ }
+ return result;
+ }
+
+ /**
+ * The base implementation of `_.set`.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @param {Array|string} path The path of the property to set.
+ * @param {*} value The value to set.
+ * @param {Function} [customizer] The function to customize path creation.
+ * @returns {Object} Returns `object`.
+ */
+ function baseSet(object, path, value, customizer) {
+ path = isKey(path, object) ? [path + ''] : baseCastPath(path);
+
+ var index = -1,
+ length = path.length,
+ lastIndex = length - 1,
+ nested = object;
+
+ while (nested != null && ++index < length) {
+ var key = path[index];
+ if (isObject(nested)) {
+ var newValue = value;
+ if (index != lastIndex) {
+ var objValue = nested[key];
+ newValue = customizer ? customizer(objValue, key, nested) : undefined;
+ if (newValue === undefined) {
+ newValue = objValue == null
+ ? (isIndex(path[index + 1]) ? [] : {})
+ : objValue;
+ }
+ }
+ assignValue(nested, key, newValue);
+ }
+ nested = nested[key];
+ }
+ return object;
+ }
+
+ /**
+ * The base implementation of `setData` without support for hot loop detection.
+ *
+ * @private
+ * @param {Function} func The function to associate metadata with.
+ * @param {*} data The metadata.
+ * @returns {Function} Returns `func`.
+ */
+ var baseSetData = !metaMap ? identity : function(func, data) {
+ metaMap.set(func, data);
+ return func;
+ };
+
+ /**
+ * The base implementation of `_.slice` without an iteratee call guard.
+ *
+ * @private
+ * @param {Array} array The array to slice.
+ * @param {number} [start=0] The start position.
+ * @param {number} [end=array.length] The end position.
+ * @returns {Array} Returns the slice of `array`.
+ */
+ function baseSlice(array, start, end) {
+ var index = -1,
+ length = array.length;
+
+ if (start < 0) {
+ start = -start > length ? 0 : (length + start);
+ }
+ end = end > length ? length : end;
+ if (end < 0) {
+ end += length;
+ }
+ length = start > end ? 0 : ((end - start) >>> 0);
+ start >>>= 0;
+
+ var result = Array(length);
+ while (++index < length) {
+ result[index] = array[index + start];
+ }
+ return result;
+ }
+
+ /**
+ * The base implementation of `_.some` without support for iteratee shorthands.
+ *
+ * @private
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} predicate The function invoked per iteration.
+ * @returns {boolean} Returns `true` if any element passes the predicate check, else `false`.
+ */
+ function baseSome(collection, predicate) {
+ var result;
+
+ baseEach(collection, function(value, index, collection) {
+ result = predicate(value, index, collection);
+ return !result;
+ });
+ return !!result;
+ }
+
+ /**
+ * The base implementation of `_.sortedIndex` and `_.sortedLastIndex` which
+ * performs a binary search of `array` to determine the index at which `value`
+ * should be inserted into `array` in order to maintain its sort order.
+ *
+ * @private
+ * @param {Array} array The sorted array to inspect.
+ * @param {*} value The value to evaluate.
+ * @param {boolean} [retHighest] Specify returning the highest qualified index.
+ * @returns {number} Returns the index at which `value` should be inserted
+ * into `array`.
+ */
+ function baseSortedIndex(array, value, retHighest) {
+ var low = 0,
+ high = array ? array.length : low;
+
+ if (typeof value == 'number' && value === value && high <= HALF_MAX_ARRAY_LENGTH) {
+ while (low < high) {
+ var mid = (low + high) >>> 1,
+ computed = array[mid];
+
+ if ((retHighest ? (computed <= value) : (computed < value)) && computed !== null) {
+ low = mid + 1;
+ } else {
+ high = mid;
+ }
+ }
+ return high;
+ }
+ return baseSortedIndexBy(array, value, identity, retHighest);
+ }
+
+ /**
+ * The base implementation of `_.sortedIndexBy` and `_.sortedLastIndexBy`
+ * which invokes `iteratee` for `value` and each element of `array` to compute
+ * their sort ranking. The iteratee is invoked with one argument; (value).
+ *
+ * @private
+ * @param {Array} array The sorted array to inspect.
+ * @param {*} value The value to evaluate.
+ * @param {Function} iteratee The iteratee invoked per element.
+ * @param {boolean} [retHighest] Specify returning the highest qualified index.
+ * @returns {number} Returns the index at which `value` should be inserted into `array`.
+ */
+ function baseSortedIndexBy(array, value, iteratee, retHighest) {
+ value = iteratee(value);
+
+ var low = 0,
+ high = array ? array.length : 0,
+ valIsNaN = value !== value,
+ valIsNull = value === null,
+ valIsUndef = value === undefined;
+
+ while (low < high) {
+ var mid = nativeFloor((low + high) / 2),
+ computed = iteratee(array[mid]),
+ isDef = computed !== undefined,
+ isReflexive = computed === computed;
+
+ if (valIsNaN) {
+ var setLow = isReflexive || retHighest;
+ } else if (valIsNull) {
+ setLow = isReflexive && isDef && (retHighest || computed != null);
+ } else if (valIsUndef) {
+ setLow = isReflexive && (retHighest || isDef);
+ } else if (computed == null) {
+ setLow = false;
+ } else {
+ setLow = retHighest ? (computed <= value) : (computed < value);
+ }
+ if (setLow) {
+ low = mid + 1;
+ } else {
+ high = mid;
+ }
+ }
+ return nativeMin(high, MAX_ARRAY_INDEX);
+ }
+
+ /**
+ * The base implementation of `_.sortedUniq`.
+ *
+ * @private
+ * @param {Array} array The array to inspect.
+ * @returns {Array} Returns the new duplicate free array.
+ */
+ function baseSortedUniq(array) {
+ return baseSortedUniqBy(array);
+ }
+
+ /**
+ * The base implementation of `_.sortedUniqBy` without support for iteratee
+ * shorthands.
+ *
+ * @private
+ * @param {Array} array The array to inspect.
+ * @param {Function} [iteratee] The iteratee invoked per element.
+ * @returns {Array} Returns the new duplicate free array.
+ */
+ function baseSortedUniqBy(array, iteratee) {
+ var index = 0,
+ length = array.length,
+ value = array[0],
+ computed = iteratee ? iteratee(value) : value,
+ seen = computed,
+ resIndex = 1,
+ result = [value];
+
+ while (++index < length) {
+ value = array[index],
+ computed = iteratee ? iteratee(value) : value;
+
+ if (!eq(computed, seen)) {
+ seen = computed;
+ result[resIndex++] = value;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * The base implementation of `_.uniqBy` without support for iteratee shorthands.
+ *
+ * @private
+ * @param {Array} array The array to inspect.
+ * @param {Function} [iteratee] The iteratee invoked per element.
+ * @param {Function} [comparator] The comparator invoked per element.
+ * @returns {Array} Returns the new duplicate free array.
+ */
+ function baseUniq(array, iteratee, comparator) {
+ var index = -1,
+ includes = arrayIncludes,
+ length = array.length,
+ isCommon = true,
+ result = [],
+ seen = result;
+
+ if (comparator) {
+ isCommon = false;
+ includes = arrayIncludesWith;
+ }
+ else if (length >= LARGE_ARRAY_SIZE) {
+ var set = iteratee ? null : createSet(array);
+ if (set) {
+ return setToArray(set);
+ }
+ isCommon = false;
+ includes = cacheHas;
+ seen = new SetCache;
+ }
+ else {
+ seen = iteratee ? [] : result;
+ }
+ outer:
+ while (++index < length) {
+ var value = array[index],
+ computed = iteratee ? iteratee(value) : value;
+
+ if (isCommon && computed === computed) {
+ var seenIndex = seen.length;
+ while (seenIndex--) {
+ if (seen[seenIndex] === computed) {
+ continue outer;
+ }
+ }
+ if (iteratee) {
+ seen.push(computed);
+ }
+ result.push(value);
+ }
+ else if (!includes(seen, computed, comparator)) {
+ if (seen !== result) {
+ seen.push(computed);
+ }
+ result.push(value);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * The base implementation of `_.unset`.
+ *
+ * @private
+ * @param {Object} object The object to modify.
+ * @param {Array|string} path The path of the property to unset.
+ * @returns {boolean} Returns `true` if the property is deleted, else `false`.
+ */
+ function baseUnset(object, path) {
+ path = isKey(path, object) ? [path + ''] : baseCastPath(path);
+ object = parent(object, path);
+ var key = last(path);
+ return (object != null && has(object, key)) ? delete object[key] : true;
+ }
+
+ /**
+ * The base implementation of `_.update`.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @param {Array|string} path The path of the property to update.
+ * @param {Function} updater The function to produce the updated value.
+ * @param {Function} [customizer] The function to customize path creation.
+ * @returns {Object} Returns `object`.
+ */
+ function baseUpdate(object, path, updater, customizer) {
+ return baseSet(object, path, updater(baseGet(object, path)), customizer);
+ }
+
+ /**
+ * The base implementation of methods like `_.dropWhile` and `_.takeWhile`
+ * without support for iteratee shorthands.
+ *
+ * @private
+ * @param {Array} array The array to query.
+ * @param {Function} predicate The function invoked per iteration.
+ * @param {boolean} [isDrop] Specify dropping elements instead of taking them.
+ * @param {boolean} [fromRight] Specify iterating from right to left.
+ * @returns {Array} Returns the slice of `array`.
+ */
+ function baseWhile(array, predicate, isDrop, fromRight) {
+ var length = array.length,
+ index = fromRight ? length : -1;
+
+ while ((fromRight ? index-- : ++index < length) &&
+ predicate(array[index], index, array)) {}
+
+ return isDrop
+ ? baseSlice(array, (fromRight ? 0 : index), (fromRight ? index + 1 : length))
+ : baseSlice(array, (fromRight ? index + 1 : 0), (fromRight ? length : index));
+ }
+
+ /**
+ * The base implementation of `wrapperValue` which returns the result of
+ * performing a sequence of actions on the unwrapped `value`, where each
+ * successive action is supplied the return value of the previous.
+ *
+ * @private
+ * @param {*} value The unwrapped value.
+ * @param {Array} actions Actions to perform to resolve the unwrapped value.
+ * @returns {*} Returns the resolved value.
+ */
+ function baseWrapperValue(value, actions) {
+ var result = value;
+ if (result instanceof LazyWrapper) {
+ result = result.value();
+ }
+ return arrayReduce(actions, function(result, action) {
+ return action.func.apply(action.thisArg, arrayPush([result], action.args));
+ }, result);
+ }
+
+ /**
+ * The base implementation of methods like `_.xor`, without support for
+ * iteratee shorthands, that accepts an array of arrays to inspect.
+ *
+ * @private
+ * @param {Array} arrays The arrays to inspect.
+ * @param {Function} [iteratee] The iteratee invoked per element.
+ * @param {Function} [comparator] The comparator invoked per element.
+ * @returns {Array} Returns the new array of values.
+ */
+ function baseXor(arrays, iteratee, comparator) {
+ var index = -1,
+ length = arrays.length;
+
+ while (++index < length) {
+ var result = result
+ ? arrayPush(
+ baseDifference(result, arrays[index], iteratee, comparator),
+ baseDifference(arrays[index], result, iteratee, comparator)
+ )
+ : arrays[index];
+ }
+ return (result && result.length) ? baseUniq(result, iteratee, comparator) : [];
+ }
+
+ /**
+ * This base implementation of `_.zipObject` which assigns values using `assignFunc`.
+ *
+ * @private
+ * @param {Array} props The property names.
+ * @param {Array} values The property values.
+ * @param {Function} assignFunc The function to assign values.
+ * @returns {Object} Returns the new object.
+ */
+ function baseZipObject(props, values, assignFunc) {
+ var index = -1,
+ length = props.length,
+ valsLength = values.length,
+ result = {};
+
+ while (++index < length) {
+ assignFunc(result, props[index], index < valsLength ? values[index] : undefined);
+ }
+ return result;
+ }
+
+ /**
+ * Creates a clone of `buffer`.
+ *
+ * @private
+ * @param {Buffer} buffer The buffer to clone.
+ * @param {boolean} [isDeep] Specify a deep clone.
+ * @returns {Buffer} Returns the cloned buffer.
+ */
+ function cloneBuffer(buffer, isDeep) {
+ if (isDeep) {
+ return buffer.slice();
+ }
+ var result = new buffer.constructor(buffer.length);
+ buffer.copy(result);
+ return result;
+ }
+
+ /**
+ * Creates a clone of `arrayBuffer`.
+ *
+ * @private
+ * @param {ArrayBuffer} arrayBuffer The array buffer to clone.
+ * @returns {ArrayBuffer} Returns the cloned array buffer.
+ */
+ function cloneArrayBuffer(arrayBuffer) {
+ var result = new arrayBuffer.constructor(arrayBuffer.byteLength);
+ new Uint8Array(result).set(new Uint8Array(arrayBuffer));
+ return result;
+ }
+
+ /**
+ * Creates a clone of `map`.
+ *
+ * @private
+ * @param {Object} map The map to clone.
+ * @returns {Object} Returns the cloned map.
+ */
+ function cloneMap(map) {
+ return arrayReduce(mapToArray(map), addMapEntry, new map.constructor);
+ }
+
+ /**
+ * Creates a clone of `regexp`.
+ *
+ * @private
+ * @param {Object} regexp The regexp to clone.
+ * @returns {Object} Returns the cloned regexp.
+ */
+ function cloneRegExp(regexp) {
+ var result = new regexp.constructor(regexp.source, reFlags.exec(regexp));
+ result.lastIndex = regexp.lastIndex;
+ return result;
+ }
+
+ /**
+ * Creates a clone of `set`.
+ *
+ * @private
+ * @param {Object} set The set to clone.
+ * @returns {Object} Returns the cloned set.
+ */
+ function cloneSet(set) {
+ return arrayReduce(setToArray(set), addSetEntry, new set.constructor);
+ }
+
+ /**
+ * Creates a clone of the `symbol` object.
+ *
+ * @private
+ * @param {Object} symbol The symbol object to clone.
+ * @returns {Object} Returns the cloned symbol object.
+ */
+ function cloneSymbol(symbol) {
+ return symbolValueOf ? Object(symbolValueOf.call(symbol)) : {};
+ }
+
+ /**
+ * Creates a clone of `typedArray`.
+ *
+ * @private
+ * @param {Object} typedArray The typed array to clone.
+ * @param {boolean} [isDeep] Specify a deep clone.
+ * @returns {Object} Returns the cloned typed array.
+ */
+ function cloneTypedArray(typedArray, isDeep) {
+ var buffer = isDeep ? cloneArrayBuffer(typedArray.buffer) : typedArray.buffer;
+ return new typedArray.constructor(buffer, typedArray.byteOffset, typedArray.length);
+ }
+
+ /**
+ * Creates an array that is the composition of partially applied arguments,
+ * placeholders, and provided arguments into a single array of arguments.
+ *
+ * @private
+ * @param {Array|Object} args The provided arguments.
+ * @param {Array} partials The arguments to prepend to those provided.
+ * @param {Array} holders The `partials` placeholder indexes.
+ * @params {boolean} [isCurried] Specify composing for a curried function.
+ * @returns {Array} Returns the new array of composed arguments.
+ */
+ function composeArgs(args, partials, holders, isCurried) {
+ var argsIndex = -1,
+ argsLength = args.length,
+ holdersLength = holders.length,
+ leftIndex = -1,
+ leftLength = partials.length,
+ rangeLength = nativeMax(argsLength - holdersLength, 0),
+ result = Array(leftLength + rangeLength),
+ isUncurried = !isCurried;
+
+ while (++leftIndex < leftLength) {
+ result[leftIndex] = partials[leftIndex];
+ }
+ while (++argsIndex < holdersLength) {
+ if (isUncurried || argsIndex < argsLength) {
+ result[holders[argsIndex]] = args[argsIndex];
+ }
+ }
+ while (rangeLength--) {
+ result[leftIndex++] = args[argsIndex++];
+ }
+ return result;
+ }
+
+ /**
+ * This function is like `composeArgs` except that the arguments composition
+ * is tailored for `_.partialRight`.
+ *
+ * @private
+ * @param {Array|Object} args The provided arguments.
+ * @param {Array} partials The arguments to append to those provided.
+ * @param {Array} holders The `partials` placeholder indexes.
+ * @params {boolean} [isCurried] Specify composing for a curried function.
+ * @returns {Array} Returns the new array of composed arguments.
+ */
+ function composeArgsRight(args, partials, holders, isCurried) {
+ var argsIndex = -1,
+ argsLength = args.length,
+ holdersIndex = -1,
+ holdersLength = holders.length,
+ rightIndex = -1,
+ rightLength = partials.length,
+ rangeLength = nativeMax(argsLength - holdersLength, 0),
+ result = Array(rangeLength + rightLength),
+ isUncurried = !isCurried;
+
+ while (++argsIndex < rangeLength) {
+ result[argsIndex] = args[argsIndex];
+ }
+ var offset = argsIndex;
+ while (++rightIndex < rightLength) {
+ result[offset + rightIndex] = partials[rightIndex];
+ }
+ while (++holdersIndex < holdersLength) {
+ if (isUncurried || argsIndex < argsLength) {
+ result[offset + holders[holdersIndex]] = args[argsIndex++];
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Copies the values of `source` to `array`.
+ *
+ * @private
+ * @param {Array} source The array to copy values from.
+ * @param {Array} [array=[]] The array to copy values to.
+ * @returns {Array} Returns `array`.
+ */
+ function copyArray(source, array) {
+ var index = -1,
+ length = source.length;
+
+ array || (array = Array(length));
+ while (++index < length) {
+ array[index] = source[index];
+ }
+ return array;
+ }
+
+ /**
+ * Copies properties of `source` to `object`.
+ *
+ * @private
+ * @param {Object} source The object to copy properties from.
+ * @param {Array} props The property names to copy.
+ * @param {Object} [object={}] The object to copy properties to.
+ * @returns {Object} Returns `object`.
+ */
+ function copyObject(source, props, object) {
+ return copyObjectWith(source, props, object);
+ }
+
+ /**
+ * This function is like `copyObject` except that it accepts a function to
+ * customize copied values.
+ *
+ * @private
+ * @param {Object} source The object to copy properties from.
+ * @param {Array} props The property names to copy.
+ * @param {Object} [object={}] The object to copy properties to.
+ * @param {Function} [customizer] The function to customize copied values.
+ * @returns {Object} Returns `object`.
+ */
+ function copyObjectWith(source, props, object, customizer) {
+ object || (object = {});
+
+ var index = -1,
+ length = props.length;
+
+ while (++index < length) {
+ var key = props[index];
+
+ var newValue = customizer
+ ? customizer(object[key], source[key], key, object, source)
+ : source[key];
+
+ assignValue(object, key, newValue);
+ }
+ return object;
+ }
+
+ /**
+ * Copies own symbol properties of `source` to `object`.
+ *
+ * @private
+ * @param {Object} source The object to copy symbols from.
+ * @param {Object} [object={}] The object to copy symbols to.
+ * @returns {Object} Returns `object`.
+ */
+ function copySymbols(source, object) {
+ return copyObject(source, getSymbols(source), object);
+ }
+
+ /**
+ * Creates a function like `_.groupBy`.
+ *
+ * @private
+ * @param {Function} setter The function to set accumulator values.
+ * @param {Function} [initializer] The accumulator object initializer.
+ * @returns {Function} Returns the new aggregator function.
+ */
+ function createAggregator(setter, initializer) {
+ return function(collection, iteratee) {
+ var func = isArray(collection) ? arrayAggregator : baseAggregator,
+ accumulator = initializer ? initializer() : {};
+
+ return func(collection, setter, getIteratee(iteratee), accumulator);
+ };
+ }
+
+ /**
+ * Creates a function like `_.assign`.
+ *
+ * @private
+ * @param {Function} assigner The function to assign values.
+ * @returns {Function} Returns the new assigner function.
+ */
+ function createAssigner(assigner) {
+ return rest(function(object, sources) {
+ var index = -1,
+ length = sources.length,
+ customizer = length > 1 ? sources[length - 1] : undefined,
+ guard = length > 2 ? sources[2] : undefined;
+
+ customizer = typeof customizer == 'function'
+ ? (length--, customizer)
+ : undefined;
+
+ if (guard && isIterateeCall(sources[0], sources[1], guard)) {
+ customizer = length < 3 ? undefined : customizer;
+ length = 1;
+ }
+ object = Object(object);
+ while (++index < length) {
+ var source = sources[index];
+ if (source) {
+ assigner(object, source, index, customizer);
+ }
+ }
+ return object;
+ });
+ }
+
+ /**
+ * Creates a `baseEach` or `baseEachRight` function.
+ *
+ * @private
+ * @param {Function} eachFunc The function to iterate over a collection.
+ * @param {boolean} [fromRight] Specify iterating from right to left.
+ * @returns {Function} Returns the new base function.
+ */
+ function createBaseEach(eachFunc, fromRight) {
+ return function(collection, iteratee) {
+ if (collection == null) {
+ return collection;
+ }
+ if (!isArrayLike(collection)) {
+ return eachFunc(collection, iteratee);
+ }
+ var length = collection.length,
+ index = fromRight ? length : -1,
+ iterable = Object(collection);
+
+ while ((fromRight ? index-- : ++index < length)) {
+ if (iteratee(iterable[index], index, iterable) === false) {
+ break;
+ }
+ }
+ return collection;
+ };
+ }
+
+ /**
+ * Creates a base function for methods like `_.forIn`.
+ *
+ * @private
+ * @param {boolean} [fromRight] Specify iterating from right to left.
+ * @returns {Function} Returns the new base function.
+ */
+ function createBaseFor(fromRight) {
+ return function(object, iteratee, keysFunc) {
+ var index = -1,
+ iterable = Object(object),
+ props = keysFunc(object),
+ length = props.length;
+
+ while (length--) {
+ var key = props[fromRight ? length : ++index];
+ if (iteratee(iterable[key], key, iterable) === false) {
+ break;
+ }
+ }
+ return object;
+ };
+ }
+
+ /**
+ * Creates a function that wraps `func` to invoke it with the optional `this`
+ * binding of `thisArg`.
+ *
+ * @private
+ * @param {Function} func The function to wrap.
+ * @param {number} bitmask The bitmask of wrapper flags. See `createWrapper` for more details.
+ * @param {*} [thisArg] The `this` binding of `func`.
+ * @returns {Function} Returns the new wrapped function.
+ */
+ function createBaseWrapper(func, bitmask, thisArg) {
+ var isBind = bitmask & BIND_FLAG,
+ Ctor = createCtorWrapper(func);
+
+ function wrapper() {
+ var fn = (this && this !== root && this instanceof wrapper) ? Ctor : func;
+ return fn.apply(isBind ? thisArg : this, arguments);
+ }
+ return wrapper;
+ }
+
+ /**
+ * Creates a function like `_.lowerFirst`.
+ *
+ * @private
+ * @param {string} methodName The name of the `String` case method to use.
+ * @returns {Function} Returns the new function.
+ */
+ function createCaseFirst(methodName) {
+ return function(string) {
+ string = toString(string);
+
+ var strSymbols = reHasComplexSymbol.test(string)
+ ? stringToArray(string)
+ : undefined;
+
+ var chr = strSymbols ? strSymbols[0] : string.charAt(0),
+ trailing = strSymbols ? strSymbols.slice(1).join('') : string.slice(1);
+
+ return chr[methodName]() + trailing;
+ };
+ }
+
+ /**
+ * Creates a function like `_.camelCase`.
+ *
+ * @private
+ * @param {Function} callback The function to combine each word.
+ * @returns {Function} Returns the new compounder function.
+ */
+ function createCompounder(callback) {
+ return function(string) {
+ return arrayReduce(words(deburr(string)), callback, '');
+ };
+ }
+
+ /**
+ * Creates a function that produces an instance of `Ctor` regardless of
+ * whether it was invoked as part of a `new` expression or by `call` or `apply`.
+ *
+ * @private
+ * @param {Function} Ctor The constructor to wrap.
+ * @returns {Function} Returns the new wrapped function.
+ */
+ function createCtorWrapper(Ctor) {
+ return function() {
+ // Use a `switch` statement to work with class constructors.
+ // See http://ecma-international.org/ecma-262/6.0/#sec-ecmascript-function-objects-call-thisargument-argumentslist
+ // for more details.
+ var args = arguments;
+ switch (args.length) {
+ case 0: return new Ctor;
+ case 1: return new Ctor(args[0]);
+ case 2: return new Ctor(args[0], args[1]);
+ case 3: return new Ctor(args[0], args[1], args[2]);
+ case 4: return new Ctor(args[0], args[1], args[2], args[3]);
+ case 5: return new Ctor(args[0], args[1], args[2], args[3], args[4]);
+ case 6: return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5]);
+ case 7: return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
+ }
+ var thisBinding = baseCreate(Ctor.prototype),
+ result = Ctor.apply(thisBinding, args);
+
+ // Mimic the constructor's `return` behavior.
+ // See https://es5.github.io/#x13.2.2 for more details.
+ return isObject(result) ? result : thisBinding;
+ };
+ }
+
+ /**
+ * Creates a function that wraps `func` to enable currying.
+ *
+ * @private
+ * @param {Function} func The function to wrap.
+ * @param {number} bitmask The bitmask of wrapper flags. See `createWrapper` for more details.
+ * @param {number} arity The arity of `func`.
+ * @returns {Function} Returns the new wrapped function.
+ */
+ function createCurryWrapper(func, bitmask, arity) {
+ var Ctor = createCtorWrapper(func);
+
+ function wrapper() {
+ var length = arguments.length,
+ args = Array(length),
+ index = length,
+ placeholder = getPlaceholder(wrapper);
+
+ while (index--) {
+ args[index] = arguments[index];
+ }
+ var holders = (length < 3 && args[0] !== placeholder && args[length - 1] !== placeholder)
+ ? []
+ : replaceHolders(args, placeholder);
+
+ length -= holders.length;
+ if (length < arity) {
+ return createRecurryWrapper(
+ func, bitmask, createHybridWrapper, wrapper.placeholder, undefined,
+ args, holders, undefined, undefined, arity - length);
+ }
+ var fn = (this && this !== root && this instanceof wrapper) ? Ctor : func;
+ return apply(fn, this, args);
+ }
+ return wrapper;
+ }
+
+ /**
+ * Creates a `_.flow` or `_.flowRight` function.
+ *
+ * @private
+ * @param {boolean} [fromRight] Specify iterating from right to left.
+ * @returns {Function} Returns the new flow function.
+ */
+ function createFlow(fromRight) {
+ return rest(function(funcs) {
+ funcs = baseFlatten(funcs, 1);
+
+ var length = funcs.length,
+ index = length,
+ prereq = LodashWrapper.prototype.thru;
+
+ if (fromRight) {
+ funcs.reverse();
+ }
+ while (index--) {
+ var func = funcs[index];
+ if (typeof func != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
+ if (prereq && !wrapper && getFuncName(func) == 'wrapper') {
+ var wrapper = new LodashWrapper([], true);
+ }
+ }
+ index = wrapper ? index : length;
+ while (++index < length) {
+ func = funcs[index];
+
+ var funcName = getFuncName(func),
+ data = funcName == 'wrapper' ? getData(func) : undefined;
+
+ if (data && isLaziable(data[0]) &&
+ data[1] == (ARY_FLAG | CURRY_FLAG | PARTIAL_FLAG | REARG_FLAG) &&
+ !data[4].length && data[9] == 1
+ ) {
+ wrapper = wrapper[getFuncName(data[0])].apply(wrapper, data[3]);
+ } else {
+ wrapper = (func.length == 1 && isLaziable(func)) ? wrapper[funcName]() : wrapper.thru(func);
+ }
+ }
+ return function() {
+ var args = arguments,
+ value = args[0];
+
+ if (wrapper && args.length == 1 &&
+ isArray(value) && value.length >= LARGE_ARRAY_SIZE) {
+ return wrapper.plant(value).value();
+ }
+ var index = 0,
+ result = length ? funcs[index].apply(this, args) : value;
+
+ while (++index < length) {
+ result = funcs[index].call(this, result);
+ }
+ return result;
+ };
+ });
+ }
+
+ /**
+ * Creates a function that wraps `func` to invoke it with optional `this`
+ * binding of `thisArg`, partial application, and currying.
+ *
+ * @private
+ * @param {Function|string} func The function or method name to wrap.
+ * @param {number} bitmask The bitmask of wrapper flags. See `createWrapper` for more details.
+ * @param {*} [thisArg] The `this` binding of `func`.
+ * @param {Array} [partials] The arguments to prepend to those provided to the new function.
+ * @param {Array} [holders] The `partials` placeholder indexes.
+ * @param {Array} [partialsRight] The arguments to append to those provided to the new function.
+ * @param {Array} [holdersRight] The `partialsRight` placeholder indexes.
+ * @param {Array} [argPos] The argument positions of the new function.
+ * @param {number} [ary] The arity cap of `func`.
+ * @param {number} [arity] The arity of `func`.
+ * @returns {Function} Returns the new wrapped function.
+ */
+ function createHybridWrapper(func, bitmask, thisArg, partials, holders, partialsRight, holdersRight, argPos, ary, arity) {
+ var isAry = bitmask & ARY_FLAG,
+ isBind = bitmask & BIND_FLAG,
+ isBindKey = bitmask & BIND_KEY_FLAG,
+ isCurried = bitmask & (CURRY_FLAG | CURRY_RIGHT_FLAG),
+ isFlip = bitmask & FLIP_FLAG,
+ Ctor = isBindKey ? undefined : createCtorWrapper(func);
+
+ function wrapper() {
+ var length = arguments.length,
+ index = length,
+ args = Array(length);
+
+ while (index--) {
+ args[index] = arguments[index];
+ }
+ if (isCurried) {
+ var placeholder = getPlaceholder(wrapper),
+ holdersCount = countHolders(args, placeholder);
+ }
+ if (partials) {
+ args = composeArgs(args, partials, holders, isCurried);
+ }
+ if (partialsRight) {
+ args = composeArgsRight(args, partialsRight, holdersRight, isCurried);
+ }
+ length -= holdersCount;
+ if (isCurried && length < arity) {
+ var newHolders = replaceHolders(args, placeholder);
+ return createRecurryWrapper(
+ func, bitmask, createHybridWrapper, wrapper.placeholder, thisArg,
+ args, newHolders, argPos, ary, arity - length
+ );
+ }
+ var thisBinding = isBind ? thisArg : this,
+ fn = isBindKey ? thisBinding[func] : func;
+
+ length = args.length;
+ if (argPos) {
+ args = reorder(args, argPos);
+ } else if (isFlip && length > 1) {
+ args.reverse();
+ }
+ if (isAry && ary < length) {
+ args.length = ary;
+ }
+ if (this && this !== root && this instanceof wrapper) {
+ fn = Ctor || createCtorWrapper(fn);
+ }
+ return fn.apply(thisBinding, args);
+ }
+ return wrapper;
+ }
+
+ /**
+ * Creates a function like `_.invertBy`.
+ *
+ * @private
+ * @param {Function} setter The function to set accumulator values.
+ * @param {Function} toIteratee The function to resolve iteratees.
+ * @returns {Function} Returns the new inverter function.
+ */
+ function createInverter(setter, toIteratee) {
+ return function(object, iteratee) {
+ return baseInverter(object, setter, toIteratee(iteratee), {});
+ };
+ }
+
+ /**
+ * Creates a function like `_.over`.
+ *
+ * @private
+ * @param {Function} arrayFunc The function to iterate over iteratees.
+ * @returns {Function} Returns the new invoker function.
+ */
+ function createOver(arrayFunc) {
+ return rest(function(iteratees) {
+ iteratees = arrayMap(baseFlatten(iteratees, 1), getIteratee());
+ return rest(function(args) {
+ var thisArg = this;
+ return arrayFunc(iteratees, function(iteratee) {
+ return apply(iteratee, thisArg, args);
+ });
+ });
+ });
+ }
+
+ /**
+ * Creates the padding for `string` based on `length`. The `chars` string
+ * is truncated if the number of characters exceeds `length`.
+ *
+ * @private
+ * @param {string} string The string to create padding for.
+ * @param {number} [length=0] The padding length.
+ * @param {string} [chars=' '] The string used as padding.
+ * @returns {string} Returns the padding for `string`.
+ */
+ function createPadding(string, length, chars) {
+ length = toInteger(length);
+
+ var strLength = stringSize(string);
+ if (!length || strLength >= length) {
+ return '';
+ }
+ var padLength = length - strLength;
+ chars = chars === undefined ? ' ' : (chars + '');
+
+ var result = repeat(chars, nativeCeil(padLength / stringSize(chars)));
+ return reHasComplexSymbol.test(chars)
+ ? stringToArray(result).slice(0, padLength).join('')
+ : result.slice(0, padLength);
+ }
+
+ /**
+ * Creates a function that wraps `func` to invoke it with the optional `this`
+ * binding of `thisArg` and the `partials` prepended to those provided to
+ * the wrapper.
+ *
+ * @private
+ * @param {Function} func The function to wrap.
+ * @param {number} bitmask The bitmask of wrapper flags. See `createWrapper` for more details.
+ * @param {*} thisArg The `this` binding of `func`.
+ * @param {Array} partials The arguments to prepend to those provided to the new function.
+ * @returns {Function} Returns the new wrapped function.
+ */
+ function createPartialWrapper(func, bitmask, thisArg, partials) {
+ var isBind = bitmask & BIND_FLAG,
+ Ctor = createCtorWrapper(func);
+
+ function wrapper() {
+ var argsIndex = -1,
+ argsLength = arguments.length,
+ leftIndex = -1,
+ leftLength = partials.length,
+ args = Array(leftLength + argsLength),
+ fn = (this && this !== root && this instanceof wrapper) ? Ctor : func;
+
+ while (++leftIndex < leftLength) {
+ args[leftIndex] = partials[leftIndex];
+ }
+ while (argsLength--) {
+ args[leftIndex++] = arguments[++argsIndex];
+ }
+ return apply(fn, isBind ? thisArg : this, args);
+ }
+ return wrapper;
+ }
+
+ /**
+ * Creates a `_.range` or `_.rangeRight` function.
+ *
+ * @private
+ * @param {boolean} [fromRight] Specify iterating from right to left.
+ * @returns {Function} Returns the new range function.
+ */
+ function createRange(fromRight) {
+ return function(start, end, step) {
+ if (step && typeof step != 'number' && isIterateeCall(start, end, step)) {
+ end = step = undefined;
+ }
+ // Ensure the sign of `-0` is preserved.
+ start = toNumber(start);
+ start = start === start ? start : 0;
+ if (end === undefined) {
+ end = start;
+ start = 0;
+ } else {
+ end = toNumber(end) || 0;
+ }
+ step = step === undefined ? (start < end ? 1 : -1) : (toNumber(step) || 0);
+ return baseRange(start, end, step, fromRight);
+ };
+ }
+
+ /**
+ * Creates a function that wraps `func` to continue currying.
+ *
+ * @private
+ * @param {Function} func The function to wrap.
+ * @param {number} bitmask The bitmask of wrapper flags. See `createWrapper` for more details.
+ * @param {Function} wrapFunc The function to create the `func` wrapper.
+ * @param {*} placeholder The placeholder value.
+ * @param {*} [thisArg] The `this` binding of `func`.
+ * @param {Array} [partials] The arguments to prepend to those provided to the new function.
+ * @param {Array} [holders] The `partials` placeholder indexes.
+ * @param {Array} [argPos] The argument positions of the new function.
+ * @param {number} [ary] The arity cap of `func`.
+ * @param {number} [arity] The arity of `func`.
+ * @returns {Function} Returns the new wrapped function.
+ */
+ function createRecurryWrapper(func, bitmask, wrapFunc, placeholder, thisArg, partials, holders, argPos, ary, arity) {
+ var isCurry = bitmask & CURRY_FLAG,
+ newArgPos = argPos ? copyArray(argPos) : undefined,
+ newHolders = isCurry ? holders : undefined,
+ newHoldersRight = isCurry ? undefined : holders,
+ newPartials = isCurry ? partials : undefined,
+ newPartialsRight = isCurry ? undefined : partials;
+
+ bitmask |= (isCurry ? PARTIAL_FLAG : PARTIAL_RIGHT_FLAG);
+ bitmask &= ~(isCurry ? PARTIAL_RIGHT_FLAG : PARTIAL_FLAG);
+
+ if (!(bitmask & CURRY_BOUND_FLAG)) {
+ bitmask &= ~(BIND_FLAG | BIND_KEY_FLAG);
+ }
+ var newData = [
+ func, bitmask, thisArg, newPartials, newHolders, newPartialsRight,
+ newHoldersRight, newArgPos, ary, arity
+ ];
+
+ var result = wrapFunc.apply(undefined, newData);
+ if (isLaziable(func)) {
+ setData(result, newData);
+ }
+ result.placeholder = placeholder;
+ return result;
+ }
+
+ /**
+ * Creates a function like `_.round`.
+ *
+ * @private
+ * @param {string} methodName The name of the `Math` method to use when rounding.
+ * @returns {Function} Returns the new round function.
+ */
+ function createRound(methodName) {
+ var func = Math[methodName];
+ return function(number, precision) {
+ number = toNumber(number);
+ precision = toInteger(precision);
+ if (precision) {
+ // Shift with exponential notation to avoid floating-point issues.
+ // See [MDN](https://mdn.io/round#Examples) for more details.
+ var pair = (toString(number) + 'e').split('e'),
+ value = func(pair[0] + 'e' + (+pair[1] + precision));
+
+ pair = (toString(value) + 'e').split('e');
+ return +(pair[0] + 'e' + (+pair[1] - precision));
+ }
+ return func(number);
+ };
+ }
+
+ /**
+ * Creates a set of `values`.
+ *
+ * @private
+ * @param {Array} values The values to add to the set.
+ * @returns {Object} Returns the new set.
+ */
+ var createSet = !(Set && new Set([1, 2]).size === 2) ? noop : function(values) {
+ return new Set(values);
+ };
+
+ /**
+ * Creates a function that either curries or invokes `func` with optional
+ * `this` binding and partially applied arguments.
+ *
+ * @private
+ * @param {Function|string} func The function or method name to wrap.
+ * @param {number} bitmask The bitmask of wrapper flags.
+ * The bitmask may be composed of the following flags:
+ * 1 - `_.bind`
+ * 2 - `_.bindKey`
+ * 4 - `_.curry` or `_.curryRight` of a bound function
+ * 8 - `_.curry`
+ * 16 - `_.curryRight`
+ * 32 - `_.partial`
+ * 64 - `_.partialRight`
+ * 128 - `_.rearg`
+ * 256 - `_.ary`
+ * @param {*} [thisArg] The `this` binding of `func`.
+ * @param {Array} [partials] The arguments to be partially applied.
+ * @param {Array} [holders] The `partials` placeholder indexes.
+ * @param {Array} [argPos] The argument positions of the new function.
+ * @param {number} [ary] The arity cap of `func`.
+ * @param {number} [arity] The arity of `func`.
+ * @returns {Function} Returns the new wrapped function.
+ */
+ function createWrapper(func, bitmask, thisArg, partials, holders, argPos, ary, arity) {
+ var isBindKey = bitmask & BIND_KEY_FLAG;
+ if (!isBindKey && typeof func != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
+ var length = partials ? partials.length : 0;
+ if (!length) {
+ bitmask &= ~(PARTIAL_FLAG | PARTIAL_RIGHT_FLAG);
+ partials = holders = undefined;
+ }
+ ary = ary === undefined ? ary : nativeMax(toInteger(ary), 0);
+ arity = arity === undefined ? arity : toInteger(arity);
+ length -= holders ? holders.length : 0;
+
+ if (bitmask & PARTIAL_RIGHT_FLAG) {
+ var partialsRight = partials,
+ holdersRight = holders;
+
+ partials = holders = undefined;
+ }
+ var data = isBindKey ? undefined : getData(func);
+
+ var newData = [
+ func, bitmask, thisArg, partials, holders, partialsRight, holdersRight,
+ argPos, ary, arity
+ ];
+
+ if (data) {
+ mergeData(newData, data);
+ }
+ func = newData[0];
+ bitmask = newData[1];
+ thisArg = newData[2];
+ partials = newData[3];
+ holders = newData[4];
+ arity = newData[9] = newData[9] == null
+ ? (isBindKey ? 0 : func.length)
+ : nativeMax(newData[9] - length, 0);
+
+ if (!arity && bitmask & (CURRY_FLAG | CURRY_RIGHT_FLAG)) {
+ bitmask &= ~(CURRY_FLAG | CURRY_RIGHT_FLAG);
+ }
+ if (!bitmask || bitmask == BIND_FLAG) {
+ var result = createBaseWrapper(func, bitmask, thisArg);
+ } else if (bitmask == CURRY_FLAG || bitmask == CURRY_RIGHT_FLAG) {
+ result = createCurryWrapper(func, bitmask, arity);
+ } else if ((bitmask == PARTIAL_FLAG || bitmask == (BIND_FLAG | PARTIAL_FLAG)) && !holders.length) {
+ result = createPartialWrapper(func, bitmask, thisArg, partials);
+ } else {
+ result = createHybridWrapper.apply(undefined, newData);
+ }
+ var setter = data ? baseSetData : setData;
+ return setter(result, newData);
+ }
+
+ /**
+ * A specialized version of `baseIsEqualDeep` for arrays with support for
+ * partial deep comparisons.
+ *
+ * @private
+ * @param {Array} array The array to compare.
+ * @param {Array} other The other array to compare.
+ * @param {Function} equalFunc The function to determine equivalents of values.
+ * @param {Function} customizer The function to customize comparisons.
+ * @param {number} bitmask The bitmask of comparison flags. See `baseIsEqual` for more details.
+ * @param {Object} stack Tracks traversed `array` and `other` objects.
+ * @returns {boolean} Returns `true` if the arrays are equivalent, else `false`.
+ */
+ function equalArrays(array, other, equalFunc, customizer, bitmask, stack) {
+ var index = -1,
+ isPartial = bitmask & PARTIAL_COMPARE_FLAG,
+ isUnordered = bitmask & UNORDERED_COMPARE_FLAG,
+ arrLength = array.length,
+ othLength = other.length;
+
+ if (arrLength != othLength && !(isPartial && othLength > arrLength)) {
+ return false;
+ }
+ // Assume cyclic values are equal.
+ var stacked = stack.get(array);
+ if (stacked) {
+ return stacked == other;
+ }
+ var result = true;
+ stack.set(array, other);
+
+ // Ignore non-index properties.
+ while (++index < arrLength) {
+ var arrValue = array[index],
+ othValue = other[index];
+
+ if (customizer) {
+ var compared = isPartial
+ ? customizer(othValue, arrValue, index, other, array, stack)
+ : customizer(arrValue, othValue, index, array, other, stack);
+ }
+ if (compared !== undefined) {
+ if (compared) {
+ continue;
+ }
+ result = false;
+ break;
+ }
+ // Recursively compare arrays (susceptible to call stack limits).
+ if (isUnordered) {
+ if (!arraySome(other, function(othValue) {
+ return arrValue === othValue || equalFunc(arrValue, othValue, customizer, bitmask, stack);
+ })) {
+ result = false;
+ break;
+ }
+ } else if (!(arrValue === othValue || equalFunc(arrValue, othValue, customizer, bitmask, stack))) {
+ result = false;
+ break;
+ }
+ }
+ stack['delete'](array);
+ return result;
+ }
+
+ /**
+ * A specialized version of `baseIsEqualDeep` for comparing objects of
+ * the same `toStringTag`.
+ *
+ * **Note:** This function only supports comparing values with tags of
+ * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`.
+ *
+ * @private
+ * @param {Object} object The object to compare.
+ * @param {Object} other The other object to compare.
+ * @param {string} tag The `toStringTag` of the objects to compare.
+ * @param {Function} equalFunc The function to determine equivalents of values.
+ * @param {Function} customizer The function to customize comparisons.
+ * @param {number} bitmask The bitmask of comparison flags. See `baseIsEqual` for more details.
+ * @param {Object} stack Tracks traversed `object` and `other` objects.
+ * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
+ */
+ function equalByTag(object, other, tag, equalFunc, customizer, bitmask, stack) {
+ switch (tag) {
+ case arrayBufferTag:
+ if ((object.byteLength != other.byteLength) ||
+ !equalFunc(new Uint8Array(object), new Uint8Array(other))) {
+ return false;
+ }
+ return true;
+
+ case boolTag:
+ case dateTag:
+ // Coerce dates and booleans to numbers, dates to milliseconds and booleans
+ // to `1` or `0` treating invalid dates coerced to `NaN` as not equal.
+ return +object == +other;
+
+ case errorTag:
+ return object.name == other.name && object.message == other.message;
+
+ case numberTag:
+ // Treat `NaN` vs. `NaN` as equal.
+ return (object != +object) ? other != +other : object == +other;
+
+ case regexpTag:
+ case stringTag:
+ // Coerce regexes to strings and treat strings primitives and string
+ // objects as equal. See https://es5.github.io/#x15.10.6.4 for more details.
+ return object == (other + '');
+
+ case mapTag:
+ var convert = mapToArray;
+
+ case setTag:
+ var isPartial = bitmask & PARTIAL_COMPARE_FLAG;
+ convert || (convert = setToArray);
+
+ if (object.size != other.size && !isPartial) {
+ return false;
+ }
+ // Assume cyclic values are equal.
+ var stacked = stack.get(object);
+ if (stacked) {
+ return stacked == other;
+ }
+ // Recursively compare objects (susceptible to call stack limits).
+ return equalArrays(convert(object), convert(other), equalFunc, customizer, bitmask | UNORDERED_COMPARE_FLAG, stack.set(object, other));
+
+ case symbolTag:
+ if (symbolValueOf) {
+ return symbolValueOf.call(object) == symbolValueOf.call(other);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * A specialized version of `baseIsEqualDeep` for objects with support for
+ * partial deep comparisons.
+ *
+ * @private
+ * @param {Object} object The object to compare.
+ * @param {Object} other The other object to compare.
+ * @param {Function} equalFunc The function to determine equivalents of values.
+ * @param {Function} customizer The function to customize comparisons.
+ * @param {number} bitmask The bitmask of comparison flags. See `baseIsEqual` for more details.
+ * @param {Object} stack Tracks traversed `object` and `other` objects.
+ * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
+ */
+ function equalObjects(object, other, equalFunc, customizer, bitmask, stack) {
+ var isPartial = bitmask & PARTIAL_COMPARE_FLAG,
+ objProps = keys(object),
+ objLength = objProps.length,
+ othProps = keys(other),
+ othLength = othProps.length;
+
+ if (objLength != othLength && !isPartial) {
+ return false;
+ }
+ var index = objLength;
+ while (index--) {
+ var key = objProps[index];
+ if (!(isPartial ? key in other : baseHas(other, key))) {
+ return false;
+ }
+ }
+ // Assume cyclic values are equal.
+ var stacked = stack.get(object);
+ if (stacked) {
+ return stacked == other;
+ }
+ var result = true;
+ stack.set(object, other);
+
+ var skipCtor = isPartial;
+ while (++index < objLength) {
+ key = objProps[index];
+ var objValue = object[key],
+ othValue = other[key];
+
+ if (customizer) {
+ var compared = isPartial
+ ? customizer(othValue, objValue, key, other, object, stack)
+ : customizer(objValue, othValue, key, object, other, stack);
+ }
+ // Recursively compare objects (susceptible to call stack limits).
+ if (!(compared === undefined
+ ? (objValue === othValue || equalFunc(objValue, othValue, customizer, bitmask, stack))
+ : compared
+ )) {
+ result = false;
+ break;
+ }
+ skipCtor || (skipCtor = key == 'constructor');
+ }
+ if (result && !skipCtor) {
+ var objCtor = object.constructor,
+ othCtor = other.constructor;
+
+ // Non `Object` object instances with different constructors are not equal.
+ if (objCtor != othCtor &&
+ ('constructor' in object && 'constructor' in other) &&
+ !(typeof objCtor == 'function' && objCtor instanceof objCtor &&
+ typeof othCtor == 'function' && othCtor instanceof othCtor)) {
+ result = false;
+ }
+ }
+ stack['delete'](object);
+ return result;
+ }
+
+ /**
+ * Gets metadata for `func`.
+ *
+ * @private
+ * @param {Function} func The function to query.
+ * @returns {*} Returns the metadata for `func`.
+ */
+ var getData = !metaMap ? noop : function(func) {
+ return metaMap.get(func);
+ };
+
+ /**
+ * Gets the name of `func`.
+ *
+ * @private
+ * @param {Function} func The function to query.
+ * @returns {string} Returns the function name.
+ */
+ function getFuncName(func) {
+ var result = (func.name + ''),
+ array = realNames[result],
+ length = hasOwnProperty.call(realNames, result) ? array.length : 0;
+
+ while (length--) {
+ var data = array[length],
+ otherFunc = data.func;
+ if (otherFunc == null || otherFunc == func) {
+ return data.name;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Gets the appropriate "iteratee" function. If the `_.iteratee` method is
+ * customized this function returns the custom method, otherwise it returns
+ * `baseIteratee`. If arguments are provided the chosen function is invoked
+ * with them and its result is returned.
+ *
+ * @private
+ * @param {*} [value] The value to convert to an iteratee.
+ * @param {number} [arity] The arity of the created iteratee.
+ * @returns {Function} Returns the chosen function or its result.
+ */
+ function getIteratee() {
+ var result = lodash.iteratee || iteratee;
+ result = result === iteratee ? baseIteratee : result;
+ return arguments.length ? result(arguments[0], arguments[1]) : result;
+ }
+
+ /**
+ * Gets the "length" property value of `object`.
+ *
+ * **Note:** This function is used to avoid a [JIT bug](https://bugs.webkit.org/show_bug.cgi?id=142792)
+ * that affects Safari on at least iOS 8.1-8.3 ARM64.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @returns {*} Returns the "length" value.
+ */
+ var getLength = baseProperty('length');
+
+ /**
+ * Gets the property names, values, and compare flags of `object`.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the match data of `object`.
+ */
+ function getMatchData(object) {
+ var result = toPairs(object),
+ length = result.length;
+
+ while (length--) {
+ result[length][2] = isStrictComparable(result[length][1]);
+ }
+ return result;
+ }
+
+ /**
+ * Gets the native function at `key` of `object`.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @param {string} key The key of the method to get.
+ * @returns {*} Returns the function if it's native, else `undefined`.
+ */
+ function getNative(object, key) {
+ var value = object[key];
+ return isNative(value) ? value : undefined;
+ }
+
+ /**
+ * Gets the argument placeholder value for `func`.
+ *
+ * @private
+ * @param {Function} func The function to inspect.
+ * @returns {*} Returns the placeholder value.
+ */
+ function getPlaceholder(func) {
+ var object = hasOwnProperty.call(lodash, 'placeholder') ? lodash : func;
+ return object.placeholder;
+ }
+
+ /**
+ * Creates an array of the own symbol properties of `object`.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the array of symbols.
+ */
+ var getSymbols = getOwnPropertySymbols || function() {
+ return [];
+ };
+
+ /**
+ * Gets the `toStringTag` of `value`.
+ *
+ * @private
+ * @param {*} value The value to query.
+ * @returns {string} Returns the `toStringTag`.
+ */
+ function getTag(value) {
+ return objectToString.call(value);
+ }
+
+ // Fallback for IE 11 providing `toStringTag` values for maps, sets, and weakmaps.
+ if ((Map && getTag(new Map) != mapTag) ||
+ (Set && getTag(new Set) != setTag) ||
+ (WeakMap && getTag(new WeakMap) != weakMapTag)) {
+ getTag = function(value) {
+ var result = objectToString.call(value),
+ Ctor = result == objectTag ? value.constructor : null,
+ ctorString = typeof Ctor == 'function' ? funcToString.call(Ctor) : '';
+
+ if (ctorString) {
+ switch (ctorString) {
+ case mapCtorString: return mapTag;
+ case setCtorString: return setTag;
+ case weakMapCtorString: return weakMapTag;
+ }
+ }
+ return result;
+ };
+ }
+
+ /**
+ * Gets the view, applying any `transforms` to the `start` and `end` positions.
+ *
+ * @private
+ * @param {number} start The start of the view.
+ * @param {number} end The end of the view.
+ * @param {Array} transforms The transformations to apply to the view.
+ * @returns {Object} Returns an object containing the `start` and `end`
+ * positions of the view.
+ */
+ function getView(start, end, transforms) {
+ var index = -1,
+ length = transforms.length;
+
+ while (++index < length) {
+ var data = transforms[index],
+ size = data.size;
+
+ switch (data.type) {
+ case 'drop': start += size; break;
+ case 'dropRight': end -= size; break;
+ case 'take': end = nativeMin(end, start + size); break;
+ case 'takeRight': start = nativeMax(start, end - size); break;
+ }
+ }
+ return { 'start': start, 'end': end };
+ }
+
+ /**
+ * Checks if `path` exists on `object`.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @param {Array|string} path The path to check.
+ * @param {Function} hasFunc The function to check properties.
+ * @returns {boolean} Returns `true` if `path` exists, else `false`.
+ */
+ function hasPath(object, path, hasFunc) {
+ if (object == null) {
+ return false;
+ }
+ var result = hasFunc(object, path);
+ if (!result && !isKey(path)) {
+ path = baseCastPath(path);
+ object = parent(object, path);
+ if (object != null) {
+ path = last(path);
+ result = hasFunc(object, path);
+ }
+ }
+ var length = object ? object.length : undefined;
+ return result || (
+ !!length && isLength(length) && isIndex(path, length) &&
+ (isArray(object) || isString(object) || isArguments(object))
+ );
+ }
+
+ /**
+ * Initializes an array clone.
+ *
+ * @private
+ * @param {Array} array The array to clone.
+ * @returns {Array} Returns the initialized clone.
+ */
+ function initCloneArray(array) {
+ var length = array.length,
+ result = array.constructor(length);
+
+ // Add properties assigned by `RegExp#exec`.
+ if (length && typeof array[0] == 'string' && hasOwnProperty.call(array, 'index')) {
+ result.index = array.index;
+ result.input = array.input;
+ }
+ return result;
+ }
+
+ /**
+ * Initializes an object clone.
+ *
+ * @private
+ * @param {Object} object The object to clone.
+ * @returns {Object} Returns the initialized clone.
+ */
+ function initCloneObject(object) {
+ return (typeof object.constructor == 'function' && !isPrototype(object))
+ ? baseCreate(getPrototypeOf(object))
+ : {};
+ }
+
+ /**
+ * Initializes an object clone based on its `toStringTag`.
+ *
+ * **Note:** This function only supports cloning values with tags of
+ * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`.
+ *
+ * @private
+ * @param {Object} object The object to clone.
+ * @param {string} tag The `toStringTag` of the object to clone.
+ * @param {boolean} [isDeep] Specify a deep clone.
+ * @returns {Object} Returns the initialized clone.
+ */
+ function initCloneByTag(object, tag, isDeep) {
+ var Ctor = object.constructor;
+ switch (tag) {
+ case arrayBufferTag:
+ return cloneArrayBuffer(object);
+
+ case boolTag:
+ case dateTag:
+ return new Ctor(+object);
+
+ case float32Tag: case float64Tag:
+ case int8Tag: case int16Tag: case int32Tag:
+ case uint8Tag: case uint8ClampedTag: case uint16Tag: case uint32Tag:
+ return cloneTypedArray(object, isDeep);
+
+ case mapTag:
+ return cloneMap(object);
+
+ case numberTag:
+ case stringTag:
+ return new Ctor(object);
+
+ case regexpTag:
+ return cloneRegExp(object);
+
+ case setTag:
+ return cloneSet(object);
+
+ case symbolTag:
+ return cloneSymbol(object);
+ }
+ }
+
+ /**
+ * Creates an array of index keys for `object` values of arrays,
+ * `arguments` objects, and strings, otherwise `null` is returned.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @returns {Array|null} Returns index keys, else `null`.
+ */
+ function indexKeys(object) {
+ var length = object ? object.length : undefined;
+ if (isLength(length) &&
+ (isArray(object) || isString(object) || isArguments(object))) {
+ return baseTimes(length, String);
+ }
+ return null;
+ }
+
+ /**
+ * Checks if the given arguments are from an iteratee call.
+ *
+ * @private
+ * @param {*} value The potential iteratee value argument.
+ * @param {*} index The potential iteratee index or key argument.
+ * @param {*} object The potential iteratee object argument.
+ * @returns {boolean} Returns `true` if the arguments are from an iteratee call, else `false`.
+ */
+ function isIterateeCall(value, index, object) {
+ if (!isObject(object)) {
+ return false;
+ }
+ var type = typeof index;
+ if (type == 'number'
+ ? (isArrayLike(object) && isIndex(index, object.length))
+ : (type == 'string' && index in object)) {
+ return eq(object[index], value);
+ }
+ return false;
+ }
+
+ /**
+ * Checks if `value` is a property name and not a property path.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @param {Object} [object] The object to query keys on.
+ * @returns {boolean} Returns `true` if `value` is a property name, else `false`.
+ */
+ function isKey(value, object) {
+ if (typeof value == 'number') {
+ return true;
+ }
+ return !isArray(value) &&
+ (reIsPlainProp.test(value) || !reIsDeepProp.test(value) ||
+ (object != null && value in Object(object)));
+ }
+
+ /**
+ * Checks if `value` is suitable for use as unique object key.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is suitable, else `false`.
+ */
+ function isKeyable(value) {
+ var type = typeof value;
+ return type == 'number' || type == 'boolean' ||
+ (type == 'string' && value != '__proto__') || value == null;
+ }
+
+ /**
+ * Checks if `func` has a lazy counterpart.
+ *
+ * @private
+ * @param {Function} func The function to check.
+ * @returns {boolean} Returns `true` if `func` has a lazy counterpart, else `false`.
+ */
+ function isLaziable(func) {
+ var funcName = getFuncName(func),
+ other = lodash[funcName];
+
+ if (typeof other != 'function' || !(funcName in LazyWrapper.prototype)) {
+ return false;
+ }
+ if (func === other) {
+ return true;
+ }
+ var data = getData(other);
+ return !!data && func === data[0];
+ }
+
+ /**
+ * Checks if `value` is likely a prototype object.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a prototype, else `false`.
+ */
+ function isPrototype(value) {
+ var Ctor = value && value.constructor,
+ proto = (typeof Ctor == 'function' && Ctor.prototype) || objectProto;
+
+ return value === proto;
+ }
+
+ /**
+ * Checks if `value` is suitable for strict equality comparisons, i.e. `===`.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` if suitable for strict
+ * equality comparisons, else `false`.
+ */
+ function isStrictComparable(value) {
+ return value === value && !isObject(value);
+ }
+
+ /**
+ * Merges the function metadata of `source` into `data`.
+ *
+ * Merging metadata reduces the number of wrappers used to invoke a function.
+ * This is possible because methods like `_.bind`, `_.curry`, and `_.partial`
+ * may be applied regardless of execution order. Methods like `_.ary` and `_.rearg`
+ * modify function arguments, making the order in which they are executed important,
+ * preventing the merging of metadata. However, we make an exception for a safe
+ * combined case where curried functions have `_.ary` and or `_.rearg` applied.
+ *
+ * @private
+ * @param {Array} data The destination metadata.
+ * @param {Array} source The source metadata.
+ * @returns {Array} Returns `data`.
+ */
+ function mergeData(data, source) {
+ var bitmask = data[1],
+ srcBitmask = source[1],
+ newBitmask = bitmask | srcBitmask,
+ isCommon = newBitmask < (BIND_FLAG | BIND_KEY_FLAG | ARY_FLAG);
+
+ var isCombo =
+ ((srcBitmask == ARY_FLAG) && (bitmask == CURRY_FLAG)) ||
+ ((srcBitmask == ARY_FLAG) && (bitmask == REARG_FLAG) && (data[7].length <= source[8])) ||
+ ((srcBitmask == (ARY_FLAG | REARG_FLAG)) && (source[7].length <= source[8]) && (bitmask == CURRY_FLAG));
+
+ // Exit early if metadata can't be merged.
+ if (!(isCommon || isCombo)) {
+ return data;
+ }
+ // Use source `thisArg` if available.
+ if (srcBitmask & BIND_FLAG) {
+ data[2] = source[2];
+ // Set when currying a bound function.
+ newBitmask |= bitmask & BIND_FLAG ? 0 : CURRY_BOUND_FLAG;
+ }
+ // Compose partial arguments.
+ var value = source[3];
+ if (value) {
+ var partials = data[3];
+ data[3] = partials ? composeArgs(partials, value, source[4]) : copyArray(value);
+ data[4] = partials ? replaceHolders(data[3], PLACEHOLDER) : copyArray(source[4]);
+ }
+ // Compose partial right arguments.
+ value = source[5];
+ if (value) {
+ partials = data[5];
+ data[5] = partials ? composeArgsRight(partials, value, source[6]) : copyArray(value);
+ data[6] = partials ? replaceHolders(data[5], PLACEHOLDER) : copyArray(source[6]);
+ }
+ // Use source `argPos` if available.
+ value = source[7];
+ if (value) {
+ data[7] = copyArray(value);
+ }
+ // Use source `ary` if it's smaller.
+ if (srcBitmask & ARY_FLAG) {
+ data[8] = data[8] == null ? source[8] : nativeMin(data[8], source[8]);
+ }
+ // Use source `arity` if one is not provided.
+ if (data[9] == null) {
+ data[9] = source[9];
+ }
+ // Use source `func` and merge bitmasks.
+ data[0] = source[0];
+ data[1] = newBitmask;
+
+ return data;
+ }
+
+ /**
+ * Used by `_.defaultsDeep` to customize its `_.merge` use.
+ *
+ * @private
+ * @param {*} objValue The destination value.
+ * @param {*} srcValue The source value.
+ * @param {string} key The key of the property to merge.
+ * @param {Object} object The parent object of `objValue`.
+ * @param {Object} source The parent object of `srcValue`.
+ * @param {Object} [stack] Tracks traversed source values and their merged counterparts.
+ * @returns {*} Returns the value to assign.
+ */
+ function mergeDefaults(objValue, srcValue, key, object, source, stack) {
+ if (isObject(objValue) && isObject(srcValue)) {
+ baseMerge(objValue, srcValue, undefined, mergeDefaults, stack.set(srcValue, objValue));
+ }
+ return objValue;
+ }
+
+ /**
+ * Gets the parent value at `path` of `object`.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @param {Array} path The path to get the parent value of.
+ * @returns {*} Returns the parent value.
+ */
+ function parent(object, path) {
+ return path.length == 1 ? object : get(object, baseSlice(path, 0, -1));
+ }
+
+ /**
+ * Reorder `array` according to the specified indexes where the element at
+ * the first index is assigned as the first element, the element at
+ * the second index is assigned as the second element, and so on.
+ *
+ * @private
+ * @param {Array} array The array to reorder.
+ * @param {Array} indexes The arranged array indexes.
+ * @returns {Array} Returns `array`.
+ */
+ function reorder(array, indexes) {
+ var arrLength = array.length,
+ length = nativeMin(indexes.length, arrLength),
+ oldArray = copyArray(array);
+
+ while (length--) {
+ var index = indexes[length];
+ array[length] = isIndex(index, arrLength) ? oldArray[index] : undefined;
+ }
+ return array;
+ }
+
+ /**
+ * Sets metadata for `func`.
+ *
+ * **Note:** If this function becomes hot, i.e. is invoked a lot in a short
+ * period of time, it will trip its breaker and transition to an identity function
+ * to avoid garbage collection pauses in V8. See [V8 issue 2070](https://code.google.com/p/v8/issues/detail?id=2070)
+ * for more details.
+ *
+ * @private
+ * @param {Function} func The function to associate metadata with.
+ * @param {*} data The metadata.
+ * @returns {Function} Returns `func`.
+ */
+ var setData = (function() {
+ var count = 0,
+ lastCalled = 0;
+
+ return function(key, value) {
+ var stamp = now(),
+ remaining = HOT_SPAN - (stamp - lastCalled);
+
+ lastCalled = stamp;
+ if (remaining > 0) {
+ if (++count >= HOT_COUNT) {
+ return key;
+ }
+ } else {
+ count = 0;
+ }
+ return baseSetData(key, value);
+ };
+ }());
+
+ /**
+ * Converts `string` to a property path array.
+ *
+ * @private
+ * @param {string} string The string to convert.
+ * @returns {Array} Returns the property path array.
+ */
+ function stringToPath(string) {
+ var result = [];
+ toString(string).replace(rePropName, function(match, number, quote, string) {
+ result.push(quote ? string.replace(reEscapeChar, '$1') : (number || match));
+ });
+ return result;
+ }
+
+ /**
+ * Creates a clone of `wrapper`.
+ *
+ * @private
+ * @param {Object} wrapper The wrapper to clone.
+ * @returns {Object} Returns the cloned wrapper.
+ */
+ function wrapperClone(wrapper) {
+ if (wrapper instanceof LazyWrapper) {
+ return wrapper.clone();
+ }
+ var result = new LodashWrapper(wrapper.__wrapped__, wrapper.__chain__);
+ result.__actions__ = copyArray(wrapper.__actions__);
+ result.__index__ = wrapper.__index__;
+ result.__values__ = wrapper.__values__;
+ return result;
+ }
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Creates an array of elements split into groups the length of `size`.
+ * If `array` can't be split evenly, the final chunk will be the remaining
+ * elements.
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The array to process.
+ * @param {number} [size=0] The length of each chunk.
+ * @returns {Array} Returns the new array containing chunks.
+ * @example
+ *
+ * _.chunk(['a', 'b', 'c', 'd'], 2);
+ * // => [['a', 'b'], ['c', 'd']]
+ *
+ * _.chunk(['a', 'b', 'c', 'd'], 3);
+ * // => [['a', 'b', 'c'], ['d']]
+ */
+ function chunk(array, size) {
+ size = nativeMax(toInteger(size), 0);
+
+ var length = array ? array.length : 0;
+ if (!length || size < 1) {
+ return [];
+ }
+ var index = 0,
+ resIndex = 0,
+ result = Array(nativeCeil(length / size));
+
+ while (index < length) {
+ result[resIndex++] = baseSlice(array, index, (index += size));
+ }
+ return result;
+ }
+
+ /**
+ * Creates an array with all falsey values removed. The values `false`, `null`,
+ * `0`, `""`, `undefined`, and `NaN` are falsey.
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The array to compact.
+ * @returns {Array} Returns the new array of filtered values.
+ * @example
+ *
+ * _.compact([0, 1, false, 2, '', 3]);
+ * // => [1, 2, 3]
+ */
+ function compact(array) {
+ var index = -1,
+ length = array ? array.length : 0,
+ resIndex = 0,
+ result = [];
+
+ while (++index < length) {
+ var value = array[index];
+ if (value) {
+ result[resIndex++] = value;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Creates a new array concatenating `array` with any additional arrays
+ * and/or values.
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The array to concatenate.
+ * @param {...*} [values] The values to concatenate.
+ * @returns {Array} Returns the new concatenated array.
+ * @example
+ *
+ * var array = [1];
+ * var other = _.concat(array, 2, [3], [[4]]);
+ *
+ * console.log(other);
+ * // => [1, 2, 3, [4]]
+ *
+ * console.log(array);
+ * // => [1]
+ */
+ var concat = rest(function(array, values) {
+ if (!isArray(array)) {
+ array = array == null ? [] : [Object(array)];
+ }
+ values = baseFlatten(values, 1);
+ return arrayConcat(array, values);
+ });
+
+ /**
+ * Creates an array of unique `array` values not included in the other
+ * given arrays using [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+ * for equality comparisons. The order of result values is determined by the
+ * order they occur in the first array.
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The array to inspect.
+ * @param {...Array} [values] The values to exclude.
+ * @returns {Array} Returns the new array of filtered values.
+ * @example
+ *
+ * _.difference([3, 2, 1], [4, 2]);
+ * // => [3, 1]
+ */
+ var difference = rest(function(array, values) {
+ return isArrayLikeObject(array)
+ ? baseDifference(array, baseFlatten(values, 1, true))
+ : [];
+ });
+
+ /**
+ * This method is like `_.difference` except that it accepts `iteratee` which
+ * is invoked for each element of `array` and `values` to generate the criterion
+ * by which they're compared. Result values are chosen from the first array.
+ * The iteratee is invoked with one argument: (value).
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The array to inspect.
+ * @param {...Array} [values] The values to exclude.
+ * @param {Function|Object|string} [iteratee=_.identity] The iteratee invoked per element.
+ * @returns {Array} Returns the new array of filtered values.
+ * @example
+ *
+ * _.differenceBy([3.1, 2.2, 1.3], [4.4, 2.5], Math.floor);
+ * // => [3.1, 1.3]
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.differenceBy([{ 'x': 2 }, { 'x': 1 }], [{ 'x': 1 }], 'x');
+ * // => [{ 'x': 2 }]
+ */
+ var differenceBy = rest(function(array, values) {
+ var iteratee = last(values);
+ if (isArrayLikeObject(iteratee)) {
+ iteratee = undefined;
+ }
+ return isArrayLikeObject(array)
+ ? baseDifference(array, baseFlatten(values, 1, true), getIteratee(iteratee))
+ : [];
+ });
+
+ /**
+ * This method is like `_.difference` except that it accepts `comparator`
+ * which is invoked to compare elements of `array` to `values`. Result values
+ * are chosen from the first array. The comparator is invoked with two arguments:
+ * (arrVal, othVal).
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The array to inspect.
+ * @param {...Array} [values] The values to exclude.
+ * @param {Function} [comparator] The comparator invoked per element.
+ * @returns {Array} Returns the new array of filtered values.
+ * @example
+ *
+ * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
+ *
+ * _.differenceWith(objects, [{ 'x': 1, 'y': 2 }], _.isEqual);
+ * // => [{ 'x': 2, 'y': 1 }]
+ */
+ var differenceWith = rest(function(array, values) {
+ var comparator = last(values);
+ if (isArrayLikeObject(comparator)) {
+ comparator = undefined;
+ }
+ return isArrayLikeObject(array)
+ ? baseDifference(array, baseFlatten(values, 1, true), undefined, comparator)
+ : [];
+ });
+
+ /**
+ * Creates a slice of `array` with `n` elements dropped from the beginning.
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The array to query.
+ * @param {number} [n=1] The number of elements to drop.
+ * @param- {Object} [guard] Enables use as an iteratee for functions like `_.map`.
+ * @returns {Array} Returns the slice of `array`.
+ * @example
+ *
+ * _.drop([1, 2, 3]);
+ * // => [2, 3]
+ *
+ * _.drop([1, 2, 3], 2);
+ * // => [3]
+ *
+ * _.drop([1, 2, 3], 5);
+ * // => []
+ *
+ * _.drop([1, 2, 3], 0);
+ * // => [1, 2, 3]
+ */
+ function drop(array, n, guard) {
+ var length = array ? array.length : 0;
+ if (!length) {
+ return [];
+ }
+ n = (guard || n === undefined) ? 1 : toInteger(n);
+ return baseSlice(array, n < 0 ? 0 : n, length);
+ }
+
+ /**
+ * Creates a slice of `array` with `n` elements dropped from the end.
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The array to query.
+ * @param {number} [n=1] The number of elements to drop.
+ * @param- {Object} [guard] Enables use as an iteratee for functions like `_.map`.
+ * @returns {Array} Returns the slice of `array`.
+ * @example
+ *
+ * _.dropRight([1, 2, 3]);
+ * // => [1, 2]
+ *
+ * _.dropRight([1, 2, 3], 2);
+ * // => [1]
+ *
+ * _.dropRight([1, 2, 3], 5);
+ * // => []
+ *
+ * _.dropRight([1, 2, 3], 0);
+ * // => [1, 2, 3]
+ */
+ function dropRight(array, n, guard) {
+ var length = array ? array.length : 0;
+ if (!length) {
+ return [];
+ }
+ n = (guard || n === undefined) ? 1 : toInteger(n);
+ n = length - n;
+ return baseSlice(array, 0, n < 0 ? 0 : n);
+ }
+
+ /**
+ * Creates a slice of `array` excluding elements dropped from the end.
+ * Elements are dropped until `predicate` returns falsey. The predicate is
+ * invoked with three arguments: (value, index, array).
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The array to query.
+ * @param {Function|Object|string} [predicate=_.identity] The function invoked per iteration.
+ * @returns {Array} Returns the slice of `array`.
+ * @example
+ *
+ * var users = [
+ * { 'user': 'barney', 'active': true },
+ * { 'user': 'fred', 'active': false },
+ * { 'user': 'pebbles', 'active': false }
+ * ];
+ *
+ * _.dropRightWhile(users, function(o) { return !o.active; });
+ * // => objects for ['barney']
+ *
+ * // The `_.matches` iteratee shorthand.
+ * _.dropRightWhile(users, { 'user': 'pebbles', 'active': false });
+ * // => objects for ['barney', 'fred']
+ *
+ * // The `_.matchesProperty` iteratee shorthand.
+ * _.dropRightWhile(users, ['active', false]);
+ * // => objects for ['barney']
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.dropRightWhile(users, 'active');
+ * // => objects for ['barney', 'fred', 'pebbles']
+ */
+ function dropRightWhile(array, predicate) {
+ return (array && array.length)
+ ? baseWhile(array, getIteratee(predicate, 3), true, true)
+ : [];
+ }
+
+ /**
+ * Creates a slice of `array` excluding elements dropped from the beginning.
+ * Elements are dropped until `predicate` returns falsey. The predicate is
+ * invoked with three arguments: (value, index, array).
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The array to query.
+ * @param {Function|Object|string} [predicate=_.identity] The function invoked per iteration.
+ * @returns {Array} Returns the slice of `array`.
+ * @example
+ *
+ * var users = [
+ * { 'user': 'barney', 'active': false },
+ * { 'user': 'fred', 'active': false },
+ * { 'user': 'pebbles', 'active': true }
+ * ];
+ *
+ * _.dropWhile(users, function(o) { return !o.active; });
+ * // => objects for ['pebbles']
+ *
+ * // The `_.matches` iteratee shorthand.
+ * _.dropWhile(users, { 'user': 'barney', 'active': false });
+ * // => objects for ['fred', 'pebbles']
+ *
+ * // The `_.matchesProperty` iteratee shorthand.
+ * _.dropWhile(users, ['active', false]);
+ * // => objects for ['pebbles']
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.dropWhile(users, 'active');
+ * // => objects for ['barney', 'fred', 'pebbles']
+ */
+ function dropWhile(array, predicate) {
+ return (array && array.length)
+ ? baseWhile(array, getIteratee(predicate, 3), true)
+ : [];
+ }
+
+ /**
+ * Fills elements of `array` with `value` from `start` up to, but not
+ * including, `end`.
+ *
+ * **Note:** This method mutates `array`.
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The array to fill.
+ * @param {*} value The value to fill `array` with.
+ * @param {number} [start=0] The start position.
+ * @param {number} [end=array.length] The end position.
+ * @returns {Array} Returns `array`.
+ * @example
+ *
+ * var array = [1, 2, 3];
+ *
+ * _.fill(array, 'a');
+ * console.log(array);
+ * // => ['a', 'a', 'a']
+ *
+ * _.fill(Array(3), 2);
+ * // => [2, 2, 2]
+ *
+ * _.fill([4, 6, 8, 10], '*', 1, 3);
+ * // => [4, '*', '*', 10]
+ */
+ function fill(array, value, start, end) {
+ var length = array ? array.length : 0;
+ if (!length) {
+ return [];
+ }
+ if (start && typeof start != 'number' && isIterateeCall(array, value, start)) {
+ start = 0;
+ end = length;
+ }
+ return baseFill(array, value, start, end);
+ }
+
+ /**
+ * This method is like `_.find` except that it returns the index of the first
+ * element `predicate` returns truthy for instead of the element itself.
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The array to search.
+ * @param {Function|Object|string} [predicate=_.identity] The function invoked per iteration.
+ * @returns {number} Returns the index of the found element, else `-1`.
+ * @example
+ *
+ * var users = [
+ * { 'user': 'barney', 'active': false },
+ * { 'user': 'fred', 'active': false },
+ * { 'user': 'pebbles', 'active': true }
+ * ];
+ *
+ * _.findIndex(users, function(o) { return o.user == 'barney'; });
+ * // => 0
+ *
+ * // The `_.matches` iteratee shorthand.
+ * _.findIndex(users, { 'user': 'fred', 'active': false });
+ * // => 1
+ *
+ * // The `_.matchesProperty` iteratee shorthand.
+ * _.findIndex(users, ['active', false]);
+ * // => 0
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.findIndex(users, 'active');
+ * // => 2
+ */
+ function findIndex(array, predicate) {
+ return (array && array.length)
+ ? baseFindIndex(array, getIteratee(predicate, 3))
+ : -1;
+ }
+
+ /**
+ * This method is like `_.findIndex` except that it iterates over elements
+ * of `collection` from right to left.
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The array to search.
+ * @param {Function|Object|string} [predicate=_.identity] The function invoked per iteration.
+ * @returns {number} Returns the index of the found element, else `-1`.
+ * @example
+ *
+ * var users = [
+ * { 'user': 'barney', 'active': true },
+ * { 'user': 'fred', 'active': false },
+ * { 'user': 'pebbles', 'active': false }
+ * ];
+ *
+ * _.findLastIndex(users, function(o) { return o.user == 'pebbles'; });
+ * // => 2
+ *
+ * // The `_.matches` iteratee shorthand.
+ * _.findLastIndex(users, { 'user': 'barney', 'active': true });
+ * // => 0
+ *
+ * // The `_.matchesProperty` iteratee shorthand.
+ * _.findLastIndex(users, ['active', false]);
+ * // => 2
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.findLastIndex(users, 'active');
+ * // => 0
+ */
+ function findLastIndex(array, predicate) {
+ return (array && array.length)
+ ? baseFindIndex(array, getIteratee(predicate, 3), true)
+ : -1;
+ }
+
+ /**
+ * Flattens `array` a single level deep.
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The array to flatten.
+ * @returns {Array} Returns the new flattened array.
+ * @example
+ *
+ * _.flatten([1, [2, [3, [4]], 5]]);
+ * // => [1, 2, [3, [4]], 5]
+ */
+ function flatten(array) {
+ var length = array ? array.length : 0;
+ return length ? baseFlatten(array, 1) : [];
+ }
+
+ /**
+ * Recursively flattens `array`.
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The array to flatten.
+ * @returns {Array} Returns the new flattened array.
+ * @example
+ *
+ * _.flattenDeep([1, [2, [3, [4]], 5]]);
+ * // => [1, 2, 3, 4, 5]
+ */
+ function flattenDeep(array) {
+ var length = array ? array.length : 0;
+ return length ? baseFlatten(array, INFINITY) : [];
+ }
+
+ /**
+ * Recursively flatten `array` up to `depth` times.
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The array to flatten.
+ * @param {number} [depth=1] The maximum recursion depth.
+ * @returns {Array} Returns the new flattened array.
+ * @example
+ *
+ * var array = [1, [2, [3, [4]], 5]];
+ *
+ * _.flattenDepth(array, 1);
+ * // => [1, 2, [3, [4]], 5]
+ *
+ * _.flattenDepth(array, 2);
+ * // => [1, 2, 3, [4], 5]
+ */
+ function flattenDepth(array, depth) {
+ var length = array ? array.length : 0;
+ if (!length) {
+ return [];
+ }
+ depth = depth === undefined ? 1 : toInteger(depth);
+ return baseFlatten(array, depth);
+ }
+
+ /**
+ * The inverse of `_.toPairs`; this method returns an object composed
+ * from key-value `pairs`.
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} pairs The key-value pairs.
+ * @returns {Object} Returns the new object.
+ * @example
+ *
+ * _.fromPairs([['fred', 30], ['barney', 40]]);
+ * // => { 'fred': 30, 'barney': 40 }
+ */
+ function fromPairs(pairs) {
+ var index = -1,
+ length = pairs ? pairs.length : 0,
+ result = {};
+
+ while (++index < length) {
+ var pair = pairs[index];
+ result[pair[0]] = pair[1];
+ }
+ return result;
+ }
+
+ /**
+ * Gets the first element of `array`.
+ *
+ * @static
+ * @memberOf _
+ * @alias first
+ * @category Array
+ * @param {Array} array The array to query.
+ * @returns {*} Returns the first element of `array`.
+ * @example
+ *
+ * _.head([1, 2, 3]);
+ * // => 1
+ *
+ * _.head([]);
+ * // => undefined
+ */
+ function head(array) {
+ return array ? array[0] : undefined;
+ }
+
+ /**
+ * Gets the index at which the first occurrence of `value` is found in `array`
+ * using [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+ * for equality comparisons. If `fromIndex` is negative, it's used as the offset
+ * from the end of `array`.
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The array to search.
+ * @param {*} value The value to search for.
+ * @param {number} [fromIndex=0] The index to search from.
+ * @returns {number} Returns the index of the matched value, else `-1`.
+ * @example
+ *
+ * _.indexOf([1, 2, 1, 2], 2);
+ * // => 1
+ *
+ * // Search from the `fromIndex`.
+ * _.indexOf([1, 2, 1, 2], 2, 2);
+ * // => 3
+ */
+ function indexOf(array, value, fromIndex) {
+ var length = array ? array.length : 0;
+ if (!length) {
+ return -1;
+ }
+ fromIndex = toInteger(fromIndex);
+ if (fromIndex < 0) {
+ fromIndex = nativeMax(length + fromIndex, 0);
+ }
+ return baseIndexOf(array, value, fromIndex);
+ }
+
+ /**
+ * Gets all but the last element of `array`.
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The array to query.
+ * @returns {Array} Returns the slice of `array`.
+ * @example
+ *
+ * _.initial([1, 2, 3]);
+ * // => [1, 2]
+ */
+ function initial(array) {
+ return dropRight(array, 1);
+ }
+
+ /**
+ * Creates an array of unique values that are included in all given arrays
+ * using [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+ * for equality comparisons. The order of result values is determined by the
+ * order they occur in the first array.
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {...Array} [arrays] The arrays to inspect.
+ * @returns {Array} Returns the new array of intersecting values.
+ * @example
+ *
+ * _.intersection([2, 1], [4, 2], [1, 2]);
+ * // => [2]
+ */
+ var intersection = rest(function(arrays) {
+ var mapped = arrayMap(arrays, baseCastArrayLikeObject);
+ return (mapped.length && mapped[0] === arrays[0])
+ ? baseIntersection(mapped)
+ : [];
+ });
+
+ /**
+ * This method is like `_.intersection` except that it accepts `iteratee`
+ * which is invoked for each element of each `arrays` to generate the criterion
+ * by which they're compared. Result values are chosen from the first array.
+ * The iteratee is invoked with one argument: (value).
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {...Array} [arrays] The arrays to inspect.
+ * @param {Function|Object|string} [iteratee=_.identity] The iteratee invoked per element.
+ * @returns {Array} Returns the new array of intersecting values.
+ * @example
+ *
+ * _.intersectionBy([2.1, 1.2], [4.3, 2.4], Math.floor);
+ * // => [2.1]
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.intersectionBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');
+ * // => [{ 'x': 1 }]
+ */
+ var intersectionBy = rest(function(arrays) {
+ var iteratee = last(arrays),
+ mapped = arrayMap(arrays, baseCastArrayLikeObject);
+
+ if (iteratee === last(mapped)) {
+ iteratee = undefined;
+ } else {
+ mapped.pop();
+ }
+ return (mapped.length && mapped[0] === arrays[0])
+ ? baseIntersection(mapped, getIteratee(iteratee))
+ : [];
+ });
+
+ /**
+ * This method is like `_.intersection` except that it accepts `comparator`
+ * which is invoked to compare elements of `arrays`. Result values are chosen
+ * from the first array. The comparator is invoked with two arguments:
+ * (arrVal, othVal).
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {...Array} [arrays] The arrays to inspect.
+ * @param {Function} [comparator] The comparator invoked per element.
+ * @returns {Array} Returns the new array of intersecting values.
+ * @example
+ *
+ * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
+ * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];
+ *
+ * _.intersectionWith(objects, others, _.isEqual);
+ * // => [{ 'x': 1, 'y': 2 }]
+ */
+ var intersectionWith = rest(function(arrays) {
+ var comparator = last(arrays),
+ mapped = arrayMap(arrays, baseCastArrayLikeObject);
+
+ if (comparator === last(mapped)) {
+ comparator = undefined;
+ } else {
+ mapped.pop();
+ }
+ return (mapped.length && mapped[0] === arrays[0])
+ ? baseIntersection(mapped, undefined, comparator)
+ : [];
+ });
+
+ /**
+ * Converts all elements in `array` into a string separated by `separator`.
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The array to convert.
+ * @param {string} [separator=','] The element separator.
+ * @returns {string} Returns the joined string.
+ * @example
+ *
+ * _.join(['a', 'b', 'c'], '~');
+ * // => 'a~b~c'
+ */
+ function join(array, separator) {
+ return array ? nativeJoin.call(array, separator) : '';
+ }
+
+ /**
+ * Gets the last element of `array`.
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The array to query.
+ * @returns {*} Returns the last element of `array`.
+ * @example
+ *
+ * _.last([1, 2, 3]);
+ * // => 3
+ */
+ function last(array) {
+ var length = array ? array.length : 0;
+ return length ? array[length - 1] : undefined;
+ }
+
+ /**
+ * This method is like `_.indexOf` except that it iterates over elements of
+ * `array` from right to left.
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The array to search.
+ * @param {*} value The value to search for.
+ * @param {number} [fromIndex=array.length-1] The index to search from.
+ * @returns {number} Returns the index of the matched value, else `-1`.
+ * @example
+ *
+ * _.lastIndexOf([1, 2, 1, 2], 2);
+ * // => 3
+ *
+ * // Search from the `fromIndex`.
+ * _.lastIndexOf([1, 2, 1, 2], 2, 2);
+ * // => 1
+ */
+ function lastIndexOf(array, value, fromIndex) {
+ var length = array ? array.length : 0;
+ if (!length) {
+ return -1;
+ }
+ var index = length;
+ if (fromIndex !== undefined) {
+ index = toInteger(fromIndex);
+ index = (index < 0 ? nativeMax(length + index, 0) : nativeMin(index, length - 1)) + 1;
+ }
+ if (value !== value) {
+ return indexOfNaN(array, index, true);
+ }
+ while (index--) {
+ if (array[index] === value) {
+ return index;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Removes all given values from `array` using
+ * [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+ * for equality comparisons.
+ *
+ * **Note:** Unlike `_.without`, this method mutates `array`. Use `_.remove`
+ * to remove elements from an array by predicate.
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The array to modify.
+ * @param {...*} [values] The values to remove.
+ * @returns {Array} Returns `array`.
+ * @example
+ *
+ * var array = [1, 2, 3, 1, 2, 3];
+ *
+ * _.pull(array, 2, 3);
+ * console.log(array);
+ * // => [1, 1]
+ */
+ var pull = rest(pullAll);
+
+ /**
+ * This method is like `_.pull` except that it accepts an array of values to remove.
+ *
+ * **Note:** Unlike `_.difference`, this method mutates `array`.
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The array to modify.
+ * @param {Array} values The values to remove.
+ * @returns {Array} Returns `array`.
+ * @example
+ *
+ * var array = [1, 2, 3, 1, 2, 3];
+ *
+ * _.pullAll(array, [2, 3]);
+ * console.log(array);
+ * // => [1, 1]
+ */
+ function pullAll(array, values) {
+ return (array && array.length && values && values.length)
+ ? basePullAll(array, values)
+ : array;
+ }
+
+ /**
+ * This method is like `_.pullAll` except that it accepts `iteratee` which is
+ * invoked for each element of `array` and `values` to generate the criterion
+ * by which they're compared. The iteratee is invoked with one argument: (value).
+ *
+ * **Note:** Unlike `_.differenceBy`, this method mutates `array`.
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The array to modify.
+ * @param {Array} values The values to remove.
+ * @param {Function|Object|string} [iteratee=_.identity] The iteratee invoked per element.
+ * @returns {Array} Returns `array`.
+ * @example
+ *
+ * var array = [{ 'x': 1 }, { 'x': 2 }, { 'x': 3 }, { 'x': 1 }];
+ *
+ * _.pullAllBy(array, [{ 'x': 1 }, { 'x': 3 }], 'x');
+ * console.log(array);
+ * // => [{ 'x': 2 }]
+ */
+ function pullAllBy(array, values, iteratee) {
+ return (array && array.length && values && values.length)
+ ? basePullAll(array, values, getIteratee(iteratee))
+ : array;
+ }
+
+ /**
+ * This method is like `_.pullAll` except that it accepts `comparator` which
+ * is invoked to compare elements of `array` to `values`. The comparator is
+ * invoked with two arguments: (arrVal, othVal).
+ *
+ * **Note:** Unlike `_.differenceWith`, this method mutates `array`.
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The array to modify.
+ * @param {Array} values The values to remove.
+ * @param {Function} [comparator] The comparator invoked per element.
+ * @returns {Array} Returns `array`.
+ * @example
+ *
+ * var array = [{ 'x': 1, 'y': 2 }, { 'x': 3, 'y': 4 }, { 'x': 5, 'y': 6 }];
+ *
+ * _.pullAllWith(array, [{ 'x': 3, 'y': 4 }], _.isEqual);
+ * console.log(array);
+ * // => [{ 'x': 1, 'y': 2 }, { 'x': 5, 'y': 6 }]
+ */
+ function pullAllWith(array, values, comparator) {
+ return (array && array.length && values && values.length)
+ ? basePullAll(array, values, undefined, comparator)
+ : array;
+ }
+
+ /**
+ * Removes elements from `array` corresponding to `indexes` and returns an
+ * array of removed elements.
+ *
+ * **Note:** Unlike `_.at`, this method mutates `array`.
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The array to modify.
+ * @param {...(number|number[])} [indexes] The indexes of elements to remove,
+ * specified individually or in arrays.
+ * @returns {Array} Returns the new array of removed elements.
+ * @example
+ *
+ * var array = [5, 10, 15, 20];
+ * var evens = _.pullAt(array, 1, 3);
+ *
+ * console.log(array);
+ * // => [5, 15]
+ *
+ * console.log(evens);
+ * // => [10, 20]
+ */
+ var pullAt = rest(function(array, indexes) {
+ indexes = arrayMap(baseFlatten(indexes, 1), String);
+
+ var result = baseAt(array, indexes);
+ basePullAt(array, indexes.sort(compareAscending));
+ return result;
+ });
+
+ /**
+ * Removes all elements from `array` that `predicate` returns truthy for
+ * and returns an array of the removed elements. The predicate is invoked
+ * with three arguments: (value, index, array).
+ *
+ * **Note:** Unlike `_.filter`, this method mutates `array`. Use `_.pull`
+ * to pull elements from an array by value.
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The array to modify.
+ * @param {Function|Object|string} [predicate=_.identity] The function invoked per iteration.
+ * @returns {Array} Returns the new array of removed elements.
+ * @example
+ *
+ * var array = [1, 2, 3, 4];
+ * var evens = _.remove(array, function(n) {
+ * return n % 2 == 0;
+ * });
+ *
+ * console.log(array);
+ * // => [1, 3]
+ *
+ * console.log(evens);
+ * // => [2, 4]
+ */
+ function remove(array, predicate) {
+ var result = [];
+ if (!(array && array.length)) {
+ return result;
+ }
+ var index = -1,
+ indexes = [],
+ length = array.length;
+
+ predicate = getIteratee(predicate, 3);
+ while (++index < length) {
+ var value = array[index];
+ if (predicate(value, index, array)) {
+ result.push(value);
+ indexes.push(index);
+ }
+ }
+ basePullAt(array, indexes);
+ return result;
+ }
+
+ /**
+ * Reverses `array` so that the first element becomes the last, the second
+ * element becomes the second to last, and so on.
+ *
+ * **Note:** This method mutates `array` and is based on
+ * [`Array#reverse`](https://mdn.io/Array/reverse).
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @returns {Array} Returns `array`.
+ * @example
+ *
+ * var array = [1, 2, 3];
+ *
+ * _.reverse(array);
+ * // => [3, 2, 1]
+ *
+ * console.log(array);
+ * // => [3, 2, 1]
+ */
+ function reverse(array) {
+ return array ? nativeReverse.call(array) : array;
+ }
+
+ /**
+ * Creates a slice of `array` from `start` up to, but not including, `end`.
+ *
+ * **Note:** This method is used instead of [`Array#slice`](https://mdn.io/Array/slice)
+ * to ensure dense arrays are returned.
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The array to slice.
+ * @param {number} [start=0] The start position.
+ * @param {number} [end=array.length] The end position.
+ * @returns {Array} Returns the slice of `array`.
+ */
+ function slice(array, start, end) {
+ var length = array ? array.length : 0;
+ if (!length) {
+ return [];
+ }
+ if (end && typeof end != 'number' && isIterateeCall(array, start, end)) {
+ start = 0;
+ end = length;
+ }
+ else {
+ start = start == null ? 0 : toInteger(start);
+ end = end === undefined ? length : toInteger(end);
+ }
+ return baseSlice(array, start, end);
+ }
+
+ /**
+ * Uses a binary search to determine the lowest index at which `value` should
+ * be inserted into `array` in order to maintain its sort order.
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The sorted array to inspect.
+ * @param {*} value The value to evaluate.
+ * @returns {number} Returns the index at which `value` should be inserted into `array`.
+ * @example
+ *
+ * _.sortedIndex([30, 50], 40);
+ * // => 1
+ *
+ * _.sortedIndex([4, 5], 4);
+ * // => 0
+ */
+ function sortedIndex(array, value) {
+ return baseSortedIndex(array, value);
+ }
+
+ /**
+ * This method is like `_.sortedIndex` except that it accepts `iteratee`
+ * which is invoked for `value` and each element of `array` to compute their
+ * sort ranking. The iteratee is invoked with one argument: (value).
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The sorted array to inspect.
+ * @param {*} value The value to evaluate.
+ * @param {Function|Object|string} [iteratee=_.identity] The iteratee invoked per element.
+ * @returns {number} Returns the index at which `value` should be inserted into `array`.
+ * @example
+ *
+ * var dict = { 'thirty': 30, 'forty': 40, 'fifty': 50 };
+ *
+ * _.sortedIndexBy(['thirty', 'fifty'], 'forty', _.propertyOf(dict));
+ * // => 1
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.sortedIndexBy([{ 'x': 4 }, { 'x': 5 }], { 'x': 4 }, 'x');
+ * // => 0
+ */
+ function sortedIndexBy(array, value, iteratee) {
+ return baseSortedIndexBy(array, value, getIteratee(iteratee));
+ }
+
+ /**
+ * This method is like `_.indexOf` except that it performs a binary
+ * search on a sorted `array`.
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The array to search.
+ * @param {*} value The value to search for.
+ * @returns {number} Returns the index of the matched value, else `-1`.
+ * @example
+ *
+ * _.sortedIndexOf([1, 1, 2, 2], 2);
+ * // => 2
+ */
+ function sortedIndexOf(array, value) {
+ var length = array ? array.length : 0;
+ if (length) {
+ var index = baseSortedIndex(array, value);
+ if (index < length && eq(array[index], value)) {
+ return index;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * This method is like `_.sortedIndex` except that it returns the highest
+ * index at which `value` should be inserted into `array` in order to
+ * maintain its sort order.
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The sorted array to inspect.
+ * @param {*} value The value to evaluate.
+ * @returns {number} Returns the index at which `value` should be inserted into `array`.
+ * @example
+ *
+ * _.sortedLastIndex([4, 5], 4);
+ * // => 1
+ */
+ function sortedLastIndex(array, value) {
+ return baseSortedIndex(array, value, true);
+ }
+
+ /**
+ * This method is like `_.sortedLastIndex` except that it accepts `iteratee`
+ * which is invoked for `value` and each element of `array` to compute their
+ * sort ranking. The iteratee is invoked with one argument: (value).
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The sorted array to inspect.
+ * @param {*} value The value to evaluate.
+ * @param {Function|Object|string} [iteratee=_.identity] The iteratee invoked per element.
+ * @returns {number} Returns the index at which `value` should be inserted into `array`.
+ * @example
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.sortedLastIndexBy([{ 'x': 4 }, { 'x': 5 }], { 'x': 4 }, 'x');
+ * // => 1
+ */
+ function sortedLastIndexBy(array, value, iteratee) {
+ return baseSortedIndexBy(array, value, getIteratee(iteratee), true);
+ }
+
+ /**
+ * This method is like `_.lastIndexOf` except that it performs a binary
+ * search on a sorted `array`.
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The array to search.
+ * @param {*} value The value to search for.
+ * @returns {number} Returns the index of the matched value, else `-1`.
+ * @example
+ *
+ * _.sortedLastIndexOf([1, 1, 2, 2], 2);
+ * // => 3
+ */
+ function sortedLastIndexOf(array, value) {
+ var length = array ? array.length : 0;
+ if (length) {
+ var index = baseSortedIndex(array, value, true) - 1;
+ if (eq(array[index], value)) {
+ return index;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * This method is like `_.uniq` except that it's designed and optimized
+ * for sorted arrays.
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The array to inspect.
+ * @returns {Array} Returns the new duplicate free array.
+ * @example
+ *
+ * _.sortedUniq([1, 1, 2]);
+ * // => [1, 2]
+ */
+ function sortedUniq(array) {
+ return (array && array.length)
+ ? baseSortedUniq(array)
+ : [];
+ }
+
+ /**
+ * This method is like `_.uniqBy` except that it's designed and optimized
+ * for sorted arrays.
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The array to inspect.
+ * @param {Function} [iteratee] The iteratee invoked per element.
+ * @returns {Array} Returns the new duplicate free array.
+ * @example
+ *
+ * _.sortedUniqBy([1.1, 1.2, 2.3, 2.4], Math.floor);
+ * // => [1.1, 2.3]
+ */
+ function sortedUniqBy(array, iteratee) {
+ return (array && array.length)
+ ? baseSortedUniqBy(array, getIteratee(iteratee))
+ : [];
+ }
+
+ /**
+ * Gets all but the first element of `array`.
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The array to query.
+ * @returns {Array} Returns the slice of `array`.
+ * @example
+ *
+ * _.tail([1, 2, 3]);
+ * // => [2, 3]
+ */
+ function tail(array) {
+ return drop(array, 1);
+ }
+
+ /**
+ * Creates a slice of `array` with `n` elements taken from the beginning.
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The array to query.
+ * @param {number} [n=1] The number of elements to take.
+ * @param- {Object} [guard] Enables use as an iteratee for functions like `_.map`.
+ * @returns {Array} Returns the slice of `array`.
+ * @example
+ *
+ * _.take([1, 2, 3]);
+ * // => [1]
+ *
+ * _.take([1, 2, 3], 2);
+ * // => [1, 2]
+ *
+ * _.take([1, 2, 3], 5);
+ * // => [1, 2, 3]
+ *
+ * _.take([1, 2, 3], 0);
+ * // => []
+ */
+ function take(array, n, guard) {
+ if (!(array && array.length)) {
+ return [];
+ }
+ n = (guard || n === undefined) ? 1 : toInteger(n);
+ return baseSlice(array, 0, n < 0 ? 0 : n);
+ }
+
+ /**
+ * Creates a slice of `array` with `n` elements taken from the end.
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The array to query.
+ * @param {number} [n=1] The number of elements to take.
+ * @param- {Object} [guard] Enables use as an iteratee for functions like `_.map`.
+ * @returns {Array} Returns the slice of `array`.
+ * @example
+ *
+ * _.takeRight([1, 2, 3]);
+ * // => [3]
+ *
+ * _.takeRight([1, 2, 3], 2);
+ * // => [2, 3]
+ *
+ * _.takeRight([1, 2, 3], 5);
+ * // => [1, 2, 3]
+ *
+ * _.takeRight([1, 2, 3], 0);
+ * // => []
+ */
+ function takeRight(array, n, guard) {
+ var length = array ? array.length : 0;
+ if (!length) {
+ return [];
+ }
+ n = (guard || n === undefined) ? 1 : toInteger(n);
+ n = length - n;
+ return baseSlice(array, n < 0 ? 0 : n, length);
+ }
+
+ /**
+ * Creates a slice of `array` with elements taken from the end. Elements are
+ * taken until `predicate` returns falsey. The predicate is invoked with three
+ * arguments: (value, index, array).
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The array to query.
+ * @param {Function|Object|string} [predicate=_.identity] The function invoked per iteration.
+ * @returns {Array} Returns the slice of `array`.
+ * @example
+ *
+ * var users = [
+ * { 'user': 'barney', 'active': true },
+ * { 'user': 'fred', 'active': false },
+ * { 'user': 'pebbles', 'active': false }
+ * ];
+ *
+ * _.takeRightWhile(users, function(o) { return !o.active; });
+ * // => objects for ['fred', 'pebbles']
+ *
+ * // The `_.matches` iteratee shorthand.
+ * _.takeRightWhile(users, { 'user': 'pebbles', 'active': false });
+ * // => objects for ['pebbles']
+ *
+ * // The `_.matchesProperty` iteratee shorthand.
+ * _.takeRightWhile(users, ['active', false]);
+ * // => objects for ['fred', 'pebbles']
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.takeRightWhile(users, 'active');
+ * // => []
+ */
+ function takeRightWhile(array, predicate) {
+ return (array && array.length)
+ ? baseWhile(array, getIteratee(predicate, 3), false, true)
+ : [];
+ }
+
+ /**
+ * Creates a slice of `array` with elements taken from the beginning. Elements
+ * are taken until `predicate` returns falsey. The predicate is invoked with
+ * three arguments: (value, index, array).
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The array to query.
+ * @param {Function|Object|string} [predicate=_.identity] The function invoked per iteration.
+ * @returns {Array} Returns the slice of `array`.
+ * @example
+ *
+ * var users = [
+ * { 'user': 'barney', 'active': false },
+ * { 'user': 'fred', 'active': false},
+ * { 'user': 'pebbles', 'active': true }
+ * ];
+ *
+ * _.takeWhile(users, function(o) { return !o.active; });
+ * // => objects for ['barney', 'fred']
+ *
+ * // The `_.matches` iteratee shorthand.
+ * _.takeWhile(users, { 'user': 'barney', 'active': false });
+ * // => objects for ['barney']
+ *
+ * // The `_.matchesProperty` iteratee shorthand.
+ * _.takeWhile(users, ['active', false]);
+ * // => objects for ['barney', 'fred']
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.takeWhile(users, 'active');
+ * // => []
+ */
+ function takeWhile(array, predicate) {
+ return (array && array.length)
+ ? baseWhile(array, getIteratee(predicate, 3))
+ : [];
+ }
+
+ /**
+ * Creates an array of unique values, in order, from all given arrays using
+ * [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+ * for equality comparisons.
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {...Array} [arrays] The arrays to inspect.
+ * @returns {Array} Returns the new array of combined values.
+ * @example
+ *
+ * _.union([2, 1], [4, 2], [1, 2]);
+ * // => [2, 1, 4]
+ */
+ var union = rest(function(arrays) {
+ return baseUniq(baseFlatten(arrays, 1, true));
+ });
+
+ /**
+ * This method is like `_.union` except that it accepts `iteratee` which is
+ * invoked for each element of each `arrays` to generate the criterion by which
+ * uniqueness is computed. The iteratee is invoked with one argument: (value).
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {...Array} [arrays] The arrays to inspect.
+ * @param {Function|Object|string} [iteratee=_.identity] The iteratee invoked per element.
+ * @returns {Array} Returns the new array of combined values.
+ * @example
+ *
+ * _.unionBy([2.1, 1.2], [4.3, 2.4], Math.floor);
+ * // => [2.1, 1.2, 4.3]
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.unionBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');
+ * // => [{ 'x': 1 }, { 'x': 2 }]
+ */
+ var unionBy = rest(function(arrays) {
+ var iteratee = last(arrays);
+ if (isArrayLikeObject(iteratee)) {
+ iteratee = undefined;
+ }
+ return baseUniq(baseFlatten(arrays, 1, true), getIteratee(iteratee));
+ });
+
+ /**
+ * This method is like `_.union` except that it accepts `comparator` which
+ * is invoked to compare elements of `arrays`. The comparator is invoked
+ * with two arguments: (arrVal, othVal).
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {...Array} [arrays] The arrays to inspect.
+ * @param {Function} [comparator] The comparator invoked per element.
+ * @returns {Array} Returns the new array of combined values.
+ * @example
+ *
+ * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
+ * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];
+ *
+ * _.unionWith(objects, others, _.isEqual);
+ * // => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }]
+ */
+ var unionWith = rest(function(arrays) {
+ var comparator = last(arrays);
+ if (isArrayLikeObject(comparator)) {
+ comparator = undefined;
+ }
+ return baseUniq(baseFlatten(arrays, 1, true), undefined, comparator);
+ });
+
+ /**
+ * Creates a duplicate-free version of an array, using
+ * [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+ * for equality comparisons, in which only the first occurrence of each element
+ * is kept.
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The array to inspect.
+ * @returns {Array} Returns the new duplicate free array.
+ * @example
+ *
+ * _.uniq([2, 1, 2]);
+ * // => [2, 1]
+ */
+ function uniq(array) {
+ return (array && array.length)
+ ? baseUniq(array)
+ : [];
+ }
+
+ /**
+ * This method is like `_.uniq` except that it accepts `iteratee` which is
+ * invoked for each element in `array` to generate the criterion by which
+ * uniqueness is computed. The iteratee is invoked with one argument: (value).
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The array to inspect.
+ * @param {Function|Object|string} [iteratee=_.identity] The iteratee invoked per element.
+ * @returns {Array} Returns the new duplicate free array.
+ * @example
+ *
+ * _.uniqBy([2.1, 1.2, 2.3], Math.floor);
+ * // => [2.1, 1.2]
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.uniqBy([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x');
+ * // => [{ 'x': 1 }, { 'x': 2 }]
+ */
+ function uniqBy(array, iteratee) {
+ return (array && array.length)
+ ? baseUniq(array, getIteratee(iteratee))
+ : [];
+ }
+
+ /**
+ * This method is like `_.uniq` except that it accepts `comparator` which
+ * is invoked to compare elements of `array`. The comparator is invoked with
+ * two arguments: (arrVal, othVal).
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The array to inspect.
+ * @param {Function} [comparator] The comparator invoked per element.
+ * @returns {Array} Returns the new duplicate free array.
+ * @example
+ *
+ * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 2 }];
+ *
+ * _.uniqWith(objects, _.isEqual);
+ * // => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]
+ */
+ function uniqWith(array, comparator) {
+ return (array && array.length)
+ ? baseUniq(array, undefined, comparator)
+ : [];
+ }
+
+ /**
+ * This method is like `_.zip` except that it accepts an array of grouped
+ * elements and creates an array regrouping the elements to their pre-zip
+ * configuration.
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The array of grouped elements to process.
+ * @returns {Array} Returns the new array of regrouped elements.
+ * @example
+ *
+ * var zipped = _.zip(['fred', 'barney'], [30, 40], [true, false]);
+ * // => [['fred', 30, true], ['barney', 40, false]]
+ *
+ * _.unzip(zipped);
+ * // => [['fred', 'barney'], [30, 40], [true, false]]
+ */
+ function unzip(array) {
+ if (!(array && array.length)) {
+ return [];
+ }
+ var length = 0;
+ array = arrayFilter(array, function(group) {
+ if (isArrayLikeObject(group)) {
+ length = nativeMax(group.length, length);
+ return true;
+ }
+ });
+ return baseTimes(length, function(index) {
+ return arrayMap(array, baseProperty(index));
+ });
+ }
+
+ /**
+ * This method is like `_.unzip` except that it accepts `iteratee` to specify
+ * how regrouped values should be combined. The iteratee is invoked with the
+ * elements of each group: (...group).
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The array of grouped elements to process.
+ * @param {Function} [iteratee=_.identity] The function to combine regrouped values.
+ * @returns {Array} Returns the new array of regrouped elements.
+ * @example
+ *
+ * var zipped = _.zip([1, 2], [10, 20], [100, 200]);
+ * // => [[1, 10, 100], [2, 20, 200]]
+ *
+ * _.unzipWith(zipped, _.add);
+ * // => [3, 30, 300]
+ */
+ function unzipWith(array, iteratee) {
+ if (!(array && array.length)) {
+ return [];
+ }
+ var result = unzip(array);
+ if (iteratee == null) {
+ return result;
+ }
+ return arrayMap(result, function(group) {
+ return apply(iteratee, undefined, group);
+ });
+ }
+
+ /**
+ * Creates an array excluding all given values using
+ * [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+ * for equality comparisons.
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} array The array to filter.
+ * @param {...*} [values] The values to exclude.
+ * @returns {Array} Returns the new array of filtered values.
+ * @example
+ *
+ * _.without([1, 2, 1, 3], 1, 2);
+ * // => [3]
+ */
+ var without = rest(function(array, values) {
+ return isArrayLikeObject(array)
+ ? baseDifference(array, values)
+ : [];
+ });
+
+ /**
+ * Creates an array of unique values that is the [symmetric difference](https://en.wikipedia.org/wiki/Symmetric_difference)
+ * of the given arrays. The order of result values is determined by the order
+ * they occur in the arrays.
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {...Array} [arrays] The arrays to inspect.
+ * @returns {Array} Returns the new array of values.
+ * @example
+ *
+ * _.xor([2, 1], [4, 2]);
+ * // => [1, 4]
+ */
+ var xor = rest(function(arrays) {
+ return baseXor(arrayFilter(arrays, isArrayLikeObject));
+ });
+
+ /**
+ * This method is like `_.xor` except that it accepts `iteratee` which is
+ * invoked for each element of each `arrays` to generate the criterion by which
+ * by which they're compared. The iteratee is invoked with one argument: (value).
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {...Array} [arrays] The arrays to inspect.
+ * @param {Function|Object|string} [iteratee=_.identity] The iteratee invoked per element.
+ * @returns {Array} Returns the new array of values.
+ * @example
+ *
+ * _.xorBy([2.1, 1.2], [4.3, 2.4], Math.floor);
+ * // => [1.2, 4.3]
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.xorBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');
+ * // => [{ 'x': 2 }]
+ */
+ var xorBy = rest(function(arrays) {
+ var iteratee = last(arrays);
+ if (isArrayLikeObject(iteratee)) {
+ iteratee = undefined;
+ }
+ return baseXor(arrayFilter(arrays, isArrayLikeObject), getIteratee(iteratee));
+ });
+
+ /**
+ * This method is like `_.xor` except that it accepts `comparator` which is
+ * invoked to compare elements of `arrays`. The comparator is invoked with
+ * two arguments: (arrVal, othVal).
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {...Array} [arrays] The arrays to inspect.
+ * @param {Function} [comparator] The comparator invoked per element.
+ * @returns {Array} Returns the new array of values.
+ * @example
+ *
+ * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
+ * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];
+ *
+ * _.xorWith(objects, others, _.isEqual);
+ * // => [{ 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }]
+ */
+ var xorWith = rest(function(arrays) {
+ var comparator = last(arrays);
+ if (isArrayLikeObject(comparator)) {
+ comparator = undefined;
+ }
+ return baseXor(arrayFilter(arrays, isArrayLikeObject), undefined, comparator);
+ });
+
+ /**
+ * Creates an array of grouped elements, the first of which contains the first
+ * elements of the given arrays, the second of which contains the second elements
+ * of the given arrays, and so on.
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {...Array} [arrays] The arrays to process.
+ * @returns {Array} Returns the new array of grouped elements.
+ * @example
+ *
+ * _.zip(['fred', 'barney'], [30, 40], [true, false]);
+ * // => [['fred', 30, true], ['barney', 40, false]]
+ */
+ var zip = rest(unzip);
+
+ /**
+ * This method is like `_.fromPairs` except that it accepts two arrays,
+ * one of property names and one of corresponding values.
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} [props=[]] The property names.
+ * @param {Array} [values=[]] The property values.
+ * @returns {Object} Returns the new object.
+ * @example
+ *
+ * _.zipObject(['a', 'b'], [1, 2]);
+ * // => { 'a': 1, 'b': 2 }
+ */
+ function zipObject(props, values) {
+ return baseZipObject(props || [], values || [], assignValue);
+ }
+
+ /**
+ * This method is like `_.zipObject` except that it supports property paths.
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {Array} [props=[]] The property names.
+ * @param {Array} [values=[]] The property values.
+ * @returns {Object} Returns the new object.
+ * @example
+ *
+ * _.zipObjectDeep(['a.b[0].c', 'a.b[1].d'], [1, 2]);
+ * // => { 'a': { 'b': [{ 'c': 1 }, { 'd': 2 }] } }
+ */
+ function zipObjectDeep(props, values) {
+ return baseZipObject(props || [], values || [], baseSet);
+ }
+
+ /**
+ * This method is like `_.zip` except that it accepts `iteratee` to specify
+ * how grouped values should be combined. The iteratee is invoked with the
+ * elements of each group: (...group).
+ *
+ * @static
+ * @memberOf _
+ * @category Array
+ * @param {...Array} [arrays] The arrays to process.
+ * @param {Function} [iteratee=_.identity] The function to combine grouped values.
+ * @returns {Array} Returns the new array of grouped elements.
+ * @example
+ *
+ * _.zipWith([1, 2], [10, 20], [100, 200], function(a, b, c) {
+ * return a + b + c;
+ * });
+ * // => [111, 222]
+ */
+ var zipWith = rest(function(arrays) {
+ var length = arrays.length,
+ iteratee = length > 1 ? arrays[length - 1] : undefined;
+
+ iteratee = typeof iteratee == 'function' ? (arrays.pop(), iteratee) : undefined;
+ return unzipWith(arrays, iteratee);
+ });
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Creates a `lodash` object that wraps `value` with explicit method chaining enabled.
+ * The result of such method chaining must be unwrapped with `_#value`.
+ *
+ * @static
+ * @memberOf _
+ * @category Seq
+ * @param {*} value The value to wrap.
+ * @returns {Object} Returns the new `lodash` wrapper instance.
+ * @example
+ *
+ * var users = [
+ * { 'user': 'barney', 'age': 36 },
+ * { 'user': 'fred', 'age': 40 },
+ * { 'user': 'pebbles', 'age': 1 }
+ * ];
+ *
+ * var youngest = _
+ * .chain(users)
+ * .sortBy('age')
+ * .map(function(o) {
+ * return o.user + ' is ' + o.age;
+ * })
+ * .head()
+ * .value();
+ * // => 'pebbles is 1'
+ */
+ function chain(value) {
+ var result = lodash(value);
+ result.__chain__ = true;
+ return result;
+ }
+
+ /**
+ * This method invokes `interceptor` and returns `value`. The interceptor
+ * is invoked with one argument; (value). The purpose of this method is to
+ * "tap into" a method chain in order to modify intermediate results.
+ *
+ * @static
+ * @memberOf _
+ * @category Seq
+ * @param {*} value The value to provide to `interceptor`.
+ * @param {Function} interceptor The function to invoke.
+ * @returns {*} Returns `value`.
+ * @example
+ *
+ * _([1, 2, 3])
+ * .tap(function(array) {
+ * // Mutate input array.
+ * array.pop();
+ * })
+ * .reverse()
+ * .value();
+ * // => [2, 1]
+ */
+ function tap(value, interceptor) {
+ interceptor(value);
+ return value;
+ }
+
+ /**
+ * This method is like `_.tap` except that it returns the result of `interceptor`.
+ * The purpose of this method is to "pass thru" values replacing intermediate
+ * results in a method chain.
+ *
+ * @static
+ * @memberOf _
+ * @category Seq
+ * @param {*} value The value to provide to `interceptor`.
+ * @param {Function} interceptor The function to invoke.
+ * @returns {*} Returns the result of `interceptor`.
+ * @example
+ *
+ * _(' abc ')
+ * .chain()
+ * .trim()
+ * .thru(function(value) {
+ * return [value];
+ * })
+ * .value();
+ * // => ['abc']
+ */
+ function thru(value, interceptor) {
+ return interceptor(value);
+ }
+
+ /**
+ * This method is the wrapper version of `_.at`.
+ *
+ * @name at
+ * @memberOf _
+ * @category Seq
+ * @param {...(string|string[])} [paths] The property paths of elements to pick,
+ * specified individually or in arrays.
+ * @returns {Object} Returns the new `lodash` wrapper instance.
+ * @example
+ *
+ * var object = { 'a': [{ 'b': { 'c': 3 } }, 4] };
+ *
+ * _(object).at(['a[0].b.c', 'a[1]']).value();
+ * // => [3, 4]
+ *
+ * _(['a', 'b', 'c']).at(0, 2).value();
+ * // => ['a', 'c']
+ */
+ var wrapperAt = rest(function(paths) {
+ paths = baseFlatten(paths, 1);
+ var length = paths.length,
+ start = length ? paths[0] : 0,
+ value = this.__wrapped__,
+ interceptor = function(object) { return baseAt(object, paths); };
+
+ if (length > 1 || this.__actions__.length ||
+ !(value instanceof LazyWrapper) || !isIndex(start)) {
+ return this.thru(interceptor);
+ }
+ value = value.slice(start, +start + (length ? 1 : 0));
+ value.__actions__.push({
+ 'func': thru,
+ 'args': [interceptor],
+ 'thisArg': undefined
+ });
+ return new LodashWrapper(value, this.__chain__).thru(function(array) {
+ if (length && !array.length) {
+ array.push(undefined);
+ }
+ return array;
+ });
+ });
+
+ /**
+ * Enables explicit method chaining on the wrapper object.
+ *
+ * @name chain
+ * @memberOf _
+ * @category Seq
+ * @returns {Object} Returns the new `lodash` wrapper instance.
+ * @example
+ *
+ * var users = [
+ * { 'user': 'barney', 'age': 36 },
+ * { 'user': 'fred', 'age': 40 }
+ * ];
+ *
+ * // A sequence without explicit chaining.
+ * _(users).head();
+ * // => { 'user': 'barney', 'age': 36 }
+ *
+ * // A sequence with explicit chaining.
+ * _(users)
+ * .chain()
+ * .head()
+ * .pick('user')
+ * .value();
+ * // => { 'user': 'barney' }
+ */
+ function wrapperChain() {
+ return chain(this);
+ }
+
+ /**
+ * Executes the chained sequence and returns the wrapped result.
+ *
+ * @name commit
+ * @memberOf _
+ * @category Seq
+ * @returns {Object} Returns the new `lodash` wrapper instance.
+ * @example
+ *
+ * var array = [1, 2];
+ * var wrapped = _(array).push(3);
+ *
+ * console.log(array);
+ * // => [1, 2]
+ *
+ * wrapped = wrapped.commit();
+ * console.log(array);
+ * // => [1, 2, 3]
+ *
+ * wrapped.last();
+ * // => 3
+ *
+ * console.log(array);
+ * // => [1, 2, 3]
+ */
+ function wrapperCommit() {
+ return new LodashWrapper(this.value(), this.__chain__);
+ }
+
+ /**
+ * This method is the wrapper version of `_.flatMap`.
+ *
+ * @name flatMap
+ * @memberOf _
+ * @category Seq
+ * @param {Function|Object|string} [iteratee=_.identity] The function invoked per iteration.
+ * @returns {Object} Returns the new `lodash` wrapper instance.
+ * @example
+ *
+ * function duplicate(n) {
+ * return [n, n];
+ * }
+ *
+ * _([1, 2]).flatMap(duplicate).value();
+ * // => [1, 1, 2, 2]
+ */
+ function wrapperFlatMap(iteratee) {
+ return this.map(iteratee).flatten();
+ }
+
+ /**
+ * Gets the next value on a wrapped object following the
+ * [iterator protocol](https://mdn.io/iteration_protocols#iterator).
+ *
+ * @name next
+ * @memberOf _
+ * @category Seq
+ * @returns {Object} Returns the next iterator value.
+ * @example
+ *
+ * var wrapped = _([1, 2]);
+ *
+ * wrapped.next();
+ * // => { 'done': false, 'value': 1 }
+ *
+ * wrapped.next();
+ * // => { 'done': false, 'value': 2 }
+ *
+ * wrapped.next();
+ * // => { 'done': true, 'value': undefined }
+ */
+ function wrapperNext() {
+ if (this.__values__ === undefined) {
+ this.__values__ = toArray(this.value());
+ }
+ var done = this.__index__ >= this.__values__.length,
+ value = done ? undefined : this.__values__[this.__index__++];
+
+ return { 'done': done, 'value': value };
+ }
+
+ /**
+ * Enables the wrapper to be iterable.
+ *
+ * @name Symbol.iterator
+ * @memberOf _
+ * @category Seq
+ * @returns {Object} Returns the wrapper object.
+ * @example
+ *
+ * var wrapped = _([1, 2]);
+ *
+ * wrapped[Symbol.iterator]() === wrapped;
+ * // => true
+ *
+ * Array.from(wrapped);
+ * // => [1, 2]
+ */
+ function wrapperToIterator() {
+ return this;
+ }
+
+ /**
+ * Creates a clone of the chained sequence planting `value` as the wrapped value.
+ *
+ * @name plant
+ * @memberOf _
+ * @category Seq
+ * @param {*} value The value to plant.
+ * @returns {Object} Returns the new `lodash` wrapper instance.
+ * @example
+ *
+ * function square(n) {
+ * return n * n;
+ * }
+ *
+ * var wrapped = _([1, 2]).map(square);
+ * var other = wrapped.plant([3, 4]);
+ *
+ * other.value();
+ * // => [9, 16]
+ *
+ * wrapped.value();
+ * // => [1, 4]
+ */
+ function wrapperPlant(value) {
+ var result,
+ parent = this;
+
+ while (parent instanceof baseLodash) {
+ var clone = wrapperClone(parent);
+ clone.__index__ = 0;
+ clone.__values__ = undefined;
+ if (result) {
+ previous.__wrapped__ = clone;
+ } else {
+ result = clone;
+ }
+ var previous = clone;
+ parent = parent.__wrapped__;
+ }
+ previous.__wrapped__ = value;
+ return result;
+ }
+
+ /**
+ * This method is the wrapper version of `_.reverse`.
+ *
+ * **Note:** This method mutates the wrapped array.
+ *
+ * @name reverse
+ * @memberOf _
+ * @category Seq
+ * @returns {Object} Returns the new `lodash` wrapper instance.
+ * @example
+ *
+ * var array = [1, 2, 3];
+ *
+ * _(array).reverse().value()
+ * // => [3, 2, 1]
+ *
+ * console.log(array);
+ * // => [3, 2, 1]
+ */
+ function wrapperReverse() {
+ var value = this.__wrapped__;
+ if (value instanceof LazyWrapper) {
+ var wrapped = value;
+ if (this.__actions__.length) {
+ wrapped = new LazyWrapper(this);
+ }
+ wrapped = wrapped.reverse();
+ wrapped.__actions__.push({
+ 'func': thru,
+ 'args': [reverse],
+ 'thisArg': undefined
+ });
+ return new LodashWrapper(wrapped, this.__chain__);
+ }
+ return this.thru(reverse);
+ }
+
+ /**
+ * Executes the chained sequence to extract the unwrapped value.
+ *
+ * @name value
+ * @memberOf _
+ * @alias toJSON, valueOf
+ * @category Seq
+ * @returns {*} Returns the resolved unwrapped value.
+ * @example
+ *
+ * _([1, 2, 3]).value();
+ * // => [1, 2, 3]
+ */
+ function wrapperValue() {
+ return baseWrapperValue(this.__wrapped__, this.__actions__);
+ }
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Creates an object composed of keys generated from the results of running
+ * each element of `collection` through `iteratee`. The corresponding value
+ * of each key is the number of times the key was returned by `iteratee`.
+ * The iteratee is invoked with one argument: (value).
+ *
+ * @static
+ * @memberOf _
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function|Object|string} [iteratee=_.identity] The iteratee to transform keys.
+ * @returns {Object} Returns the composed aggregate object.
+ * @example
+ *
+ * _.countBy([6.1, 4.2, 6.3], Math.floor);
+ * // => { '4': 1, '6': 2 }
+ *
+ * _.countBy(['one', 'two', 'three'], 'length');
+ * // => { '3': 2, '5': 1 }
+ */
+ var countBy = createAggregator(function(result, value, key) {
+ hasOwnProperty.call(result, key) ? ++result[key] : (result[key] = 1);
+ });
+
+ /**
+ * Checks if `predicate` returns truthy for **all** elements of `collection`.
+ * Iteration is stopped once `predicate` returns falsey. The predicate is
+ * invoked with three arguments: (value, index|key, collection).
+ *
+ * @static
+ * @memberOf _
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function|Object|string} [predicate=_.identity] The function invoked per iteration.
+ * @param- {Object} [guard] Enables use as an iteratee for functions like `_.map`.
+ * @returns {boolean} Returns `true` if all elements pass the predicate check, else `false`.
+ * @example
+ *
+ * _.every([true, 1, null, 'yes'], Boolean);
+ * // => false
+ *
+ * var users = [
+ * { 'user': 'barney', 'active': false },
+ * { 'user': 'fred', 'active': false }
+ * ];
+ *
+ * // The `_.matches` iteratee shorthand.
+ * _.every(users, { 'user': 'barney', 'active': false });
+ * // => false
+ *
+ * // The `_.matchesProperty` iteratee shorthand.
+ * _.every(users, ['active', false]);
+ * // => true
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.every(users, 'active');
+ * // => false
+ */
+ function every(collection, predicate, guard) {
+ var func = isArray(collection) ? arrayEvery : baseEvery;
+ if (guard && isIterateeCall(collection, predicate, guard)) {
+ predicate = undefined;
+ }
+ return func(collection, getIteratee(predicate, 3));
+ }
+
+ /**
+ * Iterates over elements of `collection`, returning an array of all elements
+ * `predicate` returns truthy for. The predicate is invoked with three arguments:
+ * (value, index|key, collection).
+ *
+ * @static
+ * @memberOf _
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function|Object|string} [predicate=_.identity] The function invoked per iteration.
+ * @returns {Array} Returns the new filtered array.
+ * @example
+ *
+ * var users = [
+ * { 'user': 'barney', 'age': 36, 'active': true },
+ * { 'user': 'fred', 'age': 40, 'active': false }
+ * ];
+ *
+ * _.filter(users, function(o) { return !o.active; });
+ * // => objects for ['fred']
+ *
+ * // The `_.matches` iteratee shorthand.
+ * _.filter(users, { 'age': 36, 'active': true });
+ * // => objects for ['barney']
+ *
+ * // The `_.matchesProperty` iteratee shorthand.
+ * _.filter(users, ['active', false]);
+ * // => objects for ['fred']
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.filter(users, 'active');
+ * // => objects for ['barney']
+ */
+ function filter(collection, predicate) {
+ var func = isArray(collection) ? arrayFilter : baseFilter;
+ return func(collection, getIteratee(predicate, 3));
+ }
+
+ /**
+ * Iterates over elements of `collection`, returning the first element
+ * `predicate` returns truthy for. The predicate is invoked with three arguments:
+ * (value, index|key, collection).
+ *
+ * @static
+ * @memberOf _
+ * @category Collection
+ * @param {Array|Object} collection The collection to search.
+ * @param {Function|Object|string} [predicate=_.identity] The function invoked per iteration.
+ * @returns {*} Returns the matched element, else `undefined`.
+ * @example
+ *
+ * var users = [
+ * { 'user': 'barney', 'age': 36, 'active': true },
+ * { 'user': 'fred', 'age': 40, 'active': false },
+ * { 'user': 'pebbles', 'age': 1, 'active': true }
+ * ];
+ *
+ * _.find(users, function(o) { return o.age < 40; });
+ * // => object for 'barney'
+ *
+ * // The `_.matches` iteratee shorthand.
+ * _.find(users, { 'age': 1, 'active': true });
+ * // => object for 'pebbles'
+ *
+ * // The `_.matchesProperty` iteratee shorthand.
+ * _.find(users, ['active', false]);
+ * // => object for 'fred'
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.find(users, 'active');
+ * // => object for 'barney'
+ */
+ function find(collection, predicate) {
+ predicate = getIteratee(predicate, 3);
+ if (isArray(collection)) {
+ var index = baseFindIndex(collection, predicate);
+ return index > -1 ? collection[index] : undefined;
+ }
+ return baseFind(collection, predicate, baseEach);
+ }
+
+ /**
+ * This method is like `_.find` except that it iterates over elements of
+ * `collection` from right to left.
+ *
+ * @static
+ * @memberOf _
+ * @category Collection
+ * @param {Array|Object} collection The collection to search.
+ * @param {Function|Object|string} [predicate=_.identity] The function invoked per iteration.
+ * @returns {*} Returns the matched element, else `undefined`.
+ * @example
+ *
+ * _.findLast([1, 2, 3, 4], function(n) {
+ * return n % 2 == 1;
+ * });
+ * // => 3
+ */
+ function findLast(collection, predicate) {
+ predicate = getIteratee(predicate, 3);
+ if (isArray(collection)) {
+ var index = baseFindIndex(collection, predicate, true);
+ return index > -1 ? collection[index] : undefined;
+ }
+ return baseFind(collection, predicate, baseEachRight);
+ }
+
+ /**
+ * Creates an array of flattened values by running each element in `collection`
+ * through `iteratee` and concating its result to the other mapped values.
+ * The iteratee is invoked with three arguments: (value, index|key, collection).
+ *
+ * @static
+ * @memberOf _
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function|Object|string} [iteratee=_.identity] The function invoked per iteration.
+ * @returns {Array} Returns the new flattened array.
+ * @example
+ *
+ * function duplicate(n) {
+ * return [n, n];
+ * }
+ *
+ * _.flatMap([1, 2], duplicate);
+ * // => [1, 1, 2, 2]
+ */
+ function flatMap(collection, iteratee) {
+ return baseFlatten(map(collection, iteratee), 1);
+ }
+
+ /**
+ * Iterates over elements of `collection` invoking `iteratee` for each element.
+ * The iteratee is invoked with three arguments: (value, index|key, collection).
+ * Iteratee functions may exit iteration early by explicitly returning `false`.
+ *
+ * **Note:** As with other "Collections" methods, objects with a "length" property
+ * are iterated like arrays. To avoid this behavior use `_.forIn` or `_.forOwn`
+ * for object iteration.
+ *
+ * @static
+ * @memberOf _
+ * @alias each
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+ * @returns {Array|Object} Returns `collection`.
+ * @example
+ *
+ * _([1, 2]).forEach(function(value) {
+ * console.log(value);
+ * });
+ * // => logs `1` then `2`
+ *
+ * _.forEach({ 'a': 1, 'b': 2 }, function(value, key) {
+ * console.log(key);
+ * });
+ * // => logs 'a' then 'b' (iteration order is not guaranteed)
+ */
+ function forEach(collection, iteratee) {
+ return (typeof iteratee == 'function' && isArray(collection))
+ ? arrayEach(collection, iteratee)
+ : baseEach(collection, baseCastFunction(iteratee));
+ }
+
+ /**
+ * This method is like `_.forEach` except that it iterates over elements of
+ * `collection` from right to left.
+ *
+ * @static
+ * @memberOf _
+ * @alias eachRight
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+ * @returns {Array|Object} Returns `collection`.
+ * @example
+ *
+ * _.forEachRight([1, 2], function(value) {
+ * console.log(value);
+ * });
+ * // => logs `2` then `1`
+ */
+ function forEachRight(collection, iteratee) {
+ return (typeof iteratee == 'function' && isArray(collection))
+ ? arrayEachRight(collection, iteratee)
+ : baseEachRight(collection, baseCastFunction(iteratee));
+ }
+
+ /**
+ * Creates an object composed of keys generated from the results of running
+ * each element of `collection` through `iteratee`. The corresponding value
+ * of each key is an array of elements responsible for generating the key.
+ * The iteratee is invoked with one argument: (value).
+ *
+ * @static
+ * @memberOf _
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function|Object|string} [iteratee=_.identity] The iteratee to transform keys.
+ * @returns {Object} Returns the composed aggregate object.
+ * @example
+ *
+ * _.groupBy([6.1, 4.2, 6.3], Math.floor);
+ * // => { '4': [4.2], '6': [6.1, 6.3] }
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.groupBy(['one', 'two', 'three'], 'length');
+ * // => { '3': ['one', 'two'], '5': ['three'] }
+ */
+ var groupBy = createAggregator(function(result, value, key) {
+ if (hasOwnProperty.call(result, key)) {
+ result[key].push(value);
+ } else {
+ result[key] = [value];
+ }
+ });
+
+ /**
+ * Checks if `value` is in `collection`. If `collection` is a string it's checked
+ * for a substring of `value`, otherwise [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+ * is used for equality comparisons. If `fromIndex` is negative, it's used as
+ * the offset from the end of `collection`.
+ *
+ * @static
+ * @memberOf _
+ * @category Collection
+ * @param {Array|Object|string} collection The collection to search.
+ * @param {*} value The value to search for.
+ * @param {number} [fromIndex=0] The index to search from.
+ * @param- {Object} [guard] Enables use as an iteratee for functions like `_.reduce`.
+ * @returns {boolean} Returns `true` if `value` is found, else `false`.
+ * @example
+ *
+ * _.includes([1, 2, 3], 1);
+ * // => true
+ *
+ * _.includes([1, 2, 3], 1, 2);
+ * // => false
+ *
+ * _.includes({ 'user': 'fred', 'age': 40 }, 'fred');
+ * // => true
+ *
+ * _.includes('pebbles', 'eb');
+ * // => true
+ */
+ function includes(collection, value, fromIndex, guard) {
+ collection = isArrayLike(collection) ? collection : values(collection);
+ fromIndex = (fromIndex && !guard) ? toInteger(fromIndex) : 0;
+
+ var length = collection.length;
+ if (fromIndex < 0) {
+ fromIndex = nativeMax(length + fromIndex, 0);
+ }
+ return isString(collection)
+ ? (fromIndex <= length && collection.indexOf(value, fromIndex) > -1)
+ : (!!length && baseIndexOf(collection, value, fromIndex) > -1);
+ }
+
+ /**
+ * Invokes the method at `path` of each element in `collection`, returning
+ * an array of the results of each invoked method. Any additional arguments
+ * are provided to each invoked method. If `methodName` is a function it's
+ * invoked for, and `this` bound to, each element in `collection`.
+ *
+ * @static
+ * @memberOf _
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Array|Function|string} path The path of the method to invoke or
+ * the function invoked per iteration.
+ * @param {...*} [args] The arguments to invoke each method with.
+ * @returns {Array} Returns the array of results.
+ * @example
+ *
+ * _.invokeMap([[5, 1, 7], [3, 2, 1]], 'sort');
+ * // => [[1, 5, 7], [1, 2, 3]]
+ *
+ * _.invokeMap([123, 456], String.prototype.split, '');
+ * // => [['1', '2', '3'], ['4', '5', '6']]
+ */
+ var invokeMap = rest(function(collection, path, args) {
+ var index = -1,
+ isFunc = typeof path == 'function',
+ isProp = isKey(path),
+ result = isArrayLike(collection) ? Array(collection.length) : [];
+
+ baseEach(collection, function(value) {
+ var func = isFunc ? path : ((isProp && value != null) ? value[path] : undefined);
+ result[++index] = func ? apply(func, value, args) : baseInvoke(value, path, args);
+ });
+ return result;
+ });
+
+ /**
+ * Creates an object composed of keys generated from the results of running
+ * each element of `collection` through `iteratee`. The corresponding value
+ * of each key is the last element responsible for generating the key. The
+ * iteratee is invoked with one argument: (value).
+ *
+ * @static
+ * @memberOf _
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function|Object|string} [iteratee=_.identity] The iteratee to transform keys.
+ * @returns {Object} Returns the composed aggregate object.
+ * @example
+ *
+ * var array = [
+ * { 'dir': 'left', 'code': 97 },
+ * { 'dir': 'right', 'code': 100 }
+ * ];
+ *
+ * _.keyBy(array, function(o) {
+ * return String.fromCharCode(o.code);
+ * });
+ * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } }
+ *
+ * _.keyBy(array, 'dir');
+ * // => { 'left': { 'dir': 'left', 'code': 97 }, 'right': { 'dir': 'right', 'code': 100 } }
+ */
+ var keyBy = createAggregator(function(result, value, key) {
+ result[key] = value;
+ });
+
+ /**
+ * Creates an array of values by running each element in `collection` through
+ * `iteratee`. The iteratee is invoked with three arguments:
+ * (value, index|key, collection).
+ *
+ * Many lodash methods are guarded to work as iteratees for methods like
+ * `_.every`, `_.filter`, `_.map`, `_.mapValues`, `_.reject`, and `_.some`.
+ *
+ * The guarded methods are:
+ * `ary`, `curry`, `curryRight`, `drop`, `dropRight`, `every`, `fill`,
+ * `invert`, `parseInt`, `random`, `range`, `rangeRight`, `slice`, `some`,
+ * `sortBy`, `take`, `takeRight`, `template`, `trim`, `trimEnd`, `trimStart`,
+ * and `words`
+ *
+ * @static
+ * @memberOf _
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function|Object|string} [iteratee=_.identity] The function invoked per iteration.
+ * @returns {Array} Returns the new mapped array.
+ * @example
+ *
+ * function square(n) {
+ * return n * n;
+ * }
+ *
+ * _.map([4, 8], square);
+ * // => [16, 64]
+ *
+ * _.map({ 'a': 4, 'b': 8 }, square);
+ * // => [16, 64] (iteration order is not guaranteed)
+ *
+ * var users = [
+ * { 'user': 'barney' },
+ * { 'user': 'fred' }
+ * ];
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.map(users, 'user');
+ * // => ['barney', 'fred']
+ */
+ function map(collection, iteratee) {
+ var func = isArray(collection) ? arrayMap : baseMap;
+ return func(collection, getIteratee(iteratee, 3));
+ }
+
+ /**
+ * This method is like `_.sortBy` except that it allows specifying the sort
+ * orders of the iteratees to sort by. If `orders` is unspecified, all values
+ * are sorted in ascending order. Otherwise, specify an order of "desc" for
+ * descending or "asc" for ascending sort order of corresponding values.
+ *
+ * @static
+ * @memberOf _
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function[]|Object[]|string[]} [iteratees=[_.identity]] The iteratees to sort by.
+ * @param {string[]} [orders] The sort orders of `iteratees`.
+ * @param- {Object} [guard] Enables use as an iteratee for functions like `_.reduce`.
+ * @returns {Array} Returns the new sorted array.
+ * @example
+ *
+ * var users = [
+ * { 'user': 'fred', 'age': 48 },
+ * { 'user': 'barney', 'age': 34 },
+ * { 'user': 'fred', 'age': 42 },
+ * { 'user': 'barney', 'age': 36 }
+ * ];
+ *
+ * // Sort by `user` in ascending order and by `age` in descending order.
+ * _.orderBy(users, ['user', 'age'], ['asc', 'desc']);
+ * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 42]]
+ */
+ function orderBy(collection, iteratees, orders, guard) {
+ if (collection == null) {
+ return [];
+ }
+ if (!isArray(iteratees)) {
+ iteratees = iteratees == null ? [] : [iteratees];
+ }
+ orders = guard ? undefined : orders;
+ if (!isArray(orders)) {
+ orders = orders == null ? [] : [orders];
+ }
+ return baseOrderBy(collection, iteratees, orders);
+ }
+
+ /**
+ * Creates an array of elements split into two groups, the first of which
+ * contains elements `predicate` returns truthy for, the second of which
+ * contains elements `predicate` returns falsey for. The predicate is
+ * invoked with one argument: (value).
+ *
+ * @static
+ * @memberOf _
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function|Object|string} [predicate=_.identity] The function invoked per iteration.
+ * @returns {Array} Returns the array of grouped elements.
+ * @example
+ *
+ * var users = [
+ * { 'user': 'barney', 'age': 36, 'active': false },
+ * { 'user': 'fred', 'age': 40, 'active': true },
+ * { 'user': 'pebbles', 'age': 1, 'active': false }
+ * ];
+ *
+ * _.partition(users, function(o) { return o.active; });
+ * // => objects for [['fred'], ['barney', 'pebbles']]
+ *
+ * // The `_.matches` iteratee shorthand.
+ * _.partition(users, { 'age': 1, 'active': false });
+ * // => objects for [['pebbles'], ['barney', 'fred']]
+ *
+ * // The `_.matchesProperty` iteratee shorthand.
+ * _.partition(users, ['active', false]);
+ * // => objects for [['barney', 'pebbles'], ['fred']]
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.partition(users, 'active');
+ * // => objects for [['fred'], ['barney', 'pebbles']]
+ */
+ var partition = createAggregator(function(result, value, key) {
+ result[key ? 0 : 1].push(value);
+ }, function() { return [[], []]; });
+
+ /**
+ * Reduces `collection` to a value which is the accumulated result of running
+ * each element in `collection` through `iteratee`, where each successive
+ * invocation is supplied the return value of the previous. If `accumulator`
+ * is not given the first element of `collection` is used as the initial
+ * value. The iteratee is invoked with four arguments:
+ * (accumulator, value, index|key, collection).
+ *
+ * Many lodash methods are guarded to work as iteratees for methods like
+ * `_.reduce`, `_.reduceRight`, and `_.transform`.
+ *
+ * The guarded methods are:
+ * `assign`, `defaults`, `defaultsDeep`, `includes`, `merge`, `orderBy`,
+ * and `sortBy`
+ *
+ * @static
+ * @memberOf _
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+ * @param {*} [accumulator] The initial value.
+ * @returns {*} Returns the accumulated value.
+ * @example
+ *
+ * _.reduce([1, 2], function(sum, n) {
+ * return sum + n;
+ * }, 0);
+ * // => 3
+ *
+ * _.reduce({ 'a': 1, 'b': 2, 'c': 1 }, function(result, value, key) {
+ * (result[value] || (result[value] = [])).push(key);
+ * return result;
+ * }, {});
+ * // => { '1': ['a', 'c'], '2': ['b'] } (iteration order is not guaranteed)
+ */
+ function reduce(collection, iteratee, accumulator) {
+ var func = isArray(collection) ? arrayReduce : baseReduce,
+ initAccum = arguments.length < 3;
+
+ return func(collection, getIteratee(iteratee, 4), accumulator, initAccum, baseEach);
+ }
+
+ /**
+ * This method is like `_.reduce` except that it iterates over elements of
+ * `collection` from right to left.
+ *
+ * @static
+ * @memberOf _
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+ * @param {*} [accumulator] The initial value.
+ * @returns {*} Returns the accumulated value.
+ * @example
+ *
+ * var array = [[0, 1], [2, 3], [4, 5]];
+ *
+ * _.reduceRight(array, function(flattened, other) {
+ * return flattened.concat(other);
+ * }, []);
+ * // => [4, 5, 2, 3, 0, 1]
+ */
+ function reduceRight(collection, iteratee, accumulator) {
+ var func = isArray(collection) ? arrayReduceRight : baseReduce,
+ initAccum = arguments.length < 3;
+
+ return func(collection, getIteratee(iteratee, 4), accumulator, initAccum, baseEachRight);
+ }
+
+ /**
+ * The opposite of `_.filter`; this method returns the elements of `collection`
+ * that `predicate` does **not** return truthy for.
+ *
+ * @static
+ * @memberOf _
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function|Object|string} [predicate=_.identity] The function invoked per iteration.
+ * @returns {Array} Returns the new filtered array.
+ * @example
+ *
+ * var users = [
+ * { 'user': 'barney', 'age': 36, 'active': false },
+ * { 'user': 'fred', 'age': 40, 'active': true }
+ * ];
+ *
+ * _.reject(users, function(o) { return !o.active; });
+ * // => objects for ['fred']
+ *
+ * // The `_.matches` iteratee shorthand.
+ * _.reject(users, { 'age': 40, 'active': true });
+ * // => objects for ['barney']
+ *
+ * // The `_.matchesProperty` iteratee shorthand.
+ * _.reject(users, ['active', false]);
+ * // => objects for ['fred']
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.reject(users, 'active');
+ * // => objects for ['barney']
+ */
+ function reject(collection, predicate) {
+ var func = isArray(collection) ? arrayFilter : baseFilter;
+ predicate = getIteratee(predicate, 3);
+ return func(collection, function(value, index, collection) {
+ return !predicate(value, index, collection);
+ });
+ }
+
+ /**
+ * Gets a random element from `collection`.
+ *
+ * @static
+ * @memberOf _
+ * @category Collection
+ * @param {Array|Object} collection The collection to sample.
+ * @returns {*} Returns the random element.
+ * @example
+ *
+ * _.sample([1, 2, 3, 4]);
+ * // => 2
+ */
+ function sample(collection) {
+ var array = isArrayLike(collection) ? collection : values(collection),
+ length = array.length;
+
+ return length > 0 ? array[baseRandom(0, length - 1)] : undefined;
+ }
+
+ /**
+ * Gets `n` random elements at unique keys from `collection` up to the
+ * size of `collection`.
+ *
+ * @static
+ * @memberOf _
+ * @category Collection
+ * @param {Array|Object} collection The collection to sample.
+ * @param {number} [n=0] The number of elements to sample.
+ * @returns {Array} Returns the random elements.
+ * @example
+ *
+ * _.sampleSize([1, 2, 3], 2);
+ * // => [3, 1]
+ *
+ * _.sampleSize([1, 2, 3], 4);
+ * // => [2, 3, 1]
+ */
+ function sampleSize(collection, n) {
+ var index = -1,
+ result = toArray(collection),
+ length = result.length,
+ lastIndex = length - 1;
+
+ n = baseClamp(toInteger(n), 0, length);
+ while (++index < n) {
+ var rand = baseRandom(index, lastIndex),
+ value = result[rand];
+
+ result[rand] = result[index];
+ result[index] = value;
+ }
+ result.length = n;
+ return result;
+ }
+
+ /**
+ * Creates an array of shuffled values, using a version of the
+ * [Fisher-Yates shuffle](https://en.wikipedia.org/wiki/Fisher-Yates_shuffle).
+ *
+ * @static
+ * @memberOf _
+ * @category Collection
+ * @param {Array|Object} collection The collection to shuffle.
+ * @returns {Array} Returns the new shuffled array.
+ * @example
+ *
+ * _.shuffle([1, 2, 3, 4]);
+ * // => [4, 1, 3, 2]
+ */
+ function shuffle(collection) {
+ return sampleSize(collection, MAX_ARRAY_LENGTH);
+ }
+
+ /**
+ * Gets the size of `collection` by returning its length for array-like
+ * values or the number of own enumerable properties for objects.
+ *
+ * @static
+ * @memberOf _
+ * @category Collection
+ * @param {Array|Object} collection The collection to inspect.
+ * @returns {number} Returns the collection size.
+ * @example
+ *
+ * _.size([1, 2, 3]);
+ * // => 3
+ *
+ * _.size({ 'a': 1, 'b': 2 });
+ * // => 2
+ *
+ * _.size('pebbles');
+ * // => 7
+ */
+ function size(collection) {
+ if (collection == null) {
+ return 0;
+ }
+ if (isArrayLike(collection)) {
+ var result = collection.length;
+ return (result && isString(collection)) ? stringSize(collection) : result;
+ }
+ return keys(collection).length;
+ }
+
+ /**
+ * Checks if `predicate` returns truthy for **any** element of `collection`.
+ * Iteration is stopped once `predicate` returns truthy. The predicate is
+ * invoked with three arguments: (value, index|key, collection).
+ *
+ * @static
+ * @memberOf _
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function|Object|string} [predicate=_.identity] The function invoked per iteration.
+ * @param- {Object} [guard] Enables use as an iteratee for functions like `_.map`.
+ * @returns {boolean} Returns `true` if any element passes the predicate check, else `false`.
+ * @example
+ *
+ * _.some([null, 0, 'yes', false], Boolean);
+ * // => true
+ *
+ * var users = [
+ * { 'user': 'barney', 'active': true },
+ * { 'user': 'fred', 'active': false }
+ * ];
+ *
+ * // The `_.matches` iteratee shorthand.
+ * _.some(users, { 'user': 'barney', 'active': false });
+ * // => false
+ *
+ * // The `_.matchesProperty` iteratee shorthand.
+ * _.some(users, ['active', false]);
+ * // => true
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.some(users, 'active');
+ * // => true
+ */
+ function some(collection, predicate, guard) {
+ var func = isArray(collection) ? arraySome : baseSome;
+ if (guard && isIterateeCall(collection, predicate, guard)) {
+ predicate = undefined;
+ }
+ return func(collection, getIteratee(predicate, 3));
+ }
+
+ /**
+ * Creates an array of elements, sorted in ascending order by the results of
+ * running each element in a collection through each iteratee. This method
+ * performs a stable sort, that is, it preserves the original sort order of
+ * equal elements. The iteratees are invoked with one argument: (value).
+ *
+ * @static
+ * @memberOf _
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {...(Function|Function[]|Object|Object[]|string|string[])} [iteratees=[_.identity]]
+ * The iteratees to sort by, specified individually or in arrays.
+ * @returns {Array} Returns the new sorted array.
+ * @example
+ *
+ * var users = [
+ * { 'user': 'fred', 'age': 48 },
+ * { 'user': 'barney', 'age': 36 },
+ * { 'user': 'fred', 'age': 42 },
+ * { 'user': 'barney', 'age': 34 }
+ * ];
+ *
+ * _.sortBy(users, function(o) { return o.user; });
+ * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 42]]
+ *
+ * _.sortBy(users, ['user', 'age']);
+ * // => objects for [['barney', 34], ['barney', 36], ['fred', 42], ['fred', 48]]
+ *
+ * _.sortBy(users, 'user', function(o) {
+ * return Math.floor(o.age / 10);
+ * });
+ * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 42]]
+ */
+ var sortBy = rest(function(collection, iteratees) {
+ if (collection == null) {
+ return [];
+ }
+ var length = iteratees.length;
+ if (length > 1 && isIterateeCall(collection, iteratees[0], iteratees[1])) {
+ iteratees = [];
+ } else if (length > 2 && isIterateeCall(iteratees[0], iteratees[1], iteratees[2])) {
+ iteratees.length = 1;
+ }
+ return baseOrderBy(collection, baseFlatten(iteratees, 1), []);
+ });
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Gets the timestamp of the number of milliseconds that have elapsed since
+ * the Unix epoch (1 January 1970 00:00:00 UTC).
+ *
+ * @static
+ * @memberOf _
+ * @type {Function}
+ * @category Date
+ * @returns {number} Returns the timestamp.
+ * @example
+ *
+ * _.defer(function(stamp) {
+ * console.log(_.now() - stamp);
+ * }, _.now());
+ * // => logs the number of milliseconds it took for the deferred function to be invoked
+ */
+ var now = Date.now;
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * The opposite of `_.before`; this method creates a function that invokes
+ * `func` once it's called `n` or more times.
+ *
+ * @static
+ * @memberOf _
+ * @category Function
+ * @param {number} n The number of calls before `func` is invoked.
+ * @param {Function} func The function to restrict.
+ * @returns {Function} Returns the new restricted function.
+ * @example
+ *
+ * var saves = ['profile', 'settings'];
+ *
+ * var done = _.after(saves.length, function() {
+ * console.log('done saving!');
+ * });
+ *
+ * _.forEach(saves, function(type) {
+ * asyncSave({ 'type': type, 'complete': done });
+ * });
+ * // => logs 'done saving!' after the two async saves have completed
+ */
+ function after(n, func) {
+ if (typeof func != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
+ n = toInteger(n);
+ return function() {
+ if (--n < 1) {
+ return func.apply(this, arguments);
+ }
+ };
+ }
+
+ /**
+ * Creates a function that accepts up to `n` arguments, ignoring any
+ * additional arguments.
+ *
+ * @static
+ * @memberOf _
+ * @category Function
+ * @param {Function} func The function to cap arguments for.
+ * @param {number} [n=func.length] The arity cap.
+ * @param- {Object} [guard] Enables use as an iteratee for functions like `_.map`.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * _.map(['6', '8', '10'], _.ary(parseInt, 1));
+ * // => [6, 8, 10]
+ */
+ function ary(func, n, guard) {
+ n = guard ? undefined : n;
+ n = (func && n == null) ? func.length : n;
+ return createWrapper(func, ARY_FLAG, undefined, undefined, undefined, undefined, n);
+ }
+
+ /**
+ * Creates a function that invokes `func`, with the `this` binding and arguments
+ * of the created function, while it's called less than `n` times. Subsequent
+ * calls to the created function return the result of the last `func` invocation.
+ *
+ * @static
+ * @memberOf _
+ * @category Function
+ * @param {number} n The number of calls at which `func` is no longer invoked.
+ * @param {Function} func The function to restrict.
+ * @returns {Function} Returns the new restricted function.
+ * @example
+ *
+ * jQuery(element).on('click', _.before(5, addContactToList));
+ * // => allows adding up to 4 contacts to the list
+ */
+ function before(n, func) {
+ var result;
+ if (typeof func != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
+ n = toInteger(n);
+ return function() {
+ if (--n > 0) {
+ result = func.apply(this, arguments);
+ }
+ if (n <= 1) {
+ func = undefined;
+ }
+ return result;
+ };
+ }
+
+ /**
+ * Creates a function that invokes `func` with the `this` binding of `thisArg`
+ * and prepends any additional `_.bind` arguments to those provided to the
+ * bound function.
+ *
+ * The `_.bind.placeholder` value, which defaults to `_` in monolithic builds,
+ * may be used as a placeholder for partially applied arguments.
+ *
+ * **Note:** Unlike native `Function#bind` this method doesn't set the "length"
+ * property of bound functions.
+ *
+ * @static
+ * @memberOf _
+ * @category Function
+ * @param {Function} func The function to bind.
+ * @param {*} thisArg The `this` binding of `func`.
+ * @param {...*} [partials] The arguments to be partially applied.
+ * @returns {Function} Returns the new bound function.
+ * @example
+ *
+ * var greet = function(greeting, punctuation) {
+ * return greeting + ' ' + this.user + punctuation;
+ * };
+ *
+ * var object = { 'user': 'fred' };
+ *
+ * var bound = _.bind(greet, object, 'hi');
+ * bound('!');
+ * // => 'hi fred!'
+ *
+ * // Bound with placeholders.
+ * var bound = _.bind(greet, object, _, '!');
+ * bound('hi');
+ * // => 'hi fred!'
+ */
+ var bind = rest(function(func, thisArg, partials) {
+ var bitmask = BIND_FLAG;
+ if (partials.length) {
+ var holders = replaceHolders(partials, getPlaceholder(bind));
+ bitmask |= PARTIAL_FLAG;
+ }
+ return createWrapper(func, bitmask, thisArg, partials, holders);
+ });
+
+ /**
+ * Creates a function that invokes the method at `object[key]` and prepends
+ * any additional `_.bindKey` arguments to those provided to the bound function.
+ *
+ * This method differs from `_.bind` by allowing bound functions to reference
+ * methods that may be redefined or don't yet exist.
+ * See [Peter Michaux's article](http://peter.michaux.ca/articles/lazy-function-definition-pattern)
+ * for more details.
+ *
+ * The `_.bindKey.placeholder` value, which defaults to `_` in monolithic
+ * builds, may be used as a placeholder for partially applied arguments.
+ *
+ * @static
+ * @memberOf _
+ * @category Function
+ * @param {Object} object The object to invoke the method on.
+ * @param {string} key The key of the method.
+ * @param {...*} [partials] The arguments to be partially applied.
+ * @returns {Function} Returns the new bound function.
+ * @example
+ *
+ * var object = {
+ * 'user': 'fred',
+ * 'greet': function(greeting, punctuation) {
+ * return greeting + ' ' + this.user + punctuation;
+ * }
+ * };
+ *
+ * var bound = _.bindKey(object, 'greet', 'hi');
+ * bound('!');
+ * // => 'hi fred!'
+ *
+ * object.greet = function(greeting, punctuation) {
+ * return greeting + 'ya ' + this.user + punctuation;
+ * };
+ *
+ * bound('!');
+ * // => 'hiya fred!'
+ *
+ * // Bound with placeholders.
+ * var bound = _.bindKey(object, 'greet', _, '!');
+ * bound('hi');
+ * // => 'hiya fred!'
+ */
+ var bindKey = rest(function(object, key, partials) {
+ var bitmask = BIND_FLAG | BIND_KEY_FLAG;
+ if (partials.length) {
+ var holders = replaceHolders(partials, getPlaceholder(bindKey));
+ bitmask |= PARTIAL_FLAG;
+ }
+ return createWrapper(key, bitmask, object, partials, holders);
+ });
+
+ /**
+ * Creates a function that accepts arguments of `func` and either invokes
+ * `func` returning its result, if at least `arity` number of arguments have
+ * been provided, or returns a function that accepts the remaining `func`
+ * arguments, and so on. The arity of `func` may be specified if `func.length`
+ * is not sufficient.
+ *
+ * The `_.curry.placeholder` value, which defaults to `_` in monolithic builds,
+ * may be used as a placeholder for provided arguments.
+ *
+ * **Note:** This method doesn't set the "length" property of curried functions.
+ *
+ * @static
+ * @memberOf _
+ * @category Function
+ * @param {Function} func The function to curry.
+ * @param {number} [arity=func.length] The arity of `func`.
+ * @param- {Object} [guard] Enables use as an iteratee for functions like `_.map`.
+ * @returns {Function} Returns the new curried function.
+ * @example
+ *
+ * var abc = function(a, b, c) {
+ * return [a, b, c];
+ * };
+ *
+ * var curried = _.curry(abc);
+ *
+ * curried(1)(2)(3);
+ * // => [1, 2, 3]
+ *
+ * curried(1, 2)(3);
+ * // => [1, 2, 3]
+ *
+ * curried(1, 2, 3);
+ * // => [1, 2, 3]
+ *
+ * // Curried with placeholders.
+ * curried(1)(_, 3)(2);
+ * // => [1, 2, 3]
+ */
+ function curry(func, arity, guard) {
+ arity = guard ? undefined : arity;
+ var result = createWrapper(func, CURRY_FLAG, undefined, undefined, undefined, undefined, undefined, arity);
+ result.placeholder = curry.placeholder;
+ return result;
+ }
+
+ /**
+ * This method is like `_.curry` except that arguments are applied to `func`
+ * in the manner of `_.partialRight` instead of `_.partial`.
+ *
+ * The `_.curryRight.placeholder` value, which defaults to `_` in monolithic
+ * builds, may be used as a placeholder for provided arguments.
+ *
+ * **Note:** This method doesn't set the "length" property of curried functions.
+ *
+ * @static
+ * @memberOf _
+ * @category Function
+ * @param {Function} func The function to curry.
+ * @param {number} [arity=func.length] The arity of `func`.
+ * @param- {Object} [guard] Enables use as an iteratee for functions like `_.map`.
+ * @returns {Function} Returns the new curried function.
+ * @example
+ *
+ * var abc = function(a, b, c) {
+ * return [a, b, c];
+ * };
+ *
+ * var curried = _.curryRight(abc);
+ *
+ * curried(3)(2)(1);
+ * // => [1, 2, 3]
+ *
+ * curried(2, 3)(1);
+ * // => [1, 2, 3]
+ *
+ * curried(1, 2, 3);
+ * // => [1, 2, 3]
+ *
+ * // Curried with placeholders.
+ * curried(3)(1, _)(2);
+ * // => [1, 2, 3]
+ */
+ function curryRight(func, arity, guard) {
+ arity = guard ? undefined : arity;
+ var result = createWrapper(func, CURRY_RIGHT_FLAG, undefined, undefined, undefined, undefined, undefined, arity);
+ result.placeholder = curryRight.placeholder;
+ return result;
+ }
+
+ /**
+ * Creates a debounced function that delays invoking `func` until after `wait`
+ * milliseconds have elapsed since the last time the debounced function was
+ * invoked. The debounced function comes with a `cancel` method to cancel
+ * delayed `func` invocations and a `flush` method to immediately invoke them.
+ * Provide an options object to indicate whether `func` should be invoked on
+ * the leading and/or trailing edge of the `wait` timeout. The `func` is invoked
+ * with the last arguments provided to the debounced function. Subsequent calls
+ * to the debounced function return the result of the last `func` invocation.
+ *
+ * **Note:** If `leading` and `trailing` options are `true`, `func` is invoked
+ * on the trailing edge of the timeout only if the debounced function is
+ * invoked more than once during the `wait` timeout.
+ *
+ * See [David Corbacho's article](http://drupalmotion.com/article/debounce-and-throttle-visual-explanation)
+ * for details over the differences between `_.debounce` and `_.throttle`.
+ *
+ * @static
+ * @memberOf _
+ * @category Function
+ * @param {Function} func The function to debounce.
+ * @param {number} [wait=0] The number of milliseconds to delay.
+ * @param {Object} [options] The options object.
+ * @param {boolean} [options.leading=false] Specify invoking on the leading
+ * edge of the timeout.
+ * @param {number} [options.maxWait] The maximum time `func` is allowed to be
+ * delayed before it's invoked.
+ * @param {boolean} [options.trailing=true] Specify invoking on the trailing
+ * edge of the timeout.
+ * @returns {Function} Returns the new debounced function.
+ * @example
+ *
+ * // Avoid costly calculations while the window size is in flux.
+ * jQuery(window).on('resize', _.debounce(calculateLayout, 150));
+ *
+ * // Invoke `sendMail` when clicked, debouncing subsequent calls.
+ * jQuery(element).on('click', _.debounce(sendMail, 300, {
+ * 'leading': true,
+ * 'trailing': false
+ * }));
+ *
+ * // Ensure `batchLog` is invoked once after 1 second of debounced calls.
+ * var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 });
+ * var source = new EventSource('/stream');
+ * jQuery(source).on('message', debounced);
+ *
+ * // Cancel the trailing debounced invocation.
+ * jQuery(window).on('popstate', debounced.cancel);
+ */
+ function debounce(func, wait, options) {
+ var args,
+ maxTimeoutId,
+ result,
+ stamp,
+ thisArg,
+ timeoutId,
+ trailingCall,
+ lastCalled = 0,
+ leading = false,
+ maxWait = false,
+ trailing = true;
+
+ if (typeof func != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
+ wait = toNumber(wait) || 0;
+ if (isObject(options)) {
+ leading = !!options.leading;
+ maxWait = 'maxWait' in options && nativeMax(toNumber(options.maxWait) || 0, wait);
+ trailing = 'trailing' in options ? !!options.trailing : trailing;
+ }
+
+ function cancel() {
+ if (timeoutId) {
+ clearTimeout(timeoutId);
+ }
+ if (maxTimeoutId) {
+ clearTimeout(maxTimeoutId);
+ }
+ lastCalled = 0;
+ args = maxTimeoutId = thisArg = timeoutId = trailingCall = undefined;
+ }
+
+ function complete(isCalled, id) {
+ if (id) {
+ clearTimeout(id);
+ }
+ maxTimeoutId = timeoutId = trailingCall = undefined;
+ if (isCalled) {
+ lastCalled = now();
+ result = func.apply(thisArg, args);
+ if (!timeoutId && !maxTimeoutId) {
+ args = thisArg = undefined;
+ }
+ }
+ }
+
+ function delayed() {
+ var remaining = wait - (now() - stamp);
+ if (remaining <= 0 || remaining > wait) {
+ complete(trailingCall, maxTimeoutId);
+ } else {
+ timeoutId = setTimeout(delayed, remaining);
+ }
+ }
+
+ function flush() {
+ if ((timeoutId && trailingCall) || (maxTimeoutId && trailing)) {
+ result = func.apply(thisArg, args);
+ }
+ cancel();
+ return result;
+ }
+
+ function maxDelayed() {
+ complete(trailing, timeoutId);
+ }
+
+ function debounced() {
+ args = arguments;
+ stamp = now();
+ thisArg = this;
+ trailingCall = trailing && (timeoutId || !leading);
+
+ if (maxWait === false) {
+ var leadingCall = leading && !timeoutId;
+ } else {
+ if (!lastCalled && !maxTimeoutId && !leading) {
+ lastCalled = stamp;
+ }
+ var remaining = maxWait - (stamp - lastCalled);
+
+ var isCalled = (remaining <= 0 || remaining > maxWait) &&
+ (leading || maxTimeoutId);
+
+ if (isCalled) {
+ if (maxTimeoutId) {
+ maxTimeoutId = clearTimeout(maxTimeoutId);
+ }
+ lastCalled = stamp;
+ result = func.apply(thisArg, args);
+ }
+ else if (!maxTimeoutId) {
+ maxTimeoutId = setTimeout(maxDelayed, remaining);
+ }
+ }
+ if (isCalled && timeoutId) {
+ timeoutId = clearTimeout(timeoutId);
+ }
+ else if (!timeoutId && wait !== maxWait) {
+ timeoutId = setTimeout(delayed, wait);
+ }
+ if (leadingCall) {
+ isCalled = true;
+ result = func.apply(thisArg, args);
+ }
+ if (isCalled && !timeoutId && !maxTimeoutId) {
+ args = thisArg = undefined;
+ }
+ return result;
+ }
+ debounced.cancel = cancel;
+ debounced.flush = flush;
+ return debounced;
+ }
+
+ /**
+ * Defers invoking the `func` until the current call stack has cleared. Any
+ * additional arguments are provided to `func` when it's invoked.
+ *
+ * @static
+ * @memberOf _
+ * @category Function
+ * @param {Function} func The function to defer.
+ * @param {...*} [args] The arguments to invoke `func` with.
+ * @returns {number} Returns the timer id.
+ * @example
+ *
+ * _.defer(function(text) {
+ * console.log(text);
+ * }, 'deferred');
+ * // => logs 'deferred' after one or more milliseconds
+ */
+ var defer = rest(function(func, args) {
+ return baseDelay(func, 1, args);
+ });
+
+ /**
+ * Invokes `func` after `wait` milliseconds. Any additional arguments are
+ * provided to `func` when it's invoked.
+ *
+ * @static
+ * @memberOf _
+ * @category Function
+ * @param {Function} func The function to delay.
+ * @param {number} wait The number of milliseconds to delay invocation.
+ * @param {...*} [args] The arguments to invoke `func` with.
+ * @returns {number} Returns the timer id.
+ * @example
+ *
+ * _.delay(function(text) {
+ * console.log(text);
+ * }, 1000, 'later');
+ * // => logs 'later' after one second
+ */
+ var delay = rest(function(func, wait, args) {
+ return baseDelay(func, toNumber(wait) || 0, args);
+ });
+
+ /**
+ * Creates a function that invokes `func` with arguments reversed.
+ *
+ * @static
+ * @memberOf _
+ * @category Function
+ * @param {Function} func The function to flip arguments for.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var flipped = _.flip(function() {
+ * return _.toArray(arguments);
+ * });
+ *
+ * flipped('a', 'b', 'c', 'd');
+ * // => ['d', 'c', 'b', 'a']
+ */
+ function flip(func) {
+ return createWrapper(func, FLIP_FLAG);
+ }
+
+ /**
+ * Creates a function that memoizes the result of `func`. If `resolver` is
+ * provided it determines the cache key for storing the result based on the
+ * arguments provided to the memoized function. By default, the first argument
+ * provided to the memoized function is used as the map cache key. The `func`
+ * is invoked with the `this` binding of the memoized function.
+ *
+ * **Note:** The cache is exposed as the `cache` property on the memoized
+ * function. Its creation may be customized by replacing the `_.memoize.Cache`
+ * constructor with one whose instances implement the [`Map`](http://ecma-international.org/ecma-262/6.0/#sec-properties-of-the-map-prototype-object)
+ * method interface of `delete`, `get`, `has`, and `set`.
+ *
+ * @static
+ * @memberOf _
+ * @category Function
+ * @param {Function} func The function to have its output memoized.
+ * @param {Function} [resolver] The function to resolve the cache key.
+ * @returns {Function} Returns the new memoizing function.
+ * @example
+ *
+ * var object = { 'a': 1, 'b': 2 };
+ * var other = { 'c': 3, 'd': 4 };
+ *
+ * var values = _.memoize(_.values);
+ * values(object);
+ * // => [1, 2]
+ *
+ * values(other);
+ * // => [3, 4]
+ *
+ * object.a = 2;
+ * values(object);
+ * // => [1, 2]
+ *
+ * // Modify the result cache.
+ * values.cache.set(object, ['a', 'b']);
+ * values(object);
+ * // => ['a', 'b']
+ *
+ * // Replace `_.memoize.Cache`.
+ * _.memoize.Cache = WeakMap;
+ */
+ function memoize(func, resolver) {
+ if (typeof func != 'function' || (resolver && typeof resolver != 'function')) {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
+ var memoized = function() {
+ var args = arguments,
+ key = resolver ? resolver.apply(this, args) : args[0],
+ cache = memoized.cache;
+
+ if (cache.has(key)) {
+ return cache.get(key);
+ }
+ var result = func.apply(this, args);
+ memoized.cache = cache.set(key, result);
+ return result;
+ };
+ memoized.cache = new memoize.Cache;
+ return memoized;
+ }
+
+ /**
+ * Creates a function that negates the result of the predicate `func`. The
+ * `func` predicate is invoked with the `this` binding and arguments of the
+ * created function.
+ *
+ * @static
+ * @memberOf _
+ * @category Function
+ * @param {Function} predicate The predicate to negate.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * function isEven(n) {
+ * return n % 2 == 0;
+ * }
+ *
+ * _.filter([1, 2, 3, 4, 5, 6], _.negate(isEven));
+ * // => [1, 3, 5]
+ */
+ function negate(predicate) {
+ if (typeof predicate != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
+ return function() {
+ return !predicate.apply(this, arguments);
+ };
+ }
+
+ /**
+ * Creates a function that is restricted to invoking `func` once. Repeat calls
+ * to the function return the value of the first invocation. The `func` is
+ * invoked with the `this` binding and arguments of the created function.
+ *
+ * @static
+ * @memberOf _
+ * @category Function
+ * @param {Function} func The function to restrict.
+ * @returns {Function} Returns the new restricted function.
+ * @example
+ *
+ * var initialize = _.once(createApplication);
+ * initialize();
+ * initialize();
+ * // `initialize` invokes `createApplication` once
+ */
+ function once(func) {
+ return before(2, func);
+ }
+
+ /**
+ * Creates a function that invokes `func` with arguments transformed by
+ * corresponding `transforms`.
+ *
+ * @static
+ * @memberOf _
+ * @category Function
+ * @param {Function} func The function to wrap.
+ * @param {...(Function|Function[])} [transforms] The functions to transform
+ * arguments, specified individually or in arrays.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * function doubled(n) {
+ * return n * 2;
+ * }
+ *
+ * function square(n) {
+ * return n * n;
+ * }
+ *
+ * var func = _.overArgs(function(x, y) {
+ * return [x, y];
+ * }, square, doubled);
+ *
+ * func(9, 3);
+ * // => [81, 6]
+ *
+ * func(10, 5);
+ * // => [100, 10]
+ */
+ var overArgs = rest(function(func, transforms) {
+ transforms = arrayMap(baseFlatten(transforms, 1), getIteratee());
+
+ var funcsLength = transforms.length;
+ return rest(function(args) {
+ var index = -1,
+ length = nativeMin(args.length, funcsLength);
+
+ while (++index < length) {
+ args[index] = transforms[index].call(this, args[index]);
+ }
+ return apply(func, this, args);
+ });
+ });
+
+ /**
+ * Creates a function that invokes `func` with `partial` arguments prepended
+ * to those provided to the new function. This method is like `_.bind` except
+ * it does **not** alter the `this` binding.
+ *
+ * The `_.partial.placeholder` value, which defaults to `_` in monolithic
+ * builds, may be used as a placeholder for partially applied arguments.
+ *
+ * **Note:** This method doesn't set the "length" property of partially
+ * applied functions.
+ *
+ * @static
+ * @memberOf _
+ * @category Function
+ * @param {Function} func The function to partially apply arguments to.
+ * @param {...*} [partials] The arguments to be partially applied.
+ * @returns {Function} Returns the new partially applied function.
+ * @example
+ *
+ * var greet = function(greeting, name) {
+ * return greeting + ' ' + name;
+ * };
+ *
+ * var sayHelloTo = _.partial(greet, 'hello');
+ * sayHelloTo('fred');
+ * // => 'hello fred'
+ *
+ * // Partially applied with placeholders.
+ * var greetFred = _.partial(greet, _, 'fred');
+ * greetFred('hi');
+ * // => 'hi fred'
+ */
+ var partial = rest(function(func, partials) {
+ var holders = replaceHolders(partials, getPlaceholder(partial));
+ return createWrapper(func, PARTIAL_FLAG, undefined, partials, holders);
+ });
+
+ /**
+ * This method is like `_.partial` except that partially applied arguments
+ * are appended to those provided to the new function.
+ *
+ * The `_.partialRight.placeholder` value, which defaults to `_` in monolithic
+ * builds, may be used as a placeholder for partially applied arguments.
+ *
+ * **Note:** This method doesn't set the "length" property of partially
+ * applied functions.
+ *
+ * @static
+ * @memberOf _
+ * @category Function
+ * @param {Function} func The function to partially apply arguments to.
+ * @param {...*} [partials] The arguments to be partially applied.
+ * @returns {Function} Returns the new partially applied function.
+ * @example
+ *
+ * var greet = function(greeting, name) {
+ * return greeting + ' ' + name;
+ * };
+ *
+ * var greetFred = _.partialRight(greet, 'fred');
+ * greetFred('hi');
+ * // => 'hi fred'
+ *
+ * // Partially applied with placeholders.
+ * var sayHelloTo = _.partialRight(greet, 'hello', _);
+ * sayHelloTo('fred');
+ * // => 'hello fred'
+ */
+ var partialRight = rest(function(func, partials) {
+ var holders = replaceHolders(partials, getPlaceholder(partialRight));
+ return createWrapper(func, PARTIAL_RIGHT_FLAG, undefined, partials, holders);
+ });
+
+ /**
+ * Creates a function that invokes `func` with arguments arranged according
+ * to the specified indexes where the argument value at the first index is
+ * provided as the first argument, the argument value at the second index is
+ * provided as the second argument, and so on.
+ *
+ * @static
+ * @memberOf _
+ * @category Function
+ * @param {Function} func The function to rearrange arguments for.
+ * @param {...(number|number[])} indexes The arranged argument indexes,
+ * specified individually or in arrays.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var rearged = _.rearg(function(a, b, c) {
+ * return [a, b, c];
+ * }, 2, 0, 1);
+ *
+ * rearged('b', 'c', 'a')
+ * // => ['a', 'b', 'c']
+ */
+ var rearg = rest(function(func, indexes) {
+ return createWrapper(func, REARG_FLAG, undefined, undefined, undefined, baseFlatten(indexes, 1));
+ });
+
+ /**
+ * Creates a function that invokes `func` with the `this` binding of the
+ * created function and arguments from `start` and beyond provided as an array.
+ *
+ * **Note:** This method is based on the [rest parameter](https://mdn.io/rest_parameters).
+ *
+ * @static
+ * @memberOf _
+ * @category Function
+ * @param {Function} func The function to apply a rest parameter to.
+ * @param {number} [start=func.length-1] The start position of the rest parameter.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var say = _.rest(function(what, names) {
+ * return what + ' ' + _.initial(names).join(', ') +
+ * (_.size(names) > 1 ? ', & ' : '') + _.last(names);
+ * });
+ *
+ * say('hello', 'fred', 'barney', 'pebbles');
+ * // => 'hello fred, barney, & pebbles'
+ */
+ function rest(func, start) {
+ if (typeof func != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
+ start = nativeMax(start === undefined ? (func.length - 1) : toInteger(start), 0);
+ return function() {
+ var args = arguments,
+ index = -1,
+ length = nativeMax(args.length - start, 0),
+ array = Array(length);
+
+ while (++index < length) {
+ array[index] = args[start + index];
+ }
+ switch (start) {
+ case 0: return func.call(this, array);
+ case 1: return func.call(this, args[0], array);
+ case 2: return func.call(this, args[0], args[1], array);
+ }
+ var otherArgs = Array(start + 1);
+ index = -1;
+ while (++index < start) {
+ otherArgs[index] = args[index];
+ }
+ otherArgs[start] = array;
+ return apply(func, this, otherArgs);
+ };
+ }
+
+ /**
+ * Creates a function that invokes `func` with the `this` binding of the created
+ * function and an array of arguments much like [`Function#apply`](https://es5.github.io/#x15.3.4.3).
+ *
+ * **Note:** This method is based on the [spread operator](https://mdn.io/spread_operator).
+ *
+ * @static
+ * @memberOf _
+ * @category Function
+ * @param {Function} func The function to spread arguments over.
+ * @param {number} [start=0] The start position of the spread.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var say = _.spread(function(who, what) {
+ * return who + ' says ' + what;
+ * });
+ *
+ * say(['fred', 'hello']);
+ * // => 'fred says hello'
+ *
+ * var numbers = Promise.all([
+ * Promise.resolve(40),
+ * Promise.resolve(36)
+ * ]);
+ *
+ * numbers.then(_.spread(function(x, y) {
+ * return x + y;
+ * }));
+ * // => a Promise of 76
+ */
+ function spread(func, start) {
+ if (typeof func != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
+ start = start === undefined ? 0 : nativeMax(toInteger(start), 0);
+ return rest(function(args) {
+ var array = args[start],
+ otherArgs = args.slice(0, start);
+
+ if (array) {
+ arrayPush(otherArgs, array);
+ }
+ return apply(func, this, otherArgs);
+ });
+ }
+
+ /**
+ * Creates a throttled function that only invokes `func` at most once per
+ * every `wait` milliseconds. The throttled function comes with a `cancel`
+ * method to cancel delayed `func` invocations and a `flush` method to
+ * immediately invoke them. Provide an options object to indicate whether
+ * `func` should be invoked on the leading and/or trailing edge of the `wait`
+ * timeout. The `func` is invoked with the last arguments provided to the
+ * throttled function. Subsequent calls to the throttled function return the
+ * result of the last `func` invocation.
+ *
+ * **Note:** If `leading` and `trailing` options are `true`, `func` is invoked
+ * on the trailing edge of the timeout only if the throttled function is
+ * invoked more than once during the `wait` timeout.
+ *
+ * See [David Corbacho's article](http://drupalmotion.com/article/debounce-and-throttle-visual-explanation)
+ * for details over the differences between `_.throttle` and `_.debounce`.
+ *
+ * @static
+ * @memberOf _
+ * @category Function
+ * @param {Function} func The function to throttle.
+ * @param {number} [wait=0] The number of milliseconds to throttle invocations to.
+ * @param {Object} [options] The options object.
+ * @param {boolean} [options.leading=true] Specify invoking on the leading
+ * edge of the timeout.
+ * @param {boolean} [options.trailing=true] Specify invoking on the trailing
+ * edge of the timeout.
+ * @returns {Function} Returns the new throttled function.
+ * @example
+ *
+ * // Avoid excessively updating the position while scrolling.
+ * jQuery(window).on('scroll', _.throttle(updatePosition, 100));
+ *
+ * // Invoke `renewToken` when the click event is fired, but not more than once every 5 minutes.
+ * var throttled = _.throttle(renewToken, 300000, { 'trailing': false });
+ * jQuery(element).on('click', throttled);
+ *
+ * // Cancel the trailing throttled invocation.
+ * jQuery(window).on('popstate', throttled.cancel);
+ */
+ function throttle(func, wait, options) {
+ var leading = true,
+ trailing = true;
+
+ if (typeof func != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
+ if (isObject(options)) {
+ leading = 'leading' in options ? !!options.leading : leading;
+ trailing = 'trailing' in options ? !!options.trailing : trailing;
+ }
+ return debounce(func, wait, {
+ 'leading': leading,
+ 'maxWait': wait,
+ 'trailing': trailing
+ });
+ }
+
+ /**
+ * Creates a function that accepts up to one argument, ignoring any
+ * additional arguments.
+ *
+ * @static
+ * @memberOf _
+ * @category Function
+ * @param {Function} func The function to cap arguments for.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * _.map(['6', '8', '10'], _.unary(parseInt));
+ * // => [6, 8, 10]
+ */
+ function unary(func) {
+ return ary(func, 1);
+ }
+
+ /**
+ * Creates a function that provides `value` to the wrapper function as its
+ * first argument. Any additional arguments provided to the function are
+ * appended to those provided to the wrapper function. The wrapper is invoked
+ * with the `this` binding of the created function.
+ *
+ * @static
+ * @memberOf _
+ * @category Function
+ * @param {*} value The value to wrap.
+ * @param {Function} [wrapper=identity] The wrapper function.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var p = _.wrap(_.escape, function(func, text) {
+ * return '<p>' + func(text) + '</p>';
+ * });
+ *
+ * p('fred, barney, & pebbles');
+ * // => '<p>fred, barney, & pebbles</p>'
+ */
+ function wrap(value, wrapper) {
+ wrapper = wrapper == null ? identity : wrapper;
+ return partial(wrapper, value);
+ }
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Casts `value` as an array if it's not one.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to inspect.
+ * @returns {Array} Returns the cast array.
+ * @example
+ *
+ * _.castArray(1);
+ * // => [1]
+ *
+ * _.castArray({ 'a': 1 });
+ * // => [{ 'a': 1 }]
+ *
+ * _.castArray('abc');
+ * // => ['abc']
+ *
+ * _.castArray(null);
+ * // => [null]
+ *
+ * _.castArray(undefined);
+ * // => [undefined]
+ *
+ * _.castArray();
+ * // => []
+ *
+ * var array = [1, 2, 3];
+ * console.log(_.castArray(array) === array);
+ * // => true
+ */
+ function castArray() {
+ if (!arguments.length) {
+ return [];
+ }
+ var value = arguments[0];
+ return isArray(value) ? value : [value];
+ }
+
+ /**
+ * Creates a shallow clone of `value`.
+ *
+ * **Note:** This method is loosely based on the
+ * [structured clone algorithm](https://mdn.io/Structured_clone_algorithm)
+ * and supports cloning arrays, array buffers, booleans, date objects, maps,
+ * numbers, `Object` objects, regexes, sets, strings, symbols, and typed
+ * arrays. The own enumerable properties of `arguments` objects are cloned
+ * as plain objects. An empty object is returned for uncloneable values such
+ * as error objects, functions, DOM nodes, and WeakMaps.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to clone.
+ * @returns {*} Returns the cloned value.
+ * @example
+ *
+ * var objects = [{ 'a': 1 }, { 'b': 2 }];
+ *
+ * var shallow = _.clone(objects);
+ * console.log(shallow[0] === objects[0]);
+ * // => true
+ */
+ function clone(value) {
+ return baseClone(value, false, true);
+ }
+
+ /**
+ * This method is like `_.clone` except that it accepts `customizer` which
+ * is invoked to produce the cloned value. If `customizer` returns `undefined`
+ * cloning is handled by the method instead. The `customizer` is invoked with
+ * up to four arguments; (value [, index|key, object, stack]).
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to clone.
+ * @param {Function} [customizer] The function to customize cloning.
+ * @returns {*} Returns the cloned value.
+ * @example
+ *
+ * function customizer(value) {
+ * if (_.isElement(value)) {
+ * return value.cloneNode(false);
+ * }
+ * }
+ *
+ * var el = _.cloneWith(document.body, customizer);
+ *
+ * console.log(el === document.body);
+ * // => false
+ * console.log(el.nodeName);
+ * // => 'BODY'
+ * console.log(el.childNodes.length);
+ * // => 0
+ */
+ function cloneWith(value, customizer) {
+ return baseClone(value, false, true, customizer);
+ }
+
+ /**
+ * This method is like `_.clone` except that it recursively clones `value`.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to recursively clone.
+ * @returns {*} Returns the deep cloned value.
+ * @example
+ *
+ * var objects = [{ 'a': 1 }, { 'b': 2 }];
+ *
+ * var deep = _.cloneDeep(objects);
+ * console.log(deep[0] === objects[0]);
+ * // => false
+ */
+ function cloneDeep(value) {
+ return baseClone(value, true, true);
+ }
+
+ /**
+ * This method is like `_.cloneWith` except that it recursively clones `value`.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to recursively clone.
+ * @param {Function} [customizer] The function to customize cloning.
+ * @returns {*} Returns the deep cloned value.
+ * @example
+ *
+ * function customizer(value) {
+ * if (_.isElement(value)) {
+ * return value.cloneNode(true);
+ * }
+ * }
+ *
+ * var el = _.cloneDeepWith(document.body, customizer);
+ *
+ * console.log(el === document.body);
+ * // => false
+ * console.log(el.nodeName);
+ * // => 'BODY'
+ * console.log(el.childNodes.length);
+ * // => 20
+ */
+ function cloneDeepWith(value, customizer) {
+ return baseClone(value, true, true, customizer);
+ }
+
+ /**
+ * Performs a [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+ * comparison between two values to determine if they are equivalent.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to compare.
+ * @param {*} other The other value to compare.
+ * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
+ * @example
+ *
+ * var object = { 'user': 'fred' };
+ * var other = { 'user': 'fred' };
+ *
+ * _.eq(object, object);
+ * // => true
+ *
+ * _.eq(object, other);
+ * // => false
+ *
+ * _.eq('a', 'a');
+ * // => true
+ *
+ * _.eq('a', Object('a'));
+ * // => false
+ *
+ * _.eq(NaN, NaN);
+ * // => true
+ */
+ function eq(value, other) {
+ return value === other || (value !== value && other !== other);
+ }
+
+ /**
+ * Checks if `value` is greater than `other`.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to compare.
+ * @param {*} other The other value to compare.
+ * @returns {boolean} Returns `true` if `value` is greater than `other`, else `false`.
+ * @example
+ *
+ * _.gt(3, 1);
+ * // => true
+ *
+ * _.gt(3, 3);
+ * // => false
+ *
+ * _.gt(1, 3);
+ * // => false
+ */
+ function gt(value, other) {
+ return value > other;
+ }
+
+ /**
+ * Checks if `value` is greater than or equal to `other`.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to compare.
+ * @param {*} other The other value to compare.
+ * @returns {boolean} Returns `true` if `value` is greater than or equal to `other`, else `false`.
+ * @example
+ *
+ * _.gte(3, 1);
+ * // => true
+ *
+ * _.gte(3, 3);
+ * // => true
+ *
+ * _.gte(1, 3);
+ * // => false
+ */
+ function gte(value, other) {
+ return value >= other;
+ }
+
+ /**
+ * Checks if `value` is likely an `arguments` object.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
+ * @example
+ *
+ * _.isArguments(function() { return arguments; }());
+ * // => true
+ *
+ * _.isArguments([1, 2, 3]);
+ * // => false
+ */
+ function isArguments(value) {
+ // Safari 8.1 incorrectly makes `arguments.callee` enumerable in strict mode.
+ return isArrayLikeObject(value) && hasOwnProperty.call(value, 'callee') &&
+ (!propertyIsEnumerable.call(value, 'callee') || objectToString.call(value) == argsTag);
+ }
+
+ /**
+ * Checks if `value` is classified as an `Array` object.
+ *
+ * @static
+ * @memberOf _
+ * @type {Function}
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
+ * @example
+ *
+ * _.isArray([1, 2, 3]);
+ * // => true
+ *
+ * _.isArray(document.body.children);
+ * // => false
+ *
+ * _.isArray('abc');
+ * // => false
+ *
+ * _.isArray(_.noop);
+ * // => false
+ */
+ var isArray = Array.isArray;
+
+ /**
+ * Checks if `value` is classified as an `ArrayBuffer` object.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
+ * @example
+ *
+ * _.isArrayBuffer(new ArrayBuffer(2));
+ * // => true
+ *
+ * _.isArrayBuffer(new Array(2));
+ * // => false
+ */
+ function isArrayBuffer(value) {
+ return isObjectLike(value) && objectToString.call(value) == arrayBufferTag;
+ }
+
+ /**
+ * Checks if `value` is array-like. A value is considered array-like if it's
+ * not a function and has a `value.length` that's an integer greater than or
+ * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is array-like, else `false`.
+ * @example
+ *
+ * _.isArrayLike([1, 2, 3]);
+ * // => true
+ *
+ * _.isArrayLike(document.body.children);
+ * // => true
+ *
+ * _.isArrayLike('abc');
+ * // => true
+ *
+ * _.isArrayLike(_.noop);
+ * // => false
+ */
+ function isArrayLike(value) {
+ return value != null && isLength(getLength(value)) && !isFunction(value);
+ }
+
+ /**
+ * This method is like `_.isArrayLike` except that it also checks if `value`
+ * is an object.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is an array-like object, else `false`.
+ * @example
+ *
+ * _.isArrayLikeObject([1, 2, 3]);
+ * // => true
+ *
+ * _.isArrayLikeObject(document.body.children);
+ * // => true
+ *
+ * _.isArrayLikeObject('abc');
+ * // => false
+ *
+ * _.isArrayLikeObject(_.noop);
+ * // => false
+ */
+ function isArrayLikeObject(value) {
+ return isObjectLike(value) && isArrayLike(value);
+ }
+
+ /**
+ * Checks if `value` is classified as a boolean primitive or object.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
+ * @example
+ *
+ * _.isBoolean(false);
+ * // => true
+ *
+ * _.isBoolean(null);
+ * // => false
+ */
+ function isBoolean(value) {
+ return value === true || value === false ||
+ (isObjectLike(value) && objectToString.call(value) == boolTag);
+ }
+
+ /**
+ * Checks if `value` is a buffer.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a buffer, else `false`.
+ * @example
+ *
+ * _.isBuffer(new Buffer(2));
+ * // => true
+ *
+ * _.isBuffer(new Uint8Array(2));
+ * // => false
+ */
+ var isBuffer = !Buffer ? constant(false) : function(value) {
+ return value instanceof Buffer;
+ };
+
+ /**
+ * Checks if `value` is classified as a `Date` object.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
+ * @example
+ *
+ * _.isDate(new Date);
+ * // => true
+ *
+ * _.isDate('Mon April 23 2012');
+ * // => false
+ */
+ function isDate(value) {
+ return isObjectLike(value) && objectToString.call(value) == dateTag;
+ }
+
+ /**
+ * Checks if `value` is likely a DOM element.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a DOM element, else `false`.
+ * @example
+ *
+ * _.isElement(document.body);
+ * // => true
+ *
+ * _.isElement('<body>');
+ * // => false
+ */
+ function isElement(value) {
+ return !!value && value.nodeType === 1 && isObjectLike(value) && !isPlainObject(value);
+ }
+
+ /**
+ * Checks if `value` is an empty collection or object. A value is considered
+ * empty if it's an `arguments` object, array, string, or jQuery-like collection
+ * with a length of `0` or has no own enumerable properties.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is empty, else `false`.
+ * @example
+ *
+ * _.isEmpty(null);
+ * // => true
+ *
+ * _.isEmpty(true);
+ * // => true
+ *
+ * _.isEmpty(1);
+ * // => true
+ *
+ * _.isEmpty([1, 2, 3]);
+ * // => false
+ *
+ * _.isEmpty({ 'a': 1 });
+ * // => false
+ */
+ function isEmpty(value) {
+ if (isArrayLike(value) &&
+ (isArray(value) || isString(value) ||
+ isFunction(value.splice) || isArguments(value))) {
+ return !value.length;
+ }
+ for (var key in value) {
+ if (hasOwnProperty.call(value, key)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Performs a deep comparison between two values to determine if they are
+ * equivalent.
+ *
+ * **Note:** This method supports comparing arrays, array buffers, booleans,
+ * date objects, error objects, maps, numbers, `Object` objects, regexes,
+ * sets, strings, symbols, and typed arrays. `Object` objects are compared
+ * by their own, not inherited, enumerable properties. Functions and DOM
+ * nodes are **not** supported.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to compare.
+ * @param {*} other The other value to compare.
+ * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
+ * @example
+ *
+ * var object = { 'user': 'fred' };
+ * var other = { 'user': 'fred' };
+ *
+ * _.isEqual(object, other);
+ * // => true
+ *
+ * object === other;
+ * // => false
+ */
+ function isEqual(value, other) {
+ return baseIsEqual(value, other);
+ }
+
+ /**
+ * This method is like `_.isEqual` except that it accepts `customizer` which
+ * is invoked to compare values. If `customizer` returns `undefined` comparisons
+ * are handled by the method instead. The `customizer` is invoked with up to
+ * six arguments: (objValue, othValue [, index|key, object, other, stack]).
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to compare.
+ * @param {*} other The other value to compare.
+ * @param {Function} [customizer] The function to customize comparisons.
+ * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
+ * @example
+ *
+ * function isGreeting(value) {
+ * return /^h(?:i|ello)$/.test(value);
+ * }
+ *
+ * function customizer(objValue, othValue) {
+ * if (isGreeting(objValue) && isGreeting(othValue)) {
+ * return true;
+ * }
+ * }
+ *
+ * var array = ['hello', 'goodbye'];
+ * var other = ['hi', 'goodbye'];
+ *
+ * _.isEqualWith(array, other, customizer);
+ * // => true
+ */
+ function isEqualWith(value, other, customizer) {
+ customizer = typeof customizer == 'function' ? customizer : undefined;
+ var result = customizer ? customizer(value, other) : undefined;
+ return result === undefined ? baseIsEqual(value, other, customizer) : !!result;
+ }
+
+ /**
+ * Checks if `value` is an `Error`, `EvalError`, `RangeError`, `ReferenceError`,
+ * `SyntaxError`, `TypeError`, or `URIError` object.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is an error object, else `false`.
+ * @example
+ *
+ * _.isError(new Error);
+ * // => true
+ *
+ * _.isError(Error);
+ * // => false
+ */
+ function isError(value) {
+ if (!isObjectLike(value)) {
+ return false;
+ }
+ return (objectToString.call(value) == errorTag) ||
+ (typeof value.message == 'string' && typeof value.name == 'string');
+ }
+
+ /**
+ * Checks if `value` is a finite primitive number.
+ *
+ * **Note:** This method is based on [`Number.isFinite`](https://mdn.io/Number/isFinite).
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a finite number, else `false`.
+ * @example
+ *
+ * _.isFinite(3);
+ * // => true
+ *
+ * _.isFinite(Number.MAX_VALUE);
+ * // => true
+ *
+ * _.isFinite(3.14);
+ * // => true
+ *
+ * _.isFinite(Infinity);
+ * // => false
+ */
+ function isFinite(value) {
+ return typeof value == 'number' && nativeIsFinite(value);
+ }
+
+ /**
+ * Checks if `value` is classified as a `Function` object.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
+ * @example
+ *
+ * _.isFunction(_);
+ * // => true
+ *
+ * _.isFunction(/abc/);
+ * // => false
+ */
+ function isFunction(value) {
+ // The use of `Object#toString` avoids issues with the `typeof` operator
+ // in Safari 8 which returns 'object' for typed array and weak map constructors,
+ // and PhantomJS 1.9 which returns 'function' for `NodeList` instances.
+ var tag = isObject(value) ? objectToString.call(value) : '';
+ return tag == funcTag || tag == genTag;
+ }
+
+ /**
+ * Checks if `value` is an integer.
+ *
+ * **Note:** This method is based on [`Number.isInteger`](https://mdn.io/Number/isInteger).
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is an integer, else `false`.
+ * @example
+ *
+ * _.isInteger(3);
+ * // => true
+ *
+ * _.isInteger(Number.MIN_VALUE);
+ * // => false
+ *
+ * _.isInteger(Infinity);
+ * // => false
+ *
+ * _.isInteger('3');
+ * // => false
+ */
+ function isInteger(value) {
+ return typeof value == 'number' && value == toInteger(value);
+ }
+
+ /**
+ * Checks if `value` is a valid array-like length.
+ *
+ * **Note:** This function is loosely based on [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength).
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a valid length, else `false`.
+ * @example
+ *
+ * _.isLength(3);
+ * // => true
+ *
+ * _.isLength(Number.MIN_VALUE);
+ * // => false
+ *
+ * _.isLength(Infinity);
+ * // => false
+ *
+ * _.isLength('3');
+ * // => false
+ */
+ function isLength(value) {
+ return typeof value == 'number' &&
+ value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
+ }
+
+ /**
+ * Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`.
+ * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is an object, else `false`.
+ * @example
+ *
+ * _.isObject({});
+ * // => true
+ *
+ * _.isObject([1, 2, 3]);
+ * // => true
+ *
+ * _.isObject(_.noop);
+ * // => true
+ *
+ * _.isObject(null);
+ * // => false
+ */
+ function isObject(value) {
+ var type = typeof value;
+ return !!value && (type == 'object' || type == 'function');
+ }
+
+ /**
+ * Checks if `value` is object-like. A value is object-like if it's not `null`
+ * and has a `typeof` result of "object".
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is object-like, else `false`.
+ * @example
+ *
+ * _.isObjectLike({});
+ * // => true
+ *
+ * _.isObjectLike([1, 2, 3]);
+ * // => true
+ *
+ * _.isObjectLike(_.noop);
+ * // => false
+ *
+ * _.isObjectLike(null);
+ * // => false
+ */
+ function isObjectLike(value) {
+ return !!value && typeof value == 'object';
+ }
+
+ /**
+ * Checks if `value` is classified as a `Map` object.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
+ * @example
+ *
+ * _.isMap(new Map);
+ * // => true
+ *
+ * _.isMap(new WeakMap);
+ * // => false
+ */
+ function isMap(value) {
+ return isObjectLike(value) && getTag(value) == mapTag;
+ }
+
+ /**
+ * Performs a partial deep comparison between `object` and `source` to
+ * determine if `object` contains equivalent property values. This method is
+ * equivalent to a `_.matches` function when `source` is partially applied.
+ *
+ * **Note:** This method supports comparing the same values as `_.isEqual`.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {Object} object The object to inspect.
+ * @param {Object} source The object of property values to match.
+ * @returns {boolean} Returns `true` if `object` is a match, else `false`.
+ * @example
+ *
+ * var object = { 'user': 'fred', 'age': 40 };
+ *
+ * _.isMatch(object, { 'age': 40 });
+ * // => true
+ *
+ * _.isMatch(object, { 'age': 36 });
+ * // => false
+ */
+ function isMatch(object, source) {
+ return object === source || baseIsMatch(object, source, getMatchData(source));
+ }
+
+ /**
+ * This method is like `_.isMatch` except that it accepts `customizer` which
+ * is invoked to compare values. If `customizer` returns `undefined` comparisons
+ * are handled by the method instead. The `customizer` is invoked with five
+ * arguments: (objValue, srcValue, index|key, object, source).
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {Object} object The object to inspect.
+ * @param {Object} source The object of property values to match.
+ * @param {Function} [customizer] The function to customize comparisons.
+ * @returns {boolean} Returns `true` if `object` is a match, else `false`.
+ * @example
+ *
+ * function isGreeting(value) {
+ * return /^h(?:i|ello)$/.test(value);
+ * }
+ *
+ * function customizer(objValue, srcValue) {
+ * if (isGreeting(objValue) && isGreeting(srcValue)) {
+ * return true;
+ * }
+ * }
+ *
+ * var object = { 'greeting': 'hello' };
+ * var source = { 'greeting': 'hi' };
+ *
+ * _.isMatchWith(object, source, customizer);
+ * // => true
+ */
+ function isMatchWith(object, source, customizer) {
+ customizer = typeof customizer == 'function' ? customizer : undefined;
+ return baseIsMatch(object, source, getMatchData(source), customizer);
+ }
+
+ /**
+ * Checks if `value` is `NaN`.
+ *
+ * **Note:** This method is not the same as [`isNaN`](https://es5.github.io/#x15.1.2.4)
+ * which returns `true` for `undefined` and other non-numeric values.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is `NaN`, else `false`.
+ * @example
+ *
+ * _.isNaN(NaN);
+ * // => true
+ *
+ * _.isNaN(new Number(NaN));
+ * // => true
+ *
+ * isNaN(undefined);
+ * // => true
+ *
+ * _.isNaN(undefined);
+ * // => false
+ */
+ function isNaN(value) {
+ // An `NaN` primitive is the only value that is not equal to itself.
+ // Perform the `toStringTag` check first to avoid errors with some ActiveX objects in IE.
+ return isNumber(value) && value != +value;
+ }
+
+ /**
+ * Checks if `value` is a native function.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a native function, else `false`.
+ * @example
+ *
+ * _.isNative(Array.prototype.push);
+ * // => true
+ *
+ * _.isNative(_);
+ * // => false
+ */
+ function isNative(value) {
+ if (value == null) {
+ return false;
+ }
+ if (isFunction(value)) {
+ return reIsNative.test(funcToString.call(value));
+ }
+ return isObjectLike(value) &&
+ (isHostObject(value) ? reIsNative : reIsHostCtor).test(value);
+ }
+
+ /**
+ * Checks if `value` is `null`.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is `null`, else `false`.
+ * @example
+ *
+ * _.isNull(null);
+ * // => true
+ *
+ * _.isNull(void 0);
+ * // => false
+ */
+ function isNull(value) {
+ return value === null;
+ }
+
+ /**
+ * Checks if `value` is `null` or `undefined`.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is nullish, else `false`.
+ * @example
+ *
+ * _.isNil(null);
+ * // => true
+ *
+ * _.isNil(void 0);
+ * // => true
+ *
+ * _.isNil(NaN);
+ * // => false
+ */
+ function isNil(value) {
+ return value == null;
+ }
+
+ /**
+ * Checks if `value` is classified as a `Number` primitive or object.
+ *
+ * **Note:** To exclude `Infinity`, `-Infinity`, and `NaN`, which are classified
+ * as numbers, use the `_.isFinite` method.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
+ * @example
+ *
+ * _.isNumber(3);
+ * // => true
+ *
+ * _.isNumber(Number.MIN_VALUE);
+ * // => true
+ *
+ * _.isNumber(Infinity);
+ * // => true
+ *
+ * _.isNumber('3');
+ * // => false
+ */
+ function isNumber(value) {
+ return typeof value == 'number' ||
+ (isObjectLike(value) && objectToString.call(value) == numberTag);
+ }
+
+ /**
+ * Checks if `value` is a plain object, that is, an object created by the
+ * `Object` constructor or one with a `[[Prototype]]` of `null`.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a plain object, else `false`.
+ * @example
+ *
+ * function Foo() {
+ * this.a = 1;
+ * }
+ *
+ * _.isPlainObject(new Foo);
+ * // => false
+ *
+ * _.isPlainObject([1, 2, 3]);
+ * // => false
+ *
+ * _.isPlainObject({ 'x': 0, 'y': 0 });
+ * // => true
+ *
+ * _.isPlainObject(Object.create(null));
+ * // => true
+ */
+ function isPlainObject(value) {
+ if (!isObjectLike(value) ||
+ objectToString.call(value) != objectTag || isHostObject(value)) {
+ return false;
+ }
+ var proto = getPrototypeOf(value);
+ if (proto === null) {
+ return true;
+ }
+ var Ctor = proto.constructor;
+ return (typeof Ctor == 'function' &&
+ Ctor instanceof Ctor && funcToString.call(Ctor) == objectCtorString);
+ }
+
+ /**
+ * Checks if `value` is classified as a `RegExp` object.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
+ * @example
+ *
+ * _.isRegExp(/abc/);
+ * // => true
+ *
+ * _.isRegExp('/abc/');
+ * // => false
+ */
+ function isRegExp(value) {
+ return isObject(value) && objectToString.call(value) == regexpTag;
+ }
+
+ /**
+ * Checks if `value` is a safe integer. An integer is safe if it's an IEEE-754
+ * double precision number which isn't the result of a rounded unsafe integer.
+ *
+ * **Note:** This method is based on [`Number.isSafeInteger`](https://mdn.io/Number/isSafeInteger).
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a safe integer, else `false`.
+ * @example
+ *
+ * _.isSafeInteger(3);
+ * // => true
+ *
+ * _.isSafeInteger(Number.MIN_VALUE);
+ * // => false
+ *
+ * _.isSafeInteger(Infinity);
+ * // => false
+ *
+ * _.isSafeInteger('3');
+ * // => false
+ */
+ function isSafeInteger(value) {
+ return isInteger(value) && value >= -MAX_SAFE_INTEGER && value <= MAX_SAFE_INTEGER;
+ }
+
+ /**
+ * Checks if `value` is classified as a `Set` object.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
+ * @example
+ *
+ * _.isSet(new Set);
+ * // => true
+ *
+ * _.isSet(new WeakSet);
+ * // => false
+ */
+ function isSet(value) {
+ return isObjectLike(value) && getTag(value) == setTag;
+ }
+
+ /**
+ * Checks if `value` is classified as a `String` primitive or object.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
+ * @example
+ *
+ * _.isString('abc');
+ * // => true
+ *
+ * _.isString(1);
+ * // => false
+ */
+ function isString(value) {
+ return typeof value == 'string' ||
+ (!isArray(value) && isObjectLike(value) && objectToString.call(value) == stringTag);
+ }
+
+ /**
+ * Checks if `value` is classified as a `Symbol` primitive or object.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
+ * @example
+ *
+ * _.isSymbol(Symbol.iterator);
+ * // => true
+ *
+ * _.isSymbol('abc');
+ * // => false
+ */
+ function isSymbol(value) {
+ return typeof value == 'symbol' ||
+ (isObjectLike(value) && objectToString.call(value) == symbolTag);
+ }
+
+ /**
+ * Checks if `value` is classified as a typed array.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
+ * @example
+ *
+ * _.isTypedArray(new Uint8Array);
+ * // => true
+ *
+ * _.isTypedArray([]);
+ * // => false
+ */
+ function isTypedArray(value) {
+ return isObjectLike(value) &&
+ isLength(value.length) && !!typedArrayTags[objectToString.call(value)];
+ }
+
+ /**
+ * Checks if `value` is `undefined`.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is `undefined`, else `false`.
+ * @example
+ *
+ * _.isUndefined(void 0);
+ * // => true
+ *
+ * _.isUndefined(null);
+ * // => false
+ */
+ function isUndefined(value) {
+ return value === undefined;
+ }
+
+ /**
+ * Checks if `value` is classified as a `WeakMap` object.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
+ * @example
+ *
+ * _.isWeakMap(new WeakMap);
+ * // => true
+ *
+ * _.isWeakMap(new Map);
+ * // => false
+ */
+ function isWeakMap(value) {
+ return isObjectLike(value) && getTag(value) == weakMapTag;
+ }
+
+ /**
+ * Checks if `value` is classified as a `WeakSet` object.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
+ * @example
+ *
+ * _.isWeakSet(new WeakSet);
+ * // => true
+ *
+ * _.isWeakSet(new Set);
+ * // => false
+ */
+ function isWeakSet(value) {
+ return isObjectLike(value) && objectToString.call(value) == weakSetTag;
+ }
+
+ /**
+ * Checks if `value` is less than `other`.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to compare.
+ * @param {*} other The other value to compare.
+ * @returns {boolean} Returns `true` if `value` is less than `other`, else `false`.
+ * @example
+ *
+ * _.lt(1, 3);
+ * // => true
+ *
+ * _.lt(3, 3);
+ * // => false
+ *
+ * _.lt(3, 1);
+ * // => false
+ */
+ function lt(value, other) {
+ return value < other;
+ }
+
+ /**
+ * Checks if `value` is less than or equal to `other`.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to compare.
+ * @param {*} other The other value to compare.
+ * @returns {boolean} Returns `true` if `value` is less than or equal to `other`, else `false`.
+ * @example
+ *
+ * _.lte(1, 3);
+ * // => true
+ *
+ * _.lte(3, 3);
+ * // => true
+ *
+ * _.lte(3, 1);
+ * // => false
+ */
+ function lte(value, other) {
+ return value <= other;
+ }
+
+ /**
+ * Converts `value` to an array.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to convert.
+ * @returns {Array} Returns the converted array.
+ * @example
+ *
+ * _.toArray({ 'a': 1, 'b': 2 });
+ * // => [1, 2]
+ *
+ * _.toArray('abc');
+ * // => ['a', 'b', 'c']
+ *
+ * _.toArray(1);
+ * // => []
+ *
+ * _.toArray(null);
+ * // => []
+ */
+ function toArray(value) {
+ if (!value) {
+ return [];
+ }
+ if (isArrayLike(value)) {
+ return isString(value) ? stringToArray(value) : copyArray(value);
+ }
+ if (iteratorSymbol && value[iteratorSymbol]) {
+ return iteratorToArray(value[iteratorSymbol]());
+ }
+ var tag = getTag(value),
+ func = tag == mapTag ? mapToArray : (tag == setTag ? setToArray : values);
+
+ return func(value);
+ }
+
+ /**
+ * Converts `value` to an integer.
+ *
+ * **Note:** This function is loosely based on [`ToInteger`](http://www.ecma-international.org/ecma-262/6.0/#sec-tointeger).
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to convert.
+ * @returns {number} Returns the converted integer.
+ * @example
+ *
+ * _.toInteger(3);
+ * // => 3
+ *
+ * _.toInteger(Number.MIN_VALUE);
+ * // => 0
+ *
+ * _.toInteger(Infinity);
+ * // => 1.7976931348623157e+308
+ *
+ * _.toInteger('3');
+ * // => 3
+ */
+ function toInteger(value) {
+ if (!value) {
+ return value === 0 ? value : 0;
+ }
+ value = toNumber(value);
+ if (value === INFINITY || value === -INFINITY) {
+ var sign = (value < 0 ? -1 : 1);
+ return sign * MAX_INTEGER;
+ }
+ var remainder = value % 1;
+ return value === value ? (remainder ? value - remainder : value) : 0;
+ }
+
+ /**
+ * Converts `value` to an integer suitable for use as the length of an
+ * array-like object.
+ *
+ * **Note:** This method is based on [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength).
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to convert.
+ * @returns {number} Returns the converted integer.
+ * @example
+ *
+ * _.toLength(3);
+ * // => 3
+ *
+ * _.toLength(Number.MIN_VALUE);
+ * // => 0
+ *
+ * _.toLength(Infinity);
+ * // => 4294967295
+ *
+ * _.toLength('3');
+ * // => 3
+ */
+ function toLength(value) {
+ return value ? baseClamp(toInteger(value), 0, MAX_ARRAY_LENGTH) : 0;
+ }
+
+ /**
+ * Converts `value` to a number.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to process.
+ * @returns {number} Returns the number.
+ * @example
+ *
+ * _.toNumber(3);
+ * // => 3
+ *
+ * _.toNumber(Number.MIN_VALUE);
+ * // => 5e-324
+ *
+ * _.toNumber(Infinity);
+ * // => Infinity
+ *
+ * _.toNumber('3');
+ * // => 3
+ */
+ function toNumber(value) {
+ if (isObject(value)) {
+ var other = isFunction(value.valueOf) ? value.valueOf() : value;
+ value = isObject(other) ? (other + '') : other;
+ }
+ if (typeof value != 'string') {
+ return value === 0 ? value : +value;
+ }
+ value = value.replace(reTrim, '');
+ var isBinary = reIsBinary.test(value);
+ return (isBinary || reIsOctal.test(value))
+ ? freeParseInt(value.slice(2), isBinary ? 2 : 8)
+ : (reIsBadHex.test(value) ? NAN : +value);
+ }
+
+ /**
+ * Converts `value` to a plain object flattening inherited enumerable
+ * properties of `value` to own properties of the plain object.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to convert.
+ * @returns {Object} Returns the converted plain object.
+ * @example
+ *
+ * function Foo() {
+ * this.b = 2;
+ * }
+ *
+ * Foo.prototype.c = 3;
+ *
+ * _.assign({ 'a': 1 }, new Foo);
+ * // => { 'a': 1, 'b': 2 }
+ *
+ * _.assign({ 'a': 1 }, _.toPlainObject(new Foo));
+ * // => { 'a': 1, 'b': 2, 'c': 3 }
+ */
+ function toPlainObject(value) {
+ return copyObject(value, keysIn(value));
+ }
+
+ /**
+ * Converts `value` to a safe integer. A safe integer can be compared and
+ * represented correctly.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to convert.
+ * @returns {number} Returns the converted integer.
+ * @example
+ *
+ * _.toSafeInteger(3);
+ * // => 3
+ *
+ * _.toSafeInteger(Number.MIN_VALUE);
+ * // => 0
+ *
+ * _.toSafeInteger(Infinity);
+ * // => 9007199254740991
+ *
+ * _.toSafeInteger('3');
+ * // => 3
+ */
+ function toSafeInteger(value) {
+ return baseClamp(toInteger(value), -MAX_SAFE_INTEGER, MAX_SAFE_INTEGER);
+ }
+
+ /**
+ * Converts `value` to a string if it's not one. An empty string is returned
+ * for `null` and `undefined` values. The sign of `-0` is preserved.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to process.
+ * @returns {string} Returns the string.
+ * @example
+ *
+ * _.toString(null);
+ * // => ''
+ *
+ * _.toString(-0);
+ * // => '-0'
+ *
+ * _.toString([1, 2, 3]);
+ * // => '1,2,3'
+ */
+ function toString(value) {
+ // Exit early for strings to avoid a performance hit in some environments.
+ if (typeof value == 'string') {
+ return value;
+ }
+ if (value == null) {
+ return '';
+ }
+ if (isSymbol(value)) {
+ return symbolToString ? symbolToString.call(value) : '';
+ }
+ var result = (value + '');
+ return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result;
+ }
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Assigns own enumerable properties of source objects to the destination
+ * object. Source objects are applied from left to right. Subsequent sources
+ * overwrite property assignments of previous sources.
+ *
+ * **Note:** This method mutates `object` and is loosely based on
+ * [`Object.assign`](https://mdn.io/Object/assign).
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The destination object.
+ * @param {...Object} [sources] The source objects.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * function Foo() {
+ * this.c = 3;
+ * }
+ *
+ * function Bar() {
+ * this.e = 5;
+ * }
+ *
+ * Foo.prototype.d = 4;
+ * Bar.prototype.f = 6;
+ *
+ * _.assign({ 'a': 1 }, new Foo, new Bar);
+ * // => { 'a': 1, 'c': 3, 'e': 5 }
+ */
+ var assign = createAssigner(function(object, source) {
+ if (nonEnumShadows || isPrototype(source) || isArrayLike(source)) {
+ copyObject(source, keys(source), object);
+ return;
+ }
+ for (var key in source) {
+ if (hasOwnProperty.call(source, key)) {
+ assignValue(object, key, source[key]);
+ }
+ }
+ });
+
+ /**
+ * This method is like `_.assign` except that it iterates over own and
+ * inherited source properties.
+ *
+ * **Note:** This method mutates `object`.
+ *
+ * @static
+ * @memberOf _
+ * @alias extend
+ * @category Object
+ * @param {Object} object The destination object.
+ * @param {...Object} [sources] The source objects.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * function Foo() {
+ * this.b = 2;
+ * }
+ *
+ * function Bar() {
+ * this.d = 4;
+ * }
+ *
+ * Foo.prototype.c = 3;
+ * Bar.prototype.e = 5;
+ *
+ * _.assignIn({ 'a': 1 }, new Foo, new Bar);
+ * // => { 'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5 }
+ */
+ var assignIn = createAssigner(function(object, source) {
+ if (nonEnumShadows || isPrototype(source) || isArrayLike(source)) {
+ copyObject(source, keysIn(source), object);
+ return;
+ }
+ for (var key in source) {
+ assignValue(object, key, source[key]);
+ }
+ });
+
+ /**
+ * This method is like `_.assignIn` except that it accepts `customizer` which
+ * is invoked to produce the assigned values. If `customizer` returns `undefined`
+ * assignment is handled by the method instead. The `customizer` is invoked
+ * with five arguments: (objValue, srcValue, key, object, source).
+ *
+ * **Note:** This method mutates `object`.
+ *
+ * @static
+ * @memberOf _
+ * @alias extendWith
+ * @category Object
+ * @param {Object} object The destination object.
+ * @param {...Object} sources The source objects.
+ * @param {Function} [customizer] The function to customize assigned values.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * function customizer(objValue, srcValue) {
+ * return _.isUndefined(objValue) ? srcValue : objValue;
+ * }
+ *
+ * var defaults = _.partialRight(_.assignInWith, customizer);
+ *
+ * defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 });
+ * // => { 'a': 1, 'b': 2 }
+ */
+ var assignInWith = createAssigner(function(object, source, srcIndex, customizer) {
+ copyObjectWith(source, keysIn(source), object, customizer);
+ });
+
+ /**
+ * This method is like `_.assign` except that it accepts `customizer` which
+ * is invoked to produce the assigned values. If `customizer` returns `undefined`
+ * assignment is handled by the method instead. The `customizer` is invoked
+ * with five arguments: (objValue, srcValue, key, object, source).
+ *
+ * **Note:** This method mutates `object`.
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The destination object.
+ * @param {...Object} sources The source objects.
+ * @param {Function} [customizer] The function to customize assigned values.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * function customizer(objValue, srcValue) {
+ * return _.isUndefined(objValue) ? srcValue : objValue;
+ * }
+ *
+ * var defaults = _.partialRight(_.assignWith, customizer);
+ *
+ * defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 });
+ * // => { 'a': 1, 'b': 2 }
+ */
+ var assignWith = createAssigner(function(object, source, srcIndex, customizer) {
+ copyObjectWith(source, keys(source), object, customizer);
+ });
+
+ /**
+ * Creates an array of values corresponding to `paths` of `object`.
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to iterate over.
+ * @param {...(string|string[])} [paths] The property paths of elements to pick,
+ * specified individually or in arrays.
+ * @returns {Array} Returns the new array of picked elements.
+ * @example
+ *
+ * var object = { 'a': [{ 'b': { 'c': 3 } }, 4] };
+ *
+ * _.at(object, ['a[0].b.c', 'a[1]']);
+ * // => [3, 4]
+ *
+ * _.at(['a', 'b', 'c'], 0, 2);
+ * // => ['a', 'c']
+ */
+ var at = rest(function(object, paths) {
+ return baseAt(object, baseFlatten(paths, 1));
+ });
+
+ /**
+ * Creates an object that inherits from the `prototype` object. If a `properties`
+ * object is given its own enumerable properties are assigned to the created object.
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} prototype The object to inherit from.
+ * @param {Object} [properties] The properties to assign to the object.
+ * @returns {Object} Returns the new object.
+ * @example
+ *
+ * function Shape() {
+ * this.x = 0;
+ * this.y = 0;
+ * }
+ *
+ * function Circle() {
+ * Shape.call(this);
+ * }
+ *
+ * Circle.prototype = _.create(Shape.prototype, {
+ * 'constructor': Circle
+ * });
+ *
+ * var circle = new Circle;
+ * circle instanceof Circle;
+ * // => true
+ *
+ * circle instanceof Shape;
+ * // => true
+ */
+ function create(prototype, properties) {
+ var result = baseCreate(prototype);
+ return properties ? baseAssign(result, properties) : result;
+ }
+
+ /**
+ * Assigns own and inherited enumerable properties of source objects to the
+ * destination object for all destination properties that resolve to `undefined`.
+ * Source objects are applied from left to right. Once a property is set,
+ * additional values of the same property are ignored.
+ *
+ * **Note:** This method mutates `object`.
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The destination object.
+ * @param {...Object} [sources] The source objects.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * _.defaults({ 'user': 'barney' }, { 'age': 36 }, { 'user': 'fred' });
+ * // => { 'user': 'barney', 'age': 36 }
+ */
+ var defaults = rest(function(args) {
+ args.push(undefined, assignInDefaults);
+ return apply(assignInWith, undefined, args);
+ });
+
+ /**
+ * This method is like `_.defaults` except that it recursively assigns
+ * default properties.
+ *
+ * **Note:** This method mutates `object`.
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The destination object.
+ * @param {...Object} [sources] The source objects.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * _.defaultsDeep({ 'user': { 'name': 'barney' } }, { 'user': { 'name': 'fred', 'age': 36 } });
+ * // => { 'user': { 'name': 'barney', 'age': 36 } }
+ *
+ */
+ var defaultsDeep = rest(function(args) {
+ args.push(undefined, mergeDefaults);
+ return apply(mergeWith, undefined, args);
+ });
+
+ /**
+ * This method is like `_.find` except that it returns the key of the first
+ * element `predicate` returns truthy for instead of the element itself.
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to search.
+ * @param {Function|Object|string} [predicate=_.identity] The function invoked per iteration.
+ * @returns {string|undefined} Returns the key of the matched element, else `undefined`.
+ * @example
+ *
+ * var users = {
+ * 'barney': { 'age': 36, 'active': true },
+ * 'fred': { 'age': 40, 'active': false },
+ * 'pebbles': { 'age': 1, 'active': true }
+ * };
+ *
+ * _.findKey(users, function(o) { return o.age < 40; });
+ * // => 'barney' (iteration order is not guaranteed)
+ *
+ * // The `_.matches` iteratee shorthand.
+ * _.findKey(users, { 'age': 1, 'active': true });
+ * // => 'pebbles'
+ *
+ * // The `_.matchesProperty` iteratee shorthand.
+ * _.findKey(users, ['active', false]);
+ * // => 'fred'
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.findKey(users, 'active');
+ * // => 'barney'
+ */
+ function findKey(object, predicate) {
+ return baseFind(object, getIteratee(predicate, 3), baseForOwn, true);
+ }
+
+ /**
+ * This method is like `_.findKey` except that it iterates over elements of
+ * a collection in the opposite order.
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to search.
+ * @param {Function|Object|string} [predicate=_.identity] The function invoked per iteration.
+ * @returns {string|undefined} Returns the key of the matched element, else `undefined`.
+ * @example
+ *
+ * var users = {
+ * 'barney': { 'age': 36, 'active': true },
+ * 'fred': { 'age': 40, 'active': false },
+ * 'pebbles': { 'age': 1, 'active': true }
+ * };
+ *
+ * _.findLastKey(users, function(o) { return o.age < 40; });
+ * // => returns 'pebbles' assuming `_.findKey` returns 'barney'
+ *
+ * // The `_.matches` iteratee shorthand.
+ * _.findLastKey(users, { 'age': 36, 'active': true });
+ * // => 'barney'
+ *
+ * // The `_.matchesProperty` iteratee shorthand.
+ * _.findLastKey(users, ['active', false]);
+ * // => 'fred'
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.findLastKey(users, 'active');
+ * // => 'pebbles'
+ */
+ function findLastKey(object, predicate) {
+ return baseFind(object, getIteratee(predicate, 3), baseForOwnRight, true);
+ }
+
+ /**
+ * Iterates over own and inherited enumerable properties of an object invoking
+ * `iteratee` for each property. The iteratee is invoked with three arguments:
+ * (value, key, object). Iteratee functions may exit iteration early by explicitly
+ * returning `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to iterate over.
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * function Foo() {
+ * this.a = 1;
+ * this.b = 2;
+ * }
+ *
+ * Foo.prototype.c = 3;
+ *
+ * _.forIn(new Foo, function(value, key) {
+ * console.log(key);
+ * });
+ * // => logs 'a', 'b', then 'c' (iteration order is not guaranteed)
+ */
+ function forIn(object, iteratee) {
+ return object == null
+ ? object
+ : baseFor(object, baseCastFunction(iteratee), keysIn);
+ }
+
+ /**
+ * This method is like `_.forIn` except that it iterates over properties of
+ * `object` in the opposite order.
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to iterate over.
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * function Foo() {
+ * this.a = 1;
+ * this.b = 2;
+ * }
+ *
+ * Foo.prototype.c = 3;
+ *
+ * _.forInRight(new Foo, function(value, key) {
+ * console.log(key);
+ * });
+ * // => logs 'c', 'b', then 'a' assuming `_.forIn` logs 'a', 'b', then 'c'
+ */
+ function forInRight(object, iteratee) {
+ return object == null
+ ? object
+ : baseForRight(object, baseCastFunction(iteratee), keysIn);
+ }
+
+ /**
+ * Iterates over own enumerable properties of an object invoking `iteratee`
+ * for each property. The iteratee is invoked with three arguments:
+ * (value, key, object). Iteratee functions may exit iteration early by
+ * explicitly returning `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to iterate over.
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * function Foo() {
+ * this.a = 1;
+ * this.b = 2;
+ * }
+ *
+ * Foo.prototype.c = 3;
+ *
+ * _.forOwn(new Foo, function(value, key) {
+ * console.log(key);
+ * });
+ * // => logs 'a' then 'b' (iteration order is not guaranteed)
+ */
+ function forOwn(object, iteratee) {
+ return object && baseForOwn(object, baseCastFunction(iteratee));
+ }
+
+ /**
+ * This method is like `_.forOwn` except that it iterates over properties of
+ * `object` in the opposite order.
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to iterate over.
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * function Foo() {
+ * this.a = 1;
+ * this.b = 2;
+ * }
+ *
+ * Foo.prototype.c = 3;
+ *
+ * _.forOwnRight(new Foo, function(value, key) {
+ * console.log(key);
+ * });
+ * // => logs 'b' then 'a' assuming `_.forOwn` logs 'a' then 'b'
+ */
+ function forOwnRight(object, iteratee) {
+ return object && baseForOwnRight(object, baseCastFunction(iteratee));
+ }
+
+ /**
+ * Creates an array of function property names from own enumerable properties
+ * of `object`.
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to inspect.
+ * @returns {Array} Returns the new array of property names.
+ * @example
+ *
+ * function Foo() {
+ * this.a = _.constant('a');
+ * this.b = _.constant('b');
+ * }
+ *
+ * Foo.prototype.c = _.constant('c');
+ *
+ * _.functions(new Foo);
+ * // => ['a', 'b']
+ */
+ function functions(object) {
+ return object == null ? [] : baseFunctions(object, keys(object));
+ }
+
+ /**
+ * Creates an array of function property names from own and inherited
+ * enumerable properties of `object`.
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to inspect.
+ * @returns {Array} Returns the new array of property names.
+ * @example
+ *
+ * function Foo() {
+ * this.a = _.constant('a');
+ * this.b = _.constant('b');
+ * }
+ *
+ * Foo.prototype.c = _.constant('c');
+ *
+ * _.functionsIn(new Foo);
+ * // => ['a', 'b', 'c']
+ */
+ function functionsIn(object) {
+ return object == null ? [] : baseFunctions(object, keysIn(object));
+ }
+
+ /**
+ * Gets the value at `path` of `object`. If the resolved value is
+ * `undefined` the `defaultValue` is used in its place.
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to query.
+ * @param {Array|string} path The path of the property to get.
+ * @param {*} [defaultValue] The value returned if the resolved value is `undefined`.
+ * @returns {*} Returns the resolved value.
+ * @example
+ *
+ * var object = { 'a': [{ 'b': { 'c': 3 } }] };
+ *
+ * _.get(object, 'a[0].b.c');
+ * // => 3
+ *
+ * _.get(object, ['a', '0', 'b', 'c']);
+ * // => 3
+ *
+ * _.get(object, 'a.b.c', 'default');
+ * // => 'default'
+ */
+ function get(object, path, defaultValue) {
+ var result = object == null ? undefined : baseGet(object, path);
+ return result === undefined ? defaultValue : result;
+ }
+
+ /**
+ * Checks if `path` is a direct property of `object`.
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to query.
+ * @param {Array|string} path The path to check.
+ * @returns {boolean} Returns `true` if `path` exists, else `false`.
+ * @example
+ *
+ * var object = { 'a': { 'b': { 'c': 3 } } };
+ * var other = _.create({ 'a': _.create({ 'b': _.create({ 'c': 3 }) }) });
+ *
+ * _.has(object, 'a');
+ * // => true
+ *
+ * _.has(object, 'a.b.c');
+ * // => true
+ *
+ * _.has(object, ['a', 'b', 'c']);
+ * // => true
+ *
+ * _.has(other, 'a');
+ * // => false
+ */
+ function has(object, path) {
+ return hasPath(object, path, baseHas);
+ }
+
+ /**
+ * Checks if `path` is a direct or inherited property of `object`.
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to query.
+ * @param {Array|string} path The path to check.
+ * @returns {boolean} Returns `true` if `path` exists, else `false`.
+ * @example
+ *
+ * var object = _.create({ 'a': _.create({ 'b': _.create({ 'c': 3 }) }) });
+ *
+ * _.hasIn(object, 'a');
+ * // => true
+ *
+ * _.hasIn(object, 'a.b.c');
+ * // => true
+ *
+ * _.hasIn(object, ['a', 'b', 'c']);
+ * // => true
+ *
+ * _.hasIn(object, 'b');
+ * // => false
+ */
+ function hasIn(object, path) {
+ return hasPath(object, path, baseHasIn);
+ }
+
+ /**
+ * Creates an object composed of the inverted keys and values of `object`.
+ * If `object` contains duplicate values, subsequent values overwrite property
+ * assignments of previous values.
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to invert.
+ * @returns {Object} Returns the new inverted object.
+ * @example
+ *
+ * var object = { 'a': 1, 'b': 2, 'c': 1 };
+ *
+ * _.invert(object);
+ * // => { '1': 'c', '2': 'b' }
+ */
+ var invert = createInverter(function(result, value, key) {
+ result[value] = key;
+ }, constant(identity));
+
+ /**
+ * This method is like `_.invert` except that the inverted object is generated
+ * from the results of running each element of `object` through `iteratee`.
+ * The corresponding inverted value of each inverted key is an array of keys
+ * responsible for generating the inverted value. The iteratee is invoked
+ * with one argument: (value).
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to invert.
+ * @param {Function|Object|string} [iteratee=_.identity] The iteratee invoked per element.
+ * @returns {Object} Returns the new inverted object.
+ * @example
+ *
+ * var object = { 'a': 1, 'b': 2, 'c': 1 };
+ *
+ * _.invertBy(object);
+ * // => { '1': ['a', 'c'], '2': ['b'] }
+ *
+ * _.invertBy(object, function(value) {
+ * return 'group' + value;
+ * });
+ * // => { 'group1': ['a', 'c'], 'group2': ['b'] }
+ */
+ var invertBy = createInverter(function(result, value, key) {
+ if (hasOwnProperty.call(result, value)) {
+ result[value].push(key);
+ } else {
+ result[value] = [key];
+ }
+ }, getIteratee);
+
+ /**
+ * Invokes the method at `path` of `object`.
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to query.
+ * @param {Array|string} path The path of the method to invoke.
+ * @param {...*} [args] The arguments to invoke the method with.
+ * @returns {*} Returns the result of the invoked method.
+ * @example
+ *
+ * var object = { 'a': [{ 'b': { 'c': [1, 2, 3, 4] } }] };
+ *
+ * _.invoke(object, 'a[0].b.c.slice', 1, 3);
+ * // => [2, 3]
+ */
+ var invoke = rest(baseInvoke);
+
+ /**
+ * Creates an array of the own enumerable property names of `object`.
+ *
+ * **Note:** Non-object values are coerced to objects. See the
+ * [ES spec](http://ecma-international.org/ecma-262/6.0/#sec-object.keys)
+ * for more details.
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the array of property names.
+ * @example
+ *
+ * function Foo() {
+ * this.a = 1;
+ * this.b = 2;
+ * }
+ *
+ * Foo.prototype.c = 3;
+ *
+ * _.keys(new Foo);
+ * // => ['a', 'b'] (iteration order is not guaranteed)
+ *
+ * _.keys('hi');
+ * // => ['0', '1']
+ */
+ function keys(object) {
+ var isProto = isPrototype(object);
+ if (!(isProto || isArrayLike(object))) {
+ return baseKeys(object);
+ }
+ var indexes = indexKeys(object),
+ skipIndexes = !!indexes,
+ result = indexes || [],
+ length = result.length;
+
+ for (var key in object) {
+ if (baseHas(object, key) &&
+ !(skipIndexes && (key == 'length' || isIndex(key, length))) &&
+ !(isProto && key == 'constructor')) {
+ result.push(key);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Creates an array of the own and inherited enumerable property names of `object`.
+ *
+ * **Note:** Non-object values are coerced to objects.
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the array of property names.
+ * @example
+ *
+ * function Foo() {
+ * this.a = 1;
+ * this.b = 2;
+ * }
+ *
+ * Foo.prototype.c = 3;
+ *
+ * _.keysIn(new Foo);
+ * // => ['a', 'b', 'c'] (iteration order is not guaranteed)
+ */
+ function keysIn(object) {
+ var index = -1,
+ isProto = isPrototype(object),
+ props = baseKeysIn(object),
+ propsLength = props.length,
+ indexes = indexKeys(object),
+ skipIndexes = !!indexes,
+ result = indexes || [],
+ length = result.length;
+
+ while (++index < propsLength) {
+ var key = props[index];
+ if (!(skipIndexes && (key == 'length' || isIndex(key, length))) &&
+ !(key == 'constructor' && (isProto || !hasOwnProperty.call(object, key)))) {
+ result.push(key);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * The opposite of `_.mapValues`; this method creates an object with the
+ * same values as `object` and keys generated by running each own enumerable
+ * property of `object` through `iteratee`. The iteratee is invoked with
+ * three arguments: (value, key, object).
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to iterate over.
+ * @param {Function|Object|string} [iteratee=_.identity] The function invoked per iteration.
+ * @returns {Object} Returns the new mapped object.
+ * @example
+ *
+ * _.mapKeys({ 'a': 1, 'b': 2 }, function(value, key) {
+ * return key + value;
+ * });
+ * // => { 'a1': 1, 'b2': 2 }
+ */
+ function mapKeys(object, iteratee) {
+ var result = {};
+ iteratee = getIteratee(iteratee, 3);
+
+ baseForOwn(object, function(value, key, object) {
+ result[iteratee(value, key, object)] = value;
+ });
+ return result;
+ }
+
+ /**
+ * Creates an object with the same keys as `object` and values generated by
+ * running each own enumerable property of `object` through `iteratee`. The
+ * iteratee is invoked with three arguments: (value, key, object).
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to iterate over.
+ * @param {Function|Object|string} [iteratee=_.identity] The function invoked per iteration.
+ * @returns {Object} Returns the new mapped object.
+ * @example
+ *
+ * var users = {
+ * 'fred': { 'user': 'fred', 'age': 40 },
+ * 'pebbles': { 'user': 'pebbles', 'age': 1 }
+ * };
+ *
+ * _.mapValues(users, function(o) { return o.age; });
+ * // => { 'fred': 40, 'pebbles': 1 } (iteration order is not guaranteed)
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.mapValues(users, 'age');
+ * // => { 'fred': 40, 'pebbles': 1 } (iteration order is not guaranteed)
+ */
+ function mapValues(object, iteratee) {
+ var result = {};
+ iteratee = getIteratee(iteratee, 3);
+
+ baseForOwn(object, function(value, key, object) {
+ result[key] = iteratee(value, key, object);
+ });
+ return result;
+ }
+
+ /**
+ * This method is like `_.assign` except that it recursively merges own and
+ * inherited enumerable properties of source objects into the destination
+ * object. Source properties that resolve to `undefined` are skipped if a
+ * destination value exists. Array and plain object properties are merged
+ * recursively.Other objects and value types are overridden by assignment.
+ * Source objects are applied from left to right. Subsequent sources
+ * overwrite property assignments of previous sources.
+ *
+ * **Note:** This method mutates `object`.
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The destination object.
+ * @param {...Object} [sources] The source objects.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * var users = {
+ * 'data': [{ 'user': 'barney' }, { 'user': 'fred' }]
+ * };
+ *
+ * var ages = {
+ * 'data': [{ 'age': 36 }, { 'age': 40 }]
+ * };
+ *
+ * _.merge(users, ages);
+ * // => { 'data': [{ 'user': 'barney', 'age': 36 }, { 'user': 'fred', 'age': 40 }] }
+ */
+ var merge = createAssigner(function(object, source, srcIndex) {
+ baseMerge(object, source, srcIndex);
+ });
+
+ /**
+ * This method is like `_.merge` except that it accepts `customizer` which
+ * is invoked to produce the merged values of the destination and source
+ * properties. If `customizer` returns `undefined` merging is handled by the
+ * method instead. The `customizer` is invoked with seven arguments:
+ * (objValue, srcValue, key, object, source, stack).
+ *
+ * **Note:** This method mutates `object`.
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The destination object.
+ * @param {...Object} sources The source objects.
+ * @param {Function} customizer The function to customize assigned values.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * function customizer(objValue, srcValue) {
+ * if (_.isArray(objValue)) {
+ * return objValue.concat(srcValue);
+ * }
+ * }
+ *
+ * var object = {
+ * 'fruits': ['apple'],
+ * 'vegetables': ['beet']
+ * };
+ *
+ * var other = {
+ * 'fruits': ['banana'],
+ * 'vegetables': ['carrot']
+ * };
+ *
+ * _.mergeWith(object, other, customizer);
+ * // => { 'fruits': ['apple', 'banana'], 'vegetables': ['beet', 'carrot'] }
+ */
+ var mergeWith = createAssigner(function(object, source, srcIndex, customizer) {
+ baseMerge(object, source, srcIndex, customizer);
+ });
+
+ /**
+ * The opposite of `_.pick`; this method creates an object composed of the
+ * own and inherited enumerable properties of `object` that are not omitted.
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The source object.
+ * @param {...(string|string[])} [props] The property names to omit, specified
+ * individually or in arrays.
+ * @returns {Object} Returns the new object.
+ * @example
+ *
+ * var object = { 'a': 1, 'b': '2', 'c': 3 };
+ *
+ * _.omit(object, ['a', 'c']);
+ * // => { 'b': '2' }
+ */
+ var omit = rest(function(object, props) {
+ if (object == null) {
+ return {};
+ }
+ props = arrayMap(baseFlatten(props, 1), String);
+ return basePick(object, baseDifference(keysIn(object), props));
+ });
+
+ /**
+ * The opposite of `_.pickBy`; this method creates an object composed of
+ * the own and inherited enumerable properties of `object` that `predicate`
+ * doesn't return truthy for. The predicate is invoked with two arguments:
+ * (value, key).
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The source object.
+ * @param {Function|Object|string} [predicate=_.identity] The function invoked per property.
+ * @returns {Object} Returns the new object.
+ * @example
+ *
+ * var object = { 'a': 1, 'b': '2', 'c': 3 };
+ *
+ * _.omitBy(object, _.isNumber);
+ * // => { 'b': '2' }
+ */
+ function omitBy(object, predicate) {
+ predicate = getIteratee(predicate);
+ return basePickBy(object, function(value, key) {
+ return !predicate(value, key);
+ });
+ }
+
+ /**
+ * Creates an object composed of the picked `object` properties.
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The source object.
+ * @param {...(string|string[])} [props] The property names to pick, specified
+ * individually or in arrays.
+ * @returns {Object} Returns the new object.
+ * @example
+ *
+ * var object = { 'a': 1, 'b': '2', 'c': 3 };
+ *
+ * _.pick(object, ['a', 'c']);
+ * // => { 'a': 1, 'c': 3 }
+ */
+ var pick = rest(function(object, props) {
+ return object == null ? {} : basePick(object, baseFlatten(props, 1));
+ });
+
+ /**
+ * Creates an object composed of the `object` properties `predicate` returns
+ * truthy for. The predicate is invoked with two arguments: (value, key).
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The source object.
+ * @param {Function|Object|string} [predicate=_.identity] The function invoked per property.
+ * @returns {Object} Returns the new object.
+ * @example
+ *
+ * var object = { 'a': 1, 'b': '2', 'c': 3 };
+ *
+ * _.pickBy(object, _.isNumber);
+ * // => { 'a': 1, 'c': 3 }
+ */
+ function pickBy(object, predicate) {
+ return object == null ? {} : basePickBy(object, getIteratee(predicate));
+ }
+
+ /**
+ * This method is like `_.get` except that if the resolved value is a function
+ * it's invoked with the `this` binding of its parent object and its result
+ * is returned.
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to query.
+ * @param {Array|string} path The path of the property to resolve.
+ * @param {*} [defaultValue] The value returned if the resolved value is `undefined`.
+ * @returns {*} Returns the resolved value.
+ * @example
+ *
+ * var object = { 'a': [{ 'b': { 'c1': 3, 'c2': _.constant(4) } }] };
+ *
+ * _.result(object, 'a[0].b.c1');
+ * // => 3
+ *
+ * _.result(object, 'a[0].b.c2');
+ * // => 4
+ *
+ * _.result(object, 'a[0].b.c3', 'default');
+ * // => 'default'
+ *
+ * _.result(object, 'a[0].b.c3', _.constant('default'));
+ * // => 'default'
+ */
+ function result(object, path, defaultValue) {
+ if (!isKey(path, object)) {
+ path = baseCastPath(path);
+ var result = get(object, path);
+ object = parent(object, path);
+ } else {
+ result = object == null ? undefined : object[path];
+ }
+ if (result === undefined) {
+ result = defaultValue;
+ }
+ return isFunction(result) ? result.call(object) : result;
+ }
+
+ /**
+ * Sets the value at `path` of `object`. If a portion of `path` doesn't exist
+ * it's created. Arrays are created for missing index properties while objects
+ * are created for all other missing properties. Use `_.setWith` to customize
+ * `path` creation.
+ *
+ * **Note:** This method mutates `object`.
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to modify.
+ * @param {Array|string} path The path of the property to set.
+ * @param {*} value The value to set.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * var object = { 'a': [{ 'b': { 'c': 3 } }] };
+ *
+ * _.set(object, 'a[0].b.c', 4);
+ * console.log(object.a[0].b.c);
+ * // => 4
+ *
+ * _.set(object, 'x[0].y.z', 5);
+ * console.log(object.x[0].y.z);
+ * // => 5
+ */
+ function set(object, path, value) {
+ return object == null ? object : baseSet(object, path, value);
+ }
+
+ /**
+ * This method is like `_.set` except that it accepts `customizer` which is
+ * invoked to produce the objects of `path`. If `customizer` returns `undefined`
+ * path creation is handled by the method instead. The `customizer` is invoked
+ * with three arguments: (nsValue, key, nsObject).
+ *
+ * **Note:** This method mutates `object`.
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to modify.
+ * @param {Array|string} path The path of the property to set.
+ * @param {*} value The value to set.
+ * @param {Function} [customizer] The function to customize assigned values.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * var object = {};
+ *
+ * _.setWith(object, '[0][1]', 'a', Object);
+ * // => { '0': { '1': 'a' } }
+ */
+ function setWith(object, path, value, customizer) {
+ customizer = typeof customizer == 'function' ? customizer : undefined;
+ return object == null ? object : baseSet(object, path, value, customizer);
+ }
+
+ /**
+ * Creates an array of own enumerable key-value pairs for `object` which
+ * can be consumed by `_.fromPairs`.
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the new array of key-value pairs.
+ * @example
+ *
+ * function Foo() {
+ * this.a = 1;
+ * this.b = 2;
+ * }
+ *
+ * Foo.prototype.c = 3;
+ *
+ * _.toPairs(new Foo);
+ * // => [['a', 1], ['b', 2]] (iteration order is not guaranteed)
+ */
+ function toPairs(object) {
+ return baseToPairs(object, keys(object));
+ }
+
+ /**
+ * Creates an array of own and inherited enumerable key-value pairs for
+ * `object` which can be consumed by `_.fromPairs`.
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the new array of key-value pairs.
+ * @example
+ *
+ * function Foo() {
+ * this.a = 1;
+ * this.b = 2;
+ * }
+ *
+ * Foo.prototype.c = 3;
+ *
+ * _.toPairsIn(new Foo);
+ * // => [['a', 1], ['b', 2], ['c', 1]] (iteration order is not guaranteed)
+ */
+ function toPairsIn(object) {
+ return baseToPairs(object, keysIn(object));
+ }
+
+ /**
+ * An alternative to `_.reduce`; this method transforms `object` to a new
+ * `accumulator` object which is the result of running each of its own enumerable
+ * properties through `iteratee`, with each invocation potentially mutating
+ * the `accumulator` object. The iteratee is invoked with four arguments:
+ * (accumulator, value, key, object). Iteratee functions may exit iteration
+ * early by explicitly returning `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Array|Object} object The object to iterate over.
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+ * @param {*} [accumulator] The custom accumulator value.
+ * @returns {*} Returns the accumulated value.
+ * @example
+ *
+ * _.transform([2, 3, 4], function(result, n) {
+ * result.push(n *= n);
+ * return n % 2 == 0;
+ * }, []);
+ * // => [4, 9]
+ *
+ * _.transform({ 'a': 1, 'b': 2, 'c': 1 }, function(result, value, key) {
+ * (result[value] || (result[value] = [])).push(key);
+ * }, {});
+ * // => { '1': ['a', 'c'], '2': ['b'] }
+ */
+ function transform(object, iteratee, accumulator) {
+ var isArr = isArray(object) || isTypedArray(object);
+ iteratee = getIteratee(iteratee, 4);
+
+ if (accumulator == null) {
+ if (isArr || isObject(object)) {
+ var Ctor = object.constructor;
+ if (isArr) {
+ accumulator = isArray(object) ? new Ctor : [];
+ } else {
+ accumulator = isFunction(Ctor) ? baseCreate(getPrototypeOf(object)) : {};
+ }
+ } else {
+ accumulator = {};
+ }
+ }
+ (isArr ? arrayEach : baseForOwn)(object, function(value, index, object) {
+ return iteratee(accumulator, value, index, object);
+ });
+ return accumulator;
+ }
+
+ /**
+ * Removes the property at `path` of `object`.
+ *
+ * **Note:** This method mutates `object`.
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to modify.
+ * @param {Array|string} path The path of the property to unset.
+ * @returns {boolean} Returns `true` if the property is deleted, else `false`.
+ * @example
+ *
+ * var object = { 'a': [{ 'b': { 'c': 7 } }] };
+ * _.unset(object, 'a[0].b.c');
+ * // => true
+ *
+ * console.log(object);
+ * // => { 'a': [{ 'b': {} }] };
+ *
+ * _.unset(object, 'a[0].b.c');
+ * // => true
+ *
+ * console.log(object);
+ * // => { 'a': [{ 'b': {} }] };
+ */
+ function unset(object, path) {
+ return object == null ? true : baseUnset(object, path);
+ }
+
+ /**
+ * This method is like `_.set` except that accepts `updater` to produce the
+ * value to set. Use `_.updateWith` to customize `path` creation. The `updater`
+ * is invoked with one argument: (value).
+ *
+ * **Note:** This method mutates `object`.
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to modify.
+ * @param {Array|string} path The path of the property to set.
+ * @param {Function} updater The function to produce the updated value.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * var object = { 'a': [{ 'b': { 'c': 3 } }] };
+ *
+ * _.update(object, 'a[0].b.c', function(n) { return n * n; });
+ * console.log(object.a[0].b.c);
+ * // => 9
+ *
+ * _.update(object, 'x[0].y.z', function(n) { return n ? n + 1 : 0; });
+ * console.log(object.x[0].y.z);
+ * // => 0
+ */
+ function update(object, path, updater) {
+ return object == null ? object : baseUpdate(object, path, baseCastFunction(updater));
+ }
+
+ /**
+ * This method is like `_.update` except that it accepts `customizer` which is
+ * invoked to produce the objects of `path`. If `customizer` returns `undefined`
+ * path creation is handled by the method instead. The `customizer` is invoked
+ * with three arguments: (nsValue, key, nsObject).
+ *
+ * **Note:** This method mutates `object`.
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to modify.
+ * @param {Array|string} path The path of the property to set.
+ * @param {Function} updater The function to produce the updated value.
+ * @param {Function} [customizer] The function to customize assigned values.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * var object = {};
+ *
+ * _.updateWith(object, '[0][1]', _.constant('a'), Object);
+ * // => { '0': { '1': 'a' } }
+ */
+ function updateWith(object, path, updater, customizer) {
+ customizer = typeof customizer == 'function' ? customizer : undefined;
+ return object == null ? object : baseUpdate(object, path, baseCastFunction(updater), customizer);
+ }
+
+ /**
+ * Creates an array of the own enumerable property values of `object`.
+ *
+ * **Note:** Non-object values are coerced to objects.
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the array of property values.
+ * @example
+ *
+ * function Foo() {
+ * this.a = 1;
+ * this.b = 2;
+ * }
+ *
+ * Foo.prototype.c = 3;
+ *
+ * _.values(new Foo);
+ * // => [1, 2] (iteration order is not guaranteed)
+ *
+ * _.values('hi');
+ * // => ['h', 'i']
+ */
+ function values(object) {
+ return object ? baseValues(object, keys(object)) : [];
+ }
+
+ /**
+ * Creates an array of the own and inherited enumerable property values of `object`.
+ *
+ * **Note:** Non-object values are coerced to objects.
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the array of property values.
+ * @example
+ *
+ * function Foo() {
+ * this.a = 1;
+ * this.b = 2;
+ * }
+ *
+ * Foo.prototype.c = 3;
+ *
+ * _.valuesIn(new Foo);
+ * // => [1, 2, 3] (iteration order is not guaranteed)
+ */
+ function valuesIn(object) {
+ return object == null ? [] : baseValues(object, keysIn(object));
+ }
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Clamps `number` within the inclusive `lower` and `upper` bounds.
+ *
+ * @static
+ * @memberOf _
+ * @category Number
+ * @param {number} number The number to clamp.
+ * @param {number} [lower] The lower bound.
+ * @param {number} upper The upper bound.
+ * @returns {number} Returns the clamped number.
+ * @example
+ *
+ * _.clamp(-10, -5, 5);
+ * // => -5
+ *
+ * _.clamp(10, -5, 5);
+ * // => 5
+ */
+ function clamp(number, lower, upper) {
+ if (upper === undefined) {
+ upper = lower;
+ lower = undefined;
+ }
+ if (upper !== undefined) {
+ upper = toNumber(upper);
+ upper = upper === upper ? upper : 0;
+ }
+ if (lower !== undefined) {
+ lower = toNumber(lower);
+ lower = lower === lower ? lower : 0;
+ }
+ return baseClamp(toNumber(number), lower, upper);
+ }
+
+ /**
+ * Checks if `n` is between `start` and up to but not including, `end`. If
+ * `end` is not specified it's set to `start` with `start` then set to `0`.
+ * If `start` is greater than `end` the params are swapped to support
+ * negative ranges.
+ *
+ * @static
+ * @memberOf _
+ * @category Number
+ * @param {number} number The number to check.
+ * @param {number} [start=0] The start of the range.
+ * @param {number} end The end of the range.
+ * @returns {boolean} Returns `true` if `number` is in the range, else `false`.
+ * @example
+ *
+ * _.inRange(3, 2, 4);
+ * // => true
+ *
+ * _.inRange(4, 8);
+ * // => true
+ *
+ * _.inRange(4, 2);
+ * // => false
+ *
+ * _.inRange(2, 2);
+ * // => false
+ *
+ * _.inRange(1.2, 2);
+ * // => true
+ *
+ * _.inRange(5.2, 4);
+ * // => false
+ *
+ * _.inRange(-3, -2, -6);
+ * // => true
+ */
+ function inRange(number, start, end) {
+ start = toNumber(start) || 0;
+ if (end === undefined) {
+ end = start;
+ start = 0;
+ } else {
+ end = toNumber(end) || 0;
+ }
+ number = toNumber(number);
+ return baseInRange(number, start, end);
+ }
+
+ /**
+ * Produces a random number between the inclusive `lower` and `upper` bounds.
+ * If only one argument is provided a number between `0` and the given number
+ * is returned. If `floating` is `true`, or either `lower` or `upper` are floats,
+ * a floating-point number is returned instead of an integer.
+ *
+ * **Note:** JavaScript follows the IEEE-754 standard for resolving
+ * floating-point values which can produce unexpected results.
+ *
+ * @static
+ * @memberOf _
+ * @category Number
+ * @param {number} [lower=0] The lower bound.
+ * @param {number} [upper=1] The upper bound.
+ * @param {boolean} [floating] Specify returning a floating-point number.
+ * @returns {number} Returns the random number.
+ * @example
+ *
+ * _.random(0, 5);
+ * // => an integer between 0 and 5
+ *
+ * _.random(5);
+ * // => also an integer between 0 and 5
+ *
+ * _.random(5, true);
+ * // => a floating-point number between 0 and 5
+ *
+ * _.random(1.2, 5.2);
+ * // => a floating-point number between 1.2 and 5.2
+ */
+ function random(lower, upper, floating) {
+ if (floating && typeof floating != 'boolean' && isIterateeCall(lower, upper, floating)) {
+ upper = floating = undefined;
+ }
+ if (floating === undefined) {
+ if (typeof upper == 'boolean') {
+ floating = upper;
+ upper = undefined;
+ }
+ else if (typeof lower == 'boolean') {
+ floating = lower;
+ lower = undefined;
+ }
+ }
+ if (lower === undefined && upper === undefined) {
+ lower = 0;
+ upper = 1;
+ }
+ else {
+ lower = toNumber(lower) || 0;
+ if (upper === undefined) {
+ upper = lower;
+ lower = 0;
+ } else {
+ upper = toNumber(upper) || 0;
+ }
+ }
+ if (lower > upper) {
+ var temp = lower;
+ lower = upper;
+ upper = temp;
+ }
+ if (floating || lower % 1 || upper % 1) {
+ var rand = nativeRandom();
+ return nativeMin(lower + (rand * (upper - lower + freeParseFloat('1e-' + ((rand + '').length - 1)))), upper);
+ }
+ return baseRandom(lower, upper);
+ }
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Converts `string` to [camel case](https://en.wikipedia.org/wiki/CamelCase).
+ *
+ * @static
+ * @memberOf _
+ * @category String
+ * @param {string} [string=''] The string to convert.
+ * @returns {string} Returns the camel cased string.
+ * @example
+ *
+ * _.camelCase('Foo Bar');
+ * // => 'fooBar'
+ *
+ * _.camelCase('--foo-bar');
+ * // => 'fooBar'
+ *
+ * _.camelCase('__foo_bar__');
+ * // => 'fooBar'
+ */
+ var camelCase = createCompounder(function(result, word, index) {
+ word = word.toLowerCase();
+ return result + (index ? capitalize(word) : word);
+ });
+
+ /**
+ * Converts the first character of `string` to upper case and the remaining
+ * to lower case.
+ *
+ * @static
+ * @memberOf _
+ * @category String
+ * @param {string} [string=''] The string to capitalize.
+ * @returns {string} Returns the capitalized string.
+ * @example
+ *
+ * _.capitalize('FRED');
+ * // => 'Fred'
+ */
+ function capitalize(string) {
+ return upperFirst(toString(string).toLowerCase());
+ }
+
+ /**
+ * Deburrs `string` by converting [latin-1 supplementary letters](https://en.wikipedia.org/wiki/Latin-1_Supplement_(Unicode_block)#Character_table)
+ * to basic latin letters and removing [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks).
+ *
+ * @static
+ * @memberOf _
+ * @category String
+ * @param {string} [string=''] The string to deburr.
+ * @returns {string} Returns the deburred string.
+ * @example
+ *
+ * _.deburr('déjà vu');
+ * // => 'deja vu'
+ */
+ function deburr(string) {
+ string = toString(string);
+ return string && string.replace(reLatin1, deburrLetter).replace(reComboMark, '');
+ }
+
+ /**
+ * Checks if `string` ends with the given target string.
+ *
+ * @static
+ * @memberOf _
+ * @category String
+ * @param {string} [string=''] The string to search.
+ * @param {string} [target] The string to search for.
+ * @param {number} [position=string.length] The position to search from.
+ * @returns {boolean} Returns `true` if `string` ends with `target`, else `false`.
+ * @example
+ *
+ * _.endsWith('abc', 'c');
+ * // => true
+ *
+ * _.endsWith('abc', 'b');
+ * // => false
+ *
+ * _.endsWith('abc', 'b', 2);
+ * // => true
+ */
+ function endsWith(string, target, position) {
+ string = toString(string);
+ target = typeof target == 'string' ? target : (target + '');
+
+ var length = string.length;
+ position = position === undefined
+ ? length
+ : baseClamp(toInteger(position), 0, length);
+
+ position -= target.length;
+ return position >= 0 && string.indexOf(target, position) == position;
+ }
+
+ /**
+ * Converts the characters "&", "<", ">", '"', "'", and "\`" in `string` to
+ * their corresponding HTML entities.
+ *
+ * **Note:** No other characters are escaped. To escape additional
+ * characters use a third-party library like [_he_](https://mths.be/he).
+ *
+ * Though the ">" character is escaped for symmetry, characters like
+ * ">" and "/" don't need escaping in HTML and have no special meaning
+ * unless they're part of a tag or unquoted attribute value.
+ * See [Mathias Bynens's article](https://mathiasbynens.be/notes/ambiguous-ampersands)
+ * (under "semi-related fun fact") for more details.
+ *
+ * Backticks are escaped because in IE < 9, they can break out of
+ * attribute values or HTML comments. See [#59](https://html5sec.org/#59),
+ * [#102](https://html5sec.org/#102), [#108](https://html5sec.org/#108), and
+ * [#133](https://html5sec.org/#133) of the [HTML5 Security Cheatsheet](https://html5sec.org/)
+ * for more details.
+ *
+ * When working with HTML you should always [quote attribute values](http://wonko.com/post/html-escaping)
+ * to reduce XSS vectors.
+ *
+ * @static
+ * @memberOf _
+ * @category String
+ * @param {string} [string=''] The string to escape.
+ * @returns {string} Returns the escaped string.
+ * @example
+ *
+ * _.escape('fred, barney, & pebbles');
+ * // => 'fred, barney, & pebbles'
+ */
+ function escape(string) {
+ string = toString(string);
+ return (string && reHasUnescapedHtml.test(string))
+ ? string.replace(reUnescapedHtml, escapeHtmlChar)
+ : string;
+ }
+
+ /**
+ * Escapes the `RegExp` special characters "^", "$", "\", ".", "*", "+",
+ * "?", "(", ")", "[", "]", "{", "}", and "|" in `string`.
+ *
+ * @static
+ * @memberOf _
+ * @category String
+ * @param {string} [string=''] The string to escape.
+ * @returns {string} Returns the escaped string.
+ * @example
+ *
+ * _.escapeRegExp('[lodash](https://lodash.com/)');
+ * // => '\[lodash\]\(https://lodash\.com/\)'
+ */
+ function escapeRegExp(string) {
+ string = toString(string);
+ return (string && reHasRegExpChar.test(string))
+ ? string.replace(reRegExpChar, '\\$&')
+ : string;
+ }
+
+ /**
+ * Converts `string` to [kebab case](https://en.wikipedia.org/wiki/Letter_case#Special_case_styles).
+ *
+ * @static
+ * @memberOf _
+ * @category String
+ * @param {string} [string=''] The string to convert.
+ * @returns {string} Returns the kebab cased string.
+ * @example
+ *
+ * _.kebabCase('Foo Bar');
+ * // => 'foo-bar'
+ *
+ * _.kebabCase('fooBar');
+ * // => 'foo-bar'
+ *
+ * _.kebabCase('__foo_bar__');
+ * // => 'foo-bar'
+ */
+ var kebabCase = createCompounder(function(result, word, index) {
+ return result + (index ? '-' : '') + word.toLowerCase();
+ });
+
+ /**
+ * Converts `string`, as space separated words, to lower case.
+ *
+ * @static
+ * @memberOf _
+ * @category String
+ * @param {string} [string=''] The string to convert.
+ * @returns {string} Returns the lower cased string.
+ * @example
+ *
+ * _.lowerCase('--Foo-Bar');
+ * // => 'foo bar'
+ *
+ * _.lowerCase('fooBar');
+ * // => 'foo bar'
+ *
+ * _.lowerCase('__FOO_BAR__');
+ * // => 'foo bar'
+ */
+ var lowerCase = createCompounder(function(result, word, index) {
+ return result + (index ? ' ' : '') + word.toLowerCase();
+ });
+
+ /**
+ * Converts the first character of `string` to lower case.
+ *
+ * @static
+ * @memberOf _
+ * @category String
+ * @param {string} [string=''] The string to convert.
+ * @returns {string} Returns the converted string.
+ * @example
+ *
+ * _.lowerFirst('Fred');
+ * // => 'fred'
+ *
+ * _.lowerFirst('FRED');
+ * // => 'fRED'
+ */
+ var lowerFirst = createCaseFirst('toLowerCase');
+
+ /**
+ * Converts the first character of `string` to upper case.
+ *
+ * @static
+ * @memberOf _
+ * @category String
+ * @param {string} [string=''] The string to convert.
+ * @returns {string} Returns the converted string.
+ * @example
+ *
+ * _.upperFirst('fred');
+ * // => 'Fred'
+ *
+ * _.upperFirst('FRED');
+ * // => 'FRED'
+ */
+ var upperFirst = createCaseFirst('toUpperCase');
+
+ /**
+ * Pads `string` on the left and right sides if it's shorter than `length`.
+ * Padding characters are truncated if they can't be evenly divided by `length`.
+ *
+ * @static
+ * @memberOf _
+ * @category String
+ * @param {string} [string=''] The string to pad.
+ * @param {number} [length=0] The padding length.
+ * @param {string} [chars=' '] The string used as padding.
+ * @returns {string} Returns the padded string.
+ * @example
+ *
+ * _.pad('abc', 8);
+ * // => ' abc '
+ *
+ * _.pad('abc', 8, '_-');
+ * // => '_-abc_-_'
+ *
+ * _.pad('abc', 3);
+ * // => 'abc'
+ */
+ function pad(string, length, chars) {
+ string = toString(string);
+ length = toInteger(length);
+
+ var strLength = stringSize(string);
+ if (!length || strLength >= length) {
+ return string;
+ }
+ var mid = (length - strLength) / 2,
+ leftLength = nativeFloor(mid),
+ rightLength = nativeCeil(mid);
+
+ return createPadding('', leftLength, chars) + string + createPadding('', rightLength, chars);
+ }
+
+ /**
+ * Pads `string` on the right side if it's shorter than `length`. Padding
+ * characters are truncated if they exceed `length`.
+ *
+ * @static
+ * @memberOf _
+ * @category String
+ * @param {string} [string=''] The string to pad.
+ * @param {number} [length=0] The padding length.
+ * @param {string} [chars=' '] The string used as padding.
+ * @returns {string} Returns the padded string.
+ * @example
+ *
+ * _.padEnd('abc', 6);
+ * // => 'abc '
+ *
+ * _.padEnd('abc', 6, '_-');
+ * // => 'abc_-_'
+ *
+ * _.padEnd('abc', 3);
+ * // => 'abc'
+ */
+ function padEnd(string, length, chars) {
+ string = toString(string);
+ return string + createPadding(string, length, chars);
+ }
+
+ /**
+ * Pads `string` on the left side if it's shorter than `length`. Padding
+ * characters are truncated if they exceed `length`.
+ *
+ * @static
+ * @memberOf _
+ * @category String
+ * @param {string} [string=''] The string to pad.
+ * @param {number} [length=0] The padding length.
+ * @param {string} [chars=' '] The string used as padding.
+ * @returns {string} Returns the padded string.
+ * @example
+ *
+ * _.padStart('abc', 6);
+ * // => ' abc'
+ *
+ * _.padStart('abc', 6, '_-');
+ * // => '_-_abc'
+ *
+ * _.padStart('abc', 3);
+ * // => 'abc'
+ */
+ function padStart(string, length, chars) {
+ string = toString(string);
+ return createPadding(string, length, chars) + string;
+ }
+
+ /**
+ * Converts `string` to an integer of the specified radix. If `radix` is
+ * `undefined` or `0`, a `radix` of `10` is used unless `value` is a hexadecimal,
+ * in which case a `radix` of `16` is used.
+ *
+ * **Note:** This method aligns with the [ES5 implementation](https://es5.github.io/#x15.1.2.2)
+ * of `parseInt`.
+ *
+ * @static
+ * @memberOf _
+ * @category String
+ * @param {string} string The string to convert.
+ * @param {number} [radix=10] The radix to interpret `value` by.
+ * @param- {Object} [guard] Enables use as an iteratee for functions like `_.map`.
+ * @returns {number} Returns the converted integer.
+ * @example
+ *
+ * _.parseInt('08');
+ * // => 8
+ *
+ * _.map(['6', '08', '10'], _.parseInt);
+ * // => [6, 8, 10]
+ */
+ function parseInt(string, radix, guard) {
+ // Chrome fails to trim leading <BOM> whitespace characters.
+ // See https://code.google.com/p/v8/issues/detail?id=3109 for more details.
+ if (guard || radix == null) {
+ radix = 0;
+ } else if (radix) {
+ radix = +radix;
+ }
+ string = toString(string).replace(reTrim, '');
+ return nativeParseInt(string, radix || (reHasHexPrefix.test(string) ? 16 : 10));
+ }
+
+ /**
+ * Repeats the given string `n` times.
+ *
+ * @static
+ * @memberOf _
+ * @category String
+ * @param {string} [string=''] The string to repeat.
+ * @param {number} [n=0] The number of times to repeat the string.
+ * @returns {string} Returns the repeated string.
+ * @example
+ *
+ * _.repeat('*', 3);
+ * // => '***'
+ *
+ * _.repeat('abc', 2);
+ * // => 'abcabc'
+ *
+ * _.repeat('abc', 0);
+ * // => ''
+ */
+ function repeat(string, n) {
+ string = toString(string);
+ n = toInteger(n);
+
+ var result = '';
+ if (!string || n < 1 || n > MAX_SAFE_INTEGER) {
+ return result;
+ }
+ // Leverage the exponentiation by squaring algorithm for a faster repeat.
+ // See https://en.wikipedia.org/wiki/Exponentiation_by_squaring for more details.
+ do {
+ if (n % 2) {
+ result += string;
+ }
+ n = nativeFloor(n / 2);
+ string += string;
+ } while (n);
+
+ return result;
+ }
+
+ /**
+ * Replaces matches for `pattern` in `string` with `replacement`.
+ *
+ * **Note:** This method is based on [`String#replace`](https://mdn.io/String/replace).
+ *
+ * @static
+ * @memberOf _
+ * @category String
+ * @param {string} [string=''] The string to modify.
+ * @param {RegExp|string} pattern The pattern to replace.
+ * @param {Function|string} replacement The match replacement.
+ * @returns {string} Returns the modified string.
+ * @example
+ *
+ * _.replace('Hi Fred', 'Fred', 'Barney');
+ * // => 'Hi Barney'
+ */
+ function replace() {
+ var args = arguments,
+ string = toString(args[0]);
+
+ return args.length < 3 ? string : string.replace(args[1], args[2]);
+ }
+
+ /**
+ * Converts `string` to [snake case](https://en.wikipedia.org/wiki/Snake_case).
+ *
+ * @static
+ * @memberOf _
+ * @category String
+ * @param {string} [string=''] The string to convert.
+ * @returns {string} Returns the snake cased string.
+ * @example
+ *
+ * _.snakeCase('Foo Bar');
+ * // => 'foo_bar'
+ *
+ * _.snakeCase('fooBar');
+ * // => 'foo_bar'
+ *
+ * _.snakeCase('--foo-bar');
+ * // => 'foo_bar'
+ */
+ var snakeCase = createCompounder(function(result, word, index) {
+ return result + (index ? '_' : '') + word.toLowerCase();
+ });
+
+ /**
+ * Splits `string` by `separator`.
+ *
+ * **Note:** This method is based on [`String#split`](https://mdn.io/String/split).
+ *
+ * @static
+ * @memberOf _
+ * @category String
+ * @param {string} [string=''] The string to split.
+ * @param {RegExp|string} separator The separator pattern to split by.
+ * @param {number} [limit] The length to truncate results to.
+ * @returns {Array} Returns the new array of string segments.
+ * @example
+ *
+ * _.split('a-b-c', '-', 2);
+ * // => ['a', 'b']
+ */
+ function split(string, separator, limit) {
+ return toString(string).split(separator, limit);
+ }
+
+ /**
+ * Converts `string` to [start case](https://en.wikipedia.org/wiki/Letter_case#Stylistic_or_specialised_usage).
+ *
+ * @static
+ * @memberOf _
+ * @category String
+ * @param {string} [string=''] The string to convert.
+ * @returns {string} Returns the start cased string.
+ * @example
+ *
+ * _.startCase('--foo-bar');
+ * // => 'Foo Bar'
+ *
+ * _.startCase('fooBar');
+ * // => 'Foo Bar'
+ *
+ * _.startCase('__foo_bar__');
+ * // => 'Foo Bar'
+ */
+ var startCase = createCompounder(function(result, word, index) {
+ return result + (index ? ' ' : '') + capitalize(word);
+ });
+
+ /**
+ * Checks if `string` starts with the given target string.
+ *
+ * @static
+ * @memberOf _
+ * @category String
+ * @param {string} [string=''] The string to search.
+ * @param {string} [target] The string to search for.
+ * @param {number} [position=0] The position to search from.
+ * @returns {boolean} Returns `true` if `string` starts with `target`, else `false`.
+ * @example
+ *
+ * _.startsWith('abc', 'a');
+ * // => true
+ *
+ * _.startsWith('abc', 'b');
+ * // => false
+ *
+ * _.startsWith('abc', 'b', 1);
+ * // => true
+ */
+ function startsWith(string, target, position) {
+ string = toString(string);
+ position = baseClamp(toInteger(position), 0, string.length);
+ return string.lastIndexOf(target, position) == position;
+ }
+
+ /**
+ * Creates a compiled template function that can interpolate data properties
+ * in "interpolate" delimiters, HTML-escape interpolated data properties in
+ * "escape" delimiters, and execute JavaScript in "evaluate" delimiters. Data
+ * properties may be accessed as free variables in the template. If a setting
+ * object is given it takes precedence over `_.templateSettings` values.
+ *
+ * **Note:** In the development build `_.template` utilizes
+ * [sourceURLs](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl)
+ * for easier debugging.
+ *
+ * For more information on precompiling templates see
+ * [lodash's custom builds documentation](https://lodash.com/custom-builds).
+ *
+ * For more information on Chrome extension sandboxes see
+ * [Chrome's extensions documentation](https://developer.chrome.com/extensions/sandboxingEval).
+ *
+ * @static
+ * @memberOf _
+ * @category String
+ * @param {string} [string=''] The template string.
+ * @param {Object} [options] The options object.
+ * @param {RegExp} [options.escape] The HTML "escape" delimiter.
+ * @param {RegExp} [options.evaluate] The "evaluate" delimiter.
+ * @param {Object} [options.imports] An object to import into the template as free variables.
+ * @param {RegExp} [options.interpolate] The "interpolate" delimiter.
+ * @param {string} [options.sourceURL] The sourceURL of the template's compiled source.
+ * @param {string} [options.variable] The data object variable name.
+ * @param- {Object} [guard] Enables use as an iteratee for functions like `_.map`.
+ * @returns {Function} Returns the compiled template function.
+ * @example
+ *
+ * // Use the "interpolate" delimiter to create a compiled template.
+ * var compiled = _.template('hello <%= user %>!');
+ * compiled({ 'user': 'fred' });
+ * // => 'hello fred!'
+ *
+ * // Use the HTML "escape" delimiter to escape data property values.
+ * var compiled = _.template('<b><%- value %></b>');
+ * compiled({ 'value': '<script>' });
+ * // => '<b><script></b>'
+ *
+ * // Use the "evaluate" delimiter to execute JavaScript and generate HTML.
+ * var compiled = _.template('<% _.forEach(users, function(user) { %><li><%- user %></li><% }); %>');
+ * compiled({ 'users': ['fred', 'barney'] });
+ * // => '<li>fred</li><li>barney</li>'
+ *
+ * // Use the internal `print` function in "evaluate" delimiters.
+ * var compiled = _.template('<% print("hello " + user); %>!');
+ * compiled({ 'user': 'barney' });
+ * // => 'hello barney!'
+ *
+ * // Use the ES delimiter as an alternative to the default "interpolate" delimiter.
+ * var compiled = _.template('hello ${ user }!');
+ * compiled({ 'user': 'pebbles' });
+ * // => 'hello pebbles!'
+ *
+ * // Use custom template delimiters.
+ * _.templateSettings.interpolate = /{{([\s\S]+?)}}/g;
+ * var compiled = _.template('hello {{ user }}!');
+ * compiled({ 'user': 'mustache' });
+ * // => 'hello mustache!'
+ *
+ * // Use backslashes to treat delimiters as plain text.
+ * var compiled = _.template('<%= "\\<%- value %\\>" %>');
+ * compiled({ 'value': 'ignored' });
+ * // => '<%- value %>'
+ *
+ * // Use the `imports` option to import `jQuery` as `jq`.
+ * var text = '<% jq.each(users, function(user) { %><li><%- user %></li><% }); %>';
+ * var compiled = _.template(text, { 'imports': { 'jq': jQuery } });
+ * compiled({ 'users': ['fred', 'barney'] });
+ * // => '<li>fred</li><li>barney</li>'
+ *
+ * // Use the `sourceURL` option to specify a custom sourceURL for the template.
+ * var compiled = _.template('hello <%= user %>!', { 'sourceURL': '/basic/greeting.jst' });
+ * compiled(data);
+ * // => find the source of "greeting.jst" under the Sources tab or Resources panel of the web inspector
+ *
+ * // Use the `variable` option to ensure a with-statement isn't used in the compiled template.
+ * var compiled = _.template('hi <%= data.user %>!', { 'variable': 'data' });
+ * compiled.source;
+ * // => function(data) {
+ * // var __t, __p = '';
+ * // __p += 'hi ' + ((__t = ( data.user )) == null ? '' : __t) + '!';
+ * // return __p;
+ * // }
+ *
+ * // Use the `source` property to inline compiled templates for meaningful
+ * // line numbers in error messages and stack traces.
+ * fs.writeFileSync(path.join(cwd, 'jst.js'), '\
+ * var JST = {\
+ * "main": ' + _.template(mainText).source + '\
+ * };\
+ * ');
+ */
+ function template(string, options, guard) {
+ // Based on John Resig's `tmpl` implementation (http://ejohn.org/blog/javascript-micro-templating/)
+ // and Laura Doktorova's doT.js (https://github.com/olado/doT).
+ var settings = lodash.templateSettings;
+
+ if (guard && isIterateeCall(string, options, guard)) {
+ options = undefined;
+ }
+ string = toString(string);
+ options = assignInWith({}, options, settings, assignInDefaults);
+
+ var imports = assignInWith({}, options.imports, settings.imports, assignInDefaults),
+ importsKeys = keys(imports),
+ importsValues = baseValues(imports, importsKeys);
+
+ var isEscaping,
+ isEvaluating,
+ index = 0,
+ interpolate = options.interpolate || reNoMatch,
+ source = "__p += '";
+
+ // Compile the regexp to match each delimiter.
+ var reDelimiters = RegExp(
+ (options.escape || reNoMatch).source + '|' +
+ interpolate.source + '|' +
+ (interpolate === reInterpolate ? reEsTemplate : reNoMatch).source + '|' +
+ (options.evaluate || reNoMatch).source + '|$'
+ , 'g');
+
+ // Use a sourceURL for easier debugging.
+ var sourceURL = '//# sourceURL=' +
+ ('sourceURL' in options
+ ? options.sourceURL
+ : ('lodash.templateSources[' + (++templateCounter) + ']')
+ ) + '\n';
+
+ string.replace(reDelimiters, function(match, escapeValue, interpolateValue, esTemplateValue, evaluateValue, offset) {
+ interpolateValue || (interpolateValue = esTemplateValue);
+
+ // Escape characters that can't be included in string literals.
+ source += string.slice(index, offset).replace(reUnescapedString, escapeStringChar);
+
+ // Replace delimiters with snippets.
+ if (escapeValue) {
+ isEscaping = true;
+ source += "' +\n__e(" + escapeValue + ") +\n'";
+ }
+ if (evaluateValue) {
+ isEvaluating = true;
+ source += "';\n" + evaluateValue + ";\n__p += '";
+ }
+ if (interpolateValue) {
+ source += "' +\n((__t = (" + interpolateValue + ")) == null ? '' : __t) +\n'";
+ }
+ index = offset + match.length;
+
+ // The JS engine embedded in Adobe products needs `match` returned in
+ // order to produce the correct `offset` value.
+ return match;
+ });
+
+ source += "';\n";
+
+ // If `variable` is not specified wrap a with-statement around the generated
+ // code to add the data object to the top of the scope chain.
+ var variable = options.variable;
+ if (!variable) {
+ source = 'with (obj) {\n' + source + '\n}\n';
+ }
+ // Cleanup code by stripping empty strings.
+ source = (isEvaluating ? source.replace(reEmptyStringLeading, '') : source)
+ .replace(reEmptyStringMiddle, '$1')
+ .replace(reEmptyStringTrailing, '$1;');
+
+ // Frame code as the function body.
+ source = 'function(' + (variable || 'obj') + ') {\n' +
+ (variable
+ ? ''
+ : 'obj || (obj = {});\n'
+ ) +
+ "var __t, __p = ''" +
+ (isEscaping
+ ? ', __e = _.escape'
+ : ''
+ ) +
+ (isEvaluating
+ ? ', __j = Array.prototype.join;\n' +
+ "function print() { __p += __j.call(arguments, '') }\n"
+ : ';\n'
+ ) +
+ source +
+ 'return __p\n}';
+
+ var result = attempt(function() {
+ return Function(importsKeys, sourceURL + 'return ' + source)
+ .apply(undefined, importsValues);
+ });
+
+ // Provide the compiled function's source by its `toString` method or
+ // the `source` property as a convenience for inlining compiled templates.
+ result.source = source;
+ if (isError(result)) {
+ throw result;
+ }
+ return result;
+ }
+
+ /**
+ * Converts `string`, as a whole, to lower case just like
+ * [String#toLowerCase](https://mdn.io/toLowerCase).
+ *
+ * @static
+ * @memberOf _
+ * @category String
+ * @param {string} [string=''] The string to convert.
+ * @returns {string} Returns the lower cased string.
+ * @example
+ *
+ * _.toLower('--Foo-Bar');
+ * // => '--foo-bar'
+ *
+ * _.toLower('fooBar');
+ * // => 'foobar'
+ *
+ * _.toLower('__FOO_BAR__');
+ * // => '__foo_bar__'
+ */
+ function toLower(value) {
+ return toString(value).toLowerCase();
+ }
+
+ /**
+ * Converts `string`, as a whole, to upper case just like
+ * [String#toUpperCase](https://mdn.io/toUpperCase).
+ *
+ * @static
+ * @memberOf _
+ * @category String
+ * @param {string} [string=''] The string to convert.
+ * @returns {string} Returns the upper cased string.
+ * @example
+ *
+ * _.toUpper('--foo-bar');
+ * // => '--FOO-BAR'
+ *
+ * _.toUpper('fooBar');
+ * // => 'FOOBAR'
+ *
+ * _.toUpper('__foo_bar__');
+ * // => '__FOO_BAR__'
+ */
+ function toUpper(value) {
+ return toString(value).toUpperCase();
+ }
+
+ /**
+ * Removes leading and trailing whitespace or specified characters from `string`.
+ *
+ * @static
+ * @memberOf _
+ * @category String
+ * @param {string} [string=''] The string to trim.
+ * @param {string} [chars=whitespace] The characters to trim.
+ * @param- {Object} [guard] Enables use as an iteratee for functions like `_.map`.
+ * @returns {string} Returns the trimmed string.
+ * @example
+ *
+ * _.trim(' abc ');
+ * // => 'abc'
+ *
+ * _.trim('-_-abc-_-', '_-');
+ * // => 'abc'
+ *
+ * _.map([' foo ', ' bar '], _.trim);
+ * // => ['foo', 'bar']
+ */
+ function trim(string, chars, guard) {
+ string = toString(string);
+ if (!string) {
+ return string;
+ }
+ if (guard || chars === undefined) {
+ return string.replace(reTrim, '');
+ }
+ chars = (chars + '');
+ if (!chars) {
+ return string;
+ }
+ var strSymbols = stringToArray(string),
+ chrSymbols = stringToArray(chars);
+
+ return strSymbols
+ .slice(charsStartIndex(strSymbols, chrSymbols), charsEndIndex(strSymbols, chrSymbols) + 1)
+ .join('');
+ }
+
+ /**
+ * Removes trailing whitespace or specified characters from `string`.
+ *
+ * @static
+ * @memberOf _
+ * @category String
+ * @param {string} [string=''] The string to trim.
+ * @param {string} [chars=whitespace] The characters to trim.
+ * @param- {Object} [guard] Enables use as an iteratee for functions like `_.map`.
+ * @returns {string} Returns the trimmed string.
+ * @example
+ *
+ * _.trimEnd(' abc ');
+ * // => ' abc'
+ *
+ * _.trimEnd('-_-abc-_-', '_-');
+ * // => '-_-abc'
+ */
+ function trimEnd(string, chars, guard) {
+ string = toString(string);
+ if (!string) {
+ return string;
+ }
+ if (guard || chars === undefined) {
+ return string.replace(reTrimEnd, '');
+ }
+ chars = (chars + '');
+ if (!chars) {
+ return string;
+ }
+ var strSymbols = stringToArray(string);
+ return strSymbols
+ .slice(0, charsEndIndex(strSymbols, stringToArray(chars)) + 1)
+ .join('');
+ }
+
+ /**
+ * Removes leading whitespace or specified characters from `string`.
+ *
+ * @static
+ * @memberOf _
+ * @category String
+ * @param {string} [string=''] The string to trim.
+ * @param {string} [chars=whitespace] The characters to trim.
+ * @param- {Object} [guard] Enables use as an iteratee for functions like `_.map`.
+ * @returns {string} Returns the trimmed string.
+ * @example
+ *
+ * _.trimStart(' abc ');
+ * // => 'abc '
+ *
+ * _.trimStart('-_-abc-_-', '_-');
+ * // => 'abc-_-'
+ */
+ function trimStart(string, chars, guard) {
+ string = toString(string);
+ if (!string) {
+ return string;
+ }
+ if (guard || chars === undefined) {
+ return string.replace(reTrimStart, '');
+ }
+ chars = (chars + '');
+ if (!chars) {
+ return string;
+ }
+ var strSymbols = stringToArray(string);
+ return strSymbols
+ .slice(charsStartIndex(strSymbols, stringToArray(chars)))
+ .join('');
+ }
+
+ /**
+ * Truncates `string` if it's longer than the given maximum string length.
+ * The last characters of the truncated string are replaced with the omission
+ * string which defaults to "...".
+ *
+ * @static
+ * @memberOf _
+ * @category String
+ * @param {string} [string=''] The string to truncate.
+ * @param {Object} [options=({})] The options object.
+ * @param {number} [options.length=30] The maximum string length.
+ * @param {string} [options.omission='...'] The string to indicate text is omitted.
+ * @param {RegExp|string} [options.separator] The separator pattern to truncate to.
+ * @returns {string} Returns the truncated string.
+ * @example
+ *
+ * _.truncate('hi-diddly-ho there, neighborino');
+ * // => 'hi-diddly-ho there, neighbo...'
+ *
+ * _.truncate('hi-diddly-ho there, neighborino', {
+ * 'length': 24,
+ * 'separator': ' '
+ * });
+ * // => 'hi-diddly-ho there,...'
+ *
+ * _.truncate('hi-diddly-ho there, neighborino', {
+ * 'length': 24,
+ * 'separator': /,? +/
+ * });
+ * // => 'hi-diddly-ho there...'
+ *
+ * _.truncate('hi-diddly-ho there, neighborino', {
+ * 'omission': ' [...]'
+ * });
+ * // => 'hi-diddly-ho there, neig [...]'
+ */
+ function truncate(string, options) {
+ var length = DEFAULT_TRUNC_LENGTH,
+ omission = DEFAULT_TRUNC_OMISSION;
+
+ if (isObject(options)) {
+ var separator = 'separator' in options ? options.separator : separator;
+ length = 'length' in options ? toInteger(options.length) : length;
+ omission = 'omission' in options ? toString(options.omission) : omission;
+ }
+ string = toString(string);
+
+ var strLength = string.length;
+ if (reHasComplexSymbol.test(string)) {
+ var strSymbols = stringToArray(string);
+ strLength = strSymbols.length;
+ }
+ if (length >= strLength) {
+ return string;
+ }
+ var end = length - stringSize(omission);
+ if (end < 1) {
+ return omission;
+ }
+ var result = strSymbols
+ ? strSymbols.slice(0, end).join('')
+ : string.slice(0, end);
+
+ if (separator === undefined) {
+ return result + omission;
+ }
+ if (strSymbols) {
+ end += (result.length - end);
+ }
+ if (isRegExp(separator)) {
+ if (string.slice(end).search(separator)) {
+ var match,
+ substring = result;
+
+ if (!separator.global) {
+ separator = RegExp(separator.source, toString(reFlags.exec(separator)) + 'g');
+ }
+ separator.lastIndex = 0;
+ while ((match = separator.exec(substring))) {
+ var newEnd = match.index;
+ }
+ result = result.slice(0, newEnd === undefined ? end : newEnd);
+ }
+ } else if (string.indexOf(separator, end) != end) {
+ var index = result.lastIndexOf(separator);
+ if (index > -1) {
+ result = result.slice(0, index);
+ }
+ }
+ return result + omission;
+ }
+
+ /**
+ * The inverse of `_.escape`; this method converts the HTML entities
+ * `&`, `<`, `>`, `"`, `'`, and ``` in `string` to their
+ * corresponding characters.
+ *
+ * **Note:** No other HTML entities are unescaped. To unescape additional HTML
+ * entities use a third-party library like [_he_](https://mths.be/he).
+ *
+ * @static
+ * @memberOf _
+ * @category String
+ * @param {string} [string=''] The string to unescape.
+ * @returns {string} Returns the unescaped string.
+ * @example
+ *
+ * _.unescape('fred, barney, & pebbles');
+ * // => 'fred, barney, & pebbles'
+ */
+ function unescape(string) {
+ string = toString(string);
+ return (string && reHasEscapedHtml.test(string))
+ ? string.replace(reEscapedHtml, unescapeHtmlChar)
+ : string;
+ }
+
+ /**
+ * Converts `string`, as space separated words, to upper case.
+ *
+ * @static
+ * @memberOf _
+ * @category String
+ * @param {string} [string=''] The string to convert.
+ * @returns {string} Returns the upper cased string.
+ * @example
+ *
+ * _.upperCase('--foo-bar');
+ * // => 'FOO BAR'
+ *
+ * _.upperCase('fooBar');
+ * // => 'FOO BAR'
+ *
+ * _.upperCase('__foo_bar__');
+ * // => 'FOO BAR'
+ */
+ var upperCase = createCompounder(function(result, word, index) {
+ return result + (index ? ' ' : '') + word.toUpperCase();
+ });
+
+ /**
+ * Splits `string` into an array of its words.
+ *
+ * @static
+ * @memberOf _
+ * @category String
+ * @param {string} [string=''] The string to inspect.
+ * @param {RegExp|string} [pattern] The pattern to match words.
+ * @param- {Object} [guard] Enables use as an iteratee for functions like `_.map`.
+ * @returns {Array} Returns the words of `string`.
+ * @example
+ *
+ * _.words('fred, barney, & pebbles');
+ * // => ['fred', 'barney', 'pebbles']
+ *
+ * _.words('fred, barney, & pebbles', /[^, ]+/g);
+ * // => ['fred', 'barney', '&', 'pebbles']
+ */
+ function words(string, pattern, guard) {
+ string = toString(string);
+ pattern = guard ? undefined : pattern;
+
+ if (pattern === undefined) {
+ pattern = reHasComplexWord.test(string) ? reComplexWord : reBasicWord;
+ }
+ return string.match(pattern) || [];
+ }
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Attempts to invoke `func`, returning either the result or the caught error
+ * object. Any additional arguments are provided to `func` when it's invoked.
+ *
+ * @static
+ * @memberOf _
+ * @category Util
+ * @param {Function} func The function to attempt.
+ * @returns {*} Returns the `func` result or error object.
+ * @example
+ *
+ * // Avoid throwing errors for invalid selectors.
+ * var elements = _.attempt(function(selector) {
+ * return document.querySelectorAll(selector);
+ * }, '>_>');
+ *
+ * if (_.isError(elements)) {
+ * elements = [];
+ * }
+ */
+ var attempt = rest(function(func, args) {
+ try {
+ return apply(func, undefined, args);
+ } catch (e) {
+ return isError(e) ? e : new Error(e);
+ }
+ });
+
+ /**
+ * Binds methods of an object to the object itself, overwriting the existing
+ * method.
+ *
+ * **Note:** This method doesn't set the "length" property of bound functions.
+ *
+ * @static
+ * @memberOf _
+ * @category Util
+ * @param {Object} object The object to bind and assign the bound methods to.
+ * @param {...(string|string[])} methodNames The object method names to bind,
+ * specified individually or in arrays.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * var view = {
+ * 'label': 'docs',
+ * 'onClick': function() {
+ * console.log('clicked ' + this.label);
+ * }
+ * };
+ *
+ * _.bindAll(view, 'onClick');
+ * jQuery(element).on('click', view.onClick);
+ * // => logs 'clicked docs' when clicked
+ */
+ var bindAll = rest(function(object, methodNames) {
+ arrayEach(baseFlatten(methodNames, 1), function(key) {
+ object[key] = bind(object[key], object);
+ });
+ return object;
+ });
+
+ /**
+ * Creates a function that iterates over `pairs` invoking the corresponding
+ * function of the first predicate to return truthy. The predicate-function
+ * pairs are invoked with the `this` binding and arguments of the created
+ * function.
+ *
+ * @static
+ * @memberOf _
+ * @category Util
+ * @param {Array} pairs The predicate-function pairs.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var func = _.cond([
+ * [_.matches({ 'a': 1 }), _.constant('matches A')],
+ * [_.conforms({ 'b': _.isNumber }), _.constant('matches B')],
+ * [_.constant(true), _.constant('no match')]
+ * ]);
+ *
+ * func({ 'a': 1, 'b': 2 });
+ * // => 'matches A'
+ *
+ * func({ 'a': 0, 'b': 1 });
+ * // => 'matches B'
+ *
+ * func({ 'a': '1', 'b': '2' });
+ * // => 'no match'
+ */
+ function cond(pairs) {
+ var length = pairs ? pairs.length : 0,
+ toIteratee = getIteratee();
+
+ pairs = !length ? [] : arrayMap(pairs, function(pair) {
+ if (typeof pair[1] != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
+ return [toIteratee(pair[0]), pair[1]];
+ });
+
+ return rest(function(args) {
+ var index = -1;
+ while (++index < length) {
+ var pair = pairs[index];
+ if (apply(pair[0], this, args)) {
+ return apply(pair[1], this, args);
+ }
+ }
+ });
+ }
+
+ /**
+ * Creates a function that invokes the predicate properties of `source` with
+ * the corresponding property values of a given object, returning `true` if
+ * all predicates return truthy, else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Util
+ * @param {Object} source The object of property predicates to conform to.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var users = [
+ * { 'user': 'barney', 'age': 36 },
+ * { 'user': 'fred', 'age': 40 }
+ * ];
+ *
+ * _.filter(users, _.conforms({ 'age': _.partial(_.gt, _, 38) }));
+ * // => [{ 'user': 'fred', 'age': 40 }]
+ */
+ function conforms(source) {
+ return baseConforms(baseClone(source, true));
+ }
+
+ /**
+ * Creates a function that returns `value`.
+ *
+ * @static
+ * @memberOf _
+ * @category Util
+ * @param {*} value The value to return from the new function.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var object = { 'user': 'fred' };
+ * var getter = _.constant(object);
+ *
+ * getter() === object;
+ * // => true
+ */
+ function constant(value) {
+ return function() {
+ return value;
+ };
+ }
+
+ /**
+ * Creates a function that returns the result of invoking the given functions
+ * with the `this` binding of the created function, where each successive
+ * invocation is supplied the return value of the previous.
+ *
+ * @static
+ * @memberOf _
+ * @category Util
+ * @param {...(Function|Function[])} [funcs] Functions to invoke.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * function square(n) {
+ * return n * n;
+ * }
+ *
+ * var addSquare = _.flow(_.add, square);
+ * addSquare(1, 2);
+ * // => 9
+ */
+ var flow = createFlow();
+
+ /**
+ * This method is like `_.flow` except that it creates a function that
+ * invokes the given functions from right to left.
+ *
+ * @static
+ * @memberOf _
+ * @category Util
+ * @param {...(Function|Function[])} [funcs] Functions to invoke.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * function square(n) {
+ * return n * n;
+ * }
+ *
+ * var addSquare = _.flowRight(square, _.add);
+ * addSquare(1, 2);
+ * // => 9
+ */
+ var flowRight = createFlow(true);
+
+ /**
+ * This method returns the first argument given to it.
+ *
+ * @static
+ * @memberOf _
+ * @category Util
+ * @param {*} value Any value.
+ * @returns {*} Returns `value`.
+ * @example
+ *
+ * var object = { 'user': 'fred' };
+ *
+ * _.identity(object) === object;
+ * // => true
+ */
+ function identity(value) {
+ return value;
+ }
+
+ /**
+ * Creates a function that invokes `func` with the arguments of the created
+ * function. If `func` is a property name the created callback returns the
+ * property value for a given element. If `func` is an object the created
+ * callback returns `true` for elements that contain the equivalent object
+ * properties, otherwise it returns `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Util
+ * @param {*} [func=_.identity] The value to convert to a callback.
+ * @returns {Function} Returns the callback.
+ * @example
+ *
+ * var users = [
+ * { 'user': 'barney', 'age': 36 },
+ * { 'user': 'fred', 'age': 40 }
+ * ];
+ *
+ * // Create custom iteratee shorthands.
+ * _.iteratee = _.wrap(_.iteratee, function(callback, func) {
+ * var p = /^(\S+)\s*([<>])\s*(\S+)$/.exec(func);
+ * return !p ? callback(func) : function(object) {
+ * return (p[2] == '>' ? object[p[1]] > p[3] : object[p[1]] < p[3]);
+ * };
+ * });
+ *
+ * _.filter(users, 'age > 36');
+ * // => [{ 'user': 'fred', 'age': 40 }]
+ */
+ function iteratee(func) {
+ return baseIteratee(typeof func == 'function' ? func : baseClone(func, true));
+ }
+
+ /**
+ * Creates a function that performs a partial deep comparison between a given
+ * object and `source`, returning `true` if the given object has equivalent
+ * property values, else `false`. The created function is equivalent to
+ * `_.isMatch` with a `source` partially applied.
+ *
+ * **Note:** This method supports comparing the same values as `_.isEqual`.
+ *
+ * @static
+ * @memberOf _
+ * @category Util
+ * @param {Object} source The object of property values to match.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var users = [
+ * { 'user': 'barney', 'age': 36, 'active': true },
+ * { 'user': 'fred', 'age': 40, 'active': false }
+ * ];
+ *
+ * _.filter(users, _.matches({ 'age': 40, 'active': false }));
+ * // => [{ 'user': 'fred', 'age': 40, 'active': false }]
+ */
+ function matches(source) {
+ return baseMatches(baseClone(source, true));
+ }
+
+ /**
+ * Creates a function that performs a partial deep comparison between the
+ * value at `path` of a given object to `srcValue`, returning `true` if the
+ * object value is equivalent, else `false`.
+ *
+ * **Note:** This method supports comparing the same values as `_.isEqual`.
+ *
+ * @static
+ * @memberOf _
+ * @category Util
+ * @param {Array|string} path The path of the property to get.
+ * @param {*} srcValue The value to match.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var users = [
+ * { 'user': 'barney' },
+ * { 'user': 'fred' }
+ * ];
+ *
+ * _.find(users, _.matchesProperty('user', 'fred'));
+ * // => { 'user': 'fred' }
+ */
+ function matchesProperty(path, srcValue) {
+ return baseMatchesProperty(path, baseClone(srcValue, true));
+ }
+
+ /**
+ * Creates a function that invokes the method at `path` of a given object.
+ * Any additional arguments are provided to the invoked method.
+ *
+ * @static
+ * @memberOf _
+ * @category Util
+ * @param {Array|string} path The path of the method to invoke.
+ * @param {...*} [args] The arguments to invoke the method with.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var objects = [
+ * { 'a': { 'b': { 'c': _.constant(2) } } },
+ * { 'a': { 'b': { 'c': _.constant(1) } } }
+ * ];
+ *
+ * _.map(objects, _.method('a.b.c'));
+ * // => [2, 1]
+ *
+ * _.invokeMap(_.sortBy(objects, _.method(['a', 'b', 'c'])), 'a.b.c');
+ * // => [1, 2]
+ */
+ var method = rest(function(path, args) {
+ return function(object) {
+ return baseInvoke(object, path, args);
+ };
+ });
+
+ /**
+ * The opposite of `_.method`; this method creates a function that invokes
+ * the method at a given path of `object`. Any additional arguments are
+ * provided to the invoked method.
+ *
+ * @static
+ * @memberOf _
+ * @category Util
+ * @param {Object} object The object to query.
+ * @param {...*} [args] The arguments to invoke the method with.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var array = _.times(3, _.constant),
+ * object = { 'a': array, 'b': array, 'c': array };
+ *
+ * _.map(['a[2]', 'c[0]'], _.methodOf(object));
+ * // => [2, 0]
+ *
+ * _.map([['a', '2'], ['c', '0']], _.methodOf(object));
+ * // => [2, 0]
+ */
+ var methodOf = rest(function(object, args) {
+ return function(path) {
+ return baseInvoke(object, path, args);
+ };
+ });
+
+ /**
+ * Adds all own enumerable function properties of a source object to the
+ * destination object. If `object` is a function then methods are added to
+ * its prototype as well.
+ *
+ * **Note:** Use `_.runInContext` to create a pristine `lodash` function to
+ * avoid conflicts caused by modifying the original.
+ *
+ * @static
+ * @memberOf _
+ * @category Util
+ * @param {Function|Object} [object=lodash] The destination object.
+ * @param {Object} source The object of functions to add.
+ * @param {Object} [options] The options object.
+ * @param {boolean} [options.chain=true] Specify whether the functions added
+ * are chainable.
+ * @returns {Function|Object} Returns `object`.
+ * @example
+ *
+ * function vowels(string) {
+ * return _.filter(string, function(v) {
+ * return /[aeiou]/i.test(v);
+ * });
+ * }
+ *
+ * _.mixin({ 'vowels': vowels });
+ * _.vowels('fred');
+ * // => ['e']
+ *
+ * _('fred').vowels().value();
+ * // => ['e']
+ *
+ * _.mixin({ 'vowels': vowels }, { 'chain': false });
+ * _('fred').vowels();
+ * // => ['e']
+ */
+ function mixin(object, source, options) {
+ var props = keys(source),
+ methodNames = baseFunctions(source, props);
+
+ if (options == null &&
+ !(isObject(source) && (methodNames.length || !props.length))) {
+ options = source;
+ source = object;
+ object = this;
+ methodNames = baseFunctions(source, keys(source));
+ }
+ var chain = (isObject(options) && 'chain' in options) ? options.chain : true,
+ isFunc = isFunction(object);
+
+ arrayEach(methodNames, function(methodName) {
+ var func = source[methodName];
+ object[methodName] = func;
+ if (isFunc) {
+ object.prototype[methodName] = function() {
+ var chainAll = this.__chain__;
+ if (chain || chainAll) {
+ var result = object(this.__wrapped__),
+ actions = result.__actions__ = copyArray(this.__actions__);
+
+ actions.push({ 'func': func, 'args': arguments, 'thisArg': object });
+ result.__chain__ = chainAll;
+ return result;
+ }
+ return func.apply(object, arrayPush([this.value()], arguments));
+ };
+ }
+ });
+
+ return object;
+ }
+
+ /**
+ * Reverts the `_` variable to its previous value and returns a reference to
+ * the `lodash` function.
+ *
+ * @static
+ * @memberOf _
+ * @category Util
+ * @returns {Function} Returns the `lodash` function.
+ * @example
+ *
+ * var lodash = _.noConflict();
+ */
+ function noConflict() {
+ if (root._ === this) {
+ root._ = oldDash;
+ }
+ return this;
+ }
+
+ /**
+ * A no-operation function that returns `undefined` regardless of the
+ * arguments it receives.
+ *
+ * @static
+ * @memberOf _
+ * @category Util
+ * @example
+ *
+ * var object = { 'user': 'fred' };
+ *
+ * _.noop(object) === undefined;
+ * // => true
+ */
+ function noop() {
+ // No operation performed.
+ }
+
+ /**
+ * Creates a function that returns its nth argument.
+ *
+ * @static
+ * @memberOf _
+ * @category Util
+ * @param {number} [n=0] The index of the argument to return.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var func = _.nthArg(1);
+ *
+ * func('a', 'b', 'c');
+ * // => 'b'
+ */
+ function nthArg(n) {
+ n = toInteger(n);
+ return function() {
+ return arguments[n];
+ };
+ }
+
+ /**
+ * Creates a function that invokes `iteratees` with the arguments provided
+ * to the created function and returns their results.
+ *
+ * @static
+ * @memberOf _
+ * @category Util
+ * @param {...(Function|Function[])} iteratees The iteratees to invoke.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var func = _.over(Math.max, Math.min);
+ *
+ * func(1, 2, 3, 4);
+ * // => [4, 1]
+ */
+ var over = createOver(arrayMap);
+
+ /**
+ * Creates a function that checks if **all** of the `predicates` return
+ * truthy when invoked with the arguments provided to the created function.
+ *
+ * @static
+ * @memberOf _
+ * @category Util
+ * @param {...(Function|Function[])} predicates The predicates to check.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var func = _.overEvery(Boolean, isFinite);
+ *
+ * func('1');
+ * // => true
+ *
+ * func(null);
+ * // => false
+ *
+ * func(NaN);
+ * // => false
+ */
+ var overEvery = createOver(arrayEvery);
+
+ /**
+ * Creates a function that checks if **any** of the `predicates` return
+ * truthy when invoked with the arguments provided to the created function.
+ *
+ * @static
+ * @memberOf _
+ * @category Util
+ * @param {...(Function|Function[])} predicates The predicates to check.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var func = _.overSome(Boolean, isFinite);
+ *
+ * func('1');
+ * // => true
+ *
+ * func(null);
+ * // => true
+ *
+ * func(NaN);
+ * // => false
+ */
+ var overSome = createOver(arraySome);
+
+ /**
+ * Creates a function that returns the value at `path` of a given object.
+ *
+ * @static
+ * @memberOf _
+ * @category Util
+ * @param {Array|string} path The path of the property to get.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var objects = [
+ * { 'a': { 'b': { 'c': 2 } } },
+ * { 'a': { 'b': { 'c': 1 } } }
+ * ];
+ *
+ * _.map(objects, _.property('a.b.c'));
+ * // => [2, 1]
+ *
+ * _.map(_.sortBy(objects, _.property(['a', 'b', 'c'])), 'a.b.c');
+ * // => [1, 2]
+ */
+ function property(path) {
+ return isKey(path) ? baseProperty(path) : basePropertyDeep(path);
+ }
+
+ /**
+ * The opposite of `_.property`; this method creates a function that returns
+ * the value at a given path of `object`.
+ *
+ * @static
+ * @memberOf _
+ * @category Util
+ * @param {Object} object The object to query.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var array = [0, 1, 2],
+ * object = { 'a': array, 'b': array, 'c': array };
+ *
+ * _.map(['a[2]', 'c[0]'], _.propertyOf(object));
+ * // => [2, 0]
+ *
+ * _.map([['a', '2'], ['c', '0']], _.propertyOf(object));
+ * // => [2, 0]
+ */
+ function propertyOf(object) {
+ return function(path) {
+ return object == null ? undefined : baseGet(object, path);
+ };
+ }
+
+ /**
+ * Creates an array of numbers (positive and/or negative) progressing from
+ * `start` up to, but not including, `end`. A step of `-1` is used if a negative
+ * `start` is specified without an `end` or `step`. If `end` is not specified
+ * it's set to `start` with `start` then set to `0`.
+ *
+ * **Note:** JavaScript follows the IEEE-754 standard for resolving
+ * floating-point values which can produce unexpected results.
+ *
+ * @static
+ * @memberOf _
+ * @category Util
+ * @param {number} [start=0] The start of the range.
+ * @param {number} end The end of the range.
+ * @param {number} [step=1] The value to increment or decrement by.
+ * @returns {Array} Returns the new array of numbers.
+ * @example
+ *
+ * _.range(4);
+ * // => [0, 1, 2, 3]
+ *
+ * _.range(-4);
+ * // => [0, -1, -2, -3]
+ *
+ * _.range(1, 5);
+ * // => [1, 2, 3, 4]
+ *
+ * _.range(0, 20, 5);
+ * // => [0, 5, 10, 15]
+ *
+ * _.range(0, -4, -1);
+ * // => [0, -1, -2, -3]
+ *
+ * _.range(1, 4, 0);
+ * // => [1, 1, 1]
+ *
+ * _.range(0);
+ * // => []
+ */
+ var range = createRange();
+
+ /**
+ * This method is like `_.range` except that it populates values in
+ * descending order.
+ *
+ * @static
+ * @memberOf _
+ * @category Util
+ * @param {number} [start=0] The start of the range.
+ * @param {number} end The end of the range.
+ * @param {number} [step=1] The value to increment or decrement by.
+ * @returns {Array} Returns the new array of numbers.
+ * @example
+ *
+ * _.rangeRight(4);
+ * // => [3, 2, 1, 0]
+ *
+ * _.rangeRight(-4);
+ * // => [-3, -2, -1, 0]
+ *
+ * _.rangeRight(1, 5);
+ * // => [4, 3, 2, 1]
+ *
+ * _.rangeRight(0, 20, 5);
+ * // => [15, 10, 5, 0]
+ *
+ * _.rangeRight(0, -4, -1);
+ * // => [-3, -2, -1, 0]
+ *
+ * _.rangeRight(1, 4, 0);
+ * // => [1, 1, 1]
+ *
+ * _.rangeRight(0);
+ * // => []
+ */
+ var rangeRight = createRange(true);
+
+ /**
+ * Invokes the iteratee `n` times, returning an array of the results of
+ * each invocation. The iteratee is invoked with one argument; (index).
+ *
+ * @static
+ * @memberOf _
+ * @category Util
+ * @param {number} n The number of times to invoke `iteratee`.
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+ * @returns {Array} Returns the array of results.
+ * @example
+ *
+ * _.times(3, String);
+ * // => ['0', '1', '2']
+ *
+ * _.times(4, _.constant(true));
+ * // => [true, true, true, true]
+ */
+ function times(n, iteratee) {
+ n = toInteger(n);
+ if (n < 1 || n > MAX_SAFE_INTEGER) {
+ return [];
+ }
+ var index = MAX_ARRAY_LENGTH,
+ length = nativeMin(n, MAX_ARRAY_LENGTH);
+
+ iteratee = baseCastFunction(iteratee);
+ n -= MAX_ARRAY_LENGTH;
+
+ var result = baseTimes(length, iteratee);
+ while (++index < n) {
+ iteratee(index);
+ }
+ return result;
+ }
+
+ /**
+ * Converts `value` to a property path array.
+ *
+ * @static
+ * @memberOf _
+ * @category Util
+ * @param {*} value The value to convert.
+ * @returns {Array} Returns the new property path array.
+ * @example
+ *
+ * _.toPath('a.b.c');
+ * // => ['a', 'b', 'c']
+ *
+ * _.toPath('a[0].b.c');
+ * // => ['a', '0', 'b', 'c']
+ *
+ * var path = ['a', 'b', 'c'],
+ * newPath = _.toPath(path);
+ *
+ * console.log(newPath);
+ * // => ['a', 'b', 'c']
+ *
+ * console.log(path === newPath);
+ * // => false
+ */
+ function toPath(value) {
+ return isArray(value) ? arrayMap(value, String) : stringToPath(value);
+ }
+
+ /**
+ * Generates a unique ID. If `prefix` is given the ID is appended to it.
+ *
+ * @static
+ * @memberOf _
+ * @category Util
+ * @param {string} [prefix=''] The value to prefix the ID with.
+ * @returns {string} Returns the unique ID.
+ * @example
+ *
+ * _.uniqueId('contact_');
+ * // => 'contact_104'
+ *
+ * _.uniqueId();
+ * // => '105'
+ */
+ function uniqueId(prefix) {
+ var id = ++idCounter;
+ return toString(prefix) + id;
+ }
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Adds two numbers.
+ *
+ * @static
+ * @memberOf _
+ * @category Math
+ * @param {number} augend The first number in an addition.
+ * @param {number} addend The second number in an addition.
+ * @returns {number} Returns the total.
+ * @example
+ *
+ * _.add(6, 4);
+ * // => 10
+ */
+ function add(augend, addend) {
+ var result;
+ if (augend === undefined && addend === undefined) {
+ return 0;
+ }
+ if (augend !== undefined) {
+ result = augend;
+ }
+ if (addend !== undefined) {
+ result = result === undefined ? addend : (result + addend);
+ }
+ return result;
+ }
+
+ /**
+ * Computes `number` rounded up to `precision`.
+ *
+ * @static
+ * @memberOf _
+ * @category Math
+ * @param {number} number The number to round up.
+ * @param {number} [precision=0] The precision to round up to.
+ * @returns {number} Returns the rounded up number.
+ * @example
+ *
+ * _.ceil(4.006);
+ * // => 5
+ *
+ * _.ceil(6.004, 2);
+ * // => 6.01
+ *
+ * _.ceil(6040, -2);
+ * // => 6100
+ */
+ var ceil = createRound('ceil');
+
+ /**
+ * Computes `number` rounded down to `precision`.
+ *
+ * @static
+ * @memberOf _
+ * @category Math
+ * @param {number} number The number to round down.
+ * @param {number} [precision=0] The precision to round down to.
+ * @returns {number} Returns the rounded down number.
+ * @example
+ *
+ * _.floor(4.006);
+ * // => 4
+ *
+ * _.floor(0.046, 2);
+ * // => 0.04
+ *
+ * _.floor(4060, -2);
+ * // => 4000
+ */
+ var floor = createRound('floor');
+
+ /**
+ * Computes the maximum value of `array`. If `array` is empty or falsey
+ * `undefined` is returned.
+ *
+ * @static
+ * @memberOf _
+ * @category Math
+ * @param {Array} array The array to iterate over.
+ * @returns {*} Returns the maximum value.
+ * @example
+ *
+ * _.max([4, 2, 8, 6]);
+ * // => 8
+ *
+ * _.max([]);
+ * // => undefined
+ */
+ function max(array) {
+ return (array && array.length)
+ ? baseExtremum(array, identity, gt)
+ : undefined;
+ }
+
+ /**
+ * This method is like `_.max` except that it accepts `iteratee` which is
+ * invoked for each element in `array` to generate the criterion by which
+ * the value is ranked. The iteratee is invoked with one argument: (value).
+ *
+ * @static
+ * @memberOf _
+ * @category Math
+ * @param {Array} array The array to iterate over.
+ * @param {Function|Object|string} [iteratee=_.identity] The iteratee invoked per element.
+ * @returns {*} Returns the maximum value.
+ * @example
+ *
+ * var objects = [{ 'n': 1 }, { 'n': 2 }];
+ *
+ * _.maxBy(objects, function(o) { return o.n; });
+ * // => { 'n': 2 }
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.maxBy(objects, 'n');
+ * // => { 'n': 2 }
+ */
+ function maxBy(array, iteratee) {
+ return (array && array.length)
+ ? baseExtremum(array, getIteratee(iteratee), gt)
+ : undefined;
+ }
+
+ /**
+ * Computes the mean of the values in `array`.
+ *
+ * @static
+ * @memberOf _
+ * @category Math
+ * @param {Array} array The array to iterate over.
+ * @returns {number} Returns the mean.
+ * @example
+ *
+ * _.mean([4, 2, 8, 6]);
+ * // => 5
+ */
+ function mean(array) {
+ return sum(array) / (array ? array.length : 0);
+ }
+
+ /**
+ * Computes the minimum value of `array`. If `array` is empty or falsey
+ * `undefined` is returned.
+ *
+ * @static
+ * @memberOf _
+ * @category Math
+ * @param {Array} array The array to iterate over.
+ * @returns {*} Returns the minimum value.
+ * @example
+ *
+ * _.min([4, 2, 8, 6]);
+ * // => 2
+ *
+ * _.min([]);
+ * // => undefined
+ */
+ function min(array) {
+ return (array && array.length)
+ ? baseExtremum(array, identity, lt)
+ : undefined;
+ }
+
+ /**
+ * This method is like `_.min` except that it accepts `iteratee` which is
+ * invoked for each element in `array` to generate the criterion by which
+ * the value is ranked. The iteratee is invoked with one argument: (value).
+ *
+ * @static
+ * @memberOf _
+ * @category Math
+ * @param {Array} array The array to iterate over.
+ * @param {Function|Object|string} [iteratee=_.identity] The iteratee invoked per element.
+ * @returns {*} Returns the minimum value.
+ * @example
+ *
+ * var objects = [{ 'n': 1 }, { 'n': 2 }];
+ *
+ * _.minBy(objects, function(o) { return o.n; });
+ * // => { 'n': 1 }
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.minBy(objects, 'n');
+ * // => { 'n': 1 }
+ */
+ function minBy(array, iteratee) {
+ return (array && array.length)
+ ? baseExtremum(array, getIteratee(iteratee), lt)
+ : undefined;
+ }
+
+ /**
+ * Computes `number` rounded to `precision`.
+ *
+ * @static
+ * @memberOf _
+ * @category Math
+ * @param {number} number The number to round.
+ * @param {number} [precision=0] The precision to round to.
+ * @returns {number} Returns the rounded number.
+ * @example
+ *
+ * _.round(4.006);
+ * // => 4
+ *
+ * _.round(4.006, 2);
+ * // => 4.01
+ *
+ * _.round(4060, -2);
+ * // => 4100
+ */
+ var round = createRound('round');
+
+ /**
+ * Subtract two numbers.
+ *
+ * @static
+ * @memberOf _
+ * @category Math
+ * @param {number} minuend The first number in a subtraction.
+ * @param {number} subtrahend The second number in a subtraction.
+ * @returns {number} Returns the difference.
+ * @example
+ *
+ * _.subtract(6, 4);
+ * // => 2
+ */
+ function subtract(minuend, subtrahend) {
+ var result;
+ if (minuend === undefined && subtrahend === undefined) {
+ return 0;
+ }
+ if (minuend !== undefined) {
+ result = minuend;
+ }
+ if (subtrahend !== undefined) {
+ result = result === undefined ? subtrahend : (result - subtrahend);
+ }
+ return result;
+ }
+
+ /**
+ * Computes the sum of the values in `array`.
+ *
+ * @static
+ * @memberOf _
+ * @category Math
+ * @param {Array} array The array to iterate over.
+ * @returns {number} Returns the sum.
+ * @example
+ *
+ * _.sum([4, 2, 8, 6]);
+ * // => 20
+ */
+ function sum(array) {
+ return (array && array.length)
+ ? baseSum(array, identity)
+ : 0;
+ }
+
+ /**
+ * This method is like `_.sum` except that it accepts `iteratee` which is
+ * invoked for each element in `array` to generate the value to be summed.
+ * The iteratee is invoked with one argument: (value).
+ *
+ * @static
+ * @memberOf _
+ * @category Math
+ * @param {Array} array The array to iterate over.
+ * @param {Function|Object|string} [iteratee=_.identity] The iteratee invoked per element.
+ * @returns {number} Returns the sum.
+ * @example
+ *
+ * var objects = [{ 'n': 4 }, { 'n': 2 }, { 'n': 8 }, { 'n': 6 }];
+ *
+ * _.sumBy(objects, function(o) { return o.n; });
+ * // => 20
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.sumBy(objects, 'n');
+ * // => 20
+ */
+ function sumBy(array, iteratee) {
+ return (array && array.length)
+ ? baseSum(array, getIteratee(iteratee))
+ : 0;
+ }
+
+ /*------------------------------------------------------------------------*/
+
+ // Ensure wrappers are instances of `baseLodash`.
+ lodash.prototype = baseLodash.prototype;
+ lodash.prototype.constructor = lodash;
+
+ LodashWrapper.prototype = baseCreate(baseLodash.prototype);
+ LodashWrapper.prototype.constructor = LodashWrapper;
+
+ LazyWrapper.prototype = baseCreate(baseLodash.prototype);
+ LazyWrapper.prototype.constructor = LazyWrapper;
+
+ // Avoid inheriting from `Object.prototype` when possible.
+ Hash.prototype = nativeCreate ? nativeCreate(null) : objectProto;
+
+ // Add functions to the `MapCache`.
+ MapCache.prototype.clear = mapClear;
+ MapCache.prototype['delete'] = mapDelete;
+ MapCache.prototype.get = mapGet;
+ MapCache.prototype.has = mapHas;
+ MapCache.prototype.set = mapSet;
+
+ // Add functions to the `SetCache`.
+ SetCache.prototype.push = cachePush;
+
+ // Add functions to the `Stack` cache.
+ Stack.prototype.clear = stackClear;
+ Stack.prototype['delete'] = stackDelete;
+ Stack.prototype.get = stackGet;
+ Stack.prototype.has = stackHas;
+ Stack.prototype.set = stackSet;
+
+ // Assign cache to `_.memoize`.
+ memoize.Cache = MapCache;
+
+ // Add functions that return wrapped values when chaining.
+ lodash.after = after;
+ lodash.ary = ary;
+ lodash.assign = assign;
+ lodash.assignIn = assignIn;
+ lodash.assignInWith = assignInWith;
+ lodash.assignWith = assignWith;
+ lodash.at = at;
+ lodash.before = before;
+ lodash.bind = bind;
+ lodash.bindAll = bindAll;
+ lodash.bindKey = bindKey;
+ lodash.castArray = castArray;
+ lodash.chain = chain;
+ lodash.chunk = chunk;
+ lodash.compact = compact;
+ lodash.concat = concat;
+ lodash.cond = cond;
+ lodash.conforms = conforms;
+ lodash.constant = constant;
+ lodash.countBy = countBy;
+ lodash.create = create;
+ lodash.curry = curry;
+ lodash.curryRight = curryRight;
+ lodash.debounce = debounce;
+ lodash.defaults = defaults;
+ lodash.defaultsDeep = defaultsDeep;
+ lodash.defer = defer;
+ lodash.delay = delay;
+ lodash.difference = difference;
+ lodash.differenceBy = differenceBy;
+ lodash.differenceWith = differenceWith;
+ lodash.drop = drop;
+ lodash.dropRight = dropRight;
+ lodash.dropRightWhile = dropRightWhile;
+ lodash.dropWhile = dropWhile;
+ lodash.fill = fill;
+ lodash.filter = filter;
+ lodash.flatMap = flatMap;
+ lodash.flatten = flatten;
+ lodash.flattenDeep = flattenDeep;
+ lodash.flattenDepth = flattenDepth;
+ lodash.flip = flip;
+ lodash.flow = flow;
+ lodash.flowRight = flowRight;
+ lodash.fromPairs = fromPairs;
+ lodash.functions = functions;
+ lodash.functionsIn = functionsIn;
+ lodash.groupBy = groupBy;
+ lodash.initial = initial;
+ lodash.intersection = intersection;
+ lodash.intersectionBy = intersectionBy;
+ lodash.intersectionWith = intersectionWith;
+ lodash.invert = invert;
+ lodash.invertBy = invertBy;
+ lodash.invokeMap = invokeMap;
+ lodash.iteratee = iteratee;
+ lodash.keyBy = keyBy;
+ lodash.keys = keys;
+ lodash.keysIn = keysIn;
+ lodash.map = map;
+ lodash.mapKeys = mapKeys;
+ lodash.mapValues = mapValues;
+ lodash.matches = matches;
+ lodash.matchesProperty = matchesProperty;
+ lodash.memoize = memoize;
+ lodash.merge = merge;
+ lodash.mergeWith = mergeWith;
+ lodash.method = method;
+ lodash.methodOf = methodOf;
+ lodash.mixin = mixin;
+ lodash.negate = negate;
+ lodash.nthArg = nthArg;
+ lodash.omit = omit;
+ lodash.omitBy = omitBy;
+ lodash.once = once;
+ lodash.orderBy = orderBy;
+ lodash.over = over;
+ lodash.overArgs = overArgs;
+ lodash.overEvery = overEvery;
+ lodash.overSome = overSome;
+ lodash.partial = partial;
+ lodash.partialRight = partialRight;
+ lodash.partition = partition;
+ lodash.pick = pick;
+ lodash.pickBy = pickBy;
+ lodash.property = property;
+ lodash.propertyOf = propertyOf;
+ lodash.pull = pull;
+ lodash.pullAll = pullAll;
+ lodash.pullAllBy = pullAllBy;
+ lodash.pullAllWith = pullAllWith;
+ lodash.pullAt = pullAt;
+ lodash.range = range;
+ lodash.rangeRight = rangeRight;
+ lodash.rearg = rearg;
+ lodash.reject = reject;
+ lodash.remove = remove;
+ lodash.rest = rest;
+ lodash.reverse = reverse;
+ lodash.sampleSize = sampleSize;
+ lodash.set = set;
+ lodash.setWith = setWith;
+ lodash.shuffle = shuffle;
+ lodash.slice = slice;
+ lodash.sortBy = sortBy;
+ lodash.sortedUniq = sortedUniq;
+ lodash.sortedUniqBy = sortedUniqBy;
+ lodash.split = split;
+ lodash.spread = spread;
+ lodash.tail = tail;
+ lodash.take = take;
+ lodash.takeRight = takeRight;
+ lodash.takeRightWhile = takeRightWhile;
+ lodash.takeWhile = takeWhile;
+ lodash.tap = tap;
+ lodash.throttle = throttle;
+ lodash.thru = thru;
+ lodash.toArray = toArray;
+ lodash.toPairs = toPairs;
+ lodash.toPairsIn = toPairsIn;
+ lodash.toPath = toPath;
+ lodash.toPlainObject = toPlainObject;
+ lodash.transform = transform;
+ lodash.unary = unary;
+ lodash.union = union;
+ lodash.unionBy = unionBy;
+ lodash.unionWith = unionWith;
+ lodash.uniq = uniq;
+ lodash.uniqBy = uniqBy;
+ lodash.uniqWith = uniqWith;
+ lodash.unset = unset;
+ lodash.unzip = unzip;
+ lodash.unzipWith = unzipWith;
+ lodash.update = update;
+ lodash.updateWith = updateWith;
+ lodash.values = values;
+ lodash.valuesIn = valuesIn;
+ lodash.without = without;
+ lodash.words = words;
+ lodash.wrap = wrap;
+ lodash.xor = xor;
+ lodash.xorBy = xorBy;
+ lodash.xorWith = xorWith;
+ lodash.zip = zip;
+ lodash.zipObject = zipObject;
+ lodash.zipObjectDeep = zipObjectDeep;
+ lodash.zipWith = zipWith;
+
+ // Add aliases.
+ lodash.extend = assignIn;
+ lodash.extendWith = assignInWith;
+
+ // Add functions to `lodash.prototype`.
+ mixin(lodash, lodash);
+
+ /*------------------------------------------------------------------------*/
+
+ // Add functions that return unwrapped values when chaining.
+ lodash.add = add;
+ lodash.attempt = attempt;
+ lodash.camelCase = camelCase;
+ lodash.capitalize = capitalize;
+ lodash.ceil = ceil;
+ lodash.clamp = clamp;
+ lodash.clone = clone;
+ lodash.cloneDeep = cloneDeep;
+ lodash.cloneDeepWith = cloneDeepWith;
+ lodash.cloneWith = cloneWith;
+ lodash.deburr = deburr;
+ lodash.endsWith = endsWith;
+ lodash.eq = eq;
+ lodash.escape = escape;
+ lodash.escapeRegExp = escapeRegExp;
+ lodash.every = every;
+ lodash.find = find;
+ lodash.findIndex = findIndex;
+ lodash.findKey = findKey;
+ lodash.findLast = findLast;
+ lodash.findLastIndex = findLastIndex;
+ lodash.findLastKey = findLastKey;
+ lodash.floor = floor;
+ lodash.forEach = forEach;
+ lodash.forEachRight = forEachRight;
+ lodash.forIn = forIn;
+ lodash.forInRight = forInRight;
+ lodash.forOwn = forOwn;
+ lodash.forOwnRight = forOwnRight;
+ lodash.get = get;
+ lodash.gt = gt;
+ lodash.gte = gte;
+ lodash.has = has;
+ lodash.hasIn = hasIn;
+ lodash.head = head;
+ lodash.identity = identity;
+ lodash.includes = includes;
+ lodash.indexOf = indexOf;
+ lodash.inRange = inRange;
+ lodash.invoke = invoke;
+ lodash.isArguments = isArguments;
+ lodash.isArray = isArray;
+ lodash.isArrayBuffer = isArrayBuffer;
+ lodash.isArrayLike = isArrayLike;
+ lodash.isArrayLikeObject = isArrayLikeObject;
+ lodash.isBoolean = isBoolean;
+ lodash.isBuffer = isBuffer;
+ lodash.isDate = isDate;
+ lodash.isElement = isElement;
+ lodash.isEmpty = isEmpty;
+ lodash.isEqual = isEqual;
+ lodash.isEqualWith = isEqualWith;
+ lodash.isError = isError;
+ lodash.isFinite = isFinite;
+ lodash.isFunction = isFunction;
+ lodash.isInteger = isInteger;
+ lodash.isLength = isLength;
+ lodash.isMap = isMap;
+ lodash.isMatch = isMatch;
+ lodash.isMatchWith = isMatchWith;
+ lodash.isNaN = isNaN;
+ lodash.isNative = isNative;
+ lodash.isNil = isNil;
+ lodash.isNull = isNull;
+ lodash.isNumber = isNumber;
+ lodash.isObject = isObject;
+ lodash.isObjectLike = isObjectLike;
+ lodash.isPlainObject = isPlainObject;
+ lodash.isRegExp = isRegExp;
+ lodash.isSafeInteger = isSafeInteger;
+ lodash.isSet = isSet;
+ lodash.isString = isString;
+ lodash.isSymbol = isSymbol;
+ lodash.isTypedArray = isTypedArray;
+ lodash.isUndefined = isUndefined;
+ lodash.isWeakMap = isWeakMap;
+ lodash.isWeakSet = isWeakSet;
+ lodash.join = join;
+ lodash.kebabCase = kebabCase;
+ lodash.last = last;
+ lodash.lastIndexOf = lastIndexOf;
+ lodash.lowerCase = lowerCase;
+ lodash.lowerFirst = lowerFirst;
+ lodash.lt = lt;
+ lodash.lte = lte;
+ lodash.max = max;
+ lodash.maxBy = maxBy;
+ lodash.mean = mean;
+ lodash.min = min;
+ lodash.minBy = minBy;
+ lodash.noConflict = noConflict;
+ lodash.noop = noop;
+ lodash.now = now;
+ lodash.pad = pad;
+ lodash.padEnd = padEnd;
+ lodash.padStart = padStart;
+ lodash.parseInt = parseInt;
+ lodash.random = random;
+ lodash.reduce = reduce;
+ lodash.reduceRight = reduceRight;
+ lodash.repeat = repeat;
+ lodash.replace = replace;
+ lodash.result = result;
+ lodash.round = round;
+ lodash.runInContext = runInContext;
+ lodash.sample = sample;
+ lodash.size = size;
+ lodash.snakeCase = snakeCase;
+ lodash.some = some;
+ lodash.sortedIndex = sortedIndex;
+ lodash.sortedIndexBy = sortedIndexBy;
+ lodash.sortedIndexOf = sortedIndexOf;
+ lodash.sortedLastIndex = sortedLastIndex;
+ lodash.sortedLastIndexBy = sortedLastIndexBy;
+ lodash.sortedLastIndexOf = sortedLastIndexOf;
+ lodash.startCase = startCase;
+ lodash.startsWith = startsWith;
+ lodash.subtract = subtract;
+ lodash.sum = sum;
+ lodash.sumBy = sumBy;
+ lodash.template = template;
+ lodash.times = times;
+ lodash.toInteger = toInteger;
+ lodash.toLength = toLength;
+ lodash.toLower = toLower;
+ lodash.toNumber = toNumber;
+ lodash.toSafeInteger = toSafeInteger;
+ lodash.toString = toString;
+ lodash.toUpper = toUpper;
+ lodash.trim = trim;
+ lodash.trimEnd = trimEnd;
+ lodash.trimStart = trimStart;
+ lodash.truncate = truncate;
+ lodash.unescape = unescape;
+ lodash.uniqueId = uniqueId;
+ lodash.upperCase = upperCase;
+ lodash.upperFirst = upperFirst;
+
+ // Add aliases.
+ lodash.each = forEach;
+ lodash.eachRight = forEachRight;
+ lodash.first = head;
+
+ mixin(lodash, (function() {
+ var source = {};
+ baseForOwn(lodash, function(func, methodName) {
+ if (!hasOwnProperty.call(lodash.prototype, methodName)) {
+ source[methodName] = func;
+ }
+ });
+ return source;
+ }()), { 'chain': false });
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * The semantic version number.
+ *
+ * @static
+ * @memberOf _
+ * @type {string}
+ */
+ lodash.VERSION = VERSION;
+
+ // Assign default placeholders.
+ arrayEach(['bind', 'bindKey', 'curry', 'curryRight', 'partial', 'partialRight'], function(methodName) {
+ lodash[methodName].placeholder = lodash;
+ });
+
+ // Add `LazyWrapper` methods for `_.drop` and `_.take` variants.
+ arrayEach(['drop', 'take'], function(methodName, index) {
+ LazyWrapper.prototype[methodName] = function(n) {
+ var filtered = this.__filtered__;
+ if (filtered && !index) {
+ return new LazyWrapper(this);
+ }
+ n = n === undefined ? 1 : nativeMax(toInteger(n), 0);
+
+ var result = this.clone();
+ if (filtered) {
+ result.__takeCount__ = nativeMin(n, result.__takeCount__);
+ } else {
+ result.__views__.push({
+ 'size': nativeMin(n, MAX_ARRAY_LENGTH),
+ 'type': methodName + (result.__dir__ < 0 ? 'Right' : '')
+ });
+ }
+ return result;
+ };
+
+ LazyWrapper.prototype[methodName + 'Right'] = function(n) {
+ return this.reverse()[methodName](n).reverse();
+ };
+ });
+
+ // Add `LazyWrapper` methods that accept an `iteratee` value.
+ arrayEach(['filter', 'map', 'takeWhile'], function(methodName, index) {
+ var type = index + 1,
+ isFilter = type == LAZY_FILTER_FLAG || type == LAZY_WHILE_FLAG;
+
+ LazyWrapper.prototype[methodName] = function(iteratee) {
+ var result = this.clone();
+ result.__iteratees__.push({
+ 'iteratee': getIteratee(iteratee, 3),
+ 'type': type
+ });
+ result.__filtered__ = result.__filtered__ || isFilter;
+ return result;
+ };
+ });
+
+ // Add `LazyWrapper` methods for `_.head` and `_.last`.
+ arrayEach(['head', 'last'], function(methodName, index) {
+ var takeName = 'take' + (index ? 'Right' : '');
+
+ LazyWrapper.prototype[methodName] = function() {
+ return this[takeName](1).value()[0];
+ };
+ });
+
+ // Add `LazyWrapper` methods for `_.initial` and `_.tail`.
+ arrayEach(['initial', 'tail'], function(methodName, index) {
+ var dropName = 'drop' + (index ? '' : 'Right');
+
+ LazyWrapper.prototype[methodName] = function() {
+ return this.__filtered__ ? new LazyWrapper(this) : this[dropName](1);
+ };
+ });
+
+ LazyWrapper.prototype.compact = function() {
+ return this.filter(identity);
+ };
+
+ LazyWrapper.prototype.find = function(predicate) {
+ return this.filter(predicate).head();
+ };
+
+ LazyWrapper.prototype.findLast = function(predicate) {
+ return this.reverse().find(predicate);
+ };
+
+ LazyWrapper.prototype.invokeMap = rest(function(path, args) {
+ if (typeof path == 'function') {
+ return new LazyWrapper(this);
+ }
+ return this.map(function(value) {
+ return baseInvoke(value, path, args);
+ });
+ });
+
+ LazyWrapper.prototype.reject = function(predicate) {
+ predicate = getIteratee(predicate, 3);
+ return this.filter(function(value) {
+ return !predicate(value);
+ });
+ };
+
+ LazyWrapper.prototype.slice = function(start, end) {
+ start = toInteger(start);
+
+ var result = this;
+ if (result.__filtered__ && (start > 0 || end < 0)) {
+ return new LazyWrapper(result);
+ }
+ if (start < 0) {
+ result = result.takeRight(-start);
+ } else if (start) {
+ result = result.drop(start);
+ }
+ if (end !== undefined) {
+ end = toInteger(end);
+ result = end < 0 ? result.dropRight(-end) : result.take(end - start);
+ }
+ return result;
+ };
+
+ LazyWrapper.prototype.takeRightWhile = function(predicate) {
+ return this.reverse().takeWhile(predicate).reverse();
+ };
+
+ LazyWrapper.prototype.toArray = function() {
+ return this.take(MAX_ARRAY_LENGTH);
+ };
+
+ // Add `LazyWrapper` methods to `lodash.prototype`.
+ baseForOwn(LazyWrapper.prototype, function(func, methodName) {
+ var checkIteratee = /^(?:filter|find|map|reject)|While$/.test(methodName),
+ isTaker = /^(?:head|last)$/.test(methodName),
+ lodashFunc = lodash[isTaker ? ('take' + (methodName == 'last' ? 'Right' : '')) : methodName],
+ retUnwrapped = isTaker || /^find/.test(methodName);
+
+ if (!lodashFunc) {
+ return;
+ }
+ lodash.prototype[methodName] = function() {
+ var value = this.__wrapped__,
+ args = isTaker ? [1] : arguments,
+ isLazy = value instanceof LazyWrapper,
+ iteratee = args[0],
+ useLazy = isLazy || isArray(value);
+
+ var interceptor = function(value) {
+ var result = lodashFunc.apply(lodash, arrayPush([value], args));
+ return (isTaker && chainAll) ? result[0] : result;
+ };
+
+ if (useLazy && checkIteratee && typeof iteratee == 'function' && iteratee.length != 1) {
+ // Avoid lazy use if the iteratee has a "length" value other than `1`.
+ isLazy = useLazy = false;
+ }
+ var chainAll = this.__chain__,
+ isHybrid = !!this.__actions__.length,
+ isUnwrapped = retUnwrapped && !chainAll,
+ onlyLazy = isLazy && !isHybrid;
+
+ if (!retUnwrapped && useLazy) {
+ value = onlyLazy ? value : new LazyWrapper(this);
+ var result = func.apply(value, args);
+ result.__actions__.push({ 'func': thru, 'args': [interceptor], 'thisArg': undefined });
+ return new LodashWrapper(result, chainAll);
+ }
+ if (isUnwrapped && onlyLazy) {
+ return func.apply(this, args);
+ }
+ result = this.thru(interceptor);
+ return isUnwrapped ? (isTaker ? result.value()[0] : result.value()) : result;
+ };
+ });
+
+ // Add `Array` and `String` methods to `lodash.prototype`.
+ arrayEach(['pop', 'push', 'shift', 'sort', 'splice', 'unshift'], function(methodName) {
+ var func = arrayProto[methodName],
+ chainName = /^(?:push|sort|unshift)$/.test(methodName) ? 'tap' : 'thru',
+ retUnwrapped = /^(?:pop|shift)$/.test(methodName);
+
+ lodash.prototype[methodName] = function() {
+ var args = arguments;
+ if (retUnwrapped && !this.__chain__) {
+ return func.apply(this.value(), args);
+ }
+ return this[chainName](function(value) {
+ return func.apply(value, args);
+ });
+ };
+ });
+
+ // Map minified function names to their real names.
+ baseForOwn(LazyWrapper.prototype, function(func, methodName) {
+ var lodashFunc = lodash[methodName];
+ if (lodashFunc) {
+ var key = (lodashFunc.name + ''),
+ names = realNames[key] || (realNames[key] = []);
+
+ names.push({ 'name': methodName, 'func': lodashFunc });
+ }
+ });
+
+ realNames[createHybridWrapper(undefined, BIND_KEY_FLAG).name] = [{
+ 'name': 'wrapper',
+ 'func': undefined
+ }];
+
+ // Add functions to the lazy wrapper.
+ LazyWrapper.prototype.clone = lazyClone;
+ LazyWrapper.prototype.reverse = lazyReverse;
+ LazyWrapper.prototype.value = lazyValue;
+
+ // Add chaining functions to the `lodash` wrapper.
+ lodash.prototype.at = wrapperAt;
+ lodash.prototype.chain = wrapperChain;
+ lodash.prototype.commit = wrapperCommit;
+ lodash.prototype.flatMap = wrapperFlatMap;
+ lodash.prototype.next = wrapperNext;
+ lodash.prototype.plant = wrapperPlant;
+ lodash.prototype.reverse = wrapperReverse;
+ lodash.prototype.toJSON = lodash.prototype.valueOf = lodash.prototype.value = wrapperValue;
+
+ if (iteratorSymbol) {
+ lodash.prototype[iteratorSymbol] = wrapperToIterator;
+ }
+ return lodash;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ // Export lodash.
+ var _ = runInContext();
+
+ // Expose lodash on the free variable `window` or `self` when available. This
+ // prevents errors in cases where lodash is loaded by a script tag in the presence
+ // of an AMD loader. See http://requirejs.org/docs/errors.html#mismatch for more details.
+ (freeWindow || freeSelf || {})._ = _;
+
+ // Some AMD build optimizers like r.js check for condition patterns like the following:
+ if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) {
+ // Define as an anonymous module so, through path mapping, it can be
+ // referenced as the "underscore" module.
+ define(function() {
+ return _;
+ });
+ }
+ // Check for `exports` after `define` in case a build optimizer adds an `exports` object.
+ else if (freeExports && freeModule) {
+ // Export for Node.js.
+ if (moduleExports) {
+ (freeModule.exports = _)._ = _;
+ }
+ // Export for CommonJS support.
+ freeExports._ = _;
+ }
+ else {
+ // Export to the global object.
+ root._ = _;
+ }
+}.call(this));
--- /dev/null
+/*@licstart The following is the entire license notice for the
+JavaScript code in this page.
+
+Copyright 2012-2015 The Dojo Foundation
+Based on Underscore.js 1.7.0,
+Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+
+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 SOFTWAR
+
+@licend The above is the entire license notice
+for the JavaScript code in this page.
+*/
+;(function(){function n(n,t,e){e=(e||0)-1;for(var r=n?n.length:0;++e<r;)if(n[e]===t)return e;return-1}function t(t,e){var r=typeof e;if(t=t.l,"boolean"==r||null==e)return t[e]?0:-1;"number"!=r&&"string"!=r&&(r="object");var u="number"==r?e:b+e;return t=(t=t[r])&&t[u],"object"==r?t&&-1<n(t,e)?0:-1:t?0:-1}function e(n){var t=this.l,e=typeof n;if("boolean"==e||null==n)t[n]=true;else{"number"!=e&&"string"!=e&&(e="object");var r="number"==e?n:b+n,t=t[e]||(t[e]={});"object"==e?(t[r]||(t[r]=[])).push(n):t[r]=true
+}}function r(n){return n.charCodeAt(0)}function u(n,t){for(var e=n.m,r=t.m,u=-1,o=e.length;++u<o;){var a=e[u],i=r[u];if(a!==i){if(a>i||typeof a=="undefined")return 1;if(a<i||typeof i=="undefined")return-1}}return n.n-t.n}function o(n){var t=-1,r=n.length,u=n[0],o=n[r/2|0],a=n[r-1];if(u&&typeof u=="object"&&o&&typeof o=="object"&&a&&typeof a=="object")return false;for(u=l(),u["false"]=u["null"]=u["true"]=u.undefined=false,o=l(),o.k=n,o.l=u,o.push=e;++t<r;)o.push(n[t]);return o}function a(n){return"\\"+Y[n]
+}function i(){return v.pop()||[]}function l(){return y.pop()||{k:null,l:null,m:null,"false":false,n:0,"null":false,number:null,object:null,push:null,string:null,"true":false,undefined:false,o:null}}function f(n){return typeof n.toString!="function"&&typeof(n+"")=="string"}function c(n){n.length=0,v.length<w&&v.push(n)}function p(n){var t=n.l;t&&p(t),n.k=n.l=n.m=n.object=n.number=n.string=n.o=null,y.length<w&&y.push(n)}function s(n,t,e){t||(t=0),typeof e=="undefined"&&(e=n?n.length:0);var r=-1;e=e-t||0;for(var u=Array(0>e?0:e);++r<e;)u[r]=n[t+r];
+return u}function g(e){function v(n){return n&&typeof n=="object"&&!qe(n)&&we.call(n,"__wrapped__")?n:new y(n)}function y(n,t){this.__chain__=!!t,this.__wrapped__=n}function w(n){function t(){if(r){var n=s(r);je.apply(n,arguments)}if(this instanceof t){var o=nt(e.prototype),n=e.apply(o,n||arguments);return xt(n)?n:o}return e.apply(u,n||arguments)}var e=n[0],r=n[2],u=n[4];return ze(t,n),t}function Y(n,t,e,r,u){if(e){var o=e(n);if(typeof o!="undefined")return o}if(!xt(n))return n;var a=he.call(n);if(!V[a]||!Le.nodeClass&&f(n))return n;
+var l=Te[a];switch(a){case L:case z:return new l(+n);case W:case M:return new l(n);case J:return o=l(n.source,S.exec(n)),o.lastIndex=n.lastIndex,o}if(a=qe(n),t){var p=!r;r||(r=i()),u||(u=i());for(var g=r.length;g--;)if(r[g]==n)return u[g];o=a?l(n.length):{}}else o=a?s(n):Ye({},n);return a&&(we.call(n,"index")&&(o.index=n.index),we.call(n,"input")&&(o.input=n.input)),t?(r.push(n),u.push(o),(a?Xe:tr)(n,function(n,a){o[a]=Y(n,t,e,r,u)}),p&&(c(r),c(u)),o):o}function nt(n){return xt(n)?Se(n):{}}function tt(n,t,e){if(typeof n!="function")return Ht;
+if(typeof t=="undefined"||!("prototype"in n))return n;var r=n.__bindData__;if(typeof r=="undefined"&&(Le.funcNames&&(r=!n.name),r=r||!Le.funcDecomp,!r)){var u=be.call(n);Le.funcNames||(r=!A.test(u)),r||(r=B.test(u),ze(n,r))}if(false===r||true!==r&&1&r[1])return n;switch(e){case 1:return function(e){return n.call(t,e)};case 2:return function(e,r){return n.call(t,e,r)};case 3:return function(e,r,u){return n.call(t,e,r,u)};case 4:return function(e,r,u,o){return n.call(t,e,r,u,o)}}return Mt(n,t)}function et(n){function t(){var n=l?a:this;
+if(u){var h=s(u);je.apply(h,arguments)}return(o||c)&&(h||(h=s(arguments)),o&&je.apply(h,o),c&&h.length<i)?(r|=16,et([e,p?r:-4&r,h,null,a,i])):(h||(h=arguments),f&&(e=n[g]),this instanceof t?(n=nt(e.prototype),h=e.apply(n,h),xt(h)?h:n):e.apply(n,h))}var e=n[0],r=n[1],u=n[2],o=n[3],a=n[4],i=n[5],l=1&r,f=2&r,c=4&r,p=8&r,g=e;return ze(t,n),t}function rt(e,r){var u=-1,a=ht(),i=e?e.length:0,l=i>=_&&a===n,f=[];if(l){var c=o(r);c?(a=t,r=c):l=false}for(;++u<i;)c=e[u],0>a(r,c)&&f.push(c);return l&&p(r),f}function ot(n,t,e,r){r=(r||0)-1;
+for(var u=n?n.length:0,o=[];++r<u;){var a=n[r];if(a&&typeof a=="object"&&typeof a.length=="number"&&(qe(a)||dt(a))){t||(a=ot(a,t,e));var i=-1,l=a.length,f=o.length;for(o.length+=l;++i<l;)o[f++]=a[i]}else e||o.push(a)}return o}function at(n,t,e,r,u,o){if(e){var a=e(n,t);if(typeof a!="undefined")return!!a}if(n===t)return 0!==n||1/n==1/t;if(n===n&&!(n&&X[typeof n]||t&&X[typeof t]))return false;if(null==n||null==t)return n===t;var l=he.call(n),p=he.call(t);if(l==T&&(l=G),p==T&&(p=G),l!=p)return false;switch(l){case L:case z:return+n==+t;
+case W:return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case J:case M:return n==ie(t)}if(p=l==$,!p){var s=we.call(n,"__wrapped__"),g=we.call(t,"__wrapped__");if(s||g)return at(s?n.__wrapped__:n,g?t.__wrapped__:t,e,r,u,o);if(l!=G||!Le.nodeClass&&(f(n)||f(t)))return false;if(l=!Le.argsObject&&dt(n)?oe:n.constructor,s=!Le.argsObject&&dt(t)?oe:t.constructor,l!=s&&!(jt(l)&&l instanceof l&&jt(s)&&s instanceof s)&&"constructor"in n&&"constructor"in t)return false}for(l=!u,u||(u=i()),o||(o=i()),s=u.length;s--;)if(u[s]==n)return o[s]==t;
+var h=0,a=true;if(u.push(n),o.push(t),p){if(s=n.length,h=t.length,(a=h==s)||r)for(;h--;)if(p=s,g=t[h],r)for(;p--&&!(a=at(n[p],g,e,r,u,o)););else if(!(a=at(n[h],g,e,r,u,o)))break}else nr(t,function(t,i,l){return we.call(l,i)?(h++,a=we.call(n,i)&&at(n[i],t,e,r,u,o)):void 0}),a&&!r&&nr(n,function(n,t,e){return we.call(e,t)?a=-1<--h:void 0});return u.pop(),o.pop(),l&&(c(u),c(o)),a}function it(n,t,e,r,u){(qe(t)?Dt:tr)(t,function(t,o){var a,i,l=t,f=n[o];if(t&&((i=qe(t))||er(t))){for(l=r.length;l--;)if(a=r[l]==t){f=u[l];
+break}if(!a){var c;e&&(l=e(f,t),c=typeof l!="undefined")&&(f=l),c||(f=i?qe(f)?f:[]:er(f)?f:{}),r.push(t),u.push(f),c||it(f,t,e,r,u)}}else e&&(l=e(f,t),typeof l=="undefined"&&(l=t)),typeof l!="undefined"&&(f=l);n[o]=f})}function lt(n,t){return n+de(Fe()*(t-n+1))}function ft(e,r,u){var a=-1,l=ht(),f=e?e.length:0,s=[],g=!r&&f>=_&&l===n,h=u||g?i():s;for(g&&(h=o(h),l=t);++a<f;){var v=e[a],y=u?u(v,a,e):v;(r?!a||h[h.length-1]!==y:0>l(h,y))&&((u||g)&&h.push(y),s.push(v))}return g?(c(h.k),p(h)):u&&c(h),s}function ct(n){return function(t,e,r){var u={};
+if(e=v.createCallback(e,r,3),qe(t)){r=-1;for(var o=t.length;++r<o;){var a=t[r];n(u,a,e(a,r,t),t)}}else Xe(t,function(t,r,o){n(u,t,e(t,r,o),o)});return u}}function pt(n,t,e,r,u,o){var a=1&t,i=4&t,l=16&t,f=32&t;if(!(2&t||jt(n)))throw new le;l&&!e.length&&(t&=-17,l=e=false),f&&!r.length&&(t&=-33,f=r=false);var c=n&&n.__bindData__;return c&&true!==c?(c=s(c),c[2]&&(c[2]=s(c[2])),c[3]&&(c[3]=s(c[3])),!a||1&c[1]||(c[4]=u),!a&&1&c[1]&&(t|=8),!i||4&c[1]||(c[5]=o),l&&je.apply(c[2]||(c[2]=[]),e),f&&Ee.apply(c[3]||(c[3]=[]),r),c[1]|=t,pt.apply(null,c)):(1==t||17===t?w:et)([n,t,e,r,u,o])
+}function st(){Q.h=F,Q.b=Q.c=Q.g=Q.i="",Q.e="t",Q.j=true;for(var n,t=0;n=arguments[t];t++)for(var e in n)Q[e]=n[e];t=Q.a,Q.d=/^[^,]+/.exec(t)[0],n=ee,t="return function("+t+"){",e=Q;var r="var n,t="+e.d+",E="+e.e+";if(!t)return E;"+e.i+";";e.b?(r+="var u=t.length;n=-1;if("+e.b+"){",Le.unindexedChars&&(r+="if(s(t)){t=t.split('')}"),r+="while(++n<u){"+e.g+";}}else{"):Le.nonEnumArgs&&(r+="var u=t.length;n=-1;if(u&&p(t)){while(++n<u){n+='';"+e.g+";}}else{"),Le.enumPrototypes&&(r+="var G=typeof t=='function';"),Le.enumErrorProps&&(r+="var F=t===k||t instanceof Error;");
+var u=[];if(Le.enumPrototypes&&u.push('!(G&&n=="prototype")'),Le.enumErrorProps&&u.push('!(F&&(n=="message"||n=="name"))'),e.j&&e.f)r+="var C=-1,D=B[typeof t]&&v(t),u=D?D.length:0;while(++C<u){n=D[C];",u.length&&(r+="if("+u.join("&&")+"){"),r+=e.g+";",u.length&&(r+="}"),r+="}";else if(r+="for(n in t){",e.j&&u.push("m.call(t, n)"),u.length&&(r+="if("+u.join("&&")+"){"),r+=e.g+";",u.length&&(r+="}"),r+="}",Le.nonEnumShadows){for(r+="if(t!==A){var i=t.constructor,r=t===(i&&i.prototype),f=t===J?I:t===k?j:L.call(t),x=y[f];",k=0;7>k;k++)r+="n='"+e.h[k]+"';if((!(r&&x[n])&&m.call(t,n))",e.j||(r+="||(!x[n]&&t[n]!==A[n])"),r+="){"+e.g+"}";
+r+="}"}return(e.b||Le.nonEnumArgs)&&(r+="}"),r+=e.c+";return E",n("d,j,k,m,o,p,q,s,v,A,B,y,I,J,L",t+r+"}")(tt,q,ce,we,d,dt,qe,kt,Q.f,pe,X,$e,M,se,he)}function gt(n){return Ve[n]}function ht(){var t=(t=v.indexOf)===zt?n:t;return t}function vt(n){return typeof n=="function"&&ve.test(n)}function yt(n){var t,e;return!n||he.call(n)!=G||(t=n.constructor,jt(t)&&!(t instanceof t))||!Le.argsClass&&dt(n)||!Le.nodeClass&&f(n)?false:Le.ownLast?(nr(n,function(n,t,r){return e=we.call(r,t),false}),false!==e):(nr(n,function(n,t){e=t
+}),typeof e=="undefined"||we.call(n,e))}function mt(n){return He[n]}function dt(n){return n&&typeof n=="object"&&typeof n.length=="number"&&he.call(n)==T||false}function bt(n,t,e){var r=We(n),u=r.length;for(t=tt(t,e,3);u--&&(e=r[u],false!==t(n[e],e,n)););return n}function _t(n){var t=[];return nr(n,function(n,e){jt(n)&&t.push(e)}),t.sort()}function wt(n){for(var t=-1,e=We(n),r=e.length,u={};++t<r;){var o=e[t];u[n[o]]=o}return u}function jt(n){return typeof n=="function"}function xt(n){return!(!n||!X[typeof n])
+}function Ct(n){return typeof n=="number"||n&&typeof n=="object"&&he.call(n)==W||false}function kt(n){return typeof n=="string"||n&&typeof n=="object"&&he.call(n)==M||false}function Et(n){for(var t=-1,e=We(n),r=e.length,u=Zt(r);++t<r;)u[t]=n[e[t]];return u}function Ot(n,t,e){var r=-1,u=ht(),o=n?n.length:0,a=false;return e=(0>e?Be(0,o+e):e)||0,qe(n)?a=-1<u(n,t,e):typeof o=="number"?a=-1<(kt(n)?n.indexOf(t,e):u(n,t,e)):Xe(n,function(n){return++r<e?void 0:!(a=n===t)}),a}function St(n,t,e){var r=true;if(t=v.createCallback(t,e,3),qe(n)){e=-1;
+for(var u=n.length;++e<u&&(r=!!t(n[e],e,n)););}else Xe(n,function(n,e,u){return r=!!t(n,e,u)});return r}function At(n,t,e){var r=[];if(t=v.createCallback(t,e,3),qe(n)){e=-1;for(var u=n.length;++e<u;){var o=n[e];t(o,e,n)&&r.push(o)}}else Xe(n,function(n,e,u){t(n,e,u)&&r.push(n)});return r}function It(n,t,e){if(t=v.createCallback(t,e,3),!qe(n)){var r;return Xe(n,function(n,e,u){return t(n,e,u)?(r=n,false):void 0}),r}e=-1;for(var u=n.length;++e<u;){var o=n[e];if(t(o,e,n))return o}}function Dt(n,t,e){if(t&&typeof e=="undefined"&&qe(n)){e=-1;
+for(var r=n.length;++e<r&&false!==t(n[e],e,n););}else Xe(n,t,e);return n}function Nt(n,t,e){var r=n,u=n?n.length:0;if(t=t&&typeof e=="undefined"?t:tt(t,e,3),qe(n))for(;u--&&false!==t(n[u],u,n););else{if(typeof u!="number")var o=We(n),u=o.length;else Le.unindexedChars&&kt(n)&&(r=n.split(""));Xe(n,function(n,e,a){return e=o?o[--u]:--u,t(r[e],e,a)})}return n}function Bt(n,t,e){var r=-1,u=n?n.length:0,o=Zt(typeof u=="number"?u:0);if(t=v.createCallback(t,e,3),qe(n))for(;++r<u;)o[r]=t(n[r],r,n);else Xe(n,function(n,e,u){o[++r]=t(n,e,u)
+});return o}function Pt(n,t,e){var u=-1/0,o=u;if(typeof t!="function"&&e&&e[t]===n&&(t=null),null==t&&qe(n)){e=-1;for(var a=n.length;++e<a;){var i=n[e];i>o&&(o=i)}}else t=null==t&&kt(n)?r:v.createCallback(t,e,3),Xe(n,function(n,e,r){e=t(n,e,r),e>u&&(u=e,o=n)});return o}function Rt(n,t,e,r){var u=3>arguments.length;if(t=v.createCallback(t,r,4),qe(n)){var o=-1,a=n.length;for(u&&(e=n[++o]);++o<a;)e=t(e,n[o],o,n)}else Xe(n,function(n,r,o){e=u?(u=false,n):t(e,n,r,o)});return e}function Ft(n,t,e,r){var u=3>arguments.length;
+return t=v.createCallback(t,r,4),Nt(n,function(n,r,o){e=u?(u=false,n):t(e,n,r,o)}),e}function Tt(n){var t=-1,e=n?n.length:0,r=Zt(typeof e=="number"?e:0);return Dt(n,function(n){var e=lt(0,++t);r[t]=r[e],r[e]=n}),r}function $t(n,t,e){var r;if(t=v.createCallback(t,e,3),qe(n)){e=-1;for(var u=n.length;++e<u&&!(r=t(n[e],e,n)););}else Xe(n,function(n,e,u){return!(r=t(n,e,u))});return!!r}function Lt(n,t,e){var r=0,u=n?n.length:0;if(typeof t!="number"&&null!=t){var o=-1;for(t=v.createCallback(t,e,3);++o<u&&t(n[o],o,n);)r++
+}else if(r=t,null==r||e)return n?n[0]:h;return s(n,0,Pe(Be(0,r),u))}function zt(t,e,r){if(typeof r=="number"){var u=t?t.length:0;r=0>r?Be(0,u+r):r||0}else if(r)return r=Kt(t,e),t[r]===e?r:-1;return n(t,e,r)}function qt(n,t,e){if(typeof t!="number"&&null!=t){var r=0,u=-1,o=n?n.length:0;for(t=v.createCallback(t,e,3);++u<o&&t(n[u],u,n);)r++}else r=null==t||e?1:Be(0,t);return s(n,r)}function Kt(n,t,e,r){var u=0,o=n?n.length:u;for(e=e?v.createCallback(e,r,1):Ht,t=e(t);u<o;)r=u+o>>>1,e(n[r])<t?u=r+1:o=r;
+return u}function Wt(n,t,e,r){return typeof t!="boolean"&&null!=t&&(r=e,e=typeof t!="function"&&r&&r[t]===n?null:t,t=false),null!=e&&(e=v.createCallback(e,r,3)),ft(n,t,e)}function Gt(){for(var n=1<arguments.length?arguments:arguments[0],t=-1,e=n?Pt(ar(n,"length")):0,r=Zt(0>e?0:e);++t<e;)r[t]=ar(n,t);return r}function Jt(n,t){var e=-1,r=n?n.length:0,u={};for(t||!r||qe(n[0])||(t=[]);++e<r;){var o=n[e];t?u[o]=t[e]:o&&(u[o[0]]=o[1])}return u}function Mt(n,t){return 2<arguments.length?pt(n,17,s(arguments,2),null,t):pt(n,1,null,null,t)
+}function Vt(n,t,e){var r,u,o,a,i,l,f,c=0,p=false,s=true;if(!jt(n))throw new le;if(t=Be(0,t)||0,true===e)var g=true,s=false;else xt(e)&&(g=e.leading,p="maxWait"in e&&(Be(t,e.maxWait)||0),s="trailing"in e?e.trailing:s);var v=function(){var e=t-(ir()-a);0<e?l=Ce(v,e):(u&&me(u),e=f,u=l=f=h,e&&(c=ir(),o=n.apply(i,r),l||u||(r=i=null)))},y=function(){l&&me(l),u=l=f=h,(s||p!==t)&&(c=ir(),o=n.apply(i,r),l||u||(r=i=null))};return function(){if(r=arguments,a=ir(),i=this,f=s&&(l||!g),false===p)var e=g&&!l;else{u||g||(c=a);
+var h=p-(a-c),m=0>=h;m?(u&&(u=me(u)),c=a,o=n.apply(i,r)):u||(u=Ce(y,h))}return m&&l?l=me(l):l||t===p||(l=Ce(v,t)),e&&(m=true,o=n.apply(i,r)),!m||l||u||(r=i=null),o}}function Ht(n){return n}function Ut(n,t,e){var r=true,u=t&&_t(t);t&&(e||u.length)||(null==e&&(e=t),o=y,t=n,n=v,u=_t(t)),false===e?r=false:xt(e)&&"chain"in e&&(r=e.chain);var o=n,a=jt(o);Dt(u,function(e){var u=n[e]=t[e];a&&(o.prototype[e]=function(){var t=this.__chain__,e=this.__wrapped__,a=[e];if(je.apply(a,arguments),a=u.apply(n,a),r||t){if(e===a&&xt(a))return this;
+a=new o(a),a.__chain__=t}return a})})}function Qt(){}function Xt(n){return function(t){return t[n]}}function Yt(){return this.__wrapped__}e=e?ut.defaults(Z.Object(),e,ut.pick(Z,R)):Z;var Zt=e.Array,ne=e.Boolean,te=e.Date,ee=e.Function,re=e.Math,ue=e.Number,oe=e.Object,ae=e.RegExp,ie=e.String,le=e.TypeError,fe=[],ce=e.Error.prototype,pe=oe.prototype,se=ie.prototype,ge=e._,he=pe.toString,ve=ae("^"+ie(he).replace(/[.*+?^${}()|[\]\\]/g,"\\$&").replace(/toString| for [^\]]+/g,".*?")+"$"),ye=re.ceil,me=e.clearTimeout,de=re.floor,be=ee.prototype.toString,_e=vt(_e=oe.getPrototypeOf)&&_e,we=pe.hasOwnProperty,je=fe.push,xe=pe.propertyIsEnumerable,Ce=e.setTimeout,ke=fe.splice,Ee=fe.unshift,Oe=function(){try{var n={},t=vt(t=oe.defineProperty)&&t,e=t(n,n,n)&&t
+}catch(r){}return e}(),Se=vt(Se=oe.create)&&Se,Ae=vt(Ae=Zt.isArray)&&Ae,Ie=e.isFinite,De=e.isNaN,Ne=vt(Ne=oe.keys)&&Ne,Be=re.max,Pe=re.min,Re=e.parseInt,Fe=re.random,Te={};Te[$]=Zt,Te[L]=ne,Te[z]=te,Te[K]=ee,Te[G]=oe,Te[W]=ue,Te[J]=ae,Te[M]=ie;var $e={};$e[$]=$e[z]=$e[W]={constructor:true,toLocaleString:true,toString:true,valueOf:true},$e[L]=$e[M]={constructor:true,toString:true,valueOf:true},$e[q]=$e[K]=$e[J]={constructor:true,toString:true},$e[G]={constructor:true},function(){for(var n=F.length;n--;){var t,e=F[n];
+for(t in $e)we.call($e,t)&&!we.call($e[t],e)&&($e[t][e]=false)}}(),y.prototype=v.prototype;var Le=v.support={};!function(){var n=function(){this.x=1},t={0:1,length:1},r=[];n.prototype={valueOf:1,y:1};for(var u in new n)r.push(u);for(u in arguments);Le.argsClass=he.call(arguments)==T,Le.argsObject=arguments.constructor==oe&&!(arguments instanceof Zt),Le.enumErrorProps=xe.call(ce,"message")||xe.call(ce,"name"),Le.enumPrototypes=xe.call(n,"prototype"),Le.funcDecomp=!vt(e.WinRTError)&&B.test(g),Le.funcNames=typeof ee.name=="string",Le.nonEnumArgs=0!=u,Le.nonEnumShadows=!/valueOf/.test(r),Le.ownLast="x"!=r[0],Le.spliceObjects=(fe.splice.call(t,0,1),!t[0]),Le.unindexedChars="xx"!="x"[0]+oe("x")[0];
+try{Le.nodeClass=!(he.call(document)==G&&!({toString:0}+""))}catch(o){Le.nodeClass=true}}(1),v.templateSettings={escape:/<%-([\s\S]+?)%>/g,evaluate:/<%([\s\S]+?)%>/g,interpolate:I,variable:"",imports:{_:v}},Se||(nt=function(){function n(){}return function(t){if(xt(t)){n.prototype=t;var r=new n;n.prototype=null}return r||e.Object()}}());var ze=Oe?function(n,t){U.value=t,Oe(n,"__bindData__",U)}:Qt;Le.argsClass||(dt=function(n){return n&&typeof n=="object"&&typeof n.length=="number"&&we.call(n,"callee")&&!xe.call(n,"callee")||false
+});var qe=Ae||function(n){return n&&typeof n=="object"&&typeof n.length=="number"&&he.call(n)==$||false},Ke=st({a:"z",e:"[]",i:"if(!(B[typeof z]))return E",g:"E.push(n)"}),We=Ne?function(n){return xt(n)?Le.enumPrototypes&&typeof n=="function"||Le.nonEnumArgs&&n.length&&dt(n)?Ke(n):Ne(n):[]}:Ke,Ge={a:"g,e,K",i:"e=e&&typeof K=='undefined'?e:d(e,K,3)",b:"typeof u=='number'",v:We,g:"if(e(t[n],n,g)===false)return E"},Je={a:"z,H,l",i:"var a=arguments,b=0,c=typeof l=='number'?2:a.length;while(++b<c){t=a[b];if(t&&B[typeof t]){",v:We,g:"if(typeof E[n]=='undefined')E[n]=t[n]",c:"}}"},Me={i:"if(!B[typeof t])return E;"+Ge.i,b:false},Ve={"&":"&","<":"<",">":">",'"':""","'":"'"},He=wt(Ve),Ue=ae("("+We(He).join("|")+")","g"),Qe=ae("["+We(Ve).join("")+"]","g"),Xe=st(Ge),Ye=st(Je,{i:Je.i.replace(";",";if(c>3&&typeof a[c-2]=='function'){var e=d(a[--c-1],a[c--],2)}else if(c>2&&typeof a[c-1]=='function'){e=a[--c]}"),g:"E[n]=e?e(E[n],t[n]):t[n]"}),Ze=st(Je),nr=st(Ge,Me,{j:false}),tr=st(Ge,Me);
+jt(/x/)&&(jt=function(n){return typeof n=="function"&&he.call(n)==K});var er=_e?function(n){if(!n||he.call(n)!=G||!Le.argsClass&&dt(n))return false;var t=n.valueOf,e=vt(t)&&(e=_e(t))&&_e(e);return e?n==e||_e(n)==e:yt(n)}:yt,rr=ct(function(n,t,e){we.call(n,e)?n[e]++:n[e]=1}),ur=ct(function(n,t,e){(we.call(n,e)?n[e]:n[e]=[]).push(t)}),or=ct(function(n,t,e){n[e]=t}),ar=Bt,ir=vt(ir=te.now)&&ir||function(){return(new te).getTime()},lr=8==Re(j+"08")?Re:function(n,t){return Re(kt(n)?n.replace(D,""):n,t||0)};
+return v.after=function(n,t){if(!jt(t))throw new le;return function(){return 1>--n?t.apply(this,arguments):void 0}},v.assign=Ye,v.at=function(n){var t=arguments,e=-1,r=ot(t,true,false,1),t=t[2]&&t[2][t[1]]===n?1:r.length,u=Zt(t);for(Le.unindexedChars&&kt(n)&&(n=n.split(""));++e<t;)u[e]=n[r[e]];return u},v.bind=Mt,v.bindAll=function(n){for(var t=1<arguments.length?ot(arguments,true,false,1):_t(n),e=-1,r=t.length;++e<r;){var u=t[e];n[u]=pt(n[u],1,null,null,n)}return n},v.bindKey=function(n,t){return 2<arguments.length?pt(t,19,s(arguments,2),null,n):pt(t,3,null,null,n)
+},v.chain=function(n){return n=new y(n),n.__chain__=true,n},v.compact=function(n){for(var t=-1,e=n?n.length:0,r=[];++t<e;){var u=n[t];u&&r.push(u)}return r},v.compose=function(){for(var n=arguments,t=n.length;t--;)if(!jt(n[t]))throw new le;return function(){for(var t=arguments,e=n.length;e--;)t=[n[e].apply(this,t)];return t[0]}},v.constant=function(n){return function(){return n}},v.countBy=rr,v.create=function(n,t){var e=nt(n);return t?Ye(e,t):e},v.createCallback=function(n,t,e){var r=typeof n;if(null==n||"function"==r)return tt(n,t,e);
+if("object"!=r)return Xt(n);var u=We(n),o=u[0],a=n[o];return 1!=u.length||a!==a||xt(a)?function(t){for(var e=u.length,r=false;e--&&(r=at(t[u[e]],n[u[e]],null,true)););return r}:function(n){return n=n[o],a===n&&(0!==a||1/a==1/n)}},v.curry=function(n,t){return t=typeof t=="number"?t:+t||n.length,pt(n,4,null,null,null,t)},v.debounce=Vt,v.defaults=Ze,v.defer=function(n){if(!jt(n))throw new le;var t=s(arguments,1);return Ce(function(){n.apply(h,t)},1)},v.delay=function(n,t){if(!jt(n))throw new le;var e=s(arguments,2);
+return Ce(function(){n.apply(h,e)},t)},v.difference=function(n){return rt(n,ot(arguments,true,true,1))},v.filter=At,v.flatten=function(n,t,e,r){return typeof t!="boolean"&&null!=t&&(r=e,e=typeof t!="function"&&r&&r[t]===n?null:t,t=false),null!=e&&(n=Bt(n,e,r)),ot(n,t)},v.forEach=Dt,v.forEachRight=Nt,v.forIn=nr,v.forInRight=function(n,t,e){var r=[];nr(n,function(n,t){r.push(t,n)});var u=r.length;for(t=tt(t,e,3);u--&&false!==t(r[u--],r[u],n););return n},v.forOwn=tr,v.forOwnRight=bt,v.functions=_t,v.groupBy=ur,v.indexBy=or,v.initial=function(n,t,e){var r=0,u=n?n.length:0;
+if(typeof t!="number"&&null!=t){var o=u;for(t=v.createCallback(t,e,3);o--&&t(n[o],o,n);)r++}else r=null==t||e?1:t||r;return s(n,0,Pe(Be(0,u-r),u))},v.intersection=function(){for(var e=[],r=-1,u=arguments.length,a=i(),l=ht(),f=l===n,s=i();++r<u;){var g=arguments[r];(qe(g)||dt(g))&&(e.push(g),a.push(f&&g.length>=_&&o(r?e[r]:s)))}var f=e[0],h=-1,v=f?f.length:0,y=[];n:for(;++h<v;){var m=a[0],g=f[h];if(0>(m?t(m,g):l(s,g))){for(r=u,(m||s).push(g);--r;)if(m=a[r],0>(m?t(m,g):l(e[r],g)))continue n;y.push(g)
+}}for(;u--;)(m=a[u])&&p(m);return c(a),c(s),y},v.invert=wt,v.invoke=function(n,t){var e=s(arguments,2),r=-1,u=typeof t=="function",o=n?n.length:0,a=Zt(typeof o=="number"?o:0);return Dt(n,function(n){a[++r]=(u?t:n[t]).apply(n,e)}),a},v.keys=We,v.map=Bt,v.mapValues=function(n,t,e){var r={};return t=v.createCallback(t,e,3),tr(n,function(n,e,u){r[e]=t(n,e,u)}),r},v.max=Pt,v.memoize=function(n,t){if(!jt(n))throw new le;var e=function(){var r=e.cache,u=t?t.apply(this,arguments):b+arguments[0];return we.call(r,u)?r[u]:r[u]=n.apply(this,arguments)
+};return e.cache={},e},v.merge=function(n){var t=arguments,e=2;if(!xt(n))return n;if("number"!=typeof t[2]&&(e=t.length),3<e&&"function"==typeof t[e-2])var r=tt(t[--e-1],t[e--],2);else 2<e&&"function"==typeof t[e-1]&&(r=t[--e]);for(var t=s(arguments,1,e),u=-1,o=i(),a=i();++u<e;)it(n,t[u],r,o,a);return c(o),c(a),n},v.min=function(n,t,e){var u=1/0,o=u;if(typeof t!="function"&&e&&e[t]===n&&(t=null),null==t&&qe(n)){e=-1;for(var a=n.length;++e<a;){var i=n[e];i<o&&(o=i)}}else t=null==t&&kt(n)?r:v.createCallback(t,e,3),Xe(n,function(n,e,r){e=t(n,e,r),e<u&&(u=e,o=n)
+});return o},v.omit=function(n,t,e){var r={};if(typeof t!="function"){var u=[];nr(n,function(n,t){u.push(t)});for(var u=rt(u,ot(arguments,true,false,1)),o=-1,a=u.length;++o<a;){var i=u[o];r[i]=n[i]}}else t=v.createCallback(t,e,3),nr(n,function(n,e,u){t(n,e,u)||(r[e]=n)});return r},v.once=function(n){var t,e;if(!jt(n))throw new le;return function(){return t?e:(t=true,e=n.apply(this,arguments),n=null,e)}},v.pairs=function(n){for(var t=-1,e=We(n),r=e.length,u=Zt(r);++t<r;){var o=e[t];u[t]=[o,n[o]]}return u
+},v.partial=function(n){return pt(n,16,s(arguments,1))},v.partialRight=function(n){return pt(n,32,null,s(arguments,1))},v.pick=function(n,t,e){var r={};if(typeof t!="function")for(var u=-1,o=ot(arguments,true,false,1),a=xt(n)?o.length:0;++u<a;){var i=o[u];i in n&&(r[i]=n[i])}else t=v.createCallback(t,e,3),nr(n,function(n,e,u){t(n,e,u)&&(r[e]=n)});return r},v.pluck=ar,v.property=Xt,v.pull=function(n){for(var t=arguments,e=0,r=t.length,u=n?n.length:0;++e<r;)for(var o=-1,a=t[e];++o<u;)n[o]===a&&(ke.call(n,o--,1),u--);
+return n},v.range=function(n,t,e){n=+n||0,e=typeof e=="number"?e:+e||1,null==t&&(t=n,n=0);var r=-1;t=Be(0,ye((t-n)/(e||1)));for(var u=Zt(t);++r<t;)u[r]=n,n+=e;return u},v.reject=function(n,t,e){return t=v.createCallback(t,e,3),At(n,function(n,e,r){return!t(n,e,r)})},v.remove=function(n,t,e){var r=-1,u=n?n.length:0,o=[];for(t=v.createCallback(t,e,3);++r<u;)e=n[r],t(e,r,n)&&(o.push(e),ke.call(n,r--,1),u--);return o},v.rest=qt,v.shuffle=Tt,v.sortBy=function(n,t,e){var r=-1,o=qe(t),a=n?n.length:0,f=Zt(typeof a=="number"?a:0);
+for(o||(t=v.createCallback(t,e,3)),Dt(n,function(n,e,u){var a=f[++r]=l();o?a.m=Bt(t,function(t){return n[t]}):(a.m=i())[0]=t(n,e,u),a.n=r,a.o=n}),a=f.length,f.sort(u);a--;)n=f[a],f[a]=n.o,o||c(n.m),p(n);return f},v.tap=function(n,t){return t(n),n},v.throttle=function(n,t,e){var r=true,u=true;if(!jt(n))throw new le;return false===e?r=false:xt(e)&&(r="leading"in e?e.leading:r,u="trailing"in e?e.trailing:u),H.leading=r,H.maxWait=t,H.trailing=u,Vt(n,t,H)},v.times=function(n,t,e){n=-1<(n=+n)?n:0;var r=-1,u=Zt(n);
+for(t=tt(t,e,1);++r<n;)u[r]=t(r);return u},v.toArray=function(n){return n&&typeof n.length=="number"?Le.unindexedChars&&kt(n)?n.split(""):s(n):Et(n)},v.transform=function(n,t,e,r){var u=qe(n);if(null==e)if(u)e=[];else{var o=n&&n.constructor;e=nt(o&&o.prototype)}return t&&(t=v.createCallback(t,r,4),(u?Xe:tr)(n,function(n,r,u){return t(e,n,r,u)})),e},v.union=function(){return ft(ot(arguments,true,true))},v.uniq=Wt,v.values=Et,v.where=At,v.without=function(n){return rt(n,s(arguments,1))},v.wrap=function(n,t){return pt(t,16,[n])
+},v.xor=function(){for(var n=-1,t=arguments.length;++n<t;){var e=arguments[n];if(qe(e)||dt(e))var r=r?ft(rt(r,e).concat(rt(e,r))):e}return r||[]},v.zip=Gt,v.zipObject=Jt,v.collect=Bt,v.drop=qt,v.each=Dt,v.eachRight=Nt,v.extend=Ye,v.methods=_t,v.object=Jt,v.select=At,v.tail=qt,v.unique=Wt,v.unzip=Gt,Ut(v),v.clone=function(n,t,e,r){return typeof t!="boolean"&&null!=t&&(r=e,e=t,t=false),Y(n,t,typeof e=="function"&&tt(e,r,1))},v.cloneDeep=function(n,t,e){return Y(n,true,typeof t=="function"&&tt(t,e,1))},v.contains=Ot,v.escape=function(n){return null==n?"":ie(n).replace(Qe,gt)
+},v.every=St,v.find=It,v.findIndex=function(n,t,e){var r=-1,u=n?n.length:0;for(t=v.createCallback(t,e,3);++r<u;)if(t(n[r],r,n))return r;return-1},v.findKey=function(n,t,e){var r;return t=v.createCallback(t,e,3),tr(n,function(n,e,u){return t(n,e,u)?(r=e,false):void 0}),r},v.findLast=function(n,t,e){var r;return t=v.createCallback(t,e,3),Nt(n,function(n,e,u){return t(n,e,u)?(r=n,false):void 0}),r},v.findLastIndex=function(n,t,e){var r=n?n.length:0;for(t=v.createCallback(t,e,3);r--;)if(t(n[r],r,n))return r;
+return-1},v.findLastKey=function(n,t,e){var r;return t=v.createCallback(t,e,3),bt(n,function(n,e,u){return t(n,e,u)?(r=e,false):void 0}),r},v.has=function(n,t){return n?we.call(n,t):false},v.identity=Ht,v.indexOf=zt,v.isArguments=dt,v.isArray=qe,v.isBoolean=function(n){return true===n||false===n||n&&typeof n=="object"&&he.call(n)==L||false},v.isDate=function(n){return n&&typeof n=="object"&&he.call(n)==z||false},v.isElement=function(n){return n&&1===n.nodeType||false},v.isEmpty=function(n){var t=true;if(!n)return t;var e=he.call(n),r=n.length;
+return e==$||e==M||(Le.argsClass?e==T:dt(n))||e==G&&typeof r=="number"&&jt(n.splice)?!r:(tr(n,function(){return t=false}),t)},v.isEqual=function(n,t,e,r){return at(n,t,typeof e=="function"&&tt(e,r,2))},v.isFinite=function(n){return Ie(n)&&!De(parseFloat(n))},v.isFunction=jt,v.isNaN=function(n){return Ct(n)&&n!=+n},v.isNull=function(n){return null===n},v.isNumber=Ct,v.isObject=xt,v.isPlainObject=er,v.isRegExp=function(n){return n&&X[typeof n]&&he.call(n)==J||false},v.isString=kt,v.isUndefined=function(n){return typeof n=="undefined"
+},v.lastIndexOf=function(n,t,e){var r=n?n.length:0;for(typeof e=="number"&&(r=(0>e?Be(0,r+e):Pe(e,r-1))+1);r--;)if(n[r]===t)return r;return-1},v.mixin=Ut,v.noConflict=function(){return e._=ge,this},v.noop=Qt,v.now=ir,v.parseInt=lr,v.random=function(n,t,e){var r=null==n,u=null==t;return null==e&&(typeof n=="boolean"&&u?(e=n,n=1):u||typeof t!="boolean"||(e=t,u=true)),r&&u&&(t=1),n=+n||0,u?(t=n,n=0):t=+t||0,e||n%1||t%1?(e=Fe(),Pe(n+e*(t-n+parseFloat("1e-"+((e+"").length-1))),t)):lt(n,t)},v.reduce=Rt,v.reduceRight=Ft,v.result=function(n,t){if(n){var e=n[t];
+return jt(e)?n[t]():e}},v.runInContext=g,v.size=function(n){var t=n?n.length:0;return typeof t=="number"?t:We(n).length},v.some=$t,v.sortedIndex=Kt,v.template=function(n,t,e){var r=v.templateSettings;n=ie(n||""),e=Ze({},e,r);var u,o=Ze({},e.imports,r.imports),r=We(o),o=Et(o),i=0,l=e.interpolate||N,f="__p+='",l=ae((e.escape||N).source+"|"+l.source+"|"+(l===I?O:N).source+"|"+(e.evaluate||N).source+"|$","g");n.replace(l,function(t,e,r,o,l,c){return r||(r=o),f+=n.slice(i,c).replace(P,a),e&&(f+="'+__e("+e+")+'"),l&&(u=true,f+="';"+l+";\n__p+='"),r&&(f+="'+((__t=("+r+"))==null?'':__t)+'"),i=c+t.length,t
+}),f+="';",l=e=e.variable,l||(e="obj",f="with("+e+"){"+f+"}"),f=(u?f.replace(x,""):f).replace(C,"$1").replace(E,"$1;"),f="function("+e+"){"+(l?"":e+"||("+e+"={});")+"var __t,__p='',__e=_.escape"+(u?",__j=Array.prototype.join;function print(){__p+=__j.call(arguments,'')}":";")+f+"return __p}";try{var c=ee(r,"return "+f).apply(h,o)}catch(p){throw p.source=f,p}return t?c(t):(c.source=f,c)},v.unescape=function(n){return null==n?"":ie(n).replace(Ue,mt)},v.uniqueId=function(n){var t=++m;return ie(null==n?"":n)+t
+},v.all=St,v.any=$t,v.detect=It,v.findWhere=It,v.foldl=Rt,v.foldr=Ft,v.include=Ot,v.inject=Rt,Ut(function(){var n={};return tr(v,function(t,e){v.prototype[e]||(n[e]=t)}),n}(),false),v.first=Lt,v.last=function(n,t,e){var r=0,u=n?n.length:0;if(typeof t!="number"&&null!=t){var o=u;for(t=v.createCallback(t,e,3);o--&&t(n[o],o,n);)r++}else if(r=t,null==r||e)return n?n[u-1]:h;return s(n,Be(0,u-r))},v.sample=function(n,t,e){return n&&typeof n.length!="number"?n=Et(n):Le.unindexedChars&&kt(n)&&(n=n.split("")),null==t||e?n?n[lt(0,n.length-1)]:h:(n=Tt(n),n.length=Pe(Be(0,t),n.length),n)
+},v.take=Lt,v.head=Lt,tr(v,function(n,t){var e="sample"!==t;v.prototype[t]||(v.prototype[t]=function(t,r){var u=this.__chain__,o=n(this.__wrapped__,t,r);return u||null!=t&&(!r||e&&typeof t=="function")?new y(o,u):o})}),v.VERSION="2.4.1",v.prototype.chain=function(){return this.__chain__=true,this},v.prototype.toString=function(){return ie(this.__wrapped__)},v.prototype.value=Yt,v.prototype.valueOf=Yt,Xe(["join","pop","shift"],function(n){var t=fe[n];v.prototype[n]=function(){var n=this.__chain__,e=t.apply(this.__wrapped__,arguments);
+return n?new y(e,n):e}}),Xe(["push","reverse","sort","unshift"],function(n){var t=fe[n];v.prototype[n]=function(){return t.apply(this.__wrapped__,arguments),this}}),Xe(["concat","slice","splice"],function(n){var t=fe[n];v.prototype[n]=function(){return new y(t.apply(this.__wrapped__,arguments),this.__chain__)}}),Le.spliceObjects||Xe(["pop","shift","splice"],function(n){var t=fe[n],e="splice"==n;v.prototype[n]=function(){var n=this.__chain__,r=this.__wrapped__,u=t.apply(r,arguments);return 0===r.length&&delete r[0],n||e?new y(u,n):u
+}}),v}var h,v=[],y=[],m=0,d={},b=+new Date+"",_=75,w=40,j=" \t\x0B\f\xa0\ufeff\n\r\u2028\u2029\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000",x=/\b__p\+='';/g,C=/\b(__p\+=)''\+/g,E=/(__e\(.*?\)|\b__t\))\+'';/g,O=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,S=/\w*$/,A=/^\s*function[ \n\r\t]+\w/,I=/<%=([\s\S]+?)%>/g,D=RegExp("^["+j+"]*0+(?=.$)"),N=/($^)/,B=/\bthis\b/,P=/['\n\r\t\u2028\u2029\\]/g,R="Array Boolean Date Error Function Math Number Object RegExp String _ attachEvent clearTimeout isFinite isNaN parseInt setTimeout".split(" "),F="constructor hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf".split(" "),T="[object Arguments]",$="[object Array]",L="[object Boolean]",z="[object Date]",q="[object Error]",K="[object Function]",W="[object Number]",G="[object Object]",J="[object RegExp]",M="[object String]",V={};
+V[K]=false,V[T]=V[$]=V[L]=V[z]=V[W]=V[G]=V[J]=V[M]=true;var H={leading:false,maxWait:0,trailing:false},U={configurable:false,enumerable:false,value:null,writable:false},Q={a:"",b:null,c:"",d:"",e:"",v:null,g:"",h:null,support:null,i:"",j:false},X={"boolean":false,"function":true,object:true,number:false,string:false,undefined:false},Y={"\\":"\\","'":"'","\n":"n","\r":"r","\t":"t","\u2028":"u2028","\u2029":"u2029"},Z=X[typeof window]&&window||this,nt=X[typeof exports]&&exports&&!exports.nodeType&&exports,tt=X[typeof module]&&module&&!module.nodeType&&module,et=tt&&tt.exports===nt&&nt,rt=X[typeof global]&&global;
+!rt||rt.global!==rt&&rt.window!==rt||(Z=rt);var ut=g();typeof define=="function"&&typeof define.amd=="object"&&define.amd?(Z._=ut, define(function(){return ut})):nt&&tt?et?(tt.exports=ut)._=ut:nt._=ut:Z._=ut}).call(this);