From 9df70c63f5576e23cffbbb2f5b72cecac4bcc431 Mon Sep 17 00:00:00 2001 From: Darren Date: Sun, 2 Sep 2012 14:55:36 +0100 Subject: [PATCH] Build script + minified kiwi.js --- client_backbone/dev/app.js | 8 + client_backbone/dev/build.js | 51 +- client_backbone/dev/model.js | 333 --- client_backbone/dev/model_application.js | 7 + client_backbone/dev/model_channel.js | 31 + client_backbone/dev/model_member.js | 97 + client_backbone/dev/model_memberlist.js | 59 + client_backbone/dev/model_panel.js | 89 + client_backbone/dev/model_panellist.js | 29 + client_backbone/dev/model_server.js | 19 + client_backbone/dev/utils.js | 6 - client_backbone/index.html | 17 +- client_backbone/kiwi.js | 2967 +++++++++++----------- client_backbone/kiwi.min.js | 2 +- 14 files changed, 1874 insertions(+), 1841 deletions(-) create mode 100644 client_backbone/dev/app.js delete mode 100644 client_backbone/dev/model.js create mode 100644 client_backbone/dev/model_channel.js create mode 100644 client_backbone/dev/model_member.js create mode 100644 client_backbone/dev/model_memberlist.js create mode 100644 client_backbone/dev/model_panel.js create mode 100644 client_backbone/dev/model_panellist.js create mode 100644 client_backbone/dev/model_server.js diff --git a/client_backbone/dev/app.js b/client_backbone/dev/app.js new file mode 100644 index 0000000..704a3e0 --- /dev/null +++ b/client_backbone/dev/app.js @@ -0,0 +1,8 @@ +// Holds anything kiwi client specific (ie. front, gateway, kiwi.plugs..) +/** +* @namespace +*/ +var kiwi = window.kiwi = {}; + +kiwi.model = {}; +kiwi.view = {}; \ No newline at end of file diff --git a/client_backbone/dev/build.js b/client_backbone/dev/build.js index 4f23d9a..2749271 100644 --- a/client_backbone/dev/build.js +++ b/client_backbone/dev/build.js @@ -5,32 +5,41 @@ var FILE_ENCODING = 'utf-8', EOL = '\n'; -function concat(opts) { - var file_list = opts.src; - var dist_path = opts.dest; +function concat(src) { + var file_list = src; var out = file_list.map(function(file_path){ - return fs.readFileSync(file_path, FILE_ENCODING); + return fs.readFileSync(file_path, FILE_ENCODING) + '\n\n'; }); - fs.writeFileSync(dist_path, out.join(EOL), FILE_ENCODING); + return out.join(EOL); } -concat({ - src : [ - 'utils.js', - 'model.js', - 'model_application.js', - 'model_gateway.js', - 'view.js' - ], - dest : '../kiwi.js' -}); - - -ast = uglyfyJS.parser.parse(fs.readFileSync('../kiwi.js', FILE_ENCODING)); -ast = uglyfyJS.uglify.ast_mangle(ast); -ast = uglyfyJS.uglify.ast_squeeze(ast); -fs.writeFileSync('../kiwi.min.js', uglyfyJS.uglify.gen_code(ast), FILE_ENCODING); +var src = concat([ + __dirname + '/app.js', + __dirname + '/model_application.js', + __dirname + '/model_gateway.js', + __dirname + '/model_member.js', + __dirname + '/model_memberlist.js', + __dirname + '/model_panel.js', + __dirname + '/model_panellist.js', + __dirname + '/model_channel.js', + __dirname + '/model_server.js', + + __dirname + '/utils.js', + __dirname + '/view.js' +]); + + +src = '(function (window) {\n\n' + src + '\n\n})(window);'; + + +fs.writeFileSync(__dirname + '/../kiwi.js', src, FILE_ENCODING); + + +src = uglyfyJS.parser.parse(src); +src = uglyfyJS.uglify.ast_mangle(src); +src = uglyfyJS.uglify.ast_squeeze(src); +fs.writeFileSync(__dirname + '/../kiwi.min.js', uglyfyJS.uglify.gen_code(src), FILE_ENCODING); diff --git a/client_backbone/dev/model.js b/client_backbone/dev/model.js deleted file mode 100644 index 9c4c665..0000000 --- a/client_backbone/dev/model.js +++ /dev/null @@ -1,333 +0,0 @@ -/*jslint white:true, regexp: true, nomen: true, devel: true, undef: true, browser: true, continue: true, sloppy: true, forin: true, newcap: true, plusplus: true, maxerr: 50, indent: 4 */ -/*global kiwi */ -kiwi.model = {}; - -kiwi.model.MemberList = Backbone.Collection.extend({ - model: kiwi.model.Member, - comparator: function (a, b) { - var i, a_modes, b_modes, a_idx, b_idx, a_nick, b_nick; - var user_prefixes = kiwi.gateway.get('user_prefixes'); - a_modes = a.get("modes"); - b_modes = b.get("modes"); - // Try to sort by modes first - if (a_modes.length > 0) { - // a has modes, but b doesn't so a should appear first - if (b_modes.length === 0) { - return -1; - } - a_idx = b_idx = -1; - // Compare the first (highest) mode - for (i = 0; i < user_prefixes.length; i++) { - if (user_prefixes[i].mode === a_modes[0]) { - a_idx = i; - } - } - for (i = 0; i < user_prefixes.length; i++) { - if (user_prefixes[i].mode === b_modes[0]) { - b_idx = i; - } - } - if (a_idx < b_idx) { - return -1; - } else if (a_idx > b_idx) { - return 1; - } - // If we get to here both a and b have the same highest mode so have to resort to lexicographical sorting - - } else if (b_modes.length > 0) { - // b has modes but a doesn't so b should appear first - return 1; - } - a_nick = a.get("nick").toLocaleUpperCase(); - b_nick = b.get("nick").toLocaleUpperCase(); - // Lexicographical sorting - if (a_nick < b_nick) { - return -1; - } else if (a_nick > b_nick) { - return 1; - } else { - // This should never happen; both users have the same nick. - console.log('Something\'s gone wrong somewhere - two users have the same nick!'); - return 0; - } - }, - initialize: function (options) { - this.view = new kiwi.view.MemberList({"model": this}); - }, - getByNick: function (nick) { - if (typeof nick !== 'string') return; - return this.find(function (m) { - return nick.toLowerCase() === m.get('nick').toLowerCase(); - }); - } -}); - -kiwi.model.Member = Backbone.Model.extend({ - sortModes: function (modes) { - return modes.sort(function (a, b) { - var a_idx, b_idx, i; - var user_prefixes = kiwi.gateway.get('user_prefixes'); - - for (i = 0; i < user_prefixes.length; i++) { - if (user_prefixes[i].mode === a) { - a_idx = i; - } - } - for (i = 0; i < user_prefixes.length; i++) { - if (user_prefixes[i].mode === b) { - b_idx = i; - } - } - if (a_idx < b_idx) { - return -1; - } else if (a_idx > b_idx) { - return 1; - } else { - return 0; - } - }); - }, - initialize: function (attributes) { - var nick, modes, prefix; - nick = this.stripPrefix(this.get("nick")); - - modes = this.get("modes"); - modes = modes || []; - this.sortModes(modes); - this.set({"nick": nick, "modes": modes, "prefix": this.getPrefix(modes)}, {silent: true}); - }, - addMode: function (mode) { - var modes_to_add = mode.split(''), - modes, prefix; - - modes = this.get("modes"); - $.each(modes_to_add, function (index, item) { - modes.push(item); - }); - - modes = this.sortModes(modes); - this.set({"prefix": this.getPrefix(modes), "modes": modes}); - }, - removeMode: function (mode) { - var modes_to_remove = mode.split(''), - modes, prefix; - - modes = this.get("modes"); - modes = _.reject(modes, function (m) { - return (modes_to_remove.indexOf(m) !== -1); - }); - - this.set({"prefix": this.getPrefix(modes), "modes": modes}); - }, - getPrefix: function (modes) { - var prefix = ''; - var user_prefixes = kiwi.gateway.get('user_prefixes'); - - if (typeof modes[0] !== 'undefined') { - prefix = _.detect(user_prefixes, function (prefix) { - return prefix.mode === modes[0]; - }); - prefix = (prefix) ? prefix.symbol : ''; - } - return prefix; - }, - stripPrefix: function (nick) { - var tmp = nick, i, j, k; - var user_prefixes = kiwi.gateway.get('user_prefixes'); - i = 0; - - for (j = 0; j < nick.length; j++) { - for (k = 0; k < user_prefixes.length; k++) { - if (nick.charAt(j) === user_prefixes[k].symbol) { - i++; - break; - } - } - } - - return tmp.substr(i); - }, - displayNick: function (full) { - var display = this.get('nick'); - - if (full) { - if (this.get("ident")) { - display += ' [' + this.get("ident") + '@' + this.get("hostname") + ']'; - } - } - - return display; - } -}); - -kiwi.model.PanelList = Backbone.Collection.extend({ - model: kiwi.model.Panel, - - // Holds the active panel - active: null, - - comparator: function (chan) { - return chan.get("name"); - }, - initialize: function () { - this.view = new kiwi.view.Tabs({"el": $('#toolbar .panellist')[0], "model": this}); - - // Automatically create a server tab - this.server = new kiwi.model.Server({'name': kiwi.gateway.get('name')}); - kiwi.gateway.on('change:name', this.view.render, this.view); - this.add(this.server); - - this.bind('active', function (active_panel) { - this.active = active_panel; - }, this); - - }, - getByName: function (name) { - if (typeof name !== 'string') return; - return this.find(function (c) { - return name.toLowerCase() === c.get('name').toLowerCase(); - }); - } -}); - -kiwi.model.Panel = Backbone.Model.extend({ - initialize: function (attributes) { - var name = this.get("name") || ""; - this.view = new kiwi.view.Panel({"model": this, "name": name}); - this.set({ - "scrollback": [], - "name": name - }, {"silent": true}); - }, - - addMsg: function (nick, msg, type, opts) { - var message_obj, bs, d; - - opts = opts || {}; - - // Time defaults to now - if (!opts || typeof opts.time === 'undefined') { - d = new Date(); - opts.time = d.getHours().toString().lpad(2, "0") + ":" + d.getMinutes().toString().lpad(2, "0") + ":" + d.getSeconds().toString().lpad(2, "0"); - } - - // CSS style defaults to empty string - if (!opts || typeof opts.style === 'undefined') { - opts.style = ''; - } - - // Escape any HTML that may be in here - // This doesn't seem right to be here.. should be in view (??) - msg = $('
').text(msg).html(); - - // Run through the plugins - message_obj = {"msg": msg, "time": opts.time, "nick": nick, "chan": this.get("name"), "type": type, "style": opts.style}; - //tmp = kiwi.plugs.run('addmsg', message_obj); - if (!message_obj) { - return; - } - - // The CSS class (action, topic, notice, etc) - if (typeof message_obj.type !== "string") { - message_obj.type = ''; - } - - // Make sure we don't have NaN or something - if (typeof message_obj.msg !== "string") { - message_obj.msg = ''; - } - - // Convert IRC formatting into HTML formatting - message_obj.msg = formatIRCMsg(message_obj.msg); - - // Update the scrollback - bs = this.get("scrollback"); - bs.push(message_obj); - - // Keep the scrolback limited - if (bs.length > 250) { - bs.splice(250); - } - this.set({"scrollback": bs}, {silent: true}); - - this.trigger("msg", message_obj); - }, - - close: function () { - this.view.remove(); - delete this.view; - - var members = this.get('members'); - if (members) { - members.reset([]); - this.unset('members'); - } - - this.destroy(); - - // If closing the active panel, switch to the server panel - if (this.cid === kiwi.app.panels.active.cid) { - kiwi.app.panels.server.view.show(); - } - }, - - isChannel: function () { - var channel_prefix = kiwi.gateway.get('channel_prefix'), - this_name = this.get('name'); - - if (!this_name) return false; - return (channel_prefix.indexOf(this_name[0]) > -1); - } -}); - -kiwi.model.Server = kiwi.model.Panel.extend({ - server_login: null, - - initialize: function (attributes) { - var name = "Server"; - this.view = new kiwi.view.Panel({"model": this, "name": name}); - this.set({ - "scrollback": [], - "name": name - }, {"silent": true}); - - //this.addMsg(' ', '--> Kiwi IRC: Such an awesome IRC client', '', {style: 'color:#009900;'}); - - this.server_login = new kiwi.view.ServerSelect(); - - this.view.$el.append(this.server_login.$el); - this.server_login.show(); - } -}); - -// TODO: Channel modes -// TODO: Listen to gateway events for anythign related to this channel -kiwi.model.Channel = kiwi.model.Panel.extend({ - initialize: function (attributes) { - var name = this.get("name") || "", - members; - - this.view = new kiwi.view.Channel({"model": this, "name": name}); - this.set({ - "members": new kiwi.model.MemberList(), - "name": name, - "scrollback": [], - "topic": "" - }, {"silent": true}); - - members = this.get("members"); - members.bind("add", function (member) { - this.addMsg(' ', '--> ' + member.displayNick(true) + ' has joined', 'action join'); - }, this); - - members.bind("remove", function (member, members, options) { - var msg = (options.message) ? '(' + options.message + ')' : ''; - - if (options.type === 'quit') { - this.addMsg(' ', '<-- ' + member.displayNick(true) + ' has quit ' + msg, 'action quit'); - } else { - this.addMsg(' ', '<-- ' + member.displayNick(true) + ' has left ' + msg, 'action part'); - } - }, this); - } -}); diff --git a/client_backbone/dev/model_application.js b/client_backbone/dev/model_application.js index d851cc5..71ad828 100644 --- a/client_backbone/dev/model_application.js +++ b/client_backbone/dev/model_application.js @@ -13,6 +13,13 @@ kiwi.model.Application = Backbone.Model.extend(new (function () { }; this.start = function () { + // Only debug if set in the querystring + if (!getQueryVariable('debug')) { + manageDebug(false); + } else { + manageDebug(true); + } + // Set the gateway up kiwi.gateway = new kiwi.model.Gateway(); this.bindGatewayCommands(kiwi.gateway); diff --git a/client_backbone/dev/model_channel.js b/client_backbone/dev/model_channel.js new file mode 100644 index 0000000..3104508 --- /dev/null +++ b/client_backbone/dev/model_channel.js @@ -0,0 +1,31 @@ +// TODO: Channel modes +// TODO: Listen to gateway events for anythign related to this channel +kiwi.model.Channel = kiwi.model.Panel.extend({ + initialize: function (attributes) { + var name = this.get("name") || "", + members; + + this.view = new kiwi.view.Channel({"model": this, "name": name}); + this.set({ + "members": new kiwi.model.MemberList(), + "name": name, + "scrollback": [], + "topic": "" + }, {"silent": true}); + + members = this.get("members"); + members.bind("add", function (member) { + this.addMsg(' ', '--> ' + member.displayNick(true) + ' has joined', 'action join'); + }, this); + + members.bind("remove", function (member, members, options) { + var msg = (options.message) ? '(' + options.message + ')' : ''; + + if (options.type === 'quit') { + this.addMsg(' ', '<-- ' + member.displayNick(true) + ' has quit ' + msg, 'action quit'); + } else { + this.addMsg(' ', '<-- ' + member.displayNick(true) + ' has left ' + msg, 'action part'); + } + }, this); + } +}); \ No newline at end of file diff --git a/client_backbone/dev/model_member.js b/client_backbone/dev/model_member.js new file mode 100644 index 0000000..e74fd05 --- /dev/null +++ b/client_backbone/dev/model_member.js @@ -0,0 +1,97 @@ +kiwi.model.Member = Backbone.Model.extend({ + sortModes: function (modes) { + return modes.sort(function (a, b) { + var a_idx, b_idx, i; + var user_prefixes = kiwi.gateway.get('user_prefixes'); + + for (i = 0; i < user_prefixes.length; i++) { + if (user_prefixes[i].mode === a) { + a_idx = i; + } + } + for (i = 0; i < user_prefixes.length; i++) { + if (user_prefixes[i].mode === b) { + b_idx = i; + } + } + if (a_idx < b_idx) { + return -1; + } else if (a_idx > b_idx) { + return 1; + } else { + return 0; + } + }); + }, + initialize: function (attributes) { + var nick, modes, prefix; + nick = this.stripPrefix(this.get("nick")); + + modes = this.get("modes"); + modes = modes || []; + this.sortModes(modes); + this.set({"nick": nick, "modes": modes, "prefix": this.getPrefix(modes)}, {silent: true}); + }, + addMode: function (mode) { + var modes_to_add = mode.split(''), + modes, prefix; + + modes = this.get("modes"); + $.each(modes_to_add, function (index, item) { + modes.push(item); + }); + + modes = this.sortModes(modes); + this.set({"prefix": this.getPrefix(modes), "modes": modes}); + }, + removeMode: function (mode) { + var modes_to_remove = mode.split(''), + modes, prefix; + + modes = this.get("modes"); + modes = _.reject(modes, function (m) { + return (modes_to_remove.indexOf(m) !== -1); + }); + + this.set({"prefix": this.getPrefix(modes), "modes": modes}); + }, + getPrefix: function (modes) { + var prefix = ''; + var user_prefixes = kiwi.gateway.get('user_prefixes'); + + if (typeof modes[0] !== 'undefined') { + prefix = _.detect(user_prefixes, function (prefix) { + return prefix.mode === modes[0]; + }); + prefix = (prefix) ? prefix.symbol : ''; + } + return prefix; + }, + stripPrefix: function (nick) { + var tmp = nick, i, j, k; + var user_prefixes = kiwi.gateway.get('user_prefixes'); + i = 0; + + for (j = 0; j < nick.length; j++) { + for (k = 0; k < user_prefixes.length; k++) { + if (nick.charAt(j) === user_prefixes[k].symbol) { + i++; + break; + } + } + } + + return tmp.substr(i); + }, + displayNick: function (full) { + var display = this.get('nick'); + + if (full) { + if (this.get("ident")) { + display += ' [' + this.get("ident") + '@' + this.get("hostname") + ']'; + } + } + + return display; + } +}); \ No newline at end of file diff --git a/client_backbone/dev/model_memberlist.js b/client_backbone/dev/model_memberlist.js new file mode 100644 index 0000000..94f6558 --- /dev/null +++ b/client_backbone/dev/model_memberlist.js @@ -0,0 +1,59 @@ +kiwi.model.MemberList = Backbone.Collection.extend({ + model: kiwi.model.Member, + comparator: function (a, b) { + var i, a_modes, b_modes, a_idx, b_idx, a_nick, b_nick; + var user_prefixes = kiwi.gateway.get('user_prefixes'); + a_modes = a.get("modes"); + b_modes = b.get("modes"); + // Try to sort by modes first + if (a_modes.length > 0) { + // a has modes, but b doesn't so a should appear first + if (b_modes.length === 0) { + return -1; + } + a_idx = b_idx = -1; + // Compare the first (highest) mode + for (i = 0; i < user_prefixes.length; i++) { + if (user_prefixes[i].mode === a_modes[0]) { + a_idx = i; + } + } + for (i = 0; i < user_prefixes.length; i++) { + if (user_prefixes[i].mode === b_modes[0]) { + b_idx = i; + } + } + if (a_idx < b_idx) { + return -1; + } else if (a_idx > b_idx) { + return 1; + } + // If we get to here both a and b have the same highest mode so have to resort to lexicographical sorting + + } else if (b_modes.length > 0) { + // b has modes but a doesn't so b should appear first + return 1; + } + a_nick = a.get("nick").toLocaleUpperCase(); + b_nick = b.get("nick").toLocaleUpperCase(); + // Lexicographical sorting + if (a_nick < b_nick) { + return -1; + } else if (a_nick > b_nick) { + return 1; + } else { + // This should never happen; both users have the same nick. + console.log('Something\'s gone wrong somewhere - two users have the same nick!'); + return 0; + } + }, + initialize: function (options) { + this.view = new kiwi.view.MemberList({"model": this}); + }, + getByNick: function (nick) { + if (typeof nick !== 'string') return; + return this.find(function (m) { + return nick.toLowerCase() === m.get('nick').toLowerCase(); + }); + } +}); \ No newline at end of file diff --git a/client_backbone/dev/model_panel.js b/client_backbone/dev/model_panel.js new file mode 100644 index 0000000..3bc446f --- /dev/null +++ b/client_backbone/dev/model_panel.js @@ -0,0 +1,89 @@ +kiwi.model.Panel = Backbone.Model.extend({ + initialize: function (attributes) { + var name = this.get("name") || ""; + this.view = new kiwi.view.Panel({"model": this, "name": name}); + this.set({ + "scrollback": [], + "name": name + }, {"silent": true}); + }, + + addMsg: function (nick, msg, type, opts) { + var message_obj, bs, d; + + opts = opts || {}; + + // Time defaults to now + if (!opts || typeof opts.time === 'undefined') { + d = new Date(); + opts.time = d.getHours().toString().lpad(2, "0") + ":" + d.getMinutes().toString().lpad(2, "0") + ":" + d.getSeconds().toString().lpad(2, "0"); + } + + // CSS style defaults to empty string + if (!opts || typeof opts.style === 'undefined') { + opts.style = ''; + } + + // Escape any HTML that may be in here + // This doesn't seem right to be here.. should be in view (??) + msg = $('
').text(msg).html(); + + // Run through the plugins + message_obj = {"msg": msg, "time": opts.time, "nick": nick, "chan": this.get("name"), "type": type, "style": opts.style}; + //tmp = kiwi.plugs.run('addmsg', message_obj); + if (!message_obj) { + return; + } + + // The CSS class (action, topic, notice, etc) + if (typeof message_obj.type !== "string") { + message_obj.type = ''; + } + + // Make sure we don't have NaN or something + if (typeof message_obj.msg !== "string") { + message_obj.msg = ''; + } + + // Convert IRC formatting into HTML formatting + message_obj.msg = formatIRCMsg(message_obj.msg); + + // Update the scrollback + bs = this.get("scrollback"); + bs.push(message_obj); + + // Keep the scrolback limited + if (bs.length > 250) { + bs.splice(250); + } + this.set({"scrollback": bs}, {silent: true}); + + this.trigger("msg", message_obj); + }, + + close: function () { + this.view.remove(); + delete this.view; + + var members = this.get('members'); + if (members) { + members.reset([]); + this.unset('members'); + } + + this.destroy(); + + // If closing the active panel, switch to the server panel + if (this.cid === kiwi.app.panels.active.cid) { + kiwi.app.panels.server.view.show(); + } + }, + + isChannel: function () { + var channel_prefix = kiwi.gateway.get('channel_prefix'), + this_name = this.get('name'); + + if (!this_name) return false; + return (channel_prefix.indexOf(this_name[0]) > -1); + } +}); \ No newline at end of file diff --git a/client_backbone/dev/model_panellist.js b/client_backbone/dev/model_panellist.js new file mode 100644 index 0000000..28217f3 --- /dev/null +++ b/client_backbone/dev/model_panellist.js @@ -0,0 +1,29 @@ +kiwi.model.PanelList = Backbone.Collection.extend({ + model: kiwi.model.Panel, + + // Holds the active panel + active: null, + + comparator: function (chan) { + return chan.get("name"); + }, + initialize: function () { + this.view = new kiwi.view.Tabs({"el": $('#toolbar .panellist')[0], "model": this}); + + // Automatically create a server tab + this.server = new kiwi.model.Server({'name': kiwi.gateway.get('name')}); + kiwi.gateway.on('change:name', this.view.render, this.view); + this.add(this.server); + + this.bind('active', function (active_panel) { + this.active = active_panel; + }, this); + + }, + getByName: function (name) { + if (typeof name !== 'string') return; + return this.find(function (c) { + return name.toLowerCase() === c.get('name').toLowerCase(); + }); + } +}); \ No newline at end of file diff --git a/client_backbone/dev/model_server.js b/client_backbone/dev/model_server.js new file mode 100644 index 0000000..af5ee1d --- /dev/null +++ b/client_backbone/dev/model_server.js @@ -0,0 +1,19 @@ +kiwi.model.Server = kiwi.model.Panel.extend({ + server_login: null, + + initialize: function (attributes) { + var name = "Server"; + this.view = new kiwi.view.Panel({"model": this, "name": name}); + this.set({ + "scrollback": [], + "name": name + }, {"silent": true}); + + //this.addMsg(' ', '--> Kiwi IRC: Such an awesome IRC client', '', {style: 'color:#009900;'}); + + this.server_login = new kiwi.view.ServerSelect(); + + this.view.$el.append(this.server_login.$el); + this.server_login.show(); + } +}); \ No newline at end of file diff --git a/client_backbone/dev/utils.js b/client_backbone/dev/utils.js index 496306b..d80fd80 100644 --- a/client_backbone/dev/utils.js +++ b/client_backbone/dev/utils.js @@ -1,12 +1,6 @@ /*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 */ -// Holds anything kiwi client specific (ie. front, gateway, kiwi.plugs..) -/** -* @namespace -*/ -var kiwi = {}; - /** diff --git a/client_backbone/index.html b/client_backbone/index.html index 50500e9..60c4a91 100644 --- a/client_backbone/index.html +++ b/client_backbone/index.html @@ -87,10 +87,17 @@ // If in debug mode, load each development script if (getQueryVariable('debug')) { scripts = scripts.concat([ - 'dev/utils.js', - 'dev/model.js', + 'dev/app.js', 'dev/model_application.js', 'dev/model_gateway.js', + 'dev/model_member.js', + 'dev/model_memberlist.js', + 'dev/model_panel.js', + 'dev/model_panellist.js', + 'dev/model_channel.js', + 'dev/model_server.js', + + 'dev/utils.js', 'dev/view.js' ]); } else { @@ -100,12 +107,6 @@ // Run after all dependancies have been loaded function startApp () { - // Only debug if set in the querystring - if (!getQueryVariable('debug')) { - manageDebug(false); - } else { - manageDebug(true); - } kiwi.app = new kiwi.model.Application({container: $('#kiwi')}); kiwi.app.start(); } diff --git a/client_backbone/kiwi.js b/client_backbone/kiwi.js index 22a8ded..f891ef0 100644 --- a/client_backbone/kiwi.js +++ b/client_backbone/kiwi.js @@ -1,940 +1,15 @@ -/*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 */ - -// Holds anything kiwi client specific (ie. front, gateway, kiwi.plugs..) -/** -* @namespace -*/ -var kiwi = {}; - - - -/** -* Suppresses console.log -* @param {Boolean} debug Whether to re-enable console.log or not -*/ -function manageDebug(debug) { - var log, consoleBackUp; - if (window.console) { - consoleBackUp = window.console.log; - window.console.log = function () { - if (debug) { - consoleBackUp.apply(console, arguments); - } - }; - } else { - log = window.opera ? window.opera.postError : alert; - window.console = {}; - window.console.log = function (str) { - if (debug) { - log(str); - } - }; - } -} - -/** -* 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; -} - - - - - -/** -* Formats a message. Adds bold, underline and colouring -* @param {String} msg The message to format -* @returns {String} The HTML formatted message -*/ -function formatIRCMsg (msg) { - var re, next; - - if ((!msg) || (typeof msg !== 'string')) { - return ''; - } - - // bold - if (msg.indexOf(String.fromCharCode(2)) !== -1) { - next = ''; - while (msg.indexOf(String.fromCharCode(2)) !== -1) { - msg = msg.replace(String.fromCharCode(2), next); - next = (next === '') ? '' : ''; - } - if (next === '') { - msg = msg + ''; - } - } - - // underline - if (msg.indexOf(String.fromCharCode(31)) !== -1) { - next = ''; - while (msg.indexOf(String.fromCharCode(31)) !== -1) { - msg = msg.replace(String.fromCharCode(31), next); - next = (next === '') ? '' : ''; - } - if (next === '') { - msg = msg + ''; - } - } - - // colour - /** - * @inner - */ - msg = (function (msg) { - var replace, colourMatch, col, i, match, to, endCol, fg, bg, str; - replace = ''; - /** - * @inner - */ - colourMatch = function (str) { - var re = /^\x03([0-9][0-5]?)(,([0-9][0-5]?))?/; - return re.exec(str); - }; - /** - * @inner - */ - col = 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; - } - }; - if (msg.indexOf('\x03') !== -1) { - i = msg.indexOf('\x03'); - replace = msg.substr(0, i); - while (i < msg.length) { - /** - * @inner - */ - match = colourMatch(msg.substr(i, 6)); - if (match) { - //console.log(match); - // Next colour code - to = msg.indexOf('\x03', i + 1); - endCol = msg.indexOf(String.fromCharCode(15), i + 1); - if (endCol !== -1) { - if (to === -1) { - to = endCol; - } else { - to = ((to < endCol) ? to : endCol); - } - } - if (to === -1) { - to = msg.length; - } - //console.log(i, to); - fg = col(match[1]); - bg = col(match[3]); - str = msg.substring(i + 1 + match[1].length + ((bg !== null) ? match[2].length + 1 : 0), to); - //console.log(str); - replace += '' + str + ''; - i = to; - } else { - if ((msg[i] !== '\x03') && (msg[i] !== String.fromCharCode(15))) { - replace += msg[i]; - } - i++; - } - } - return replace; - } - return msg; - }(msg)); - - return msg; -} - - - - - +(function (window) { +// Holds anything kiwi client specific (ie. front, gateway, kiwi.plugs..) +/** +* @namespace +*/ +var kiwi = window.kiwi = {}; + +kiwi.model = {}; +kiwi.view = {}; -/* - PLUGINS - Each function in each object is looped through and ran. The resulting text - is expected to be returned. -*/ -var plugins = [ - { - name: "images", - onaddmsg: function (event, opts) { - if (!event.msg) { - return event; - } - - event.msg = event.msg.replace(/^((https?\:\/\/|ftp\:\/\/)|(www\.))(\S+)(\w{2,4})(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?(\.jpg|\.jpeg|\.gif|\.bmp|\.png)$/gi, function (url) { - // Don't let any future plugins change it (ie. html_safe plugins) - event.event_bubbles = false; - - var img = ''; - return '' + img + '
'; - }); - - return event; - } - }, - - { - name: "html_safe", - onaddmsg: function (event, opts) { - event.msg = $('
').text(event.msg).html(); - event.nick = $('
').text(event.nick).html(); - - return event; - } - }, - - { - name: "activity", - onaddmsg: function (event, opts) { - //if (kiwi.front.cur_channel.name.toLowerCase() !== kiwi.front.tabviews[event.tabview.toLowerCase()].name) { - // kiwi.front.tabviews[event.tabview].activity(); - //} - - return event; - } - }, - - { - name: "highlight", - onaddmsg: function (event, opts) { - //var tab = Tabviews.getTab(event.tabview.toLowerCase()); - - // If we have a highlight... - //if (event.msg.toLowerCase().indexOf(kiwi.gateway.nick.toLowerCase()) > -1) { - // if (Tabview.getCurrentTab() !== tab) { - // tab.highlight(); - // } - // if (kiwi.front.isChannel(tab.name)) { - // event.msg = '' + event.msg + ''; - // } - //} - - // If it's a PM, highlight - //if (!kiwi.front.isChannel(tab.name) && tab.name !== "server" - // && Tabview.getCurrentTab().name.toLowerCase() !== tab.name - //) { - // tab.highlight(); - //} - - return event; - } - }, - - - - { - //Following method taken from: http://snipplr.com/view/13533/convert-text-urls-into-links/ - name: "linkify_plain", - onaddmsg: function (event, opts) { - if (!event.msg) { - return event; - } - - event.msg = event.msg.replace(/((https?\:\/\/|ftp\:\/\/)|(www\.))(\S+)(\w{2,4})(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/gi, function (url) { - var nice; - // If it's any of the supported images in the images plugin, skip it - if (url.match(/(\.jpg|\.jpeg|\.gif|\.bmp|\.png)$/)) { - return url; - } - - nice = url; - if (url.match('^https?:\/\/')) { - //nice = nice.replace(/^https?:\/\//i,'') - nice = url; // Shutting up JSLint... - } else { - url = 'http://' + url; - } - - //return '' + nice + '
'; - return '' + nice + ''; - }); - - return event; - } - }, - - { - name: "lftobr", - onaddmsg: function (event, opts) { - if (!event.msg) { - return event; - } - - event.msg = event.msg.replace(/\n/gi, function (txt) { - return '
'; - }); - - return event; - } - }, - - - /* - * Disabled due to many websites closing kiwi with iframe busting - { - name: "inBrowser", - oninit: function (event, opts) { - $('#windows a.link_ext').live('mouseover', this.mouseover); - $('#windows a.link_ext').live('mouseout', this.mouseout); - $('#windows a.link_ext').live('click', this.mouseclick); - }, - - onunload: function (event, opts) { - // TODO: make this work (remove all .link_ext_browser as created in mouseover()) - $('#windows a.link_ext').die('mouseover', this.mouseover); - $('#windows a.link_ext').die('mouseout', this.mouseout); - $('#windows a.link_ext').die('click', this.mouseclick); - }, - - - - mouseover: function (e) { - var a = $(this), - tt = $('.tt', a), - tooltip; - - if (tt.text() === '') { - tooltip = $('Open in Kiwi..'); - tt.append(tooltip); - } - - tt.css('top', -tt.outerHeight() + 'px'); - tt.css('left', (a.outerWidth() / 2) - (tt.outerWidth() / 2)); - }, - - mouseout: function (e) { - var a = $(this), - tt = $('.tt', a); - }, - - mouseclick: function (e) { - var a = $(this), - t; - - switch (e.target.className) { - case 'link_ext': - case 'link_img_a': - return true; - //break; - case 'link_ext_browser': - t = new Utilityview('Browser'); - t.topic = a.attr('href'); - - t.iframe = $(''); - t.iframe.attr('src', a.attr('href')); - t.div.append(t.iframe); - t.show(); - break; - } - return false; - - } - }, - */ - - { - name: "nick_colour", - onaddmsg: function (event, opts) { - if (!event.msg) { - return event; - } - - //if (typeof kiwi.front.tabviews[event.tabview].nick_colours === 'undefined') { - // kiwi.front.tabviews[event.tabview].nick_colours = {}; - //} - - //if (typeof kiwi.front.tabviews[event.tabview].nick_colours[event.nick] === 'undefined') { - // kiwi.front.tabviews[event.tabview].nick_colours[event.nick] = this.randColour(); - //} - - //var c = kiwi.front.tabviews[event.tabview].nick_colours[event.nick]; - var c = this.randColour(); - event.nick = '' + event.nick + ''; - - return event; - }, - - - - randColour: function () { - var h = this.rand(-250, 0), - s = this.rand(30, 100), - l = this.rand(20, 70); - return 'hsl(' + h + ',' + s + '%,' + l + '%)'; - }, - - - rand: function (min, max) { - return parseInt(Math.random() * (max - min + 1), 10) + min; - } - }, - - { - name: "kiwitest", - oninit: function (event, opts) { - console.log('registering namespace'); - $(gateway).bind("kiwi.lol.browser", function (e, data) { - console.log('YAY kiwitest'); - console.log(data); - }); - } - } -]; - - - - - - - -/** -* @namespace -*/ -kiwi.plugs = {}; -/** -* Loaded plugins -*/ -kiwi.plugs.loaded = {}; -/** -* Load a plugin -* @param {Object} plugin The plugin to be loaded -* @returns {Boolean} True on success, false on failure -*/ -kiwi.plugs.loadPlugin = function (plugin) { - var plugin_ret; - if (typeof plugin.name !== 'string') { - return false; - } - - plugin_ret = kiwi.plugs.run('plugin_load', {plugin: plugin}); - if (typeof plugin_ret === 'object') { - kiwi.plugs.loaded[plugin_ret.plugin.name] = plugin_ret.plugin; - kiwi.plugs.loaded[plugin_ret.plugin.name].local_data = new kiwi.dataStore('kiwi_plugin_' + plugin_ret.plugin.name); - } - kiwi.plugs.run('init', {}, {run_only: plugin_ret.plugin.name}); - - return true; -}; - -/** -* Unload a plugin -* @param {String} plugin_name The name of the plugin to unload -*/ -kiwi.plugs.unloadPlugin = function (plugin_name) { - if (typeof kiwi.plugs.loaded[plugin_name] !== 'object') { - return; - } - - kiwi.plugs.run('unload', {}, {run_only: plugin_name}); - delete kiwi.plugs.loaded[plugin_name]; -}; - - - -/** -* Run an event against all loaded plugins -* @param {String} event_name The name of the event -* @param {Object} event_data The data to pass to the plugin -* @param {Object} opts Options -* @returns {Object} Event data, possibly modified by the plugins -*/ -kiwi.plugs.run = function (event_name, event_data, opts) { - var ret = event_data, - ret_tmp, - plugin_name; - - // Set some defaults if not provided - event_data = (typeof event_data === 'undefined') ? {} : event_data; - opts = (typeof opts === 'undefined') ? {} : opts; - - for (plugin_name in kiwi.plugs.loaded) { - // If we're only calling 1 plugin, make sure it's that one - if (typeof opts.run_only === 'string' && opts.run_only !== plugin_name) { - continue; - } - - if (typeof kiwi.plugs.loaded[plugin_name]['on' + event_name] === 'function') { - try { - ret_tmp = kiwi.plugs.loaded[plugin_name]['on' + event_name](ret, opts); - if (ret_tmp === null) { - return null; - } - ret = ret_tmp; - - if (typeof ret.event_bubbles === 'boolean' && ret.event_bubbles === false) { - delete ret.event_bubbles; - return ret; - } - } catch (e) { - } - } - } - - return ret; -}; - - - -/** -* @constructor -* @param {String} data_namespace The namespace for the data store -*/ -kiwi.dataStore = function (data_namespace) { - var namespace = data_namespace; - - this.get = function (key) { - return $.jStorage.get(data_namespace + '_' + key); - }; - - this.set = function (key, value) { - return $.jStorage.set(data_namespace + '_' + key, value); - }; -}; - -kiwi.data = new kiwi.dataStore('kiwi'); - - - - -/* - * jQuery jStorage plugin - * https://github.com/andris9/jStorage/ - */ -(function(f){if(!f||!(f.toJSON||Object.toJSON||window.JSON)){throw new Error("jQuery, MooTools or Prototype needs to be loaded before jStorage!")}var g={},d={jStorage:"{}"},h=null,j=0,l=f.toJSON||Object.toJSON||(window.JSON&&(JSON.encode||JSON.stringify)),e=f.evalJSON||(window.JSON&&(JSON.decode||JSON.parse))||function(m){return String(m).evalJSON()},i=false;_XMLService={isXML:function(n){var m=(n?n.ownerDocument||n:0).documentElement;return m?m.nodeName!=="HTML":false},encode:function(n){if(!this.isXML(n)){return false}try{return new XMLSerializer().serializeToString(n)}catch(m){try{return n.xml}catch(o){}}return false},decode:function(n){var m=("DOMParser" in window&&(new DOMParser()).parseFromString)||(window.ActiveXObject&&function(p){var q=new ActiveXObject("Microsoft.XMLDOM");q.async="false";q.loadXML(p);return q}),o;if(!m){return false}o=m.call("DOMParser" in window&&(new DOMParser())||window,n,"text/xml");return this.isXML(o)?o:false}};function k(){if("localStorage" in window){try{if(window.localStorage){d=window.localStorage;i="localStorage"}}catch(p){}}else{if("globalStorage" in window){try{if(window.globalStorage){d=window.globalStorage[window.location.hostname];i="globalStorage"}}catch(o){}}else{h=document.createElement("link");if(h.addBehavior){h.style.behavior="url(#default#userData)";document.getElementsByTagName("head")[0].appendChild(h);h.load("jStorage");var n="{}";try{n=h.getAttribute("jStorage")}catch(m){}d.jStorage=n;i="userDataBehavior"}else{h=null;return}}}b()}function b(){if(d.jStorage){try{g=e(String(d.jStorage))}catch(m){d.jStorage="{}"}}else{d.jStorage="{}"}j=d.jStorage?String(d.jStorage).length:0}function c(){try{d.jStorage=l(g);if(h){h.setAttribute("jStorage",d.jStorage);h.save("jStorage")}j=d.jStorage?String(d.jStorage).length:0}catch(m){}}function a(m){if(!m||(typeof m!="string"&&typeof m!="number")){throw new TypeError("Key name must be string or numeric")}return true}f.jStorage={version:"0.1.5.1",set:function(m,n){a(m);if(_XMLService.isXML(n)){n={_is_xml:true,xml:_XMLService.encode(n)}}g[m]=n;c();return n},get:function(m,n){a(m);if(m in g){if(g[m]&&typeof g[m]=="object"&&g[m]._is_xml&&g[m]._is_xml){return _XMLService.decode(g[m].xml)}else{return g[m]}}return typeof(n)=="undefined"?null:n},deleteKey:function(m){a(m);if(m in g){delete g[m];c();return true}return false},flush:function(){g={};c();return true},storageObj:function(){function m(){}m.prototype=g;return new m()},index:function(){var m=[],n;for(n in g){if(g.hasOwnProperty(n)){m.push(n)}}return m},storageSize:function(){return j},currentBackend:function(){return i},storageAvailable:function(){return !!i},reInit:function(){var m,o;if(h&&h.addBehavior){m=document.createElement("link");h.parentNode.replaceChild(m,h);h=m;h.style.behavior="url(#default#userData)";document.getElementsByTagName("head")[0].appendChild(h);h.load("jStorage");o="{}";try{o=h.getAttribute("jStorage")}catch(n){}d.jStorage=o;i="userDataBehavior"}b()}};k()})(window.jQuery||window.$); -/*jslint white:true, regexp: true, nomen: true, devel: true, undef: true, browser: true, continue: true, sloppy: true, forin: true, newcap: true, plusplus: true, maxerr: 50, indent: 4 */ -/*global kiwi */ -kiwi.model = {}; - -kiwi.model.MemberList = Backbone.Collection.extend({ - model: kiwi.model.Member, - comparator: function (a, b) { - var i, a_modes, b_modes, a_idx, b_idx, a_nick, b_nick; - var user_prefixes = kiwi.gateway.get('user_prefixes'); - a_modes = a.get("modes"); - b_modes = b.get("modes"); - // Try to sort by modes first - if (a_modes.length > 0) { - // a has modes, but b doesn't so a should appear first - if (b_modes.length === 0) { - return -1; - } - a_idx = b_idx = -1; - // Compare the first (highest) mode - for (i = 0; i < user_prefixes.length; i++) { - if (user_prefixes[i].mode === a_modes[0]) { - a_idx = i; - } - } - for (i = 0; i < user_prefixes.length; i++) { - if (user_prefixes[i].mode === b_modes[0]) { - b_idx = i; - } - } - if (a_idx < b_idx) { - return -1; - } else if (a_idx > b_idx) { - return 1; - } - // If we get to here both a and b have the same highest mode so have to resort to lexicographical sorting - - } else if (b_modes.length > 0) { - // b has modes but a doesn't so b should appear first - return 1; - } - a_nick = a.get("nick").toLocaleUpperCase(); - b_nick = b.get("nick").toLocaleUpperCase(); - // Lexicographical sorting - if (a_nick < b_nick) { - return -1; - } else if (a_nick > b_nick) { - return 1; - } else { - // This should never happen; both users have the same nick. - console.log('Something\'s gone wrong somewhere - two users have the same nick!'); - return 0; - } - }, - initialize: function (options) { - this.view = new kiwi.view.MemberList({"model": this}); - }, - getByNick: function (nick) { - if (typeof nick !== 'string') return; - return this.find(function (m) { - return nick.toLowerCase() === m.get('nick').toLowerCase(); - }); - } -}); - -kiwi.model.Member = Backbone.Model.extend({ - sortModes: function (modes) { - return modes.sort(function (a, b) { - var a_idx, b_idx, i; - var user_prefixes = kiwi.gateway.get('user_prefixes'); - - for (i = 0; i < user_prefixes.length; i++) { - if (user_prefixes[i].mode === a) { - a_idx = i; - } - } - for (i = 0; i < user_prefixes.length; i++) { - if (user_prefixes[i].mode === b) { - b_idx = i; - } - } - if (a_idx < b_idx) { - return -1; - } else if (a_idx > b_idx) { - return 1; - } else { - return 0; - } - }); - }, - initialize: function (attributes) { - var nick, modes, prefix; - nick = this.stripPrefix(this.get("nick")); - - modes = this.get("modes"); - modes = modes || []; - this.sortModes(modes); - this.set({"nick": nick, "modes": modes, "prefix": this.getPrefix(modes)}, {silent: true}); - }, - addMode: function (mode) { - var modes_to_add = mode.split(''), - modes, prefix; - - modes = this.get("modes"); - $.each(modes_to_add, function (index, item) { - modes.push(item); - }); - - modes = this.sortModes(modes); - this.set({"prefix": this.getPrefix(modes), "modes": modes}); - }, - removeMode: function (mode) { - var modes_to_remove = mode.split(''), - modes, prefix; - - modes = this.get("modes"); - modes = _.reject(modes, function (m) { - return (modes_to_remove.indexOf(m) !== -1); - }); - - this.set({"prefix": this.getPrefix(modes), "modes": modes}); - }, - getPrefix: function (modes) { - var prefix = ''; - var user_prefixes = kiwi.gateway.get('user_prefixes'); - - if (typeof modes[0] !== 'undefined') { - prefix = _.detect(user_prefixes, function (prefix) { - return prefix.mode === modes[0]; - }); - prefix = (prefix) ? prefix.symbol : ''; - } - return prefix; - }, - stripPrefix: function (nick) { - var tmp = nick, i, j, k; - var user_prefixes = kiwi.gateway.get('user_prefixes'); - i = 0; - - for (j = 0; j < nick.length; j++) { - for (k = 0; k < user_prefixes.length; k++) { - if (nick.charAt(j) === user_prefixes[k].symbol) { - i++; - break; - } - } - } - - return tmp.substr(i); - }, - displayNick: function (full) { - var display = this.get('nick'); - - if (full) { - if (this.get("ident")) { - display += ' [' + this.get("ident") + '@' + this.get("hostname") + ']'; - } - } - - return display; - } -}); - -kiwi.model.PanelList = Backbone.Collection.extend({ - model: kiwi.model.Panel, - - // Holds the active panel - active: null, - - comparator: function (chan) { - return chan.get("name"); - }, - initialize: function () { - this.view = new kiwi.view.Tabs({"el": $('#toolbar .panellist')[0], "model": this}); - - // Automatically create a server tab - this.server = new kiwi.model.Server({'name': kiwi.gateway.get('name')}); - kiwi.gateway.on('change:name', this.view.render, this.view); - this.add(this.server); - - this.bind('active', function (active_panel) { - this.active = active_panel; - }, this); - - }, - getByName: function (name) { - if (typeof name !== 'string') return; - return this.find(function (c) { - return name.toLowerCase() === c.get('name').toLowerCase(); - }); - } -}); - -kiwi.model.Panel = Backbone.Model.extend({ - initialize: function (attributes) { - var name = this.get("name") || ""; - this.view = new kiwi.view.Panel({"model": this, "name": name}); - this.set({ - "scrollback": [], - "name": name - }, {"silent": true}); - }, - - addMsg: function (nick, msg, type, opts) { - var message_obj, bs, d; - - opts = opts || {}; - - // Time defaults to now - if (!opts || typeof opts.time === 'undefined') { - d = new Date(); - opts.time = d.getHours().toString().lpad(2, "0") + ":" + d.getMinutes().toString().lpad(2, "0") + ":" + d.getSeconds().toString().lpad(2, "0"); - } - - // CSS style defaults to empty string - if (!opts || typeof opts.style === 'undefined') { - opts.style = ''; - } - - // Escape any HTML that may be in here - // This doesn't seem right to be here.. should be in view (??) - msg = $('
').text(msg).html(); - - // Run through the plugins - message_obj = {"msg": msg, "time": opts.time, "nick": nick, "chan": this.get("name"), "type": type, "style": opts.style}; - //tmp = kiwi.plugs.run('addmsg', message_obj); - if (!message_obj) { - return; - } - - // The CSS class (action, topic, notice, etc) - if (typeof message_obj.type !== "string") { - message_obj.type = ''; - } - - // Make sure we don't have NaN or something - if (typeof message_obj.msg !== "string") { - message_obj.msg = ''; - } - - // Convert IRC formatting into HTML formatting - message_obj.msg = formatIRCMsg(message_obj.msg); - - // Update the scrollback - bs = this.get("scrollback"); - bs.push(message_obj); - - // Keep the scrolback limited - if (bs.length > 250) { - bs.splice(250); - } - this.set({"scrollback": bs}, {silent: true}); - - this.trigger("msg", message_obj); - }, - - close: function () { - this.view.remove(); - delete this.view; - - var members = this.get('members'); - if (members) { - members.reset([]); - this.unset('members'); - } - - this.destroy(); - - // If closing the active panel, switch to the server panel - if (this.cid === kiwi.app.panels.active.cid) { - kiwi.app.panels.server.view.show(); - } - }, - - isChannel: function () { - var channel_prefix = kiwi.gateway.get('channel_prefix'), - this_name = this.get('name'); - - if (!this_name) return false; - return (channel_prefix.indexOf(this_name[0]) > -1); - } -}); - -kiwi.model.Server = kiwi.model.Panel.extend({ - server_login: null, - - initialize: function (attributes) { - var name = "Server"; - this.view = new kiwi.view.Panel({"model": this, "name": name}); - this.set({ - "scrollback": [], - "name": name - }, {"silent": true}); - - //this.addMsg(' ', '--> Kiwi IRC: Such an awesome IRC client', '', {style: 'color:#009900;'}); - - this.server_login = new kiwi.view.ServerSelect(); - - this.view.$el.append(this.server_login.$el); - this.server_login.show(); - } -}); - -// TODO: Channel modes -// TODO: Listen to gateway events for anythign related to this channel -kiwi.model.Channel = kiwi.model.Panel.extend({ - initialize: function (attributes) { - var that = this, - name = this.get("name") || "", - members; - - this.view = new kiwi.view.Channel({"model": this, "name": name}); - this.set({ - "members": new kiwi.model.MemberList(), - "name": name, - "scrollback": [], - "topic": "" - }, {"silent": true}); - - members = this.get("members"); - members.bind("add", function (member) { - this.addMsg(' ', '--> ' + member.displayNick(true) + ' has joined', 'action join'); - }, this); - - members.bind("remove", function (member, members, options) { - var msg = (options.message) ? '(' + options.message + ')' : ''; - - if (options.type === 'quit') { - this.addMsg(' ', '<-- ' + member.displayNick(true) + ' has quit ' + msg, 'action quit'); - } else { - this.addMsg(' ', '<-- ' + member.displayNick(true) + ' has left ' + msg, 'action part'); - } - }, this); - } -}); - kiwi.model.Application = Backbone.Model.extend(new (function () { var that = this; @@ -950,6 +25,13 @@ kiwi.model.Application = Backbone.Model.extend(new (function () { }; this.start = function () { + // Only debug if set in the querystring + if (!getQueryVariable('debug')) { + manageDebug(false); + } else { + manageDebug(true); + } + // Set the gateway up kiwi.gateway = new kiwi.model.Gateway(); this.bindGatewayCommands(kiwi.gateway); @@ -1136,666 +218,1604 @@ kiwi.model.Application = Backbone.Model.extend(new (function () { }); - gw.on('ontopic', function (event) { - var c; - c = that.panels.getByName(event.channel); - if (!c) return; + gw.on('ontopic', function (event) { + var c; + c = that.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') === kiwi.app.panels.active.get('name')) { + that.topicbar.setCurrentTopic(event.topic); + } + }); + + + gw.on('ontopicsetby', function (event) { + var c, when; + c = that.panels.getByName(event.channel); + if (!c) return; + + when = new Date(event.when * 1000).toLocaleString(); + c.addMsg('', 'Topic set by ' + event.nick + ' at ' + when, 'topic'); + }); + + + gw.on('onuserlist', function (event) { + var channel; + channel = that.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}); + channel.temp_userlist.push(user); + }); + }); + + + gw.on('onuserlist_end', function (event) { + var channel; + channel = that.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; + }); + + + gw.on('onmode', function (event) { + var channel, members, member; + + if (!event.channel) return; + channel = that.panels.getByName(event.channel); + if (!channel) return; + + members = channel.get('members'); + if (!members) return; + + member = members.getByNick(event.effected_nick); + if (!member) return; + + if (event.mode[0] === '+') { + member.addMode(event.mode.substr(1)); + } else if (event.mode[0] === '-') { + member.removeMode(event.mode.substr(1)); + } + }); + + + gw.on('onnick', function (event) { + var member; + + $.each(that.panels.models, function (index, panel) { + if (!panel.isChannel()) return; + + member = panel.get('members').getByNick(event.nick); + if (member) { + member.set('nick', event.newnick); + panel.addMsg('', '== ' + event.nick + ' is now known as ' + event.newnick, 'action nick'); + } + }); + }); + }; + + + + /** + * Bind to certain commands that may be typed into the control box + */ + this.bindControllboxCommands = function (controlbox) { + controlbox.on('unknown_command', this.unknownCommand); + + controlbox.on('command', this.allCommands); + controlbox.on('command_msg', this.msgCommand); + + controlbox.on('command_action', this.actionCommand); + controlbox.on('command_me', this.actionCommand); + + controlbox.on('command_join', this.joinCommand); + controlbox.on('command_j', this.joinCommand); + + controlbox.on('command_part', this.partCommand); + controlbox.on('command_p', this.partCommand); + + controlbox.on('command_nick', function (ev) { + kiwi.gateway.changeNick(ev.params[0]); + }); + + controlbox.on('command_query', this.queryCommand); + controlbox.on('command_q', this.queryCommand); + + controlbox.on('command_topic', this.topicCommand); + + controlbox.on('command_notice', this.noticeCommand); + + controlbox.on('command_css', function (ev) { + var queryString = '?reload=' + new Date().getTime(); + $('link[rel="stylesheet"]').each(function () { + this.href = this.href.replace(/\?.*|$/, queryString); + }); + }); + }; + + // A fallback action. Send a raw command to the server + this.unknownCommand = function (ev) { + var raw_cmd = ev.command + ' ' + ev.params.join(' '); + console.log('RAW: ' + raw_cmd); + kiwi.gateway.raw(raw_cmd); + }; + + this.allCommands = function (ev) { + console.log('allCommands', ev); + }; + + this.joinCommand = function (ev) { + var channel, channel_names; + + channel_names = ev.params.join(' ').split(','); + + $.each(channel_names, function (index, channel_name) { + // Trim any whitespace off the name + channel_name = channel_name.trim(); + + // 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}); + kiwi.app.panels.add(channel); + } + + kiwi.gateway.join(channel_name); + }); + + if (channel) channel.view.show(); + + }; + + this.queryCommand = function (ev) { + var destination, panel; + + destination = ev.params[0]; + + // Check if we have the panel already. If not, create it + panel = that.panels.getByName(destination); + if (!panel) { + panel = new kiwi.model.Channel({name: destination}); + kiwi.app.panels.add(panel); + } + + if (panel) panel.view.show(); + + }; + + this.msgCommand = function (ev) { + var destination = ev.params[0], + panel = that.panels.getByName(destination) || that.panels.server; + + ev.params.shift(); + + panel.addMsg(kiwi.gateway.get('nick'), ev.params.join(' ')); + kiwi.gateway.privmsg(destination, ev.params.join(' ')); + }; + + this.actionCommand = function (ev) { + if (kiwi.app.panels.active === kiwi.app.panels.server) { + return; + } + + var panel = kiwi.app.panels.active; + panel.addMsg('', '* ' + kiwi.gateway.get('nick') + ' ' + ev.params.join(' '), 'action'); + kiwi.gateway.action(panel.get('name'), ev.params.join(' ')); + }; + + this.partCommand = function (ev) { + if (ev.params.length === 0) { + kiwi.gateway.part(kiwi.app.panels.active.get('name')); + } else { + _.each(ev.params, function (channel) { + kiwi.gateway.part(channel); + }); + } + // TODO: More responsive = close tab now, more accurate = leave until part event + //kiwi.app.panels.remove(kiwi.app.panels.active); + }; + + this.topicCommand = function (ev) { + var channel_name; + + if (ev.params.length === 0) return; - // Set the channels topic - c.set('topic', event.topic); + if (that.isChannelName(ev.params[0])) { + channel_name = ev.params[0]; + ev.params.shift(); + } else { + channel_name = kiwi.app.panels.active.get('name'); + } - // If this is the active channel, update the topic bar too - if (c.get('name') === kiwi.app.panels.active.get('name')) { - that.topicbar.setCurrentTopic(event.topic); - } - }); + kiwi.gateway.topic(channel_name, ev.params.join(' ')); + }; + this.noticeCommand = function (ev) { + var destination; - gw.on('ontopicsetby', function (event) { - var c, when; - c = that.panels.getByName(event.channel); - if (!c) return; + // Make sure we have a destination and some sort of message + if (ev.params.length <= 1) return; - when = new Date(event.when * 1000).toLocaleString(); - c.addMsg('', 'Topic set by ' + event.nick + ' at ' + when, 'topic'); - }); + destination = ev.params[0]; + ev.params.shift(); + kiwi.gateway.notice(destination, ev.params.join(' ')); + }; - gw.on('onuserlist', function (event) { - var channel; - channel = that.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}); - channel.temp_userlist.push(user); - }); - }); - gw.on('onuserlist_end', function (event) { - var channel; - channel = that.panels.getByName(event.channel); + this.isChannelName = function (channel_name) { + var channel_prefix = kiwi.gateway.get('channel_prefix'); - // If we didn't find a channel for this, may aswell leave - if (!channel) return; + if (!channel_name || !channel_name.length) return false; + return (channel_prefix.indexOf(channel_name[0]) > -1); + }; - // Update the members list with the new list - channel.get('members').reset(channel.temp_userlist || []); +})()); + + +kiwi.model.Gateway = Backbone.Model.extend(new (function () { + var that = this; - // Clear the temporary userlist - delete channel.temp_userlist; - }); + this.defaults = { + /** + * The name of the network + * @type String + */ + name: 'Server', + /** + * The address (URL) of the network + * @type String + */ + address: '', - gw.on('onmode', function (event) { - var channel, members, member; + /** + * The current nickname + * @type String + */ + nick: '', - if (!event.channel) return; - channel = that.panels.getByName(event.channel); - if (!channel) return; + /** + * The channel prefix for this network + * @type String + */ + channel_prefix: '#', - members = channel.get('members'); - if (!members) return; + /** + * The user prefixes for channel owner/admin/op/voice etc. on this network + * @type Array + */ + user_prefixes: ['~', '&', '@', '+'], - member = members.getByNick(event.effected_nick); - if (!member) return; + /** + * The URL to the Kiwi server + * @type String + */ + //kiwi_server: '//kiwi' + kiwi_server: 'http://localhost:7778/kiwi' + }; - if (event.mode[0] === '+') { - member.addMode(event.mode.substr(1)); - } else if (event.mode[0] === '-') { - member.removeMode(event.mode.substr(1)); - } - }); + this.initialize = function () { + // Update `that` with this new Model object + that = this; - gw.on('onnick', function (event) { - var member; + // For ease of access. The socket.io object + this.socket = this.get('socket'); - $.each(that.panels.models, function (index, panel) { - if (!panel.isChannel()) return; + // Redundant perhaps? Legacy + this.session_id = ''; - member = panel.get('members').getByNick(event.nick); - if (member) { - member.set('nick', event.newnick); - panel.addMsg('', '== ' + event.nick + ' is now known as ' + event.newnick, 'action nick'); - } - }); - }); + network = this; }; - /** - * Bind to certain commands that may be typed into the control box - */ - this.bindControllboxCommands = function (controlbox) { - controlbox.on('unknown_command', this.unknownCommand); - - controlbox.on('command', this.allCommands); - controlbox.on('command_msg', this.msgCommand); + * Connects to the server + * @param {String} host The hostname or IP address of the IRC server to connect to + * @param {Number} port The port of the IRC server to connect to + * @param {Boolean} ssl Whether or not to connect to the IRC server using SSL + * @param {String} password The password to supply to the IRC server during registration + * @param {Function} callback A callback function to be invoked once Kiwi's server has connected to the IRC server + */ + this.connect = function (host, port, ssl, password, callback) { + this.socket = io.connect(this.get('kiwi_server'), { + 'try multiple transports': true, + 'connect timeout': 3000, + 'max reconnection attempts': 7, + 'reconnection delay': 2000 + }); + this.socket.on('connect_failed', function (reason) { + // TODO: When does this even actually get fired? I can't find a case! ~Darren + console.debug('Unable to connect Socket.IO', reason); + console.log("kiwi.gateway.socket.on('connect_failed')"); + //kiwi.front.tabviews.server.addMsg(null, ' ', 'Unable to connect to Kiwi IRC.\n' + reason, 'error'); + this.socket.disconnect(); + this.emit("connect_fail", {reason: reason}); + }); - controlbox.on('command_action', this.actionCommand); - controlbox.on('command_me', this.actionCommand); + this.socket.on('error', function (e) { + this.emit("connect_fail", {reason: e}); + console.log("kiwi.gateway.socket.on('error')", {reason: e}); + }); - controlbox.on('command_join', this.joinCommand); - controlbox.on('command_j', this.joinCommand); + this.socket.on('connecting', function (transport_type) { + console.log("kiwi.gateway.socket.on('connecting')"); + this.emit("connecting"); + that.trigger("connecting"); + }); - controlbox.on('command_part', this.partCommand); - controlbox.on('command_p', this.partCommand); + this.socket.on('connect', function () { + this.emit('irc connect', that.get('nick'), host, port, ssl, password, callback); + console.log("kiwi.gateway.socket.on('connect')"); + }); - controlbox.on('command_nick', function (ev) { - kiwi.gateway.changeNick(ev.params[0]); + this.socket.on('too_many_connections', function () { + this.emit("connect_fail", {reason: 'too_many_connections'}); }); - controlbox.on('command_query', this.queryCommand); - controlbox.on('command_q', this.queryCommand); + this.socket.on('message', this.parse); - controlbox.on('command_topic', this.topicCommand); + this.socket.on('disconnect', function () { + this.emit("disconnect", {}); + console.log("kiwi.gateway.socket.on('disconnect')"); + }); - controlbox.on('command_notice', this.noticeCommand); + this.socket.on('close', function () { + console.log("kiwi.gateway.socket.on('close')"); + }); - controlbox.on('command_css', function (ev) { - var queryString = '?reload=' + new Date().getTime(); - $('link[rel="stylesheet"]').each(function () { - this.href = this.href.replace(/\?.*|$/, queryString); - }); + this.socket.on('reconnecting', function (reconnectionDelay, reconnectionAttempts) { + console.log("kiwi.gateway.socket.on('reconnecting')"); + this.emit("reconnecting", {delay: reconnectionDelay, attempts: reconnectionAttempts}); }); - }; - // A fallback action. Send a raw command to the server - this.unknownCommand = function (ev) { - var raw_cmd = ev.command + ' ' + ev.params.join(' '); - console.log('RAW: ' + raw_cmd); - kiwi.gateway.raw(raw_cmd); + this.socket.on('reconnect_failed', function () { + console.log("kiwi.gateway.socket.on('reconnect_failed')"); + }); }; - this.allCommands = function (ev) { - console.log('allCommands', ev); - }; - this.joinCommand = function (ev) { - var channel, channel_names; + /* + Events: + msg + action + server_connect + options + motd + notice + userlist + nick + join + topic + part + kick + quit + whois + syncchannel_redirect + debug + */ + /** + * Parses the response from the server + */ + this.parse = function (item) { + console.log('gateway event', item); + if (item.event !== undefined) { + that.trigger('on' + item.event, item); + + switch (item.event) { + case 'options': + $.each(item.options, function (name, value) { + switch (name) { + case 'CHANTYPES': + // TODO: Check this. Why is it only getting the first char? + that.set('channel_prefix', value.charAt(0)); + break; + case 'NETWORK': + that.set('name', value); + break; + case 'PREFIX': + that.set('user_prefixes', value); + break; + } + }); + break; - channel_names = ev.params.join(' ').split(','); + case 'connect': + that.set('nick', item.nick); + break; - $.each(channel_names, function (index, channel_name) { - // Trim any whitespace off the name - channel_name = channel_name.trim(); + case 'nick': + if (item.nick === that.get('nick')) { + that.set('nick', item.newnick); + } + break; + /* + case 'sync': + if (kiwi.gateway.onSync && kiwi.gateway.syncing) { + kiwi.gateway.syncing = false; + kiwi.gateway.onSync(item); + } + break; + */ - // 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}); - kiwi.app.panels.add(channel); + case 'kiwi': + this.emit('kiwi.' + item.namespace, item.data); + break; } - - kiwi.gateway.join(channel_name); - }); - - if (channel) channel.view.show(); - + } }; - this.queryCommand = function (ev) { - var destination, panel; - - destination = ev.params[0]; + /** + * Sends data to the server + * @private + * @param {Object} data The data to send + * @param {Function} callback A callback function + */ + this.sendData = function (data, callback) { + this.socket.emit('message', {sid: this.session_id, data: JSON.stringify(data)}, callback); + }; - // Check if we have the panel already. If not, create it - panel = that.panels.getByName(destination); - if (!panel) { - panel = new kiwi.model.Channel({name: destination}); - kiwi.app.panels.add(panel); - } + /** + * Sends a PRIVMSG message + * @param {String} target The target of the message (e.g. a channel or nick) + * @param {String} msg The message to send + * @param {Function} callback A callback function + */ + this.privmsg = function (target, msg, callback) { + var data = { + method: 'privmsg', + args: { + target: target, + msg: msg + } + }; - if (panel) panel.view.show(); - + this.sendData(data, callback); }; - this.msgCommand = function (ev) { - var destination = ev.params[0], - panel = that.panels.getByName(destination) || that.panels.server; - - ev.params.shift(); + /** + * Sends a NOTICE message + * @param {String} target The target of the message (e.g. a channel or nick) + * @param {String} msg The message to send + * @param {Function} callback A callback function + */ + this.notice = function (target, msg, callback) { + var data = { + method: 'notice', + args: { + target: target, + msg: msg + } + }; - panel.addMsg(kiwi.gateway.get('nick'), ev.params.join(' ')); - kiwi.gateway.privmsg(destination, ev.params.join(' ')); + this.sendData(data, callback); }; - this.actionCommand = function (ev) { - if (kiwi.app.panels.active === kiwi.app.panels.server) { - return; - } + /** + * Sends a CTCP message + * @param {Boolean} request Indicates whether this is a CTCP request (true) or reply (false) + * @param {String} type The type of CTCP message, e.g. 'VERSION', 'TIME', 'PING' etc. + * @param {String} target The target of the message, e.g a channel or nick + * @param {String} params Additional paramaters + * @param {Function} callback A callback function + */ + this.ctcp = function (request, type, target, params, callback) { + var data = { + method: 'ctcp', + args: { + request: request, + type: type, + target: target, + params: params + } + }; - var panel = kiwi.app.panels.active; - panel.addMsg('', '* ' + kiwi.gateway.get('nick') + ' ' + ev.params.join(' '), 'action'); - kiwi.gateway.action(panel.get('name'), ev.params.join(' ')); + this.sendData(data, callback); }; - this.partCommand = function (ev) { - if (ev.params.length === 0) { - kiwi.gateway.part(kiwi.app.panels.active.get('name')); - } else { - _.each(ev.params, function (channel) { - kiwi.gateway.part(channel); - }); - } - // TODO: More responsive = close tab now, more accurate = leave until part event - //kiwi.app.panels.remove(kiwi.app.panels.active); + /** + * @param {String} target The target of the message (e.g. a channel or nick) + * @param {String} msg The message to send + * @param {Function} callback A callback function + */ + this.action = function (target, msg, callback) { + this.ctcp(true, 'ACTION', target, msg, callback); }; - this.topicCommand = function (ev) { - var channel_name; - - if (ev.params.length === 0) return; - - if (that.isChannelName(ev.params[0])) { - channel_name = ev.params[0]; - ev.params.shift(); - } else { - channel_name = kiwi.app.panels.active.get('name'); - } + /** + * Joins a channel + * @param {String} channel The channel to join + * @param {String} key The key to the channel + * @param {Function} callback A callback function + */ + this.join = function (channel, key, callback) { + var data = { + method: 'join', + args: { + channel: channel, + key: key + } + }; - kiwi.gateway.topic(channel_name, ev.params.join(' ')); + this.sendData(data, callback); }; - this.noticeCommand = function (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(); + /** + * Leaves a channel + * @param {String} channel The channel to part + * @param {Function} callback A callback function + */ + this.part = function (channel, callback) { + var data = { + method: 'part', + args: { + channel: channel + } + }; - kiwi.gateway.notice(destination, ev.params.join(' ')); + this.sendData(data, callback); }; + /** + * Queries or modifies a channell topic + * @param {String} channel The channel to query or modify + * @param {String} new_topic The new topic to set + * @param {Function} callback A callback function + */ + this.topic = function (channel, new_topic, callback) { + var data = { + method: 'topic', + args: { + channel: channel, + topic: new_topic + } + }; - - - - this.isChannelName = function (channel_name) { - var channel_prefix = kiwi.gateway.get('channel_prefix'); - - if (!channel_name || !channel_name.length) return false; - return (channel_prefix.indexOf(channel_name[0]) > -1); + this.sendData(data, callback); }; -})()); -kiwi.model.Gateway = Backbone.Model.extend(new (function () { - var that = this; - - this.defaults = { - /** - * The name of the network - * @type String - */ - name: 'Server', - - /** - * The address (URL) of the network - * @type String - */ - address: '', - - /** - * The current nickname - * @type String - */ - nick: '', + /** + * Kicks a user from a channel + * @param {String} channel The channel to kick the user from + * @param {String} nick The nick of the user to kick + * @param {String} reason The reason for kicking the user + * @param {Function} callback A callback function + */ + this.kick = function (channel, nick, reason, callback) { + var data = { + method: 'kick', + args: { + channel: channel, + nick: nick, + reason: reason + } + }; - /** - * The channel prefix for this network - * @type String - */ - channel_prefix: '#', + this.sendData(data, callback); + }; - /** - * The user prefixes for channel owner/admin/op/voice etc. on this network - * @type Array - */ - user_prefixes: ['~', '&', '@', '+'], + /** + * Disconnects us from the server + * @param {String} msg The quit message to send to the IRC server + * @param {Function} callback A callback function + */ + this.quit = function (msg, callback) { + msg = msg || ""; + var data = { + method: 'quit', + args: { + message: msg + } + }; - /** - * The URL to the Kiwi server - * @type String - */ - //kiwi_server: '//kiwi' - kiwi_server: 'http://localhost:7778/kiwi' + this.sendData(data, callback); }; + /** + * Sends a string unmodified to the IRC server + * @param {String} data The data to send to the IRC server + * @param {Function} callback A callback function + */ + this.raw = function (data, callback) { + data = { + method: 'raw', + args: { + data: data + } + }; - this.initialize = function () { - // Update `that` with this new Model object - that = this; - - // For ease of access. The socket.io object - this.socket = this.get('socket'); + this.sendData(data, callback); + }; - // Redundant perhaps? Legacy - this.session_id = ''; + /** + * Changes our nickname + * @param {String} new_nick Our new nickname + * @param {Function} callback A callback function + */ + this.changeNick = function (new_nick, callback) { + var data = { + method: 'nick', + args: { + nick: new_nick + } + }; - network = this; + this.sendData(data, callback); }; - /** - * Connects to the server - * @param {String} host The hostname or IP address of the IRC server to connect to - * @param {Number} port The port of the IRC server to connect to - * @param {Boolean} ssl Whether or not to connect to the IRC server using SSL - * @param {String} password The password to supply to the IRC server during registration - * @param {Function} callback A callback function to be invoked once Kiwi's server has connected to the IRC server + * Sends data to a fellow Kiwi IRC user + * @param {String} target The nick of the Kiwi IRC user to send to + * @param {String} data The data to send + * @param {Function} callback A callback function */ - this.connect = function (host, port, ssl, password, callback) { - this.socket = io.connect(this.get('kiwi_server'), { - 'try multiple transports': true, - 'connect timeout': 3000, - 'max reconnection attempts': 7, - 'reconnection delay': 2000 - }); - this.socket.on('connect_failed', function (reason) { - // TODO: When does this even actually get fired? I can't find a case! ~Darren - console.debug('Unable to connect Socket.IO', reason); - console.log("kiwi.gateway.socket.on('connect_failed')"); - //kiwi.front.tabviews.server.addMsg(null, ' ', 'Unable to connect to Kiwi IRC.\n' + reason, 'error'); - this.socket.disconnect(); - this.emit("connect_fail", {reason: reason}); - }); + this.kiwi = function (target, data, callback) { + data = { + method: 'kiwi', + args: { + target: target, + data: data + } + }; - this.socket.on('error', function (e) { - this.emit("connect_fail", {reason: e}); - console.log("kiwi.gateway.socket.on('error')", {reason: e}); - }); + this.sendData(data, callback); + }; +})()); + + +kiwi.model.Member = Backbone.Model.extend({ + sortModes: function (modes) { + return modes.sort(function (a, b) { + var a_idx, b_idx, i; + var user_prefixes = kiwi.gateway.get('user_prefixes'); - this.socket.on('connecting', function (transport_type) { - console.log("kiwi.gateway.socket.on('connecting')"); - this.emit("connecting"); - that.trigger("connecting"); + for (i = 0; i < user_prefixes.length; i++) { + if (user_prefixes[i].mode === a) { + a_idx = i; + } + } + for (i = 0; i < user_prefixes.length; i++) { + if (user_prefixes[i].mode === b) { + b_idx = i; + } + } + if (a_idx < b_idx) { + return -1; + } else if (a_idx > b_idx) { + return 1; + } else { + return 0; + } }); + }, + initialize: function (attributes) { + var nick, modes, prefix; + nick = this.stripPrefix(this.get("nick")); - this.socket.on('connect', function () { - this.emit('irc connect', that.get('nick'), host, port, ssl, password, callback); - console.log("kiwi.gateway.socket.on('connect')"); + modes = this.get("modes"); + modes = modes || []; + this.sortModes(modes); + this.set({"nick": nick, "modes": modes, "prefix": this.getPrefix(modes)}, {silent: true}); + }, + addMode: function (mode) { + var modes_to_add = mode.split(''), + modes, prefix; + + modes = this.get("modes"); + $.each(modes_to_add, function (index, item) { + modes.push(item); }); + + modes = this.sortModes(modes); + this.set({"prefix": this.getPrefix(modes), "modes": modes}); + }, + removeMode: function (mode) { + var modes_to_remove = mode.split(''), + modes, prefix; - this.socket.on('too_many_connections', function () { - this.emit("connect_fail", {reason: 'too_many_connections'}); + modes = this.get("modes"); + modes = _.reject(modes, function (m) { + return (modes_to_remove.indexOf(m) !== -1); }); - this.socket.on('message', this.parse); + this.set({"prefix": this.getPrefix(modes), "modes": modes}); + }, + getPrefix: function (modes) { + var prefix = ''; + var user_prefixes = kiwi.gateway.get('user_prefixes'); - this.socket.on('disconnect', function () { - this.emit("disconnect", {}); - console.log("kiwi.gateway.socket.on('disconnect')"); - }); + if (typeof modes[0] !== 'undefined') { + prefix = _.detect(user_prefixes, function (prefix) { + return prefix.mode === modes[0]; + }); + prefix = (prefix) ? prefix.symbol : ''; + } + return prefix; + }, + stripPrefix: function (nick) { + var tmp = nick, i, j, k; + var user_prefixes = kiwi.gateway.get('user_prefixes'); + i = 0; - this.socket.on('close', function () { - console.log("kiwi.gateway.socket.on('close')"); - }); + for (j = 0; j < nick.length; j++) { + for (k = 0; k < user_prefixes.length; k++) { + if (nick.charAt(j) === user_prefixes[k].symbol) { + i++; + break; + } + } + } - this.socket.on('reconnecting', function (reconnectionDelay, reconnectionAttempts) { - console.log("kiwi.gateway.socket.on('reconnecting')"); - this.emit("reconnecting", {delay: reconnectionDelay, attempts: reconnectionAttempts}); - }); + return tmp.substr(i); + }, + displayNick: function (full) { + var display = this.get('nick'); - this.socket.on('reconnect_failed', function () { - console.log("kiwi.gateway.socket.on('reconnect_failed')"); + if (full) { + if (this.get("ident")) { + display += ' [' + this.get("ident") + '@' + this.get("hostname") + ']'; + } + } + + return display; + } +}); + + +kiwi.model.MemberList = Backbone.Collection.extend({ + model: kiwi.model.Member, + comparator: function (a, b) { + var i, a_modes, b_modes, a_idx, b_idx, a_nick, b_nick; + var user_prefixes = kiwi.gateway.get('user_prefixes'); + a_modes = a.get("modes"); + b_modes = b.get("modes"); + // Try to sort by modes first + if (a_modes.length > 0) { + // a has modes, but b doesn't so a should appear first + if (b_modes.length === 0) { + return -1; + } + a_idx = b_idx = -1; + // Compare the first (highest) mode + for (i = 0; i < user_prefixes.length; i++) { + if (user_prefixes[i].mode === a_modes[0]) { + a_idx = i; + } + } + for (i = 0; i < user_prefixes.length; i++) { + if (user_prefixes[i].mode === b_modes[0]) { + b_idx = i; + } + } + if (a_idx < b_idx) { + return -1; + } else if (a_idx > b_idx) { + return 1; + } + // If we get to here both a and b have the same highest mode so have to resort to lexicographical sorting + + } else if (b_modes.length > 0) { + // b has modes but a doesn't so b should appear first + return 1; + } + a_nick = a.get("nick").toLocaleUpperCase(); + b_nick = b.get("nick").toLocaleUpperCase(); + // Lexicographical sorting + if (a_nick < b_nick) { + return -1; + } else if (a_nick > b_nick) { + return 1; + } else { + // This should never happen; both users have the same nick. + console.log('Something\'s gone wrong somewhere - two users have the same nick!'); + return 0; + } + }, + initialize: function (options) { + this.view = new kiwi.view.MemberList({"model": this}); + }, + getByNick: function (nick) { + if (typeof nick !== 'string') return; + return this.find(function (m) { + return nick.toLowerCase() === m.get('nick').toLowerCase(); }); - }; + } +}); + + +kiwi.model.Panel = Backbone.Model.extend({ + initialize: function (attributes) { + var name = this.get("name") || ""; + this.view = new kiwi.view.Panel({"model": this, "name": name}); + this.set({ + "scrollback": [], + "name": name + }, {"silent": true}); + }, + addMsg: function (nick, msg, type, opts) { + var message_obj, bs, d; - /* - Events: - msg - action - server_connect - options - motd - notice - userlist - nick - join - topic - part - kick - quit - whois - syncchannel_redirect - debug - */ - /** - * Parses the response from the server - */ - this.parse = function (item) { - console.log('gateway event', item); - if (item.event !== undefined) { - that.trigger('on' + item.event, item); + opts = opts || {}; - switch (item.event) { - case 'options': - $.each(item.options, function (name, value) { - switch (name) { - case 'CHANTYPES': - // TODO: Check this. Why is it only getting the first char? - that.set('channel_prefix', value.charAt(0)); - break; - case 'NETWORK': - that.set('name', value); - break; - case 'PREFIX': - that.set('user_prefixes', value); - break; - } - }); - break; + // Time defaults to now + if (!opts || typeof opts.time === 'undefined') { + d = new Date(); + opts.time = d.getHours().toString().lpad(2, "0") + ":" + d.getMinutes().toString().lpad(2, "0") + ":" + d.getSeconds().toString().lpad(2, "0"); + } - case 'connect': - that.set('nick', item.nick); - break; + // CSS style defaults to empty string + if (!opts || typeof opts.style === 'undefined') { + opts.style = ''; + } - case 'nick': - if (item.nick === that.get('nick')) { - that.set('nick', item.newnick); - } - break; - /* - case 'sync': - if (kiwi.gateway.onSync && kiwi.gateway.syncing) { - kiwi.gateway.syncing = false; - kiwi.gateway.onSync(item); - } - break; - */ + // Escape any HTML that may be in here + // This doesn't seem right to be here.. should be in view (??) + msg = $('
').text(msg).html(); - case 'kiwi': - this.emit('kiwi.' + item.namespace, item.data); - break; - } + // Run through the plugins + message_obj = {"msg": msg, "time": opts.time, "nick": nick, "chan": this.get("name"), "type": type, "style": opts.style}; + //tmp = kiwi.plugs.run('addmsg', message_obj); + if (!message_obj) { + return; } - }; - /** - * Sends data to the server - * @private - * @param {Object} data The data to send - * @param {Function} callback A callback function - */ - this.sendData = function (data, callback) { - this.socket.emit('message', {sid: this.session_id, data: JSON.stringify(data)}, callback); - }; + // The CSS class (action, topic, notice, etc) + if (typeof message_obj.type !== "string") { + message_obj.type = ''; + } - /** - * Sends a PRIVMSG message - * @param {String} target The target of the message (e.g. a channel or nick) - * @param {String} msg The message to send - * @param {Function} callback A callback function - */ - this.privmsg = function (target, msg, callback) { - var data = { - method: 'privmsg', - args: { - target: target, - msg: msg - } - }; + // Make sure we don't have NaN or something + if (typeof message_obj.msg !== "string") { + message_obj.msg = ''; + } - this.sendData(data, callback); - }; + // Convert IRC formatting into HTML formatting + message_obj.msg = formatIRCMsg(message_obj.msg); - /** - * Sends a NOTICE message - * @param {String} target The target of the message (e.g. a channel or nick) - * @param {String} msg The message to send - * @param {Function} callback A callback function - */ - this.notice = function (target, msg, callback) { - var data = { - method: 'notice', - args: { - target: target, - msg: msg - } - }; + // Update the scrollback + bs = this.get("scrollback"); + bs.push(message_obj); - this.sendData(data, callback); - }; + // Keep the scrolback limited + if (bs.length > 250) { + bs.splice(250); + } + this.set({"scrollback": bs}, {silent: true}); - /** - * Sends a CTCP message - * @param {Boolean} request Indicates whether this is a CTCP request (true) or reply (false) - * @param {String} type The type of CTCP message, e.g. 'VERSION', 'TIME', 'PING' etc. - * @param {String} target The target of the message, e.g a channel or nick - * @param {String} params Additional paramaters - * @param {Function} callback A callback function - */ - this.ctcp = function (request, type, target, params, callback) { - var data = { - method: 'ctcp', - args: { - request: request, - type: type, - target: target, - params: params - } - }; + this.trigger("msg", message_obj); + }, - this.sendData(data, callback); - }; + close: function () { + this.view.remove(); + delete this.view; - /** - * @param {String} target The target of the message (e.g. a channel or nick) - * @param {String} msg The message to send - * @param {Function} callback A callback function - */ - this.action = function (target, msg, callback) { - this.ctcp(true, 'ACTION', target, msg, callback); - }; + var members = this.get('members'); + if (members) { + members.reset([]); + this.unset('members'); + } - /** - * Joins a channel - * @param {String} channel The channel to join - * @param {String} key The key to the channel - * @param {Function} callback A callback function - */ - this.join = function (channel, key, callback) { - var data = { - method: 'join', - args: { - channel: channel, - key: key - } - }; + this.destroy(); - this.sendData(data, callback); - }; + // If closing the active panel, switch to the server panel + if (this.cid === kiwi.app.panels.active.cid) { + kiwi.app.panels.server.view.show(); + } + }, - /** - * Leaves a channel - * @param {String} channel The channel to part - * @param {Function} callback A callback function - */ - this.part = function (channel, callback) { - var data = { - method: 'part', - args: { - channel: channel - } - }; + isChannel: function () { + var channel_prefix = kiwi.gateway.get('channel_prefix'), + this_name = this.get('name'); - this.sendData(data, callback); - }; + if (!this_name) return false; + return (channel_prefix.indexOf(this_name[0]) > -1); + } +}); + + +kiwi.model.PanelList = Backbone.Collection.extend({ + model: kiwi.model.Panel, - /** - * Queries or modifies a channell topic - * @param {String} channel The channel to query or modify - * @param {String} new_topic The new topic to set - * @param {Function} callback A callback function - */ - this.topic = function (channel, new_topic, callback) { - var data = { - method: 'topic', - args: { - channel: channel, - topic: new_topic - } - }; + // Holds the active panel + active: null, - this.sendData(data, callback); - }; + comparator: function (chan) { + return chan.get("name"); + }, + initialize: function () { + this.view = new kiwi.view.Tabs({"el": $('#toolbar .panellist')[0], "model": this}); - /** - * Kicks a user from a channel - * @param {String} channel The channel to kick the user from - * @param {String} nick The nick of the user to kick - * @param {String} reason The reason for kicking the user - * @param {Function} callback A callback function - */ - this.kick = function (channel, nick, reason, callback) { - var data = { - method: 'kick', - args: { - channel: channel, - nick: nick, - reason: reason - } - }; + // Automatically create a server tab + this.server = new kiwi.model.Server({'name': kiwi.gateway.get('name')}); + kiwi.gateway.on('change:name', this.view.render, this.view); + this.add(this.server); - this.sendData(data, callback); - }; + this.bind('active', function (active_panel) { + this.active = active_panel; + }, this); - /** - * Disconnects us from the server - * @param {String} msg The quit message to send to the IRC server - * @param {Function} callback A callback function - */ - this.quit = function (msg, callback) { - msg = msg || ""; - var data = { - method: 'quit', - args: { - message: msg - } - }; + }, + getByName: function (name) { + if (typeof name !== 'string') return; + return this.find(function (c) { + return name.toLowerCase() === c.get('name').toLowerCase(); + }); + } +}); + + +// TODO: Channel modes +// TODO: Listen to gateway events for anythign related to this channel +kiwi.model.Channel = kiwi.model.Panel.extend({ + initialize: function (attributes) { + var name = this.get("name") || "", + members; - this.sendData(data, callback); - }; + this.view = new kiwi.view.Channel({"model": this, "name": name}); + this.set({ + "members": new kiwi.model.MemberList(), + "name": name, + "scrollback": [], + "topic": "" + }, {"silent": true}); - /** - * Sends a string unmodified to the IRC server - * @param {String} data The data to send to the IRC server - * @param {Function} callback A callback function - */ - this.raw = function (data, callback) { - data = { - method: 'raw', - args: { - data: data - } - }; + members = this.get("members"); + members.bind("add", function (member) { + this.addMsg(' ', '--> ' + member.displayNick(true) + ' has joined', 'action join'); + }, this); - this.sendData(data, callback); - }; + members.bind("remove", function (member, members, options) { + var msg = (options.message) ? '(' + options.message + ')' : ''; - /** - * Changes our nickname - * @param {String} new_nick Our new nickname - * @param {Function} callback A callback function - */ - this.changeNick = function (new_nick, callback) { - var data = { - method: 'nick', - args: { - nick: new_nick + if (options.type === 'quit') { + this.addMsg(' ', '<-- ' + member.displayNick(true) + ' has quit ' + msg, 'action quit'); + } else { + this.addMsg(' ', '<-- ' + member.displayNick(true) + ' has left ' + msg, 'action part'); } - }; + }, this); + } +}); + + +kiwi.model.Server = kiwi.model.Panel.extend({ + server_login: null, - this.sendData(data, callback); - }; + initialize: function (attributes) { + var name = "Server"; + this.view = new kiwi.view.Panel({"model": this, "name": name}); + this.set({ + "scrollback": [], + "name": name + }, {"silent": true}); - /** - * Sends data to a fellow Kiwi IRC user - * @param {String} target The nick of the Kiwi IRC user to send to - * @param {String} data The data to send - * @param {Function} callback A callback function - */ - this.kiwi = function (target, data, callback) { - data = { - method: 'kiwi', - args: { - target: target, - data: data - } - }; + //this.addMsg(' ', '--> Kiwi IRC: Such an awesome IRC client', '', {style: 'color:#009900;'}); - this.sendData(data, callback); - }; -})()); + this.server_login = new kiwi.view.ServerSelect(); + + this.view.$el.append(this.server_login.$el); + this.server_login.show(); + } +}); + + +/*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 */ + + + +/** +* Suppresses console.log +* @param {Boolean} debug Whether to re-enable console.log or not +*/ +function manageDebug(debug) { + var log, consoleBackUp; + if (window.console) { + consoleBackUp = window.console.log; + window.console.log = function () { + if (debug) { + consoleBackUp.apply(console, arguments); + } + }; + } else { + log = window.opera ? window.opera.postError : alert; + window.console = {}; + window.console.log = function (str) { + if (debug) { + log(str); + } + }; + } +} + +/** +* 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; +} + + + + + +/** +* Formats a message. Adds bold, underline and colouring +* @param {String} msg The message to format +* @returns {String} The HTML formatted message +*/ +function formatIRCMsg (msg) { + var re, next; + + if ((!msg) || (typeof msg !== 'string')) { + return ''; + } + + // bold + if (msg.indexOf(String.fromCharCode(2)) !== -1) { + next = ''; + while (msg.indexOf(String.fromCharCode(2)) !== -1) { + msg = msg.replace(String.fromCharCode(2), next); + next = (next === '') ? '' : ''; + } + if (next === '') { + msg = msg + ''; + } + } + + // underline + if (msg.indexOf(String.fromCharCode(31)) !== -1) { + next = ''; + while (msg.indexOf(String.fromCharCode(31)) !== -1) { + msg = msg.replace(String.fromCharCode(31), next); + next = (next === '') ? '' : ''; + } + if (next === '') { + msg = msg + ''; + } + } + + // colour + /** + * @inner + */ + msg = (function (msg) { + var replace, colourMatch, col, i, match, to, endCol, fg, bg, str; + replace = ''; + /** + * @inner + */ + colourMatch = function (str) { + var re = /^\x03([0-9][0-5]?)(,([0-9][0-5]?))?/; + return re.exec(str); + }; + /** + * @inner + */ + col = 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; + } + }; + if (msg.indexOf('\x03') !== -1) { + i = msg.indexOf('\x03'); + replace = msg.substr(0, i); + while (i < msg.length) { + /** + * @inner + */ + match = colourMatch(msg.substr(i, 6)); + if (match) { + //console.log(match); + // Next colour code + to = msg.indexOf('\x03', i + 1); + endCol = msg.indexOf(String.fromCharCode(15), i + 1); + if (endCol !== -1) { + if (to === -1) { + to = endCol; + } else { + to = ((to < endCol) ? to : endCol); + } + } + if (to === -1) { + to = msg.length; + } + //console.log(i, to); + fg = col(match[1]); + bg = col(match[3]); + str = msg.substring(i + 1 + match[1].length + ((bg !== null) ? match[2].length + 1 : 0), to); + //console.log(str); + replace += '' + str + ''; + i = to; + } else { + if ((msg[i] !== '\x03') && (msg[i] !== String.fromCharCode(15))) { + replace += msg[i]; + } + i++; + } + } + return replace; + } + return msg; + }(msg)); + + return msg; +} + + + + + + + + +/* + PLUGINS + Each function in each object is looped through and ran. The resulting text + is expected to be returned. +*/ +var plugins = [ + { + name: "images", + onaddmsg: function (event, opts) { + if (!event.msg) { + return event; + } + + event.msg = event.msg.replace(/^((https?\:\/\/|ftp\:\/\/)|(www\.))(\S+)(\w{2,4})(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?(\.jpg|\.jpeg|\.gif|\.bmp|\.png)$/gi, function (url) { + // Don't let any future plugins change it (ie. html_safe plugins) + event.event_bubbles = false; + + var img = ''; + return '' + img + '
'; + }); + + return event; + } + }, + + { + name: "html_safe", + onaddmsg: function (event, opts) { + event.msg = $('
').text(event.msg).html(); + event.nick = $('
').text(event.nick).html(); + + return event; + } + }, + + { + name: "activity", + onaddmsg: function (event, opts) { + //if (kiwi.front.cur_channel.name.toLowerCase() !== kiwi.front.tabviews[event.tabview.toLowerCase()].name) { + // kiwi.front.tabviews[event.tabview].activity(); + //} + + return event; + } + }, + + { + name: "highlight", + onaddmsg: function (event, opts) { + //var tab = Tabviews.getTab(event.tabview.toLowerCase()); + + // If we have a highlight... + //if (event.msg.toLowerCase().indexOf(kiwi.gateway.nick.toLowerCase()) > -1) { + // if (Tabview.getCurrentTab() !== tab) { + // tab.highlight(); + // } + // if (kiwi.front.isChannel(tab.name)) { + // event.msg = '' + event.msg + ''; + // } + //} + + // If it's a PM, highlight + //if (!kiwi.front.isChannel(tab.name) && tab.name !== "server" + // && Tabview.getCurrentTab().name.toLowerCase() !== tab.name + //) { + // tab.highlight(); + //} + + return event; + } + }, + + + + { + //Following method taken from: http://snipplr.com/view/13533/convert-text-urls-into-links/ + name: "linkify_plain", + onaddmsg: function (event, opts) { + if (!event.msg) { + return event; + } + + event.msg = event.msg.replace(/((https?\:\/\/|ftp\:\/\/)|(www\.))(\S+)(\w{2,4})(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/gi, function (url) { + var nice; + // If it's any of the supported images in the images plugin, skip it + if (url.match(/(\.jpg|\.jpeg|\.gif|\.bmp|\.png)$/)) { + return url; + } + + nice = url; + if (url.match('^https?:\/\/')) { + //nice = nice.replace(/^https?:\/\//i,'') + nice = url; // Shutting up JSLint... + } else { + url = 'http://' + url; + } + + //return '' + nice + '
'; + return '' + nice + ''; + }); + + return event; + } + }, + + { + name: "lftobr", + onaddmsg: function (event, opts) { + if (!event.msg) { + return event; + } + + event.msg = event.msg.replace(/\n/gi, function (txt) { + return '
'; + }); + + return event; + } + }, + + + /* + * Disabled due to many websites closing kiwi with iframe busting + { + name: "inBrowser", + oninit: function (event, opts) { + $('#windows a.link_ext').live('mouseover', this.mouseover); + $('#windows a.link_ext').live('mouseout', this.mouseout); + $('#windows a.link_ext').live('click', this.mouseclick); + }, + + onunload: function (event, opts) { + // TODO: make this work (remove all .link_ext_browser as created in mouseover()) + $('#windows a.link_ext').die('mouseover', this.mouseover); + $('#windows a.link_ext').die('mouseout', this.mouseout); + $('#windows a.link_ext').die('click', this.mouseclick); + }, + + + + mouseover: function (e) { + var a = $(this), + tt = $('.tt', a), + tooltip; + + if (tt.text() === '') { + tooltip = $('Open in Kiwi..'); + tt.append(tooltip); + } + + tt.css('top', -tt.outerHeight() + 'px'); + tt.css('left', (a.outerWidth() / 2) - (tt.outerWidth() / 2)); + }, + + mouseout: function (e) { + var a = $(this), + tt = $('.tt', a); + }, + + mouseclick: function (e) { + var a = $(this), + t; + + switch (e.target.className) { + case 'link_ext': + case 'link_img_a': + return true; + //break; + case 'link_ext_browser': + t = new Utilityview('Browser'); + t.topic = a.attr('href'); + + t.iframe = $(''); + t.iframe.attr('src', a.attr('href')); + t.div.append(t.iframe); + t.show(); + break; + } + return false; + + } + }, + */ + + { + name: "nick_colour", + onaddmsg: function (event, opts) { + if (!event.msg) { + return event; + } + + //if (typeof kiwi.front.tabviews[event.tabview].nick_colours === 'undefined') { + // kiwi.front.tabviews[event.tabview].nick_colours = {}; + //} + + //if (typeof kiwi.front.tabviews[event.tabview].nick_colours[event.nick] === 'undefined') { + // kiwi.front.tabviews[event.tabview].nick_colours[event.nick] = this.randColour(); + //} + + //var c = kiwi.front.tabviews[event.tabview].nick_colours[event.nick]; + var c = this.randColour(); + event.nick = '' + event.nick + ''; + + return event; + }, + + + + randColour: function () { + var h = this.rand(-250, 0), + s = this.rand(30, 100), + l = this.rand(20, 70); + return 'hsl(' + h + ',' + s + '%,' + l + '%)'; + }, + + + rand: function (min, max) { + return parseInt(Math.random() * (max - min + 1), 10) + min; + } + }, + + { + name: "kiwitest", + oninit: function (event, opts) { + console.log('registering namespace'); + $(gateway).bind("kiwi.lol.browser", function (e, data) { + console.log('YAY kiwitest'); + console.log(data); + }); + } + } +]; + + + + + + + +/** +* @namespace +*/ +kiwi.plugs = {}; +/** +* Loaded plugins +*/ +kiwi.plugs.loaded = {}; +/** +* Load a plugin +* @param {Object} plugin The plugin to be loaded +* @returns {Boolean} True on success, false on failure +*/ +kiwi.plugs.loadPlugin = function (plugin) { + var plugin_ret; + if (typeof plugin.name !== 'string') { + return false; + } + + plugin_ret = kiwi.plugs.run('plugin_load', {plugin: plugin}); + if (typeof plugin_ret === 'object') { + kiwi.plugs.loaded[plugin_ret.plugin.name] = plugin_ret.plugin; + kiwi.plugs.loaded[plugin_ret.plugin.name].local_data = new kiwi.dataStore('kiwi_plugin_' + plugin_ret.plugin.name); + } + kiwi.plugs.run('init', {}, {run_only: plugin_ret.plugin.name}); + + return true; +}; + +/** +* Unload a plugin +* @param {String} plugin_name The name of the plugin to unload +*/ +kiwi.plugs.unloadPlugin = function (plugin_name) { + if (typeof kiwi.plugs.loaded[plugin_name] !== 'object') { + return; + } + + kiwi.plugs.run('unload', {}, {run_only: plugin_name}); + delete kiwi.plugs.loaded[plugin_name]; +}; + + + +/** +* Run an event against all loaded plugins +* @param {String} event_name The name of the event +* @param {Object} event_data The data to pass to the plugin +* @param {Object} opts Options +* @returns {Object} Event data, possibly modified by the plugins +*/ +kiwi.plugs.run = function (event_name, event_data, opts) { + var ret = event_data, + ret_tmp, + plugin_name; + + // Set some defaults if not provided + event_data = (typeof event_data === 'undefined') ? {} : event_data; + opts = (typeof opts === 'undefined') ? {} : opts; + + for (plugin_name in kiwi.plugs.loaded) { + // If we're only calling 1 plugin, make sure it's that one + if (typeof opts.run_only === 'string' && opts.run_only !== plugin_name) { + continue; + } + + if (typeof kiwi.plugs.loaded[plugin_name]['on' + event_name] === 'function') { + try { + ret_tmp = kiwi.plugs.loaded[plugin_name]['on' + event_name](ret, opts); + if (ret_tmp === null) { + return null; + } + ret = ret_tmp; + + if (typeof ret.event_bubbles === 'boolean' && ret.event_bubbles === false) { + delete ret.event_bubbles; + return ret; + } + } catch (e) { + } + } + } + + return ret; +}; + + + +/** +* @constructor +* @param {String} data_namespace The namespace for the data store +*/ +kiwi.dataStore = function (data_namespace) { + var namespace = data_namespace; + + this.get = function (key) { + return $.jStorage.get(data_namespace + '_' + key); + }; + + this.set = function (key, value) { + return $.jStorage.set(data_namespace + '_' + key, value); + }; +}; + +kiwi.data = new kiwi.dataStore('kiwi'); + + + + +/* + * jQuery jStorage plugin + * https://github.com/andris9/jStorage/ + */ +(function(f){if(!f||!(f.toJSON||Object.toJSON||window.JSON)){throw new Error("jQuery, MooTools or Prototype needs to be loaded before jStorage!")}var g={},d={jStorage:"{}"},h=null,j=0,l=f.toJSON||Object.toJSON||(window.JSON&&(JSON.encode||JSON.stringify)),e=f.evalJSON||(window.JSON&&(JSON.decode||JSON.parse))||function(m){return String(m).evalJSON()},i=false;_XMLService={isXML:function(n){var m=(n?n.ownerDocument||n:0).documentElement;return m?m.nodeName!=="HTML":false},encode:function(n){if(!this.isXML(n)){return false}try{return new XMLSerializer().serializeToString(n)}catch(m){try{return n.xml}catch(o){}}return false},decode:function(n){var m=("DOMParser" in window&&(new DOMParser()).parseFromString)||(window.ActiveXObject&&function(p){var q=new ActiveXObject("Microsoft.XMLDOM");q.async="false";q.loadXML(p);return q}),o;if(!m){return false}o=m.call("DOMParser" in window&&(new DOMParser())||window,n,"text/xml");return this.isXML(o)?o:false}};function k(){if("localStorage" in window){try{if(window.localStorage){d=window.localStorage;i="localStorage"}}catch(p){}}else{if("globalStorage" in window){try{if(window.globalStorage){d=window.globalStorage[window.location.hostname];i="globalStorage"}}catch(o){}}else{h=document.createElement("link");if(h.addBehavior){h.style.behavior="url(#default#userData)";document.getElementsByTagName("head")[0].appendChild(h);h.load("jStorage");var n="{}";try{n=h.getAttribute("jStorage")}catch(m){}d.jStorage=n;i="userDataBehavior"}else{h=null;return}}}b()}function b(){if(d.jStorage){try{g=e(String(d.jStorage))}catch(m){d.jStorage="{}"}}else{d.jStorage="{}"}j=d.jStorage?String(d.jStorage).length:0}function c(){try{d.jStorage=l(g);if(h){h.setAttribute("jStorage",d.jStorage);h.save("jStorage")}j=d.jStorage?String(d.jStorage).length:0}catch(m){}}function a(m){if(!m||(typeof m!="string"&&typeof m!="number")){throw new TypeError("Key name must be string or numeric")}return true}f.jStorage={version:"0.1.5.1",set:function(m,n){a(m);if(_XMLService.isXML(n)){n={_is_xml:true,xml:_XMLService.encode(n)}}g[m]=n;c();return n},get:function(m,n){a(m);if(m in g){if(g[m]&&typeof g[m]=="object"&&g[m]._is_xml&&g[m]._is_xml){return _XMLService.decode(g[m].xml)}else{return g[m]}}return typeof(n)=="undefined"?null:n},deleteKey:function(m){a(m);if(m in g){delete g[m];c();return true}return false},flush:function(){g={};c();return true},storageObj:function(){function m(){}m.prototype=g;return new m()},index:function(){var m=[],n;for(n in g){if(g.hasOwnProperty(n)){m.push(n)}}return m},storageSize:function(){return j},currentBackend:function(){return i},storageAvailable:function(){return !!i},reInit:function(){var m,o;if(h&&h.addBehavior){m=document.createElement("link");h.parentNode.replaceChild(m,h);h=m;h.style.behavior="url(#default#userData)";document.getElementsByTagName("head")[0].appendChild(h);h.load("jStorage");o="{}";try{o=h.getAttribute("jStorage")}catch(n){}d.jStorage=o;i="userDataBehavior"}b()}};k()})(window.jQuery||window.$); + + /*jslint white:true, regexp: true, nomen: true, devel: true, undef: true, browser: true, continue: true, sloppy: true, forin: true, newcap: true, plusplus: true, maxerr: 50, indent: 4 */ /*global kiwi */ @@ -2164,11 +2184,10 @@ kiwi.view.ControlBox = Backbone.View.extend({ }, initialize: function () { - var cb = this; // TODO: Why is `that` not recognised in the below closure? - that = this; + var that = this; kiwi.gateway.bind('change:nick', function () { - $('.nick', cb.$el).text(this.get('nick')); + $('.nick', that.$el).text(this.get('nick')); }); }, @@ -2306,4 +2325,8 @@ kiwi.view.Application = Backbone.View.extend({ this.doLayout(); } } -}); \ No newline at end of file +}); + + + +})(window); \ No newline at end of file diff --git a/client_backbone/kiwi.min.js b/client_backbone/kiwi.min.js index f8bfca3..0118213 100644 --- a/client_backbone/kiwi.min.js +++ b/client_backbone/kiwi.min.js @@ -1 +1 @@ -function manageDebug(a){var b,c;window.console?(c=window.console.log,window.console.log=function(){a&&c.apply(console,arguments)}):(b=window.opera?window.opera.postError:alert,window.console={},window.console.log=function(c){a&&b(c)})}function randomString(a){var b="0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz",c="",d,e;for(d=0;d";while(a.indexOf(String.fromCharCode(2))!==-1)a=a.replace(String.fromCharCode(2),c),c=c===""?"":"";c===""&&(a+="")}if(a.indexOf(String.fromCharCode(31))!==-1){c="";while(a.indexOf(String.fromCharCode(31))!==-1)a=a.replace(String.fromCharCode(31),c),c=c===""?"":"";c===""&&(a+="")}return a=function(a){var b,c,d,e,f,g,h,i,j,k;b="",c=function(a){var b=/^\x03([0-9][0-5]?)(,([0-9][0-5]?))?/;return b.exec(a)},d=function(a){switch(parseInt(a,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}};if(a.indexOf("")!==-1){e=a.indexOf(""),b=a.substr(0,e);while(e'+k+"",e=g):(a[e]!==""&&a[e]!==String.fromCharCode(15)&&(b+=a[e]),e++);return b}return a}(a),a}var kiwi={};typeof String.prototype.trim=="undefined"&&(String.prototype.trim=function(){return this.replace(/^\s+|\s+$/g,"")}),typeof String.prototype.lpad=="undefined"&&(String.prototype.lpad=function(a,b){var c="",d;for(d=0;d';return''+c+'
'}),a):a}},{name:"html_safe",onaddmsg:function(a,b){return a.msg=$("
").text(a.msg).html(),a.nick=$("
").text(a.nick).html(),a}},{name:"activity",onaddmsg:function(a,b){return a}},{name:"highlight",onaddmsg:function(a,b){return a}},{name:"linkify_plain",onaddmsg:function(a,b){return a.msg?(a.msg=a.msg.replace(/((https?\:\/\/|ftp\:\/\/)|(www\.))(\S+)(\w{2,4})(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/gi,function(a){var b;return a.match(/(\.jpg|\.jpeg|\.gif|\.bmp|\.png)$/)?a:(b=a,a.match("^https?://")?b=a:a="http://"+a,''+b+"")}),a):a}},{name:"lftobr",onaddmsg:function(a,b){return a.msg?(a.msg=a.msg.replace(/\n/gi,function(a){return"
"}),a):a}},{name:"nick_colour",onaddmsg:function(a,b){if(!a.msg)return a;var c=this.randColour();return a.nick=''+a.nick+"",a},randColour:function(){var a=this.rand(-250,0),b=this.rand(30,100),c=this.rand(20,70);return"hsl("+a+","+b+"%,"+c+"%)"},rand:function(a,b){return parseInt(Math.random()*(b-a+1),10)+a}},{name:"kiwitest",oninit:function(a,b){console.log("registering namespace"),$(gateway).bind("kiwi.lol.browser",function(a,b){console.log("YAY kiwitest"),console.log(b)})}}];kiwi.plugs={},kiwi.plugs.loaded={},kiwi.plugs.loadPlugin=function(a){var b;return typeof a.name!="string"?!1:(b=kiwi.plugs.run("plugin_load",{plugin:a}),typeof b=="object"&&(kiwi.plugs.loaded[b.plugin.name]=b.plugin,kiwi.plugs.loaded[b.plugin.name].local_data=new kiwi.dataStore("kiwi_plugin_"+b.plugin.name)),kiwi.plugs.run("init",{},{run_only:b.plugin.name}),!0)},kiwi.plugs.unloadPlugin=function(a){if(typeof kiwi.plugs.loaded[a]!="object")return;kiwi.plugs.run("unload",{},{run_only:a}),delete kiwi.plugs.loaded[a]},kiwi.plugs.run=function(a,b,c){var d=b,e,f;b=typeof b=="undefined"?{}:b,c=typeof c=="undefined"?{}:c;for(f in kiwi.plugs.loaded){if(typeof c.run_only=="string"&&c.run_only!==f)continue;if(typeof kiwi.plugs.loaded[f]["on"+a]=="function")try{e=kiwi.plugs.loaded[f]["on"+a](d,c);if(e===null)return null;d=e;if(typeof d.event_bubbles=="boolean"&&d.event_bubbles===!1)return delete d.event_bubbles,d}catch(g){}}return d},kiwi.dataStore=function(a){var b=a;this.get=function(b){return $.jStorage.get(a+"_"+b)},this.set=function(b,c){return $.jStorage.set(a+"_"+b,c)}},kiwi.data=new kiwi.dataStore("kiwi"),function(a){function i(){if("localStorage"in window)try{window.localStorage&&(c=window.localStorage,h="localStorage")}catch(a){}else if("globalStorage"in window)try{window.globalStorage&&(c=window.globalStorage[window.location.hostname],h="globalStorage")}catch(b){}else{d=document.createElement("link");if(!d.addBehavior){d=null;return}d.style.behavior="url(#default#userData)",document.getElementsByTagName("head")[0].appendChild(d),d.load("jStorage");var e="{}";try{e=d.getAttribute("jStorage")}catch(f){}c.jStorage=e,h="userDataBehavior"}j()}function j(){if(c.jStorage)try{b=g(String(c.jStorage))}catch(a){c.jStorage="{}"}else c.jStorage="{}";e=c.jStorage?String(c.jStorage).length:0}function k(){try{c.jStorage=f(b),d&&(d.setAttribute("jStorage",c.jStorage),d.save("jStorage")),e=c.jStorage?String(c.jStorage).length:0}catch(a){}}function l(a){if(!a||typeof a!="string"&&typeof a!="number")throw new TypeError("Key name must be string or numeric");return!0}if(!a||!(a.toJSON||Object.toJSON||window.JSON))throw new Error("jQuery, MooTools or Prototype needs to be loaded before jStorage!");var b={},c={jStorage:"{}"},d=null,e=0,f=a.toJSON||Object.toJSON||window.JSON&&(JSON.encode||JSON.stringify),g=a.evalJSON||window.JSON&&(JSON.decode||JSON.parse)||function(a){return String(a).evalJSON()},h=!1;_XMLService={isXML:function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1},encode:function(a){if(!this.isXML(a))return!1;try{return(new XMLSerializer).serializeToString(a)}catch(b){try{return a.xml}catch(c){}}return!1},decode:function(a){var b="DOMParser"in window&&(new DOMParser).parseFromString||window.ActiveXObject&&function(a){var b=new ActiveXObject("Microsoft.XMLDOM");return b.async="false",b.loadXML(a),b},c;return b?(c=b.call("DOMParser"in window&&new DOMParser||window,a,"text/xml"),this.isXML(c)?c:!1):!1}},a.jStorage={version:"0.1.5.1",set:function(a,c){return l(a),_XMLService.isXML(c)&&(c={_is_xml:!0,xml:_XMLService.encode(c)}),b[a]=c,k(),c},get:function(a,c){return l(a),a in b?b[a]&&typeof b[a]=="object"&&b[a]._is_xml&&b[a]._is_xml?_XMLService.decode(b[a].xml):b[a]:typeof c=="undefined"?null:c},deleteKey:function(a){return l(a),a in b?(delete b[a],k(),!0):!1},flush:function(){return b={},k(),!0},storageObj:function(){function a(){}return a.prototype=b,new a},index:function(){var a=[],c;for(c in b)b.hasOwnProperty(c)&&a.push(c);return a},storageSize:function(){return e},currentBackend:function(){return h},storageAvailable:function(){return!!h},reInit:function(){var a,b;if(d&&d.addBehavior){a=document.createElement("link"),d.parentNode.replaceChild(a,d),d=a,d.style.behavior="url(#default#userData)",document.getElementsByTagName("head")[0].appendChild(d),d.load("jStorage"),b="{}";try{b=d.getAttribute("jStorage")}catch(e){}c.jStorage=b,h="userDataBehavior"}j()}},i()}(window.jQuery||window.$),kiwi.model={},kiwi.model.MemberList=Backbone.Collection.extend({model:kiwi.model.Member,comparator:function(a,b){var c,d,e,f,g,h,i,j=kiwi.gateway.get("user_prefixes");d=a.get("modes"),e=b.get("modes");if(d.length>0){if(e.length===0)return-1;f=g=-1;for(c=0;cg)return 1}else if(e.length>0)return 1;return h=a.get("nick").toLocaleUpperCase(),i=b.get("nick").toLocaleUpperCase(),hi?1:(console.log("Something's gone wrong somewhere - two users have the same nick!"),0)},initialize:function(a){this.view=new kiwi.view.MemberList({model:this})},getByNick:function(a){if(typeof a!="string")return;return this.find(function(b){return a.toLowerCase()===b.get("nick").toLowerCase()})}}),kiwi.model.Member=Backbone.Model.extend({sortModes:function(a){return a.sort(function(a,b){var c,d,e,f=kiwi.gateway.get("user_prefixes");for(e=0;ed?1:0})},initialize:function(a){var b,c,d;b=this.stripPrefix(this.get("nick")),c=this.get("modes"),c=c||[],this.sortModes(c),this.set({nick:b,modes:c,prefix:this.getPrefix(c)},{silent:!0})},addMode:function(a){var b=a.split(""),c,d;c=this.get("modes"),$.each(b,function(a,b){c.push(b)}),c=this.sortModes(c),this.set({prefix:this.getPrefix(c),modes:c})},removeMode:function(a){var b=a.split(""),c,d;c=this.get("modes"),c=_.reject(c,function(a){return b.indexOf(a)!==-1}),this.set({prefix:this.getPrefix(c),modes:c})},getPrefix:function(a){var b="",c=kiwi.gateway.get("user_prefixes");return typeof a[0]!="undefined"&&(b=_.detect(c,function(b){return b.mode===a[0]}),b=b?b.symbol:""),b},stripPrefix:function(a){var b=a,c,d,e,f=kiwi.gateway.get("user_prefixes");c=0;for(d=0;d").text(b).html(),e={msg:b,time:d.time,nick:a,chan:this.get("name"),type:c,style:d.style};if(!e)return;typeof e.type!="string"&&(e.type=""),typeof e.msg!="string"&&(e.msg=""),e.msg=formatIRCMsg(e.msg),f=this.get("scrollback"),f.push(e),f.length>250&&f.splice(250),this.set({scrollback:f},{silent:!0}),this.trigger("msg",e)},close:function(){this.view.remove(),delete this.view;var a=this.get("members");a&&(a.reset([]),this.unset("members")),this.destroy(),this.cid===kiwi.app.panels.active.cid&&kiwi.app.panels.server.view.show()},isChannel:function(){var a=kiwi.gateway.get("channel_prefix"),b=this.get("name");return b?a.indexOf(b[0])>-1:!1}}),kiwi.model.Server=kiwi.model.Panel.extend({server_login:null,initialize:function(a){var b="Server";this.view=new kiwi.view.Panel({model:this,name:b}),this.set({scrollback:[],name:b},{silent:!0}),this.server_login=new kiwi.view.ServerSelect,this.view.$el.append(this.server_login.$el),this.server_login.show()}}),kiwi.model.Channel=kiwi.model.Panel.extend({initialize:function(a){var b=this,c=this.get("name")||"",d;this.view=new kiwi.view.Channel({model:this,name:c}),this.set({members:new kiwi.model.MemberList,name:c,scrollback:[],topic:""},{silent:!0}),d=this.get("members"),d.bind("add",function(a){this.addMsg(" ","--> "+a.displayNick(!0)+" has joined","action join")},this),d.bind("remove",function(a,b,c){var d=c.message?"("+c.message+")":"";c.type==="quit"?this.addMsg(" ","<-- "+a.displayNick(!0)+" has quit "+d,"action quit"):this.addMsg(" ","<-- "+a.displayNick(!0)+" has left "+d,"action part")},this)}}),kiwi.model.Application=Backbone.Model.extend(new function(){var a=this,b={};this.panels=null,this.initialize=function(){a=this},this.start=function(){kiwi.gateway=new kiwi.model.Gateway,this.bindGatewayCommands(kiwi.gateway),this.initializeClient(),this.view.barsHide(!0),this.panels.server.server_login.bind("server_connect",function(a){b=a,kiwi.gateway.set("nick",a.nick),kiwi.gateway.connect(a.server,6667,!1,!1,function(){})})},this.initializeClient=function(){this.view=new kiwi.view.Application({model:this,el:this.get("container")}),this.panels=new kiwi.model.PanelList,this.controlbox=new kiwi.view.ControlBox({el:$("#controlbox")[0]}),this.bindControllboxCommands(this.controlbox),this.topicbar=new kiwi.view.TopicBar({el:$("#topic")[0]}),this.panels.server.view.show(),this.view.doLayout(),this.panels.server.server_login.populateFields({nick:getQueryVariable("nick")||"kiwi_"+Math.ceil(Math.random()*1e4).toString(),server:getQueryVariable("server")||"irc.kiwiirc.com",channel:window.location.hash||"#test"})},this.bindGatewayCommands=function(c){c.on("onmotd",function(b){a.panels.server.addMsg(b.server,b.msg,"motd")}),c.on("onconnect",function(c){a.view.barsShow(),b.channel&&kiwi.gateway.join(b.channel)}),c.on("onjoin",function(b){var c,d,e;c=a.panels.getByName(b.channel),c||(c=new kiwi.model.Channel({name:b.channel}),a.panels.add(c)),d=c.get("members");if(!d)return;e=new kiwi.model.Member({nick:b.nick,ident:b.ident,hostname:b.hostname}),d.add(e)}),c.on("onpart",function(b){var c,d,e,f={};f.type="part",f.message=b.message||"",c=a.panels.getByName(b.channel);if(!c)return;if(b.nick===kiwi.gateway.get("nick")){c.close();return}d=c.get("members");if(!d)return;e=d.getByNick(b.nick);if(!e)return;d.remove(e,f)}),c.on("onquit",function(b){var c,d,e={};e.type="quit",e.message=b.message||"",$.each(a.panels.models,function(a,d){if(!d.isChannel())return;c=d.get("members").getByNick(b.nick),c&&d.get("members").remove(c,e)})}),c.on("onmsg",function(b){var c,d=b.channel==kiwi.gateway.get("nick");d?(c=a.panels.getByName(b.nick),c||(c=new kiwi.model.Channel({name:b.nick}),a.panels.add(c))):(c=a.panels.getByName(b.channel),c||(c=a.panels.server)),c.addMsg(b.nick,b.msg)}),c.on("onnotice",function(b){var c;c=a.panels.getByName(b.target)||a.panels.getByName(b.nick),c||(c=a.panels.server),c.addMsg("["+(b.nick||"")+"]",b.msg)}),c.on("onaction",function(b){var c,d=b.channel==kiwi.gateway.get("nick");d?(c=a.panels.getByName(b.nick),c||(c=new kiwi.model.Channel({name:b.nick}),a.panels.add(c))):(c=a.panels.getByName(b.channel),c||(c=a.panels.server)),c.addMsg("","* "+b.nick+" "+b.msg,"action")}),c.on("ontopic",function(b){var c;c=a.panels.getByName(b.channel);if(!c)return;c.set("topic",b.topic),c.get("name")===kiwi.app.panels.active.get("name")&&a.topicbar.setCurrentTopic(b.topic)}),c.on("ontopicsetby",function(b){var c,d;c=a.panels.getByName(b.channel);if(!c)return;d=(new Date(b.when*1e3)).toLocaleString(),c.addMsg("","Topic set by "+b.nick+" at "+d,"topic")}),c.on("onuserlist",function(b){var c;c=a.panels.getByName(b.channel);if(!c)return;c.temp_userlist=c.temp_userlist||[],_.each(b.users,function(a){var b=new kiwi.model.Member({nick:a.nick,modes:a.modes});c.temp_userlist.push(b)})}),c.on("onuserlist_end",function(b){var c;c=a.panels.getByName(b.channel);if(!c)return;c.get("members").reset(c.temp_userlist||[]),delete c.temp_userlist}),c.on("onmode",function(b){var c,d,e;if(!b.channel)return;c=a.panels.getByName(b.channel);if(!c)return;d=c.get("members");if(!d)return;e=d.getByNick(b.effected_nick);if(!e)return;b.mode[0]==="+"?e.addMode(b.mode.substr(1)):b.mode[0]==="-"&&e.removeMode(b.mode.substr(1))}),c.on("onnick",function(b){var c;$.each(a.panels.models,function(a,d){if(!d.isChannel())return;c=d.get("members").getByNick(b.nick),c&&(c.set("nick",b.newnick),d.addMsg("","== "+b.nick+" is now known as "+b.newnick,"action nick"))})})},this.bindControllboxCommands=function(a){a.on("unknown_command",this.unknownCommand),a.on("command",this.allCommands),a.on("command_msg",this.msgCommand),a.on("command_action",this.actionCommand),a.on("command_me",this.actionCommand),a.on("command_join",this.joinCommand),a.on("command_j",this.joinCommand),a.on("command_part",this.partCommand),a.on("command_p",this.partCommand),a.on("command_nick",function(a){kiwi.gateway.changeNick(a.params[0])}),a.on("command_query",this.queryCommand),a.on("command_q",this.queryCommand),a.on("command_topic",this.topicCommand),a.on("command_notice",this.noticeCommand),a.on("command_css",function(a){var b="?reload="+(new Date).getTime();$('link[rel="stylesheet"]').each(function(){this.href=this.href.replace(/\?.*|$/,b)})})},this.unknownCommand=function(a){var b=a.command+" "+a.params.join(" ");console.log("RAW: "+b),kiwi.gateway.raw(b)},this.allCommands=function(a){console.log("allCommands",a)},this.joinCommand=function(b){var c,d;d=b.params.join(" ").split(","),$.each(d,function(b,d){d=d.trim(),c=a.panels.getByName(d),c||(c=new kiwi.model.Channel({name:d}),kiwi.app.panels.add(c)),kiwi.gateway.join(d)}),c&&c.view.show()},this.queryCommand=function(b){var c,d;c=b.params[0],d=a.panels.getByName(c),d||(d=new kiwi.model.Channel({name:c}),kiwi.app.panels.add(d)),d&&d.view.show()},this.msgCommand=function(b){var c=b.params[0],d=a.panels.getByName(c)||a.panels.server;b.params.shift(),d.addMsg(kiwi.gateway.get("nick"),b.params.join(" ")),kiwi.gateway.privmsg(c,b.params.join(" "))},this.actionCommand=function(a){if(kiwi.app.panels.active===kiwi.app.panels.server)return;var b=kiwi.app.panels.active;b.addMsg("","* "+kiwi.gateway.get("nick")+" "+a.params.join(" "),"action"),kiwi.gateway.action(b.get("name"),a.params.join(" "))},this.partCommand=function(a){a.params.length===0?kiwi.gateway.part(kiwi.app.panels.active.get("name")):_.each(a.params,function(a){kiwi.gateway.part(a)})},this.topicCommand=function(b){var c;if(b.params.length===0)return;a.isChannelName(b.params[0])?(c=b.params[0],b.params.shift()):c=kiwi.app.panels.active.get("name"),kiwi.gateway.topic(c,b.params.join(" "))},this.noticeCommand=function(a){var b;if(a.params.length<=1)return;b=a.params[0],a.params.shift(),kiwi.gateway.notice(b,a.params.join(" "))},this.isChannelName=function(a){var b=kiwi.gateway.get("channel_prefix");return!a||!a.length?!1:b.indexOf(a[0])>-1}}),kiwi.model.Gateway=Backbone.Model.extend(new function(){var a=this;this.defaults={name:"Server",address:"",nick:"",channel_prefix:"#",user_prefixes:["~","&","@","+"],kiwi_server:"http://localhost:7778/kiwi"},this.initialize=function(){a=this,this.socket=this.get("socket"),this.session_id="",network=this},this.connect=function(b,c,d,e,f){this.socket=io.connect(this.get("kiwi_server"),{"try multiple transports":!0,"connect timeout":3e3,"max reconnection attempts":7,"reconnection delay":2e3}),this.socket.on("connect_failed",function(a){console.debug("Unable to connect Socket.IO",a),console.log("kiwi.gateway.socket.on('connect_failed')"),this.socket.disconnect(),this.emit("connect_fail",{reason:a})}),this.socket.on("error",function(a){this.emit("connect_fail",{reason:a}),console.log("kiwi.gateway.socket.on('error')",{reason:a})}),this.socket.on("connecting",function(b){console.log("kiwi.gateway.socket.on('connecting')"),this.emit("connecting"),a.trigger("connecting")}),this.socket.on("connect",function(){this.emit("irc connect",a.get("nick"),b,c,d,e,f),console.log("kiwi.gateway.socket.on('connect')")}),this.socket.on("too_many_connections",function(){this.emit("connect_fail",{reason:"too_many_connections"})}),this.socket.on("message",this.parse),this.socket.on("disconnect",function(){this.emit("disconnect",{}),console.log("kiwi.gateway.socket.on('disconnect')")}),this.socket.on("close",function(){console.log("kiwi.gateway.socket.on('close')")}),this.socket.on("reconnecting",function(a,b){console.log("kiwi.gateway.socket.on('reconnecting')"),this.emit("reconnecting",{delay:a,attempts:b})}),this.socket.on("reconnect_failed",function(){console.log("kiwi.gateway.socket.on('reconnect_failed')")})},this.parse=function(b){console.log("gateway event",b);if(b.event!==undefined){a.trigger("on"+b.event,b);switch(b.event){case"options":$.each(b.options,function(b,c){switch(b){case"CHANTYPES":a.set("channel_prefix",c.charAt(0));break;case"NETWORK":a.set("name",c);break;case"PREFIX":a.set("user_prefixes",c)}});break;case"connect":a.set("nick",b.nick);break;case"nick":b.nick===a.get("nick")&&a.set("nick",b.newnick);break;case"kiwi":this.emit("kiwi."+b.namespace,b.data)}}},this.sendData=function(a,b){this.socket.emit("message",{sid:this.session_id,data:JSON.stringify(a)},b)},this.privmsg=function(a,b,c){var d={method:"privmsg",args:{target:a,msg:b}};this.sendData(d,c)},this.notice=function(a,b,c){var d={method:"notice",args:{target:a,msg:b}};this.sendData(d,c)},this.ctcp=function(a,b,c,d,e){var f={method:"ctcp",args:{request:a,type:b,target:c,params:d}};this.sendData(f,e)},this.action=function(a,b,c){this.ctcp(!0,"ACTION",a,b,c)},this.join=function(a,b,c){var d={method:"join",args:{channel:a,key:b}};this.sendData(d,c)},this.part=function(a,b){var c={method:"part",args:{channel:a}};this.sendData(c,b)},this.topic=function(a,b,c){var d={method:"topic",args:{channel:a,topic:b}};this.sendData(d,c)},this.kick=function(a,b,c,d){var e={method:"kick",args:{channel:a,nick:b,reason:c}};this.sendData(e,d)},this.quit=function(a,b){a=a||"";var c={method:"quit",args:{message:a}};this.sendData(c,b)},this.raw=function(a,b){a={method:"raw",args:{data:a}},this.sendData(a,b)},this.changeNick=function(a,b){var c={method:"nick",args:{nick:a}};this.sendData(c,b)},this.kiwi=function(a,b,c){b={method:"kiwi",args:{target:a,data:b}},this.sendData(b,c)}}),kiwi.view={},kiwi.view.MemberList=Backbone.View.extend({tagName:"ul",events:{"click .nick":"nickClick"},initialize:function(a){this.model.bind("all",this.render,this),$(this.el).appendTo("#memberlists")},render:function(){var a=$(this.el);a.empty(),this.model.forEach(function(b){$('
  • '+b.get("prefix")+""+b.get("nick")+"
  • ").appendTo(a).data("member",b)})},nickClick:function(a){var b=$(a.currentTarget).parent("li"),c=b.data("member"),d=new kiwi.view.UserBox;d.member=c,$(".userbox",this.$el).remove(),b.append(d.$el)},show:function(){$("#memberlists").children().removeClass("active"),$(this.el).addClass("active")}}),kiwi.view.UserBox=Backbone.View.extend({member:{},events:{"click .query":"queryClick","click .info":"infoClick"},initialize:function(){this.$el=$($("#tmpl_userbox").html())},queryClick:function(a){var b=new kiwi.model.Channel({name:this.member.get("nick")});kiwi.app.panels.add(b),b.view.show()},infoClick:function(a){kiwi.gateway.raw("WHOIS "+this.member.get("nick"))}}),kiwi.view.ServerSelect=Backbone.View.extend({events:{"submit form":"submitLogin","click .show_more":"showMore"},initialize:function(){this.$el=$($("#tmpl_server_select").html()),kiwi.gateway.bind("onconnect",this.networkConnected,this),kiwi.gateway.bind("connecting",this.networkConnecting,this)},submitLogin:function(a){var b={nick:$(".nick",this.$el).val(),server:$(".server",this.$el).val(),channel:$(".channel",this.$el).val()};return this.trigger("server_connect",b),!1},showMore:function(a){$(".more",this.$el).slideDown("fast")},populateFields:function(a){var b,c,d;a=a||{},b=a.nick||"",c=a.server||"",d=a.channel||"",$(".nick",this.$el).val(b),$(".server",this.$el).val(c),$(".channel",this.$el).val(d)},hide:function(){this.$el.slideUp()},show:function(){this.$el.show(),$(".nick",this.$el).focus()},setStatus:function(a,b){$(".status",this.$el).text(a).attr("class","status").addClass(b).show()},clearStatus:function(){$(".status",this.$el).hide()},networkConnected:function(a){this.setStatus("Connected :)","ok"),$("form",this.$el).hide()},networkConnecting:function(a){this.setStatus("Connecting..","ok")}}),kiwi.view.Panel=Backbone.View.extend({tagName:"div",className:"messages",events:{"click .chan":"chanClick"},$container:null,initialize:function(a){this.initializePanel(a)},initializePanel:function(a){this.$el.css("display","none"),a.container?this.$container=$(a.container):this.$container=$("#panels .container1"),this.$el.appendTo(this.$container),this.model.bind("msg",this.newMsg,this),this.msg_count=0,this.model.set({view:this},{silent:!0})},render:function(){this.$el.empty(),this.model.get("backscroll").forEach(this.newMsg)},newMsg:function(a){var b,c,d=this.$el;b=new RegExp("\\B("+kiwi.gateway.channel_prefix+"[^ ,.\\007]+)","g"),a.msg=a.msg.replace(b,function(a){return''+a+""}),c='
    <%- time %>
    <%- nick %>
    <%= msg %>
    ',d.append(_.template(c,a)),this.scrollToBottom(),this.msg_count++,this.msg_count>250&&($(".msg:first",this.div).remove(),this.msg_count--)},chanClick:function(a){console.log(a)},show:function(){var a=this.$el;this.$container.children().css("display","none"),a.css("display","block");var b=this.model.get("members");b?(b.view.show(),this.$container.parent().css("right","200px")):($("#memberlists").children().removeClass("active"),this.$container.parent().css("right","0")),this.scrollToBottom(),this.trigger("active",this.model),kiwi.app.panels.trigger("active",this.model)},scrollToBottom:function(){this.$container[0].scrollTop=this.$container[0].scrollHeight}}),kiwi.view.Channel=kiwi.view.Panel.extend({initialize:function(a){this.initializePanel(a),this.model.bind("change:topic",this.topic,this)},topic:function(a){if(typeof a!="string"||!a)a=this.model.get("topic");this.model.addMsg("","=== Topic for "+this.model.get("name")+" is: "+a,"topic"),kiwi.app.panels.active===this&&kiwi.app.topicbar.setCurrentTopic(this.model.get("topic"))}}),kiwi.view.Tabs=Backbone.View.extend({events:{"click li":"tabClick","click li img":"partClick"},initialize:function(){this.model.on("add",this.panelAdded,this),this.model.on("remove",this.panelRemoved,this),this.model.on("reset",this.render,this)},render:function(){var a=this;$this=$(this.el),$this.empty(),$("
  • "+kiwi.gateway.get("name")+"
  • ").data("panel_id",this.model.server.cid).appendTo($this),this.model.forEach(function(b){if(b==a.model.server)return;$("
  • "+b.get("name")+"
  • ").data("panel_id",b.cid).appendTo($this)})},panelAdded:function(a){a.tab=$("
  • "+a.get("name")+"
  • "),a.tab.data("panel_id",a.cid).appendTo(this.$el),a.view.on("active",this.panelActive,this)},panelRemoved:function(a){a.tab.remove(),delete a.tab},panelActive:function(a){$("img",this.$el).remove(),this.$el.children().removeClass("active"),a.tab.addClass("active"),a.tab.append('')},tabClick:function(a){var b=this.model.getByCid($(a.currentTarget).data("panel_id"));if(!b)return;b.view.show()},partClick:function(a){var b=this.model.getByCid($(a.currentTarget).parent().data("panel_id"));b.isChannel()?kiwi.gateway.part(b.get("name")):b.close()}}),kiwi.view.TopicBar=Backbone.View.extend({events:{"keydown input":"process"},initialize:function(){kiwi.app.panels.bind("active",function(a){this.setCurrentTopic(a.get("topic"))},this)},process:function(a){var b=$(a.currentTarget),c=b.val();if(a.keyCode!==13)return;kiwi.app.panels.active.isChannel()&&kiwi.gateway.topic(kiwi.app.panels.active.get("name"),c)},setCurrentTopic:function(a){$("input",this.$el).val(a)}}),kiwi.view.ControlBox=Backbone.View.extend({buffer:[],buffer_pos:0,events:{"keydown input":"process"},initialize:function(){var a=this;that=this,kiwi.gateway.bind("change:nick",function(){$(".nick",a.$el).text(this.get("nick"))})},process:function(a){var b=$(a.currentTarget),c=b.val();switch(!0){case a.keyCode===13:c=c.trim(),c&&(this.processInput(b.val()),this.buffer.push(b.val()),this.buffer_pos=this.buffer.length),b.val("");break;case a.keyCode===38:this.buffer_pos>0&&(this.buffer_pos--,b.val(this.buffer[this.buffer_pos]));break;case a.keyCode===40:this.buffer_pos";while(a.indexOf(String.fromCharCode(2))!==-1)a=a.replace(String.fromCharCode(2),c),c=c===""?"":"";c===""&&(a+="")}if(a.indexOf(String.fromCharCode(31))!==-1){c="";while(a.indexOf(String.fromCharCode(31))!==-1)a=a.replace(String.fromCharCode(31),c),c=c===""?"":"";c===""&&(a+="")}return a=function(a){var b,c,d,e,f,g,h,i,j,k;b="",c=function(a){var b=/^\x03([0-9][0-5]?)(,([0-9][0-5]?))?/;return b.exec(a)},d=function(a){switch(parseInt(a,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}};if(a.indexOf("")!==-1){e=a.indexOf(""),b=a.substr(0,e);while(e'+k+"",e=g):(a[e]!==""&&a[e]!==String.fromCharCode(15)&&(b+=a[e]),e++);return b}return a}(a),a}var b=a.kiwi={};b.model={},b.view={},b.model.Application=Backbone.Model.extend(new function(){var d=this,e={};this.panels=null,this.initialize=function(){d=this},this.start=function(){getQueryVariable("debug")?c(!0):c(!1),b.gateway=new b.model.Gateway,this.bindGatewayCommands(b.gateway),this.initializeClient(),this.view.barsHide(!0),this.panels.server.server_login.bind("server_connect",function(a){e=a,b.gateway.set("nick",a.nick),b.gateway.connect(a.server,6667,!1,!1,function(){})})},this.initializeClient=function(){this.view=new b.view.Application({model:this,el:this.get("container")}),this.panels=new b.model.PanelList,this.controlbox=new b.view.ControlBox({el:$("#controlbox")[0]}),this.bindControllboxCommands(this.controlbox),this.topicbar=new b.view.TopicBar({el:$("#topic")[0]}),this.panels.server.view.show(),this.view.doLayout(),this.panels.server.server_login.populateFields({nick:getQueryVariable("nick")||"kiwi_"+Math.ceil(Math.random()*1e4).toString(),server:getQueryVariable("server")||"irc.kiwiirc.com",channel:a.location.hash||"#test"})},this.bindGatewayCommands=function(a){a.on("onmotd",function(a){d.panels.server.addMsg(a.server,a.msg,"motd")}),a.on("onconnect",function(a){d.view.barsShow(),e.channel&&b.gateway.join(e.channel)}),a.on("onjoin",function(a){var c,e,f;c=d.panels.getByName(a.channel),c||(c=new b.model.Channel({name:a.channel}),d.panels.add(c)),e=c.get("members");if(!e)return;f=new b.model.Member({nick:a.nick,ident:a.ident,hostname:a.hostname}),e.add(f)}),a.on("onpart",function(a){var c,e,f,g={};g.type="part",g.message=a.message||"",c=d.panels.getByName(a.channel);if(!c)return;if(a.nick===b.gateway.get("nick")){c.close();return}e=c.get("members");if(!e)return;f=e.getByNick(a.nick);if(!f)return;e.remove(f,g)}),a.on("onquit",function(a){var b,c,e={};e.type="quit",e.message=a.message||"",$.each(d.panels.models,function(c,d){if(!d.isChannel())return;b=d.get("members").getByNick(a.nick),b&&d.get("members").remove(b,e)})}),a.on("onmsg",function(a){var c,e=a.channel==b.gateway.get("nick");e?(c=d.panels.getByName(a.nick),c||(c=new b.model.Channel({name:a.nick}),d.panels.add(c))):(c=d.panels.getByName(a.channel),c||(c=d.panels.server)),c.addMsg(a.nick,a.msg)}),a.on("onnotice",function(a){var b;b=d.panels.getByName(a.target)||d.panels.getByName(a.nick),b||(b=d.panels.server),b.addMsg("["+(a.nick||"")+"]",a.msg)}),a.on("onaction",function(a){var c,e=a.channel==b.gateway.get("nick");e?(c=d.panels.getByName(a.nick),c||(c=new b.model.Channel({name:a.nick}),d.panels.add(c))):(c=d.panels.getByName(a.channel),c||(c=d.panels.server)),c.addMsg("","* "+a.nick+" "+a.msg,"action")}),a.on("ontopic",function(a){var c;c=d.panels.getByName(a.channel);if(!c)return;c.set("topic",a.topic),c.get("name")===b.app.panels.active.get("name")&&d.topicbar.setCurrentTopic(a.topic)}),a.on("ontopicsetby",function(a){var b,c;b=d.panels.getByName(a.channel);if(!b)return;c=(new Date(a.when*1e3)).toLocaleString(),b.addMsg("","Topic set by "+a.nick+" at "+c,"topic")}),a.on("onuserlist",function(a){var c;c=d.panels.getByName(a.channel);if(!c)return;c.temp_userlist=c.temp_userlist||[],_.each(a.users,function(a){var d=new b.model.Member({nick:a.nick,modes:a.modes});c.temp_userlist.push(d)})}),a.on("onuserlist_end",function(a){var b;b=d.panels.getByName(a.channel);if(!b)return;b.get("members").reset(b.temp_userlist||[]),delete b.temp_userlist}),a.on("onmode",function(a){var b,c,e;if(!a.channel)return;b=d.panels.getByName(a.channel);if(!b)return;c=b.get("members");if(!c)return;e=c.getByNick(a.effected_nick);if(!e)return;a.mode[0]==="+"?e.addMode(a.mode.substr(1)):a.mode[0]==="-"&&e.removeMode(a.mode.substr(1))}),a.on("onnick",function(a){var b;$.each(d.panels.models,function(c,d){if(!d.isChannel())return;b=d.get("members").getByNick(a.nick),b&&(b.set("nick",a.newnick),d.addMsg("","== "+a.nick+" is now known as "+a.newnick,"action nick"))})})},this.bindControllboxCommands=function(a){a.on("unknown_command",this.unknownCommand),a.on("command",this.allCommands),a.on("command_msg",this.msgCommand),a.on("command_action",this.actionCommand),a.on("command_me",this.actionCommand),a.on("command_join",this.joinCommand),a.on("command_j",this.joinCommand),a.on("command_part",this.partCommand),a.on("command_p",this.partCommand),a.on("command_nick",function(a){b.gateway.changeNick(a.params[0])}),a.on("command_query",this.queryCommand),a.on("command_q",this.queryCommand),a.on("command_topic",this.topicCommand),a.on("command_notice",this.noticeCommand),a.on("command_css",function(a){var b="?reload="+(new Date).getTime();$('link[rel="stylesheet"]').each(function(){this.href=this.href.replace(/\?.*|$/,b)})})},this.unknownCommand=function(a){var c=a.command+" "+a.params.join(" ");console.log("RAW: "+c),b.gateway.raw(c)},this.allCommands=function(a){console.log("allCommands",a)},this.joinCommand=function(a){var c,e;e=a.params.join(" ").split(","),$.each(e,function(a,e){e=e.trim(),c=d.panels.getByName(e),c||(c=new b.model.Channel({name:e}),b.app.panels.add(c)),b.gateway.join(e)}),c&&c.view.show()},this.queryCommand=function(a){var c,e;c=a.params[0],e=d.panels.getByName(c),e||(e=new b.model.Channel({name:c}),b.app.panels.add(e)),e&&e.view.show()},this.msgCommand=function(a){var c=a.params[0],e=d.panels.getByName(c)||d.panels.server;a.params.shift(),e.addMsg(b.gateway.get("nick"),a.params.join(" ")),b.gateway.privmsg(c,a.params.join(" "))},this.actionCommand=function(a){if(b.app.panels.active===b.app.panels.server)return;var c=b.app.panels.active;c.addMsg("","* "+b.gateway.get("nick")+" "+a.params.join(" "),"action"),b.gateway.action(c.get("name"),a.params.join(" "))},this.partCommand=function(a){a.params.length===0?b.gateway.part(b.app.panels.active.get("name")):_.each(a.params,function(a){b.gateway.part(a)})},this.topicCommand=function(a){var c;if(a.params.length===0)return;d.isChannelName(a.params[0])?(c=a.params[0],a.params.shift()):c=b.app.panels.active.get("name"),b.gateway.topic(c,a.params.join(" "))},this.noticeCommand=function(a){var c;if(a.params.length<=1)return;c=a.params[0],a.params.shift(),b.gateway.notice(c,a.params.join(" "))},this.isChannelName=function(a){var c=b.gateway.get("channel_prefix");return!a||!a.length?!1:c.indexOf(a[0])>-1}}),b.model.Gateway=Backbone.Model.extend(new function(){var a=this;this.defaults={name:"Server",address:"",nick:"",channel_prefix:"#",user_prefixes:["~","&","@","+"],kiwi_server:"http://localhost:7778/kiwi"},this.initialize=function(){a=this,this.socket=this.get("socket"),this.session_id="",network=this},this.connect=function(b,c,d,e,f){this.socket=io.connect(this.get("kiwi_server"),{"try multiple transports":!0,"connect timeout":3e3,"max reconnection attempts":7,"reconnection delay":2e3}),this.socket.on("connect_failed",function(a){console.debug("Unable to connect Socket.IO",a),console.log("kiwi.gateway.socket.on('connect_failed')"),this.socket.disconnect(),this.emit("connect_fail",{reason:a})}),this.socket.on("error",function(a){this.emit("connect_fail",{reason:a}),console.log("kiwi.gateway.socket.on('error')",{reason:a})}),this.socket.on("connecting",function(b){console.log("kiwi.gateway.socket.on('connecting')"),this.emit("connecting"),a.trigger("connecting")}),this.socket.on("connect",function(){this.emit("irc connect",a.get("nick"),b,c,d,e,f),console.log("kiwi.gateway.socket.on('connect')")}),this.socket.on("too_many_connections",function(){this.emit("connect_fail",{reason:"too_many_connections"})}),this.socket.on("message",this.parse),this.socket.on("disconnect",function(){this.emit("disconnect",{}),console.log("kiwi.gateway.socket.on('disconnect')")}),this.socket.on("close",function(){console.log("kiwi.gateway.socket.on('close')")}),this.socket.on("reconnecting",function(a,b){console.log("kiwi.gateway.socket.on('reconnecting')"),this.emit("reconnecting",{delay:a,attempts:b})}),this.socket.on("reconnect_failed",function(){console.log("kiwi.gateway.socket.on('reconnect_failed')")})},this.parse=function(b){console.log("gateway event",b);if(b.event!==undefined){a.trigger("on"+b.event,b);switch(b.event){case"options":$.each(b.options,function(b,c){switch(b){case"CHANTYPES":a.set("channel_prefix",c.charAt(0));break;case"NETWORK":a.set("name",c);break;case"PREFIX":a.set("user_prefixes",c)}});break;case"connect":a.set("nick",b.nick);break;case"nick":b.nick===a.get("nick")&&a.set("nick",b.newnick);break;case"kiwi":this.emit("kiwi."+b.namespace,b.data)}}},this.sendData=function(a,b){this.socket.emit("message",{sid:this.session_id,data:JSON.stringify(a)},b)},this.privmsg=function(a,b,c){var d={method:"privmsg",args:{target:a,msg:b}};this.sendData(d,c)},this.notice=function(a,b,c){var d={method:"notice",args:{target:a,msg:b}};this.sendData(d,c)},this.ctcp=function(a,b,c,d,e){var f={method:"ctcp",args:{request:a,type:b,target:c,params:d}};this.sendData(f,e)},this.action=function(a,b,c){this.ctcp(!0,"ACTION",a,b,c)},this.join=function(a,b,c){var d={method:"join",args:{channel:a,key:b}};this.sendData(d,c)},this.part=function(a,b){var c={method:"part",args:{channel:a}};this.sendData(c,b)},this.topic=function(a,b,c){var d={method:"topic",args:{channel:a,topic:b}};this.sendData(d,c)},this.kick=function(a,b,c,d){var e={method:"kick",args:{channel:a,nick:b,reason:c}};this.sendData(e,d)},this.quit=function(a,b){a=a||"";var c={method:"quit",args:{message:a}};this.sendData(c,b)},this.raw=function(a,b){a={method:"raw",args:{data:a}},this.sendData(a,b)},this.changeNick=function(a,b){var c={method:"nick",args:{nick:a}};this.sendData(c,b)},this.kiwi=function(a,b,c){b={method:"kiwi",args:{target:a,data:b}},this.sendData(b,c)}}),b.model.Member=Backbone.Model.extend({sortModes:function(a){return a.sort(function(a,c){var d,e,f,g=b.gateway.get("user_prefixes");for(f=0;fe?1:0})},initialize:function(a){var b,c,d;b=this.stripPrefix(this.get("nick")),c=this.get("modes"),c=c||[],this.sortModes(c),this.set({nick:b,modes:c,prefix:this.getPrefix(c)},{silent:!0})},addMode:function(a){var b=a.split(""),c,d;c=this.get("modes"),$.each(b,function(a,b){c.push(b)}),c=this.sortModes(c),this.set({prefix:this.getPrefix(c),modes:c})},removeMode:function(a){var b=a.split(""),c,d;c=this.get("modes"),c=_.reject(c,function(a){return b.indexOf(a)!==-1}),this.set({prefix:this.getPrefix(c),modes:c})},getPrefix:function(a){var c="",d=b.gateway.get("user_prefixes");return typeof a[0]!="undefined"&&(c=_.detect(d,function(b){return b.mode===a[0]}),c=c?c.symbol:""),c},stripPrefix:function(a){var c=a,d,e,f,g=b.gateway.get("user_prefixes");d=0;for(e=0;e0){if(f.length===0)return-1;g=h=-1;for(d=0;dh)return 1}else if(f.length>0)return 1;return i=a.get("nick").toLocaleUpperCase(),j=c.get("nick").toLocaleUpperCase(),ij?1:(console.log("Something's gone wrong somewhere - two users have the same nick!"),0)},initialize:function(a){this.view=new b.view.MemberList({model:this})},getByNick:function(a){if(typeof a!="string")return;return this.find(function(b){return a.toLowerCase()===b.get("nick").toLowerCase()})}}),b.model.Panel=Backbone.Model.extend({initialize:function(a){var c=this.get("name")||"";this.view=new b.view.Panel({model:this,name:c}),this.set({scrollback:[],name:c},{silent:!0})},addMsg:function(a,b,c,d){var e,g,h;d=d||{};if(!d||typeof d.time=="undefined")h=new Date,d.time=h.getHours().toString().lpad(2,"0")+":"+h.getMinutes().toString().lpad(2,"0")+":"+h.getSeconds().toString().lpad(2,"0");if(!d||typeof d.style=="undefined")d.style="";b=$("
    ").text(b).html(),e={msg:b,time:d.time,nick:a,chan:this.get("name"),type:c,style:d.style};if(!e)return;typeof e.type!="string"&&(e.type=""),typeof e.msg!="string"&&(e.msg=""),e.msg=f(e.msg),g=this.get("scrollback"),g.push(e),g.length>250&&g.splice(250),this.set({scrollback:g},{silent:!0}),this.trigger("msg",e)},close:function(){this.view.remove(),delete this.view;var a=this.get("members");a&&(a.reset([]),this.unset("members")),this.destroy(),this.cid===b.app.panels.active.cid&&b.app.panels.server.view.show()},isChannel:function(){var a=b.gateway.get("channel_prefix"),c=this.get("name");return c?a.indexOf(c[0])>-1:!1}}),b.model.PanelList=Backbone.Collection.extend({model:b.model.Panel,active:null,comparator:function(a){return a.get("name")},initialize:function(){this.view=new b.view.Tabs({el:$("#toolbar .panellist")[0],model:this}),this.server=new b.model.Server({name:b.gateway.get("name")}),b.gateway.on("change:name",this.view.render,this.view),this.add(this.server),this.bind("active",function(a){this.active=a},this)},getByName:function(a){if(typeof a!="string")return;return this.find(function(b){return a.toLowerCase()===b.get("name").toLowerCase()})}}),b.model.Channel=b.model.Panel.extend({initialize:function(a){var c=this.get("name")||"",d;this.view=new b.view.Channel({model:this,name:c}),this.set({members:new b.model.MemberList,name:c,scrollback:[],topic:""},{silent:!0}),d=this.get("members"),d.bind("add",function(a){this.addMsg(" ","--> "+a.displayNick(!0)+" has joined","action join")},this),d.bind("remove",function(a,b,c){var d=c.message?"("+c.message+")":"";c.type==="quit"?this.addMsg(" ","<-- "+a.displayNick(!0)+" has quit "+d,"action quit"):this.addMsg(" ","<-- "+a.displayNick(!0)+" has left "+d,"action part")},this)}}),b.model.Server=b.model.Panel.extend({server_login:null,initialize:function(a){var c="Server";this.view=new b.view.Panel({model:this,name:c}),this.set({scrollback:[],name:c},{silent:!0}),this.server_login=new b.view.ServerSelect,this.view.$el.append(this.server_login.$el),this.server_login.show()}}),typeof String.prototype.trim=="undefined"&&(String.prototype.trim=function(){return this.replace(/^\s+|\s+$/g,"")}),typeof String.prototype.lpad=="undefined"&&(String.prototype.lpad=function(a,b){var c="",d;for(d=0;d';return''+c+'
    '}),a):a}},{name:"html_safe",onaddmsg:function(a,b){return a.msg=$("
    ").text(a.msg).html(),a.nick=$("
    ").text(a.nick).html(),a}},{name:"activity",onaddmsg:function(a,b){return a}},{name:"highlight",onaddmsg:function(a,b){return a}},{name:"linkify_plain",onaddmsg:function(a,b){return a.msg?(a.msg=a.msg.replace(/((https?\:\/\/|ftp\:\/\/)|(www\.))(\S+)(\w{2,4})(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/gi,function(a){var b;return a.match(/(\.jpg|\.jpeg|\.gif|\.bmp|\.png)$/)?a:(b=a,a.match("^https?://")?b=a:a="http://"+a,''+b+"")}),a):a}},{name:"lftobr",onaddmsg:function(a,b){return a.msg?(a.msg=a.msg.replace(/\n/gi,function(a){return"
    "}),a):a}},{name:"nick_colour",onaddmsg:function(a,b){if(!a.msg)return a;var c=this.randColour();return a.nick=''+a.nick+"",a},randColour:function(){var a=this.rand(-250,0),b=this.rand(30,100),c=this.rand(20,70);return"hsl("+a+","+b+"%,"+c+"%)"},rand:function(a,b){return parseInt(Math.random()*(b-a+1),10)+a}},{name:"kiwitest",oninit:function(a,b){console.log("registering namespace"),$(gateway).bind("kiwi.lol.browser",function(a,b){console.log("YAY kiwitest"),console.log(b)})}}];b.plugs={},b.plugs.loaded={},b.plugs.loadPlugin=function(a){var c;return typeof a.name!="string"?!1:(c=b.plugs.run("plugin_load",{plugin:a}),typeof c=="object"&&(b.plugs.loaded[c.plugin.name]=c.plugin,b.plugs.loaded[c.plugin.name].local_data=new b.dataStore("kiwi_plugin_"+c.plugin.name)),b.plugs.run("init",{},{run_only:c.plugin.name}),!0)},b.plugs.unloadPlugin=function(a){if(typeof b.plugs.loaded[a]!="object")return;b.plugs.run("unload",{},{run_only:a}),delete b.plugs.loaded[a]},b.plugs.run=function(a,c,d){var e=c,f,g;c=typeof c=="undefined"?{}:c,d=typeof d=="undefined"?{}:d;for(g in b.plugs.loaded){if(typeof d.run_only=="string"&&d.run_only!==g)continue;if(typeof b.plugs.loaded[g]["on"+a]=="function")try{f=b.plugs.loaded[g]["on"+a](e,d);if(f===null)return null;e=f;if(typeof e.event_bubbles=="boolean"&&e.event_bubbles===!1)return delete e.event_bubbles,e}catch(h){}}return e},b.dataStore=function(a){var b=a;this.get=function(b){return $.jStorage.get(a+"_"+b)},this.set=function(b,c){return $.jStorage.set(a+"_"+b,c)}},b.data=new b.dataStore("kiwi"),function(b){function j(){if("localStorage"in a)try{a.localStorage&&(d=a.localStorage,i="localStorage")}catch(b){}else if("globalStorage"in a)try{a.globalStorage&&(d=a.globalStorage[a.location.hostname],i="globalStorage")}catch(c){}else{e=document.createElement("link");if(!e.addBehavior){e=null;return}e.style.behavior="url(#default#userData)",document.getElementsByTagName("head")[0].appendChild(e),e.load("jStorage");var f="{}";try{f=e.getAttribute("jStorage")}catch(g){}d.jStorage=f,i="userDataBehavior"}k()}function k(){if(d.jStorage)try{c=h(String(d.jStorage))}catch(a){d.jStorage="{}"}else d.jStorage="{}";f=d.jStorage?String(d.jStorage).length:0}function l(){try{d.jStorage=g(c),e&&(e.setAttribute("jStorage",d.jStorage),e.save("jStorage")),f=d.jStorage?String(d.jStorage).length:0}catch(a){}}function m(a){if(!a||typeof a!="string"&&typeof a!="number")throw new TypeError("Key name must be string or numeric");return!0}if(!b||!(b.toJSON||Object.toJSON||a.JSON))throw new Error("jQuery, MooTools or Prototype needs to be loaded before jStorage!");var c={},d={jStorage:"{}"},e=null,f=0,g=b.toJSON||Object.toJSON||a.JSON&&(JSON.encode||JSON.stringify),h=b.evalJSON||a.JSON&&(JSON.decode||JSON.parse)||function(a){return String(a).evalJSON()},i=!1;_XMLService={isXML:function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1},encode:function(a){if(!this.isXML(a))return!1;try{return(new XMLSerializer).serializeToString(a)}catch(b){try{return a.xml}catch(c){}}return!1},decode:function(b){var c="DOMParser"in a&&(new DOMParser).parseFromString||a.ActiveXObject&&function(a){var b=new ActiveXObject("Microsoft.XMLDOM");return b.async="false",b.loadXML(a),b},d;return c?(d=c.call("DOMParser"in a&&new DOMParser||a,b,"text/xml"),this.isXML(d)?d:!1):!1}},b.jStorage={version:"0.1.5.1",set:function(a,b){return m(a),_XMLService.isXML(b)&&(b={_is_xml:!0,xml:_XMLService.encode(b)}),c[a]=b,l(),b},get:function(a,b){return m(a),a in c?c[a]&&typeof c[a]=="object"&&c[a]._is_xml&&c[a]._is_xml?_XMLService.decode(c[a].xml):c[a]:typeof b=="undefined"?null:b},deleteKey:function(a){return m(a),a in c?(delete c[a],l(),!0):!1},flush:function(){return c={},l(),!0},storageObj:function(){function a(){}return a.prototype=c,new a},index:function(){var a=[],b;for(b in c)c.hasOwnProperty(b)&&a.push(b);return a},storageSize:function(){return f},currentBackend:function(){return i},storageAvailable:function(){return!!i},reInit:function(){var a,b;if(e&&e.addBehavior){a=document.createElement("link"),e.parentNode.replaceChild(a,e),e=a,e.style.behavior="url(#default#userData)",document.getElementsByTagName("head")[0].appendChild(e),e.load("jStorage"),b="{}";try{b=e.getAttribute("jStorage")}catch(c){}d.jStorage=b,i="userDataBehavior"}k()}},j()}(a.jQuery||a.$),b.view={},b.view.MemberList=Backbone.View.extend({tagName:"ul",events:{"click .nick":"nickClick"},initialize:function(a){this.model.bind("all",this.render,this),$(this.el).appendTo("#memberlists")},render:function(){var a=$(this.el);a.empty(),this.model.forEach(function(b){$('
  • '+b.get("prefix")+""+b.get("nick")+"
  • ").appendTo(a).data("member",b)})},nickClick:function(a){var c=$(a.currentTarget).parent("li"),d=c.data("member"),e=new b.view.UserBox;e.member=d,$(".userbox",this.$el).remove(),c.append(e.$el)},show:function(){$("#memberlists").children().removeClass("active"),$(this.el).addClass("active")}}),b.view.UserBox=Backbone.View.extend({member:{},events:{"click .query":"queryClick","click .info":"infoClick"},initialize:function(){this.$el=$($("#tmpl_userbox").html())},queryClick:function(a){var c=new b.model.Channel({name:this.member.get("nick")});b.app.panels.add(c),c.view.show()},infoClick:function(a){b.gateway.raw("WHOIS "+this.member.get("nick"))}}),b.view.ServerSelect=Backbone.View.extend({events:{"submit form":"submitLogin","click .show_more":"showMore"},initialize:function(){this.$el=$($("#tmpl_server_select").html()),b.gateway.bind("onconnect",this.networkConnected,this),b.gateway.bind("connecting",this.networkConnecting,this)},submitLogin:function(a){var b={nick:$(".nick",this.$el).val(),server:$(".server",this.$el).val(),channel:$(".channel",this.$el).val()};return this.trigger("server_connect",b),!1},showMore:function(a){$(".more",this.$el).slideDown("fast")},populateFields:function(a){var b,c,d;a=a||{},b=a.nick||"",c=a.server||"",d=a.channel||"",$(".nick",this.$el).val(b),$(".server",this.$el).val(c),$(".channel",this.$el).val(d)},hide:function(){this.$el.slideUp()},show:function(){this.$el.show(),$(".nick",this.$el).focus()},setStatus:function(a,b){$(".status",this.$el).text(a).attr("class","status").addClass(b).show()},clearStatus:function(){$(".status",this.$el).hide()},networkConnected:function(a){this.setStatus("Connected :)","ok"),$("form",this.$el).hide()},networkConnecting:function(a){this.setStatus("Connecting..","ok")}}),b.view.Panel=Backbone.View.extend({tagName:"div",className:"messages",events:{"click .chan":"chanClick"},$container:null,initialize:function(a){this.initializePanel(a)},initializePanel:function(a){this.$el.css("display","none"),a.container?this.$container=$(a.container):this.$container=$("#panels .container1"),this.$el.appendTo(this.$container),this.model.bind("msg",this.newMsg,this),this.msg_count=0,this.model.set({view:this},{silent:!0})},render:function(){this.$el.empty(),this.model.get("backscroll").forEach(this.newMsg)},newMsg:function(a){var c,d,e=this.$el;c=new RegExp("\\B("+b.gateway.channel_prefix+"[^ ,.\\007]+)","g"),a.msg=a.msg.replace(c,function(a){return''+a+""}),d='
    <%- time %>
    <%- nick %>
    <%= msg %>
    ',e.append(_.template(d,a)),this.scrollToBottom(),this.msg_count++,this.msg_count>250&&($(".msg:first",this.div).remove(),this.msg_count--)},chanClick:function(a){console.log(a)},show:function(){var a=this.$el;this.$container.children().css("display","none"),a.css("display","block");var c=this.model.get("members");c?(c.view.show(),this.$container.parent().css("right","200px")):($("#memberlists").children().removeClass("active"),this.$container.parent().css("right","0")),this.scrollToBottom(),this.trigger("active",this.model),b.app.panels.trigger("active",this.model)},scrollToBottom:function(){this.$container[0].scrollTop=this.$container[0].scrollHeight}}),b.view.Channel=b.view.Panel.extend({initialize:function(a){this.initializePanel(a),this.model.bind("change:topic",this.topic,this)},topic:function(a){if(typeof a!="string"||!a)a=this.model.get("topic");this.model.addMsg("","=== Topic for "+this.model.get("name")+" is: "+a,"topic"),b.app.panels.active===this&&b.app.topicbar.setCurrentTopic(this.model.get("topic"))}}),b.view.Tabs=Backbone.View.extend({events:{"click li":"tabClick","click li img":"partClick"},initialize:function(){this.model.on("add",this.panelAdded,this),this.model.on("remove",this.panelRemoved,this),this.model.on("reset",this.render,this)},render:function(){var a=this;$this=$(this.el),$this.empty(),$("
  • "+b.gateway.get("name")+"
  • ").data("panel_id",this.model.server.cid).appendTo($this),this.model.forEach(function(b){if(b==a.model.server)return;$("
  • "+b.get("name")+"
  • ").data("panel_id",b.cid).appendTo($this)})},panelAdded:function(a){a.tab=$("
  • "+a.get("name")+"
  • "),a.tab.data("panel_id",a.cid).appendTo(this.$el),a.view.on("active",this.panelActive,this)},panelRemoved:function(a){a.tab.remove(),delete a.tab},panelActive:function(a){$("img",this.$el).remove(),this.$el.children().removeClass("active"),a.tab.addClass("active"),a.tab.append('')},tabClick:function(a){var b=this.model.getByCid($(a.currentTarget).data("panel_id"));if(!b)return;b.view.show()},partClick:function(a){var c=this.model.getByCid($(a.currentTarget).parent().data("panel_id"));c.isChannel()?b.gateway.part(c.get("name")):c.close()}}),b.view.TopicBar=Backbone.View.extend({events:{"keydown input":"process"},initialize:function(){b.app.panels.bind("active",function(a){this.setCurrentTopic(a.get("topic"))},this)},process:function(a){var c=$(a.currentTarget),d=c.val();if(a.keyCode!==13)return;b.app.panels.active.isChannel()&&b.gateway.topic(b.app.panels.active.get("name"),d)},setCurrentTopic:function(a){$("input",this.$el).val(a)}}),b.view.ControlBox=Backbone.View.extend({buffer:[],buffer_pos:0,events:{"keydown input":"process"},initialize:function(){var a=this;b.gateway.bind("change:nick",function(){$(".nick",a.$el).text(this.get("nick"))})},process:function(a){var b=$(a.currentTarget),c=b.val();switch(!0){case a.keyCode===13:c=c.trim(),c&&(this.processInput(b.val()),this.buffer.push(b.val()),this.buffer_pos=this.buffer.length),b.val("");break;case a.keyCode===38:this.buffer_pos>0&&(this.buffer_pos--,b.val(this.buffer[this.buffer_pos]));break;case a.keyCode===40:this.buffer_pos