Tabview completely removed
authorJack Allnutt <m2ys4u@Gmail.com>
Sun, 29 Jan 2012 22:45:51 +0000 (22:45 +0000)
committerJack Allnutt <m2ys4u@Gmail.com>
Sun, 29 Jan 2012 22:45:51 +0000 (22:45 +0000)
Lots of things now broken, though, however channels/member lists are stored in the model.

Joining channels works, and can see messages recieved. Need to send using /msg and own messages don't display.

However: progress!

client/index.html.jade
client/js/backbone-git.js [new file with mode: 0755]
client/js/front.events.js
client/js/front.js
client/js/front.ui.js [changed mode: 0644->0755]
client/js/model.js
client/js/view.js
server/app.js

index d4c3f255d60188a2b5abeab699dd1436cda07787..eb96042b84b73099e61ac93e427e0385c48eff9a 100755 (executable)
@@ -110,7 +110,7 @@ html(lang="en-gb")
         - if (debug)
             script(type="text/javascript", src="/js/underscore.min.js")
             script(type="text/javascript", src="/js/util.js")
-            script(type="text/javascript", src="/js/backbone-0.5.3-min.js");
+            script(type="text/javascript", src="/js/backbone-git.js");
             script(type="text/javascript", src="/js/gateway.js")
             script(type="text/javascript", src="/js/model.js")
             script(type="text/javascript", src="/js/view.js")
