Build script + minified kiwi.js
authorDarren <darren@darrenwhitlen.com>
Sun, 2 Sep 2012 13:55:36 +0000 (14:55 +0100)
committerDarren <darren@darrenwhitlen.com>
Sun, 2 Sep 2012 13:55:36 +0000 (14:55 +0100)
14 files changed:
client_backbone/dev/app.js [new file with mode: 0644]
client_backbone/dev/build.js
client_backbone/dev/model.js [deleted file]
client_backbone/dev/model_application.js
client_backbone/dev/model_channel.js [new file with mode: 0644]
client_backbone/dev/model_member.js [new file with mode: 0644]
client_backbone/dev/model_memberlist.js [new file with mode: 0644]
client_backbone/dev/model_panel.js [new file with mode: 0644]
client_backbone/dev/model_panellist.js [new file with mode: 0644]
client_backbone/dev/model_server.js [new file with mode: 0644]
client_backbone/dev/utils.js
client_backbone/index.html
client_backbone/kiwi.js
client_backbone/kiwi.min.js

diff --git a/client_backbone/dev/app.js b/client_backbone/dev/app.js
new file mode 100644 (file)
index 0000000..704a3e0
--- /dev/null
@@ -0,0 +1,8 @@
+// Holds anything kiwi client specific (ie. front, gateway, kiwi.plugs..)\r
+/**\r
+*   @namespace\r
+*/\r
+var kiwi = window.kiwi = {};\r
+\r
+kiwi.model = {};\r
+kiwi.view = {};
\ No newline at end of file
index 4f23d9aa000f52a97fb2c1e75a73b4a877bb2816..2749271c5e3d24f56769cbba0da4c83287216370 100644 (file)
@@ -5,32 +5,41 @@ var FILE_ENCODING = 'utf-8',
     EOL = '\n';\r
 \r
 \r
