--- /dev/null
+(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);
\ No newline at end of file
--- /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