diff --git a/client/js/backbone-git.js b/client/js/backbone-git.js
new file mode 100755 (executable)
index 0000000..7c32137
--- /dev/null
@@ -0,0 +1,1260 @@
+//     Backbone.js 0.5.3\r
+//     (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.\r
+//     Backbone may be freely distributed under the MIT license.\r
+//     For all details and documentation:\r
+//     http://backbonejs.org\r
+\r
+(function(){\r
+\r
+  // Initial Setup\r
+  // -------------\r
+\r
+  // Save a reference to the global object (`window` in the browser, `global`\r
+  // on the server).\r
+  var root = this;\r
+\r
+  // Save the previous value of the `Backbone` variable, so that it can be\r
+  // restored later on, if `noConflict` is used.\r
+  var previousBackbone = root.Backbone;\r
+\r
+  // Create a local reference to slice/splice.\r
+  var slice = Array.prototype.slice;\r
+  var splice = Array.prototype.splice;\r
+\r
+  // The top-level namespace. All public Backbone classes and modules will\r
+  // be attached to this. Exported for both CommonJS and the browser.\r
+  var Backbone;\r
+  if (typeof exports !== 'undefined') {\r
+    Backbone = exports;\r
+  } else {\r
+    Backbone = root.Backbone = {};\r
+  }\r
+\r
+  // Current version of the library. Keep in sync with `package.json`.\r
+  Backbone.VERSION = '0.5.3';\r
+\r
+  // Require Underscore, if we're on the server, and it's not already present.\r
+  var _ = root._;\r
+  if (!_ && (typeof require !== 'undefined')) _ = require('underscore');\r
+\r
+  // For Backbone's purposes, jQuery, Zepto, or Ender owns the `$` variable.\r
+  var $ = root.jQuery || root.Zepto || root.ender;\r
+\r
+  // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable\r
+  // to its previous owner. Returns a reference to this Backbone object.\r
+  Backbone.noConflict = function() {\r
+    root.Backbone = previousBackbone;\r
+    return this;\r
+  };\r
+\r
+  // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option\r
+  // will fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and\r
+  // set a `X-Http-Method-Override` header.\r
+  Backbone.emulateHTTP = false;\r
+\r
+  // Turn on `emulateJSON` to support legacy servers that can't deal with direct\r
+  // `application/json` requests ... will encode the body as\r
+  // `application/x-www-form-urlencoded` instead and will send the model in a\r
+  // form param named `model`.\r
+  Backbone.emulateJSON = false;\r
+\r
+  // Backbone.Events\r
+  // -----------------\r
+\r
+  // A module that can be mixed in to *any object* in order to provide it with\r
+  // custom events. You may bind with `on` or remove with `off` callback functions\r
+  // to an event; trigger`-ing an event fires all callbacks in succession.\r
+  //\r
+  //     var object = {};\r
+  //     _.extend(object, Backbone.Events);\r
+  //     object.on('expand', function(){ alert('expanded'); });\r
+  //     object.trigger('expand');\r
+  //\r
+  Backbone.Events = {\r
+\r
+    // Bind an event, specified by a string name, `ev`, to a `callback`\r
+    // function. Passing `"all"` will bind the callback to all events fired.\r
+    on: function(events, callback, context) {\r
+      var ev;\r
+      events = events.split(/\s+/);\r
+      var calls = this._callbacks || (this._callbacks = {});\r
+      while (ev = events.shift()) {\r
+        // Create an immutable callback list, allowing traversal during\r
+        // modification.  The tail is an empty object that will always be used\r
+        // as the next node.\r
+        var list  = calls[ev] || (calls[ev] = {});\r
+        var tail = list.tail || (list.tail = list.next = {});\r
+        tail.callback = callback;\r
+        tail.context = context;\r
+        list.tail = tail.next = {};\r
+      }\r
+      return this;\r
+    },\r
+\r
+    // Remove one or many callbacks. If `context` is null, removes all callbacks\r
+    // with that function. If `callback` is null, removes all callbacks for the\r
+    // event. If `ev` is null, removes all bound callbacks for all events.\r
+    off: function(events, callback, context) {\r
+      var ev, calls, node;\r
+      if (!events) {\r
+        delete this._callbacks;\r
+      } else if (calls = this._callbacks) {\r
+        events = events.split(/\s+/);\r
+        while (ev = events.shift()) {\r
+          node = calls[ev];\r
+          delete calls[ev];\r
+          if (!callback || !node) continue;\r
+          // Create a new list, omitting the indicated event/context pairs.\r
+          while ((node = node.next) && node.next) {\r
+            if (node.callback === callback &&\r
+              (!context || node.context === context)) continue;\r
+            this.on(ev, node.callback, node.context);\r
+          }\r
+        }\r
+      }\r
+      return this;\r
+    },\r
+\r
+    // Trigger an event, firing all bound callbacks. Callbacks are passed the\r
+    // same arguments as `trigger` is, apart from the event name.\r
+    // Listening for `"all"` passes the true event name as the first argument.\r
+    trigger: function(events) {\r
+      var event, node, calls, tail, args, all, rest;\r
+      if (!(calls = this._callbacks)) return this;\r
+      all = calls['all'];\r
+      (events = events.split(/\s+/)).push(null);\r
+      // Save references to the current heads & tails.\r
+      while (event = events.shift()) {\r
+        if (all) events.push({next: all.next, tail: all.tail, event: event});\r
+        if (!(node = calls[event])) continue;\r
+        events.push({next: node.next, tail: node.tail});\r
+      }\r
+      // Traverse each list, stopping when the saved tail is reached.\r
+      rest = slice.call(arguments, 1);\r
+      while (node = events.pop()) {\r
+        tail = node.tail;\r
+        args = node.event ? [node.event].concat(rest) : rest;\r
+        while ((node = node.next) !== tail) {\r
+          node.callback.apply(node.context || this, args);\r
+        }\r
+      }\r
+      return this;\r
+    }\r
+\r
+  };\r
+\r
+  // Aliases for backwards compatibility.\r
+  Backbone.Events.bind   = Backbone.Events.on;\r
+  Backbone.Events.unbind = Backbone.Events.off;\r
+\r
+  // Backbone.Model\r
+  // --------------\r
+\r
+  // Create a new model, with defined attributes. A client id (`cid`)\r
+  // is automatically generated and assigned for you.\r
+  Backbone.Model = function(attributes, options) {\r
+    var defaults;\r
+    attributes || (attributes = {});\r
+    if (options && options.parse) attributes = this.parse(attributes);\r
+    if (defaults = getValue(this, 'defaults')) {\r
+      attributes = _.extend({}, defaults, attributes);\r
+    }\r
+    if (options && options.collection) this.collection = options.collection;\r
+    this.attributes = {};\r
+    this._escapedAttributes = {};\r
+    this.cid = _.uniqueId('c');\r
+    if (!this.set(attributes, {silent: true})) {\r
+      throw new Error("Can't create an invalid model");\r
+    }\r
+    this._changed = false;\r
+    this._previousAttributes = _.clone(this.attributes);\r
+    this.initialize.apply(this, arguments);\r
+  };\r
+\r
+  // Attach all inheritable methods to the Model prototype.\r
+  _.extend(Backbone.Model.prototype, Backbone.Events, {\r
+\r
+    // Has the item been changed since the last `"change"` event?\r
+    _changed: false,\r
+\r
+    // The default name for the JSON `id` attribute is `"id"`. MongoDB and\r
+    // CouchDB users may want to set this to `"_id"`.\r
+    idAttribute: 'id',\r
+\r
+    // Initialize is an empty function by default. Override it with your own\r
+    // initialization logic.\r
+    initialize: function(){},\r
+\r
+    // Return a copy of the model's `attributes` object.\r
+    toJSON: function() {\r
+      return _.clone(this.attributes);\r
+    },\r
+\r
+    // Get the value of an attribute.\r
+    get: function(attr) {\r
+      return this.attributes[attr];\r
+    },\r
+\r
+    // Get the HTML-escaped value of an attribute.\r
+    escape: function(attr) {\r
+      var html;\r
+      if (html = this._escapedAttributes[attr]) return html;\r
+      var val = this.attributes[attr];\r
+      return this._escapedAttributes[attr] = _.escape(val == null ? '' : '' + val);\r
+    },\r
+\r
+    // Returns `true` if the attribute contains a value that is not null\r
+    // or undefined.\r
+    has: function(attr) {\r
+      return this.attributes[attr] != null;\r
+    },\r
+\r
+    // Set a hash of model attributes on the object, firing `"change"` unless\r
+    // you choose to silence it.\r
+    set: function(key, value, options) {\r
+      var attrs, attr, val;\r
+      if (_.isObject(key) || key == null) {\r
+        attrs = key;\r
+        options = value;\r
+      } else {\r
+        attrs = {};\r
+        attrs[key] = value;\r
+      }\r
+\r
+      // Extract attributes and options.\r
+      options || (options = {});\r
+      if (!attrs) return this;\r
+      if (attrs instanceof Backbone.Model) attrs = attrs.attributes;\r
+      if (options.unset) for (var attr in attrs) attrs[attr] = void 0;\r
+      var now = this.attributes, escaped = this._escapedAttributes;\r
+\r
+      // Run validation.\r
+      if (this.validate && !this._performValidation(attrs, options)) return false;\r
+\r
+      // Check for changes of `id`.\r
+      if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];\r
+\r
+      // We're about to start triggering change events.\r
+      var alreadyChanging = this._changing;\r
+      this._changing = true;\r
+\r
+      // Update attributes.\r
+      var changes = {};\r
+      for (attr in attrs) {\r
+        val = attrs[attr];\r
+        if (!_.isEqual(now[attr], val) || (options.unset && (attr in now))) {\r
+          delete escaped[attr];\r
+          this._changed = true;\r
+          changes[attr] = val;\r
+        }\r
+        options.unset ? delete now[attr] : now[attr] = val;\r
+      }\r
+\r
+      // Fire `change:attribute` events.\r
+      for (var attr in changes) {\r
+        if (!options.silent) this.trigger('change:' + attr, this, changes[attr], options);\r
+      }\r
+\r
+      // Fire the `"change"` event, if the model has been changed.\r
+      if (!alreadyChanging) {\r
+        if (!options.silent && this._changed) this.change(options);\r
+        this._changing = false;\r
+      }\r
+      return this;\r
+    },\r
+\r
+    // Remove an attribute from the model, firing `"change"` unless you choose\r
+    // to silence it. `unset` is a noop if the attribute doesn't exist.\r
+    unset: function(attr, options) {\r
+      (options || (options = {})).unset = true;\r
+      return this.set(attr, null, options);\r
+    },\r
+\r
+    // Clear all attributes on the model, firing `"change"` unless you choose\r
+    // to silence it.\r
+    clear: function(options) {\r
+      (options || (options = {})).unset = true;\r
+      return this.set(_.clone(this.attributes), options);\r
+    },\r
+\r
+    // Fetch the model from the server. If the server's representation of the\r
+    // model differs from its current attributes, they will be overriden,\r
+    // triggering a `"change"` event.\r
+    fetch: function(options) {\r
+      options = options ? _.clone(options) : {};\r
+      var model = this;\r
+      var success = options.success;\r
+      options.success = function(resp, status, xhr) {\r
+        if (!model.set(model.parse(resp, xhr), options)) return false;\r
+        if (success) success(model, resp);\r
+      };\r
+      options.error = Backbone.wrapError(options.error, model, options);\r
+      return (this.sync || Backbone.sync).call(this, 'read', this, options);\r
+    },\r
+\r
+    // Set a hash of model attributes, and sync the model to the server.\r
+    // If the server returns an attributes hash that differs, the model's\r
+    // state will be `set` again.\r
+    save: function(key, value, options) {\r
+      var attrs;\r
+      if (_.isObject(key) || key == null) {\r
+        attrs = key;\r
+        options = value;\r
+      } else {\r
+        attrs = {};\r
+        attrs[key] = value;\r
+      }\r
+\r
+      options = options ? _.clone(options) : {};\r
+      if (attrs && !this[options.wait ? '_performValidation' : 'set'](attrs, options)) return false;\r
+      var model = this;\r
+      var success = options.success;\r
+      options.success = function(resp, status, xhr) {\r
+        var serverAttrs = model.parse(resp, xhr);\r
+        if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);\r
+        if (!model.set(serverAttrs, options)) return false;\r
+        if (success) {\r
+          success(model, resp);\r
+        } else {\r
+          model.trigger('sync', model, resp, options);\r
+        }\r
+      };\r
+      options.error = Backbone.wrapError(options.error, model, options);\r
+      var method = this.isNew() ? 'create' : 'update';\r
+      return (this.sync || Backbone.sync).call(this, method, this, options);\r
+    },\r
+\r
+    // Destroy this model on the server if it was already persisted.\r
+    // Optimistically removes the model from its collection, if it has one.\r
+    // If `wait: true` is passed, waits for the server to respond before removal.\r
+    destroy: function(options) {\r
+      options = options ? _.clone(options) : {};\r
+      var model = this;\r
+      var success = options.success;\r
+\r
+      var triggerDestroy = function() {\r
+        model.trigger('destroy', model, model.collection, options);\r
+      };\r
+\r
+      if (this.isNew()) return triggerDestroy();\r
+      options.success = function(resp) {\r
+        if (options.wait) triggerDestroy();\r
+        if (success) {\r
+          success(model, resp);\r
+        } else {\r
+          model.trigger('sync', model, resp, options);\r
+        }\r
+      };\r
+      options.error = Backbone.wrapError(options.error, model, options);\r
+      var xhr = (this.sync || Backbone.sync).call(this, 'delete', this, options);\r
+      if (!options.wait) triggerDestroy();\r
+      return xhr;\r
+    },\r
+\r
+    // Default URL for the model's representation on the server -- if you're\r
+    // using Backbone's restful methods, override this to change the endpoint\r
+    // that will be called.\r
+    url: function() {\r
+      var base = getValue(this.collection, 'url') || getValue(this, 'urlRoot') || urlError();\r
+      if (this.isNew()) return base;\r
+      return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id);\r
+    },\r
+\r
+    // **parse** converts a response into the hash of attributes to be `set` on\r
+    // the model. The default implementation is just to pass the response along.\r
+    parse: function(resp, xhr) {\r
+      return resp;\r
+    },\r
+\r
+    // Create a new model with identical attributes to this one.\r
+    clone: function() {\r
+      return new this.constructor(this.attributes);\r
+    },\r
+\r
+    // A model is new if it has never been saved to the server, and lacks an id.\r
+    isNew: function() {\r
+      return this.id == null;\r
+    },\r
+\r
+    // Call this method to manually fire a `change` event for this model.\r
+    // Calling this will cause all objects observing the model to update.\r
+    change: function(options) {\r
+      this.trigger('change', this, options);\r
+      this._previousAttributes = _.clone(this.attributes);\r
+      this._changed = false;\r
+    },\r
+\r
+    // Determine if the model has changed since the last `"change"` event.\r
+    // If you specify an attribute name, determine if that attribute has changed.\r
+    hasChanged: function(attr) {\r
+      if (attr) return !_.isEqual(this._previousAttributes[attr], this.attributes[attr]);\r
+      return this._changed;\r
+    },\r
+\r
+    // Return an object containing all the attributes that have changed, or\r
+    // false if there are no changed attributes. Useful for determining what\r
+    // parts of a view need to be updated and/or what attributes need to be\r
+    // persisted to the server. Unset attributes will be set to undefined.\r
+    changedAttributes: function(now) {\r
+      if (!this._changed) return false;\r
+      now || (now = this.attributes);\r
+      var changed = false, old = this._previousAttributes;\r
+      for (var attr in now) {\r
+        if (_.isEqual(old[attr], now[attr])) continue;\r
+        (changed || (changed = {}))[attr] = now[attr];\r
+      }\r
+      for (var attr in old) {\r
+        if (!(attr in now)) (changed || (changed = {}))[attr] = void 0;\r
+      }\r
+      return changed;\r
+    },\r
+\r
+    // Get the previous value of an attribute, recorded at the time the last\r
+    // `"change"` event was fired.\r
+    previous: function(attr) {\r
+      if (!attr || !this._previousAttributes) return null;\r
+      return this._previousAttributes[attr];\r
+    },\r
+\r
+    // Get all of the attributes of the model at the time of the previous\r
+    // `"change"` event.\r
+    previousAttributes: function() {\r
+      return _.clone(this._previousAttributes);\r
+    },\r
+\r
+    // Run validation against a set of incoming attributes, returning `true`\r
+    // if all is well. If a specific `error` callback has been passed,\r
+    // call that instead of firing the general `"error"` event.\r
+    _performValidation: function(attrs, options) {\r
+      var newAttrs = _.extend({}, this.attributes, attrs);\r
+      var error = this.validate(newAttrs, options);\r
+      if (error) {\r
+        if (options.error) {\r
+          options.error(this, error, options);\r
+        } else {\r
+          this.trigger('error', this, error, options);\r
+        }\r
+        return false;\r
+      }\r
+      return true;\r
+    }\r
+\r
+  });\r
+\r
+  // Backbone.Collection\r
+  // -------------------\r
+\r
+  // Provides a standard collection class for our sets of models, ordered\r
+  // or unordered. If a `comparator` is specified, the Collection will maintain\r
+  // its models in sort order, as they're added and removed.\r
+  Backbone.Collection = function(models, options) {\r
+    options || (options = {});\r
+    if (options.comparator) this.comparator = options.comparator;\r
+    this._reset();\r
+    this.initialize.apply(this, arguments);\r
+    if (models) this.reset(models, {silent: true, parse: options.parse});\r
+  };\r
+\r
+  // Define the Collection's inheritable methods.\r
+  _.extend(Backbone.Collection.prototype, Backbone.Events, {\r
+\r
+    // The default model for a collection is just a **Backbone.Model**.\r
+    // This should be overridden in most cases.\r
+    model: Backbone.Model,\r
+\r
+    // Initialize is an empty function by default. Override it with your own\r
+    // initialization logic.\r
+    initialize: function(){},\r
+\r
+    // The JSON representation of a Collection is an array of the\r
+    // models' attributes.\r
+    toJSON: function() {\r
+      return this.map(function(model){ return model.toJSON(); });\r
+    },\r
+\r
+    // Add a model, or list of models to the set. Pass **silent** to avoid\r
+    // firing the `add` event for every new model.\r
+    add: function(models, options) {\r
+      var i, index, length, model, cids = {};\r
+      options || (options = {});\r
+      models = _.isArray(models) ? models.slice() : [models];\r
+\r
+      // Begin by turning bare objects into model references, and preventing\r
+      // invalid models or duplicate models from being added.\r
+      for (i = 0, length = models.length; i < length; i++) {\r
+        if (!(model = models[i] = this._prepareModel(models[i], options))) {\r
+          throw new Error("Can't add an invalid model to a collection");\r
+        }\r
+        var hasId = model.id != null;\r
+        if (this._byCid[model.cid] || (hasId && this._byId[model.id])) {\r
+          throw new Error("Can't add the same model to a collection twice");\r
+        }\r
+      }\r
+\r
+      // Listen to added models' events, and index models for lookup by\r
+      // `id` and by `cid`.\r
+      for (i = 0; i < length; i++) {\r
+        (model = models[i]).on('all', this._onModelEvent, this);\r
+        this._byCid[model.cid] = model;\r
+        if (model.id != null) this._byId[model.id] = model;\r
+        cids[model.cid] = true;\r
+      }\r
+\r
+      // Insert models into the collection, re-sorting if needed, and triggering\r
+      // `add` events unless silenced.\r
+      this.length += length;\r
+      index = options.at != null ? options.at : this.models.length;\r
+      splice.apply(this.models, [index, 0].concat(models));\r
+      if (this.comparator) this.sort({silent: true});\r
+      if (options.silent) return this;\r
+      for (i = 0, length = this.models.length; i < length; i++) {\r
+        if (!cids[(model = this.models[i]).cid]) continue;\r
+        options.index = i;\r
+        model.trigger('add', model, this, options);\r
+      }\r
+      return this;\r
+    },\r
+\r
+    // Remove a model, or a list of models from the set. Pass silent to avoid\r
+    // firing the `remove` event for every model removed.\r
+    remove: function(models, options) {\r
+      var i, l, index, model;\r
+      options || (options = {});\r
+      models = _.isArray(models) ? models.slice() : [models];\r
+      for (i = 0, l = models.length; i < l; i++) {\r
+        model = this.getByCid(models[i]) || this.get(models[i]);\r
+        if (!model) continue;\r
+        delete this._byId[model.id];\r
+        delete this._byCid[model.cid];\r
+        index = this.indexOf(model);\r
+        this.models.splice(index, 1);\r
+        this.length--;\r
+        if (!options.silent) {\r
+          options.index = index;\r
+          model.trigger('remove', model, this, options);\r
+        }\r
+        this._removeReference(model);\r
+      }\r
+      return this;\r
+    },\r
+\r
+    // Get a model from the set by id.\r
+    get: function(id) {\r
+      if (id == null) return null;\r
+      return this._byId[id.id != null ? id.id : id];\r
+    },\r
+\r
+    // Get a model from the set by client id.\r
+    getByCid: function(cid) {\r
+      return cid && this._byCid[cid.cid || cid];\r
+    },\r
+\r
+    // Get the model at the given index.\r
+    at: function(index) {\r
+      return this.models[index];\r
+    },\r
+\r
+    // Force the collection to re-sort itself. You don't need to call this under\r
+    // normal circumstances, as the set will maintain sort order as each item\r
+    // is added.\r
+    sort: function(options) {\r
+      options || (options = {});\r
+      if (!this.comparator) throw new Error('Cannot sort a set without a comparator');\r
+      var boundComparator = _.bind(this.comparator, this);\r
+      if (this.comparator.length == 1) {\r
+        this.models = this.sortBy(boundComparator);\r
+      } else {\r
+        this.models.sort(boundComparator);\r
+      }\r
+      if (!options.silent) this.trigger('reset', this, options);\r
+      return this;\r
+    },\r
+\r
+    // Pluck an attribute from each model in the collection.\r
+    pluck: function(attr) {\r
+      return _.map(this.models, function(model){ return model.get(attr); });\r
+    },\r
+\r
+    // When you have more items than you want to add or remove individually,\r
+    // you can reset the entire set with a new list of models, without firing\r
+    // any `add` or `remove` events. Fires `reset` when finished.\r
+    reset: function(models, options) {\r
+      models  || (models = []);\r
+      options || (options = {});\r
+      for (var i = 0, l = this.models.length; i < l; i++) {\r
+        this._removeReference(this.models[i]);\r
+      }\r
+      this._reset();\r
+      this.add(models, {silent: true, parse: options.parse});\r
+      if (!options.silent) this.trigger('reset', this, options);\r
+      return this;\r
+    },\r
+\r
+    // Fetch the default set of models for this collection, resetting the\r
+    // collection when they arrive. If `add: true` is passed, appends the\r
+    // models to the collection instead of resetting.\r
+    fetch: function(options) {\r
+      options = options ? _.clone(options) : {};\r
+      if (options.parse === undefined) options.parse = true;\r
+      var collection = this;\r
+      var success = options.success;\r
+      options.success = function(resp, status, xhr) {\r
+        collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);\r
+        if (success) success(collection, resp);\r
+      };\r
+      options.error = Backbone.wrapError(options.error, collection, options);\r
+      return (this.sync || Backbone.sync).call(this, 'read', this, options);\r
+    },\r
+\r
+    // Create a new instance of a model in this collection. Add the model to the\r
+    // collection immediately, unless `wait: true` is passed, in which case we\r
+    // wait for the server to agree.\r
+    create: function(model, options) {\r
+      var coll = this;\r
+      options = options ? _.clone(options) : {};\r
+      model = this._prepareModel(model, options);\r
+      if (!model) return false;\r
+      if (!options.wait) coll.add(model, options);\r
+      var success = options.success;\r
+      options.success = function(nextModel, resp, xhr) {\r
+        if (options.wait) coll.add(nextModel, options);\r
+        if (success) {\r
+          success(nextModel, resp);\r
+        } else {\r
+          nextModel.trigger('sync', model, resp, options);\r
+        }\r
+      };\r
+      model.save(null, options);\r
+      return model;\r
+    },\r
+\r
+    // **parse** converts a response into a list of models to be added to the\r
+    // collection. The default implementation is just to pass it through.\r
+    parse: function(resp, xhr) {\r
+      return resp;\r
+    },\r
+\r
+    // Proxy to _'s chain. Can't be proxied the same way the rest of the\r
+    // underscore methods are proxied because it relies on the underscore\r
+    // constructor.\r
+    chain: function () {\r
+      return _(this.models).chain();\r
+    },\r
+\r
+    // Reset all internal state. Called when the collection is reset.\r
+    _reset: function(options) {\r
+      this.length = 0;\r
+      this.models = [];\r
+      this._byId  = {};\r
+      this._byCid = {};\r
+    },\r
+\r
+    // Prepare a model or hash of attributes to be added to this collection.\r
+    _prepareModel: function(model, options) {\r
+      if (!(model instanceof Backbone.Model)) {\r
+        var attrs = model;\r
+        options.collection = this;\r
+        model = new this.model(attrs, options);\r
+        if (model.validate && !model._performValidation(model.attributes, options)) model = false;\r
+      } else if (!model.collection) {\r
+        model.collection = this;\r
+      }\r
+      return model;\r
+    },\r
+\r
+    // Internal method to remove a model's ties to a collection.\r
+    _removeReference: function(model) {\r
+      if (this == model.collection) {\r
+        delete model.collection;\r
+      }\r
+      model.off('all', this._onModelEvent, this);\r
+    },\r
+\r
+    // Internal method called every time a model in the set fires an event.\r
+    // Sets need to update their indexes when models change ids. All other\r
+    // events simply proxy through. "add" and "remove" events that originate\r
+    // in other collections are ignored.\r
+    _onModelEvent: function(ev, model, collection, options) {\r
+      if ((ev == 'add' || ev == 'remove') && collection != this) return;\r
+      if (ev == 'destroy') {\r
+        this.remove(model, options);\r
+      }\r
+      if (model && ev === 'change:' + model.idAttribute) {\r
+        delete this._byId[model.previous(model.idAttribute)];\r
+        this._byId[model.id] = model;\r
+      }\r
+      this.trigger.apply(this, arguments);\r
+    }\r
+\r
+  });\r
+\r
+  // Underscore methods that we want to implement on the Collection.\r
+  var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find',\r
+    'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any',\r
+    'include', 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex',\r
+    'toArray', 'size', 'first', 'initial', 'rest', 'last', 'without', 'indexOf',\r
+    'shuffle', 'lastIndexOf', 'isEmpty', 'groupBy'];\r
+\r
+  // Mix in each Underscore method as a proxy to `Collection#models`.\r
+  _.each(methods, function(method) {\r
+    Backbone.Collection.prototype[method] = function() {\r
+      return _[method].apply(_, [this.models].concat(_.toArray(arguments)));\r
+    };\r
+  });\r
+\r
+  // Backbone.Router\r
+  // -------------------\r
+\r
+  // Routers map faux-URLs to actions, and fire events when routes are\r
+  // matched. Creating a new one sets its `routes` hash, if not set statically.\r
+  Backbone.Router = function(options) {\r
+    options || (options = {});\r
+    if (options.routes) this.routes = options.routes;\r
+    this._bindRoutes();\r
+    this.initialize.apply(this, arguments);\r
+  };\r
+\r
+  // Cached regular expressions for matching named param parts and splatted\r
+  // parts of route strings.\r
+  var namedParam    = /:\w+/g;\r
+  var splatParam    = /\*\w+/g;\r
+  var escapeRegExp  = /[-[\]{}()+?.,\\^$|#\s]/g;\r
+\r
+  // Set up all inheritable **Backbone.Router** properties and methods.\r
+  _.extend(Backbone.Router.prototype, Backbone.Events, {\r
+\r
+    // Initialize is an empty function by default. Override it with your own\r
+    // initialization logic.\r
+    initialize: function(){},\r
+\r
+    // Manually bind a single named route to a callback. For example:\r
+    //\r
+    //     this.route('search/:query/p:num', 'search', function(query, num) {\r
+    //       ...\r
+    //     });\r
+    //\r
+    route: function(route, name, callback) {\r
+      Backbone.history || (Backbone.history = new Backbone.History);\r
+      if (!_.isRegExp(route)) route = this._routeToRegExp(route);\r
+      if (!callback) callback = this[name];\r
+      Backbone.history.route(route, _.bind(function(fragment) {\r
+        var args = this._extractParameters(route, fragment);\r
+        callback && callback.apply(this, args);\r
+        this.trigger.apply(this, ['route:' + name].concat(args));\r
+        Backbone.history.trigger('route', this, name, args);\r
+      }, this));\r
+    },\r
+\r
+    // Simple proxy to `Backbone.history` to save a fragment into the history.\r
+    navigate: function(fragment, options) {\r
+      Backbone.history.navigate(fragment, options);\r
+    },\r
+\r
+    // Bind all defined routes to `Backbone.history`. We have to reverse the\r
+    // order of the routes here to support behavior where the most general\r
+    // routes can be defined at the bottom of the route map.\r
+    _bindRoutes: function() {\r
+      if (!this.routes) return;\r
+      var routes = [];\r
+      for (var route in this.routes) {\r
+        routes.unshift([route, this.routes[route]]);\r
+      }\r
+      for (var i = 0, l = routes.length; i < l; i++) {\r
+        this.route(routes[i][0], routes[i][1], this[routes[i][1]]);\r
+      }\r
+    },\r
+\r
+    // Convert a route string into a regular expression, suitable for matching\r
+    // against the current location hash.\r
+    _routeToRegExp: function(route) {\r
+      route = route.replace(escapeRegExp, '\\$&')\r
+                   .replace(namedParam, '([^\/]+)')\r
+                   .replace(splatParam, '(.*?)');\r
+      return new RegExp('^' + route + '$');\r
+    },\r
+\r
+    // Given a route, and a URL fragment that it matches, return the array of\r
+    // extracted parameters.\r
+    _extractParameters: function(route, fragment) {\r
+      return route.exec(fragment).slice(1);\r
+    }\r
+\r
+  });\r
+\r
+  // Backbone.History\r
+  // ----------------\r
+\r
+  // Handles cross-browser history management, based on URL fragments. If the\r
+  // browser does not support `onhashchange`, falls back to polling.\r
+  Backbone.History = function() {\r
+    this.handlers = [];\r
+    _.bindAll(this, 'checkUrl');\r
+  };\r
+\r
+  // Cached regex for cleaning leading hashes and slashes .\r
+  var routeStripper = /^[#\/]/;\r
+\r
+  // Cached regex for detecting MSIE.\r
+  var isExplorer = /msie [\w.]+/;\r
+\r
+  // Has the history handling already been started?\r
+  var historyStarted = false;\r
+\r
+  // Set up all inheritable **Backbone.History** properties and methods.\r
+  _.extend(Backbone.History.prototype, Backbone.Events, {\r
+\r
+    // The default interval to poll for hash changes, if necessary, is\r
+    // twenty times a second.\r
+    interval: 50,\r
+\r
+    // Get the cross-browser normalized URL fragment, either from the URL,\r
+    // the hash, or the override.\r
+    getFragment: function(fragment, forcePushState) {\r
+      if (fragment == null) {\r
+        if (this._hasPushState || forcePushState) {\r
+          fragment = window.location.pathname;\r
+          var search = window.location.search;\r
+          if (search) fragment += search;\r
+        } else {\r
+          fragment = window.location.hash;\r
+        }\r
+      }\r
+      fragment = decodeURIComponent(fragment.replace(routeStripper, ''));\r
+      if (!fragment.indexOf(this.options.root)) fragment = fragment.substr(this.options.root.length);\r
+      return fragment;\r
+    },\r
+\r
+    // Start the hash change handling, returning `true` if the current URL matches\r
+    // an existing route, and `false` otherwise.\r
+    start: function(options) {\r
+\r
+      // Figure out the initial configuration. Do we need an iframe?\r
+      // Is pushState desired ... is it available?\r
+      if (historyStarted) throw new Error("Backbone.history has already been started");\r
+      this.options          = _.extend({}, {root: '/'}, this.options, options);\r
+      this._wantsHashChange = this.options.hashChange !== false;\r
+      this._wantsPushState  = !!this.options.pushState;\r
+      this._hasPushState    = !!(this.options.pushState && window.history && window.history.pushState);\r
+      var fragment          = this.getFragment();\r
+      var docMode           = document.documentMode;\r
+      var oldIE             = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));\r
+      if (oldIE) {\r
+        this.iframe = $('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow;\r
+        this.navigate(fragment);\r
+      }\r
+\r
+      // Depending on whether we're using pushState or hashes, and whether\r
+      // 'onhashchange' is supported, determine how we check the URL state.\r
+      if (this._hasPushState) {\r
+        $(window).bind('popstate', this.checkUrl);\r
+      } else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {\r
+        $(window).bind('hashchange', this.checkUrl);\r
+      } else if (this._wantsHashChange) {\r
+        this._checkUrlInterval = setInterval(this.checkUrl, this.interval);\r
+      }\r
+\r
+      // Determine if we need to change the base url, for a pushState link\r
+      // opened by a non-pushState browser.\r
+      this.fragment = fragment;\r
+      historyStarted = true;\r
+      var loc = window.location;\r
+      var atRoot  = loc.pathname == this.options.root;\r
+\r
+      // If we've started off with a route from a `pushState`-enabled browser,\r
+      // but we're currently in a browser that doesn't support it...\r
+      if (this._wantsHashChange && this._wantsPushState && !this._hasPushState && !atRoot) {\r
+        this.fragment = this.getFragment(null, true);\r
+        window.location.replace(this.options.root + '#' + this.fragment);\r
+        // Return immediately as browser will do redirect to new url\r
+        return true;\r
+\r
+      // Or if we've started out with a hash-based route, but we're currently\r
+      // in a browser where it could be `pushState`-based instead...\r
+      } else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {\r
+        this.fragment = loc.hash.replace(routeStripper, '');\r
+        window.history.replaceState({}, document.title, loc.protocol + '//' + loc.host + this.options.root + this.fragment);\r
+      }\r
+\r
+      if (!this.options.silent) {\r
+        return this.loadUrl();\r
+      }\r
+    },\r
+\r
+    // Disable Backbone.history, perhaps temporarily. Not useful in a real app,\r
+    // but possibly useful for unit testing Routers.\r
+    stop: function() {\r
+      $(window).unbind('popstate', this.checkUrl).unbind('hashchange', this.checkUrl);\r
+      clearInterval(this._checkUrlInterval);\r
+      historyStarted = false;\r
+    },\r
+\r
+    // Add a route to be tested when the fragment changes. Routes added later\r
+    // may override previous routes.\r
+    route: function(route, callback) {\r
+      this.handlers.unshift({route: route, callback: callback});\r
+    },\r
+\r
+    // Checks the current URL to see if it has changed, and if it has,\r
+    // calls `loadUrl`, normalizing across the hidden iframe.\r
+    checkUrl: function(e) {\r
+      var current = this.getFragment();\r
+      if (current == this.fragment && this.iframe) current = this.getFragment(this.iframe.location.hash);\r
+      if (current == this.fragment || current == decodeURIComponent(this.fragment)) return false;\r
+      if (this.iframe) this.navigate(current);\r
+      this.loadUrl() || this.loadUrl(window.location.hash);\r
+    },\r
+\r
+    // Attempt to load the current URL fragment. If a route succeeds with a\r
+    // match, returns `true`. If no defined routes matches the fragment,\r
+    // returns `false`.\r
+    loadUrl: function(fragmentOverride) {\r
+      var fragment = this.fragment = this.getFragment(fragmentOverride);\r
+      var matched = _.any(this.handlers, function(handler) {\r
+        if (handler.route.test(fragment)) {\r
+          handler.callback(fragment);\r
+          return true;\r
+        }\r
+      });\r
+      return matched;\r
+    },\r
+\r
+    // Save a fragment into the hash history, or replace the URL state if the\r
+    // 'replace' option is passed. You are responsible for properly URL-encoding\r
+    // the fragment in advance.\r
+    //\r
+    // The options object can contain `trigger: true` if you wish to have the\r
+    // route callback be fired (not usually desirable), or `replace: true`, if\r
+    // you which to modify the current URL without adding an entry to the history.\r
+    navigate: function(fragment, options) {\r
+      if (!historyStarted) return false;\r
+      if (!options || options === true) options = {trigger: options};\r
+      var frag = (fragment || '').replace(routeStripper, '');\r
+      if (this.fragment == frag || this.fragment == decodeURIComponent(frag)) return;\r
+\r
+      // If pushState is available, we use it to set the fragment as a real URL.\r
+      if (this._hasPushState) {\r
+        if (frag.indexOf(this.options.root) != 0) frag = this.options.root + frag;\r
+        this.fragment = frag;\r
+        window.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, frag);\r
+\r
+      // If hash changes haven't been explicitly disabled, update the hash\r
+      // fragment to store history.\r
+      } else if (this._wantsHashChange) {\r
+        this.fragment = frag;\r
+        this._updateHash(window.location, frag, options.replace);\r
+        if (this.iframe && (frag != this.getFragment(this.iframe.location.hash))) {\r
+          // Opening and closing the iframe tricks IE7 and earlier to push a history entry on hash-tag change.\r
+          // When replace is true, we don't want this.\r
+          if(!options.replace) this.iframe.document.open().close();\r
+          this._updateHash(this.iframe.location, frag, options.replace);\r
+        }\r
+\r
+      // If you've told us that you explicitly don't want fallback hashchange-\r
+      // based history, then `navigate` becomes a page refresh.\r
+      } else {\r
+        window.location.assign(this.options.root + fragment);\r
+      }\r
+      if (options.trigger) this.loadUrl(fragment);\r
+    },\r
+\r
+    // Update the hash location, either replacing the current entry, or adding\r
+    // a new one to the browser history.\r
+    _updateHash: function(location, fragment, replace) {\r
+      if (replace) {\r
+        location.replace(location.toString().replace(/(javascript:|#).*$/, '') + '#' + fragment);\r
+      } else {\r
+        location.hash = fragment;\r
+      }\r
+    }\r
+  });\r
+\r
+  // Backbone.View\r
+  // -------------\r
+\r
+  // Creating a Backbone.View creates its initial element outside of the DOM,\r
+  // if an existing element is not provided...\r
+  Backbone.View = function(options) {\r
+    this.cid = _.uniqueId('view');\r
+    this._configure(options || {});\r
+    this._ensureElement();\r
+    this.initialize.apply(this, arguments);\r
+    this.delegateEvents();\r
+  };\r
+\r
+  // Cached regex to split keys for `delegate`.\r
+  var eventSplitter = /^(\S+)\s*(.*)$/;\r
+\r
+  // List of view options to be merged as properties.\r
+  var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName'];\r
+\r
+  // Set up all inheritable **Backbone.View** properties and methods.\r
+  _.extend(Backbone.View.prototype, Backbone.Events, {\r
+\r
+    // The default `tagName` of a View's element is `"div"`.\r
+    tagName: 'div',\r
+\r
+    // jQuery delegate for element lookup, scoped to DOM elements within the\r
+    // current view. This should be prefered to global lookups where possible.\r
+    $: function(selector) {\r
+      return $(selector, this.el);\r
+    },\r
+\r
+    // Initialize is an empty function by default. Override it with your own\r
+    // initialization logic.\r
+    initialize: function(){},\r
+\r
+    // **render** is the core function that your view should override, in order\r
+    // to populate its element (`this.el`), with the appropriate HTML. The\r
+    // convention is for **render** to always return `this`.\r
+    render: function() {\r
+      return this;\r
+    },\r
+\r
+    // Remove this view from the DOM. Note that the view isn't present in the\r
+    // DOM by default, so calling this method may be a no-op.\r
+    remove: function() {\r
+      this.$el.remove();\r
+      return this;\r
+    },\r
+\r
+    // For small amounts of DOM Elements, where a full-blown template isn't\r
+    // needed, use **make** to manufacture elements, one at a time.\r
+    //\r
+    //     var el = this.make('li', {'class': 'row'}, this.model.escape('title'));\r
+    //\r
+    make: function(tagName, attributes, content) {\r
+      var el = document.createElement(tagName);\r
+      if (attributes) $(el).attr(attributes);\r
+      if (content) $(el).html(content);\r
+      return el;\r
+    },\r
+\r
+    // Change the view's element (`this.el` property), including event\r
+    // re-delegation.\r
+    setElement: function(element, delegate) {\r
+      this.$el = $(element);\r
+      this.el = this.$el[0];\r
+      if (delegate !== false) this.delegateEvents();\r
+    },\r
+\r
+    // Set callbacks, where `this.events` is a hash of\r
+    //\r
+    // *{"event selector": "callback"}*\r
+    //\r
+    //     {\r
+    //       'mousedown .title':  'edit',\r
+    //       'click .button':     'save'\r
+    //       'click .open':       function(e) { ... }\r
+    //     }\r
+    //\r
+    // pairs. Callbacks will be bound to the view, with `this` set properly.\r
+    // Uses event delegation for efficiency.\r
+    // Omitting the selector binds the event to `this.el`.\r
+    // This only works for delegate-able events: not `focus`, `blur`, and\r
+    // not `change`, `submit`, and `reset` in Internet Explorer.\r
+    delegateEvents: function(events) {\r
+      if (!(events || (events = getValue(this, 'events')))) return;\r
+      this.undelegateEvents();\r
+      for (var key in events) {\r
+        var method = events[key];\r
+        if (!_.isFunction(method)) method = this[events[key]];\r
+        if (!method) throw new Error('Event "' + events[key] + '" does not exist');\r
+        var match = key.match(eventSplitter);\r
+        var eventName = match[1], selector = match[2];\r
+        method = _.bind(method, this);\r
+        eventName += '.delegateEvents' + this.cid;\r
+        if (selector === '') {\r
+          this.$el.bind(eventName, method);\r
+        } else {\r
+          this.$el.delegate(selector, eventName, method);\r
+        }\r
+      }\r
+    },\r
+\r
+    // Clears all callbacks previously bound to the view with `delegateEvents`.\r
+    // You usually don't need to use this, but may wish to if you have multiple\r
+    // Backbone views attached to the same DOM element.\r
+    undelegateEvents: function() {\r
+      this.$el.unbind('.delegateEvents' + this.cid);\r
+    },\r
+\r
+    // Performs the initial configuration of a View with a set of options.\r
+    // Keys with special meaning *(model, collection, id, className)*, are\r
+    // attached directly to the view.\r
+    _configure: function(options) {\r
+      if (this.options) options = _.extend({}, this.options, options);\r
+      for (var i = 0, l = viewOptions.length; i < l; i++) {\r
+        var attr = viewOptions[i];\r
+        if (options[attr]) this[attr] = options[attr];\r
+      }\r
+      this.options = options;\r
+    },\r
+\r
+    // Ensure that the View has a DOM element to render into.\r
+    // If `this.el` is a string, pass it through `$()`, take the first\r
+    // matching element, and re-assign it to `el`. Otherwise, create\r
+    // an element from the `id`, `className` and `tagName` properties.\r
+    _ensureElement: function() {\r
+      if (!this.el) {\r
+        var attrs = getValue(this, 'attributes') || {};\r
+        if (this.id) attrs.id = this.id;\r
+        if (this.className) attrs['class'] = this.className;\r
+        this.setElement(this.make(this.tagName, attrs), false);\r
+      } else {\r
+        this.setElement(this.el, false);\r
+      }\r
+    }\r
+\r
+  });\r
+\r
+  // The self-propagating extend function that Backbone classes use.\r
+  var extend = function (protoProps, classProps) {\r
+    var child = inherits(this, protoProps, classProps);\r
+    child.extend = this.extend;\r
+    return child;\r
+  };\r
+\r
+  // Set up inheritance for the model, collection, and view.\r
+  Backbone.Model.extend = Backbone.Collection.extend =\r
+    Backbone.Router.extend = Backbone.View.extend = extend;\r
+\r
+  // Backbone.sync\r
+  // -------------\r
+\r
+  // Map from CRUD to HTTP for our default `Backbone.sync` implementation.\r
+  var methodMap = {\r
+    'create': 'POST',\r
+    'update': 'PUT',\r
+    'delete': 'DELETE',\r
+    'read':   'GET'\r
+  };\r
+\r
+  // Override this function to change the manner in which Backbone persists\r
+  // models to the server. You will be passed the type of request, and the\r
+  // model in question. By default, makes a RESTful Ajax request\r
+  // to the model's `url()`. Some possible customizations could be:\r
+  //\r
+  // * Use `setTimeout` to batch rapid-fire updates into a single request.\r
+  // * Send up the models as XML instead of JSON.\r
+  // * Persist models via WebSockets instead of Ajax.\r
+  //\r
+  // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests\r
+  // as `POST`, with a `_method` parameter containing the true HTTP method,\r
+  // as well as all requests with the body as `application/x-www-form-urlencoded`\r
+  // instead of `application/json` with the model in a param named `model`.\r
+  // Useful when interfacing with server-side languages like **PHP** that make\r
+  // it difficult to read the body of `PUT` requests.\r
+  Backbone.sync = function(method, model, options) {\r
+    var type = methodMap[method];\r
+\r
+    // Default JSON-request options.\r
+    var params = {type: type, dataType: 'json'};\r
+\r
+    // Ensure that we have a URL.\r
+    if (!options.url) {\r
+      params.url = getValue(model, 'url') || urlError();\r
+    }\r
+\r
+    // Ensure that we have the appropriate request data.\r
+    if (!options.data && model && (method == 'create' || method == 'update')) {\r
+      params.contentType = 'application/json';\r
+      params.data = JSON.stringify(model.toJSON());\r
+    }\r
+\r
+    // For older servers, emulate JSON by encoding the request into an HTML-form.\r
+    if (Backbone.emulateJSON) {\r
+      params.contentType = 'application/x-www-form-urlencoded';\r
+      params.data = params.data ? {model: params.data} : {};\r
+    }\r
+\r
+    // For older servers, emulate HTTP by mimicking the HTTP method with `_method`\r
+    // And an `X-HTTP-Method-Override` header.\r
+    if (Backbone.emulateHTTP) {\r
+      if (type === 'PUT' || type === 'DELETE') {\r
+        if (Backbone.emulateJSON) params.data._method = type;\r
+        params.type = 'POST';\r
+        params.beforeSend = function(xhr) {\r
+          xhr.setRequestHeader('X-HTTP-Method-Override', type);\r
+        };\r
+      }\r
+    }\r
+\r
+    // Don't process data on a non-GET request.\r
+    if (params.type !== 'GET' && !Backbone.emulateJSON) {\r
+      params.processData = false;\r
+    }\r
+\r
+    // Make the request, allowing the user to override any Ajax options.\r
+    return $.ajax(_.extend(params, options));\r
+  };\r
+\r
+  // Wrap an optional error callback with a fallback error event.\r
+  Backbone.wrapError = function(onError, originalModel, options) {\r
+    return function(model, resp) {\r
+      var resp = model === originalModel ? resp : model;\r
+      if (onError) {\r
+        onError(model, resp, options);\r
+      } else {\r
+        originalModel.trigger('error', model, resp, options);\r
+      }\r
+    };\r
+  };\r
+\r
+  // Helpers\r
+  // -------\r
+\r
+  // Shared empty constructor function to aid in prototype-chain creation.\r
+  var ctor = function(){};\r
+\r
+  // Helper function to correctly set up the prototype chain, for subclasses.\r
+  // Similar to `goog.inherits`, but uses a hash of prototype properties and\r
+  // class properties to be extended.\r
+  var inherits = function(parent, protoProps, staticProps) {\r
+    var child;\r
+\r
+    // The constructor function for the new subclass is either defined by you\r
+    // (the "constructor" property in your `extend` definition), or defaulted\r
+    // by us to simply call the parent's constructor.\r
+    if (protoProps && protoProps.hasOwnProperty('constructor')) {\r
+      child = protoProps.constructor;\r
+    } else {\r
+      child = function(){ parent.apply(this, arguments); };\r
+    }\r
+\r
+    // Inherit class (static) properties from parent.\r
+    _.extend(child, parent);\r
+\r
+    // Set the prototype chain to inherit from `parent`, without calling\r
+    // `parent`'s constructor function.\r
+    ctor.prototype = parent.prototype;\r
+    child.prototype = new ctor();\r
+\r
+    // Add prototype properties (instance properties) to the subclass,\r
+    // if supplied.\r
+    if (protoProps) _.extend(child.prototype, protoProps);\r
+\r
+    // Add static properties to the constructor function, if supplied.\r
+    if (staticProps) _.extend(child, staticProps);\r
+\r
+    // Correctly set child's `prototype.constructor`.\r
+    child.prototype.constructor = child;\r
+\r
+    // Set a convenience property in case the parent's prototype is needed later.\r
+    child.__super__ = parent.prototype;\r
+\r
+    return child;\r
+  };\r
+\r
+  // Helper function to get a value from a Backbone object as a property\r
+  // or as a function.\r
+  var getValue = function(object, prop) {\r
+    if (!(object && object[prop])) return null;\r
+    return _.isFunction(object[prop]) ? object[prop]() : object[prop];\r
+  };\r
+\r
+  // Throw an error when a URL is needed, and none is supplied.\r
+  var urlError = function() {\r
+    throw new Error('A "url" property or function must be specified');\r
+  };\r
+\r
+}).call(this);\r
index 6790e8fbf426092e25f7c1d50c0ea65b52ef5c47..e0d6a59b27a10a2a14187a7dd86bdee9c636713b 100755 (executable)
@@ -62,16 +62,13 @@ kiwi.front.events = {
         if (!plugin_event) {\r
             return;\r
         }\r
-        tab = Tabview.getTab(plugin_event.destination);\r
-        if (!tab) {\r
-            tab = new Tabview(plugin_event.destination);\r
-        }\r
-        tab.addMsg(null, plugin_event.nick, plugin_event.msg);\r
         \r
         var chan = kiwi.bbchans.detect(function (c) {\r
             return c.get("name") === plugin_event.destination;\r
         });\r
-        chan.addMsg(null, plugin_event.nick, plugin_event.msg);\r
+        if (chan) {\r
+            chan.addMsg(null, plugin_event.nick, plugin_event.msg);\r
+        }\r
     },\r
 \r
     /**\r
@@ -101,11 +98,12 @@ kiwi.front.events = {
             destination = data.channel;\r
         }\r
 \r
-        tab = Tabview.getTab(destination);\r
-        if (!tab) {\r
-            tab = new Tabview(destination);\r
+        var chan = kiwi.bbchans.detect(function (c) {\r
+            return c.get("name") === plugin_event.destination;\r
+        });\r
+        if (chan) {\r
+            chan.addMsg(null, ' ', '* ' + data.nick + ' ' + data.msg, 'action', 'color:#555;');\r
         }\r
-        tab.addMsg(null, ' ', '* ' + data.nick + ' ' + data.msg, 'action', 'color:#555;');\r
     },\r
 \r
     /**\r
@@ -114,9 +112,11 @@ kiwi.front.events = {
     *   @param  {Object}        data    The event data\r
     */\r
     onTopic: function (e, data) {\r
-        var tab = Tabview.getTab(data.channel);\r
-        if (tab) {\r
-            tab.changeTopic(data.topic);\r
+        var chan = kiwi.bbchans.detect(function (c) {\r
+            return c.get("name") === data.channel;\r
+        });\r
+        if (chan) {\r
+            chan.set({"topic": data.topic});\r
         }\r
     },\r
 \r
@@ -126,11 +126,12 @@ kiwi.front.events = {
     *   @param  {Object}        data    The event data\r
     */\r
     onTopicSetBy: function (e, data) {\r
-        var when, tab = Tabview.getTab(data.channel);\r
-        if (tab) {\r
-            when = new Date(data.when * 1000).toLocaleString();\r
-            tab.addMsg(null, '', 'Topic set by ' + data.nick + ' at ' + when, 'topic');\r
-        }\r
+        var when,\r
+            chan = kiwi.bbchans.detect(function (c) {\r
+                return c.get("name") === data.channel;\r
+            });\r
+        when = new Date(data.when * 1000).toLocaleString();\r
+        chan.addMsg(null, '', 'Topic set by ' + data.nick + ' at ' + when, 'topic');\r
     },\r
 \r
     /**\r
@@ -140,14 +141,14 @@ kiwi.front.events = {
     */\r
     onNotice: function (e, data) {\r
         var nick = (data.nick === undefined) ? '' : data.nick,\r
-            enick = '[' + nick + ']';\r
+            enick = '[' + nick + ']',\r
+            chan;\r
 \r
-        if (Tabview.tabExists(data.target)) {\r
-            Tabview.getTab(data.target).addMsg(null, enick, data.msg, 'notice');\r
-        } else if (Tabview.tabExists(nick)) {\r
-            Tabview.getTab(nick).addMsg(null, enick, data.msg, 'notice');\r
-        } else {\r
-            Tabview.getServerTab().addMsg(null, enick, data.msg, 'notice');\r
+        chan = kiwi.bbchans.detect(function (c) {\r
+            return c.get("name") === data.channel;\r
+        });\r
+        if (chan) {\r
+            chan.addMsg(null, enick, data.msg, 'notice');\r
         }\r
     },\r
 \r
@@ -206,15 +207,15 @@ kiwi.front.events = {
                 err_box.parent().removeClass('disconnect');\r
 \r
                 // Rejoin channels\r
-                channels = '';\r
-                _.each(Tabview.getAllTabs(), function (tabview) {\r
-                    if (tabview.name === 'server') {\r
-                        return;\r
-                    }\r
-                    channels += tabview.name + ',';\r
-                });\r
-                console.log('Rejoining: ' + channels);\r
-                kiwi.gateway.join(channels);\r
+                //channels = '';\r
+                //_.each(Tabview.getAllTabs(), function (tabview) {\r
+                //    if (tabview.name === 'server') {\r
+                //        return;\r
+                //    }\r
+                //    channels += tabview.name + ',';\r
+                //});\r
+                //console.log('Rejoining: ' + channels);\r
+                //kiwi.gateway.join(channels);\r
                 return;\r
             }\r
 \r
@@ -223,13 +224,13 @@ kiwi.front.events = {
                 kiwi.front.ui.doLayout();\r
             }\r
 \r
-            Tabview.getServerTab().addMsg(null, ' ', '=== Connected OK :)', 'status');\r
-            if (typeof init_data.channel === "string") {\r
-                kiwi.front.joinChannel(init_data.channel);\r
-            }\r
+            //Tabview.getServerTab().addMsg(null, ' ', '=== Connected OK :)', 'status');\r
+            //if (typeof init_data.channel === "string") {\r
+            //    kiwi.front.joinChannel(init_data.channel);\r
+            //}\r
             kiwi.plugs.run('connect', {success: true});\r
         } else {\r
-            Tabview.getServerTab().addMsg(null, ' ', '=== Failed to connect :(', 'status');\r
+            //Tabview.getServerTab().addMsg(null, ' ', '=== Failed to connect :(', 'status');\r
             kiwi.plugs.run('connect', {success: false});\r
         }\r
 \r
@@ -243,7 +244,7 @@ kiwi.front.events = {
     */\r
     onConnectFail: function (e, data) {\r
         var reason = (typeof data.reason === 'string') ? data.reason : '';\r
-        Tabview.getServerTab().addMsg(null, '', 'There\'s a problem connecting! (' + reason + ')', 'error');\r
+        //Tabview.getServerTab().addMsg(null, '', 'There\'s a problem connecting! (' + reason + ')', 'error');\r
         kiwi.plugs.run('connect', {success: false});\r
     },\r
     /**\r
@@ -253,10 +254,10 @@ kiwi.front.events = {
     */\r
     onDisconnect: function (e, data) {\r
         var tab, tabs;\r
-        tabs = Tabview.getAllTabs();\r
-        for (tab in tabs) {\r
-            tabs[tab].addMsg(null, '', 'Disconnected from server!', 'error disconnect');\r
-        }\r
+        //tabs = Tabview.getAllTabs();\r
+        //for (tab in tabs) {\r
+        //    tabs[tab].addMsg(null, '', 'Disconnected from server!', 'error disconnect');\r
+        //}\r
         kiwi.plugs.run('disconnect', {success: false});\r
     },\r
     /**\r
@@ -306,7 +307,7 @@ kiwi.front.events = {
     */\r
     onOptions: function (e, data) {\r
         if (typeof kiwi.gateway.network_name === "string" && kiwi.gateway.network_name !== "") {\r
-            Tabview.getServerTab().setTabText(kiwi.gateway.network_name);\r
+            //Tabview.getServerTab().setTabText(kiwi.gateway.network_name);\r
         }\r
     },\r
     /**\r
@@ -315,7 +316,7 @@ kiwi.front.events = {
     *   @param  {Object}        data    The event data\r
     */\r
     onMOTD: function (e, data) {\r
-        Tabview.getServerTab().addMsg(null, data.server, data.msg, 'motd');\r
+        //Tabview.getServerTab().addMsg(null, data.server, data.msg, 'motd');\r
     },\r
     /**\r
     *   Handles the whois event\r
@@ -335,18 +336,18 @@ kiwi.front.events = {
             idle_time = idle_time.h.toString().lpad(2, "0") + ':' + idle_time.m.toString().lpad(2, "0") + ':' + idle_time.s.toString().lpad(2, "0");\r
         }\r
 \r
-        tab = Tabview.getCurrentTab();\r
-        if (data.msg) {\r
-            tab.addMsg(null, data.nick, data.msg, 'whois');\r
-        } else if (data.logon) {\r
-            d = new Date();\r
-            d.setTime(data.logon * 1000);\r
-            d = d.toLocaleString();\r
+        //tab = Tabview.getCurrentTab();\r
+        //if (data.msg) {\r
+        //    tab.addMsg(null, data.nick, data.msg, 'whois');\r
+        //} else if (data.logon) {\r
+        //    d = new Date();\r
+        //    d.setTime(data.logon * 1000);\r
+        //    d = d.toLocaleString();\r
 \r
-            tab.addMsg(null, data.nick, 'idle for ' + idle_time + ', signed on ' + d, 'whois');\r
-        } else {\r
-            tab.addMsg(null, data.nick, 'idle for ' + idle_time, 'whois');\r
-        }\r
+        //    tab.addMsg(null, data.nick, 'idle for ' + idle_time + ', signed on ' + d, 'whois');\r
+        //} else {\r
+        //    tab.addMsg(null, data.nick, 'idle for ' + idle_time, 'whois');\r
+        //}\r
     },\r
     /**\r
     *   Handles the mode event\r
@@ -354,15 +355,30 @@ kiwi.front.events = {
     *   @param  {Object}        data    The event data\r
     */\r
     onMode: function (e, data) {\r
-        var tab;\r
+        var tab, mem;\r
         if ((typeof data.channel === 'string') && (typeof data.effected_nick === 'string')) {\r
-            tab = Tabview.getTab(data.channel);\r
-            tab.addMsg(null, ' ', '[' + data.mode + '] ' + data.effected_nick + ' by ' + data.nick, 'mode', '');\r
-            if (tab.userlist.hasUser(data.effected_nick)) {\r
-                tab.userlist.changeUserMode(data.effected_nick, data.mode.substr(1), (data.mode[0] === '+'));\r
+        //    tab = Tabview.getTab(data.channel);\r
+        //    tab.addMsg(null, ' ', '[' + data.mode + '] ' + data.effected_nick + ' by ' + data.nick, 'mode', '');\r
+        //    if (tab.userlist.hasUser(data.effected_nick)) {\r
+        //        tab.userlist.changeUserMode(data.effected_nick, data.mode.substr(1), (data.mode[0] === '+'));\r
+        //    }\r
+            chan = kiwi.bbchans.detect(function (c) {\r
+                return c.get("name") === data.channel;\r
+            });\r
+            if (chan) {\r
+                chan.addMsg(null, ' ', '[' + data.mode + '] ' + data.effected_nick + ' by ' + data.nick, 'mode', '');\r
+                mem = _.detect(chan.get("members"), function (m) {\r
+                    return data.effected_nick === m.get("nick");\r
+                });\r
+                if (mem) {\r
+                    if (data.mode[0] === '+') {\r
+                        mem.addMode(data.mode);\r
+                    } else {\r
+                        mem.removeMode(data.mode);\r
+                    }\r
+                }\r
             }\r
         }\r
-\r
         // TODO: Other mode changes that aren't +/- qaohv. - JA\r
     },\r
     /**\r
@@ -371,24 +387,29 @@ kiwi.front.events = {
     *   @param  {Object}        data    The event data\r
     */\r
     onUserList: function (e, data) {\r
-        var tab;\r
+        var tab, chan;\r
 \r
-        tab = Tabview.getTab(data.channel);\r
-        if (!tab) {\r
-            return;\r
-        }\r
+        //tab = Tabview.getTab(data.channel);\r
+        //if (!tab) {\r
+        //    return;\r
+        //}\r
 \r
+        chan = kiwi.bbchans.detect(function (c) {\r
+            return c.get("name") === data.channel;\r
+        });\r
         if ((!kiwi.front.cache.userlist) || (!kiwi.front.cache.userlist.updating)) {\r
             if (!kiwi.front.cache.userlist) {\r
                 kiwi.front.cache.userlist = {updating: true};\r
             } else {\r
                 kiwi.front.cache.userlist.updating = true;\r
             }\r
-            tab.userlist.empty();\r
+            chan.get("members").reset();\r
         }\r
 \r
-        tab.userlist.addUser(data.users);\r
-\r
+        //tab.userlist.addUser(data.users);\r
+        if (chan) {\r
+            chan.get("members").add(data.users, {"silent": true});\r
+        }\r
     },\r
     /**\r
     *   Handles the userListEnd event\r
@@ -400,6 +421,12 @@ kiwi.front.events = {
             kiwi.front.cache.userlist = {};\r
         }\r
         kiwi.front.cache.userlist.updating = false;\r
+        chan = kiwi.bbchans.detect(function (c) {\r
+            return c.get("name") === data.channel;\r
+        });\r
+        if (chan) {\r
+            chan.get("members").trigger("change");\r
+        }\r
     },\r
 \r
     /**\r
@@ -452,21 +479,30 @@ kiwi.front.events = {
     *   @param  {Object}        data    The event data\r
     */\r
     onJoin: function (e, data) {\r
-        var tab = Tabview.getTab(data.channel);\r
-        if (!tab) {\r
-            tab = new Tabview(data.channel.toLowerCase());\r
-        }\r
+        //var tab = Tabview.getTab(data.channel);\r
+        //if (!tab) {\r
+        //    tab = new Tabview(data.channel.toLowerCase());\r
+        //}\r
 \r
-        tab.addMsg(null, ' ', '--> ' + data.nick + ' [' + data.ident + '@' + data.hostname + '] has joined', 'action join', 'color:#009900;');\r
+        //tab.addMsg(null, ' ', '--> ' + data.nick + ' [' + data.ident + '@' + data.hostname + '] has joined', 'action join', 'color:#009900;');\r
 \r
-        var c = new kiwi.model.Channel({"name": data.channel.toLowerCase()});\r
-        c.get("members").add(new kiwi.model.Member({"nick": data.nick, "modes": []}));\r
-        kiwi.bbchans.add(c);\r
-        if (data.nick === kiwi.gateway.nick) {\r
-            return; // Not needed as it's already in nicklist\r
+        chan = kiwi.bbchans.detect(function (c) {\r
+            return c.get("name") === data.channel;\r
+        });\r
+        if (!chan) {\r
+            chan = new kiwi.model.Channel({"name": data.channel.toLowerCase()});\r
+            chan.get("members").add(new kiwi.model.Member({"nick": data.nick, "modes": []}));\r
+            kiwi.bbchans.add(chan);\r
+        } else {\r
+            chan.get("members").add(data.users, {"silent": true});\r
         }\r
+        chan.view.show();\r
+\r
+        //if (data.nick === kiwi.gateway.nick) {\r
+        //    return; // Not needed as it's already in nicklist\r
+        //}\r
 \r
-        tab.userlist.addUser({nick: data.nick, modes: []});\r
+        //tab.userlist.addUser({nick: data.nick, modes: []});\r
     },\r
     /**\r
     *   Handles the part event\r
@@ -474,17 +510,32 @@ kiwi.front.events = {
     *   @param  {Object}        data    The event data\r
     */\r
     onPart: function (e, data) {\r
-        var tab = Tabview.getTab(data.channel);\r
-        if (tab) {\r
+        var chan, members;\r
+        //var tab = Tabview.getTab(data.channel);\r
+        //if (tab) {\r
             // If this is us, close the tabview\r
+        //    if (data.nick === kiwi.gateway.nick) {\r
+        //        tab.close();\r
+        //        Tabview.getServerTab().show();\r
+        //        return;\r
+        //    }\r
+\r
+        //    tab.addMsg(null, ' ', '<-- ' + data.nick + ' has left (' + data.message + ')', 'action part', 'color:#990000;');\r
+        //    tab.userlist.removeUser(data.nick);\r
+        //}\r
+        chan = kiwi.bbchans.detect(function (c) {\r
+            return c.get("name") === data.channel;\r
+        });\r
+        if (chan) {\r
             if (data.nick === kiwi.gateway.nick) {\r
-                tab.close();\r
-                Tabview.getServerTab().show();\r
-                return;\r
+                chan.trigger("close");\r
+            } else {\r
+                chan.addMsg(null, ' ', '<-- ' + data.nick + ' has left (' + data.message + ')', 'action part', 'color:#990000;');\r
+                members = chan.get("members");\r
+                members.remove(_.detect(members, function (m) {\r
+                    return data.nick === m.get("nick");\r
+                }));\r
             }\r
-\r
-            tab.addMsg(null, ' ', '<-- ' + data.nick + ' has left (' + data.message + ')', 'action part', 'color:#990000;');\r
-            tab.userlist.removeUser(data.nick);\r
         }\r
     },\r
     /**\r
@@ -493,20 +544,20 @@ kiwi.front.events = {
     *   @param  {Object}        data    The event data\r
     */\r
     onKick: function (e, data) {\r
-        var tab = Tabview.getTab(data.channel);\r
-        if (tab) {\r
-            // If this is us, close the tabview\r
-            if (data.kicked === kiwi.gateway.nick) {\r
-                //tab.close();\r
-                tab.addMsg(null, ' ', '=== You have been kicked from ' + data.channel + '. ' + data.message, 'status kick');\r
-                tab.safe_to_close = true;\r
-                tab.userlist.remove();\r
-                return;\r
-            }\r
-\r
-            tab.addMsg(null, ' ', '<-- ' + data.kicked + ' kicked by ' + data.nick + '(' + data.message + ')', 'action kick', 'color:#990000;');\r
-            tab.userlist.removeUser(data.nick);\r
-        }\r
+        //var tab = Tabview.getTab(data.channel);\r
+        //if (tab) {\r
+        //    // If this is us, close the tabview\r
+        //    if (data.kicked === kiwi.gateway.nick) {\r
+        //        //tab.close();\r
+        //        tab.addMsg(null, ' ', '=== You have been kicked from ' + data.channel + '. ' + data.message, 'status kick');\r
+        //        tab.safe_to_close = true;\r
+        //        tab.userlist.remove();\r
+        //        return;\r
+        //    }\r
+\r
+        //    tab.addMsg(null, ' ', '<-- ' + data.kicked + ' kicked by ' + data.nick + '(' + data.message + ')', 'action kick', 'color:#990000;');\r
+        //    tab.userlist.removeUser(data.nick);\r
+        //}\r
     },\r
     /**\r
     *   Handles the nick event\r
@@ -514,17 +565,17 @@ kiwi.front.events = {
     *   @param  {Object}        data    The event data\r
     */\r
     onNick: function (e, data) {\r
-        if (data.nick === kiwi.gateway.nick) {\r
-            kiwi.gateway.nick = data.newnick;\r
-            kiwi.front.ui.doLayout();\r
-        }\r
+        //if (data.nick === kiwi.gateway.nick) {\r
+        //    kiwi.gateway.nick = data.newnick;\r
+        //    kiwi.front.ui.doLayout();\r
+        //}\r
 \r
-        _.each(Tabview.getAllTabs(), function (tab) {\r
-            if (tab.userlist.hasUser(data.nick)) {\r
-                tab.userlist.renameUser(data.nick, data.newnick);\r
-                tab.addMsg(null, ' ', '=== ' + data.nick + ' is now known as ' + data.newnick, 'action changenick');\r
-            }\r
-        });\r
+        //_.each(Tabview.getAllTabs(), function (tab) {\r
+        //    if (tab.userlist.hasUser(data.nick)) {\r
+        //        tab.userlist.renameUser(data.nick, data.newnick);\r
+        //        tab.addMsg(null, ' ', '=== ' + data.nick + ' is now known as ' + data.newnick, 'action changenick');\r
+        //    }\r
+        //});\r
     },\r
     /**\r
     *   Handles the quit event\r
@@ -532,12 +583,12 @@ kiwi.front.events = {
     *   @param  {Object}        data    The event data\r
     */\r
     onQuit: function (e, data) {\r
-        _.each(Tabview.getAllTabs(), function (tab) {\r
-            if (tab.userlist.hasUser(data.nick)) {\r
-                tab.userlist.removeUser(data.nick);\r
-                tab.addMsg(null, ' ', '<-- ' + data.nick + ' has quit (' + data.message + ')', 'action quit', 'color:#990000;');\r
-            }\r
-        });\r
+        //_.each(Tabview.getAllTabs(), function (tab) {\r
+        //    if (tab.userlist.hasUser(data.nick)) {\r
+        //        tab.userlist.removeUser(data.nick);\r
+        //        tab.addMsg(null, ' ', '<-- ' + data.nick + ' has quit (' + data.message + ')', 'action quit', 'color:#990000;');\r
+        //    }\r
+        //});\r
     },\r
     /**\r
     *   Handles the channelRedirect event\r
@@ -545,10 +596,10 @@ kiwi.front.events = {
     *   @param  {Object}        data    The event data\r
     */\r
     onChannelRedirect: function (e, data) {\r
-        var tab = Tabview.getTab(data.from);\r
-        tab.close();\r
-        tab = new Tabview(data.to);\r
-        tab.addMsg(null, ' ', '=== Redirected from ' + data.from, 'action');\r
+        //var tab = Tabview.getTab(data.from);\r
+        //tab.close();\r
+        //tab = new Tabview(data.to);\r
+        //tab.addMsg(null, ' ', '=== Redirected from ' + data.from, 'action');\r
     },\r
 \r
     /**\r
@@ -557,7 +608,7 @@ kiwi.front.events = {
     *   @param  {Object}        data    The event data\r
     */\r
     onIRCError: function (e, data) {\r
-        var t_view,\r
+        /*var t_view,\r
             tab = Tabview.getTab(data.channel);\r
         if (data.channel !== undefined && tab) {\r
             t_view = data.channel;\r
@@ -604,7 +655,7 @@ kiwi.front.events = {
         default:\r
             // We don't know what data contains, so don't do anything with it.\r
             console.log(e, data);\r
-        }\r
+        }*/\r
     },\r
 \r
 \r
index 99c516418d99a357e4265ca803f877bf218035e9..4dba63b90fa6ef6fad59f342943f276523bc40bc 100755 (executable)
@@ -172,15 +172,15 @@ kiwi.front = {
         kiwi.bbtabs = new kiwi.view.Tabs({"el": $('#kiwi .windowlist ul')[0], "model": kiwi.bbchans});
         
         
-        server_tabview = new Tabview('server');
-        server_tabview.userlist.setWidth(0); // Disable the userlist
-        server_tabview.setIcon('/img/app_menu.png');
-        $('.icon', server_tabview.tab).tipTip({
-            delay: 0,
-            keepAlive: true,
-            content: $('#tmpl_network_menu').tmpl({}).html(),
-            activation: 'click'
-        });
+        //server_tabview = new Tabview('server');
+        //server_tabview.userlist.setWidth(0); // Disable the userlist
+        //server_tabview.setIcon('/img/app_menu.png');
+        //$('.icon', server_tabview.tab).tipTip({
+        //    delay: 0,
+        //    keepAlive: true,
+        //    content: $('#tmpl_network_menu').tmpl({}).html(),
+        //    activation: 'click'
+        //});
 
         // Any pre-defined nick?
         if (typeof window.init_data.nick === "string") {
@@ -224,8 +224,8 @@ kiwi.front = {
             var chan, text;
             text = $(this).text();
             if (text !== kiwi.front.cache.original_topic) {
-                chan = Tabview.getCurrentTab().name;
-                kiwi.gateway.topic(chan, text);
+                //chan = Tabview.getCurrentTab().name;
+                //kiwi.gateway.topic(chan, text);
             }
         });
 
@@ -259,13 +259,13 @@ kiwi.front = {
             tab;
         for (i in chans) {
             chan = chans[i];
-            tab = Tabview.getTab(chan);
-            if ((!tab) || (tab.safe_to_close === true)) {
+            //tab = Tabview.getTab(chan);
+            //if ((!tab) || (tab.safe_to_close === true)) {
                 kiwi.gateway.join(chan);
-                tab = new Tabview(chan);
-            } else {
-                tab.show();
-            }
+                //tab = new Tabview(chan);
+            //} else {
+            //    tab.show();
+            //}
         }
     },
 
@@ -313,7 +313,8 @@ kiwi.front = {
                     parts[3] = true;
                 }
 
-                Tabview.getCurrentTab().addMsg(null, ' ', '=== Connecting to ' + parts[1] + ' on port ' + parts[2] + (parts[3] ? ' using SSL' : '') + '...', 'status');
+                //Tabview.getCurrentTab().addMsg(null, ' ', '=== Connecting to ' + parts[1] + ' on port ' + parts[2] + (parts[3] ? ' using SSL' : '') + '...', 'status');
+                console.log('Connecting to ' + parts[1] + ' on port ' + parts[2] + (parts[3] ? ' using SSL' : '') + '...');
                 kiwi.gateway.connect(parts[1], parts[2], parts[3], parts[4]);
                 break;
 
@@ -330,11 +331,11 @@ kiwi.front = {
 
             case '/part':
                 if (typeof parts[1] === "undefined") {
-                    if (Tabview.getCurrentTab().safe_to_close) {
-                        Tabview.getCurrentTab().close();
-                    } else {
-                        kiwi.gateway.part(Tabview.getCurrentTab().name);
-                    }
+                    //if (Tabview.getCurrentTab().safe_to_close) {
+                    //    Tabview.getCurrentTab().close();
+                    //} else {
+                    //    kiwi.gateway.part(Tabview.getCurrentTab().name);
+                    //}
                 } else {
                     kiwi.gateway.part(msg.substring(6));
                 }
@@ -353,7 +354,7 @@ kiwi.front = {
             case '/q':
             case '/query':
                 if (typeof parts[1] !== "undefined") {
-                    tab = new Tabview(parts[1]);
+                    //tab = new Tabview(parts[1]);
                 }
                 break;
 
@@ -364,11 +365,11 @@ kiwi.front = {
                     msg_sliced = msg.split(' ').slice(2).join(' ');
                     kiwi.gateway.privmsg(parts[1], msg_sliced);
 
-                    tab = Tabview.getTab(parts[1]);
-                    if (!tab) {
-                        tab = new Tabview(parts[1]);
-                    }
-                    tab.addMsg(null, kiwi.gateway.nick, msg_sliced);
+                    //tab = Tabview.getTab(parts[1]);
+                    //if (!tab) {
+                    //    tab = new Tabview(parts[1]);
+                    //}
+                    //tab.addMsg(null, kiwi.gateway.nick, msg_sliced);
                 }
                 break;
 
@@ -379,7 +380,7 @@ kiwi.front = {
                 }
                 t = msg.split(' ', 3);
                 nick = t[1];
-                kiwi.gateway.kick(Tabview.getCurrentTab().name, nick, t[2]);
+                //kiwi.gateway.kick(Tabview.getCurrentTab().name, nick, t[2]);
                 break;
 
             case '/quote':
@@ -387,9 +388,9 @@ kiwi.front = {
                 break;
 
             case '/me':
-                tab = Tabview.getCurrentTab();
-                kiwi.gateway.ctcp(true, 'ACTION', tab.name, msg.substring(4));
-                tab.addMsg(null, ' ', '* ' + kiwi.gateway.nick + ' ' + msg.substring(4), 'action', 'color:#555;');
+                //tab = Tabview.getCurrentTab();
+                //kiwi.gateway.ctcp(true, 'ACTION', tab.name, msg.substring(4));
+                //tab.addMsg(null, ' ', '* ' + kiwi.gateway.nick + ' ' + msg.substring(4), 'action', 'color:#555;');
                 break;
 
             case '/notice':
@@ -424,12 +425,12 @@ kiwi.front = {
                         t.setSelectionRange(pos, pos);
                     }
                 } else {
-                    kiwi.gateway.topic(Tabview.getCurrentTab().name, msg.split(' ', 2)[1]);
+                    //kiwi.gateway.topic(Tabview.getCurrentTab().name, msg.split(' ', 2)[1]);
                 }
                 break;
 
             case '/kiwi':
-                kiwi.gateway.ctcp(true, 'KIWI', Tabview.getCurrentTab().name, msg.substring(6));
+                //kiwi.gateway.ctcp(true, 'KIWI', Tabview.getCurrentTab().name, msg.substring(6));
                 break;
 
             case '/ctcp':
@@ -440,7 +441,7 @@ kiwi.front = {
                 console.log(parts);
                 
                 kiwi.gateway.ctcp(true, t, dest, msg);
-                Tabview.getServerTab().addMsg(null, 'CTCP Request', '[to ' + dest + '] ' + t + ' ' + msg, 'ctcp');
+                //Tabview.getServerTab().addMsg(null, 'CTCP Request', '[to ' + dest + '] ' + t + ' ' + msg, 'ctcp');
                 break;
             default:
                 //Tabview.getCurrentTab().addMsg(null, ' ', '--> Invalid command: '+parts[0].substring(1));
@@ -453,10 +454,10 @@ kiwi.front = {
             if (msg.trim() === '') {
                 return;
             }
-            if (Tabview.getCurrentTab().name !== 'server') {
-                kiwi.gateway.privmsg(Tabview.getCurrentTab().name, msg);
-                Tabview.getCurrentTab().addMsg(null, kiwi.gateway.nick, msg);
-            }
+            //if (Tabview.getCurrentTab().name !== 'server') {
+            //    kiwi.gateway.privmsg(Tabview.getCurrentTab().name, msg);
+            //    Tabview.getCurrentTab().addMsg(null, kiwi.gateway.nick, msg);
+            //}
         }
     },
 
@@ -471,7 +472,7 @@ kiwi.front = {
         var win_list = $('#kiwi .windowlist ul'),
             listitems = win_list.children('li').get();
         
-        listitems.sort(function (a, b) {
+        /*listitems.sort(function (a, b) {
             if (a === Tabview.getServerTab().tab[0]) {
                 return -1;
             }
@@ -485,7 +486,7 @@ kiwi.front = {
 
         $.each(listitems, function(idx, itm) {
             win_list.append(itm);
-        });
+        });*/
     },
 
 
@@ -752,459 +753,6 @@ var ChannelList = function () {
     };
 };
 
-/**
-*   @constructor
-*   @param  {String}    name    The name of the UserList
-*/
-var UserList = function (name) {
-    /*globals User */
-    var userlist, list_html, sortUsers;
-
-    userlist = [];
-
-    $('#kiwi .userlist').append($('<ul id="kiwi_userlist_' + name + '"></ul>'));
-    list_html = $('#kiwi_userlist_' + name);
-    $('a.nick', list_html[0]).live('click', this.clickHandler);
-
-    /**
-    *   @inner
-    */
-    sortUsers = function () {
-        var parent;
-        parent = list_html.parent();
-        list_html = list_html.detach();
-
-        // Not sure this is needed. 
-        // It's O(n^2) as well, so need to test to see if it works without.
-        // Alternative to test: list_html.children('li').detach();
-        list_html.children().each(function (child) {
-            var i, nick;
-            child = $(child);
-            nick = child.data('nick');
-            for (i = 0; i < userlist.length; i++) {
-                if (userlist[i].nick === nick) {
-                    userlist[i].html = child.detach();
-                    break;
-                }
-            }
-        });
-
-        userlist.sort(User.compare);
-
-        _.each(userlist, function (user) {
-            user.html = user.html.appendTo(list_html);
-        });
-
-        list_html = list_html.appendTo(parent);
-    };
-
-    /**
-    *   Adds a user or users to the UserList.
-    *   Chainable method.
-    *   @param      {Object}    users   The user or Array of users to add
-    *   @returns    {UserList}          This UserList
-    */
-    this.addUser = function (users) {
-        if (!_.isArray(users)) {
-            users = [users];
-        }
-        _.each(users, function (user) {
-            user = new User(user.nick, user.modes);
-            user.html = $('<li><a class="nick">' + user.prefix + user.nick + '</a></li>');
-            user.html.data('user', user);
-            userlist.push(user);
-        });
-        sortUsers();
-
-        return this;
-    };
-
-    /**
-    *   Removes a user or users from the UserList.
-    *   Chainable method.
-    *   @param      {String}    nicks   The nick or Array of nicks to remove
-    *   @returns    {UserList}          This UserList
-    */
-    this.removeUser = function (nicks) {
-        var toRemove;
-        if (!_.isArray(nicks)) {
-            nicks = [nicks];
-        }
-        toRemove = _.select(userlist, function (user) {
-            return _.any(nicks, function (n) {
-                return n === user.nick;
-            });
-        });
-
-        _.each(toRemove, function (user) {
-            user.html.remove();
-        });
-
-        userlist = _.difference(userlist, toRemove);
-
-        return this;
-    };
-
-    /**
-    *   Renames a user in the UserList.
-    *   Chainable method.
-    *   @param      {String}    oldNick The old nick
-    *   @param      {String}    newNick The new nick
-    *   @returns    {UserList}          This UserList
-    */
-    this.renameUser = function (oldNick, newNick) {
-        var user = _.detect(userlist, function (u) {
-            return u.nick === oldNick;
-        });
-        if (user) {
-            user.nick = newNick;
-            user.html.text(User.getPrefix(user.modes) + newNick);
-        }
-
-        sortUsers();
-
-        return this;
-    };
-
-    /**
-    *   Lists the users in this UserList.
-    *   @param      {Boolean}   modesort    True to enable sorting by mode, false for lexicographical sort
-    *   @param      {Array}     mode        If specified, only return those users who have the specified modes
-    *   @returns    {Array}                 The users in the UserList that match the criteria
-    */
-    this.listUsers = function (modesort, modes) {
-        var users = userlist;
-        if (modes) {
-            users = _.select(users, function (user) {
-                return _.any(modes, function (m) {
-                    return _.any(user.modes, function (um) {
-                        return m === um;
-                    });
-                });
-            });
-        }
-        if ((modesort === true) || (typeof modesort === undefined)) {
-            return users;
-        } else {
-            return _.sortBy(users, function (user) {
-                return user.nick;
-            });
-        }
-    };
-
-    /**
-    *   Remove this UserList from the DOM.
-    */
-    this.remove = function () {
-        list_html.remove();
-        list_html = null;
-        userlist = null;
-    };
-
-    /**
-    *   Empty the UserList.
-    *   Chainable method.
-    *   @returns    {UserList}  This UserList
-    */
-    this.empty = function () {
-        list_html.children().remove();
-        userlist = [];
-
-        return this;
-    };
-
-    /**
-    *   Checks whether a given nick is in the UserList.
-    *   @param      {String}    nick    The nick to search for
-    *   @returns    {Boolean}           True if the nick is in the userlist, false otherwise
-    */
-    this.hasUser = function (nick) {
-        return _.any(userlist, function (user) {
-            return user.nick === nick;
-        });
-    };
-
-    /**
-    *   Returns the object representing the user with the given nick, if it is in the UserList.
-    *   @param      {String}    nick    The nick to retrieve
-    *   @returns    {Object}            An object representing the user, if it exists, null otherwise
-    */
-    this.getUser = function (nick) {
-        if (this.hasUser(nick)) {
-            return _.detect(userlist, function (user) {
-                return user.nick === nick;
-            });
-        } else {
-            return null;
-        }
-    };
-
-    /**
-    *   Sets the UserList's activity.
-    *   Chainable method.
-    *   @param      {Boolean}   active  If true, sets the UserList to active. If False, sets it to inactive
-    *   @returns    {UserList}          This UserList
-    */
-    this.active = function (active) {
-        if ((arguments.length === 0) || (active)) {
-            list_html.addClass('active');
-            list_html.show();
-        } else {
-            list_html.removeClass('active');
-            list_html.hide();
-        }
-
-        return this;
-    };
-
-    /**
-    *   Updates a user's mode.
-    *   Chainable method.
-    *   @param      {String}    nick    The nick of the user to modify
-    *   @param      {String}    mode    The mode to add or remove
-    *   @param      {Boolean}   add     Adds the mode if true, removes it otherwise
-    *   @returns    {UserList}          This UserList
-    */
-    this.changeUserMode = function (nick, mode, add) {
-        var user, prefix;
-        if (this.hasUser(nick)) {
-            user = _.detect(userlist, function (u) {
-                return u.nick === nick;
-            });
-
-            prefix = user.prefix;
-            if ((arguments.length < 3) || (add)) {
-                user.addMode(mode);
-            } else {
-                user.removeMode(mode);
-            }
-            if (prefix !== user.prefix) {
-                user.html.children('a:first').text(user.prefix + user.nick);
-            }
-            sortUsers();
-        }
-
-        return this;
-    };
-};
-/**
-*   @memberOf UserList
-*/
-UserList.prototype.width = 100;     // 0 to disable
-/**
-*   Sets the width of the UserList.
-*   Chainable method.
-*   @param      {Number}    newWidth    The new width of the UserList
-*   @returns    {UserList}              This UserList
-*/
-UserList.prototype.setWidth = function (newWidth) {
-    var w, u;
-    if (typeof newWidth === 'number') {
-        this.width = newWidth;
-    }
-
-    w = $('#windows');
-    u = $('#kiwi .userlist');
-
-    u.width(this.width);
-
-    return this;
-};
-/**
-*   The click handler for this UserList
-*/
-UserList.prototype.clickHandler = function () {
-    var li = $(this).parent(),
-        user = li.data('user'),
-        userbox;
-    
-    // Remove any existing userboxes
-    $('#kiwi .userbox').remove();
-
-    if (li.data('userbox') === true) {
-        // This li already has the userbox, show remove it instead
-        li.removeData('userbox');
-
-    } else {
-        // We don't have a userbox so create one
-        userbox = $('#tmpl_user_box').tmpl({nick: user.nick}).appendTo(li);
-
-        $('.userbox_query', userbox).click(function (ev) {
-            var nick = $('#kiwi .userbox_nick').val();
-            kiwi.front.run('/query ' + nick);
-        });
-
-        $('.userbox_whois', userbox).click(function (ev) {
-            var nick = $('#kiwi .userbox_nick').val();
-            kiwi.front.run('/whois ' + nick);
-        });
-        li.data('userbox', true);
-    }
-};
-
-
-
-/**
-*   @constructor
-*   The User class. Represents a user on a channel.
-*   @param      {String}    nick    The user's nickname
-*   @param      {Array}     modes   An array of channel user modes
-*/
-var User = function (nick, modes) {
-    var sortModes;
-    /**
-    *   @inner
-    */
-    sortModes = function (modes) {
-        return modes.sort(function (a, b) {
-            var a_idx, b_idx, i;
-            for (i = 0; i < kiwi.gateway.user_prefixes.length; i++) {
-                if (kiwi.gateway.user_prefixes[i].mode === a) {
-                    a_idx = i;
-                }
-            }
-            for (i = 0; i < kiwi.gateway.user_prefixes.length; i++) {
-                if (kiwi.gateway.user_prefixes[i].mode === b) {
-                    b_idx = i;
-                }
-            }
-            if (a_idx < b_idx) {
-                return -1;
-            } else if (a_idx > b_idx) {
-                return 1;
-            } else {
-                return 0;
-            }
-        });
-    };
-
-    this.nick = User.stripPrefix(nick);
-    this.modes = modes || [];
-    this.modes = sortModes(this.modes);
-    this.prefix = User.getPrefix(this.modes);
-
-    /**
-    *   @inner
-    */
-    this.addMode = function (mode) {
-        this.modes.push(mode);
-        this.modes = sortModes(this.modes);
-        this.prefix = User.getPrefix(this.modes);
-        return this;
-    };
-};
-
-/**
-*   Removes a channel mode from the user
-*   @param      {String}    mode    The mode(s) to remove
-*   @returns    {User}              Returns the User object to allow chaining
-*/
-User.prototype.removeMode = function (mode) {
-    this.modes = _.reject(this.modes, function (m) {
-        return m === mode;
-    });
-    this.prefix = User.getPrefix(this.modes);
-    return this;
-};
-
-/**
-*   Checks to see if the user is an op on the channel
-*   @returns    {Boolean}   True if the user is an op, false otherwise
-*/
-User.prototype.isOp = function () {
-    // return true if this.mode[0] > o
-    return false;
-};
-
-/**
-*   Returns the highest user prefix (e.g.~, @, or +) that matches the modes given
-*   @param      {Array} modes   An array of mode letters
-*   @returns    {String}        The user's prefix
-*/
-User.getPrefix = function (modes) {
-    var prefix = '';
-    if (typeof modes[0] !== 'undefined') {
-        prefix = _.detect(kiwi.gateway.user_prefixes, function (prefix) {
-            return prefix.mode === modes[0];
-        });
-        prefix = (prefix) ? prefix.symbol : '';
-    }
-    return prefix;
-};
-
-/**
-*   Returns the user's nick without the mode prefix
-*   @param      {String}    nick    The nick to strip the prefix from
-*   @returns    {String}            The nick without the prefix
-*/
-User.stripPrefix = function (nick) {
-    var tmp = nick, i, j, k;
-    i = 0;
-    for (j = 0; j < nick.length; j++) {
-        for (k = 0; k < kiwi.gateway.user_prefixes.length; k++) {
-            if (nick.charAt(j) === kiwi.gateway.user_prefixes[k].symbol) {
-                i++;
-                break;
-            }
-        }
-    }
-
-    return tmp.substr(i);
-};
-
-/**
-*   Comparison function to order nicks based on their modes and/or nicks
-*   @param      {User}      a   The first User to evaluate
-*   @param      {User}      b   The second User to evaluate
-*   @returns    {Number}        -1 if a should be sorted before b, 1 if b should be sorted before a, and 0 if the two Users are the same.
-*/
-User.compare = function (a, b) {
-    var i, a_idx, b_idx, a_nick, b_nick;
-    // Try to sort by modes first
-    if (a.modes.length > 0) {
-        // a has modes, but b doesn't so a should appear first
-        if (b.modes.length === 0) {
-            return -1;
-        }
-        a_idx = b_idx = -1;
-        // Compare the first (highest) mode
-        for (i = 0; i < kiwi.gateway.user_prefixes.length; i++) {
-            if (kiwi.gateway.user_prefixes[i].mode === a.modes[0]) {
-                a_idx = i;
-            }
-        }
-        for (i = 0; i < kiwi.gateway.user_prefixes.length; i++) {
-            if (kiwi.gateway.user_prefixes[i].mode === b.modes[0]) {
-                b_idx = i;
-            }
-        }
-        if (a_idx < b_idx) {
-            return -1;
-        } else if (a_idx > b_idx) {
-            return 1;
-        }
-        // If we get to here both a and b have the same highest mode so have to resort to lexicographical sorting
-
-    } else if (b.modes.length > 0) {
-        // b has modes but a doesn't so b should appear first
-        return 1;
-    }
-    a_nick = a.nick.toLocaleUpperCase();
-    b_nick = b.nick.toLocaleUpperCase();
-    // Lexicographical sorting
-    if (a_nick < b_nick) {
-        return -1;
-    } else if (a_nick > b_nick) {
-        return 1;
-    } else {
-        // This should never happen; both users have the same nick.
-        console.log('Something\'s gone wrong somewhere - two users have the same nick!');
-        return 0;
-    }
-};
-
-
-
 /*
  *   MISC VIEW
  */
@@ -1321,370 +869,6 @@ Utilityview.prototype.clearPartImage = function () {
     $('#kiwi .toolbars .tab_part').remove();
 };
 
-
-
-
-
-/*
- *
- *   TABVIEWS
- *
- */
-
-/**
-*   @constructor
-*   A tab to show a channel or query window
-*   @param  {String}    v_name  The window's target's name (i.e. channel name or nickname)
-*/
-var Tabview = function (v_name) {
-    /*global Tabview, UserList */
-    var re, htmlsafe_name, tmp_divname, tmp_userlistname, tmp_tabname, tmp_tab, userlist_enabled = true;
-
-    if (v_name.charAt(0) === kiwi.gateway.channel_prefix) {
-        htmlsafe_name = 'chan_' + randomString(15);
-    } else {
-        htmlsafe_name = 'query_' + randomString(15);
-        userlist_enabled = false;
-    }
-
-    tmp_divname = 'kiwi_window_' + htmlsafe_name;
-    tmp_userlistname = 'kiwi_userlist_' + htmlsafe_name;
-    tmp_tabname = 'kiwi_tab_' + htmlsafe_name;
-
-    if (!Tabview.tabExists(v_name)) {
-        // Create the window
-        $('#kiwi .windows .scroller').append('<div id="' + tmp_divname + '" class="messages"></div>');
-        
-        // Create the window tab
-        
-        tmp_tab = $('<li id="' + tmp_tabname + '"><span></span></li>');
-        $('span', tmp_tab).text(v_name);
-        $('#kiwi .windowlist ul').append(tmp_tab);
-        tmp_tab.click(function (e) {
-            var tab = Tabview.getTab(v_name);
-            if (tab) {
-                tab.show();
-            }
-        });
-
-        kiwi.front.sortWindowList();
-    }
-
-    kiwi.front.tabviews[v_name.toLowerCase()] = this;
-    this.name = v_name;
-    this.div = $('#' + tmp_divname);
-    this.userlist = new UserList(htmlsafe_name);
-    this.tab = $('#' + tmp_tabname);
-    this.panel = $('#panel1');
-
-    if (!userlist_enabled) {
-        this.userlist.setWidth(0);
-    }
-    this.show();
-
-    if (typeof registerTouches === "function") {
-        //alert("Registering touch interface");
-        //registerTouches($('#'+tmp_divname));
-        registerTouches(document.getElementById(tmp_divname));
-    }
-
-    kiwi.front.ui.doLayoutSize();
-};
-Tabview.prototype.name = null;
-Tabview.prototype.div = null;
-Tabview.prototype.userlist = null;
-Tabview.prototype.tab = null;
-Tabview.prototype.topic = "";
-Tabview.prototype.safe_to_close = false;                // If we have been kicked/banned/etc from this channel, don't wait for a part message
-Tabview.prototype.panel = null;
-Tabview.prototype.msg_count = 0;
-/**
-*   Brings this view to the foreground
-*/
-Tabview.prototype.show = function () {
-    var w, u;
-
-    $('.messages', this.panel).removeClass("active");
-    $('#kiwi .userlist ul').removeClass("active");
-    $('#kiwi .toolbars ul li').removeClass("active");
-
-    w = $('#windows');
-    u = $('#kiwi .userlist');
-
-    this.panel.css('overflow-y', 'scroll');
-
-    // Set the window size accordingly
-    if (this.userlist.width > 0) {
-        this.userlist.setWidth();
-        w.css('right', u.outerWidth(true));
-        this.userlist.active(true);
-        // Enable the userlist resizer
-        $('#nicklist_resize').css('display', 'block');
-    } else {
-        w.css('right', 0);
-        // Disable the userlist resizer
-        $('#nicklist_resize').css('display', 'none');
-    }
-
-    this.div.addClass('active');
-    this.tab.addClass('active');
-
-    // Add the part image to the tab
-    this.addPartImage();
-
-    this.clearHighlight();
-    kiwi.front.ui.setTopicText(this.topic);
-    kiwi.front.cur_channel = this;
-
-    // If we're using fancy scrolling, refresh it
-    if (touch_scroll) {
-        touch_scroll.refresh();
-    }
-
-    this.scrollBottom();
-    if (!touchscreen) {
-        $('#kiwi_msginput').focus();
-    }
-};
-/**
-*   Removes the panel from the UI and destroys its contents
-*/
-Tabview.prototype.close = function () {
-    this.div.remove();
-    this.userlist.remove();
-    this.userlist = null;
-    this.tab.remove();
-
-    if (kiwi.front.cur_channel === this) {
-        kiwi.front.tabviews.server.show();
-    }
-    delete kiwi.front.tabviews[this.name.toLowerCase()];
-};
-/**
-*   Adds the close image to the tab
-*/
-Tabview.prototype.addPartImage = function () {
-    this.clearPartImage();
-
-    // We can't close this tab, so don't have the close image
-    if (this.name === 'server') {
-        return;
-    }
-
-    var del_html = '<img src="/img/redcross.png" class="tab_part" />';
-    this.tab.append(del_html);
-
-    $('.tab_part', this.tab).click(function () {
-        if (kiwi.front.isChannel($(this).parent().text())) {
-            kiwi.front.run("/part");
-        } else {
-            // Make sure we don't close the server tab
-            if (kiwi.front.cur_channel.name !== 'server') {
-                kiwi.front.cur_channel.close();
-            }
-        }
-    });
-};
-/**
-*   Removes the close image from the tab
-*/
-Tabview.prototype.clearPartImage = function () {
-    $('#kiwi .toolbars .tab_part').remove();
-};
-/**
-*   Sets the tab's icon
-*   @param  {String}    url     The URL of the icon to display
-*/
-Tabview.prototype.setIcon = function (url) {
-    this.tab.prepend('<img src="' + url + '" class="icon" />');
-    this.tab.css('padding-left', '33px');
-};
-/**
-*   Sets the tab's label
-*/
-Tabview.prototype.setTabText = function (text) {
-    $('span', this.tab).text(text);
-};
-/**
-*   Adds a message to the window.
-*   This method will automatically format the message (bold, underline, colours etc.)
-*   @param      {Date}      time    The timestamp of the message. May be null.
-*   @param      {String}    nick    The origin of the message
-*   @param      {String}    msg     The message to display
-*   @param      {String}    type    The CSS class to assign to the whole message line
-*   @param      {String}    style   Extra CSS commands to apply just to the msg
-*/
-Tabview.prototype.addMsg = function (time, nick, msg, type, style) {
-    var self, tmp, d, re, line_msg;
-
-    self = this;
-
-    tmp = {msg: msg, time: time, nick: nick, tabview: this.name};
-    tmp = kiwi.plugs.run('addmsg', tmp);
-    if (!tmp) {
-        return;
-    }
-
-
-    msg = tmp.msg;
-    time = tmp.time;
-    nick = tmp.nick;
-
-    if (time === null) {
-        d = new Date();
-        time = d.getHours().toString().lpad(2, "0") + ":" + d.getMinutes().toString().lpad(2, "0") + ":" + d.getSeconds().toString().lpad(2, "0");
-    }
-
-    // The CSS class (action, topic, notice, etc)
-    if (typeof type !== "string") {
-        type = '';
-    }
-
-    // Make sure we don't have NaN or something
-    if (typeof msg !== "string") {
-        msg = '';
-    }
-
-    // Make the channels clickable
-    re = new RegExp('\\B(' + kiwi.gateway.channel_prefix + '[^ ,.\\007]+)', 'g');
-    msg = msg.replace(re, function (match) {
-        return '<a class="chan">' + match + '</a>';
-    });
-
-    msg = kiwi.front.formatIRCMsg(msg);
-
-    // Build up and add the line
-    line_msg = $('<div class="msg ' + type + '"><div class="time">' + time + '</div><div class="nick">' + nick + '</div><div class="text" style="' + style + '">' + msg + ' </div></div>');
-    this.div.append(line_msg);
-
-    this.msg_count++;
-    if (this.msg_count > 250) {
-        $('.msg:first', this.div).remove();
-        this.msg_count--;
-    }
-
-    if (!touchscreen) {
-        this.scrollBottom();
-    } else {
-        touch_scroll.refresh();
-        //console.log(this.div.attr("scrollHeight") +" - "+ $('#windows').height());
-        this.scrollBottom();
-        //if(this.div.attr("scrollHeight") > $('#windows').height()){
-        //    touch_scroll.scrollTo(0, this.div.height());
-        //}
-    }
-};
-/**
-*   Scroll to the bottom of the window
-*/
-Tabview.prototype.scrollBottom = function () {
-    var panel = this.panel;
-    panel[0].scrollTop = panel[0].scrollHeight;
-};
-/**
-*   Change a user's nick on the channel
-*   @param      {String}    newNick     The new nick
-*   @param      {String}    oldNick     The old nick
-*/
-Tabview.prototype.changeNick = function (newNick, oldNick) {
-    var inChan = this.userlist.hasUser(oldNick);
-    if (inChan) {
-        this.userlist.renameUser(oldNick, newNick);
-        this.addMsg(null, ' ', '=== ' + oldNick + ' is now known as ' + newNick, 'action changenick');
-    }
-};
-/**
-*   Highlight the tab
-*/
-Tabview.prototype.highlight = function () {
-    this.tab.addClass('highlight');
-};
-/**
-*   Indicate activity on the tab
-*/
-Tabview.prototype.activity = function () {
-    this.tab.addClass('activity');
-};
-/**
-*   Clear the tab's highlight
-*/
-Tabview.prototype.clearHighlight = function () {
-    this.tab.removeClass('highlight');
-    this.tab.removeClass('activity');
-};
-/**
-*   Change the channel's topic
-*   @param      {String}    new_topic   The new channel topic
-*/
-Tabview.prototype.changeTopic = function (new_topic) {
-    this.topic = new_topic;
-    this.addMsg(null, ' ', '=== Topic for ' + this.name + ' is: ' + new_topic, 'topic');
-    if (kiwi.front.cur_channel.name === this.name) {
-        kiwi.front.ui.setTopicText(new_topic);
-    }
-};
-// Static functions
-/**
-*   Checks to see if a tab by the given name exists
-*   @param      {String}    name    The name to check
-*   @returns    {Boolean}           True if the tab exists, false otherwise
-*/
-Tabview.tabExists = function (name) {
-    return (Tabview.getTab(name) !== null);
-};
-/**
-*   Returns the tab which has the given name
-*   @param      {String}    name    The name of the tab to return
-*   @returns    {Tabview}           The Tabview with the given name, or null if it does not exist
-*/
-Tabview.getTab = function (name) {
-    var tab;
-
-    // Make sure we actually have a name
-    if (typeof name !== 'string') {
-        return null;
-    }
-
-    // Go through each tabview and pick out the matching one
-    $.each(kiwi.front.tabviews, function (i, item) {
-        if (item.name.toLowerCase() === name.toLowerCase()) {
-            tab = item;
-            return false;
-        }
-    });
-
-    // If we we didn't find one, return null instead
-    tab = tab || null;
-
-    return tab;
-};
-/**
-*   Returns the tab that corresponds to the server
-*   @retruns    {Tabview}       The server Tabview
-*/
-Tabview.getServerTab = function () {
-    return Tabview.getTab('server');
-};
-/**
-*   Returns all tabs
-*   @returns    {Array}         All of the tabs
-*/
-Tabview.getAllTabs = function () {
-    return kiwi.front.tabviews;
-};
-/**
-*   Returns the tab that's currently showing
-*   @returns    {Tabview}       The tab that's currently showing
-*/
-Tabview.getCurrentTab = function () {
-    return kiwi.front.cur_channel;
-};
-
-
-
-
-
-
 /**
 *   @constructor
 *   Floating message box
old mode 100644 (file)
new mode 100755 (executable)
index 2d5ea43..e7d2eb4
@@ -345,7 +345,7 @@ kiwi.front.ui = {
     *   Displays the next tab\r
     */\r
     windowsNext: function () {\r
-        var tab, tabs, curTab, next;\r
+        /*var tab, tabs, curTab, next;\r
         next = false;\r
         tabs = Tabview.getAllTabs();\r
         curTab = Tabview.getCurrentTab();\r
@@ -359,14 +359,14 @@ kiwi.front.ui = {
                 tabs[tab].show();\r
                 return;\r
             }\r
-        }\r
+        }*/\r
     },\r
 \r
     /**\r
     *   Displays the previous tab\r
     */\r
     windowsPrevious: function () {\r
-        var tab, tabs, curTab, prev_tab, next;\r
+        /*var tab, tabs, curTab, prev_tab, next;\r
         next = false;\r
         tabs = Tabview.getAllTabs();\r
         curTab = Tabview.getCurrentTab();\r
@@ -378,7 +378,7 @@ kiwi.front.ui = {
                 return;\r
             }\r
             prev_tab = tabs[tab];\r
-        }\r
+        }*/\r
     },\r
 \r
     /**\r
@@ -386,7 +386,7 @@ kiwi.front.ui = {
     *   @param  {Number}    num The index of the tab to show\r
     */\r
     windowsShow: function (num) {\r
-        num = parseInt(num, 10);\r
+        /*num = parseInt(num, 10);\r
         console.log('Showing window ' + num.toString());\r
         var i = 0, tab, tabs;\r
         tabs = Tabview.getAllTabs();\r
@@ -396,7 +396,7 @@ kiwi.front.ui = {
                 return;\r
             }\r
             i++;\r
-        }\r
+        }*/\r
     },\r
 \r
 \r
index 15443a21ca07c36122defbb412c7f0c8094f1b10..d5d490abefcb3c330d6e173028b848d544e08239 100755 (executable)
@@ -133,13 +133,15 @@ kiwi.model.ChannelList = Backbone.Collection.extend({
     }\r
 });\r
 \r
+// TODO: Channel modes\r
 kiwi.model.Channel = Backbone.Model.extend({\r
     initialize: function (attributes) {\r
         var name = this.get("name") || "";\r
         this.set({\r
             "members": new kiwi.model.MemberList(),\r
             "name": name,\r
-            "backscroll": []\r
+            "backscroll": [],\r
+            "topic": ""\r
         }, {"silent": true});\r
         this.view = new kiwi.view.Channel({"model": this, "name": name});\r
     },\r
index 27585a6f8e02344dfe30023abfdd7fcb0936c3b7..ac2a83978afb28694382832315febafa2e28dcb4 100755 (executable)
@@ -32,7 +32,8 @@ kiwi.view.Channel = Backbone.View.extend({
     },\r
     initialize: function (options) {\r
         this.htmlsafe_name = 'chan_' + randomString(15);\r
-        $(this.el).attr("id", 'kiwi_window_' + this.htmlsafe_name);\r
+        $(this.el).attr("id", 'kiwi_window_' + this.htmlsafe_name).css('display', 'none');\r
+        this.el = $(this.el).appendTo('#panel1 .scroller')[0];\r
         this.model.bind('msg', this.newMsg, this);\r
         this.msg_count = 0;\r
         this.model.set({"view": this}, {"silent": true});\r
@@ -63,6 +64,10 @@ kiwi.view.Channel = Backbone.View.extend({
     },\r
     chanClick: function (x) {\r
         console.log(x);\r
+    },\r
+    show: function () {\r
+        $('#panel1 .scroller').children().css('display','none');\r
+        $(this.el).css('display', 'block');\r
     }\r
 });\r
 \r
@@ -80,19 +85,19 @@ kiwi.view.Tabs = Backbone.View.extend({
         $this.empty();\r
         this.model.forEach(function (tab) {\r
             var tabname = $(tab.get("view").el).attr("id");\r
-            $this.append($('<li id="tab_' + tabname + '"><span>' + tab.get("name") + '</span></li>'));\r
+            $('<li id="tab_' + tabname + '"><span>' + tab.get("name") + '</span></li>').data('chan', tab).appendTo($this)\r
         });\r
     },\r
     addTab: function (tab) {\r
         var tabname = $(tab.get("view").el).attr("id"),\r
             $this = $(this.el);\r
-        $this.append($('<li id="tab_' + tabname + '"><span>' + tab.get("name") + '</span></li>'));\r
+        $('<li id="tab_' + tabname + '"><span>' + tab.get("name") + '</span></li>').data('chan', tab).appendTo($this)\r
     },\r
     removeTab: function (tab) {\r
         $('#tab_' + $(tab.get("view").el).attr("id")).remove();\r
     },\r
-    tabClick: function (x) {\r
-        console.log(x);\r
+    tabClick: function (e) {\r
+        $(e.currentTarget).data('chan').view.show();\r
     }\r
 });\r
 \r
index cab69b55aad9615f7b94f05f6a8da07a453f6a0b..eaf46339c87f39e8427185b06c74dba3e74931b8 100755 (executable)
@@ -595,7 +595,7 @@ this.httpHandler = function (request, response) {
 
                     min.underscore = fs.readFileSync(public_http_path + 'js/underscore.min.js');
                     min.util = fs.readFileSync(public_http_path + 'js/util.js');
-                    min.backbone = fs.readFileSync(public_http_path + 'js/backbone-0.5.3-min.js');
+                    min.backbone = fs.readFileSync(public_http_path + 'js/backbone-git.js');
                     min.gateway = fs.readFileSync(public_http_path + 'js/gateway.js');
                     min.model = fs.readFileSync(public_http_path + 'js/model.js');
                     min.view = fs.readFileSync(public_http_path + 'js/view.js');