-function concat(opts) {\r
-    var file_list = opts.src;\r
-    var dist_path = opts.dest;\r
+function concat(src) {\r
+    var file_list = src;\r
     var out = file_list.map(function(file_path){\r
-        return fs.readFileSync(file_path, FILE_ENCODING);\r
+        return fs.readFileSync(file_path, FILE_ENCODING) + '\n\n';\r
     });\r
 \r
-    fs.writeFileSync(dist_path, out.join(EOL), FILE_ENCODING);\r
+    return out.join(EOL);\r
 }\r
 \r
-concat({\r
-    src : [\r
-        'utils.js',\r
-        'model.js',\r
-        'model_application.js',\r
-        'model_gateway.js',\r
-        'view.js'\r
-    ],\r
-    dest : '../kiwi.js'\r
-});\r
-\r
-\r
-ast = uglyfyJS.parser.parse(fs.readFileSync('../kiwi.js', FILE_ENCODING));\r
-ast = uglyfyJS.uglify.ast_mangle(ast);\r
-ast = uglyfyJS.uglify.ast_squeeze(ast);\r
-fs.writeFileSync('../kiwi.min.js', uglyfyJS.uglify.gen_code(ast), FILE_ENCODING);\r
+var src = concat([\r
+    __dirname + '/app.js',\r
+    __dirname + '/model_application.js',\r
+    __dirname + '/model_gateway.js',\r
+    __dirname + '/model_member.js',\r
+    __dirname + '/model_memberlist.js',\r
+    __dirname + '/model_panel.js',\r
+    __dirname + '/model_panellist.js',\r
+    __dirname + '/model_channel.js',\r
+    __dirname + '/model_server.js',\r
+\r
+    __dirname + '/utils.js',\r
+    __dirname + '/view.js'\r
+]);\r
+\r
+\r
+src = '(function (window) {\n\n' + src + '\n\n})(window);';\r
+\r
+\r
+fs.writeFileSync(__dirname + '/../kiwi.js', src, FILE_ENCODING);\r
+\r
+\r
+src = uglyfyJS.parser.parse(src);\r
+src = uglyfyJS.uglify.ast_mangle(src);\r
+src = uglyfyJS.uglify.ast_squeeze(src);\r
+fs.writeFileSync(__dirname + '/../kiwi.min.js', uglyfyJS.uglify.gen_code(src), FILE_ENCODING);\r
 \r
 \r
 \r
diff --git a/client_backbone/dev/model.js b/client_backbone/dev/model.js
deleted file mode 100644 (file)
index 9c4c665..0000000
+++ /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 */\r
-/*global kiwi */\r
-kiwi.model = {};\r
-\r
-kiwi.model.MemberList = Backbone.Collection.extend({\r
-    model: kiwi.model.Member,\r
-    comparator: function (a, b) {\r
-        var i, a_modes, b_modes, a_idx, b_idx, a_nick, b_nick;\r
-        var user_prefixes = kiwi.gateway.get('user_prefixes');\r
-        a_modes = a.get("modes");\r
-        b_modes = b.get("modes");\r
-        // Try to sort by modes first\r
-        if (a_modes.length > 0) {\r
-            // a has modes, but b doesn't so a should appear first\r
-            if (b_modes.length === 0) {\r
-                return -1;\r
-            }\r
-            a_idx = b_idx = -1;\r
-            // Compare the first (highest) mode\r
-            for (i = 0; i < user_prefixes.length; i++) {\r
-                if (user_prefixes[i].mode === a_modes[0]) {\r
-                    a_idx = i;\r
-                }\r
-            }\r
-            for (i = 0; i < user_prefixes.length; i++) {\r
-                if (user_prefixes[i].mode === b_modes[0]) {\r
-                    b_idx = i;\r
-                }\r
-            }\r
-            if (a_idx < b_idx) {\r
-                return -1;\r
-            } else if (a_idx > b_idx) {\r
-                return 1;\r
-            }\r
-            // If we get to here both a and b have the same highest mode so have to resort to lexicographical sorting\r
-\r
-        } else if (b_modes.length > 0) {\r
-            // b has modes but a doesn't so b should appear first\r
-            return 1;\r
-        }\r
-        a_nick = a.get("nick").toLocaleUpperCase();\r
-        b_nick = b.get("nick").toLocaleUpperCase();\r
-        // Lexicographical sorting\r
-        if (a_nick < b_nick) {\r
-            return -1;\r
-        } else if (a_nick > b_nick) {\r
-            return 1;\r
-        } else {\r
-            // This should never happen; both users have the same nick.\r
-            console.log('Something\'s gone wrong somewhere - two users have the same nick!');\r
-            return 0;\r
-        }\r
-    },\r
-    initialize: function (options) {\r
-        this.view = new kiwi.view.MemberList({"model": this});\r
-    },\r
-    getByNick: function (nick) {\r
-        if (typeof nick !== 'string') return;\r
-        return this.find(function (m) {\r
-            return nick.toLowerCase() === m.get('nick').toLowerCase();\r
-        });\r
-    }\r
-});\r
-\r
-kiwi.model.Member = Backbone.Model.extend({\r
-    sortModes: function (modes) {\r
-        return modes.sort(function (a, b) {\r
-            var a_idx, b_idx, i;\r
-            var user_prefixes = kiwi.gateway.get('user_prefixes');\r
-\r
-            for (i = 0; i < user_prefixes.length; i++) {\r
-                if (user_prefixes[i].mode === a) {\r
-                    a_idx = i;\r
-                }\r
-            }\r
-            for (i = 0; i < user_prefixes.length; i++) {\r
-                if (user_prefixes[i].mode === b) {\r
-                    b_idx = i;\r
-                }\r
-            }\r
-            if (a_idx < b_idx) {\r
-                return -1;\r
-            } else if (a_idx > b_idx) {\r
-                return 1;\r
-            } else {\r
-                return 0;\r
-            }\r
-        });\r
-    },\r
-    initialize: function (attributes) {\r
-        var nick, modes, prefix;\r
-        nick = this.stripPrefix(this.get("nick"));\r
-\r
-        modes = this.get("modes");\r
-        modes = modes || [];\r
-        this.sortModes(modes);\r
-        this.set({"nick": nick, "modes": modes, "prefix": this.getPrefix(modes)}, {silent: true});\r
-    },\r
-    addMode: function (mode) {\r
-        var modes_to_add = mode.split(''),\r
-            modes, prefix;\r
-\r
-        modes = this.get("modes");\r
-        $.each(modes_to_add, function (index, item) {\r
-            modes.push(item);\r
-        });\r
-        \r
-        modes = this.sortModes(modes);\r
-        this.set({"prefix": this.getPrefix(modes), "modes": modes});\r
-    },\r
-    removeMode: function (mode) {\r
-        var modes_to_remove = mode.split(''),\r
-            modes, prefix;\r
-\r
-        modes = this.get("modes");\r
-        modes = _.reject(modes, function (m) {\r
-            return (modes_to_remove.indexOf(m) !== -1);\r
-        });\r
-\r
-        this.set({"prefix": this.getPrefix(modes), "modes": modes});\r
-    },\r
-    getPrefix: function (modes) {\r
-        var prefix = '';\r
-        var user_prefixes = kiwi.gateway.get('user_prefixes');\r
-\r
-        if (typeof modes[0] !== 'undefined') {\r
-            prefix = _.detect(user_prefixes, function (prefix) {\r
-                return prefix.mode === modes[0];\r
-            });\r
-            prefix = (prefix) ? prefix.symbol : '';\r
-        }\r
-        return prefix;\r
-    },\r
-    stripPrefix: function (nick) {\r
-        var tmp = nick, i, j, k;\r
-        var user_prefixes = kiwi.gateway.get('user_prefixes');\r
-        i = 0;\r
-\r
-        for (j = 0; j < nick.length; j++) {\r
-            for (k = 0; k < user_prefixes.length; k++) {\r
-                if (nick.charAt(j) === user_prefixes[k].symbol) {\r
-                    i++;\r
-                    break;\r
-                }\r
-            }\r
-        }\r
-\r
-        return tmp.substr(i);\r
-    },\r
-    displayNick: function (full) {\r
-        var display = this.get('nick');\r
-\r
-        if (full) {\r
-            if (this.get("ident")) {\r
-                display += ' [' + this.get("ident") + '@' + this.get("hostname") + ']';\r
-            }\r
-        }\r
-\r
-        return display;\r
-    }\r
-});\r
-\r
-kiwi.model.PanelList = Backbone.Collection.extend({\r
-    model: kiwi.model.Panel,\r
-\r
-    // Holds the active panel\r
-    active: null,\r
-\r
-    comparator: function (chan) {\r
-        return chan.get("name");\r
-    },\r
-    initialize: function () {\r
-        this.view = new kiwi.view.Tabs({"el": $('#toolbar .panellist')[0], "model": this});\r
-\r
-        // Automatically create a server tab\r
-        this.server = new kiwi.model.Server({'name': kiwi.gateway.get('name')});\r
-        kiwi.gateway.on('change:name', this.view.render, this.view);\r
-        this.add(this.server);\r
-\r
-        this.bind('active', function (active_panel) {\r
-            this.active = active_panel;\r
-        }, this);\r
-\r
-    },\r
-    getByName: function (name) {\r
-        if (typeof name !== 'string') return;\r
-        return this.find(function (c) {\r
-            return name.toLowerCase() === c.get('name').toLowerCase();\r
-        });\r
-    }\r
-});\r
-\r
-kiwi.model.Panel = Backbone.Model.extend({\r
-    initialize: function (attributes) {\r
-        var name = this.get("name") || "";\r
-        this.view = new kiwi.view.Panel({"model": this, "name": name});\r
-        this.set({\r
-            "scrollback": [],\r
-            "name": name\r
-        }, {"silent": true});\r
-    },\r
-\r
-    addMsg: function (nick, msg, type, opts) {\r
-        var message_obj, bs, d;\r
-\r
-        opts = opts || {};\r
-\r
-        // Time defaults to now\r
-        if (!opts || typeof opts.time === 'undefined') {\r
-            d = new Date();\r
-            opts.time = d.getHours().toString().lpad(2, "0") + ":" + d.getMinutes().toString().lpad(2, "0") + ":" + d.getSeconds().toString().lpad(2, "0");\r
-        }\r
-\r
-        // CSS style defaults to empty string\r
-        if (!opts || typeof opts.style === 'undefined') {\r
-            opts.style = '';\r
-        }\r
-\r
-        // Escape any HTML that may be in here\r
-        // This doesn't seem right to be here.. should be in view (??)\r
-        msg =  $('<div />').text(msg).html();\r
-\r
-        // Run through the plugins\r
-        message_obj = {"msg": msg, "time": opts.time, "nick": nick, "chan": this.get("name"), "type": type, "style": opts.style};\r
-        //tmp = kiwi.plugs.run('addmsg', message_obj);\r
-        if (!message_obj) {\r
-            return;\r
-        }\r
-\r
-        // The CSS class (action, topic, notice, etc)\r
-        if (typeof message_obj.type !== "string") {\r
-            message_obj.type = '';\r
-        }\r
-\r
-        // Make sure we don't have NaN or something\r
-        if (typeof message_obj.msg !== "string") {\r
-            message_obj.msg = '';\r
-        }\r
-\r
-        // Convert IRC formatting into HTML formatting\r
-        message_obj.msg = formatIRCMsg(message_obj.msg);\r
-\r
-        // Update the scrollback\r
-        bs = this.get("scrollback");\r
-        bs.push(message_obj);\r
-\r
-        // Keep the scrolback limited\r
-        if (bs.length > 250) {\r
-            bs.splice(250);\r
-        }\r
-        this.set({"scrollback": bs}, {silent: true});\r
-\r
-        this.trigger("msg", message_obj);\r
-    },\r
-\r
-    close: function () {\r
-        this.view.remove();\r
-        delete this.view;\r
-\r
-        var members = this.get('members');\r
-        if (members) {\r
-            members.reset([]);\r
-            this.unset('members');\r
-        }\r
-\r
-        this.destroy();\r
-\r
-        // If closing the active panel, switch to the server panel\r
-        if (this.cid === kiwi.app.panels.active.cid) {\r
-            kiwi.app.panels.server.view.show();\r
-        }\r
-    },\r
-\r
-    isChannel: function () {\r
-        var channel_prefix = kiwi.gateway.get('channel_prefix'),\r
-            this_name = this.get('name');\r
-\r
-        if (!this_name) return false;\r
-        return (channel_prefix.indexOf(this_name[0]) > -1);\r
-    }\r
-});\r
-\r
-kiwi.model.Server = kiwi.model.Panel.extend({\r
-    server_login: null,\r
-\r
-    initialize: function (attributes) {\r
-        var name = "Server";\r
-        this.view = new kiwi.view.Panel({"model": this, "name": name});\r
-        this.set({\r
-            "scrollback": [],\r
-            "name": name\r
-        }, {"silent": true});\r
-\r
-        //this.addMsg(' ', '--> Kiwi IRC: Such an awesome IRC client', '', {style: 'color:#009900;'});\r
-\r
-        this.server_login = new kiwi.view.ServerSelect();\r
-        \r
-        this.view.$el.append(this.server_login.$el);\r
-        this.server_login.show();\r
-    }\r
-});\r
-\r
-// TODO: Channel modes\r
-// TODO: Listen to gateway events for anythign related to this channel\r
-kiwi.model.Channel = kiwi.model.Panel.extend({\r
-    initialize: function (attributes) {\r
-        var name = this.get("name") || "",\r
-            members;\r
-\r
-        this.view = new kiwi.view.Channel({"model": this, "name": name});\r
-        this.set({\r
-            "members": new kiwi.model.MemberList(),\r
-            "name": name,\r
-            "scrollback": [],\r
-            "topic": ""\r
-        }, {"silent": true});\r
-\r
-        members = this.get("members");\r
-        members.bind("add", function (member) {\r
-            this.addMsg(' ', '--> ' + member.displayNick(true) + ' has joined', 'action join');\r
-        }, this);\r
-\r
-        members.bind("remove", function (member, members, options) {\r
-            var msg = (options.message) ? '(' + options.message + ')' : '';\r
-\r
-            if (options.type === 'quit') {\r
-                this.addMsg(' ', '<-- ' + member.displayNick(true) + ' has quit ' + msg, 'action quit');\r
-            } else {\r
-                this.addMsg(' ', '<-- ' + member.displayNick(true) + ' has left ' + msg, 'action part');\r
-            }\r
-        }, this);\r
-    }\r
-});\r
index d851cc5b21842a7c2ed4dbffbb7dadbfa4b27ee8..71ad8283119d8983a6a5b24237e78fb1c42b15bf 100644 (file)
@@ -13,6 +13,13 @@ kiwi.model.Application = Backbone.Model.extend(new (function () {
     };\r
 \r
     this.start = function () {\r
+        // Only debug if set in the querystring\r
+        if (!getQueryVariable('debug')) {\r
+            manageDebug(false);\r
+        } else {\r
+            manageDebug(true);\r
+        }\r
+        \r
         // Set the gateway up\r
         kiwi.gateway = new kiwi.model.Gateway();\r
         this.bindGatewayCommands(kiwi.gateway);\r
diff --git a/client_backbone/dev/model_channel.js b/client_backbone/dev/model_channel.js
new file mode 100644 (file)
index 0000000..3104508
--- /dev/null
@@ -0,0 +1,31 @@
+// TODO: Channel modes\r
+// TODO: Listen to gateway events for anythign related to this channel\r
+kiwi.model.Channel = kiwi.model.Panel.extend({\r
+    initialize: function (attributes) {\r
+        var name = this.get("name") || "",\r
+            members;\r
+\r
+        this.view = new kiwi.view.Channel({"model": this, "name": name});\r
+        this.set({\r
+            "members": new kiwi.model.MemberList(),\r
+            "name": name,\r
+            "scrollback": [],\r
+            "topic": ""\r
+        }, {"silent": true});\r
+\r
+        members = this.get("members");\r
+        members.bind("add", function (member) {\r
+            this.addMsg(' ', '--> ' + member.displayNick(true) + ' has joined', 'action join');\r
+        }, this);\r
+\r
+        members.bind("remove", function (member, members, options) {\r
+            var msg = (options.message) ? '(' + options.message + ')' : '';\r
+\r
+            if (options.type === 'quit') {\r
+                this.addMsg(' ', '<-- ' + member.displayNick(true) + ' has quit ' + msg, 'action quit');\r
+            } else {\r
+                this.addMsg(' ', '<-- ' + member.displayNick(true) + ' has left ' + msg, 'action part');\r
+            }\r
+        }, this);\r
+    }\r
+});
\ 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 (file)
index 0000000..e74fd05
--- /dev/null
@@ -0,0 +1,97 @@
+kiwi.model.Member = Backbone.Model.extend({\r
+    sortModes: function (modes) {\r
+        return modes.sort(function (a, b) {\r
+            var a_idx, b_idx, i;\r
+            var user_prefixes = kiwi.gateway.get('user_prefixes');\r
+\r
+            for (i = 0; i < user_prefixes.length; i++) {\r
+                if (user_prefixes[i].mode === a) {\r
+                    a_idx = i;\r
+                }\r
+            }\r
+            for (i = 0; i < user_prefixes.length; i++) {\r
+                if (user_prefixes[i].mode === b) {\r
+                    b_idx = i;\r
+                }\r
+            }\r
+            if (a_idx < b_idx) {\r
+                return -1;\r
+            } else if (a_idx > b_idx) {\r
+                return 1;\r
+            } else {\r
+                return 0;\r
+            }\r
+        });\r
+    },\r
+    initialize: function (attributes) {\r
+        var nick, modes, prefix;\r
+        nick = this.stripPrefix(this.get("nick"));\r
+\r
+        modes = this.get("modes");\r
+        modes = modes || [];\r
+        this.sortModes(modes);\r
+        this.set({"nick": nick, "modes": modes, "prefix": this.getPrefix(modes)}, {silent: true});\r
+    },\r
+    addMode: function (mode) {\r
+        var modes_to_add = mode.split(''),\r
+            modes, prefix;\r
+\r
+        modes = this.get("modes");\r
+        $.each(modes_to_add, function (index, item) {\r
+            modes.push(item);\r
+        });\r
+        \r
+        modes = this.sortModes(modes);\r
+        this.set({"prefix": this.getPrefix(modes), "modes": modes});\r
+    },\r
+    removeMode: function (mode) {\r
+        var modes_to_remove = mode.split(''),\r
+            modes, prefix;\r
+\r
+        modes = this.get("modes");\r
+        modes = _.reject(modes, function (m) {\r
+            return (modes_to_remove.indexOf(m) !== -1);\r
+        });\r
+\r
+        this.set({"prefix": this.getPrefix(modes), "modes": modes});\r
+    },\r
+    getPrefix: function (modes) {\r
+        var prefix = '';\r
+        var user_prefixes = kiwi.gateway.get('user_prefixes');\r
+\r
+        if (typeof modes[0] !== 'undefined') {\r
+            prefix = _.detect(user_prefixes, function (prefix) {\r
+                return prefix.mode === modes[0];\r
+            });\r
+            prefix = (prefix) ? prefix.symbol : '';\r
+        }\r
+        return prefix;\r
+    },\r
+    stripPrefix: function (nick) {\r
+        var tmp = nick, i, j, k;\r
+        var user_prefixes = kiwi.gateway.get('user_prefixes');\r
+        i = 0;\r
+\r
+        for (j = 0; j < nick.length; j++) {\r
+            for (k = 0; k < user_prefixes.length; k++) {\r
+                if (nick.charAt(j) === user_prefixes[k].symbol) {\r
+                    i++;\r
+                    break;\r
+                }\r
+            }\r
+        }\r
+\r
+        return tmp.substr(i);\r
+    },\r
+    displayNick: function (full) {\r
+        var display = this.get('nick');\r
+\r
+        if (full) {\r
+            if (this.get("ident")) {\r
+                display += ' [' + this.get("ident") + '@' + this.get("hostname") + ']';\r
+            }\r
+        }\r
+\r
+        return display;\r
+    }\r
+});
\ 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 (file)
index 0000000..94f6558
--- /dev/null
@@ -0,0 +1,59 @@
+kiwi.model.MemberList = Backbone.Collection.extend({\r
+    model: kiwi.model.Member,\r
+    comparator: function (a, b) {\r
+        var i, a_modes, b_modes, a_idx, b_idx, a_nick, b_nick;\r
+        var user_prefixes = kiwi.gateway.get('user_prefixes');\r
+        a_modes = a.get("modes");\r
+        b_modes = b.get("modes");\r
+        // Try to sort by modes first\r
+        if (a_modes.length > 0) {\r
+            // a has modes, but b doesn't so a should appear first\r
+            if (b_modes.length === 0) {\r
+                return -1;\r
+            }\r
+            a_idx = b_idx = -1;\r
+            // Compare the first (highest) mode\r
+            for (i = 0; i < user_prefixes.length; i++) {\r
+                if (user_prefixes[i].mode === a_modes[0]) {\r
+                    a_idx = i;\r
+                }\r
+            }\r
+            for (i = 0; i < user_prefixes.length; i++) {\r
+                if (user_prefixes[i].mode === b_modes[0]) {\r
+                    b_idx = i;\r
+                }\r
+            }\r
+            if (a_idx < b_idx) {\r
+                return -1;\r
+            } else if (a_idx > b_idx) {\r
+                return 1;\r
+            }\r
+            // If we get to here both a and b have the same highest mode so have to resort to lexicographical sorting\r
+\r
+        } else if (b_modes.length > 0) {\r
+            // b has modes but a doesn't so b should appear first\r
+            return 1;\r
+        }\r
+        a_nick = a.get("nick").toLocaleUpperCase();\r
+        b_nick = b.get("nick").toLocaleUpperCase();\r
+        // Lexicographical sorting\r
+        if (a_nick < b_nick) {\r
+            return -1;\r
+        } else if (a_nick > b_nick) {\r
+            return 1;\r
+        } else {\r
+            // This should never happen; both users have the same nick.\r
+            console.log('Something\'s gone wrong somewhere - two users have the same nick!');\r
+            return 0;\r
+        }\r
+    },\r
+    initialize: function (options) {\r
+        this.view = new kiwi.view.MemberList({"model": this});\r
+    },\r
+    getByNick: function (nick) {\r
+        if (typeof nick !== 'string') return;\r
+        return this.find(function (m) {\r
+            return nick.toLowerCase() === m.get('nick').toLowerCase();\r
+        });\r
+    }\r
+});
\ 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 (file)
index 0000000..3bc446f
--- /dev/null
@@ -0,0 +1,89 @@
+kiwi.model.Panel = Backbone.Model.extend({\r
+    initialize: function (attributes) {\r
+        var name = this.get("name") || "";\r
+        this.view = new kiwi.view.Panel({"model": this, "name": name});\r
+        this.set({\r
+            "scrollback": [],\r
+            "name": name\r
+        }, {"silent": true});\r
+    },\r
+\r
+    addMsg: function (nick, msg, type, opts) {\r
+        var message_obj, bs, d;\r
+\r
+        opts = opts || {};\r
+\r
+        // Time defaults to now\r
+        if (!opts || typeof opts.time === 'undefined') {\r
+            d = new Date();\r
+            opts.time = d.getHours().toString().lpad(2, "0") + ":" + d.getMinutes().toString().lpad(2, "0") + ":" + d.getSeconds().toString().lpad(2, "0");\r
+        }\r
+\r
+        // CSS style defaults to empty string\r
+        if (!opts || typeof opts.style === 'undefined') {\r
+            opts.style = '';\r
+        }\r
+\r
+        // Escape any HTML that may be in here\r
+        // This doesn't seem right to be here.. should be in view (??)\r
+        msg =  $('<div />').text(msg).html();\r
+\r
+        // Run through the plugins\r
+        message_obj = {"msg": msg, "time": opts.time, "nick": nick, "chan": this.get("name"), "type": type, "style": opts.style};\r
+        //tmp = kiwi.plugs.run('addmsg', message_obj);\r
+        if (!message_obj) {\r
+            return;\r
+        }\r
+\r
+        // The CSS class (action, topic, notice, etc)\r
+        if (typeof message_obj.type !== "string") {\r
+            message_obj.type = '';\r
+        }\r
+\r
+        // Make sure we don't have NaN or something\r
+        if (typeof message_obj.msg !== "string") {\r
+            message_obj.msg = '';\r
+        }\r
+\r
+        // Convert IRC formatting into HTML formatting\r
+        message_obj.msg = formatIRCMsg(message_obj.msg);\r
+\r
+        // Update the scrollback\r
+        bs = this.get("scrollback");\r
+        bs.push(message_obj);\r
+\r
+        // Keep the scrolback limited\r
+        if (bs.length > 250) {\r
+            bs.splice(250);\r
+        }\r
+        this.set({"scrollback": bs}, {silent: true});\r
+\r
+        this.trigger("msg", message_obj);\r
+    },\r
+\r
+    close: function () {\r
+        this.view.remove();\r
+        delete this.view;\r
+\r
+        var members = this.get('members');\r
+        if (members) {\r
+            members.reset([]);\r
+            this.unset('members');\r
+        }\r
+\r
+        this.destroy();\r
+\r
+        // If closing the active panel, switch to the server panel\r
+        if (this.cid === kiwi.app.panels.active.cid) {\r
+            kiwi.app.panels.server.view.show();\r
+        }\r
+    },\r
+\r
+    isChannel: function () {\r
+        var channel_prefix = kiwi.gateway.get('channel_prefix'),\r
+            this_name = this.get('name');\r
+\r
+        if (!this_name) return false;\r
+        return (channel_prefix.indexOf(this_name[0]) > -1);\r
+    }\r
+});
\ 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 (file)
index 0000000..28217f3
--- /dev/null
@@ -0,0 +1,29 @@
+kiwi.model.PanelList = Backbone.Collection.extend({\r
+    model: kiwi.model.Panel,\r
+\r
+    // Holds the active panel\r
+    active: null,\r
+\r
+    comparator: function (chan) {\r
+        return chan.get("name");\r
+    },\r
+    initialize: function () {\r
+        this.view = new kiwi.view.Tabs({"el": $('#toolbar .panellist')[0], "model": this});\r
+\r
+        // Automatically create a server tab\r
+        this.server = new kiwi.model.Server({'name': kiwi.gateway.get('name')});\r
+        kiwi.gateway.on('change:name', this.view.render, this.view);\r
+        this.add(this.server);\r
+\r
+        this.bind('active', function (active_panel) {\r
+            this.active = active_panel;\r
+        }, this);\r
+\r
+    },\r
+    getByName: function (name) {\r
+        if (typeof name !== 'string') return;\r
+        return this.find(function (c) {\r
+            return name.toLowerCase() === c.get('name').toLowerCase();\r
+        });\r
+    }\r
+});
\ 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 (file)
index 0000000..af5ee1d
--- /dev/null
@@ -0,0 +1,19 @@
+kiwi.model.Server = kiwi.model.Panel.extend({\r
+    server_login: null,\r
+\r
+    initialize: function (attributes) {\r
+        var name = "Server";\r
+        this.view = new kiwi.view.Panel({"model": this, "name": name});\r
+        this.set({\r
+            "scrollback": [],\r
+            "name": name\r
+        }, {"silent": true});\r
+\r
+        //this.addMsg(' ', '--> Kiwi IRC: Such an awesome IRC client', '', {style: 'color:#009900;'});\r
+\r
+        this.server_login = new kiwi.view.ServerSelect();\r
+        \r
+        this.view.$el.append(this.server_login.$el);\r
+        this.server_login.show();\r
+    }\r
+});
\ No newline at end of file
index 496306b6d097a44fe02c87eb0f910524f007ee53..d80fd80f5d23d51789cb40fac394d8ada35ea14c 100644 (file)
@@ -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 = {};
-
 
 
 /**
index 50500e99844abed953c1e8ca994a24e86750cd87..60c4a91830d023faddbecba564fb27f6e046e845 100644 (file)
         // 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 {
 
         // 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();
         }
index 22a8dedd5513a102e01e3e422a17d1a2bce04464..f891ef0e5d6abaf51becc777d94ef5ba510ca547 100644 (file)
-/*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 = '<b>';
-        while (msg.indexOf(String.fromCharCode(2)) !== -1) {
-            msg = msg.replace(String.fromCharCode(2), next);
-            next = (next === '<b>') ? '</b>' : '<b>';
-        }
-        if (next === '</b>') {
-            msg = msg + '</b>';
-        }
-    }
-
-    // underline
-    if (msg.indexOf(String.fromCharCode(31)) !== -1) {
-        next = '<u>';
-        while (msg.indexOf(String.fromCharCode(31)) !== -1) {
-            msg = msg.replace(String.fromCharCode(31), next);
-            next = (next === '<u>') ? '</u>' : '<u>';
-        }
-        if (next === '</u>') {
-            msg = msg + '</u>';
-        }
-    }
-
-    // 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 += '<span style="' + ((fg !== null) ? 'color: ' + fg + '; ' : '') + ((bg !== null) ? 'background-color: ' + bg + ';' : '') + '">' + str + '</span>';
-                    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..)\r
+/**\r
+*   @namespace\r
+*/\r
+var kiwi = window.kiwi = {};\r
+\r
+kiwi.model = {};\r
+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 = '<img class="link_img_a" src="' + url + '" height="100%" width="100%" />';
-                return '<a class="link_ext link_img" target="_blank" rel="nofollow" href="' + url + '" style="height:50px;width:50px;display:block">' + img + '<div class="tt box"></div></a>';
-            });
-
-            return event;
-        }
-    },
-
-    {
-        name: "html_safe",
-        onaddmsg: function (event, opts) {
-            event.msg = $('<div/>').text(event.msg).html();
-            event.nick = $('<div/>').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 = '<span style="color:red;">' + event.msg + '</span>';
-            //    }
-            //}
-
-            // 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 '<a class="link_ext" target="_blank" rel="nofollow" href="' + url + '">' + nice + '<div class="tt box"></div></a>';
-                return '<a class="link_ext" target="_blank" rel="nofollow" href="' + url + '">' + nice + '</a>';
-            });
-
-            return event;
-        }
-    },
-
-    {
-        name: "lftobr",
-        onaddmsg: function (event, opts) {
-            if (!event.msg) {
-                return event;
-            }
-
-            event.msg = event.msg.replace(/\n/gi, function (txt) {
-                return '<br/>';
-            });
-
-            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 = $('<a class="link_ext_browser">Open in Kiwi..</a>');
-                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 = $('<iframe border="0" class="utility_view" src="" style="width:100%;height:100%;border:none;"></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 = '<span style="color:' + c + ';">' + event.nick + '</span>';
-
-            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 */\r
-/*global kiwi */\r
-kiwi.model = {};\r
-\r
-kiwi.model.MemberList = Backbone.Collection.extend({\r
-    model: kiwi.model.Member,\r
-    comparator: function (a, b) {\r
-        var i, a_modes, b_modes, a_idx, b_idx, a_nick, b_nick;\r
-        var user_prefixes = kiwi.gateway.get('user_prefixes');\r
-        a_modes = a.get("modes");\r
-        b_modes = b.get("modes");\r
-        // Try to sort by modes first\r
-        if (a_modes.length > 0) {\r
-            // a has modes, but b doesn't so a should appear first\r
-            if (b_modes.length === 0) {\r
-                return -1;\r
-            }\r
-            a_idx = b_idx = -1;\r
-            // Compare the first (highest) mode\r
-            for (i = 0; i < user_prefixes.length; i++) {\r
-                if (user_prefixes[i].mode === a_modes[0]) {\r
-                    a_idx = i;\r
-                }\r
-            }\r
-            for (i = 0; i < user_prefixes.length; i++) {\r
-                if (user_prefixes[i].mode === b_modes[0]) {\r
-                    b_idx = i;\r
-                }\r
-            }\r
-            if (a_idx < b_idx) {\r
-                return -1;\r
-            } else if (a_idx > b_idx) {\r
-                return 1;\r
-            }\r
-            // If we get to here both a and b have the same highest mode so have to resort to lexicographical sorting\r
-\r
-        } else if (b_modes.length > 0) {\r
-            // b has modes but a doesn't so b should appear first\r
-            return 1;\r
-        }\r
-        a_nick = a.get("nick").toLocaleUpperCase();\r
-        b_nick = b.get("nick").toLocaleUpperCase();\r
-        // Lexicographical sorting\r
-        if (a_nick < b_nick) {\r
-            return -1;\r
-        } else if (a_nick > b_nick) {\r
-            return 1;\r
-        } else {\r
-            // This should never happen; both users have the same nick.\r
-            console.log('Something\'s gone wrong somewhere - two users have the same nick!');\r
-            return 0;\r
-        }\r
-    },\r
-    initialize: function (options) {\r
-        this.view = new kiwi.view.MemberList({"model": this});\r
-    },\r
-    getByNick: function (nick) {\r
-        if (typeof nick !== 'string') return;\r
-        return this.find(function (m) {\r
-            return nick.toLowerCase() === m.get('nick').toLowerCase();\r
-        });\r
-    }\r
-});\r
-\r
-kiwi.model.Member = Backbone.Model.extend({\r
-    sortModes: function (modes) {\r
-        return modes.sort(function (a, b) {\r
-            var a_idx, b_idx, i;\r
-            var user_prefixes = kiwi.gateway.get('user_prefixes');\r
-\r
-            for (i = 0; i < user_prefixes.length; i++) {\r
-                if (user_prefixes[i].mode === a) {\r
-                    a_idx = i;\r
-                }\r
-            }\r
-            for (i = 0; i < user_prefixes.length; i++) {\r
-                if (user_prefixes[i].mode === b) {\r
-                    b_idx = i;\r
-                }\r
-            }\r
-            if (a_idx < b_idx) {\r
-                return -1;\r
-            } else if (a_idx > b_idx) {\r
-                return 1;\r
-            } else {\r
-                return 0;\r
-            }\r
-        });\r
-    },\r
-    initialize: function (attributes) {\r
-        var nick, modes, prefix;\r
-        nick = this.stripPrefix(this.get("nick"));\r
-\r
-        modes = this.get("modes");\r
-        modes = modes || [];\r
-        this.sortModes(modes);\r
-        this.set({"nick": nick, "modes": modes, "prefix": this.getPrefix(modes)}, {silent: true});\r
-    },\r
-    addMode: function (mode) {\r
-        var modes_to_add = mode.split(''),\r
-            modes, prefix;\r
-\r
-        modes = this.get("modes");\r
-        $.each(modes_to_add, function (index, item) {\r
-            modes.push(item);\r
-        });\r
-        \r
-        modes = this.sortModes(modes);\r
-        this.set({"prefix": this.getPrefix(modes), "modes": modes});\r
-    },\r
-    removeMode: function (mode) {\r
-        var modes_to_remove = mode.split(''),\r
-            modes, prefix;\r
-\r
-        modes = this.get("modes");\r
-        modes = _.reject(modes, function (m) {\r
-            return (modes_to_remove.indexOf(m) !== -1);\r
-        });\r
-\r
-        this.set({"prefix": this.getPrefix(modes), "modes": modes});\r
-    },\r
-    getPrefix: function (modes) {\r
-        var prefix = '';\r
-        var user_prefixes = kiwi.gateway.get('user_prefixes');\r
-\r
-        if (typeof modes[0] !== 'undefined') {\r
-            prefix = _.detect(user_prefixes, function (prefix) {\r
-                return prefix.mode === modes[0];\r
-            });\r
-            prefix = (prefix) ? prefix.symbol : '';\r
-        }\r
-        return prefix;\r
-    },\r
-    stripPrefix: function (nick) {\r
-        var tmp = nick, i, j, k;\r
-        var user_prefixes = kiwi.gateway.get('user_prefixes');\r
-        i = 0;\r
-\r
-        for (j = 0; j < nick.length; j++) {\r
-            for (k = 0; k < user_prefixes.length; k++) {\r
-                if (nick.charAt(j) === user_prefixes[k].symbol) {\r
-                    i++;\r
-                    break;\r
-                }\r
-            }\r
-        }\r
-\r
-        return tmp.substr(i);\r
-    },\r
-    displayNick: function (full) {\r
-        var display = this.get('nick');\r
-\r
-        if (full) {\r
-            if (this.get("ident")) {\r
-                display += ' [' + this.get("ident") + '@' + this.get("hostname") + ']';\r
-            }\r
-        }\r
-\r
-        return display;\r
-    }\r
-});\r
-\r
-kiwi.model.PanelList = Backbone.Collection.extend({\r
-    model: kiwi.model.Panel,\r
-\r
-    // Holds the active panel\r
-    active: null,\r
-\r
-    comparator: function (chan) {\r
-        return chan.get("name");\r
-    },\r
-    initialize: function () {\r
-        this.view = new kiwi.view.Tabs({"el": $('#toolbar .panellist')[0], "model": this});\r
-\r
-        // Automatically create a server tab\r
-        this.server = new kiwi.model.Server({'name': kiwi.gateway.get('name')});\r
-        kiwi.gateway.on('change:name', this.view.render, this.view);\r
-        this.add(this.server);\r
-\r
-        this.bind('active', function (active_panel) {\r
-            this.active = active_panel;\r
-        }, this);\r
-\r
-    },\r
-    getByName: function (name) {\r
-        if (typeof name !== 'string') return;\r
-        return this.find(function (c) {\r
-            return name.toLowerCase() === c.get('name').toLowerCase();\r
-        });\r
-    }\r
-});\r
-\r
-kiwi.model.Panel = Backbone.Model.extend({\r
-    initialize: function (attributes) {\r
-        var name = this.get("name") || "";\r
-        this.view = new kiwi.view.Panel({"model": this, "name": name});\r
-        this.set({\r
-            "scrollback": [],\r
-            "name": name\r
-        }, {"silent": true});\r
-    },\r
-\r
-    addMsg: function (nick, msg, type, opts) {\r
-        var message_obj, bs, d;\r
-\r
-        opts = opts || {};\r
-\r
-        // Time defaults to now\r
-        if (!opts || typeof opts.time === 'undefined') {\r
-            d = new Date();\r
-            opts.time = d.getHours().toString().lpad(2, "0") + ":" + d.getMinutes().toString().lpad(2, "0") + ":" + d.getSeconds().toString().lpad(2, "0");\r
-        }\r
-\r
-        // CSS style defaults to empty string\r
-        if (!opts || typeof opts.style === 'undefined') {\r
-            opts.style = '';\r
-        }\r
-\r
-        // Escape any HTML that may be in here\r
-        // This doesn't seem right to be here.. should be in view (??)\r
-        msg =  $('<div />').text(msg).html();\r
-\r
-        // Run through the plugins\r
-        message_obj = {"msg": msg, "time": opts.time, "nick": nick, "chan": this.get("name"), "type": type, "style": opts.style};\r
-        //tmp = kiwi.plugs.run('addmsg', message_obj);\r
-        if (!message_obj) {\r
-            return;\r
-        }\r
-\r
-        // The CSS class (action, topic, notice, etc)\r
-        if (typeof message_obj.type !== "string") {\r
-            message_obj.type = '';\r
-        }\r
-\r
-        // Make sure we don't have NaN or something\r
-        if (typeof message_obj.msg !== "string") {\r
-            message_obj.msg = '';\r
-        }\r
-\r
-        // Convert IRC formatting into HTML formatting\r
-        message_obj.msg = formatIRCMsg(message_obj.msg);\r
-\r
-        // Update the scrollback\r
-        bs = this.get("scrollback");\r
-        bs.push(message_obj);\r
-\r
-        // Keep the scrolback limited\r
-        if (bs.length > 250) {\r
-            bs.splice(250);\r
-        }\r
-        this.set({"scrollback": bs}, {silent: true});\r
-\r
-        this.trigger("msg", message_obj);\r
-    },\r
-\r
-    close: function () {\r
-        this.view.remove();\r
-        delete this.view;\r
-\r
-        var members = this.get('members');\r
-        if (members) {\r
-            members.reset([]);\r
-            this.unset('members');\r
-        }\r
-\r
-        this.destroy();\r
-\r
-        // If closing the active panel, switch to the server panel\r
-        if (this.cid === kiwi.app.panels.active.cid) {\r
-            kiwi.app.panels.server.view.show();\r
-        }\r
-    },\r
-\r
-    isChannel: function () {\r
-        var channel_prefix = kiwi.gateway.get('channel_prefix'),\r
-            this_name = this.get('name');\r
-\r
-        if (!this_name) return false;\r
-        return (channel_prefix.indexOf(this_name[0]) > -1);\r
-    }\r
-});\r
-\r
-kiwi.model.Server = kiwi.model.Panel.extend({\r
-    server_login: null,\r
-\r
-    initialize: function (attributes) {\r
-        var name = "Server";\r
-        this.view = new kiwi.view.Panel({"model": this, "name": name});\r
-        this.set({\r
-            "scrollback": [],\r
-            "name": name\r
-        }, {"silent": true});\r
-\r
-        //this.addMsg(' ', '--> Kiwi IRC: Such an awesome IRC client', '', {style: 'color:#009900;'});\r
-\r
-        this.server_login = new kiwi.view.ServerSelect();\r
-        \r
-        this.view.$el.append(this.server_login.$el);\r
-        this.server_login.show();\r
-    }\r
-});\r
-\r
-// TODO: Channel modes\r
-// TODO: Listen to gateway events for anythign related to this channel\r
-kiwi.model.Channel = kiwi.model.Panel.extend({\r
-    initialize: function (attributes) {\r
-        var that = this,\r
-            name = this.get("name") || "",\r
-            members;\r
-\r
-        this.view = new kiwi.view.Channel({"model": this, "name": name});\r
-        this.set({\r
-            "members": new kiwi.model.MemberList(),\r
-            "name": name,\r
-            "scrollback": [],\r
-            "topic": ""\r
-        }, {"silent": true});\r
-\r
-        members = this.get("members");\r
-        members.bind("add", function (member) {\r
-            this.addMsg(' ', '--> ' + member.displayNick(true) + ' has joined', 'action join');\r
-        }, this);\r
-\r
-        members.bind("remove", function (member, members, options) {\r
-            var msg = (options.message) ? '(' + options.message + ')' : '';\r
-\r
-            if (options.type === 'quit') {\r
-                this.addMsg(' ', '<-- ' + member.displayNick(true) + ' has quit ' + msg, 'action quit');\r
-            } else {\r
-                this.addMsg(' ', '<-- ' + member.displayNick(true) + ' has left ' + msg, 'action part');\r
-            }\r
-        }, this);\r
-    }\r
-});\r
-
 kiwi.model.Application = Backbone.Model.extend(new (function () {\r
     var that = this;\r
 \r
@@ -950,6 +25,13 @@ kiwi.model.Application = Backbone.Model.extend(new (function () {
     };\r
 \r
     this.start = function () {\r
+        // Only debug if set in the querystring\r
+        if (!getQueryVariable('debug')) {\r
+            manageDebug(false);\r
+        } else {\r
+            manageDebug(true);\r
+        }\r
+        \r
         // Set the gateway up\r
         kiwi.gateway = new kiwi.model.Gateway();\r
         this.bindGatewayCommands(kiwi.gateway);\r
@@ -1136,666 +218,1604 @@ kiwi.model.Application = Backbone.Model.extend(new (function () {
         });\r
 \r
 \r
-        gw.on('ontopic', function (event) {\r
-            var c;\r
-            c = that.panels.getByName(event.channel);\r
-            if (!c) return;\r
+        gw.on('ontopic', function (event) {\r
+            var c;\r
+            c = that.panels.getByName(event.channel);\r
+            if (!c) return;\r
+\r
+            // Set the channels topic\r
+            c.set('topic', event.topic);\r
+\r
+            // If this is the active channel, update the topic bar too\r
+            if (c.get('name') === kiwi.app.panels.active.get('name')) {\r
+                that.topicbar.setCurrentTopic(event.topic);\r
+            }\r
+        });\r
+\r
+\r
+        gw.on('ontopicsetby', function (event) {\r
+            var c, when;\r
+            c = that.panels.getByName(event.channel);\r
+            if (!c) return;\r
+\r
+            when = new Date(event.when * 1000).toLocaleString();\r
+            c.addMsg('', 'Topic set by ' + event.nick + ' at ' + when, 'topic');\r
+        });\r
+\r
+\r
+        gw.on('onuserlist', function (event) {\r
+            var channel;\r
+            channel = that.panels.getByName(event.channel);\r
+\r
+            // If we didn't find a channel for this, may aswell leave\r
+            if (!channel) return;\r
+\r
+            channel.temp_userlist = channel.temp_userlist || [];\r
+            _.each(event.users, function (item) {\r
+                var user = new kiwi.model.Member({nick: item.nick, modes: item.modes});\r
+                channel.temp_userlist.push(user);\r
+            });\r
+        });\r
+\r
+\r
+        gw.on('onuserlist_end', function (event) {\r
+            var channel;\r
+            channel = that.panels.getByName(event.channel);\r
+\r
+            // If we didn't find a channel for this, may aswell leave\r
+            if (!channel) return;\r
+\r
+            // Update the members list with the new list\r
+            channel.get('members').reset(channel.temp_userlist || []);\r
+\r
+            // Clear the temporary userlist\r
+            delete channel.temp_userlist;\r
+        });\r
+\r
+\r
+        gw.on('onmode', function (event) {\r
+            var channel, members, member;\r
+\r
+            if (!event.channel) return;\r
+            channel = that.panels.getByName(event.channel);\r
+            if (!channel) return;\r
+\r
+            members = channel.get('members');\r
+            if (!members) return;\r
+\r
+            member = members.getByNick(event.effected_nick);\r
+            if (!member) return;\r
+\r
+            if (event.mode[0] === '+') {\r
+                member.addMode(event.mode.substr(1));\r
+            } else if (event.mode[0] === '-') {\r
+                member.removeMode(event.mode.substr(1));\r
+            }\r
+        });\r
+\r
+\r
+        gw.on('onnick', function (event) {\r
+            var member;\r
+\r
+            $.each(that.panels.models, function (index, panel) {\r
+                if (!panel.isChannel()) return;\r
+\r
+                member = panel.get('members').getByNick(event.nick);\r
+                if (member) {\r
+                    member.set('nick', event.newnick);\r
+                    panel.addMsg('', '== ' + event.nick + ' is now known as ' + event.newnick, 'action nick');\r
+                }\r
+            });\r
+        });\r
+    };\r
+\r
+\r
+\r
+    /**\r
+     * Bind to certain commands that may be typed into the control box\r
+     */\r
+    this.bindControllboxCommands = function (controlbox) {\r
+        controlbox.on('unknown_command', this.unknownCommand);\r
+\r
+        controlbox.on('command', this.allCommands);\r
+        controlbox.on('command_msg', this.msgCommand);\r
+\r
+        controlbox.on('command_action', this.actionCommand);\r
+        controlbox.on('command_me', this.actionCommand);\r
+\r
+        controlbox.on('command_join', this.joinCommand);\r
+        controlbox.on('command_j', this.joinCommand);\r
+\r
+        controlbox.on('command_part', this.partCommand);\r
+        controlbox.on('command_p', this.partCommand);\r
+\r
+        controlbox.on('command_nick', function (ev) {\r
+            kiwi.gateway.changeNick(ev.params[0]);\r
+        });\r
+\r
+        controlbox.on('command_query', this.queryCommand);\r
+        controlbox.on('command_q', this.queryCommand);\r
+\r
+        controlbox.on('command_topic', this.topicCommand);\r
+\r
+        controlbox.on('command_notice', this.noticeCommand);\r
+\r
+        controlbox.on('command_css', function (ev) {\r
+            var queryString = '?reload=' + new Date().getTime();\r
+            $('link[rel="stylesheet"]').each(function () {\r
+                this.href = this.href.replace(/\?.*|$/, queryString);\r
+            });\r
+        });\r
+    };\r
+\r
+    // A fallback action. Send a raw command to the server\r
+    this.unknownCommand = function (ev) {\r
+        var raw_cmd = ev.command + ' ' + ev.params.join(' ');\r
+        console.log('RAW: ' + raw_cmd);\r
+        kiwi.gateway.raw(raw_cmd);\r
+    };\r
+\r
+    this.allCommands = function (ev) {\r
+        console.log('allCommands', ev);\r
+    };\r
+\r
+    this.joinCommand = function (ev) {\r
+        var channel, channel_names;\r
+\r
+        channel_names = ev.params.join(' ').split(',');\r
+\r
+        $.each(channel_names, function (index, channel_name) {\r
+            // Trim any whitespace off the name\r
+            channel_name = channel_name.trim();\r
+\r
+            // Check if we have the panel already. If not, create it\r
+            channel = that.panels.getByName(channel_name);\r
+            if (!channel) {\r
+                channel = new kiwi.model.Channel({name: channel_name});\r
+                kiwi.app.panels.add(channel);\r
+            }\r
+\r
+            kiwi.gateway.join(channel_name);\r
+        });\r
+\r
+        if (channel) channel.view.show();\r
+        \r
+    };\r
+\r
+    this.queryCommand = function (ev) {\r
+        var destination, panel;\r
+\r
+        destination = ev.params[0];\r
+\r
+        // Check if we have the panel already. If not, create it\r
+        panel = that.panels.getByName(destination);\r
+        if (!panel) {\r
+            panel = new kiwi.model.Channel({name: destination});\r
+            kiwi.app.panels.add(panel);\r
+        }\r
+\r
+        if (panel) panel.view.show();\r
+        \r
+    };\r
+\r
+    this.msgCommand = function (ev) {\r
+        var destination = ev.params[0],\r
+            panel = that.panels.getByName(destination) || that.panels.server;\r
+\r
+        ev.params.shift();\r
+\r
+        panel.addMsg(kiwi.gateway.get('nick'), ev.params.join(' '));\r
+        kiwi.gateway.privmsg(destination, ev.params.join(' '));\r
+    };\r
+\r
+    this.actionCommand = function (ev) {\r
+        if (kiwi.app.panels.active === kiwi.app.panels.server) {\r
+            return;\r
+        }\r
+\r
+        var panel = kiwi.app.panels.active;\r
+        panel.addMsg('', '* ' + kiwi.gateway.get('nick') + ' ' + ev.params.join(' '), 'action');\r
+        kiwi.gateway.action(panel.get('name'), ev.params.join(' '));\r
+    };\r
+\r
+    this.partCommand = function (ev) {\r
+        if (ev.params.length === 0) {\r
+            kiwi.gateway.part(kiwi.app.panels.active.get('name'));\r
+        } else {\r
+            _.each(ev.params, function (channel) {\r
+                kiwi.gateway.part(channel);\r
+            });\r
+        }\r
+        // TODO: More responsive = close tab now, more accurate = leave until part event\r
+        //kiwi.app.panels.remove(kiwi.app.panels.active);\r
+    };\r
+\r
+    this.topicCommand = function (ev) {\r
+        var channel_name;\r
+\r
+        if (ev.params.length === 0) return;\r
 \r
-            // Set the channels topic\r
-            c.set('topic', event.topic);\r
+        if (that.isChannelName(ev.params[0])) {\r
+            channel_name = ev.params[0];\r
+            ev.params.shift();\r
+        } else {\r
+            channel_name = kiwi.app.panels.active.get('name');\r
+        }\r
 \r
-            // If this is the active channel, update the topic bar too\r
-            if (c.get('name') === kiwi.app.panels.active.get('name')) {\r
-                that.topicbar.setCurrentTopic(event.topic);\r
-            }\r
-        });\r
+        kiwi.gateway.topic(channel_name, ev.params.join(' '));\r
+    };\r
 \r
+    this.noticeCommand = function (ev) {\r
+        var destination;\r
 \r
-        gw.on('ontopicsetby', function (event) {\r
-            var c, when;\r
-            c = that.panels.getByName(event.channel);\r
-            if (!c) return;\r
+        // Make sure we have a destination and some sort of message\r
+        if (ev.params.length <= 1) return;\r
 \r
-            when = new Date(event.when * 1000).toLocaleString();\r
-            c.addMsg('', 'Topic set by ' + event.nick + ' at ' + when, 'topic');\r
-        });\r
+        destination = ev.params[0];\r
+        ev.params.shift();\r
 \r
+        kiwi.gateway.notice(destination, ev.params.join(' '));\r
+    };\r
 \r
-        gw.on('onuserlist', function (event) {\r
-            var channel;\r
-            channel = that.panels.getByName(event.channel);\r
 \r
-            // If we didn't find a channel for this, may aswell leave\r
-            if (!channel) return;\r
 \r
-            channel.temp_userlist = channel.temp_userlist || [];\r
-            _.each(event.users, function (item) {\r
-                var user = new kiwi.model.Member({nick: item.nick, modes: item.modes});\r
-                channel.temp_userlist.push(user);\r
-            });\r
-        });\r
 \r
 \r
-        gw.on('onuserlist_end', function (event) {\r
-            var channel;\r
-            channel = that.panels.getByName(event.channel);\r
+    this.isChannelName = function (channel_name) {\r
+        var channel_prefix = kiwi.gateway.get('channel_prefix');\r
 \r
-            // If we didn't find a channel for this, may aswell leave\r
-            if (!channel) return;\r
+        if (!channel_name || !channel_name.length) return false;\r
+        return (channel_prefix.indexOf(channel_name[0]) > -1);\r
+    };\r
 \r
-            // Update the members list with the new list\r
-            channel.get('members').reset(channel.temp_userlist || []);\r
+})());
+
+
+kiwi.model.Gateway = Backbone.Model.extend(new (function () {\r
+    var that = this;\r
 \r
-            // Clear the temporary userlist\r
-            delete channel.temp_userlist;\r
-        });\r
+    this.defaults = {\r
+        /**\r
+        *   The name of the network\r
+        *   @type    String\r
+        */\r
+        name: 'Server',\r
 \r
+        /**\r
+        *   The address (URL) of the network\r
+        *   @type    String\r
+        */\r
+        address: '',\r
 \r
-        gw.on('onmode', function (event) {\r
-            var channel, members, member;\r
+        /**\r
+        *   The current nickname\r
+        *   @type   String\r
+        */\r
+        nick: '',\r
 \r
-            if (!event.channel) return;\r
-            channel = that.panels.getByName(event.channel);\r
-            if (!channel) return;\r
+        /**\r
+        *   The channel prefix for this network\r
+        *   @type    String\r
+        */\r
+        channel_prefix: '#',\r
 \r
-            members = channel.get('members');\r
-            if (!members) return;\r
+        /**\r
+        *   The user prefixes for channel owner/admin/op/voice etc. on this network\r
+        *   @type   Array\r
+        */\r
+        user_prefixes: ['~', '&', '@', '+'],\r
 \r
-            member = members.getByNick(event.effected_nick);\r
-            if (!member) return;\r
+        /**\r
+        *   The URL to the Kiwi server\r
+        *   @type   String\r
+        */\r
+        //kiwi_server: '//kiwi'\r
+        kiwi_server: 'http://localhost:7778/kiwi'\r
+    };\r
 \r
-            if (event.mode[0] === '+') {\r
-                member.addMode(event.mode.substr(1));\r
-            } else if (event.mode[0] === '-') {\r
-                member.removeMode(event.mode.substr(1));\r
-            }\r
-        });\r
 \r
+    this.initialize = function () {\r
+        // Update `that` with this new Model object\r
+        that = this;\r
 \r
-        gw.on('onnick', function (event) {\r
-            var member;\r
+        // For ease of access. The socket.io object\r
+        this.socket = this.get('socket');\r
 \r
-            $.each(that.panels.models, function (index, panel) {\r
-                if (!panel.isChannel()) return;\r
+        // Redundant perhaps? Legacy\r
+        this.session_id = '';\r
 \r
-                member = panel.get('members').getByNick(event.nick);\r
-                if (member) {\r
-                    member.set('nick', event.newnick);\r
-                    panel.addMsg('', '== ' + event.nick + ' is now known as ' + event.newnick, 'action nick');\r
-                }\r
-            });\r
-        });\r
+        network = this;\r
     };\r
 \r
 \r
-\r
     /**\r
-     * Bind to certain commands that may be typed into the control box\r
-     */\r
-    this.bindControllboxCommands = function (controlbox) {\r
-        controlbox.on('unknown_command', this.unknownCommand);\r
-\r
-        controlbox.on('command', this.allCommands);\r
-        controlbox.on('command_msg', this.msgCommand);\r
+    *   Connects to the server\r
+    *   @param  {String}    host        The hostname or IP address of the IRC server to connect to\r
+    *   @param  {Number}    port        The port of the IRC server to connect to\r
+    *   @param  {Boolean}   ssl         Whether or not to connect to the IRC server using SSL\r
+    *   @param  {String}    password    The password to supply to the IRC server during registration\r
+    *   @param  {Function}  callback    A callback function to be invoked once Kiwi's server has connected to the IRC server\r
+    */\r
+    this.connect = function (host, port, ssl, password, callback) {\r
+        this.socket = io.connect(this.get('kiwi_server'), {\r
+            'try multiple transports': true,\r
+            'connect timeout': 3000,\r
+            'max reconnection attempts': 7,\r
+            'reconnection delay': 2000\r
+        });\r
+        this.socket.on('connect_failed', function (reason) {\r
+            // TODO: When does this even actually get fired? I can't find a case! ~Darren\r
+            console.debug('Unable to connect Socket.IO', reason);\r
+            console.log("kiwi.gateway.socket.on('connect_failed')");\r
+            //kiwi.front.tabviews.server.addMsg(null, ' ', 'Unable to connect to Kiwi IRC.\n' + reason, 'error');\r
+            this.socket.disconnect();\r
+            this.emit("connect_fail", {reason: reason});\r
+        });\r
 \r
-        controlbox.on('command_action', this.actionCommand);\r
-        controlbox.on('command_me', this.actionCommand);\r
+        this.socket.on('error', function (e) {\r
+            this.emit("connect_fail", {reason: e});\r
+            console.log("kiwi.gateway.socket.on('error')", {reason: e});\r
+        });\r
 \r
-        controlbox.on('command_join', this.joinCommand);\r
-        controlbox.on('command_j', this.joinCommand);\r
+        this.socket.on('connecting', function (transport_type) {\r
+            console.log("kiwi.gateway.socket.on('connecting')");\r
+            this.emit("connecting");\r
+            that.trigger("connecting");\r
+        });\r
 \r
-        controlbox.on('command_part', this.partCommand);\r
-        controlbox.on('command_p', this.partCommand);\r
+        this.socket.on('connect', function () {\r
+            this.emit('irc connect', that.get('nick'), host, port, ssl, password, callback);\r
+            console.log("kiwi.gateway.socket.on('connect')");\r
+        });\r
 \r
-        controlbox.on('command_nick', function (ev) {\r
-            kiwi.gateway.changeNick(ev.params[0]);\r
+        this.socket.on('too_many_connections', function () {\r
+            this.emit("connect_fail", {reason: 'too_many_connections'});\r
         });\r
 \r
-        controlbox.on('command_query', this.queryCommand);\r
-        controlbox.on('command_q', this.queryCommand);\r
+        this.socket.on('message', this.parse);\r
 \r
-        controlbox.on('command_topic', this.topicCommand);\r
+        this.socket.on('disconnect', function () {\r
+            this.emit("disconnect", {});\r
+            console.log("kiwi.gateway.socket.on('disconnect')");\r
+        });\r
 \r
-        controlbox.on('command_notice', this.noticeCommand);\r
+        this.socket.on('close', function () {\r
+            console.log("kiwi.gateway.socket.on('close')");\r
+        });\r
 \r
-        controlbox.on('command_css', function (ev) {\r
-            var queryString = '?reload=' + new Date().getTime();\r
-            $('link[rel="stylesheet"]').each(function () {\r
-                this.href = this.href.replace(/\?.*|$/, queryString);\r
-            });\r
+        this.socket.on('reconnecting', function (reconnectionDelay, reconnectionAttempts) {\r
+            console.log("kiwi.gateway.socket.on('reconnecting')");\r
+            this.emit("reconnecting", {delay: reconnectionDelay, attempts: reconnectionAttempts});\r
         });\r
-    };\r
 \r
-    // A fallback action. Send a raw command to the server\r
-    this.unknownCommand = function (ev) {\r
-        var raw_cmd = ev.command + ' ' + ev.params.join(' ');\r
-        console.log('RAW: ' + raw_cmd);\r
-        kiwi.gateway.raw(raw_cmd);\r
+        this.socket.on('reconnect_failed', function () {\r
+            console.log("kiwi.gateway.socket.on('reconnect_failed')");\r
+        });\r
     };\r
 \r
-    this.allCommands = function (ev) {\r
-        console.log('allCommands', ev);\r
-    };\r
 \r
-    this.joinCommand = function (ev) {\r
-        var channel, channel_names;\r
+    /*\r
+        Events:\r
+            msg\r
+            action\r
+            server_connect\r
+            options\r
+            motd\r
+            notice\r
+            userlist\r
+            nick\r
+            join\r
+            topic\r
+            part\r
+            kick\r
+            quit\r
+            whois\r
+            syncchannel_redirect\r
+            debug\r
+    */\r
+    /**\r
+    *   Parses the response from the server\r
+    */\r
+    this.parse = function (item) {\r
+        console.log('gateway event', item);\r
+        if (item.event !== undefined) {\r
+            that.trigger('on' + item.event, item);\r
+\r
+            switch (item.event) {\r
+            case 'options':\r
+                $.each(item.options, function (name, value) {\r
+                    switch (name) {\r
+                    case 'CHANTYPES':\r
+                        // TODO: Check this. Why is it only getting the first char?\r
+                        that.set('channel_prefix', value.charAt(0));\r
+                        break;\r
+                    case 'NETWORK':\r
+                        that.set('name', value);\r
+                        break;\r
+                    case 'PREFIX':\r
+                        that.set('user_prefixes', value);\r
+                        break;\r
+                    }\r
+                });\r
+                break;\r
 \r
-        channel_names = ev.params.join(' ').split(',');\r
+            case 'connect':\r
+                that.set('nick', item.nick);\r
+                break;\r
 \r
-        $.each(channel_names, function (index, channel_name) {\r
-            // Trim any whitespace off the name\r
-            channel_name = channel_name.trim();\r
+            case 'nick':\r
+                if (item.nick === that.get('nick')) {\r
+                    that.set('nick', item.newnick);\r
+                }\r
+                break;\r
+            /*\r
+            case 'sync':\r
+                if (kiwi.gateway.onSync && kiwi.gateway.syncing) {\r
+                    kiwi.gateway.syncing = false;\r
+                    kiwi.gateway.onSync(item);\r
+                }\r
+                break;\r
+            */\r
 \r
-            // Check if we have the panel already. If not, create it\r
-            channel = that.panels.getByName(channel_name);\r
-            if (!channel) {\r
-                channel = new kiwi.model.Channel({name: channel_name});\r
-                kiwi.app.panels.add(channel);\r
+            case 'kiwi':\r
+                this.emit('kiwi.' + item.namespace, item.data);\r
+                break;\r
             }\r
-\r
-            kiwi.gateway.join(channel_name);\r
-        });\r
-\r
-        if (channel) channel.view.show();\r
-        \r
+        }\r
     };\r
 \r
-    this.queryCommand = function (ev) {\r
-        var destination, panel;\r
-\r
-        destination = ev.params[0];\r
+    /**\r
+    *   Sends data to the server\r
+    *   @private\r
+    *   @param  {Object}    data        The data to send\r
+    *   @param  {Function}  callback    A callback function\r
+    */\r
+    this.sendData = function (data, callback) {\r
+        this.socket.emit('message', {sid: this.session_id, data: JSON.stringify(data)}, callback);\r
+    };\r
 \r
-        // Check if we have the panel already. If not, create it\r
-        panel = that.panels.getByName(destination);\r
-        if (!panel) {\r
-            panel = new kiwi.model.Channel({name: destination});\r
-            kiwi.app.panels.add(panel);\r
-        }\r
+    /**\r
+    *   Sends a PRIVMSG message\r
+    *   @param  {String}    target      The target of the message (e.g. a channel or nick)\r
+    *   @param  {String}    msg         The message to send\r
+    *   @param  {Function}  callback    A callback function\r
+    */\r
+    this.privmsg = function (target, msg, callback) {\r
+        var data = {\r
+            method: 'privmsg',\r
+            args: {\r
+                target: target,\r
+                msg: msg\r
+            }\r
+        };\r
 \r
-        if (panel) panel.view.show();\r
-        \r
+        this.sendData(data, callback);\r
     };\r
 \r
-    this.msgCommand = function (ev) {\r
-        var destination = ev.params[0],\r
-            panel = that.panels.getByName(destination) || that.panels.server;\r
-\r
-        ev.params.shift();\r
+    /**\r
+    *   Sends a NOTICE message\r
+    *   @param  {String}    target      The target of the message (e.g. a channel or nick)\r
+    *   @param  {String}    msg         The message to send\r
+    *   @param  {Function}  callback    A callback function\r
+    */\r
+    this.notice = function (target, msg, callback) {\r
+        var data = {\r
+            method: 'notice',\r
+            args: {\r
+                target: target,\r
+                msg: msg\r
+            }\r
+        };\r
 \r
-        panel.addMsg(kiwi.gateway.get('nick'), ev.params.join(' '));\r
-        kiwi.gateway.privmsg(destination, ev.params.join(' '));\r
+        this.sendData(data, callback);\r
     };\r
 \r
-    this.actionCommand = function (ev) {\r
-        if (kiwi.app.panels.active === kiwi.app.panels.server) {\r
-            return;\r
-        }\r
+    /**\r
+    *   Sends a CTCP message\r
+    *   @param  {Boolean}   request     Indicates whether this is a CTCP request (true) or reply (false)\r
+    *   @param  {String}    type        The type of CTCP message, e.g. 'VERSION', 'TIME', 'PING' etc.\r
+    *   @param  {String}    target      The target of the message, e.g a channel or nick\r
+    *   @param  {String}    params      Additional paramaters\r
+    *   @param  {Function}  callback    A callback function\r
+    */\r
+    this.ctcp = function (request, type, target, params, callback) {\r
+        var data = {\r
+            method: 'ctcp',\r
+            args: {\r
+                request: request,\r
+                type: type,\r
+                target: target,\r
+                params: params\r
+            }\r
+        };\r
 \r
-        var panel = kiwi.app.panels.active;\r
-        panel.addMsg('', '* ' + kiwi.gateway.get('nick') + ' ' + ev.params.join(' '), 'action');\r
-        kiwi.gateway.action(panel.get('name'), ev.params.join(' '));\r
+        this.sendData(data, callback);\r
     };\r
 \r
-    this.partCommand = function (ev) {\r
-        if (ev.params.length === 0) {\r
-            kiwi.gateway.part(kiwi.app.panels.active.get('name'));\r
-        } else {\r
-            _.each(ev.params, function (channel) {\r
-                kiwi.gateway.part(channel);\r
-            });\r
-        }\r
-        // TODO: More responsive = close tab now, more accurate = leave until part event\r
-        //kiwi.app.panels.remove(kiwi.app.panels.active);\r
+    /**\r
+    *   @param  {String}    target      The target of the message (e.g. a channel or nick)\r
+    *   @param  {String}    msg         The message to send\r
+    *   @param  {Function}  callback    A callback function\r
+    */\r
+    this.action = function (target, msg, callback) {\r
+        this.ctcp(true, 'ACTION', target, msg, callback);\r
     };\r
 \r
-    this.topicCommand = function (ev) {\r
-        var channel_name;\r
-\r
-        if (ev.params.length === 0) return;\r
-\r
-        if (that.isChannelName(ev.params[0])) {\r
-            channel_name = ev.params[0];\r
-            ev.params.shift();\r
-        } else {\r
-            channel_name = kiwi.app.panels.active.get('name');\r
-        }\r
+    /**\r
+    *   Joins a channel\r
+    *   @param  {String}    channel     The channel to join\r
+    *   @param  {String}    key         The key to the channel\r
+    *   @param  {Function}  callback    A callback function\r
+    */\r
+    this.join = function (channel, key, callback) {\r
+        var data = {\r
+            method: 'join',\r
+            args: {\r
+                channel: channel,\r
+                key: key\r
+            }\r
+        };\r
 \r
-        kiwi.gateway.topic(channel_name, ev.params.join(' '));\r
+        this.sendData(data, callback);\r
     };\r
 \r
-    this.noticeCommand = function (ev) {\r
-        var destination;\r
-\r
-        // Make sure we have a destination and some sort of message\r
-        if (ev.params.length <= 1) return;\r
-\r
-        destination = ev.params[0];\r
-        ev.params.shift();\r
+    /**\r
+    *   Leaves a channel\r
+    *   @param  {String}    channel     The channel to part\r
+    *   @param  {Function}  callback    A callback function\r
+    */\r
+    this.part = function (channel, callback) {\r
+        var data = {\r
+            method: 'part',\r
+            args: {\r
+                channel: channel\r
+            }\r
+        };\r
 \r
-        kiwi.gateway.notice(destination, ev.params.join(' '));\r
+        this.sendData(data, callback);\r
     };\r
 \r
+    /**\r
+    *   Queries or modifies a channell topic\r
+    *   @param  {String}    channel     The channel to query or modify\r
+    *   @param  {String}    new_topic   The new topic to set\r
+    *   @param  {Function}  callback    A callback function\r
+    */\r
+    this.topic = function (channel, new_topic, callback) {\r
+        var data = {\r
+            method: 'topic',\r
+            args: {\r
+                channel: channel,\r
+                topic: new_topic\r
+            }\r
+        };\r
 \r
-\r
-\r
-\r
-    this.isChannelName = function (channel_name) {\r
-        var channel_prefix = kiwi.gateway.get('channel_prefix');\r
-\r
-        if (!channel_name || !channel_name.length) return false;\r
-        return (channel_prefix.indexOf(channel_name[0]) > -1);\r
+        this.sendData(data, callback);\r
     };\r
 \r
-})());
-kiwi.model.Gateway = Backbone.Model.extend(new (function () {\r
-    var that = this;\r
-\r
-    this.defaults = {\r
-        /**\r
-        *   The name of the network\r
-        *   @type    String\r
-        */\r
-        name: 'Server',\r
-\r
-        /**\r
-        *   The address (URL) of the network\r
-        *   @type    String\r
-        */\r
-        address: '',\r
-\r
-        /**\r
-        *   The current nickname\r
-        *   @type   String\r
-        */\r
-        nick: '',\r
+    /**\r
+    *   Kicks a user from a channel\r
+    *   @param  {String}    channel     The channel to kick the user from\r
+    *   @param  {String}    nick        The nick of the user to kick\r
+    *   @param  {String}    reason      The reason for kicking the user\r
+    *   @param  {Function}  callback    A callback function\r
+    */\r
+    this.kick = function (channel, nick, reason, callback) {\r
+        var data = {\r
+            method: 'kick',\r
+            args: {\r
+                channel: channel,\r
+                nick: nick,\r
+                reason: reason\r
+            }\r
+        };\r
 \r
-        /**\r
-        *   The channel prefix for this network\r
-        *   @type    String\r
-        */\r
-        channel_prefix: '#',\r
+        this.sendData(data, callback);\r
+    };\r
 \r
-        /**\r
-        *   The user prefixes for channel owner/admin/op/voice etc. on this network\r
-        *   @type   Array\r
-        */\r
-        user_prefixes: ['~', '&', '@', '+'],\r
+    /**\r
+    *   Disconnects us from the server\r
+    *   @param  {String}    msg         The quit message to send to the IRC server\r
+    *   @param  {Function}   callback    A callback function\r
+    */\r
+    this.quit = function (msg, callback) {\r
+        msg = msg || "";\r
+        var data = {\r
+            method: 'quit',\r
+            args: {\r
+                message: msg\r
+            }\r
+        };\r
 \r
-        /**\r
-        *   The URL to the Kiwi server\r
-        *   @type   String\r
-        */\r
-        //kiwi_server: '//kiwi'\r
-        kiwi_server: 'http://localhost:7778/kiwi'\r
+        this.sendData(data, callback);\r
     };\r
 \r
+    /**\r
+    *   Sends a string unmodified to the IRC server\r
+    *   @param  {String}    data        The data to send to the IRC server\r
+    *   @param  {Function}  callback    A callback function\r
+    */\r
+    this.raw = function (data, callback) {\r
+        data = {\r
+            method: 'raw',\r
+            args: {\r
+                data: data\r
+            }\r
+        };\r
 \r
-    this.initialize = function () {\r
-        // Update `that` with this new Model object\r
-        that = this;\r
-\r
-        // For ease of access. The socket.io object\r
-        this.socket = this.get('socket');\r
+        this.sendData(data, callback);\r
+    };\r
 \r
-        // Redundant perhaps? Legacy\r
-        this.session_id = '';\r
+    /**\r
+    *   Changes our nickname\r
+    *   @param  {String}    new_nick    Our new nickname\r
+    *   @param  {Function}  callback    A callback function\r
+    */\r
+    this.changeNick = function (new_nick, callback) {\r
+        var data = {\r
+            method: 'nick',\r
+            args: {\r
+                nick: new_nick\r
+            }\r
+        };\r
 \r
-        network = this;\r
+        this.sendData(data, callback);\r
     };\r
 \r
-\r
     /**\r
-    *   Connects to the server\r
-    *   @param  {String}    host        The hostname or IP address of the IRC server to connect to\r
-    *   @param  {Number}    port        The port of the IRC server to connect to\r
-    *   @param  {Boolean}   ssl         Whether or not to connect to the IRC server using SSL\r
-    *   @param  {String}    password    The password to supply to the IRC server during registration\r
-    *   @param  {Function}  callback    A callback function to be invoked once Kiwi's server has connected to the IRC server\r
+    *   Sends data to a fellow Kiwi IRC user\r
+    *   @param  {String}    target      The nick of the Kiwi IRC user to send to\r
+    *   @param  {String}    data        The data to send\r
+    *   @param  {Function}  callback    A callback function\r
     */\r
-    this.connect = function (host, port, ssl, password, callback) {\r
-        this.socket = io.connect(this.get('kiwi_server'), {\r
-            'try multiple transports': true,\r
-            'connect timeout': 3000,\r
-            'max reconnection attempts': 7,\r
-            'reconnection delay': 2000\r
-        });\r
-        this.socket.on('connect_failed', function (reason) {\r
-            // TODO: When does this even actually get fired? I can't find a case! ~Darren\r
-            console.debug('Unable to connect Socket.IO', reason);\r
-            console.log("kiwi.gateway.socket.on('connect_failed')");\r
-            //kiwi.front.tabviews.server.addMsg(null, ' ', 'Unable to connect to Kiwi IRC.\n' + reason, 'error');\r
-            this.socket.disconnect();\r
-            this.emit("connect_fail", {reason: reason});\r
-        });\r
+    this.kiwi = function (target, data, callback) {\r
+        data = {\r
+            method: 'kiwi',\r
+            args: {\r
+                target: target,\r
+                data: data\r
+            }\r
+        };\r
 \r
-        this.socket.on('error', function (e) {\r
-            this.emit("connect_fail", {reason: e});\r
-            console.log("kiwi.gateway.socket.on('error')", {reason: e});\r
-        });\r
+        this.sendData(data, callback);\r
+    };\r
+})());
+
+
+kiwi.model.Member = Backbone.Model.extend({\r
+    sortModes: function (modes) {\r
+        return modes.sort(function (a, b) {\r
+            var a_idx, b_idx, i;\r
+            var user_prefixes = kiwi.gateway.get('user_prefixes');\r
 \r
-        this.socket.on('connecting', function (transport_type) {\r
-            console.log("kiwi.gateway.socket.on('connecting')");\r
-            this.emit("connecting");\r
-            that.trigger("connecting");\r
+            for (i = 0; i < user_prefixes.length; i++) {\r
+                if (user_prefixes[i].mode === a) {\r
+                    a_idx = i;\r
+                }\r
+            }\r
+            for (i = 0; i < user_prefixes.length; i++) {\r
+                if (user_prefixes[i].mode === b) {\r
+                    b_idx = i;\r
+                }\r
+            }\r
+            if (a_idx < b_idx) {\r
+                return -1;\r
+            } else if (a_idx > b_idx) {\r
+                return 1;\r
+            } else {\r
+                return 0;\r
+            }\r
         });\r
+    },\r
+    initialize: function (attributes) {\r
+        var nick, modes, prefix;\r
+        nick = this.stripPrefix(this.get("nick"));\r
 \r
-        this.socket.on('connect', function () {\r
-            this.emit('irc connect', that.get('nick'), host, port, ssl, password, callback);\r
-            console.log("kiwi.gateway.socket.on('connect')");\r
+        modes = this.get("modes");\r
+        modes = modes || [];\r
+        this.sortModes(modes);\r
+        this.set({"nick": nick, "modes": modes, "prefix": this.getPrefix(modes)}, {silent: true});\r
+    },\r
+    addMode: function (mode) {\r
+        var modes_to_add = mode.split(''),\r
+            modes, prefix;\r
+\r
+        modes = this.get("modes");\r
+        $.each(modes_to_add, function (index, item) {\r
+            modes.push(item);\r
         });\r
+        \r
+        modes = this.sortModes(modes);\r
+        this.set({"prefix": this.getPrefix(modes), "modes": modes});\r
+    },\r
+    removeMode: function (mode) {\r
+        var modes_to_remove = mode.split(''),\r
+            modes, prefix;\r
 \r
-        this.socket.on('too_many_connections', function () {\r
-            this.emit("connect_fail", {reason: 'too_many_connections'});\r
+        modes = this.get("modes");\r
+        modes = _.reject(modes, function (m) {\r
+            return (modes_to_remove.indexOf(m) !== -1);\r
         });\r
 \r
-        this.socket.on('message', this.parse);\r
+        this.set({"prefix": this.getPrefix(modes), "modes": modes});\r
+    },\r
+    getPrefix: function (modes) {\r
+        var prefix = '';\r
+        var user_prefixes = kiwi.gateway.get('user_prefixes');\r
 \r
-        this.socket.on('disconnect', function () {\r
-            this.emit("disconnect", {});\r
-            console.log("kiwi.gateway.socket.on('disconnect')");\r
-        });\r
+        if (typeof modes[0] !== 'undefined') {\r
+            prefix = _.detect(user_prefixes, function (prefix) {\r
+                return prefix.mode === modes[0];\r
+            });\r
+            prefix = (prefix) ? prefix.symbol : '';\r
+        }\r
+        return prefix;\r
+    },\r
+    stripPrefix: function (nick) {\r
+        var tmp = nick, i, j, k;\r
+        var user_prefixes = kiwi.gateway.get('user_prefixes');\r
+        i = 0;\r
 \r
-        this.socket.on('close', function () {\r
-            console.log("kiwi.gateway.socket.on('close')");\r
-        });\r
+        for (j = 0; j < nick.length; j++) {\r
+            for (k = 0; k < user_prefixes.length; k++) {\r
+                if (nick.charAt(j) === user_prefixes[k].symbol) {\r
+                    i++;\r
+                    break;\r
+                }\r
+            }\r
+        }\r
 \r
-        this.socket.on('reconnecting', function (reconnectionDelay, reconnectionAttempts) {\r
-            console.log("kiwi.gateway.socket.on('reconnecting')");\r
-            this.emit("reconnecting", {delay: reconnectionDelay, attempts: reconnectionAttempts});\r
-        });\r
+        return tmp.substr(i);\r
+    },\r
+    displayNick: function (full) {\r
+        var display = this.get('nick');\r
 \r
-        this.socket.on('reconnect_failed', function () {\r
-            console.log("kiwi.gateway.socket.on('reconnect_failed')");\r
+        if (full) {\r
+            if (this.get("ident")) {\r
+                display += ' [' + this.get("ident") + '@' + this.get("hostname") + ']';\r
+            }\r
+        }\r
+\r
+        return display;\r
+    }\r
+});
+
+
+kiwi.model.MemberList = Backbone.Collection.extend({\r
+    model: kiwi.model.Member,\r
+    comparator: function (a, b) {\r
+        var i, a_modes, b_modes, a_idx, b_idx, a_nick, b_nick;\r
+        var user_prefixes = kiwi.gateway.get('user_prefixes');\r
+        a_modes = a.get("modes");\r
+        b_modes = b.get("modes");\r
+        // Try to sort by modes first\r
+        if (a_modes.length > 0) {\r
+            // a has modes, but b doesn't so a should appear first\r
+            if (b_modes.length === 0) {\r
+                return -1;\r
+            }\r
+            a_idx = b_idx = -1;\r
+            // Compare the first (highest) mode\r
+            for (i = 0; i < user_prefixes.length; i++) {\r
+                if (user_prefixes[i].mode === a_modes[0]) {\r
+                    a_idx = i;\r
+                }\r
+            }\r
+            for (i = 0; i < user_prefixes.length; i++) {\r
+                if (user_prefixes[i].mode === b_modes[0]) {\r
+                    b_idx = i;\r
+                }\r
+            }\r
+            if (a_idx < b_idx) {\r
+                return -1;\r
+            } else if (a_idx > b_idx) {\r
+                return 1;\r
+            }\r
+            // If we get to here both a and b have the same highest mode so have to resort to lexicographical sorting\r
+\r
+        } else if (b_modes.length > 0) {\r
+            // b has modes but a doesn't so b should appear first\r
+            return 1;\r
+        }\r
+        a_nick = a.get("nick").toLocaleUpperCase();\r
+        b_nick = b.get("nick").toLocaleUpperCase();\r
+        // Lexicographical sorting\r
+        if (a_nick < b_nick) {\r
+            return -1;\r
+        } else if (a_nick > b_nick) {\r
+            return 1;\r
+        } else {\r
+            // This should never happen; both users have the same nick.\r
+            console.log('Something\'s gone wrong somewhere - two users have the same nick!');\r
+            return 0;\r
+        }\r
+    },\r
+    initialize: function (options) {\r
+        this.view = new kiwi.view.MemberList({"model": this});\r
+    },\r
+    getByNick: function (nick) {\r
+        if (typeof nick !== 'string') return;\r
+        return this.find(function (m) {\r
+            return nick.toLowerCase() === m.get('nick').toLowerCase();\r
         });\r
-    };\r
+    }\r
+});
+
+
+kiwi.model.Panel = Backbone.Model.extend({\r
+    initialize: function (attributes) {\r
+        var name = this.get("name") || "";\r
+        this.view = new kiwi.view.Panel({"model": this, "name": name});\r
+        this.set({\r
+            "scrollback": [],\r
+            "name": name\r
+        }, {"silent": true});\r
+    },\r
 \r
+    addMsg: function (nick, msg, type, opts) {\r
+        var message_obj, bs, d;\r
 \r
-    /*\r
-        Events:\r
-            msg\r
-            action\r
-            server_connect\r
-            options\r
-            motd\r
-            notice\r
-            userlist\r
-            nick\r
-            join\r
-            topic\r
-            part\r
-            kick\r
-            quit\r
-            whois\r
-            syncchannel_redirect\r
-            debug\r
-    */\r
-    /**\r
-    *   Parses the response from the server\r
-    */\r
-    this.parse = function (item) {\r
-        console.log('gateway event', item);\r
-        if (item.event !== undefined) {\r
-            that.trigger('on' + item.event, item);\r
+        opts = opts || {};\r
 \r
-            switch (item.event) {\r
-            case 'options':\r
-                $.each(item.options, function (name, value) {\r
-                    switch (name) {\r
-                    case 'CHANTYPES':\r
-                        // TODO: Check this. Why is it only getting the first char?\r
-                        that.set('channel_prefix', value.charAt(0));\r
-                        break;\r
-                    case 'NETWORK':\r
-                        that.set('name', value);\r
-                        break;\r
-                    case 'PREFIX':\r
-                        that.set('user_prefixes', value);\r
-                        break;\r
-                    }\r
-                });\r
-                break;\r
+        // Time defaults to now\r
+        if (!opts || typeof opts.time === 'undefined') {\r
+            d = new Date();\r
+            opts.time = d.getHours().toString().lpad(2, "0") + ":" + d.getMinutes().toString().lpad(2, "0") + ":" + d.getSeconds().toString().lpad(2, "0");\r
+        }\r
 \r
-            case 'connect':\r
-                that.set('nick', item.nick);\r
-                break;\r
+        // CSS style defaults to empty string\r
+        if (!opts || typeof opts.style === 'undefined') {\r
+            opts.style = '';\r
+        }\r
 \r
-            case 'nick':\r
-                if (item.nick === that.get('nick')) {\r
-                    that.set('nick', item.newnick);\r
-                }\r
-                break;\r
-            /*\r
-            case 'sync':\r
-                if (kiwi.gateway.onSync && kiwi.gateway.syncing) {\r
-                    kiwi.gateway.syncing = false;\r
-                    kiwi.gateway.onSync(item);\r
-                }\r
-                break;\r
-            */\r
+        // Escape any HTML that may be in here\r
+        // This doesn't seem right to be here.. should be in view (??)\r
+        msg =  $('<div />').text(msg).html();\r
 \r
-            case 'kiwi':\r
-                this.emit('kiwi.' + item.namespace, item.data);\r
-                break;\r
-            }\r
+        // Run through the plugins\r
+        message_obj = {"msg": msg, "time": opts.time, "nick": nick, "chan": this.get("name"), "type": type, "style": opts.style};\r
+        //tmp = kiwi.plugs.run('addmsg', message_obj);\r
+        if (!message_obj) {\r
+            return;\r
         }\r
-    };\r
 \r
-    /**\r
-    *   Sends data to the server\r
-    *   @private\r
-    *   @param  {Object}    data        The data to send\r
-    *   @param  {Function}  callback    A callback function\r
-    */\r
-    this.sendData = function (data, callback) {\r
-        this.socket.emit('message', {sid: this.session_id, data: JSON.stringify(data)}, callback);\r
-    };\r
+        // The CSS class (action, topic, notice, etc)\r
+        if (typeof message_obj.type !== "string") {\r
+            message_obj.type = '';\r
+        }\r
 \r
-    /**\r
-    *   Sends a PRIVMSG message\r
-    *   @param  {String}    target      The target of the message (e.g. a channel or nick)\r
-    *   @param  {String}    msg         The message to send\r
-    *   @param  {Function}  callback    A callback function\r
-    */\r
-    this.privmsg = function (target, msg, callback) {\r
-        var data = {\r
-            method: 'privmsg',\r
-            args: {\r
-                target: target,\r
-                msg: msg\r
-            }\r
-        };\r
+        // Make sure we don't have NaN or something\r
+        if (typeof message_obj.msg !== "string") {\r
+            message_obj.msg = '';\r
+        }\r
 \r
-        this.sendData(data, callback);\r
-    };\r
+        // Convert IRC formatting into HTML formatting\r
+        message_obj.msg = formatIRCMsg(message_obj.msg);\r
 \r
-    /**\r
-    *   Sends a NOTICE message\r
-    *   @param  {String}    target      The target of the message (e.g. a channel or nick)\r
-    *   @param  {String}    msg         The message to send\r
-    *   @param  {Function}  callback    A callback function\r
-    */\r
-    this.notice = function (target, msg, callback) {\r
-        var data = {\r
-            method: 'notice',\r
-            args: {\r
-                target: target,\r
-                msg: msg\r
-            }\r
-        };\r
+        // Update the scrollback\r
+        bs = this.get("scrollback");\r
+        bs.push(message_obj);\r
 \r
-        this.sendData(data, callback);\r
-    };\r
+        // Keep the scrolback limited\r
+        if (bs.length > 250) {\r
+            bs.splice(250);\r
+        }\r
+        this.set({"scrollback": bs}, {silent: true});\r
 \r
-    /**\r
-    *   Sends a CTCP message\r
-    *   @param  {Boolean}   request     Indicates whether this is a CTCP request (true) or reply (false)\r
-    *   @param  {String}    type        The type of CTCP message, e.g. 'VERSION', 'TIME', 'PING' etc.\r
-    *   @param  {String}    target      The target of the message, e.g a channel or nick\r
-    *   @param  {String}    params      Additional paramaters\r
-    *   @param  {Function}  callback    A callback function\r
-    */\r
-    this.ctcp = function (request, type, target, params, callback) {\r
-        var data = {\r
-            method: 'ctcp',\r
-            args: {\r
-                request: request,\r
-                type: type,\r
-                target: target,\r
-                params: params\r
-            }\r
-        };\r
+        this.trigger("msg", message_obj);\r
+    },\r
 \r
-        this.sendData(data, callback);\r
-    };\r
+    close: function () {\r
+        this.view.remove();\r
+        delete this.view;\r
 \r
-    /**\r
-    *   @param  {String}    target      The target of the message (e.g. a channel or nick)\r
-    *   @param  {String}    msg         The message to send\r
-    *   @param  {Function}  callback    A callback function\r
-    */\r
-    this.action = function (target, msg, callback) {\r
-        this.ctcp(true, 'ACTION', target, msg, callback);\r
-    };\r
+        var members = this.get('members');\r
+        if (members) {\r
+            members.reset([]);\r
+            this.unset('members');\r
+        }\r
 \r
-    /**\r
-    *   Joins a channel\r
-    *   @param  {String}    channel     The channel to join\r
-    *   @param  {String}    key         The key to the channel\r
-    *   @param  {Function}  callback    A callback function\r
-    */\r
-    this.join = function (channel, key, callback) {\r
-        var data = {\r
-            method: 'join',\r
-            args: {\r
-                channel: channel,\r
-                key: key\r
-            }\r
-        };\r
+        this.destroy();\r
 \r
-        this.sendData(data, callback);\r
-    };\r
+        // If closing the active panel, switch to the server panel\r
+        if (this.cid === kiwi.app.panels.active.cid) {\r
+            kiwi.app.panels.server.view.show();\r
+        }\r
+    },\r
 \r
-    /**\r
-    *   Leaves a channel\r
-    *   @param  {String}    channel     The channel to part\r
-    *   @param  {Function}  callback    A callback function\r
-    */\r
-    this.part = function (channel, callback) {\r
-        var data = {\r
-            method: 'part',\r
-            args: {\r
-                channel: channel\r
-            }\r
-        };\r
+    isChannel: function () {\r
+        var channel_prefix = kiwi.gateway.get('channel_prefix'),\r
+            this_name = this.get('name');\r
 \r
-        this.sendData(data, callback);\r
-    };\r
+        if (!this_name) return false;\r
+        return (channel_prefix.indexOf(this_name[0]) > -1);\r
+    }\r
+});
+
+
+kiwi.model.PanelList = Backbone.Collection.extend({\r
+    model: kiwi.model.Panel,\r
 \r
-    /**\r
-    *   Queries or modifies a channell topic\r
-    *   @param  {String}    channel     The channel to query or modify\r
-    *   @param  {String}    new_topic   The new topic to set\r
-    *   @param  {Function}  callback    A callback function\r
-    */\r
-    this.topic = function (channel, new_topic, callback) {\r
-        var data = {\r
-            method: 'topic',\r
-            args: {\r
-                channel: channel,\r
-                topic: new_topic\r
-            }\r
-        };\r
+    // Holds the active panel\r
+    active: null,\r
 \r
-        this.sendData(data, callback);\r
-    };\r
+    comparator: function (chan) {\r
+        return chan.get("name");\r
+    },\r
+    initialize: function () {\r
+        this.view = new kiwi.view.Tabs({"el": $('#toolbar .panellist')[0], "model": this});\r
 \r
-    /**\r
-    *   Kicks a user from a channel\r
-    *   @param  {String}    channel     The channel to kick the user from\r
-    *   @param  {String}    nick        The nick of the user to kick\r
-    *   @param  {String}    reason      The reason for kicking the user\r
-    *   @param  {Function}  callback    A callback function\r
-    */\r
-    this.kick = function (channel, nick, reason, callback) {\r
-        var data = {\r
-            method: 'kick',\r
-            args: {\r
-                channel: channel,\r
-                nick: nick,\r
-                reason: reason\r
-            }\r
-        };\r
+        // Automatically create a server tab\r
+        this.server = new kiwi.model.Server({'name': kiwi.gateway.get('name')});\r
+        kiwi.gateway.on('change:name', this.view.render, this.view);\r
+        this.add(this.server);\r
 \r
-        this.sendData(data, callback);\r
-    };\r
+        this.bind('active', function (active_panel) {\r
+            this.active = active_panel;\r
+        }, this);\r
 \r
-    /**\r
-    *   Disconnects us from the server\r
-    *   @param  {String}    msg         The quit message to send to the IRC server\r
-    *   @param  {Function}   callback    A callback function\r
-    */\r
-    this.quit = function (msg, callback) {\r
-        msg = msg || "";\r
-        var data = {\r
-            method: 'quit',\r
-            args: {\r
-                message: msg\r
-            }\r
-        };\r
+    },\r
+    getByName: function (name) {\r
+        if (typeof name !== 'string') return;\r
+        return this.find(function (c) {\r
+            return name.toLowerCase() === c.get('name').toLowerCase();\r
+        });\r
+    }\r
+});
+
+
+// TODO: Channel modes\r
+// TODO: Listen to gateway events for anythign related to this channel\r
+kiwi.model.Channel = kiwi.model.Panel.extend({\r
+    initialize: function (attributes) {\r
+        var name = this.get("name") || "",\r
+            members;\r
 \r
-        this.sendData(data, callback);\r
-    };\r
+        this.view = new kiwi.view.Channel({"model": this, "name": name});\r
+        this.set({\r
+            "members": new kiwi.model.MemberList(),\r
+            "name": name,\r
+            "scrollback": [],\r
+            "topic": ""\r
+        }, {"silent": true});\r
 \r
-    /**\r
-    *   Sends a string unmodified to the IRC server\r
-    *   @param  {String}    data        The data to send to the IRC server\r
-    *   @param  {Function}  callback    A callback function\r
-    */\r
-    this.raw = function (data, callback) {\r
-        data = {\r
-            method: 'raw',\r
-            args: {\r
-                data: data\r
-            }\r
-        };\r
+        members = this.get("members");\r
+        members.bind("add", function (member) {\r
+            this.addMsg(' ', '--> ' + member.displayNick(true) + ' has joined', 'action join');\r
+        }, this);\r
 \r
-        this.sendData(data, callback);\r
-    };\r
+        members.bind("remove", function (member, members, options) {\r
+            var msg = (options.message) ? '(' + options.message + ')' : '';\r
 \r
-    /**\r
-    *   Changes our nickname\r
-    *   @param  {String}    new_nick    Our new nickname\r
-    *   @param  {Function}  callback    A callback function\r
-    */\r
-    this.changeNick = function (new_nick, callback) {\r
-        var data = {\r
-            method: 'nick',\r
-            args: {\r
-                nick: new_nick\r
+            if (options.type === 'quit') {\r
+                this.addMsg(' ', '<-- ' + member.displayNick(true) + ' has quit ' + msg, 'action quit');\r
+            } else {\r
+                this.addMsg(' ', '<-- ' + member.displayNick(true) + ' has left ' + msg, 'action part');\r
             }\r
-        };\r
+        }, this);\r
+    }\r
+});
+
+
+kiwi.model.Server = kiwi.model.Panel.extend({\r
+    server_login: null,\r
 \r
-        this.sendData(data, callback);\r
-    };\r
+    initialize: function (attributes) {\r
+        var name = "Server";\r
+        this.view = new kiwi.view.Panel({"model": this, "name": name});\r
+        this.set({\r
+            "scrollback": [],\r
+            "name": name\r
+        }, {"silent": true});\r
 \r
-    /**\r
-    *   Sends data to a fellow Kiwi IRC user\r
-    *   @param  {String}    target      The nick of the Kiwi IRC user to send to\r
-    *   @param  {String}    data        The data to send\r
-    *   @param  {Function}  callback    A callback function\r
-    */\r
-    this.kiwi = function (target, data, callback) {\r
-        data = {\r
-            method: 'kiwi',\r
-            args: {\r
-                target: target,\r
-                data: data\r
-            }\r
-        };\r
+        //this.addMsg(' ', '--> Kiwi IRC: Such an awesome IRC client', '', {style: 'color:#009900;'});\r
 \r
-        this.sendData(data, callback);\r
-    };\r
-})());
+        this.server_login = new kiwi.view.ServerSelect();\r
+        \r
+        this.view.$el.append(this.server_login.$el);\r
+        this.server_login.show();\r
+    }\r
+});
+
+
+/*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 = '<b>';
+        while (msg.indexOf(String.fromCharCode(2)) !== -1) {
+            msg = msg.replace(String.fromCharCode(2), next);
+            next = (next === '<b>') ? '</b>' : '<b>';
+        }
+        if (next === '</b>') {
+            msg = msg + '</b>';
+        }
+    }
+
+    // underline
+    if (msg.indexOf(String.fromCharCode(31)) !== -1) {
+        next = '<u>';
+        while (msg.indexOf(String.fromCharCode(31)) !== -1) {
+            msg = msg.replace(String.fromCharCode(31), next);
+            next = (next === '<u>') ? '</u>' : '<u>';
+        }
+        if (next === '</u>') {
+            msg = msg + '</u>';
+        }
+    }
+
+    // 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 += '<span style="' + ((fg !== null) ? 'color: ' + fg + '; ' : '') + ((bg !== null) ? 'background-color: ' + bg + ';' : '') + '">' + str + '</span>';
+                    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 = '<img class="link_img_a" src="' + url + '" height="100%" width="100%" />';
+                return '<a class="link_ext link_img" target="_blank" rel="nofollow" href="' + url + '" style="height:50px;width:50px;display:block">' + img + '<div class="tt box"></div></a>';
+            });
+
+            return event;
+        }
+    },
+
+    {
+        name: "html_safe",
+        onaddmsg: function (event, opts) {
+            event.msg = $('<div/>').text(event.msg).html();
+            event.nick = $('<div/>').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 = '<span style="color:red;">' + event.msg + '</span>';
+            //    }
+            //}
+
+            // 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 '<a class="link_ext" target="_blank" rel="nofollow" href="' + url + '">' + nice + '<div class="tt box"></div></a>';
+                return '<a class="link_ext" target="_blank" rel="nofollow" href="' + url + '">' + nice + '</a>';
+            });
+
+            return event;
+        }
+    },
+
+    {
+        name: "lftobr",
+        onaddmsg: function (event, opts) {
+            if (!event.msg) {
+                return event;
+            }
+
+            event.msg = event.msg.replace(/\n/gi, function (txt) {
+                return '<br/>';
+            });
+
+            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 = $('<a class="link_ext_browser">Open in Kiwi..</a>');
+                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 = $('<iframe border="0" class="utility_view" src="" style="width:100%;height:100%;border:none;"></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 = '<span style="color:' + c + ';">' + event.nick + '</span>';
+
+            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 */\r
 /*global kiwi */\r
 \r
@@ -2164,11 +2184,10 @@ kiwi.view.ControlBox = Backbone.View.extend({
     },\r
 \r
     initialize: function () {\r
-        var cb = this; // TODO: Why is `that` not recognised in the below closure?\r
-        that = this;\r
+        var that = this;\r
 \r
         kiwi.gateway.bind('change:nick', function () {\r
-            $('.nick', cb.$el).text(this.get('nick'));\r
+            $('.nick', that.$el).text(this.get('nick'));\r
         });\r
     },\r
 \r
@@ -2306,4 +2325,8 @@ kiwi.view.Application = Backbone.View.extend({
             this.doLayout();\r
         }\r
     }\r
-});
\ No newline at end of file
+});
+
+
+
+})(window);
\ No newline at end of file
index f8bfca3c3164b931db63a520bf087fbb8fd14989..01182134500a4d1329ce7e2c51df63e98e3787e6 100644 (file)
@@ -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<a;d++)e=Math.floor(Math.random()*b.length),c+=b.substring(e,e+1);return c}function secondsToTime(a){var b,c,d,e,f,g;return b=Math.floor(a/3600),e=a%3600,c=Math.floor(e/60),f=e%60,d=Math.ceil(f),g={h:b,m:c,s:d},g}function formatIRCMsg(a){var b,c;if(!a||typeof a!="string")return"";if(a.indexOf(String.fromCharCode(2))!==-1){c="<b>";while(a.indexOf(String.fromCharCode(2))!==-1)a=a.replace(String.fromCharCode(2),c),c=c==="<b>"?"</b>":"<b>";c==="</b>"&&(a+="</b>")}if(a.indexOf(String.fromCharCode(31))!==-1){c="<u>";while(a.indexOf(String.fromCharCode(31))!==-1)a=a.replace(String.fromCharCode(31),c),c=c==="<u>"?"</u>":"<u>";c==="</u>"&&(a+="</u>")}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("\ 3")!==-1){e=a.indexOf("\ 3"),b=a.substr(0,e);while(e<a.length)f=c(a.substr(e,6)),f?(g=a.indexOf("\ 3",e+1),h=a.indexOf(String.fromCharCode(15),e+1),h!==-1&&(g===-1?g=h:g=g<h?g:h),g===-1&&(g=a.length),i=d(f[1]),j=d(f[3]),k=a.substring(e+1+f[1].length+(j!==null?f[2].length+1:0),g),b+='<span style="'+(i!==null?"color: "+i+"; ":"")+(j!==null?"background-color: "+j+";":"")+'">'+k+"</span>",e=g):(a[e]!=="\ 3"&&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<a;d++)c+=b;return(c+this).slice(-a)});var plugins=[{name:"images",onaddmsg:function(a,b){return a.msg?(a.msg=a.msg.replace(/^((https?\:\/\/|ftp\:\/\/)|(www\.))(\S+)(\w{2,4})(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?(\.jpg|\.jpeg|\.gif|\.bmp|\.png)$/gi,function(b){a.event_bubbles=!1;var c='<img class="link_img_a" src="'+b+'" height="100%" width="100%" />';return'<a class="link_ext link_img" target="_blank" rel="nofollow" href="'+b+'" style="height:50px;width:50px;display:block">'+c+'<div class="tt box"></div></a>'}),a):a}},{name:"html_safe",onaddmsg:function(a,b){return a.msg=$("<div/>").text(a.msg).html(),a.nick=$("<div/>").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,'<a class="link_ext" target="_blank" rel="nofollow" href="'+a+'">'+b+"</a>")}),a):a}},{name:"lftobr",onaddmsg:function(a,b){return a.msg?(a.msg=a.msg.replace(/\n/gi,function(a){return"<br/>"}),a):a}},{name:"nick_colour",onaddmsg:function(a,b){if(!a.msg)return a;var c=this.randColour();return a.nick='<span style="color:'+c+';">'+a.nick+"</span>",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;c<j.length;c++)j[c].mode===d[0]&&(f=c);for(c=0;c<j.length;c++)j[c].mode===e[0]&&(g=c);if(f<g)return-1;if(f>g)return 1}else if(e.length>0)return 1;return h=a.get("nick").toLocaleUpperCase(),i=b.get("nick").toLocaleUpperCase(),h<i?-1:h>i?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;e<f.length;e++)f[e].mode===a&&(c=e);for(e=0;e<f.length;e++)f[e].mode===b&&(d=e);return c<d?-1:c>d?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<a.length;d++)for(e=0;e<f.length;e++)if(a.charAt(d)===f[e].symbol){c++;break}return b.substr(c)},displayNick:function(a){var b=this.get("nick");return a&&this.get("ident")&&(b+=" ["+this.get("ident")+"@"+this.get("hostname")+"]"),b}}),kiwi.model.PanelList=Backbone.Collection.extend({model:kiwi.model.Panel,active:null,comparator:function(a){return a.get("name")},initialize:function(){this.view=new kiwi.view.Tabs({el:$("#toolbar .panellist")[0],model:this}),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(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()})}}),kiwi.model.Panel=Backbone.Model.extend({initialize:function(a){var b=this.get("name")||"";this.view=new kiwi.view.Panel({model:this,name:b}),this.set({scrollback:[],name:b},{silent:!0})},addMsg:function(a,b,c,d){var e,f,g;d=d||{};if(!d||typeof d.time=="undefined")g=new Date,d.time=g.getHours().toString().lpad(2,"0")+":"+g.getMinutes().toString().lpad(2,"0")+":"+g.getSeconds().toString().lpad(2,"0");if(!d||typeof d.style=="undefined")d.style="";b=$("<div />").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){$('<li><a class="nick"><span class="prefix">'+b.get("prefix")+"</span>"+b.get("nick")+"</a></li>").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 class="chan">'+a+"</a>"}),c='<div class="msg <%= type %>"><div class="time"><%- time %></div><div class="nick"><%- nick %></div><div class="text" style="<%= style %>"><%= msg %> </div></div>',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(),$("<li><span>"+kiwi.gateway.get("name")+"</span></li>").data("panel_id",this.model.server.cid).appendTo($this),this.model.forEach(function(b){if(b==a.model.server)return;$("<li><span>"+b.get("name")+"</span></li>").data("panel_id",b.cid).appendTo($this)})},panelAdded:function(a){a.tab=$("<li><span>"+a.get("name")+"</span></li>"),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('<img src="img/redcross.png" />')},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<this.buffer.length&&(this.buffer_pos++,b.val(this.buffer[this.buffer_pos]))}},processInput:function(a){var b,c=a.split(" ");c[0][0]==="/"?(b=c[0].substr(1).toLowerCase(),c=c.splice(1)):(b="msg",c.unshift(kiwi.app.panels.active.get("name"))),this.trigger("command",{command:b,params:c}),this.trigger("command_"+b,{command:b,params:c}),this._callbacks["command_"+b]||this.trigger("unknown_command",{command:b,params:c})}}),kiwi.view.Application=Backbone.View.extend({initialize:function(){$(window).resize(this.doLayout),$("#toolbar").resize(this.doLayout),$("#controlbox").resize(this.doLayout),this.doLayout(),$(window).keydown(this.setKeyFocus)},setKeyFocus:function(a){if(a.ctrlKey||a.altKey)return;if(a.srcElement.tagName.toLowerCase()==="input")return;$("#controlbox .inp").focus()},doLayout:function(){var a=$("#panels"),b=$("#memberlists"),c=$("#toolbar"),d=$("#controlbox"),e={top:c.outerHeight(!0),bottom:d.outerHeight(!0)};a.css(e),b.css(e)},barsHide:function(a){var b=this;a?($("#toolbar").slideUp(0),$("#controlbox").slideUp(0)):($("#toolbar").slideUp(),$("#controlbox").slideUp(function(){b.doLayout()}))},barsShow:function(a){var b=this;a?($("#toolbar").slideDown(0),$("#controlbox").slideDown(0),this.doLayout()):($("#toolbar").slideDown(),$("#controlbox").slideDown(function(){b.doLayout()}))}})
\ No newline at end of file
+(function(a){function c(b){var c,d;a.console?(d=a.console.log,a.console.log=function(){b&&d.apply(console,arguments)}):(c=a.opera?a.opera.postError:alert,a.console={},a.console.log=function(a){b&&c(a)})}function d(a){var b="0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz",c="",d,e;for(d=0;d<a;d++)e=Math.floor(Math.random()*b.length),c+=b.substring(e,e+1);return c}function e(a){var b,c,d,e,f,g;return b=Math.floor(a/3600),e=a%3600,c=Math.floor(e/60),f=e%60,d=Math.ceil(f),g={h:b,m:c,s:d},g}function f(a){var b,c;if(!a||typeof a!="string")return"";if(a.indexOf(String.fromCharCode(2))!==-1){c="<b>";while(a.indexOf(String.fromCharCode(2))!==-1)a=a.replace(String.fromCharCode(2),c),c=c==="<b>"?"</b>":"<b>";c==="</b>"&&(a+="</b>")}if(a.indexOf(String.fromCharCode(31))!==-1){c="<u>";while(a.indexOf(String.fromCharCode(31))!==-1)a=a.replace(String.fromCharCode(31),c),c=c==="<u>"?"</u>":"<u>";c==="</u>"&&(a+="</u>")}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("\ 3")!==-1){e=a.indexOf("\ 3"),b=a.substr(0,e);while(e<a.length)f=c(a.substr(e,6)),f?(g=a.indexOf("\ 3",e+1),h=a.indexOf(String.fromCharCode(15),e+1),h!==-1&&(g===-1?g=h:g=g<h?g:h),g===-1&&(g=a.length),i=d(f[1]),j=d(f[3]),k=a.substring(e+1+f[1].length+(j!==null?f[2].length+1:0),g),b+='<span style="'+(i!==null?"color: "+i+"; ":"")+(j!==null?"background-color: "+j+";":"")+'">'+k+"</span>",e=g):(a[e]!=="\ 3"&&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;f<g.length;f++)g[f].mode===a&&(d=f);for(f=0;f<g.length;f++)g[f].mode===c&&(e=f);return d<e?-1:d>e?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;e<a.length;e++)for(f=0;f<g.length;f++)if(a.charAt(e)===g[f].symbol){d++;break}return c.substr(d)},displayNick:function(a){var b=this.get("nick");return a&&this.get("ident")&&(b+=" ["+this.get("ident")+"@"+this.get("hostname")+"]"),b}}),b.model.MemberList=Backbone.Collection.extend({model:b.model.Member,comparator:function(a,c){var d,e,f,g,h,i,j,k=b.gateway.get("user_prefixes");e=a.get("modes"),f=c.get("modes");if(e.length>0){if(f.length===0)return-1;g=h=-1;for(d=0;d<k.length;d++)k[d].mode===e[0]&&(g=d);for(d=0;d<k.length;d++)k[d].mode===f[0]&&(h=d);if(g<h)return-1;if(g>h)return 1}else if(f.length>0)return 1;return i=a.get("nick").toLocaleUpperCase(),j=c.get("nick").toLocaleUpperCase(),i<j?-1:i>j?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=$("<div />").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<a;d++)c+=b;return(c+this).slice(-a)});var g=[{name:"images",onaddmsg:function(a,b){return a.msg?(a.msg=a.msg.replace(/^((https?\:\/\/|ftp\:\/\/)|(www\.))(\S+)(\w{2,4})(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?(\.jpg|\.jpeg|\.gif|\.bmp|\.png)$/gi,function(b){a.event_bubbles=!1;var c='<img class="link_img_a" src="'+b+'" height="100%" width="100%" />';return'<a class="link_ext link_img" target="_blank" rel="nofollow" href="'+b+'" style="height:50px;width:50px;display:block">'+c+'<div class="tt box"></div></a>'}),a):a}},{name:"html_safe",onaddmsg:function(a,b){return a.msg=$("<div/>").text(a.msg).html(),a.nick=$("<div/>").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,'<a class="link_ext" target="_blank" rel="nofollow" href="'+a+'">'+b+"</a>")}),a):a}},{name:"lftobr",onaddmsg:function(a,b){return a.msg?(a.msg=a.msg.replace(/\n/gi,function(a){return"<br/>"}),a):a}},{name:"nick_colour",onaddmsg:function(a,b){if(!a.msg)return a;var c=this.randColour();return a.nick='<span style="color:'+c+';">'+a.nick+"</span>",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){$('<li><a class="nick"><span class="prefix">'+b.get("prefix")+"</span>"+b.get("nick")+"</a></li>").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 class="chan">'+a+"</a>"}),d='<div class="msg <%= type %>"><div class="time"><%- time %></div><div class="nick"><%- nick %></div><div class="text" style="<%= style %>"><%= msg %> </div></div>',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(),$("<li><span>"+b.gateway.get("name")+"</span></li>").data("panel_id",this.model.server.cid).appendTo($this),this.model.forEach(function(b){if(b==a.model.server)return;$("<li><span>"+b.get("name")+"</span></li>").data("panel_id",b.cid).appendTo($this)})},panelAdded:function(a){a.tab=$("<li><span>"+a.get("name")+"</span></li>"),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('<img src="img/redcross.png" />')},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<this.buffer.length&&(this.buffer_pos++,b.val(this.buffer[this.buffer_pos]))}},processInput:function(a){var c,d=a.split(" ");d[0][0]==="/"?(c=d[0].substr(1).toLowerCase(),d=d.splice(1)):(c="msg",d.unshift(b.app.panels.active.get("name"))),this.trigger("command",{command:c,params:d}),this.trigger("command_"+c,{command:c,params:d}),this._callbacks["command_"+c]||this.trigger("unknown_command",{command:c,params:d})}}),b.view.Application=Backbone.View.extend({initialize:function(){$(a).resize(this.doLayout),$("#toolbar").resize(this.doLayout),$("#controlbox").resize(this.doLayout),this.doLayout(),$(a).keydown(this.setKeyFocus)},setKeyFocus:function(a){if(a.ctrlKey||a.altKey)return;if(a.srcElement.tagName.toLowerCase()==="input")return;$("#controlbox .inp").focus()},doLayout:function(){var a=$("#panels"),b=$("#memberlists"),c=$("#toolbar"),d=$("#controlbox"),e={top:c.outerHeight(!0),bottom:d.outerHeight(!0)};a.css(e),b.css(e)},barsHide:function(a){var b=this;a?($("#toolbar").slideUp(0),$("#controlbox").slideUp(0)):($("#toolbar").slideUp(),$("#controlbox").slideUp(function(){b.doLayout()}))},barsShow:function(a){var b=this;a?($("#toolbar").slideDown(0),$("#controlbox").slideDown(0),this.doLayout()):($("#toolbar").slideDown(),$("#controlbox").slideDown(function(){b.doLayout()}))}})})(window)
\ No newline at end of file