-// Backbone.js 0.9.2\r
-\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
-(function(){var l=this,y=l.Backbone,z=Array.prototype.slice,A=Array.prototype.splice,g;g="undefined"!==typeof exports?exports:l.Backbone={};g.VERSION="0.9.2";var f=l._;!f&&"undefined"!==typeof require&&(f=require("underscore"));var i=l.jQuery||l.Zepto||l.ender;g.setDomLibrary=function(a){i=a};g.noConflict=function(){l.Backbone=y;return this};g.emulateHTTP=!1;g.emulateJSON=!1;var p=/\s+/,k=g.Events={on:function(a,b,c){var d,e,f,g,j;if(!b)return this;a=a.split(p);for(d=this._callbacks||(this._callbacks=\r
-{});e=a.shift();)f=(j=d[e])?j.tail:{},f.next=g={},f.context=c,f.callback=b,d[e]={tail:g,next:j?j.next:f};return this},off:function(a,b,c){var d,e,h,g,j,q;if(e=this._callbacks){if(!a&&!b&&!c)return delete this._callbacks,this;for(a=a?a.split(p):f.keys(e);d=a.shift();)if(h=e[d],delete e[d],h&&(b||c))for(g=h.tail;(h=h.next)!==g;)if(j=h.callback,q=h.context,b&&j!==b||c&&q!==c)this.on(d,j,q);return this}},trigger:function(a){var b,c,d,e,f,g;if(!(d=this._callbacks))return this;f=d.all;a=a.split(p);for(g=\r
-z.call(arguments,1);b=a.shift();){if(c=d[b])for(e=c.tail;(c=c.next)!==e;)c.callback.apply(c.context||this,g);if(c=f){e=c.tail;for(b=[b].concat(g);(c=c.next)!==e;)c.callback.apply(c.context||this,b)}}return this}};k.bind=k.on;k.unbind=k.off;var o=g.Model=function(a,b){var c;a||(a={});b&&b.parse&&(a=this.parse(a));if(c=n(this,"defaults"))a=f.extend({},c,a);b&&b.collection&&(this.collection=b.collection);this.attributes={};this._escapedAttributes={};this.cid=f.uniqueId("c");this.changed={};this._silent=\r
-{};this._pending={};this.set(a,{silent:!0});this.changed={};this._silent={};this._pending={};this._previousAttributes=f.clone(this.attributes);this.initialize.apply(this,arguments)};f.extend(o.prototype,k,{changed:null,_silent:null,_pending:null,idAttribute:"id",initialize:function(){},toJSON:function(){return f.clone(this.attributes)},get:function(a){return this.attributes[a]},escape:function(a){var b;if(b=this._escapedAttributes[a])return b;b=this.get(a);return this._escapedAttributes[a]=f.escape(null==\r
-b?"":""+b)},has:function(a){return null!=this.get(a)},set:function(a,b,c){var d,e;f.isObject(a)||null==a?(d=a,c=b):(d={},d[a]=b);c||(c={});if(!d)return this;d instanceof o&&(d=d.attributes);if(c.unset)for(e in d)d[e]=void 0;if(!this._validate(d,c))return!1;this.idAttribute in d&&(this.id=d[this.idAttribute]);var b=c.changes={},h=this.attributes,g=this._escapedAttributes,j=this._previousAttributes||{};for(e in d){a=d[e];if(!f.isEqual(h[e],a)||c.unset&&f.has(h,e))delete g[e],(c.silent?this._silent:\r
-b)[e]=!0;c.unset?delete h[e]:h[e]=a;!f.isEqual(j[e],a)||f.has(h,e)!=f.has(j,e)?(this.changed[e]=a,c.silent||(this._pending[e]=!0)):(delete this.changed[e],delete this._pending[e])}c.silent||this.change(c);return this},unset:function(a,b){(b||(b={})).unset=!0;return this.set(a,null,b)},clear:function(a){(a||(a={})).unset=!0;return this.set(f.clone(this.attributes),a)},fetch:function(a){var a=a?f.clone(a):{},b=this,c=a.success;a.success=function(d,e,f){if(!b.set(b.parse(d,f),a))return!1;c&&c(b,d)};\r
-a.error=g.wrapError(a.error,b,a);return(this.sync||g.sync).call(this,"read",this,a)},save:function(a,b,c){var d,e;f.isObject(a)||null==a?(d=a,c=b):(d={},d[a]=b);c=c?f.clone(c):{};if(c.wait){if(!this._validate(d,c))return!1;e=f.clone(this.attributes)}a=f.extend({},c,{silent:!0});if(d&&!this.set(d,c.wait?a:c))return!1;var h=this,i=c.success;c.success=function(a,b,e){b=h.parse(a,e);if(c.wait){delete c.wait;b=f.extend(d||{},b)}if(!h.set(b,c))return false;i?i(h,a):h.trigger("sync",h,a,c)};c.error=g.wrapError(c.error,\r
-h,c);b=this.isNew()?"create":"update";b=(this.sync||g.sync).call(this,b,this,c);c.wait&&this.set(e,a);return b},destroy:function(a){var a=a?f.clone(a):{},b=this,c=a.success,d=function(){b.trigger("destroy",b,b.collection,a)};if(this.isNew())return d(),!1;a.success=function(e){a.wait&&d();c?c(b,e):b.trigger("sync",b,e,a)};a.error=g.wrapError(a.error,b,a);var e=(this.sync||g.sync).call(this,"delete",this,a);a.wait||d();return e},url:function(){var a=n(this,"urlRoot")||n(this.collection,"url")||t();\r
-return this.isNew()?a:a+("/"==a.charAt(a.length-1)?"":"/")+encodeURIComponent(this.id)},parse:function(a){return a},clone:function(){return new this.constructor(this.attributes)},isNew:function(){return null==this.id},change:function(a){a||(a={});var b=this._changing;this._changing=!0;for(var c in this._silent)this._pending[c]=!0;var d=f.extend({},a.changes,this._silent);this._silent={};for(c in d)this.trigger("change:"+c,this,this.get(c),a);if(b)return this;for(;!f.isEmpty(this._pending);){this._pending=\r
-{};this.trigger("change",this,a);for(c in this.changed)!this._pending[c]&&!this._silent[c]&&delete this.changed[c];this._previousAttributes=f.clone(this.attributes)}this._changing=!1;return this},hasChanged:function(a){return!arguments.length?!f.isEmpty(this.changed):f.has(this.changed,a)},changedAttributes:function(a){if(!a)return this.hasChanged()?f.clone(this.changed):!1;var b,c=!1,d=this._previousAttributes,e;for(e in a)if(!f.isEqual(d[e],b=a[e]))(c||(c={}))[e]=b;return c},previous:function(a){return!arguments.length||\r
-!this._previousAttributes?null:this._previousAttributes[a]},previousAttributes:function(){return f.clone(this._previousAttributes)},isValid:function(){return!this.validate(this.attributes)},_validate:function(a,b){if(b.silent||!this.validate)return!0;var a=f.extend({},this.attributes,a),c=this.validate(a,b);if(!c)return!0;b&&b.error?b.error(this,c,b):this.trigger("error",this,c,b);return!1}});var r=g.Collection=function(a,b){b||(b={});b.model&&(this.model=b.model);b.comparator&&(this.comparator=b.comparator);\r
-this._reset();this.initialize.apply(this,arguments);a&&this.reset(a,{silent:!0,parse:b.parse})};f.extend(r.prototype,k,{model:o,initialize:function(){},toJSON:function(a){return this.map(function(b){return b.toJSON(a)})},add:function(a,b){var c,d,e,g,i,j={},k={},l=[];b||(b={});a=f.isArray(a)?a.slice():[a];c=0;for(d=a.length;c<d;c++){if(!(e=a[c]=this._prepareModel(a[c],b)))throw Error("Can't add an invalid model to a collection");g=e.cid;i=e.id;j[g]||this._byCid[g]||null!=i&&(k[i]||this._byId[i])?\r
-l.push(c):j[g]=k[i]=e}for(c=l.length;c--;)a.splice(l[c],1);c=0;for(d=a.length;c<d;c++)(e=a[c]).on("all",this._onModelEvent,this),this._byCid[e.cid]=e,null!=e.id&&(this._byId[e.id]=e);this.length+=d;A.apply(this.models,[null!=b.at?b.at:this.models.length,0].concat(a));this.comparator&&this.sort({silent:!0});if(b.silent)return this;c=0;for(d=this.models.length;c<d;c++)if(j[(e=this.models[c]).cid])b.index=c,e.trigger("add",e,this,b);return this},remove:function(a,b){var c,d,e,g;b||(b={});a=f.isArray(a)?\r
-a.slice():[a];c=0;for(d=a.length;c<d;c++)if(g=this.getByCid(a[c])||this.get(a[c]))delete this._byId[g.id],delete this._byCid[g.cid],e=this.indexOf(g),this.models.splice(e,1),this.length--,b.silent||(b.index=e,g.trigger("remove",g,this,b)),this._removeReference(g);return this},push:function(a,b){a=this._prepareModel(a,b);this.add(a,b);return a},pop:function(a){var b=this.at(this.length-1);this.remove(b,a);return b},unshift:function(a,b){a=this._prepareModel(a,b);this.add(a,f.extend({at:0},b));return a},\r
-shift:function(a){var b=this.at(0);this.remove(b,a);return b},get:function(a){return null==a?void 0:this._byId[null!=a.id?a.id:a]},getByCid:function(a){return a&&this._byCid[a.cid||a]},at:function(a){return this.models[a]},where:function(a){return f.isEmpty(a)?[]:this.filter(function(b){for(var c in a)if(a[c]!==b.get(c))return!1;return!0})},sort:function(a){a||(a={});if(!this.comparator)throw Error("Cannot sort a set without a comparator");var b=f.bind(this.comparator,this);1==this.comparator.length?\r
-this.models=this.sortBy(b):this.models.sort(b);a.silent||this.trigger("reset",this,a);return this},pluck:function(a){return f.map(this.models,function(b){return b.get(a)})},reset:function(a,b){a||(a=[]);b||(b={});for(var c=0,d=this.models.length;c<d;c++)this._removeReference(this.models[c]);this._reset();this.add(a,f.extend({silent:!0},b));b.silent||this.trigger("reset",this,b);return this},fetch:function(a){a=a?f.clone(a):{};void 0===a.parse&&(a.parse=!0);var b=this,c=a.success;a.success=function(d,\r
-e,f){b[a.add?"add":"reset"](b.parse(d,f),a);c&&c(b,d)};a.error=g.wrapError(a.error,b,a);return(this.sync||g.sync).call(this,"read",this,a)},create:function(a,b){var c=this,b=b?f.clone(b):{},a=this._prepareModel(a,b);if(!a)return!1;b.wait||c.add(a,b);var d=b.success;b.success=function(e,f){b.wait&&c.add(e,b);d?d(e,f):e.trigger("sync",a,f,b)};a.save(null,b);return a},parse:function(a){return a},chain:function(){return f(this.models).chain()},_reset:function(){this.length=0;this.models=[];this._byId=\r
-{};this._byCid={}},_prepareModel:function(a,b){b||(b={});a instanceof o?a.collection||(a.collection=this):(b.collection=this,a=new this.model(a,b),a._validate(a.attributes,b)||(a=!1));return a},_removeReference:function(a){this==a.collection&&delete a.collection;a.off("all",this._onModelEvent,this)},_onModelEvent:function(a,b,c,d){("add"==a||"remove"==a)&&c!=this||("destroy"==a&&this.remove(b,d),b&&a==="change:"+b.idAttribute&&(delete this._byId[b.previous(b.idAttribute)],this._byId[b.id]=b),this.trigger.apply(this,\r
-arguments))}});f.each("forEach,each,map,reduce,reduceRight,find,detect,filter,select,reject,every,all,some,any,include,contains,invoke,max,min,sortBy,sortedIndex,toArray,size,first,initial,rest,last,without,indexOf,shuffle,lastIndexOf,isEmpty,groupBy".split(","),function(a){r.prototype[a]=function(){return f[a].apply(f,[this.models].concat(f.toArray(arguments)))}});var u=g.Router=function(a){a||(a={});a.routes&&(this.routes=a.routes);this._bindRoutes();this.initialize.apply(this,arguments)},B=/:\w+/g,\r
-C=/\*\w+/g,D=/[-[\]{}()+?.,\\^$|#\s]/g;f.extend(u.prototype,k,{initialize:function(){},route:function(a,b,c){g.history||(g.history=new m);f.isRegExp(a)||(a=this._routeToRegExp(a));c||(c=this[b]);g.history.route(a,f.bind(function(d){d=this._extractParameters(a,d);c&&c.apply(this,d);this.trigger.apply(this,["route:"+b].concat(d));g.history.trigger("route",this,b,d)},this));return this},navigate:function(a,b){g.history.navigate(a,b)},_bindRoutes:function(){if(this.routes){var a=[],b;for(b in this.routes)a.unshift([b,\r
-this.routes[b]]);b=0;for(var c=a.length;b<c;b++)this.route(a[b][0],a[b][1],this[a[b][1]])}},_routeToRegExp:function(a){a=a.replace(D,"\\$&").replace(B,"([^/]+)").replace(C,"(.*?)");return RegExp("^"+a+"$")},_extractParameters:function(a,b){return a.exec(b).slice(1)}});var m=g.History=function(){this.handlers=[];f.bindAll(this,"checkUrl")},s=/^[#\/]/,E=/msie [\w.]+/;m.started=!1;f.extend(m.prototype,k,{interval:50,getHash:function(a){return(a=(a?a.location:window.location).href.match(/#(.*)$/))?a[1]:\r
-""},getFragment:function(a,b){if(null==a)if(this._hasPushState||b){var a=window.location.pathname,c=window.location.search;c&&(a+=c)}else a=this.getHash();a.indexOf(this.options.root)||(a=a.substr(this.options.root.length));return a.replace(s,"")},start:function(a){if(m.started)throw Error("Backbone.history has already been started");m.started=!0;this.options=f.extend({},{root:"/"},this.options,a);this._wantsHashChange=!1!==this.options.hashChange;this._wantsPushState=!!this.options.pushState;this._hasPushState=\r
-!(!this.options.pushState||!window.history||!window.history.pushState);var a=this.getFragment(),b=document.documentMode;if(b=E.exec(navigator.userAgent.toLowerCase())&&(!b||7>=b))this.iframe=i('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo("body")[0].contentWindow,this.navigate(a);this._hasPushState?i(window).bind("popstate",this.checkUrl):this._wantsHashChange&&"onhashchange"in window&&!b?i(window).bind("hashchange",this.checkUrl):this._wantsHashChange&&(this._checkUrlInterval=setInterval(this.checkUrl,\r
-this.interval));this.fragment=a;a=window.location;b=a.pathname==this.options.root;if(this._wantsHashChange&&this._wantsPushState&&!this._hasPushState&&!b)return this.fragment=this.getFragment(null,!0),window.location.replace(this.options.root+"#"+this.fragment),!0;this._wantsPushState&&this._hasPushState&&b&&a.hash&&(this.fragment=this.getHash().replace(s,""),window.history.replaceState({},document.title,a.protocol+"//"+a.host+this.options.root+this.fragment));if(!this.options.silent)return this.loadUrl()},\r
-stop:function(){i(window).unbind("popstate",this.checkUrl).unbind("hashchange",this.checkUrl);clearInterval(this._checkUrlInterval);m.started=!1},route:function(a,b){this.handlers.unshift({route:a,callback:b})},checkUrl:function(){var a=this.getFragment();a==this.fragment&&this.iframe&&(a=this.getFragment(this.getHash(this.iframe)));if(a==this.fragment)return!1;this.iframe&&this.navigate(a);this.loadUrl()||this.loadUrl(this.getHash())},loadUrl:function(a){var b=this.fragment=this.getFragment(a);return f.any(this.handlers,\r
-function(a){if(a.route.test(b))return a.callback(b),!0})},navigate:function(a,b){if(!m.started)return!1;if(!b||!0===b)b={trigger:b};var c=(a||"").replace(s,"");this.fragment!=c&&(this._hasPushState?(0!=c.indexOf(this.options.root)&&(c=this.options.root+c),this.fragment=c,window.history[b.replace?"replaceState":"pushState"]({},document.title,c)):this._wantsHashChange?(this.fragment=c,this._updateHash(window.location,c,b.replace),this.iframe&&c!=this.getFragment(this.getHash(this.iframe))&&(b.replace||\r
-this.iframe.document.open().close(),this._updateHash(this.iframe.location,c,b.replace))):window.location.assign(this.options.root+a),b.trigger&&this.loadUrl(a))},_updateHash:function(a,b,c){c?a.replace(a.toString().replace(/(javascript:|#).*$/,"")+"#"+b):a.hash=b}});var v=g.View=function(a){this.cid=f.uniqueId("view");this._configure(a||{});this._ensureElement();this.initialize.apply(this,arguments);this.delegateEvents()},F=/^(\S+)\s*(.*)$/,w="model,collection,el,id,attributes,className,tagName".split(",");\r
-f.extend(v.prototype,k,{tagName:"div",$:function(a){return this.$el.find(a)},initialize:function(){},render:function(){return this},remove:function(){this.$el.remove();return this},make:function(a,b,c){a=document.createElement(a);b&&i(a).attr(b);c&&i(a).html(c);return a},setElement:function(a,b){this.$el&&this.undelegateEvents();this.$el=a instanceof i?a:i(a);this.el=this.$el[0];!1!==b&&this.delegateEvents();return this},delegateEvents:function(a){if(a||(a=n(this,"events"))){this.undelegateEvents();\r
-for(var b in a){var c=a[b];f.isFunction(c)||(c=this[a[b]]);if(!c)throw Error('Method "'+a[b]+'" does not exist');var d=b.match(F),e=d[1],d=d[2],c=f.bind(c,this),e=e+(".delegateEvents"+this.cid);""===d?this.$el.bind(e,c):this.$el.delegate(d,e,c)}}},undelegateEvents:function(){this.$el.unbind(".delegateEvents"+this.cid)},_configure:function(a){this.options&&(a=f.extend({},this.options,a));for(var b=0,c=w.length;b<c;b++){var d=w[b];a[d]&&(this[d]=a[d])}this.options=a},_ensureElement:function(){if(this.el)this.setElement(this.el,\r
-!1);else{var a=n(this,"attributes")||{};this.id&&(a.id=this.id);this.className&&(a["class"]=this.className);this.setElement(this.make(this.tagName,a),!1)}}});o.extend=r.extend=u.extend=v.extend=function(a,b){var c=G(this,a,b);c.extend=this.extend;return c};var H={create:"POST",update:"PUT","delete":"DELETE",read:"GET"};g.sync=function(a,b,c){var d=H[a];c||(c={});var e={type:d,dataType:"json"};c.url||(e.url=n(b,"url")||t());if(!c.data&&b&&("create"==a||"update"==a))e.contentType="application/json",\r
-e.data=JSON.stringify(b.toJSON());g.emulateJSON&&(e.contentType="application/x-www-form-urlencoded",e.data=e.data?{model:e.data}:{});if(g.emulateHTTP&&("PUT"===d||"DELETE"===d))g.emulateJSON&&(e.data._method=d),e.type="POST",e.beforeSend=function(a){a.setRequestHeader("X-HTTP-Method-Override",d)};"GET"!==e.type&&!g.emulateJSON&&(e.processData=!1);return i.ajax(f.extend(e,c))};g.wrapError=function(a,b,c){return function(d,e){e=d===b?e:d;a?a(b,e,c):b.trigger("error",b,e,c)}};var x=function(){},G=function(a,\r
-b,c){var d;d=b&&b.hasOwnProperty("constructor")?b.constructor:function(){a.apply(this,arguments)};f.extend(d,a);x.prototype=a.prototype;d.prototype=new x;b&&f.extend(d.prototype,b);c&&f.extend(d,c);d.prototype.constructor=d;d.__super__=a.prototype;return d},n=function(a,b){return!a||!a[b]?null:f.isFunction(a[b])?a[b]():a[b]},t=function(){throw Error('A "url" property or function must be specified');}}).call(this);
\ No newline at end of file
+(function(){var root=this;var previousBackbone=root.Backbone;var array=[];var push=array.push;var slice=array.slice;var splice=array.splice;var Backbone;if(typeof exports!=="undefined"){Backbone=exports}else{Backbone=root.Backbone={}}Backbone.VERSION="1.0.0";var _=root._;if(!_&&typeof require!=="undefined")_=require("underscore");Backbone.$=root.jQuery||root.Zepto||root.ender||root.$;Backbone.noConflict=function(){root.Backbone=previousBackbone;return this};Backbone.emulateHTTP=false;Backbone.emulateJSON=false;var Events=Backbone.Events={on:function(name,callback,context){if(!eventsApi(this,"on",name,[callback,context])||!callback)return this;this._events||(this._events={});var events=this._events[name]||(this._events[name]=[]);events.push({callback:callback,context:context,ctx:context||this});return this},once:function(name,callback,context){if(!eventsApi(this,"once",name,[callback,context])||!callback)return this;var self=this;var once=_.once(function(){self.off(name,once);callback.apply(this,arguments)});once._callback=callback;return this.on(name,once,context)},off:function(name,callback,context){var retain,ev,events,names,i,l,j,k;if(!this._events||!eventsApi(this,"off",name,[callback,context]))return this;if(!name&&!callback&&!context){this._events={};return this}names=name?[name]:_.keys(this._events);for(i=0,l=names.length;i<l;i++){name=names[i];if(events=this._events[name]){this._events[name]=retain=[];if(callback||context){for(j=0,k=events.length;j<k;j++){ev=events[j];if(callback&&callback!==ev.callback&&callback!==ev.callback._callback||context&&context!==ev.context){retain.push(ev)}}}if(!retain.length)delete this._events[name]}}return this},trigger:function(name){if(!this._events)return this;var args=slice.call(arguments,1);if(!eventsApi(this,"trigger",name,args))return this;var events=this._events[name];var allEvents=this._events.all;if(events)triggerEvents(events,args);if(allEvents)triggerEvents(allEvents,arguments);return this},stopListening:function(obj,name,callback){var listeners=this._listeners;if(!listeners)return this;var deleteListener=!name&&!callback;if(typeof name==="object")callback=this;if(obj)(listeners={})[obj._listenerId]=obj;for(var id in listeners){listeners[id].off(name,callback,this);if(deleteListener)delete this._listeners[id]}return this}};var eventSplitter=/\s+/;var eventsApi=function(obj,action,name,rest){if(!name)return true;if(typeof name==="object"){for(var key in name){obj[action].apply(obj,[key,name[key]].concat(rest))}return false}if(eventSplitter.test(name)){var names=name.split(eventSplitter);for(var i=0,l=names.length;i<l;i++){obj[action].apply(obj,[names[i]].concat(rest))}return false}return true};var triggerEvents=function(events,args){var ev,i=-1,l=events.length,a1=args[0],a2=args[1],a3=args[2];switch(args.length){case 0:while(++i<l)(ev=events[i]).callback.call(ev.ctx);return;case 1:while(++i<l)(ev=events[i]).callback.call(ev.ctx,a1);return;case 2:while(++i<l)(ev=events[i]).callback.call(ev.ctx,a1,a2);return;case 3:while(++i<l)(ev=events[i]).callback.call(ev.ctx,a1,a2,a3);return;default:while(++i<l)(ev=events[i]).callback.apply(ev.ctx,args)}};var listenMethods={listenTo:"on",listenToOnce:"once"};_.each(listenMethods,function(implementation,method){Events[method]=function(obj,name,callback){var listeners=this._listeners||(this._listeners={});var id=obj._listenerId||(obj._listenerId=_.uniqueId("l"));listeners[id]=obj;if(typeof name==="object")callback=this;obj[implementation](name,callback,this);return this}});Events.bind=Events.on;Events.unbind=Events.off;_.extend(Backbone,Events);var Model=Backbone.Model=function(attributes,options){var defaults;var attrs=attributes||{};options||(options={});this.cid=_.uniqueId("c");this.attributes={};_.extend(this,_.pick(options,modelOptions));if(options.parse)attrs=this.parse(attrs,options)||{};if(defaults=_.result(this,"defaults")){attrs=_.defaults({},attrs,defaults)}this.set(attrs,options);this.changed={};this.initialize.apply(this,arguments)};var modelOptions=["url","urlRoot","collection"];_.extend(Model.prototype,Events,{changed:null,validationError:null,idAttribute:"id",initialize:function(){},toJSON:function(options){return _.clone(this.attributes)},sync:function(){return Backbone.sync.apply(this,arguments)},get:function(attr){return this.attributes[attr]},escape:function(attr){return _.escape(this.get(attr))},has:function(attr){return this.get(attr)!=null},set:function(key,val,options){var attr,attrs,unset,changes,silent,changing,prev,current;if(key==null)return this;if(typeof key==="object"){attrs=key;options=val}else{(attrs={})[key]=val}options||(options={});if(!this._validate(attrs,options))return false;unset=options.unset;silent=options.silent;changes=[];changing=this._changing;this._changing=true;if(!changing){this._previousAttributes=_.clone(this.attributes);this.changed={}}current=this.attributes,prev=this._previousAttributes;if(this.idAttribute in attrs)this.id=attrs[this.idAttribute];for(attr in attrs){val=attrs[attr];if(!_.isEqual(current[attr],val))changes.push(attr);if(!_.isEqual(prev[attr],val)){this.changed[attr]=val}else{delete this.changed[attr]}unset?delete current[attr]:current[attr]=val}if(!silent){if(changes.length)this._pending=true;for(var i=0,l=changes.length;i<l;i++){this.trigger("change:"+changes[i],this,current[changes[i]],options)}}if(changing)return this;if(!silent){while(this._pending){this._pending=false;this.trigger("change",this,options)}}this._pending=false;this._changing=false;return this},unset:function(attr,options){return this.set(attr,void 0,_.extend({},options,{unset:true}))},clear:function(options){var attrs={};for(var key in this.attributes)attrs[key]=void 0;return this.set(attrs,_.extend({},options,{unset:true}))},hasChanged:function(attr){if(attr==null)return!_.isEmpty(this.changed);return _.has(this.changed,attr)},changedAttributes:function(diff){if(!diff)return this.hasChanged()?_.clone(this.changed):false;var val,changed=false;var old=this._changing?this._previousAttributes:this.attributes;for(var attr in diff){if(_.isEqual(old[attr],val=diff[attr]))continue;(changed||(changed={}))[attr]=val}return changed},previous:function(attr){if(attr==null||!this._previousAttributes)return null;return this._previousAttributes[attr]},previousAttributes:function(){return _.clone(this._previousAttributes)},fetch:function(options){options=options?_.clone(options):{};if(options.parse===void 0)options.parse=true;var model=this;var success=options.success;options.success=function(resp){if(!model.set(model.parse(resp,options),options))return false;if(success)success(model,resp,options);model.trigger("sync",model,resp,options)};wrapError(this,options);return this.sync("read",this,options)},save:function(key,val,options){var attrs,method,xhr,attributes=this.attributes;if(key==null||typeof key==="object"){attrs=key;options=val}else{(attrs={})[key]=val}if(attrs&&(!options||!options.wait)&&!this.set(attrs,options))return false;options=_.extend({validate:true},options);if(!this._validate(attrs,options))return false;if(attrs&&options.wait){this.attributes=_.extend({},attributes,attrs)}if(options.parse===void 0)options.parse=true;var model=this;var success=options.success;options.success=function(resp){model.attributes=attributes;var serverAttrs=model.parse(resp,options);if(options.wait)serverAttrs=_.extend(attrs||{},serverAttrs);if(_.isObject(serverAttrs)&&!model.set(serverAttrs,options)){return false}if(success)success(model,resp,options);model.trigger("sync",model,resp,options)};wrapError(this,options);method=this.isNew()?"create":options.patch?"patch":"update";if(method==="patch")options.attrs=attrs;xhr=this.sync(method,this,options);if(attrs&&options.wait)this.attributes=attributes;return xhr},destroy:function(options){options=options?_.clone(options):{};var model=this;var success=options.success;var destroy=function(){model.trigger("destroy",model,model.collection,options)};options.success=function(resp){if(options.wait||model.isNew())destroy();if(success)success(model,resp,options);if(!model.isNew())model.trigger("sync",model,resp,options)};if(this.isNew()){options.success();return false}wrapError(this,options);var xhr=this.sync("delete",this,options);if(!options.wait)destroy();return xhr},url:function(){var base=_.result(this,"urlRoot")||_.result(this.collection,"url")||urlError();if(this.isNew())return base;return base+(base.charAt(base.length-1)==="/"?"":"/")+encodeURIComponent(this.id)},parse:function(resp,options){return resp},clone:function(){return new this.constructor(this.attributes)},isNew:function(){return this.id==null},isValid:function(options){return this._validate({},_.extend(options||{},{validate:true}))},_validate:function(attrs,options){if(!options.validate||!this.validate)return true;attrs=_.extend({},this.attributes,attrs);var error=this.validationError=this.validate(attrs,options)||null;if(!error)return true;this.trigger("invalid",this,error,_.extend(options||{},{validationError:error}));return false}});var modelMethods=["keys","values","pairs","invert","pick","omit"];_.each(modelMethods,function(method){Model.prototype[method]=function(){var args=slice.call(arguments);args.unshift(this.attributes);return _[method].apply(_,args)}});var Collection=Backbone.Collection=function(models,options){options||(options={});if(options.url)this.url=options.url;if(options.model)this.model=options.model;if(options.comparator!==void 0)this.comparator=options.comparator;this._reset();this.initialize.apply(this,arguments);if(models)this.reset(models,_.extend({silent:true},options))};var setOptions={add:true,remove:true,merge:true};var addOptions={add:true,merge:false,remove:false};_.extend(Collection.prototype,Events,{model:Model,initialize:function(){},toJSON:function(options){return this.map(function(model){return model.toJSON(options)})},sync:function(){return Backbone.sync.apply(this,arguments)},add:function(models,options){return this.set(models,_.defaults(options||{},addOptions))},remove:function(models,options){models=_.isArray(models)?models.slice():[models];options||(options={});var i,l,index,model;for(i=0,l=models.length;i<l;i++){model=this.get(models[i]);if(!model)continue;delete this._byId[model.id];delete this._byId[model.cid];index=this.indexOf(model);this.models.splice(index,1);this.length--;if(!options.silent){options.index=index;model.trigger("remove",model,this,options)}this._removeReference(model)}return this},set:function(models,options){options=_.defaults(options||{},setOptions);if(options.parse)models=this.parse(models,options);if(!_.isArray(models))models=models?[models]:[];var i,l,model,attrs,existing,sort;var at=options.at;var sortable=this.comparator&&at==null&&options.sort!==false;var sortAttr=_.isString(this.comparator)?this.comparator:null;var toAdd=[],toRemove=[],modelMap={};for(i=0,l=models.length;i<l;i++){if(!(model=this._prepareModel(models[i],options)))continue;if(existing=this.get(model)){if(options.remove)modelMap[existing.cid]=true;if(options.merge){existing.set(model.attributes,options);if(sortable&&!sort&&existing.hasChanged(sortAttr))sort=true}}else if(options.add){toAdd.push(model);model.on("all",this._onModelEvent,this);this._byId[model.cid]=model;if(model.id!=null)this._byId[model.id]=model}}if(options.remove){for(i=0,l=this.length;i<l;++i){if(!modelMap[(model=this.models[i]).cid])toRemove.push(model)}if(toRemove.length)this.remove(toRemove,options)}if(toAdd.length){if(sortable)sort=true;this.length+=toAdd.length;if(at!=null){splice.apply(this.models,[at,0].concat(toAdd))}else{push.apply(this.models,toAdd)}}if(sort)this.sort({silent:true});if(options.silent)return this;for(i=0,l=toAdd.length;i<l;i++){(model=toAdd[i]).trigger("add",model,this,options)}if(sort)this.trigger("sort",this,options);return this},reset:function(models,options){options||(options={});for(var i=0,l=this.models.length;i<l;i++){this._removeReference(this.models[i])}options.previousModels=this.models;this._reset();this.add(models,_.extend({silent:true},options));if(!options.silent)this.trigger("reset",this,options);return this},push:function(model,options){model=this._prepareModel(model,options);this.add(model,_.extend({at:this.length},options));return model},pop:function(options){var model=this.at(this.length-1);this.remove(model,options);return model},unshift:function(model,options){model=this._prepareModel(model,options);this.add(model,_.extend({at:0},options));return model},shift:function(options){var model=this.at(0);this.remove(model,options);return model},slice:function(begin,end){return this.models.slice(begin,end)},get:function(obj){if(obj==null)return void 0;return this._byId[obj.id!=null?obj.id:obj.cid||obj]},at:function(index){return this.models[index]},where:function(attrs,first){if(_.isEmpty(attrs))return first?void 0:[];return this[first?"find":"filter"](function(model){for(var key in attrs){if(attrs[key]!==model.get(key))return false}return true})},findWhere:function(attrs){return this.where(attrs,true)},sort:function(options){if(!this.comparator)throw new Error("Cannot sort a set without a comparator");options||(options={});if(_.isString(this.comparator)||this.comparator.length===1){this.models=this.sortBy(this.comparator,this)}else{this.models.sort(_.bind(this.comparator,this))}if(!options.silent)this.trigger("sort",this,options);return this},sortedIndex:function(model,value,context){value||(value=this.comparator);var iterator=_.isFunction(value)?value:function(model){return model.get(value)};return _.sortedIndex(this.models,model,iterator,context)},pluck:function(attr){return _.invoke(this.models,"get",attr)},fetch:function(options){options=options?_.clone(options):{};if(options.parse===void 0)options.parse=true;var success=options.success;var collection=this;options.success=function(resp){var method=options.reset?"reset":"set";collection[method](resp,options);if(success)success(collection,resp,options);collection.trigger("sync",collection,resp,options)};wrapError(this,options);return this.sync("read",this,options)},create:function(model,options){options=options?_.clone(options):{};if(!(model=this._prepareModel(model,options)))return false;if(!options.wait)this.add(model,options);var collection=this;var success=options.success;options.success=function(resp){if(options.wait)collection.add(model,options);if(success)success(model,resp,options)};model.save(null,options);return model},parse:function(resp,options){return resp},clone:function(){return new this.constructor(this.models)},_reset:function(){this.length=0;this.models=[];this._byId={}},_prepareModel:function(attrs,options){if(attrs instanceof Model){if(!attrs.collection)attrs.collection=this;return attrs}options||(options={});options.collection=this;var model=new this.model(attrs,options);if(!model._validate(attrs,options)){this.trigger("invalid",this,attrs,options);return false}return model},_removeReference:function(model){if(this===model.collection)delete model.collection;model.off("all",this._onModelEvent,this)},_onModelEvent:function(event,model,collection,options){if((event==="add"||event==="remove")&&collection!==this)return;if(event==="destroy")this.remove(model,options);if(model&&event==="change:"+model.idAttribute){delete this._byId[model.previous(model.idAttribute)];if(model.id!=null)this._byId[model.id]=model}this.trigger.apply(this,arguments)}});var methods=["forEach","each","map","collect","reduce","foldl","inject","reduceRight","foldr","find","detect","filter","select","reject","every","all","some","any","include","contains","invoke","max","min","toArray","size","first","head","take","initial","rest","tail","drop","last","without","indexOf","shuffle","lastIndexOf","isEmpty","chain"];_.each(methods,function(method){Collection.prototype[method]=function(){var args=slice.call(arguments);args.unshift(this.models);return _[method].apply(_,args)}});var attributeMethods=["groupBy","countBy","sortBy"];_.each(attributeMethods,function(method){Collection.prototype[method]=function(value,context){var iterator=_.isFunction(value)?value:function(model){return model.get(value)};return _[method](this.models,iterator,context)}});var View=Backbone.View=function(options){this.cid=_.uniqueId("view");this._configure(options||{});this._ensureElement();this.initialize.apply(this,arguments);this.delegateEvents()};var delegateEventSplitter=/^(\S+)\s*(.*)$/;var viewOptions=["model","collection","el","id","attributes","className","tagName","events"];_.extend(View.prototype,Events,{tagName:"div",$:function(selector){return this.$el.find(selector)},initialize:function(){},render:function(){return this},remove:function(){this.$el.remove();this.stopListening();return this},setElement:function(element,delegate){if(this.$el)this.undelegateEvents();this.$el=element instanceof Backbone.$?element:Backbone.$(element);this.el=this.$el[0];if(delegate!==false)this.delegateEvents();return this},delegateEvents:function(events){if(!(events||(events=_.result(this,"events"))))return this;this.undelegateEvents();for(var key in events){var method=events[key];if(!_.isFunction(method))method=this[events[key]];if(!method)continue;var match=key.match(delegateEventSplitter);var eventName=match[1],selector=match[2];method=_.bind(method,this);eventName+=".delegateEvents"+this.cid;if(selector===""){this.$el.on(eventName,method)}else{this.$el.on(eventName,selector,method)}}return this},undelegateEvents:function(){this.$el.off(".delegateEvents"+this.cid);return this},_configure:function(options){if(this.options)options=_.extend({},_.result(this,"options"),options);_.extend(this,_.pick(options,viewOptions));this.options=options},_ensureElement:function(){if(!this.el){var attrs=_.extend({},_.result(this,"attributes"));if(this.id)attrs.id=_.result(this,"id");if(this.className)attrs["class"]=_.result(this,"className");var $el=Backbone.$("<"+_.result(this,"tagName")+">").attr(attrs);this.setElement($el,false)}else{this.setElement(_.result(this,"el"),false)}}});Backbone.sync=function(method,model,options){var type=methodMap[method];_.defaults(options||(options={}),{emulateHTTP:Backbone.emulateHTTP,emulateJSON:Backbone.emulateJSON});var params={type:type,dataType:"json"};if(!options.url){params.url=_.result(model,"url")||urlError()}if(options.data==null&&model&&(method==="create"||method==="update"||method==="patch")){params.contentType="application/json";params.data=JSON.stringify(options.attrs||model.toJSON(options))}if(options.emulateJSON){params.contentType="application/x-www-form-urlencoded";params.data=params.data?{model:params.data}:{}}if(options.emulateHTTP&&(type==="PUT"||type==="DELETE"||type==="PATCH")){params.type="POST";if(options.emulateJSON)params.data._method=type;var beforeSend=options.beforeSend;options.beforeSend=function(xhr){xhr.setRequestHeader("X-HTTP-Method-Override",type);if(beforeSend)return beforeSend.apply(this,arguments)}}if(params.type!=="GET"&&!options.emulateJSON){params.processData=false}if(params.type==="PATCH"&&window.ActiveXObject&&!(window.external&&window.external.msActiveXFilteringEnabled)){params.xhr=function(){return new ActiveXObject("Microsoft.XMLHTTP")}}var xhr=options.xhr=Backbone.ajax(_.extend(params,options));model.trigger("request",model,xhr,options);return xhr};var methodMap={create:"POST",update:"PUT",patch:"PATCH","delete":"DELETE",read:"GET"};Backbone.ajax=function(){return Backbone.$.ajax.apply(Backbone.$,arguments)};var Router=Backbone.Router=function(options){options||(options={});if(options.routes)this.routes=options.routes;this._bindRoutes();this.initialize.apply(this,arguments)};var optionalParam=/\((.*?)\)/g;var namedParam=/(\(\?)?:\w+/g;var splatParam=/\*\w+/g;var escapeRegExp=/[\-{}\[\]+?.,\\\^$|#\s]/g;_.extend(Router.prototype,Events,{initialize:function(){},route:function(route,name,callback){if(!_.isRegExp(route))route=this._routeToRegExp(route);if(_.isFunction(name)){callback=name;name=""}if(!callback)callback=this[name];var router=this;Backbone.history.route(route,function(fragment){var args=router._extractParameters(route,fragment);callback&&callback.apply(router,args);router.trigger.apply(router,["route:"+name].concat(args));router.trigger("route",name,args);Backbone.history.trigger("route",router,name,args)});return this},navigate:function(fragment,options){Backbone.history.navigate(fragment,options);return this},_bindRoutes:function(){if(!this.routes)return;this.routes=_.result(this,"routes");var route,routes=_.keys(this.routes);while((route=routes.pop())!=null){this.route(route,this.routes[route])}},_routeToRegExp:function(route){route=route.replace(escapeRegExp,"\\$&").replace(optionalParam,"(?:$1)?").replace(namedParam,function(match,optional){return optional?match:"([^/]+)"}).replace(splatParam,"(.*?)");return new RegExp("^"+route+"$")},_extractParameters:function(route,fragment){var params=route.exec(fragment).slice(1);return _.map(params,function(param){return param?decodeURIComponent(param):null})}});var History=Backbone.History=function(){this.handlers=[];_.bindAll(this,"checkUrl");if(typeof window!=="undefined"){this.location=window.location;this.history=window.history}};var routeStripper=/^[#\/]|\s+$/g;var rootStripper=/^\/+|\/+$/g;var isExplorer=/msie [\w.]+/;var trailingSlash=/\/$/;History.started=false;_.extend(History.prototype,Events,{interval:50,getHash:function(window){var match=(window||this).location.href.match(/#(.*)$/);return match?match[1]:""},getFragment:function(fragment,forcePushState){if(fragment==null){if(this._hasPushState||!this._wantsHashChange||forcePushState){fragment=this.location.pathname;var root=this.root.replace(trailingSlash,"");if(!fragment.indexOf(root))fragment=fragment.substr(root.length)}else{fragment=this.getHash()}}return fragment.replace(routeStripper,"")},start:function(options){if(History.started)throw new Error("Backbone.history has already been started");History.started=true;this.options=_.extend({},{root:"/"},this.options,options);this.root=this.options.root;this._wantsHashChange=this.options.hashChange!==false;this._wantsPushState=!!this.options.pushState;this._hasPushState=!!(this.options.pushState&&this.history&&this.history.pushState);var fragment=this.getFragment();var docMode=document.documentMode;var oldIE=isExplorer.exec(navigator.userAgent.toLowerCase())&&(!docMode||docMode<=7);this.root=("/"+this.root+"/").replace(rootStripper,"/");if(oldIE&&this._wantsHashChange){this.iframe=Backbone.$('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo("body")[0].contentWindow;this.navigate(fragment)}if(this._hasPushState){Backbone.$(window).on("popstate",this.checkUrl)}else if(this._wantsHashChange&&"onhashchange"in window&&!oldIE){Backbone.$(window).on("hashchange",this.checkUrl)}else if(this._wantsHashChange){this._checkUrlInterval=setInterval(this.checkUrl,this.interval)}this.fragment=fragment;var loc=this.location;var atRoot=loc.pathname.replace(/[^\/]$/,"$&/")===this.root;if(this._wantsHashChange&&this._wantsPushState&&!this._hasPushState&&!atRoot){this.fragment=this.getFragment(null,true);this.location.replace(this.root+this.location.search+"#"+this.fragment);return true}else if(this._wantsPushState&&this._hasPushState&&atRoot&&loc.hash){this.fragment=this.getHash().replace(routeStripper,"");this.history.replaceState({},document.title,this.root+this.fragment+loc.search)}if(!this.options.silent)return this.loadUrl()},stop:function(){Backbone.$(window).off("popstate",this.checkUrl).off("hashchange",this.checkUrl);clearInterval(this._checkUrlInterval);History.started=false},route:function(route,callback){this.handlers.unshift({route:route,callback:callback})},checkUrl:function(e){var current=this.getFragment();if(current===this.fragment&&this.iframe){current=this.getFragment(this.getHash(this.iframe))}if(current===this.fragment)return false;if(this.iframe)this.navigate(current);this.loadUrl()||this.loadUrl(this.getHash())},loadUrl:function(fragmentOverride){var fragment=this.fragment=this.getFragment(fragmentOverride);var matched=_.any(this.handlers,function(handler){if(handler.route.test(fragment)){handler.callback(fragment);return true}});return matched},navigate:function(fragment,options){if(!History.started)return false;if(!options||options===true)options={trigger:options};fragment=this.getFragment(fragment||"");if(this.fragment===fragment)return;this.fragment=fragment;var url=this.root+fragment;if(this._hasPushState){this.history[options.replace?"replaceState":"pushState"]({},document.title,url)}else if(this._wantsHashChange){this._updateHash(this.location,fragment,options.replace);if(this.iframe&&fragment!==this.getFragment(this.getHash(this.iframe))){if(!options.replace)this.iframe.document.open().close();this._updateHash(this.iframe.location,fragment,options.replace)}}else{return this.location.assign(url)}if(options.trigger)this.loadUrl(fragment)},_updateHash:function(location,fragment,replace){if(replace){var href=location.href.replace(/(javascript:|#).*$/,"");location.replace(href+"#"+fragment)}else{location.hash="#"+fragment}}});Backbone.history=new History;var extend=function(protoProps,staticProps){var parent=this;var child;if(protoProps&&_.has(protoProps,"constructor")){child=protoProps.constructor}else{child=function(){return parent.apply(this,arguments)}}_.extend(child,parent,staticProps);var Surrogate=function(){this.constructor=child};Surrogate.prototype=parent.prototype;child.prototype=new Surrogate;if(protoProps)_.extend(child.prototype,protoProps);child.__super__=parent.prototype;return child};Model.extend=Collection.extend=Router.extend=View.extend=History.extend=extend;var urlError=function(){throw new Error('A "url" property or function must be specified')};var wrapError=function(model,options){var error=options.error;options.error=function(resp){if(error)error(model,resp,options);model.trigger("error",model,resp,options)}}}).call(this);\r
+/*\r
+//@ sourceMappingURL=backbone-min.map\r
+*/
\ No newline at end of file
#kiwi p { margin:0.5em 0; }
#kiwi a { color:#36C; text-decoration:none; cursor:pointer; }
#kiwi a img { border:none; }
+#kiwi .format_span a { color: inherit; background-color: inherit; text-decoration: inherit; font-style: inherit; font-weight: inherit;}
/**
#kiwi #toolbar .panellist .alert_activity { font-weight: bold; }
#kiwi #toolbar .panellist .alert_action { font-weight: bold; }
+#kiwi #toolbar .panellist li { width: auto; text-align: left; }
#kiwi #toolbar .panellist li .part { position: absolute; top: 5px; right: 5px; }
#kiwi #toolbar .panellist li .part:before { content:"[x]"; }
#kiwi #toolbar .panellist li .part:hover { color: #900; }
#kiwi #toolbar .panellist li img.icon { left:5px; top:2px; height:auto; width:auto; }
+#kiwi #toolbar .connections { overflow:hidden; }
+#kiwi #toolbar .connections .connection { float:left; }
+#kiwi #toolbar .connections .panellist { display:inline; }
+#kiwi #toolbar .connections .panellist:after { content:""; }
+
#kiwi #status_message {
background: #FFF;
border-bottom: 1px solid;
#kiwi #memberlists ul li a.nick { }
/* The userbox shown when clicking a nick */
-#kiwi #memberlists ul li .userbox { position:relative; }
-#kiwi #memberlists ul li .userbox a { }
-#kiwi #memberlists ul li .userbox a i { }
+#kiwi .userbox { position:relative; }
+#kiwi .userbox a { }
+#kiwi .userbox a i { }
/**
* Server selection dialog
*/
-#kiwi .server_select { width:800px; margin:0 auto; overflow:hidden; }
+#kiwi .server_select { position:relative; width:320px; margin:0 auto; overflow:hidden; }
+#kiwi .server_select.initial { margin-top: 3em; }
#kiwi .server_select .more { display: none; }
#kiwi .server_select button { }
#kiwi .server_select input { }
#kiwi .server_select .basic { border-bottom: 1px solid gray; margin-bottom:1em; }
#kiwi .server_select .basic .show_more { }
#kiwi .server_select .basic tr.pass { display:none; }
+#kiwi .server_select .basic tr.key { display:none; }
#kiwi .server_select.single_server .basic { border:none; }
#kiwi .server_select .status { }
+/* Channel key icon */
+#kiwi .server_select .basic tr.channel td { position: relative; }
+#kiwi .server_select .basic tr.channel .icon-key {
+ position: absolute;
+ top: 0.4em;
+ right: 0.6em;
+ font-size: 1.3em;
+ cursor: pointer;
+}
+#kiwi .server_select .basic tr.have_key { display:none; }
+
/* When connected to an IRC server, .ok is set on the status div */
#kiwi .server_select .status.ok { }
+#kiwi .ui_menu {
+ padding: 0;
+ z-index:10;
+ position: absolute;
+ top: 100px; left: 100px;
+ background: #fff;
+ border: 1px solid #bbb;
+ background-clip: padding-box;
+}
+#kiwi .ui_menu .ui_menu_title {
+ padding: 5px 10px 5px 10px; font-weight: bold; overflow:hidden; background:#ddd;
+}
+#kiwi .ui_menu .ui_menu_content { padding: 5px 10px 5px 10px; border-top:1px solid #e9e9e9; overflow:hidden; position:relative; }
+#kiwi .ui_menu .ui_menu_content > a { display: block; }
+#kiwi .ui_menu .ui_menu_content > a[class^="icon-"]:before,
+#kiwi .ui_menu .ui_menu_content > a[class*=" icon-"]:before { margin-right: 5px; color:#666; }
+#kiwi .ui_menu .ui_menu_foot {
+ padding: 5px; border-top:1px solid #e9e9e9; background:#ddd; overflow:hidden;
+}
+#kiwi .ui_menu .ui_menu_foot .close { }
+
+
+
+
/**
* Themes
line-height: 1.4em;
vertical-align: middle;
- border-radius:5px;
-moz-border-radius:5px;
-webkit-border-radius:5px;
-khtml-border-radius:5px;
+ border-radius:5px;
behavior: url(border-radius.htc);
background-image: -webkit-gradient(
#kiwi.theme_relaxed #toolbar .panellist .alert_activity { font-weight:normal; }
#kiwi.theme_relaxed #toolbar .panellist .alert_action { font-weight:normal; }
-#kiwi.theme_relaxed #toolbar .panellist .active { padding-right:23px; }
+#kiwi.theme_relaxed #toolbar .panellist .active { padding-right:23px; border-top:2px solid #df6b26; border-bottom:none; }
#kiwi.theme_relaxed #toolbar .panellist li .part:before { content:"\f00d"; }
#kiwi.theme_relaxed #toolbar .panellist li.server span { padding-left: 5px; }
}
#kiwi.theme_relaxed #memberlists ul li a.nick { display:block; color:black; }
-#kiwi.theme_relaxed #memberlists ul li .userbox { margin:4px 1em 5px 1em; padding-bottom:0.7em; font-size:.9em; }
-#kiwi.theme_relaxed #memberlists ul li .userbox a { display:block; text-decoration:none; margin-bottom:2px; }
-#kiwi.theme_relaxed #memberlists ul li .userbox a i { font-size:1.1em; margin-right:5px; }
+#kiwi.theme_relaxed .userbox { margin:4px 1em 5px 1em; padding-bottom:0.7em; font-size:.9em; }
+#kiwi.theme_relaxed .userbox a { display:block; text-decoration:none; margin-bottom:2px; }
+#kiwi.theme_relaxed .userbox a i { font-size:1.1em; margin-right:5px; }
/* User mode styles */
#kiwi.theme_relaxed #memberlists ul li .prefix {
/* The server select dialog */
-#kiwi.theme_relaxed .server_select { width:730px; padding:3em 0 2em 0; margin: 0 auto; }
+#kiwi.theme_relaxed .server_select { margin-left:auto; margin-right:auto; }
#kiwi.theme_relaxed .server_select .more { display: none; width:270px; margin:0 auto; }
#kiwi.theme_relaxed .server_select table tr td { padding:5px; }
#kiwi.theme_relaxed .server_select button { float:right; padding:3px 7px; }
#kiwi.theme_relaxed .server_select .basic input { width:170px; }
#kiwi.theme_relaxed .server_select .basic label { font-size:1.3em; margin-top:4px; }
#kiwi.theme_relaxed .server_select .basic tr.have_pass { font-size:0.8em; }
+#kiwi.theme_relaxed .server_select .basic tr.have_key { font-size:0.8em; }
#kiwi.theme_relaxed .server_select .basic tr.channel td { padding-top:1em; }
#kiwi.theme_relaxed .server_select .basic { border-bottom: 1px dashed gray; margin-bottom:1em; }
#kiwi.theme_relaxed .server_select .basic .show_more { display: block; width:110px; margin:10px 0 0 0; font-size:0.8em; background: url(../img/more.png) no-repeat right 7px; }
}
-#kiwi.theme_relaxed .server_select .kiwi_logo { text-align: center; display:block; }
-#kiwi.theme_relaxed .server_select .kiwi_logo h1 {
- font-size:20px;
- line-height:48px; vertical-align: middle;
+#kiwi.theme_relaxed .server_select .kiwi_logo { margin-top:30px; }
+#kiwi.theme_relaxed .server_select .kiwi_logo h1 span {
+ font-size:14px;
+ line-height:24px; vertical-align: middle;
color: #555555;
}
-#kiwi.theme_relaxed .server_select .kiwi_logo img { }
+#kiwi.theme_relaxed .server_select .kiwi_logo img { display:inline; width:24px; }
#kiwi.theme_relaxed.chanlist_treeview #panels { left:160px; }
}
+#kiwi.theme_relaxed .ui_menu {
+ border-radius: 3px;
+ color: #333;
+ box-shadow: 0 3px 8px rgba(0, 0, 0, .25);
+}
+#kiwi.theme_relaxed .ui_menu .ui_menu_title {
+ border-top-left-radius: 3px;
+ border-top-right-radius: 3px;
+}
+#kiwi.theme_relaxed .ui_menu .ui_menu_content { }
+#kiwi.theme_relaxed .ui_menu .ui_menu_content.hover:hover { background:#f7f7f7; }
+#kiwi.theme_relaxed .ui_menu .ui_menu_foot {
+ border-bottom-left-radius: 3px;
+ border-bottom-right-radius: 3px;
+}
+#kiwi.theme_relaxed .ui_menu .ui_menu_foot .close { float:right; font-size:0.9em; margin-right:1em; color: #999; }
+#kiwi.theme_relaxed .ui_menu .ui_menu_foot .close:hover { color: #222; }
+
+
+
+
+
/**
* Mini theme
*/
#kiwi.theme_mini #toolbar .panellist .alert_activity { font-weight:normal; }
#kiwi.theme_mini #toolbar .panellist .alert_action { font-weight:normal; }
-#kiwi.theme_mini #toolbar .panellist .active { padding-right:23px; }
+#kiwi.theme_mini #toolbar .panellist .active { padding-right:23px; border-top:2px solid #df6b26; border-bottom:none; }
#kiwi.theme_mini #toolbar .panellist li .part:before { content:"\f00d"; }
#kiwi.theme_mini #toolbar .panellist li.server span { padding-left:5px; }
}
#kiwi.theme_mini #memberlists ul li a.nick { display:block; color:black; }
-#kiwi.theme_mini #memberlists ul li .userbox { margin:0 1em 5px 1em; padding-bottom:0.7em; font-size:.9em; }
-#kiwi.theme_mini #memberlists ul li .userbox a { display:block; text-decoration:none; margin-bottom:2px; }
-#kiwi.theme_mini #memberlists ul li .userbox a i { font-size:1.1em; margin-right:5px; }
+#kiwi.theme_mini .userbox { margin:0 1em 5px 1em; padding-bottom:0.7em; font-size:.9em; }
+#kiwi.theme_mini .userbox a { display:block; text-decoration:none; margin-bottom:2px; }
+#kiwi.theme_mini .userbox a i { font-size:1.1em; margin-right:5px; }
#kiwi.theme_mini #controlbox .input {
/* The server select dialog */
#kiwi.theme_mini .server_select { padding:3em 0 2em 0; margin: 0 auto; width:100%; }
#kiwi.theme_mini .server_select .more { display:none; }
-#kiwi.theme_mini .server_select button { display:block; padding:3px 7px; margin:0 auto; }
+#kiwi.theme_mini .server_select button { display:block; padding:3px 7px; margin:1em auto; }
#kiwi.theme_mini .server_select input.nick {
float:none; display:block; width:80%;
padding:0.5em 1em; margin:0 auto;
#kiwi.theme_mini .server_select .basic table tr.channel,
#kiwi.theme_mini .server_select .basic table tr.pass,
#kiwi.theme_mini .server_select .basic table tr.have_pass { display:none; }
+#kiwi.theme_mini .server_select .basic table tr.key,
+#kiwi.theme_mini .server_select .basic table tr.have_key { display:none; }
#kiwi.theme_mini .server_select .basic .show_more { display:none !important; }
#kiwi.theme_mini .server_select.single_server .basic { border:none; }
#kiwi.theme_mini .server_select .status { text-align: center; font-weight: bold; padding:1em; }
padding:0.5em; margin-top:1em; margin-bottom:1em; margin-right:2em;
}
-#kiwi.theme_mini .server_select .divider-verticle {
- display:none;
+#kiwi.theme_mini .server_select .kiwi_logo h1 span {
+ font-size:14px;
+ line-height:24px; vertical-align: middle;
+ color: #555555;
}
+#kiwi.theme_mini .server_select .kiwi_logo img { display:inline; width:24px; }
+
#kiwi.theme_mini .server_select .server_details {
position: relative !important;
float: none !important;
padding: 0 !important;
margin: 2em 0 0 0 !important;
}
-#kiwi.theme_mini .server_select .about_kiwi { display:none; }
-
#kiwi.theme_cli #toolbar .panellist .alert_activity { font-weight:normal; }
#kiwi.theme_cli #toolbar .panellist .alert_action { font-weight:normal; }
-#kiwi.theme_cli #toolbar .panellist .active { padding-right:23px; }
+#kiwi.theme_cli #toolbar .panellist .active { padding-right:23px; border-top:2px solid #df6b26; border-bottom:none; }
#kiwi.theme_cli #toolbar .panellist li .part:before { content:"\f00d"; }
#kiwi.theme_cli #toolbar .panellist li.server span { padding-left:5px; }
transition: 0.2s ease;
}
-#kiwi.theme_cli #memberlists ul li .userbox { margin:0 1em 0 1em; padding-bottom:0.4em; font-size:.9em; }
-#kiwi.theme_cli #memberlists ul li .userbox a { display:block; text-decoration:none; margin-bottom:2px; }
-#kiwi.theme_cli #memberlists ul li .userbox a i { font-size:1.1em; margin-right:5px; }
-#kiwi.theme_cli #memberlists ul li .userbox .divider-horizontal { display:none; }
+#kiwi.theme_cli .userbox { margin:0 1em 0 1em; padding-bottom:0.4em; font-size:.9em; }
+#kiwi.theme_cli .userbox a { display:block; text-decoration:none; margin-bottom:2px; }
+#kiwi.theme_cli .userbox a i { font-size:1.1em; margin-right:5px; }
+#kiwi.theme_cli .userbox .divider-horizontal { display:none; }
#kiwi.theme_cli .messages .msg > div { color:#6d6d6d; font-family: Inconsolata, Consolas, 'courier new', monospace; }
/* The server select dialog */
-#kiwi.theme_cli .server_select { width:730px; padding:3em 0 2em 0; margin: 0 auto; }
+#kiwi.theme_cli .server_select { margin-left:auto; margin-right:auto; color:#eee; }
#kiwi.theme_cli .server_select .more { display: none; width:270px; margin:0 auto; }
#kiwi.theme_cli .server_select table tr td { padding:5px; }
#kiwi.theme_cli .server_select button { float:right; padding:3px 7px; margin-top:10px; }
padding:0.5em; margin-top:1em; margin-bottom:1em; margin-right:2em;
}
+#kiwi.theme_cli .server_select .basic tr.channel .icon-key { color:#555; }
-#kiwi.theme_cli .server_select .kiwi_logo { text-align: center; display:block; }
+
+#kiwi.theme_cli .server_select .kiwi_logo { margin-top:30px; text-align:center; display:block; }
#kiwi.theme_cli .server_select .kiwi_logo h1 {
font-size:20px;
line-height:48px; vertical-align: middle;
- color: #555555;
+ color: #bbb;
}
-#kiwi.theme_cli .server_select .kiwi_logo img { }
+#kiwi.theme_cli .server_select .kiwi_logo img { display:inline; width:24px; }
#kiwi.theme_cli .divider-verticle {
#kiwi.theme_cli.chanlist_treeview #tabs ul li.active { padding-left:1em; }
+#kiwi.theme_cli .ui_menu {
+ color: #333;
+}
+#kiwi.theme_cli .ui_menu .ui_menu_title { }
+#kiwi.theme_cli .ui_menu .ui_menu_content { }
+#kiwi.theme_cli .ui_menu .ui_menu_content.hover:hover { background:#f7f7f7; }
+#kiwi.theme_cli .ui_menu .ui_menu_foot {
+ border-bottom-left-radius: 3px;
+ border-bottom-right-radius: 3px;
+}
+#kiwi.theme_cli .ui_menu .ui_menu_foot .close { float:right; font-size:0.9em; margin-right:1em; color: #999; }
+#kiwi.theme_cli .ui_menu .ui_menu_foot .close:hover { color: #222; }
+
* Basic theme
*/
#kiwi.theme_basic {
- background: #FFF;
+ background: url(../img/background-light.png) left top repeat-x #E3E3E3;
color: #555555;
}
#kiwi.theme_basic,
}
#kiwi.theme_basic #controlbox { background-color:#1B1B1B; }
#kiwi.theme_basic #memberlists_resize_handle {
- display:none; width:0;
+ /*display:none; width:0;*/
}
#kiwi.theme_basic #toolbar .panellist li {
line-height: 1.4em;
#kiwi.theme_basic #toolbar .panellist .alert_activity { font-weight:normal; color:green; }
#kiwi.theme_basic #toolbar .panellist .alert_action { font-weight:normal; color:green; }
-#kiwi.theme_basic #toolbar .panellist .active { padding-right:25px; }
+#kiwi.theme_basic #toolbar .panellist .active { padding-right:25px; border-top:2px solid #df6b26; border-bottom:none; }
#kiwi.theme_basic #toolbar .panellist li .part {}
#kiwi.theme_basic #toolbar .panellist li .part:before { content:"[x]"; }
-#kiwi.theme_basic #toolbar .panellist li.server span { }
+#kiwi.theme_basic #toolbar .panellist li.server span { padding-left: 5px; }
+#kiwi.theme_basic.connected #toolbar .panellist li.server:before { content: "\f0ec"; color:#3F9532; }
+#kiwi.theme_basic #toolbar .panellist li.server:before { content: "\f06a"; color:#900; font-size:1.5em; line-height:1em; vertical-align:middle; }
+
/* Tab texts are within a span */
#kiwi.theme_basic #toolbar .panellist li span { line-height:20px; vertical-align:middle; display:inline-block; }
#kiwi.theme_basic .messages .msg { }
#kiwi.theme_basic .messages .msg > div { font-family: Consolas, "Lucida Console", monospace; font-size:0.9em; }
#kiwi.theme_basic .messages .msg { border: none; }
-#kiwi.theme_basic .messages .msg .time { display:inline; margin-right:1em; margin-left:2px; color:gray; }
+#kiwi.theme_basic .messages .msg .time { display:none; margin-right:1em; margin-left:2px; color:gray; }
#kiwi.theme_basic .messages .msg .nick { display:inline; margin-right:1em; }
#kiwi.theme_basic .messages .msg .nick:before { content:"<"; }
#kiwi.theme_basic .messages .msg .nick:after { content:">"; }
padding:0.5em; margin-top:1em; margin-bottom:1em; margin-right:2em;
}
+#kiwi.theme_basic.timestamps .messages .msg .time { display:inline; }
+#kiwi.theme_basic.timestamps .messages .msg .text { }
+
#kiwi.theme_basic .messages .msg.global_nick_highlight,
#kiwi.theme_basic .messages .msg.highlight { background:#D9D9D9; }
+/* Narrow styling (window width < 400px) */
+#kiwi.theme_basic.narrow .messages .msg .nick { width: auto; }
+#kiwi.theme_basic.narrow .messages .msg .text { margin-left: 1em; border:none; }
+#kiwi.theme_basic.narrow .messages .msg.action .text { margin-left: 1em; }
+
+#kiwi.theme_basic .messages .msg .media { margin-left:0.5em; }
+#kiwi.theme_basic .messages .msg .media a { text-decoration:none; }
+#kiwi.theme_basic .messages .msg .media .media_close { font-size:0.9em; }
+#kiwi.theme_basic .messages .msg .media .media_content { margin:10px 0 0 2em; overflow:hidden; }
+#kiwi.theme_basic .messages .msg .media .media_content img { padding:3px; border:1px solid gray; }
+#kiwi.theme_basic .messages .msg .media .media_content > .content {
+ background: white;
+ overflow: hidden;
+ padding: 10px;
+ border: #DDD 1px solid;
+ border-top-color: #EEE;
+ border-bottom-color: #BBB;
+ -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
+ -moz-box-shadow: 0 1px 3px rgba(0,0,0,0.15);
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
+ border-radius: 5px;
+ float: left;
+}
+
+#kiwi.theme_basic .messages .msg .media.twitter .media_content > .content {
+ background: transparent;
+ border:none;
+ overflow: hidden;
+ box-shadow: none;
+ padding: 0;
+}
+#kiwi.theme_basic .messages .msg .media.reddit .thumbnail_nsfw {
+ display: inline-block;
+ float: left;
+}
+#kiwi.theme_basic .messages .msg .media.reddit .thumbnail { float:left; margin-right: 0.5em; }
+
#kiwi.theme_basic #memberlists {
#kiwi.theme_basic #memberlists ul li:hover {}
#kiwi.theme_basic #memberlists ul li a.nick { display:block; color:black; }
-#kiwi.theme_basic #memberlists ul li .userbox { margin:0 1em 5px 1em; padding-bottom:0.7em; font-size:.9em; }
-#kiwi.theme_basic #memberlists ul li .userbox a { display:block; text-decoration:none; margin-bottom:2px; }
-#kiwi.theme_basic #memberlists ul li .userbox a i { font-size:1.1em; margin-right:5px; }
+#kiwi.theme_basic .userbox { margin:0 1em 5px 1em; padding-bottom:0.7em; font-size:.9em; }
+#kiwi.theme_basic .userbox a { display:block; text-decoration:none; margin-bottom:2px; }
+#kiwi.theme_basic .userbox a i { font-size:1.1em; margin-right:5px; }
#kiwi.theme_basic #controlbox .input {
position:relative;
height:100%; width:100%;
display: block;
+ outline: none;
}
/* The server select dialog */
-#kiwi.theme_basic .server_select { width:730px; padding:3em 0 2em 0; margin: 0 auto; }
+#kiwi.theme_basic .server_select { margin-left:auto; margin-right:auto; }
#kiwi.theme_basic .server_select a { text-decoration: none; }
#kiwi.theme_basic .server_select .more { display: none; width:270px; margin:0 auto; }
#kiwi.theme_basic .server_select table tr td { padding:5px; }
}
-#kiwi.theme_basic .server_select .kiwi_logo { text-align: center; display:block; }
+#kiwi.theme_basic .server_select .kiwi_logo { margin-top:30px; text-align:center; display:block; }
#kiwi.theme_basic .server_select .kiwi_logo h1 {
- font-size:20px;
+ font-size:14px;
line-height:48px; vertical-align: middle;
color: #555555;
}
-#kiwi.theme_basic .server_select .kiwi_logo img { }
+#kiwi.theme_basic .server_select .kiwi_logo img { display:inline; width:24px; }
#kiwi.theme_basic.chanlist_treeview #panels { left:160px; }
border-bottom-right-radius:0;
border-top-right-radius:0;
}
+
+
+#kiwi.theme_basic .ui_menu .ui_menu_foot .close { float:right; font-size:0.9em; margin-right:1em; color: #999; }
+#kiwi.theme_basic .ui_menu .ui_menu_foot .close:hover { color: #222; }
\r
// Event managers for plugins\r
components: {\r
- EventComponent: function(event_source) {\r
+ EventComponent: function(event_source, proxy_event_name) {\r
function proxyEvent(event_name, event_data) {\r
+ if (proxy_event_name !== 'all') {\r
+ event_data = event_name.event_data;\r
+ event_name = event_name.event_name\r
+ }\r
+//console.log(proxy_event_name, event_name, event_data);\r
this.trigger(event_name, event_data);\r
}\r
\r
+ // The event we are to proxy\r
+ proxy_event_name = proxy_event_name || 'all';\r
+\r
+\r
_.extend(this, Backbone.Events);\r
this._source = event_source;\r
\r
// Proxy the events to this dispatcher\r
- event_source.on('all', proxyEvent, this);\r
+ event_source.on(proxy_event_name, proxyEvent, this);\r
\r
// Clean up this object\r
this.dispose = function () {\r
- event_source.off('all', proxyEvent);\r
+ event_source.off(proxy_event_name, proxyEvent);\r
this.off();\r
delete this.event_source;\r
};\r
},\r
\r
- Network: function() {\r
- var obj = new this.EventComponent(_kiwi.gateway);\r
+ Network: function(connection_id) {\r
+ var connection_event;\r
+\r
+ if (typeof connection_id !== 'undefined') {\r
+ connection_event = 'connection:' + connection_id.toString();\r
+ }\r
+\r
+ var obj = new this.EventComponent(_kiwi.gateway, connection_event);\r
var funcs = {\r
kiwi: 'kiwi', raw: 'raw', kick: 'kick', topic: 'topic',\r
part: 'part', join: 'join', action: 'action', ctcp: 'ctcp',\r
- notice: 'notice', msg: 'privmsg',\r
- get: 'get'\r
+ notice: 'notice', msg: 'privmsg', changeNick: 'changeNick'\r
};\r
\r
+ // Proxy each gateway method\r
_.each(funcs, function(gateway_fn, func_name) {\r
obj[func_name] = function() {\r
var fn_name = gateway_fn;\r
- return _kiwi.gateway[fn_name].apply(_kiwi.gateway, arguments);\r
+\r
+ // Add connection_id to the argument list\r
+ var args = Array.prototype.slice.call(arguments, 0);\r
+ args.unshift(connection_id);\r
+\r
+ // Call the gateway function on behalf of this connection\r
+ return _kiwi.gateway[fn_name].apply(_kiwi.gateway, args);\r
};\r
});\r
\r
__dirname + '/app.js',\r
__dirname + '/model_application.js',\r
__dirname + '/model_gateway.js',\r
+ __dirname + '/model_network.js',\r
__dirname + '/model_member.js',\r
__dirname + '/model_memberlist.js',\r
+ __dirname + '/model_newconnection.js',\r
__dirname + '/model_panel.js',\r
__dirname + '/model_panellist.js',\r
+ __dirname + '/model_networkpanellist.js',\r
__dirname + '/model_query.js',\r
__dirname + '/model_channel.js',\r
__dirname + '/model_server.js',\r
</ul>\r
</div>\r
\r
- <div id="tabs">\r
- <ul class="panellist channels"></ul>\r
- <ul class="panellist applets"></ul>\r
- </div>\r
+ <div id="tabs"></div>\r
\r
<div id="topic">\r
<div contenteditable="true"></div>\r
<div class="panel_container container1"></div>\r
</div>\r
\r
- <div id="memberlists"></div>\r
+ <div id="memberlists" class="disabled"></div>\r
\r
<div id="controlbox">\r
<div class="input">\r
<a class="query"><i class="icon-comment"></i>Message</a>\r
<a class="info"><i class="icon-info-sign"></i>Info</a>\r
<a class="slap"><i class="icon-user-md"></i>Slap!</a>\r
-\r
- <div class="divider-horizontal"></div>\r
</div>\r
</script>\r
\r
</form>\r
</script>\r
\r
+ <script type="text/html" id="tmpl_new_connection_info">\r
+ <div style="margin:1em 20px;">\r
+ <img src="https://kiwiirc.com/img/ico.png" alt="KiwiIRC Logo" title="Kiwi IRC" style="display:block; margin:0 auto;"/> <br />\r
+ <p style="font-style:italic;">A <strong>hand-crafted IRC client</strong> that you can enjoy. Designed to be used <strong>easily</strong> and <strong>freely</strong>.</p>\r
+\r
+ <p style="font-size:0.9em;margin-top:2em;">Peek at the <a href="https://www.kiwiirc.com/">Kiwi IRC homepage</a> for more information or to find out how to embed it on your own website. Looking for source code? Try the <a href="http://github.com/prawnsalad/KiwiIRC/">GitHub</a> page. This network of people may not be associated with Kiwi IRC itself.</p>\r
+ </div>\r
+ </script>\r
+\r
<script type="text/html" id="tmpl_server_select">\r
<div class="server_select">\r
\r
- <div class="server_details" style="position:relative;float:left;width:320px;padding-right:3em;margin-top:50px;">\r
+ <div class="side_panel" style="position:absolute;top:0px;left:320px;">\r
+ <div class="content" style="position:relative;width:300px;">\r
+ </div>\r
+ </div>\r
+\r
+ <div class="server_details" style="position:relative;width:320px;">\r
<div class="status">Think of a nickname..</div>\r
\r
<form>\r
\r
<tr class="channel">\r
<td><label for="server_select_channel">Channel</label></td>\r
- <td><input type="text" class="channel" id="server_select_channel"><input type="hidden" class="channel_key"></td>\r
+ <td>\r
+ <div style="position:relative;">\r
+ <input type="text" class="channel" id="server_select_channel">\r
+ <i class="icon-key" title="Channel Key"></i>\r
+ </div>\r
+ </td>\r
+ </tr>\r
+\r
+ <tr class="have_key">\r
+ <td colspan="2">\r
+ <label for="server_select_show_channel_key">Channel requires a key</label> <input type="checkbox" id="server_select_show_channel_key" style="width:auto;" />\r
+ </td>\r
+ </tr>\r
+\r
+ <tr class="key">\r
+ <td><label for="server_select_channel_key">Key</label></td>\r
+ <td><input type="password" class="channel_key" id="server_select_channel_key"></td>\r
</tr>\r
\r
<tr class="start">\r
</div>\r
</form>\r
\r
- <div class="divider-verticle"></div>\r
- </div>\r
-\r
- <div class="about_kiwi" style="position:relative;float:left;width:320px;margin-left:3em;color:#555555;">\r
<a class="kiwi_logo" href="https://kiwiirc.com/" target="_blank">\r
- <img src="<%base_path%>/assets/img/ico.png" alt="KiwiIRC Logo" title="Kiwi IRC" /> <br />\r
- <h1>Powered by Kiwi IRC</h1>\r
+ <h1><span>Powered by Kiwi IRC</span> <img src="<%base_path%>/assets/img/ico.png" alt="KiwiIRC Logo" title="Kiwi IRC" /></h1>\r
</a>\r
-\r
- <p style="font-style:italic;">A <strong>hand-crafted IRC client</strong> that you can enjoy. Designed to be used <strong>easily</strong> and <strong>freely</strong>.</p>\r
-\r
- <p style="font-size:0.9em;margin-top:2em;">Peek at the <a href="https://kiwiirc.com/">Kiwi IRC homepage</a> for more information or to find out how to embed it on your own website. Looking for source code? Try the <a href="http://github.com/prawnsalad/KiwiIRC/">GitHub</a> page. This network of people may not be associated with Kiwi IRC itself.</p>\r
</div>\r
</div>\r
</script>\r
'dev/model_gateway.js'\r
],\r
[\r
+ 'dev/model_newconnection.js',\r
'dev/model_panellist.js',\r
+ 'dev/model_networkpanellist.js',\r
'dev/model_panel.js',\r
'dev/model_member.js',\r
- 'dev/model_memberlist.js'\r
+ 'dev/model_memberlist.js',\r
+ 'dev/model_network.js'\r
],\r
\r
[\r
};\r
</script>\r
</body>\r
-</html>
\ No newline at end of file
+</html>\r
loadOnce: function (applet_name) {\r
\r
// See if we have an instance loaded already\r
- var applet = _.find(_kiwi.app.panels.models, function(panel) {\r
+ var applet = _.find(_kiwi.app.panels('applets'), function(panel) {\r
// Ignore if it's not an applet\r
if (!panel.isApplet()) return;\r
\r
applet.load(new _kiwi.applets[applet_name]({_applet_name: applet_name}));\r
\r
// Add it into the tab list\r
- _kiwi.app.panels.add(applet);\r
+ _kiwi.app.applet_panels.add(applet);\r
\r
\r
return applet;\r
\r
\r
var model = function () {\r
- /** Instance of _kiwi.model.PanelList */\r
- this.panels = null;\r
-\r
/** _kiwi.view.Application */\r
this.view = null;\r
\r
\r
// Best guess at where the kiwi server is\r
this.detectKiwiServer();\r
+\r
+ // Takes instances of model_network\r
+ this.connections = new _kiwi.model.NetworkPanelList();\r
};\r
\r
+\r
this.start = function () {\r
// Only debug if set in the querystring\r
if (!getQueryVariable('debug')) {\r
} else {\r
//manageDebug(true);\r
}\r
- \r
+\r
// Set the gateway up\r
_kiwi.gateway = new _kiwi.model.Gateway();\r
this.bindGatewayCommands(_kiwi.gateway);\r
\r
this.view.barsHide(true);\r
\r
- this.panels.server.server_login.bind('server_connect', function (event) {\r
- var server_login = this,\r
- transport_path = '';\r
- auto_connect_details = event;\r
-\r
- server_login.networkConnecting();\r
- \r
- // Path to get the socket.io transport code\r
- transport_path = that.kiwi_server + that.get('base_path') + '/transport/socket.io.js?ts='+(new Date().getTime());\r
- \r
- $script(transport_path, function () {\r
- if (!window.io) {\r
- kiwiServerNotFound();\r
- return;\r
- }\r
- _kiwi.gateway.set('kiwi_server', that.kiwi_server + '/kiwi');\r
- _kiwi.gateway.set('nick', event.nick);\r
- \r
- _kiwi.gateway.connect(event.server, event.port, event.ssl, event.password, function (error) {\r
- if (error) {\r
- kiwiServerNotFound();\r
- }\r
- });\r
- });\r
- });\r
-\r
- // TODO: Shouldn't really be here but it's not working in the view.. :/\r
- // Hack for firefox browers: Focus is not given on this event loop iteration\r
- setTimeout(function(){\r
- _kiwi.app.panels.server.server_login.$el.find('.nick').select();\r
- }, 0);\r
+ this.showIntialConenctionDialog();\r
};\r
\r
\r
- function kiwiServerNotFound (e) {\r
- that.panels.server.server_login.showError();\r
- }\r
-\r
-\r
this.detectKiwiServer = function () {\r
// If running from file, default to localhost:7777 by default\r
if (window.location.protocol === 'file:') {\r
};\r
\r
\r
+ this.showIntialConenctionDialog = function() {\r
+ var connection_dialog = new _kiwi.model.NewConnection();\r
+ this.populateDefaultServerSettings(connection_dialog);\r
+\r
+ connection_dialog.view.$el.addClass('initial');\r
+ this.view.$el.find('.panel_container:first').append(connection_dialog.view.$el);\r
+\r
+ var $info = $($('#tmpl_new_connection_info').html());\r
+\r
+ if ($info.html()) {\r
+ connection_dialog.view.infoBoxSet($info);\r
+ connection_dialog.view.infoBoxShow();\r
+ }\r
+\r
+ // TODO: Shouldn't really be here but it's not working in the view.. :/\r
+ // Hack for firefox browers: Focus is not given on this event loop iteration\r
+ setTimeout(function(){\r
+ connection_dialog.view.$el.find('.nick').select();\r
+ }, 0);\r
+\r
+ // Once connected, close this dialog and remove its own event\r
+ var fn = function() {\r
+ connection_dialog.view.$el.slideUp(function() {\r
+ connection_dialog.view.dispose();\r
+ connection_dialog = null;\r
+\r
+ _kiwi.gateway.off('onconnect', fn);\r
+ });\r
+\r
+ };\r
+ _kiwi.gateway.on('onconnect', fn);\r
+ };\r
+\r
+\r
this.initializeClient = function () {\r
this.view = new _kiwi.view.Application({model: this, el: this.get('container')});\r
- \r
+\r
+ // Applets panel list\r
+ this.applet_panels = new _kiwi.model.PanelList();\r
+ this.applet_panels.view.$el.addClass('panellist applets');\r
+ this.view.$el.find('#tabs').append(this.applet_panels.view.$el);\r
+\r
/**\r
* Set the UI components up\r
*/\r
- this.panels = new _kiwi.model.PanelList();\r
-\r
this.controlbox = new _kiwi.view.ControlBox({el: $('#controlbox')[0]});\r
this.bindControllboxCommands(this.controlbox);\r
\r
this.message = new _kiwi.view.StatusMessage({el: $('#status_message')[0]});\r
\r
this.resize_handle = new _kiwi.view.ResizeHandler({el: $('#memberlists_resize_handle')[0]});\r
- \r
- this.panels.server.view.show();\r
\r
// Rejigg the UI sizes\r
this.view.doLayout();\r
-\r
- this.populateDefaultServerSettings();\r
};\r
\r
\r
this.initializeGlobals = function () {\r
+ _kiwi.global.connections = this.connections;\r
+\r
_kiwi.global.panels = this.panels;\r
- \r
+ _kiwi.global.panels.applets = this.applet_panels;\r
+\r
_kiwi.global.components.Applet = _kiwi.model.Applet;\r
_kiwi.global.components.Panel =_kiwi.model.Panel;\r
};\r
\r
\r
- this.populateDefaultServerSettings = function () {\r
+ this.populateDefaultServerSettings = function (new_connection_dialog) {\r
var parts;\r
var defaults = {\r
- nick: getQueryVariable('nick') || '',\r
+ nick: '',\r
server: '',\r
port: 6667,\r
ssl: false,\r
- channel: window.location.hash || '#chat',\r
+ channel: '#chat',\r
channel_key: ''\r
};\r
var uricheck;\r
\r
/**\r
* Get any settings set by the server\r
- * These settings may be changed in the server selection dialog\r
+ * These settings may be changed in the server selection dialog or via URL parameters\r
*/\r
if (this.server_settings.client) {\r
if (this.server_settings.client.nick)\r
* These settings may be changed in the server selection dialog\r
*/\r
\r
+ // Any query parameters first\r
+ if (getQueryVariable('nick'))\r
+ defaults.nick = getQueryVariable('nick');\r
+\r
+ if (window.location.hash)\r
+ defaults.channel = window.location.hash;\r
+\r
+\r
// Process the URL part by part, extracting as we go\r
parts = window.location.pathname.toString().replace(this.get('base_path'), '').split('/');\r
\r
defaults.nick = defaults.nick.replace('?', Math.floor(Math.random() * 100000).toString());\r
\r
// Populate the server select box with defaults\r
- this.panels.server.server_login.populateFields(defaults);\r
+ new_connection_dialog.view.populateFields(defaults);\r
};\r
\r
\r
- this.bindGatewayCommands = function (gw) {\r
- gw.on('onmotd', function (event) {\r
- that.panels.server.addMsg(_kiwi.gateway.get('name'), event.msg, 'motd');\r
- });\r
+ this.panels = (function() {\r
+ var fn = function(panel_type) {\r
+ var panels;\r
+\r
+ // Default panel type\r
+ panel_type = panel_type || 'connections';\r
+\r
+ switch (panel_type) {\r
+ case 'connections':\r
+ panels = this.connections.panels();\r
+ break;\r
+ case 'applets':\r
+ panels = this.applet_panels.models;\r
+ break;\r
+ }\r
\r
+ // Active panels / server\r
+ panels.active = this.connections.active_panel;\r
+ panels.server = this.connections.active_connection ?\r
+ this.connections.active_connection.panels.server :\r
+ null;\r
+\r
+ return panels;\r
+ };\r
\r
+ _.extend(fn, Backbone.Events);\r
+\r
+ return fn;\r
+ })();\r
+\r
+\r
+ this.bindGatewayCommands = function (gw) {\r
gw.on('onconnect', function (event) {\r
that.view.barsShow();\r
- \r
+\r
if (auto_connect_details.channel) {\r
that.controlbox.processInput('/JOIN ' + auto_connect_details.channel + ' ' + auto_connect_details.channel_key);\r
}\r
that.view.$el.removeClass('connected');\r
\r
// Mention the disconnection on every channel\r
- $.each(_kiwi.app.panels.models, function (idx, panel) {\r
+ $.each(_kiwi.app.connections.getByConnectionId(0).panels.models, function (idx, panel) {\r
if (!panel || !panel.isChannel()) return;\r
panel.addMsg('', msg, 'action quit');\r
});\r
- _kiwi.app.panels.server.addMsg('', msg, 'action quit');\r
+ _kiwi.app.connections.getByConnectionId(0).panels.server.addMsg('', msg, 'action quit');\r
\r
gw_stat = 1;\r
});\r
gw.on('reconnecting', function (event) {\r
msg = 'You have been disconnected. Attempting to reconnect again in ' + (event.delay/1000) + ' seconds..';\r
- _kiwi.app.panels.server.addMsg('', msg, 'action quit');\r
+ _kiwi.app.connections.getByConnectionId(0).panels.server.addMsg('', msg, 'action quit');\r
});\r
gw.on('onconnect', function (event) {\r
that.view.$el.addClass('connected');\r
that.message.text(msg, {timeout: 5000});\r
\r
// Mention the disconnection on every channel\r
- $.each(_kiwi.app.panels.models, function (idx, panel) {\r
+ $.each(_kiwi.app.connections.getByConnectionId(0).panels.models, function (idx, panel) {\r
if (!panel || !panel.isChannel()) return;\r
panel.addMsg('', msg, 'action join');\r
});\r
- _kiwi.app.panels.server.addMsg('', msg, 'action join');\r
+ _kiwi.app.connections.getByConnectionId(0).panels.server.addMsg('', msg, 'action join');\r
\r
gw_stat = 0;\r
});\r
})();\r
\r
-\r
- gw.on('onjoin', function (event) {\r
- var c, members, user;\r
- c = that.panels.getByName(event.channel);\r
- if (!c) {\r
- c = new _kiwi.model.Channel({name: event.channel});\r
- that.panels.add(c);\r
- }\r
-\r
- members = c.get('members');\r
- if (!members) return;\r
-\r
- user = new _kiwi.model.Member({nick: event.nick, ident: event.ident, hostname: event.hostname});\r
- members.add(user);\r
- // TODO: highlight the new channel in some way\r
- });\r
-\r
-\r
- gw.on('onpart', function (event) {\r
- var channel, members, user,\r
- part_options = {};\r
-\r
- part_options.type = 'part';\r
- part_options.message = event.message || '';\r
-\r
- channel = that.panels.getByName(event.channel);\r
- if (!channel) return;\r
-\r
- // If this is us, close the panel\r
- if (event.nick === _kiwi.gateway.get('nick')) {\r
- channel.close();\r
- return;\r
- }\r
-\r
- members = channel.get('members');\r
- if (!members) return;\r
-\r
- user = members.getByNick(event.nick);\r
- if (!user) return;\r
-\r
- members.remove(user, part_options);\r
- });\r
-\r
-\r
- gw.on('onquit', function (event) {\r
- var member, members,\r
- quit_options = {};\r
-\r
- quit_options.type = 'quit';\r
- quit_options.message = event.message || '';\r
-\r
- $.each(that.panels.models, function (index, panel) {\r
- if (!panel.isChannel()) return;\r
-\r
- member = panel.get('members').getByNick(event.nick);\r
- if (member) {\r
- panel.get('members').remove(member, quit_options);\r
- }\r
- });\r
- });\r
-\r
-\r
- gw.on('onkick', function (event) {\r
- var channel, members, user,\r
- part_options = {};\r
-\r
- part_options.type = 'kick';\r
- part_options.by = event.nick;\r
- part_options.message = event.message || '';\r
-\r
- channel = that.panels.getByName(event.channel);\r
- if (!channel) return;\r
-\r
- members = channel.get('members');\r
- if (!members) return;\r
-\r
- user = members.getByNick(event.kicked);\r
- if (!user) return;\r
-\r
- members.remove(user, part_options);\r
-\r
- if (event.kicked === _kiwi.gateway.get('nick')) {\r
- members.reset([]);\r
- }\r
- \r
- });\r
-\r
-\r
- gw.on('onmsg', function (event) {\r
- var panel,\r
- is_pm = (event.channel == _kiwi.gateway.get('nick'));\r
-\r
- // An ignored user? don't do anything with it\r
- if (gw.isNickIgnored(event.nick)) {\r
- return;\r
- }\r
-\r
- if (is_pm) {\r
- // If a panel isn't found for this PM, create one\r
- panel = that.panels.getByName(event.nick);\r
- if (!panel) {\r
- panel = new _kiwi.model.Query({name: event.nick});\r
- that.panels.add(panel);\r
- }\r
-\r
- } else {\r
- // If a panel isn't found for this channel, reroute to the\r
- // server panel\r
- panel = that.panels.getByName(event.channel);\r
- if (!panel) {\r
- panel = that.panels.server;\r
- }\r
- }\r
-\r
- panel.addMsg(event.nick, event.msg);\r
- });\r
-\r
-\r
- gw.on('onctcp_request', function (event) {\r
- // An ignored user? don't do anything with it\r
- if (gw.isNickIgnored(event.nick)) {\r
- return;\r
- }\r
-\r
- // Reply to a TIME ctcp\r
- if (event.msg.toUpperCase() === 'TIME') {\r
- gw.ctcp(false, event.type, event.nick, (new Date()).toString());\r
- }\r
- });\r
-\r
-\r
- gw.on('onctcp_response', function (event) {\r
- // An ignored user? don't do anything with it\r
- if (gw.isNickIgnored(event.nick)) {\r
- return;\r
- }\r
- \r
- that.panels.server.addMsg('[' + event.nick + ']', 'CTCP ' + event.msg);\r
- });\r
-\r
-\r
- gw.on('onnotice', function (event) {\r
- var panel;\r
-\r
- // An ignored user? don't do anything with it\r
- if (event.nick && gw.isNickIgnored(event.nick)) {\r
- return;\r
- }\r
-\r
- // Find a panel for the destination(channel) or who its from\r
- panel = that.panels.getByName(event.target) || that.panels.getByName(event.nick);\r
- if (!panel) {\r
- panel = that.panels.server;\r
- }\r
-\r
- panel.addMsg('[' + (event.nick||'') + ']', event.msg);\r
- });\r
-\r
-\r
- gw.on('onaction', function (event) {\r
- var panel,\r
- is_pm = (event.channel == _kiwi.gateway.get('nick'));\r
-\r
- // An ignored user? don't do anything with it\r
- if (gw.isNickIgnored(event.nick)) {\r
- return;\r
- }\r
-\r
- if (is_pm) {\r
- // If a panel isn't found for this PM, create one\r
- panel = that.panels.getByName(event.nick);\r
- if (!panel) {\r
- panel = new _kiwi.model.Channel({name: event.nick});\r
- that.panels.add(panel);\r
- }\r
-\r
- } else {\r
- // If a panel isn't found for this channel, reroute to the\r
- // server panel\r
- panel = that.panels.getByName(event.channel);\r
- if (!panel) {\r
- panel = that.panels.server;\r
- }\r
- }\r
-\r
- panel.addMsg('', '* ' + event.nick + ' ' + event.msg, 'action');\r
- });\r
-\r
-\r
- gw.on('ontopic', function (event) {\r
- var c;\r
- c = that.panels.getByName(event.channel);\r
- if (!c) return;\r
-\r
- // Set the channels topic\r
- c.set('topic', event.topic);\r
-\r
- // If this is the active channel, update the topic bar too\r
- if (c.get('name') === _kiwi.app.panels.active.get('name')) {\r
- that.topicbar.setCurrentTopic(event.topic);\r
- }\r
- });\r
-\r
-\r
- gw.on('ontopicsetby', function (event) {\r
- var c, when;\r
- c = that.panels.getByName(event.channel);\r
- if (!c) return;\r
-\r
- when = formatDate(new Date(event.when * 1000));\r
- c.addMsg('', 'Topic set by ' + event.nick + ' at ' + when, 'topic');\r
- });\r
-\r
-\r
- gw.on('onuserlist', function (event) {\r
- var channel;\r
- channel = that.panels.getByName(event.channel);\r
-\r
- // If we didn't find a channel for this, may aswell leave\r
- if (!channel) return;\r
-\r
- channel.temp_userlist = channel.temp_userlist || [];\r
- _.each(event.users, function (item) {\r
- var user = new _kiwi.model.Member({nick: item.nick, modes: item.modes});\r
- channel.temp_userlist.push(user);\r
- });\r
- });\r
-\r
-\r
- gw.on('onuserlist_end', function (event) {\r
- var channel;\r
- channel = that.panels.getByName(event.channel);\r
-\r
- // If we didn't find a channel for this, may aswell leave\r
- if (!channel) return;\r
-\r
- // Update the members list with the new list\r
- channel.get('members').reset(channel.temp_userlist || []);\r
-\r
- // Clear the temporary userlist\r
- delete channel.temp_userlist;\r
- });\r
-\r
-\r
- gw.on('onmode', function (event) {\r
- var channel, i, prefixes, members, member, find_prefix;\r
- \r
- // Build a nicely formatted string to be displayed to a regular human\r
- function friendlyModeString (event_modes, alt_target) {\r
- var modes = {}, return_string;\r
-\r
- // If no default given, use the main event info\r
- if (!event_modes) {\r
- event_modes = event.modes;\r
- alt_target = event.target;\r
- }\r
-\r
- // Reformat the mode object to make it easier to work with\r
- _.each(event_modes, function (mode){\r
- var param = mode.param || alt_target || '';\r
-\r
- // Make sure we have some modes for this param\r
- if (!modes[param]) {\r
- modes[param] = {'+':'', '-':''};\r
- }\r
-\r
- modes[param][mode.mode[0]] += mode.mode.substr(1);\r
- });\r
-\r
- // Put the string together from each mode\r
- return_string = [];\r
- _.each(modes, function (modeset, param) {\r
- var str = '';\r
- if (modeset['+']) str += '+' + modeset['+'];\r
- if (modeset['-']) str += '-' + modeset['-'];\r
- return_string.push(str + ' ' + param);\r
- });\r
- return_string = return_string.join(', ');\r
-\r
- return return_string;\r
- }\r
-\r
-\r
- channel = that.panels.getByName(event.target);\r
- if (channel) {\r
- prefixes = _kiwi.gateway.get('user_prefixes');\r
- find_prefix = function (p) {\r
- return event.modes[i].mode[1] === p.mode;\r
- };\r
- for (i = 0; i < event.modes.length; i++) {\r
- if (_.any(prefixes, find_prefix)) {\r
- if (!members) {\r
- members = channel.get('members');\r
- }\r
- member = members.getByNick(event.modes[i].param);\r
- if (!member) {\r
- console.log('MODE command recieved for unknown member %s on channel %s', event.modes[i].param, event.target);\r
- return;\r
- } else {\r
- if (event.modes[i].mode[0] === '+') {\r
- member.addMode(event.modes[i].mode[1]);\r
- } else if (event.modes[i].mode[0] === '-') {\r
- member.removeMode(event.modes[i].mode[1]);\r
- }\r
- members.sort();\r
- //channel.addMsg('', '== ' + event.nick + ' set mode ' + event.modes[i].mode + ' ' + event.modes[i].param, 'action mode');\r
- }\r
- } else {\r
- // Channel mode being set\r
- // TODO: Store this somewhere?\r
- //channel.addMsg('', 'CHANNEL === ' + event.nick + ' set mode ' + event.modes[i].mode + ' on ' + event.target, 'action mode');\r
- }\r
- }\r
-\r
- channel.addMsg('', '== ' + event.nick + ' sets mode ' + friendlyModeString(), 'action mode');\r
- } else {\r
- // This is probably a mode being set on us.\r
- if (event.target.toLowerCase() === _kiwi.gateway.get("nick").toLowerCase()) {\r
- that.panels.server.addMsg('', '== ' + event.nick + ' set mode ' + friendlyModeString(), 'action mode');\r
- } else {\r
- console.log('MODE command recieved for unknown target %s: ', event.target, event);\r
- }\r
- }\r
- });\r
-\r
-\r
- gw.on('onnick', function (event) {\r
- var member;\r
-\r
- $.each(that.panels.models, function (index, panel) {\r
- if (!panel.isChannel()) return;\r
-\r
- member = panel.get('members').getByNick(event.nick);\r
- if (member) {\r
- member.set('nick', event.newnick);\r
- panel.addMsg('', '== ' + event.nick + ' is now known as ' + event.newnick, 'action nick');\r
- }\r
- });\r
- });\r
-\r
-\r
- gw.on('onwhois', function (event) {\r
- /*globals secondsToTime */\r
- var logon_date, idle_time = '', panel;\r
-\r
- if (event.end) {\r
- return;\r
- }\r
-\r
- if (typeof event.idle !== 'undefined') {\r
- idle_time = secondsToTime(parseInt(event.idle, 10));\r
- 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
- panel = _kiwi.app.panels.active;\r
- if (event.ident) {\r
- panel.addMsg(event.nick, event.nick + ' [' + event.nick + '!' + event.ident + '@' + event.host + '] * ' + event.msg, 'whois');\r
- } else if (event.chans) {\r
- panel.addMsg(event.nick, 'Channels: ' + event.chans, 'whois');\r
- } else if (event.irc_server) {\r
- panel.addMsg(event.nick, 'Connected to server: ' + event.irc_server, 'whois');\r
- } else if (event.msg) {\r
- panel.addMsg(event.nick, event.msg, 'whois');\r
- } else if (event.logon) {\r
- logon_date = new Date();\r
- logon_date.setTime(event.logon * 1000);\r
- logon_date = formatDate(logon_date);\r
-\r
- panel.addMsg(event.nick, 'idle for ' + idle_time + ', signed on ' + logon_date, 'whois');\r
- } else {\r
- panel.addMsg(event.nick, 'idle for ' + idle_time, 'whois');\r
- }\r
- });\r
-\r
- gw.on('onaway', function (event) {\r
- $.each(that.panels.models, function (index, panel) {\r
- if (!panel.isChannel()) return;\r
-\r
- member = panel.get('members').getByNick(event.nick);\r
- if (member) {\r
- member.set('away', !(!event.trailing));\r
- }\r
- });\r
- });\r
-\r
-\r
- gw.on('onlist_start', function (data) {\r
- var chanlist = _kiwi.model.Applet.loadOnce('kiwi_chanlist');\r
- chanlist.view.show();\r
- });\r
-\r
-\r
- gw.on('onirc_error', function (data) {\r
- var panel, tmp;\r
-\r
- if (data.channel !== undefined && !(panel = _kiwi.app.panels.getByName(data.channel))) {\r
- panel = _kiwi.app.panels.server;\r
- }\r
-\r
- switch (data.error) {\r
- case 'banned_from_channel':\r
- panel.addMsg(' ', '== You are banned from ' + data.channel + '. ' + data.reason, 'status');\r
- _kiwi.app.message.text('You are banned from ' + data.channel + '. ' + data.reason);\r
- break;\r
- case 'bad_channel_key':\r
- panel.addMsg(' ', '== Bad channel key for ' + data.channel, 'status');\r
- _kiwi.app.message.text('Bad channel key or password for ' + data.channel);\r
- break;\r
- case 'invite_only_channel':\r
- panel.addMsg(' ', '== ' + data.channel + ' is invite only.', 'status');\r
- _kiwi.app.message.text(data.channel + ' is invite only');\r
- break;\r
- case 'channel_is_full':\r
- panel.addMsg(' ', '== ' + data.channel + ' is full.', 'status');\r
- _kiwi.app.message.text(data.channel + ' is full');\r
- break;\r
- case 'chanop_privs_needed':\r
- panel.addMsg(' ', '== ' + data.reason, 'status');\r
- _kiwi.app.message.text(data.reason + ' (' + data.channel + ')');\r
- break;\r
- case 'no_such_nick':\r
- tmp = _kiwi.app.panels.getByName(data.nick);\r
- if (tmp) {\r
- tmp.addMsg(' ', '== ' + data.nick + ': ' + data.reason, 'status');\r
- } else {\r
- _kiwi.app.panels.server.addMsg(' ', '== ' + data.nick + ': ' + data.reason, 'status');\r
- }\r
- break;\r
- case 'nickname_in_use':\r
- _kiwi.app.panels.server.addMsg(' ', '== The nickname ' + data.nick + ' is already in use. Please select a new nickname', 'status');\r
- if (_kiwi.app.panels.server !== _kiwi.app.panels.active) {\r
- _kiwi.app.message.text('The nickname "' + data.nick + '" is already in use. Please select a new nickname');\r
- }\r
-\r
- // Only show the nickchange component if the controlbox is open\r
- if (that.controlbox.$el.css('display') !== 'none') {\r
- (new _kiwi.view.NickChangeBox()).render();\r
- }\r
-\r
- case 'password_mismatch':\r
- _kiwi.app.panels.server.addMsg(' ', '== Incorrect password given', 'status');\r
- break;\r
- default:\r
- // We don't know what data contains, so don't do anything with it.\r
- //_kiwi.front.tabviews.server.addMsg(null, ' ', '== ' + data, 'status');\r
- }\r
- });\r
};\r
\r
\r
controlbox.on('command:part', partCommand);\r
\r
controlbox.on('command:nick', function (ev) {\r
- _kiwi.gateway.changeNick(ev.params[0]);\r
+ _kiwi.gateway.changeNick(null, ev.params[0]);\r
});\r
\r
controlbox.on('command:query', queryCommand);\r
\r
controlbox.on('command:ctcp', ctcpCommand);\r
\r
+ controlbox.on('command:server', serverCommand);\r
+\r
\r
controlbox.on('command:css', function (ev) {\r
var queryString = '?reload=' + new Date().getTime();\r
}\r
\r
// Read the value to the user\r
- _kiwi.app.panels.active.addMsg('', setting + ' = ' + _kiwi.global.settings.get(setting).toString());\r
+ _kiwi.app.panels().active.addMsg('', setting + ' = ' + _kiwi.global.settings.get(setting).toString());\r
});\r
\r
\r
controlbox.on('command:save', function (ev) {\r
_kiwi.global.settings.save();\r
- _kiwi.app.panels.active.addMsg('', 'Settings have been saved');\r
+ _kiwi.app.panels().active.addMsg('', 'Settings have been saved');\r
});\r
\r
\r
// No parameters passed so list them\r
if (!ev.params[1]) {\r
$.each(controlbox.preprocessor.aliases, function (name, rule) {\r
- _kiwi.app.panels.server.addMsg(' ', name + ' => ' + rule);\r
+ _kiwi.app.panels().server.addMsg(' ', name + ' => ' + rule);\r
});\r
return;\r
}\r
// No parameters passed so list them\r
if (!ev.params[0]) {\r
if (list.length > 0) {\r
- _kiwi.app.panels.active.addMsg(' ', 'Ignored nicks:');\r
+ _kiwi.app.panels().active.addMsg(' ', 'Ignored nicks:');\r
$.each(list, function (idx, ignored_pattern) {\r
- _kiwi.app.panels.active.addMsg(' ', ignored_pattern);\r
+ _kiwi.app.panels().active.addMsg(' ', ignored_pattern);\r
});\r
} else {\r
- _kiwi.app.panels.active.addMsg(' ', 'Not ignoring anybody');\r
+ _kiwi.app.panels().active.addMsg(' ', 'Not ignoring anybody');\r
}\r
return;\r
}\r
// We have a parameter, so add it\r
list.push(ev.params[0]);\r
_kiwi.gateway.set('ignore_list', list);\r
- _kiwi.app.panels.active.addMsg(' ', 'Ignoring ' + ev.params[0]);\r
+ _kiwi.app.panels().active.addMsg(' ', 'Ignoring ' + ev.params[0]);\r
});\r
\r
\r
var list = _kiwi.gateway.get('ignore_list');\r
\r
if (!ev.params[0]) {\r
- _kiwi.app.panels.active.addMsg(' ', 'Specifiy which nick you wish to stop ignoring');\r
+ _kiwi.app.panels().active.addMsg(' ', 'Specifiy which nick you wish to stop ignoring');\r
return;\r
}\r
\r
\r
_kiwi.gateway.set('ignore_list', list);\r
\r
- _kiwi.app.panels.active.addMsg(' ', 'Stopped ignoring ' + ev.params[0]);\r
+ _kiwi.app.panels().active.addMsg(' ', 'Stopped ignoring ' + ev.params[0]);\r
});\r
\r
\r
function unknownCommand (ev) {\r
var raw_cmd = ev.command + ' ' + ev.params.join(' ');\r
console.log('RAW: ' + raw_cmd);\r
- _kiwi.gateway.raw(raw_cmd);\r
+ _kiwi.gateway.raw(null, raw_cmd);\r
}\r
\r
function allCommands (ev) {}\r
\r
function joinCommand (ev) {\r
- var channel, channel_names;\r
+ var panels, channel_names;\r
\r
channel_names = ev.params.join(' ').split(',');\r
+ panels = that.connections.active_connection.createAndJoinChannels(channel_names);\r
\r
- $.each(channel_names, function (index, channel_name_key) {\r
- // We may have a channel key so split it off\r
- var spli = channel_name_key.split(' '),\r
- channel_name = spli[0],\r
- channel_key = spli[1] || '';\r
-\r
- // Trim any whitespace off the name\r
- channel_name = channel_name.trim();\r
-\r
- // If not a valid channel name, display a warning\r
- if (!that.isChannelName(channel_name)) {\r
- _kiwi.app.panels.server.addMsg('', channel_name + ' is not a valid channel name');\r
- _kiwi.app.message.text(channel_name + ' is not a valid channel name', {timeout: 5000});\r
- return;\r
- }\r
-\r
- // Check if we have the panel already. If not, create it\r
- channel = that.panels.getByName(channel_name);\r
- if (!channel) {\r
- channel = new _kiwi.model.Channel({name: channel_name});\r
- _kiwi.app.panels.add(channel);\r
- }\r
-\r
- _kiwi.gateway.join(channel_name, channel_key);\r
- });\r
-\r
- if (channel) channel.view.show();\r
- \r
+ // Show the last channel if we have one\r
+ if (panels)\r
+ panels[panels.length - 1].view.show();\r
}\r
\r
function queryCommand (ev) {\r
destination = ev.params[0];\r
\r
// Check if we have the panel already. If not, create it\r
- panel = that.panels.getByName(destination);\r
+ panel = that.connections.active_connection.panels.getByName(destination);\r
if (!panel) {\r
panel = new _kiwi.model.Query({name: destination});\r
- _kiwi.app.panels.add(panel);\r
+ that.connections.active_connection.panels.add(panel);\r
}\r
\r
if (panel) panel.view.show();\r
\r
function msgCommand (ev) {\r
var destination = ev.params[0],\r
- panel = that.panels.getByName(destination) || that.panels.server;\r
+ panel = that.connections.active_connection.panels.getByName(destination) || that.panels().server;\r
\r
ev.params.shift();\r
\r
- panel.addMsg(_kiwi.gateway.get('nick'), ev.params.join(' '));\r
- _kiwi.gateway.privmsg(destination, ev.params.join(' '));\r
+ panel.addMsg(_kiwi.app.connections.active_connection.get('nick'), ev.params.join(' '));\r
+ _kiwi.gateway.privmsg(null, destination, ev.params.join(' '));\r
}\r
\r
function actionCommand (ev) {\r
- if (_kiwi.app.panels.active === _kiwi.app.panels.server) {\r
+ if (_kiwi.app.panels().active.isServer()) {\r
return;\r
}\r
\r
- var panel = _kiwi.app.panels.active;\r
- panel.addMsg('', '* ' + _kiwi.gateway.get('nick') + ' ' + ev.params.join(' '), 'action');\r
- _kiwi.gateway.action(panel.get('name'), ev.params.join(' '));\r
+ var panel = _kiwi.app.panels().active;\r
+ panel.addMsg('', '* ' + _kiwi.app.connections.active_connection.get('nick') + ' ' + ev.params.join(' '), 'action');\r
+ _kiwi.gateway.action(null, panel.get('name'), ev.params.join(' '));\r
}\r
\r
function partCommand (ev) {\r
if (ev.params.length === 0) {\r
- _kiwi.gateway.part(_kiwi.app.panels.active.get('name'));\r
+ _kiwi.gateway.part(null, _kiwi.app.panels().active.get('name'));\r
} else {\r
_.each(ev.params, function (channel) {\r
- _kiwi.gateway.part(channel);\r
+ _kiwi.gateway.part(null, channel);\r
});\r
}\r
- // TODO: More responsive = close tab now, more accurate = leave until part event\r
- //_kiwi.app.panels.remove(_kiwi.app.panels.active);\r
}\r
\r
function topicCommand (ev) {\r
channel_name = ev.params[0];\r
ev.params.shift();\r
} else {\r
- channel_name = _kiwi.app.panels.active.get('name');\r
+ channel_name = _kiwi.app.panels().active.get('name');\r
}\r
\r
- _kiwi.gateway.topic(channel_name, ev.params.join(' '));\r
+ _kiwi.gateway.topic(null, channel_name, ev.params.join(' '));\r
}\r
\r
function noticeCommand (ev) {\r
destination = ev.params[0];\r
ev.params.shift();\r
\r
- _kiwi.gateway.notice(destination, ev.params.join(' '));\r
+ _kiwi.gateway.notice(null, destination, ev.params.join(' '));\r
}\r
\r
function quoteCommand (ev) {\r
var raw = ev.params.join(' ');\r
- _kiwi.gateway.raw(raw);\r
+ _kiwi.gateway.raw(null, raw);\r
}\r
\r
function kickCommand (ev) {\r
- var nick, panel = _kiwi.app.panels.active;\r
+ var nick, panel = _kiwi.app.panels().active;\r
\r
if (!panel.isChannel()) return;\r
\r
nick = ev.params[0];\r
ev.params.shift();\r
\r
- _kiwi.gateway.kick(panel.get('name'), nick, ev.params.join(' '));\r
+ _kiwi.gateway.kick(null, panel.get('name'), nick, ev.params.join(' '));\r
}\r
\r
function clearCommand (ev) {\r
// Can't clear a server or applet panel\r
- if (_kiwi.app.panels.active.isServer() || _kiwi.app.panels.active.isApplet()) {\r
+ if (_kiwi.app.panels().active.isServer() || _kiwi.app.panels().active.isApplet()) {\r
return;\r
}\r
\r
- if (_kiwi.app.panels.active.clearMessages) {\r
- _kiwi.app.panels.active.clearMessages();\r
+ if (_kiwi.app.panels().active.clearMessages) {\r
+ _kiwi.app.panels().active.clearMessages();\r
}\r
}\r
\r
type = ev.params[0];\r
ev.params.shift();\r
\r
- _kiwi.gateway.ctcp(true, type, target, ev.params.join(' '));\r
+ _kiwi.gateway.ctcp(null, true, type, target, ev.params.join(' '));\r
}\r
\r
function settingsCommand (ev) {\r
if (_kiwi.applets[ev.params[0]]) {\r
panel.load(new _kiwi.applets[ev.params[0]]());\r
} else {\r
- _kiwi.app.panels.server.addMsg('', 'Applet "' + ev.params[0] + '" does not exist');\r
+ _kiwi.app.panels().server.addMsg('', 'Applet "' + ev.params[0] + '" does not exist');\r
return;\r
}\r
}\r
\r
- _kiwi.app.panels.add(panel);\r
+ _kiwi.app.connections.active_connection.panels.add(panel);\r
panel.view.show();\r
}\r
\r
\r
+ function serverCommand (ev) {\r
+ var server, port, ssl, password, nick,\r
+ tmp;\r
+\r
+ // If no server address given, show the new connection dialog\r
+ if (!ev.params[0]) {\r
+ tmp = new _kiwi.view.MenuBox('New Connection');\r
+ tmp.addItem('new_connection', new _kiwi.model.NewConnection().view.$el);\r
+ tmp.show();\r
+\r
+ // Center screen the dialog\r
+ tmp.$el.offset({\r
+ top: (that.view.$el.height() / 2) - (tmp.$el.height() / 2),\r
+ left: (that.view.$el.width() / 2) - (tmp.$el.width() / 2)\r
+ });\r
+\r
+ return;\r
+ }\r
+\r
+ // Port given in 'host:port' format and no specific port given after a space\r
+ if (ev.params[0].indexOf(':') > 0) {\r
+ tmp = ev.params[0].split(':');\r
+ server = tmp[0];\r
+ port = tmp[1];\r
+\r
+ password = ev.params[1] || undefined;\r
+\r
+ } else {\r
+ // Server + port given as 'host port'\r
+ server = ev.params[0];\r
+ port = ev.params[1] || 6667;\r
+\r
+ password = ev.params[2] || undefined;\r
+ }\r
+\r
+ // + in the port means SSL\r
+ if (port.toString()[0] === '+') {\r
+ ssl = true;\r
+ port = parseInt(port.substring(1), 10);\r
+ } else {\r
+ ssl = false;\r
+ }\r
+\r
+ // Default port if one wasn't found\r
+ port = port || 6667;\r
+\r
+ // Use the same nick as we currently have\r
+ nick = _kiwi.app.connections.active_connection.get('nick');\r
+\r
+ _kiwi.app.panels().active.addMsg('', 'Connecting to ' + server + ':' + port.toString() + '..');\r
+\r
+ _kiwi.gateway.newConnection({\r
+ nick: nick,\r
+ host: server,\r
+ port: port,\r
+ ssl: ssl,\r
+ password: password\r
+ }, function(err, new_connection) {\r
+ if (err)\r
+ _kiwi.app.panels().active.addMsg('', 'Error connecting to ' + server + ':' + port.toString() + ' (' + err.toString() + ')');\r
+ });\r
+ }\r
+\r
+\r
\r
\r
\r
return (channel_prefix.indexOf(channel_name[0]) > -1);\r
};\r
\r
+\r
};\r
\r
\r
// Some easier handler events\r
this.on('onmsg', function (event) {\r
var source,\r
- is_pm = (event.channel == that.get('nick'));\r
+ connection = _kiwi.app.connections.getByConnectionId(event.server),\r
+ is_pm = (event.channel == connection.get('nick'));\r
\r
source = is_pm ? event.nick : event.channel;\r
\r
\r
this.on('onaction', function (event) {\r
var source,\r
- is_pm = (event.channel == that.get('nick'));\r
+ connection = _kiwi.app.connections.getByConnectionId(event.server),\r
+ is_pm = (event.channel == connection.get('nick'));\r
\r
source = is_pm ? event.nick : event.channel;\r
\r
\r
/**\r
* Connects to the server\r
- * @param {String} host The hostname or IP address of the IRC server to connect to\r
- * @param {Number} port The port of the IRC server to connect to\r
- * @param {Boolean} ssl Whether or not to connect to the IRC server using SSL\r
- * @param {String} password The password to supply to the IRC server during registration\r
* @param {Function} callback A callback function to be invoked once Kiwi's server has connected to the IRC server\r
*/\r
- this.connect = function (host, port, ssl, password, callback) {\r
+ this.connect = function (callback) {\r
var resource;\r
\r
// Work out the resource URL for socket.io\r
\r
this.socket = io.connect(this.get('kiwi_server'), {\r
'resource': resource,\r
- \r
+\r
'try multiple transports': true,\r
'connect timeout': 3000,\r
'max reconnection attempts': 7,\r
* IRCD and the nick has been accepted.\r
*/\r
this.socket.on('connect', function () {\r
+ callback && callback();\r
+ /*\r
this.emit('kiwi', {command: 'connect', nick: that.get('nick'), hostname: host, port: port, ssl: ssl, password:password}, function (err, server_num) {\r
if (!err) {\r
that.server_num = server_num;\r
callback(err);\r
}\r
});\r
+ */\r
});\r
\r
this.socket.on('too_many_connections', function () {\r
\r
\r
\r
+ this.newConnection = function(connection_info, callback_fn) {\r
+ var that = this,\r
+ h = connection_info;\r
+\r
+ this.socket.emit('kiwi', {command: 'connect', nick: h.nick, hostname: h.host, port: h.port, ssl: h.ssl, password: h.password}, function (err, server_num) {\r
+ var connection;\r
+\r
+ if (!err) {\r
+ if (!_kiwi.app.connections.getByConnectionId(server_num)){\r
+ connection = new _kiwi.model.Network({connection_id: server_num});\r
+ _kiwi.app.connections.add(connection);\r
+ }\r
+\r
+ console.log("_kiwi.gateway.socket.on('connect')");\r
+ callback_fn && callback_fn(err, connection);\r
+ \r
+ } else {\r
+ console.log("_kiwi.gateway.socket.on('error')", {reason: err});\r
+ callback_fn && callback_fn(err);\r
+ }\r
+ });\r
+ };\r
+\r
+\r
+\r
this.isConnected = function () {\r
return this.socket.socket.connected;\r
};\r
*/\r
this.parse = function (command, data) {\r
//console.log('gateway event', command, data);\r
- if (command !== undefined) {\r
- that.trigger('on' + command, data);\r
\r
+ if (command !== undefined) {\r
switch (command) {\r
case 'options':\r
$.each(data.options, function (name, value) {\r
that.set('cap', data.cap);\r
break;\r
\r
- case 'connect':\r
- that.set('nick', data.nick);\r
- break;\r
-\r
- case 'nick':\r
- if (data.nick === that.get('nick')) {\r
- that.set('nick', data.newnick);\r
- }\r
- break;\r
/*\r
case 'sync':\r
if (_kiwi.gateway.onSync && _kiwi.gateway.syncing) {\r
break;\r
}\r
}\r
+\r
+\r
+ if (typeof data.server !== 'undefined') {\r
+ that.trigger('connection:' + data.server.toString(), {\r
+ event_name: command,\r
+ event_data: data\r
+ });\r
+ }\r
+\r
+ // Trigger the global events (Mainly legacy now)\r
+ that.trigger('on' + command, data);\r
};\r
\r
/**\r
* @param {Object} data The data to send\r
* @param {Function} callback A callback function\r
*/\r
- this.sendData = function (data, callback) {\r
- this.socket.emit('irc', {server: 0, data: JSON.stringify(data)}, callback);\r
+ this.sendData = function (connection_id, data, callback) {\r
+ if (typeof connection_id === 'undefined' || connection_id === null)\r
+ connection_id = _kiwi.app.connections.active_connection.get('connection_id');\r
+\r
+ var data_buffer = {\r
+ server: connection_id,\r
+ data: JSON.stringify(data)\r
+ };\r
+ this.socket.emit('irc', data_buffer, callback);\r
};\r
\r
/**\r
* @param {String} msg The message to send\r
* @param {Function} callback A callback function\r
*/\r
- this.privmsg = function (target, msg, callback) {\r
+ this.privmsg = function (connection_id, target, msg, callback) {\r
var data = {\r
method: 'privmsg',\r
args: {\r
}\r
};\r
\r
- this.sendData(data, callback);\r
+ this.sendData(connection_id, data, callback);\r
};\r
\r
/**\r
* @param {String} msg The message to send\r
* @param {Function} callback A callback function\r
*/\r
- this.notice = function (target, msg, callback) {\r
+ this.notice = function (connection_id, target, msg, callback) {\r
var data = {\r
method: 'notice',\r
args: {\r
}\r
};\r
\r
- this.sendData(data, callback);\r
+ this.sendData(connection_id, data, callback);\r
};\r
\r
/**\r
* @param {String} params Additional paramaters\r
* @param {Function} callback A callback function\r
*/\r
- this.ctcp = function (request, type, target, params, callback) {\r
+ this.ctcp = function (connection_id, request, type, target, params, callback) {\r
var data = {\r
method: 'ctcp',\r
args: {\r
}\r
};\r
\r
- this.sendData(data, callback);\r
+ this.sendData(connection_id, data, callback);\r
};\r
\r
/**\r
* @param {String} msg The message to send\r
* @param {Function} callback A callback function\r
*/\r
- this.action = function (target, msg, callback) {\r
+ this.action = function (connection_id, target, msg, callback) {\r
this.ctcp(true, 'ACTION', target, msg, callback);\r
};\r
\r
* @param {String} key The key to the channel\r
* @param {Function} callback A callback function\r
*/\r
- this.join = function (channel, key, callback) {\r
+ this.join = function (connection_id, channel, key, callback) {\r
var data = {\r
method: 'join',\r
args: {\r
}\r
};\r
\r
- this.sendData(data, callback);\r
+ this.sendData(connection_id, data, callback);\r
};\r
\r
/**\r
* @param {String} channel The channel to part\r
* @param {Function} callback A callback function\r
*/\r
- this.part = function (channel, callback) {\r
+ this.part = function (connection_id, channel, callback) {\r
var data = {\r
method: 'part',\r
args: {\r
}\r
};\r
\r
- this.sendData(data, callback);\r
+ this.sendData(connection_id, data, callback);\r
};\r
\r
/**\r
* @param {String} new_topic The new topic to set\r
* @param {Function} callback A callback function\r
*/\r
- this.topic = function (channel, new_topic, callback) {\r
+ this.topic = function (connection_id, channel, new_topic, callback) {\r
var data = {\r
method: 'topic',\r
args: {\r
}\r
};\r
\r
- this.sendData(data, callback);\r
+ this.sendData(connection_id, data, callback);\r
};\r
\r
/**\r
* @param {String} reason The reason for kicking the user\r
* @param {Function} callback A callback function\r
*/\r
- this.kick = function (channel, nick, reason, callback) {\r
+ this.kick = function (connection_id, channel, nick, reason, callback) {\r
var data = {\r
method: 'kick',\r
args: {\r
}\r
};\r
\r
- this.sendData(data, callback);\r
+ this.sendData(connection_id, data, callback);\r
};\r
\r
/**\r
* @param {String} msg The quit message to send to the IRC server\r
* @param {Function} callback A callback function\r
*/\r
- this.quit = function (msg, callback) {\r
+ this.quit = function (connection_id, msg, callback) {\r
msg = msg || "";\r
var data = {\r
method: 'quit',\r
}\r
};\r
\r
- this.sendData(data, callback);\r
+ this.sendData(connection_id, data, callback);\r
};\r
\r
/**\r
* @param {String} data The data to send to the IRC server\r
* @param {Function} callback A callback function\r
*/\r
- this.raw = function (data, callback) {\r
+ this.raw = function (connection_id, data, callback) {\r
data = {\r
method: 'raw',\r
args: {\r
}\r
};\r
\r
- this.sendData(data, callback);\r
+ this.sendData(connection_id, data, callback);\r
};\r
\r
/**\r
* @param {String} new_nick Our new nickname\r
* @param {Function} callback A callback function\r
*/\r
- this.changeNick = function (new_nick, callback) {\r
+ this.changeNick = function (connection_id, new_nick, callback) {\r
var data = {\r
method: 'nick',\r
args: {\r
}\r
};\r
\r
- this.sendData(data, callback);\r
+ this.sendData(connection_id, data, callback);\r
};\r
\r
/**\r
}\r
\r
return false;\r
- }\r
+ };\r
\r
\r
return new (Backbone.Model.extend(this))(arguments);\r
--- /dev/null
+(function () {
+
+ _kiwi.model.Network = Backbone.Model.extend({
+ defaults: {
+ connection_id: 0,
+ /**
+ * The name of the network
+ * @type String
+ */
+ name: 'Network',
+
+ /**
+ * The address (URL) of the network
+ * @type String
+ */
+ address: '',
+
+ /**
+ * The current nickname
+ * @type String
+ */
+ nick: '',
+
+ /**
+ * The channel prefix for this network
+ * @type String
+ */
+ channel_prefix: '#',
+
+ /**
+ * The user prefixes for channel owner/admin/op/voice etc. on this network
+ * @type Array
+ */
+ user_prefixes: ['~', '&', '@', '+']
+ },
+
+
+ initialize: function () {
+ this.gateway = _kiwi.global.components.Network(this.get('connection_id'));
+ this.bindGatewayEvents();
+
+ // Create our panel list (tabs)
+ this.panels = new _kiwi.model.PanelList([], this);
+ //this.panels.network = this;
+
+ // Automatically create a server tab
+ var server_panel = new _kiwi.model.Server({name: 'Server'});
+ this.panels.add(server_panel);
+ this.panels.server = this.panels.active = server_panel;
+ },
+
+
+ bindGatewayEvents: function () {
+ //this.gateway.on('all', function() {console.log('ALL', this.get('connection_id'), arguments);});
+
+ this.gateway.on('connect', onConnect, this);
+
+ this.gateway.on('nick', function(event) {
+ if (event.nick === this.get('nick')) {
+ this.set('nick', event.newnick);
+ }
+ }, this);
+
+ this.gateway.on('options', onOptions, this);
+ this.gateway.on('motd', onMotd, this);
+ this.gateway.on('join', onJoin, this);
+ this.gateway.on('part', onPart, this);
+ this.gateway.on('quit', onQuit, this);
+ this.gateway.on('kick', onKick, this);
+ this.gateway.on('msg', onMsg, this);
+ this.gateway.on('nick', onNick, this);
+ this.gateway.on('ctcp_request', onCtcpRequest, this);
+ this.gateway.on('ctcp_response', onCtcpResponse, this);
+ this.gateway.on('notice', onNotice, this);
+ this.gateway.on('action', onAction, this);
+ this.gateway.on('topic', onTopic, this);
+ this.gateway.on('topicsetby', onTopicSetBy, this);
+ this.gateway.on('userlist', onUserlist, this);
+ this.gateway.on('userlist_end', onUserlistEnd, this);
+ this.gateway.on('mode', onMode, this);
+ this.gateway.on('whois', onWhois, this);
+ this.gateway.on('away', onAway, this);
+ this.gateway.on('list_start', onListStart, this);
+ },
+
+
+ /**
+ * Create panels and join the channel
+ * This will not wait for the join event to create a panel. This
+ * increases responsiveness in case of network lag
+ */
+ createAndJoinChannels: function (channels) {
+ var that = this,
+ panels = [];
+
+ // Multiple channels may come as comma-delimited
+ if (typeof channels === 'string') {
+ channels = channels.split(',');
+ }
+
+ $.each(channels, function (index, channel_name_key) {
+ // We may have a channel key so split it off
+ var spli = channel_name_key.trim().split(' '),
+ channel_name = spli[0],
+ channel_key = spli[1] || '';
+
+ // Trim any whitespace off the name
+ channel_name = channel_name.trim();
+
+ // If not a valid channel name, display a warning
+ if (!_kiwi.app.isChannelName(channel_name)) {
+ that.panels.server.addMsg('', channel_name + ' is not a valid channel name');
+ _kiwi.app.message.text(channel_name + ' is not a valid channel name', {timeout: 5000});
+ return;
+ }
+
+ // Check if we have the panel already. If not, create it
+ channel = that.panels.getByName(channel_name);
+ if (!channel) {
+ channel = new _kiwi.model.Channel({name: channel_name});
+ that.panels.add(channel);
+ }
+
+ panels.push(channel);
+
+ that.gateway.join(channel_name, channel_key);
+ });
+
+ return panels;
+ }
+ });
+
+
+
+ function onConnect(event) {
+ var panels, channel_names;
+
+ // Update our nick with what the network gave us
+ this.set('nick', event.nick);
+
+ // Auto joining channels
+ if (this.auto_join && this.auto_join.channel) {
+ panels = this.createAndJoinChannels(this.auto_join.channel + ' ' + (this.auto_join.channel_key || ''));
+
+ // Show the last channel if we have one
+ if (panels)
+ panels[panels.length - 1].view.show();
+ }
+ }
+
+
+
+ function onOptions(event) {
+ var that = this;
+
+ $.each(event.options, function (name, value) {
+ switch (name) {
+ case 'CHANTYPES':
+ that.set('channel_prefix', value.join(''));
+ break;
+ case 'NETWORK':
+ that.set('name', value);
+ break;
+ case 'PREFIX':
+ that.set('user_prefixes', value);
+ break;
+ }
+ });
+
+ this.set('cap', event.cap);
+ }
+
+
+
+ function onMotd(event) {
+ this.panels.server.addMsg(this.get('name'), event.msg, 'motd');
+ }
+
+
+
+ function onJoin(event) {
+ var c, members, user;
+ c = this.panels.getByName(event.channel);
+ if (!c) {
+ c = new _kiwi.model.Channel({name: event.channel});
+ this.panels.add(c);
+ }
+
+ members = c.get('members');
+ if (!members) return;
+
+ user = new _kiwi.model.Member({nick: event.nick, ident: event.ident, hostname: event.hostname});
+ members.add(user);
+ }
+
+
+
+ function onPart(event) {
+ var channel, members, user,
+ part_options = {};
+
+ part_options.type = 'part';
+ part_options.message = event.message || '';
+
+ channel = this.panels.getByName(event.channel);
+ if (!channel) return;
+
+ // If this is us, close the panel
+ if (event.nick === this.get('nick')) {
+ channel.close();
+ return;
+ }
+
+ members = channel.get('members');
+ if (!members) return;
+
+ user = members.getByNick(event.nick);
+ if (!user) return;
+
+ members.remove(user, part_options);
+ }
+
+
+
+ function onQuit(event) {
+ var member, members,
+ quit_options = {};
+
+ quit_options.type = 'quit';
+ quit_options.message = event.message || '';
+
+ $.each(this.panels.models, function (index, panel) {
+ if (!panel.isChannel()) return;
+
+ member = panel.get('members').getByNick(event.nick);
+ if (member) {
+ panel.get('members').remove(member, quit_options);
+ }
+ });
+ }
+
+
+
+ function onKick(event) {
+ var channel, members, user,
+ part_options = {};
+
+ part_options.type = 'kick';
+ part_options.by = event.nick;
+ part_options.message = event.message || '';
+
+ channel = this.panels.getByName(event.channel);
+ if (!channel) return;
+
+ members = channel.get('members');
+ if (!members) return;
+
+ user = members.getByNick(event.kicked);
+ if (!user) return;
+
+ members.remove(user, part_options);
+
+ if (event.kicked === this.get('nick')) {
+ members.reset([]);
+ }
+ }
+
+
+
+ function onMsg(event) {
+ var panel,
+ is_pm = (event.channel == this.get('nick'));
+
+ // An ignored user? don't do anything with it
+ if (_kiwi.gateway.isNickIgnored(event.nick)) {
+ return;
+ }
+
+ if (is_pm) {
+ // If a panel isn't found for this PM, create one
+ panel = this.panels.getByName(event.nick);
+ if (!panel) {
+ panel = new _kiwi.model.Query({name: event.nick});
+ this.panels.add(panel);
+ }
+
+ } else {
+ // If a panel isn't found for this channel, reroute to the
+ // server panel
+ panel = this.panels.getByName(event.channel);
+ if (!panel) {
+ panel = this.panels.server;
+ }
+ }
+
+ panel.addMsg(event.nick, event.msg);
+ }
+
+
+
+ function onNick(event) {
+ var member;
+
+ $.each(this.panels.models, function (index, panel) {
+ if (panel.get('name') == event.nick)
+ panel.set('name', event.newnick);
+
+ if (!panel.isChannel()) return;
+
+ member = panel.get('members').getByNick(event.nick);
+ if (member) {
+ member.set('nick', event.newnick);
+ panel.addMsg('', '== ' + event.nick + ' is now known as ' + event.newnick, 'action nick');
+ }
+ });
+ }
+
+
+
+ function onCtcpRequest(event) {
+ // An ignored user? don't do anything with it
+ if (_kiwi.gateway.isNickIgnored(event.nick)) {
+ return;
+ }
+
+ // Reply to a TIME ctcp
+ if (event.msg.toUpperCase() === 'TIME') {
+ this.gateway.ctcp(null, false, event.type, event.nick, (new Date()).toString());
+ }
+ }
+
+
+
+ function onCtcpResponse(event) {
+ // An ignored user? don't do anything with it
+ if (_kiwi.gateway.isNickIgnored(event.nick)) {
+ return;
+ }
+
+ this.panels.server.addMsg('[' + event.nick + ']', 'CTCP ' + event.msg);
+ }
+
+
+
+ function onNotice(event) {
+ var panel;
+
+ // An ignored user? don't do anything with it
+ if (event.nick && _kiwi.gateway.isNickIgnored(event.nick)) {
+ return;
+ }
+
+ // Find a panel for the destination(channel) or who its from
+ panel = this.panels.getByName(event.target) || this.panels.getByName(event.nick);
+ if (!panel) {
+ panel = this.panels.server;
+ }
+
+ panel.addMsg('[' + (event.nick||'') + ']', event.msg);
+ }
+
+
+
+ function onAction(event) {
+ var panel,
+ is_pm = (event.channel == this.get('nick'));
+
+ // An ignored user? don't do anything with it
+ if (_kiwi.gateway.isNickIgnored(event.nick)) {
+ return;
+ }
+
+ if (is_pm) {
+ // If a panel isn't found for this PM, create one
+ panel = this.panels.getByName(event.nick);
+ if (!panel) {
+ panel = new _kiwi.model.Channel({name: event.nick});
+ this.panels.add(panel);
+ }
+
+ } else {
+ // If a panel isn't found for this channel, reroute to the
+ // server panel
+ panel = this.panels.getByName(event.channel);
+ if (!panel) {
+ panel = this.panels.server;
+ }
+ }
+
+ panel.addMsg('', '* ' + event.nick + ' ' + event.msg, 'action');
+ }
+
+
+
+ function onTopic(event) {
+ var c;
+ c = this.panels.getByName(event.channel);
+ if (!c) return;
+
+ // Set the channels topic
+ c.set('topic', event.topic);
+
+ // If this is the active channel, update the topic bar too
+ if (c.get('name') === this.panels.active.get('name')) {
+ _kiwi.app.topicbar.setCurrentTopic(event.topic);
+ }
+ }
+
+
+
+ function onTopicSetBy(event) {
+ var c, when;
+ c = this.panels.getByName(event.channel);
+ if (!c) return;
+
+ when = formatDate(new Date(event.when * 1000));
+ c.addMsg('', 'Topic set by ' + event.nick + ' at ' + when, 'topic');
+ }
+
+
+
+ function onUserlist(event) {
+ var channel;
+ channel = this.panels.getByName(event.channel);
+
+ // If we didn't find a channel for this, may aswell leave
+ if (!channel) return;
+
+ channel.temp_userlist = channel.temp_userlist || [];
+ _.each(event.users, function (item) {
+ var user = new _kiwi.model.Member({nick: item.nick, modes: item.modes});
+ channel.temp_userlist.push(user);
+ });
+ }
+
+
+
+ function onUserlistEnd(event) {
+ var channel;
+ channel = this.panels.getByName(event.channel);
+
+ // If we didn't find a channel for this, may aswell leave
+ if (!channel) return;
+
+ // Update the members list with the new list
+ channel.get('members').reset(channel.temp_userlist || []);
+
+ // Clear the temporary userlist
+ delete channel.temp_userlist;
+ }
+
+
+
+ function onMode(event) {
+ var channel, i, prefixes, members, member, find_prefix;
+
+ // Build a nicely formatted string to be displayed to a regular human
+ function friendlyModeString (event_modes, alt_target) {
+ var modes = {}, return_string;
+
+ // If no default given, use the main event info
+ if (!event_modes) {
+ event_modes = event.modes;
+ alt_target = event.target;
+ }
+
+ // Reformat the mode object to make it easier to work with
+ _.each(event_modes, function (mode){
+ var param = mode.param || alt_target || '';
+
+ // Make sure we have some modes for this param
+ if (!modes[param]) {
+ modes[param] = {'+':'', '-':''};
+ }
+
+ modes[param][mode.mode[0]] += mode.mode.substr(1);
+ });
+
+ // Put the string together from each mode
+ return_string = [];
+ _.each(modes, function (modeset, param) {
+ var str = '';
+ if (modeset['+']) str += '+' + modeset['+'];
+ if (modeset['-']) str += '-' + modeset['-'];
+ return_string.push(str + ' ' + param);
+ });
+ return_string = return_string.join(', ');
+
+ return return_string;
+ }
+
+
+ channel = this.panels.getByName(event.target);
+ if (channel) {
+ prefixes = this.get('user_prefixes');
+ find_prefix = function (p) {
+ return event.modes[i].mode[1] === p.mode;
+ };
+ for (i = 0; i < event.modes.length; i++) {
+ if (_.any(prefixes, find_prefix)) {
+ if (!members) {
+ members = channel.get('members');
+ }
+ member = members.getByNick(event.modes[i].param);
+ if (!member) {
+ console.log('MODE command recieved for unknown member %s on channel %s', event.modes[i].param, event.target);
+ return;
+ } else {
+ if (event.modes[i].mode[0] === '+') {
+ member.addMode(event.modes[i].mode[1]);
+ } else if (event.modes[i].mode[0] === '-') {
+ member.removeMode(event.modes[i].mode[1]);
+ }
+ members.sort();
+ //channel.addMsg('', '== ' + event.nick + ' set mode ' + event.modes[i].mode + ' ' + event.modes[i].param, 'action mode');
+ }
+ } else {
+ // Channel mode being set
+ // TODO: Store this somewhere?
+ //channel.addMsg('', 'CHANNEL === ' + event.nick + ' set mode ' + event.modes[i].mode + ' on ' + event.target, 'action mode');
+ }
+ }
+
+ channel.addMsg('', '== ' + event.nick + ' sets mode ' + friendlyModeString(), 'action mode');
+ } else {
+ // This is probably a mode being set on us.
+ if (event.target.toLowerCase() === this.get("nick").toLowerCase()) {
+ this.panels.server.addMsg('', '== ' + event.nick + ' set mode ' + friendlyModeString(), 'action mode');
+ } else {
+ console.log('MODE command recieved for unknown target %s: ', event.target, event);
+ }
+ }
+ }
+
+
+
+ function onWhois(event) {
+ var logon_date, idle_time = '', panel;
+
+ if (event.end)
+ return;
+
+ if (typeof event.idle !== 'undefined') {
+ idle_time = secondsToTime(parseInt(event.idle, 10));
+ idle_time = idle_time.h.toString().lpad(2, "0") + ':' + idle_time.m.toString().lpad(2, "0") + ':' + idle_time.s.toString().lpad(2, "0");
+ }
+
+ panel = _kiwi.app.panels().active;
+ if (event.ident) {
+ panel.addMsg(event.nick, event.nick + ' [' + event.nick + '!' + event.ident + '@' + event.host + '] * ' + event.msg, 'whois');
+ } else if (event.chans) {
+ panel.addMsg(event.nick, 'Channels: ' + event.chans, 'whois');
+ } else if (event.irc_server) {
+ panel.addMsg(event.nick, 'Connected to server: ' + event.irc_server, 'whois');
+ } else if (event.msg) {
+ panel.addMsg(event.nick, event.msg, 'whois');
+ } else if (event.logon) {
+ logon_date = new Date();
+ logon_date.setTime(event.logon * 1000);
+ logon_date = formatDate(logon_date);
+
+ panel.addMsg(event.nick, 'idle for ' + idle_time + ', signed on ' + logon_date, 'whois');
+ } else {
+ panel.addMsg(event.nick, 'idle for ' + idle_time, 'whois');
+ }
+ }
+
+
+
+ function onAway(event) {
+ $.each(this.panels.models, function (index, panel) {
+ if (!panel.isChannel()) return;
+
+ member = panel.get('members').getByNick(event.nick);
+ if (member) {
+ member.set('away', !(!event.trailing));
+ }
+ });
+ }
+
+
+
+ function onListStart(event) {
+ var chanlist = _kiwi.model.Applet.loadOnce('kiwi_chanlist');
+ chanlist.view.show();
+ }
+
+
+
+ function onIrcError(event) {
+ var panel, tmp;
+
+ if (event.channel !== undefined && !(panel = _kiwi.app.panels.getByName(event.channel))) {
+ panel = this.panels.server;
+ }
+
+ switch (event.error) {
+ case 'banned_from_channel':
+ panel.addMsg(' ', '== You are banned from ' + event.channel + '. ' + event.reason, 'status');
+ _kiwi.app.message.text('You are banned from ' + event.channel + '. ' + event.reason);
+ break;
+ case 'bad_channel_key':
+ panel.addMsg(' ', '== Bad channel key for ' + event.channel, 'status');
+ _kiwi.app.message.text('Bad channel key or password for ' + event.channel);
+ break;
+ case 'invite_only_channel':
+ panel.addMsg(' ', '== ' + event.channel + ' is invite only.', 'status');
+ _kiwi.app.message.text(event.channel + ' is invite only');
+ break;
+ case 'channel_is_full':
+ panel.addMsg(' ', '== ' + event.channel + ' is full.', 'status');
+ _kiwi.app.message.text(event.channel + ' is full');
+ break;
+ case 'chanop_privs_needed':
+ panel.addMsg(' ', '== ' + event.reason, 'status');
+ _kiwi.app.message.text(event.reason + ' (' + event.channel + ')');
+ break;
+ case 'no_such_nick':
+ tmp = this.panels.getByName(event.nick);
+ if (tmp) {
+ tmp.addMsg(' ', '== ' + event.nick + ': ' + event.reason, 'status');
+ } else {
+ this.panels.server.addMsg(' ', '== ' + event.nick + ': ' + event.reason, 'status');
+ }
+ break;
+ case 'nickname_in_use':
+ this.panels.server.addMsg(' ', '== The nickname ' + event.nick + ' is already in use. Please select a new nickname', 'status');
+ if (this.panels.server !== thia.panels.active) {
+ _kiwi.app.message.text('The nickname "' + event.nick + '" is already in use. Please select a new nickname');
+ }
+
+ // Only show the nickchange component if the controlbox is open
+ if (that.controlbox.$el.css('display') !== 'none') {
+ (new _kiwi.view.NickChangeBox()).render();
+ }
+
+ break;
+
+ case 'password_mismatch':
+ this.panels.server.addMsg(' ', '== Incorrect password given', 'status');
+ break;
+ default:
+ // We don't know what data contains, so don't do anything with it.
+ //_kiwi.front.tabviews.server.addMsg(null, ' ', '== ' + data, 'status');
+ }
+ }
+}
+
+)();
\ No newline at end of file
--- /dev/null
+_kiwi.model.NetworkPanelList = Backbone.Collection.extend({
+ model: _kiwi.model.Network,
+
+ initialize: function() {
+ this.view = new _kiwi.view.NetworkTabs({model: this});
+
+ this.on('add', this.onNetworkAdd, this);
+ this.on('remove', this.onNetworkRemove, this);
+
+ // Current active connection / panel
+ this.active_connection = undefined;
+ this.active_panel = undefined;
+
+ // TODO: Remove this - legacy
+ this.active = undefined;
+ },
+
+ getByConnectionId: function(id) {
+ return this.find(function(connection){
+ return connection.get('connection_id') == id;
+ });
+ },
+
+ panels: function() {
+ var panels = [];
+
+ this.each(function(network) {
+ panels = panels.concat(network.panels.models);
+ });
+
+ return panels;
+ },
+
+
+ onNetworkAdd: function(network) {
+ network.panels.on('active', this.onPanelActive, this);
+
+ // if it's our first connection, set it active
+ if (this.models.length === 1) {
+ this.active_connection = network;
+ this.active_panel = network.panels.server;
+
+ // TODO: Remove this - legacy
+ this.active = this.active_panel;
+ }
+ },
+
+ onNetworkRemove: function(network) {
+ network.panels.off('active', this.onPanelActive, this);
+ },
+
+ onPanelActive: function(panel) {
+ var connection = this.getByConnectionId(panel.tab.data('connection_id'));
+ this.trigger('active', panel, connection);
+
+ this.active_connection = connection;
+ this.active_panel = panel;
+
+ // TODO: Remove this - legacy
+ this.active = panel;
+ }
+});
\ No newline at end of file
--- /dev/null
+_kiwi.model.NewConnection = Backbone.Collection.extend({
+ initialize: function() {
+ this.view = new _kiwi.view.ServerSelect();
+
+ this.view.bind('server_connect', this.onMakeConnection, this);
+
+ },
+
+
+ onMakeConnection: function(new_connection_event) {
+ var that = this,
+ transport_path = '',
+ auto_connect_details = new_connection_event;
+
+ this.view.networkConnecting();
+
+
+ // If we don't have socket.io loaded, load it before opening a new connection
+ if (!window.io) {
+ // Path to get the socket.io transport code
+ transport_path = _kiwi.app.kiwi_server + _kiwi.app.get('base_path') + '/transport/socket.io.js?ts='+(new Date().getTime());
+
+ $script(transport_path, function() {
+ if (!window.io) {
+ that.onKiwiServerNotFound();
+ return;
+ }
+
+ _kiwi.gateway.set('kiwi_server', _kiwi.app.kiwi_server + '/kiwi');
+ _kiwi.gateway.connect(function() {
+ that.makeConnection(new_connection_event);
+ });
+ });
+
+ } else {
+ this.makeConnection(new_connection_event);
+
+ }
+
+ },
+
+
+ onKiwiServerNotFound: function() {
+ this.view.showError();
+ },
+
+
+ makeConnection: function(new_connection_event) {
+ var that = this;
+
+ this.connect_details = new_connection_event;
+
+ _kiwi.gateway.newConnection({
+ nick: new_connection_event.nick,
+ host: new_connection_event.server,
+ port: new_connection_event.port,
+ ssl: new_connection_event.ssl,
+ password: new_connection_event.password
+ }, function(err, network) {
+ that.onNewNetwork(err, network);
+ });
+ },
+
+
+ onNewNetwork: function(err, network) {
+ if (network && this.connect_details) {
+ network.auto_join = {
+ channel: this.connect_details.channel,
+ key: this.connect_details.channel_key
+ };
+ }
+
+
+ // Show the server panel if this is our first network
+ if (network && network.get('connection_id') === 0) {
+ network.panels.server.view.show();
+ }
+ }
+});
\ No newline at end of file
this.unset('members');\r
}\r
\r
- _kiwi.app.panels.remove(this);\r
+ this.get('panel_list').remove(this);\r
\r
this.unbind();\r
this.destroy();\r
\r
// If closing the active panel, switch to the server panel\r
- if (this.cid === _kiwi.app.panels.active.cid) {\r
- _kiwi.app.panels.server.view.show();\r
+ if (this === _kiwi.app.panels().active) {\r
+ _kiwi.app.connections.active_connection.panels.server.view.show();\r
}\r
},\r
\r
},\r
\r
isActive: function () {\r
- return (_kiwi.app.panels.active === this);\r
+ return (_kiwi.app.panels().active === this);\r
}\r
});
\ No newline at end of file
model: _kiwi.model.Panel,\r
\r
comparator: function (chan) {\r
- return chan.get("name");\r
+ return chan.get('name');\r
},\r
- initialize: function () {\r
- this.view = new _kiwi.view.Tabs({"el": $('#tabs')[0], "model": this});\r
+ initialize: function (elements, network) {\r
+ var that = this;\r
\r
- // Automatically create a server tab\r
- this.add(new _kiwi.model.Server({'name': _kiwi.gateway.get('name')}));\r
- this.server = this.getByName(_kiwi.gateway.get('name'));\r
+ // If this PanelList is associated with a network/connection\r
+ if (network) {\r
+ this.network = network;\r
+ }\r
+\r
+ this.view = new _kiwi.view.Tabs({model: this});\r
\r
// Holds the active panel\r
this.active = null;\r
this.active = active_panel;\r
}, this);\r
\r
+ this.bind('add', function(panel) {\r
+ panel.set('panel_list', this);\r
+ });\r
+ },\r
+\r
+\r
+\r
+ getByCid: function (cid) {\r
+ if (typeof name !== 'string') return;\r
+\r
+ return this.find(function (c) {\r
+ return cid === c.cid;\r
+ });\r
},\r
+\r
+\r
+\r
getByName: function (name) {\r
if (typeof name !== 'string') return;\r
+\r
return this.find(function (c) {\r
return name.toLowerCase() === c.get('name').toLowerCase();\r
});\r
}\r
-});
\ No newline at end of file
+});\r
}, {"silent": true});\r
\r
//this.addMsg(' ', '--> Kiwi IRC: Such an awesome IRC client', '', {style: 'color:#009900;'});\r
-\r
- this.server_login = new _kiwi.view.ServerSelect();\r
- \r
- this.view.$el.append(this.server_login.$el);\r
- this.server_login.show();\r
}\r
});
\ No newline at end of file
colours = openTags.colour.split(',');
style += 'color: ' + colours[0] + ((colours[1]) ? '; background-color: ' + colours[1] + ';' : '');
}
- return '<span style="' + style + '">';
+ return '<span class="format_span" style="' + style + '">';
}
},
colourMatch = function (str) {
.data('member', member);\r
});\r
},\r
- nickClick: function (x) {\r
- var $target = $(x.currentTarget).parent('li'),\r
+ nickClick: function (event) {\r
+ var $target = $(event.currentTarget).parent('li'),\r
member = $target.data('member'),\r
userbox;\r
\r
+ event.stopPropagation();\r
+\r
// If the userbox already exists here, hide it\r
if ($target.find('.userbox').length > 0) {\r
$('.userbox', this.$el).remove();\r
userbox.member = member;\r
userbox.channel = this.model.channel;\r
\r
- // Remove any existing userboxes\r
- $('.userbox', this.$el).remove();\r
-\r
- if (!this.model.getByNick(_kiwi.gateway.get('nick')).get('is_op')) {\r
+ if (!this.model.getByNick(_kiwi.app.connections.active_connection.get('nick')).get('is_op')) {\r
userbox.$el.children('.if_op').remove();\r
}\r
- $target.append(userbox.$el);\r
+\r
+ var menu = new _kiwi.view.MenuBox(member.get('nick') || 'User');\r
+ menu.addItem('userbox', userbox.$el);\r
+ menu.show();\r
+\r
+ // Position the userbox + menubox\r
+ (function() {\r
+ var t = event.pageY,\r
+ m_bottom = t + menu.$el.outerHeight(), // Where the bottom of menu will be\r
+ memberlist_bottom = this.$el.parent().offset().top + this.$el.parent().outerHeight();\r
+\r
+ // If the bottom of the userbox is going to be too low.. raise it\r
+ if (m_bottom > memberlist_bottom){\r
+ t = memberlist_bottom - menu.$el.outerHeight();\r
+ }\r
+\r
+ // Set the new positon\r
+ menu.$el.offset({\r
+ left: _kiwi.app.view.$el.width() - menu.$el.outerWidth() - 20,\r
+ top: t\r
+ });\r
+ }).call(this);\r
},\r
show: function () {\r
$('#memberlists').children().removeClass('active');\r
\r
queryClick: function (event) {\r
var panel = new _kiwi.model.Query({name: this.member.get('nick')});\r
- _kiwi.app.panels.add(panel);\r
+ _kiwi.app.connections.active_connection.panels.add(panel);\r
panel.view.show();\r
},\r
\r
\r
changeNick: function (event) {\r
var that = this;\r
- _kiwi.gateway.changeNick(this.$el.find('input').val(), function (err, val) {\r
+\r
+ event.preventDefault();\r
+\r
+ _kiwi.app.connections.active_connection.gateway.changeNick(this.$el.find('input').val(), function (err, val) {\r
that.close();\r
});\r
return false;\r
events: {\r
'submit form': 'submitForm',\r
'click .show_more': 'showMore',\r
- 'change .have_pass input': 'showPass'\r
+ 'change .have_pass input': 'showPass',\r
+ 'change .have_key input': 'showKey',\r
+ 'click .icon-key': 'channelKeyIconClick'\r
},\r
\r
initialize: function () {\r
}\r
}\r
\r
-\r
_kiwi.gateway.bind('onconnect', this.networkConnected, this);\r
_kiwi.gateway.bind('connecting', this.networkConnecting, this);\r
+ _kiwi.gateway.bind('onirc_error', this.onIrcError, this);\r
+ },\r
\r
- _kiwi.gateway.bind('onirc_error', function (data) {\r
- $('button', this.$el).attr('disabled', null);\r
+ dispose: function() {\r
+ _kiwi.gateway.off('onconnect', this.networkConnected, this);\r
+ _kiwi.gateway.off('connecting', this.networkConnecting, this);\r
+ _kiwi.gateway.off('onirc_error', this.onIrcError, this);\r
\r
- if (data.error == 'nickname_in_use') {\r
- this.setStatus('Nickname already taken');\r
- this.show('nick_change');\r
- }\r
-\r
- if (data.error == 'password_mismatch') {\r
- this.setStatus('Incorrect Password');\r
- this.show('nick_change');\r
- that.$el.find('.password').select();\r
- }\r
- }, this);\r
+ this.$el.remove();\r
},\r
\r
submitForm: function (event) {\r
submitLogin: function (event) {\r
// If submitting is disabled, don't do anything\r
if ($('button', this.$el).attr('disabled')) return;\r
- \r
+\r
var values = {\r
nick: $('input.nick', this.$el).val(),\r
server: $('input.server', this.$el).val(),\r
},\r
\r
submitNickChange: function (event) {\r
- _kiwi.gateway.changeNick($('input.nick', this.$el).val());\r
+ _kiwi.gateway.changeNick(null, $('input.nick', this.$el).val());\r
this.networkConnecting();\r
},\r
\r
}\r
},\r
\r
+ channelKeyIconClick: function (event) {\r
+ this.$el.find('tr.have_key input').click();\r
+ },\r
+\r
+ showKey: function (event) {\r
+ if (this.$el.find('tr.have_key input').is(':checked')) {\r
+ this.$el.find('tr.key').show().find('input').focus();\r
+ } else {\r
+ this.$el.find('tr.key').hide().find('input').val('');\r
+ }\r
+ },\r
+\r
showMore: function (event) {\r
$('.more', this.$el).slideDown('fast');\r
$('input.server', this.$el).select();\r
$('input.server', this.$el).val(server);\r
$('input.port', this.$el).val(port);\r
$('input.ssl', this.$el).prop('checked', ssl);\r
+ $('input#server_select_show_pass', this.$el).prop('checked', !(!password));\r
$('input.password', this.$el).val(password);\r
+ if (!(!password)) {\r
+ $('tr.pass', this.$el).show();\r
+ }\r
$('input.channel', this.$el).val(channel);\r
+ $('input#server_select_show_channel_key', this.$el).prop('checked', !(!channel_key));\r
$('input.channel_key', this.$el).val(channel_key);\r
+ if (!(!channel_key)) {\r
+ $('tr.key', this.$el).show();\r
+ }\r
},\r
\r
hide: function () {\r
state = new_state;\r
},\r
\r
+ infoBoxShow: function() {\r
+ var $side_panel = this.$el.find('.side_panel');\r
+ this.$el.animate({\r
+ width: parseInt($side_panel.css('left'), 10) + $side_panel.find('.content:first').outerWidth()\r
+ });\r
+ },\r
+\r
+ infoBoxHide: function() {\r
+ var $side_panel = this.$el.find('.side_panel');\r
+ this.$el.animate({\r
+ width: parseInt($side_panel.css('left'), 10)\r
+ });\r
+ },\r
+\r
+ infoBoxSet: function($info_view) {\r
+ this.$el.find('.side_panel .content')\r
+ .empty()\r
+ .append($info_view);\r
+ },\r
+\r
setStatus: function (text, class_name) {\r
$('.status', this.$el)\r
.text(text)\r
this.setStatus('Connecting..', 'ok');\r
},\r
\r
+ onIrcError: function (data) {\r
+ $('button', this.$el).attr('disabled', null);\r
+\r
+ if (data.error == 'nickname_in_use') {\r
+ this.setStatus('Nickname already taken');\r
+ this.show('nick_change');\r
+ }\r
+\r
+ if (data.error == 'password_mismatch') {\r
+ this.setStatus('Incorrect Password');\r
+ this.show('nick_change');\r
+ that.$el.find('.password').select();\r
+ }\r
+ },\r
+\r
showError: function (event) {\r
this.setStatus('Error connecting', 'error');\r
$('button', this.$el).attr('disabled', null);\r
\r
_kiwi.view.Panel = Backbone.View.extend({\r
tagName: "div",\r
- className: "messages",\r
+ className: "panel messages",\r
+\r
events: {\r
"click .chan": "chanClick",\r
'click .media .open': 'mediaClick',\r
nick_colour_hex, nick_hex, is_highlight, msg_css_classes = '';\r
\r
// Nick highlight detecting\r
- if ((new RegExp('\\b' + _kiwi.gateway.get('nick') + '\\b', 'i')).test(msg.msg)) {\r
+ if ((new RegExp('\\b' + _kiwi.app.connections.active_connection.get('nick') + '\\b', 'i')).test(msg.msg)) {\r
is_highlight = true;\r
msg_css_classes += ' highlight';\r
}\r
\r
\r
// Parse any links found\r
- msg.msg = msg.msg.replace(/(([A-Za-z0-9\-]+\:\/\/)|(www\.))([\w.\-]+)([a-zA-Z]{2,6})(:[0-9]+)?(\/[\w#!:.?$'()[\]*,;~+=&%@!\-\/]*)?/gi, function (url) {\r
+ msg.msg = msg.msg.replace(/(([A-Za-z][A-Za-z0-9\-]*\:\/\/)|(www\.))([\w.\-]+)([a-zA-Z]{2,6})(:[0-9]+)?(\/[\w#!:.?$'()[\]*,;~+=&%@!\-\/]*)?/gi, function (url) {\r
var nice = url,\r
extra_html = '';\r
\r
},\r
chanClick: function (event) {\r
if (event.target) {\r
- _kiwi.gateway.join($(event.target).data('channel'));\r
+ _kiwi.gateway.join(null, $(event.target).data('channel'));\r
} else {\r
// IE...\r
- _kiwi.gateway.join($(event.srcElement).data('channel'));\r
+ _kiwi.gateway.join(null, $(event.srcElement).data('channel'));\r
}\r
},\r
\r
var $this = this.$el;\r
\r
// Hide all other panels and show this one\r
- this.$container.children().css('display', 'none');\r
+ this.$container.children('.panel').css('display', 'none');\r
$this.css('display', 'block');\r
\r
// Show this panels memberlist\r
$('#memberlists').addClass('disabled').children().removeClass('active');\r
}\r
\r
- _kiwi.app.view.doLayout();\r
-\r
// Remove any alerts and activity counters for this panel\r
this.alert('none');\r
this.model.tab.find('.activity').text('0').addClass('zero');\r
\r
- this.trigger('active', this.model);\r
- _kiwi.app.panels.trigger('active', this.model, _kiwi.app.panels.active);\r
+ _kiwi.app.panels.trigger('active', this.model, _kiwi.app.panels().active);\r
+ this.model.trigger('active', this.model);\r
+\r
+ _kiwi.app.view.doLayout();\r
\r
this.scrollToBottom(true);\r
},\r
\r
alert: function (level) {\r
// No need to highlight if this si the active panel\r
- if (this.model == _kiwi.app.panels.active) return;\r
+ if (this.model == _kiwi.app.panels().active) return;\r
\r
var types, type_idx;\r
types = ['none', 'action', 'activity', 'highlight'];\r
// Scroll to the bottom of the panel\r
scrollToBottom: function (force_down) {\r
// If this isn't the active panel, don't scroll\r
- if (this.model !== _kiwi.app.panels.active) return;\r
+ if (this.model !== _kiwi.app.panels().active) return;\r
\r
// Don't scroll down if we're scrolled up the panel a little\r
if (force_down || this.$container.scrollTop() + this.$container.height() > this.$el.outerHeight() - 150) {\r
});\r
\r
_kiwi.view.Applet = _kiwi.view.Panel.extend({\r
- className: 'applet',\r
+ className: 'panel applet',\r
initialize: function (options) {\r
this.initializePanel(options);\r
}\r
if (typeof topic !== 'string' || !topic) {\r
topic = this.model.get("topic");\r
}\r
- \r
+\r
this.model.addMsg('', '== Topic for ' + this.model.get('name') + ' is: ' + topic, 'topic');\r
\r
// If this is the active channel then update the topic bar\r
- if (_kiwi.app.panels.active === this) {\r
+ if (_kiwi.app.panels().active === this) {\r
_kiwi.app.topicbar.setCurrentTopic(this.model.get("topic"));\r
}\r
}\r
});\r
\r
+\r
+\r
+// Model for this = _kiwi.model.NetworkPanelList\r
+_kiwi.view.NetworkTabs = Backbone.View.extend({\r
+ tagName: 'ul',\r
+ className: 'connections',\r
+\r
+ initialize: function() {\r
+ this.model.on('add', this.networkAdded, this);\r
+ this.model.on('remove', this.networkRemoved, this);\r
+\r
+ this.$el.appendTo($('#kiwi #tabs'));\r
+ },\r
+\r
+ networkAdded: function(network) {\r
+ $('<li class="connection"></li>')\r
+ .append(network.panels.view.$el)\r
+ .appendTo(this.$el);\r
+ },\r
+\r
+ networkRemoved: function(network) {\r
+ network.panels.view.remove();\r
+\r
+ _kiwi.app.view.doLayout();\r
+ }\r
+});\r
+\r
+\r
+\r
// Model for this = _kiwi.model.PanelList\r
_kiwi.view.Tabs = Backbone.View.extend({\r
+ tagName: 'ul',\r
+ className: 'panellist',\r
+\r
events: {\r
'click li': 'tabClick',\r
'click li .part': 'partClick'\r
\r
this.model.on('active', this.panelActive, this);\r
\r
- this.tabs_applets = $('ul.applets', this.$el);\r
- this.tabs_msg = $('ul.channels', this.$el);\r
+ // Network tabs start with a server, so determine what we are now\r
+ this.is_network = false;\r
\r
- _kiwi.gateway.on('change:name', function (gateway, new_val) {\r
- $('span', this.model.server.tab).text(new_val);\r
- }, this);\r
+ if (this.model.network) {\r
+ this.is_network = true;\r
+\r
+ this.model.network.on('change:name', function (network, new_val) {\r
+ $('span', this.model.server.tab).text(new_val);\r
+ }, this);\r
+ }\r
},\r
+\r
render: function () {\r
var that = this;\r
\r
- this.tabs_msg.empty();\r
+ this.$el.empty();\r
\r
- // Add the server tab first\r
- this.model.server.tab\r
- .data('panel_id', this.model.server.cid)\r
- .appendTo(this.tabs_msg);\r
+ if (this.is_network) {\r
+ // Add the server tab first\r
+ this.model.server.tab\r
+ .data('panel', this.model.server)\r
+ .data('connection_id', this.model.network.get('connection_id'))\r
+ .appendTo(this.$el);\r
+ }\r
\r
// Go through each panel adding its tab\r
this.model.forEach(function (panel) {\r
// If this is the server panel, ignore as it's already added\r
- if (panel == that.model.server) return;\r
+ if (this.is_network && panel == that.model.server)\r
+ return;\r
+\r
+ panel.tab.data('panel', panel);\r
\r
- panel.tab\r
- .data('panel_id', panel.cid)\r
- .appendTo(panel.isApplet() ? this.tabs_applets : this.tabs_msg);\r
+ if (this.is_network)\r
+ panel.tab.data('connection_id', this.model.network.get('connection_id'));\r
+\r
+ panel.tab.appendTo(that.$el);\r
});\r
\r
_kiwi.app.view.doLayout();\r
panel.tab.addClass('icon-nonexistant');\r
}\r
\r
- panel.tab.data('panel_id', panel.cid)\r
- .appendTo(panel.isApplet() ? this.tabs_applets : this.tabs_msg);\r
+ panel.tab.data('panel', panel);\r
+\r
+ if (this.is_network)\r
+ panel.tab.data('connection_id', this.model.network.get('connection_id'));\r
+\r
+ panel.tab.appendTo(this.$el);\r
\r
panel.bind('change:title', this.updateTabTitle);\r
+ panel.bind('change:name', this.updateTabTitle);\r
+\r
_kiwi.app.view.doLayout();\r
},\r
panelRemoved: function (panel) {\r
\r
panelActive: function (panel, previously_active_panel) {\r
// Remove any existing tabs or part images\r
- $('.part', this.$el).remove();\r
- this.tabs_applets.children().removeClass('active');\r
- this.tabs_msg.children().removeClass('active');\r
+ _kiwi.app.view.$el.find('.panellist .part').remove();\r
+ _kiwi.app.view.$el.find('.panellist .active').removeClass('active');\r
\r
panel.tab.addClass('active');\r
\r
tabClick: function (e) {\r
var tab = $(e.currentTarget);\r
\r
- var panel = this.model.getByCid(tab.data('panel_id'));\r
+ var panel = tab.data('panel');\r
if (!panel) {\r
// A panel wasn't found for this tab... wadda fuck\r
return;\r
\r
partClick: function (e) {\r
var tab = $(e.currentTarget).parent();\r
- var panel = this.model.getByCid(tab.data('panel_id'));\r
+ var panel = tab.data('panel');\r
+\r
+ if (!panel) return;\r
\r
// Only need to part if it's a channel\r
// If the nicklist is empty, we haven't joined the channel as yet\r
if (panel.isChannel() && panel.get('members').models.length > 0) {\r
- _kiwi.gateway.part(panel.get('name'));\r
+ this.model.network.gateway.part(panel.get('name'));\r
} else {\r
panel.close();\r
}\r
- },\r
-\r
- next: function () {\r
- var next = _kiwi.app.panels.active.tab.next();\r
- if (!next.length) next = $('li:first', this.tabs_msgs);\r
-\r
- next.click();\r
- },\r
- prev: function () {\r
- var prev = _kiwi.app.panels.active.tab.prev();\r
- if (!prev.length) prev = $('li:last', this.tabs_msgs);\r
-\r
- prev.click();\r
}\r
});\r
\r
inp_val = inp.text();\r
\r
// Only allow topic editing if this is a channel panel\r
- if (!_kiwi.app.panels.active.isChannel()) {\r
+ if (!_kiwi.app.panels().active.isChannel()) {\r
return false;\r
}\r
\r
// If hit return key, update the current topic\r
if (ev.keyCode === 13) {\r
- _kiwi.gateway.topic(_kiwi.app.panels.active.get('name'), inp_val);\r
+ _kiwi.gateway.topic(null, _kiwi.app.panels().active.get('name'), inp_val);\r
return false;\r
}\r
},\r
// Hold tab autocomplete data\r
this.tabcomplete = {active: false, data: [], prefix: ''};\r
\r
- _kiwi.gateway.bind('change:nick', function () {\r
- $('.nick', that.$el).text(this.get('nick'));\r
+ // Keep the nick view updated with nick changes\r
+ _kiwi.app.connections.on('change:nick', function(connection) {\r
+ // Only update the nick view if it's the active connection\r
+ if (connection !== _kiwi.app.connections.active_connection)\r
+ return;\r
+\r
+ $('.nick', that.$el).text(connection.get('nick'));\r
+ });\r
+\r
+ // Update our nick view as we flick between connections\r
+ _kiwi.app.connections.on('active', function(panel, connection) {\r
+ $('.nick', that.$el).text(connection.get('nick'));\r
});\r
},\r
\r
this.buffer_pos--;\r
inp.val(this.buffer[this.buffer_pos]);\r
}\r
- break;\r
+ //suppress browsers default behavior as it would set the cursor at the beginning\r
+ return false;\r
\r
case (ev.keyCode === 40): // down\r
if (this.buffer_pos < this.buffer.length) {\r
break;\r
\r
case (ev.keyCode === 219 && meta): // [ + meta\r
- _kiwi.app.panels.view.prev();\r
+ // Find all the tab elements and get the index of the active tab\r
+ var $tabs = $('#kiwi #tabs').find('li[class!=connection]');\r
+ var cur_tab_ind = (function() {\r
+ for (var idx=0; idx<$tabs.length; idx++){\r
+ if ($($tabs[idx]).hasClass('active'))\r
+ return idx;\r
+ }\r
+ })();\r
+\r
+ // Work out the previous tab along. Wrap around if needed\r
+ if (cur_tab_ind === 0) {\r
+ $prev_tab = $($tabs[$tabs.length - 1]);\r
+ } else {\r
+ $prev_tab = $($tabs[cur_tab_ind - 1]);\r
+ }\r
+\r
+ $prev_tab.click();\r
return false;\r
\r
case (ev.keyCode === 221 && meta): // ] + meta\r
- _kiwi.app.panels.view.next();\r
+ // Find all the tab elements and get the index of the active tab\r
+ var $tabs = $('#kiwi #tabs').find('li[class!=connection]');\r
+ var cur_tab_ind = (function() {\r
+ for (var idx=0; idx<$tabs.length; idx++){\r
+ if ($($tabs[idx]).hasClass('active'))\r
+ return idx;\r
+ }\r
+ })();\r
+\r
+ // Work out the next tab along. Wrap around if needed\r
+ if (cur_tab_ind === $tabs.length - 1) {\r
+ $next_tab = $($tabs[0]);\r
+ } else {\r
+ $next_tab = $($tabs[cur_tab_ind + 1]);\r
+ }\r
+\r
+ $next_tab.click();\r
return false;\r
\r
case (ev.keyCode === 9): // tab\r
this.tabcomplete.active = true;\r
if (_.isEqual(this.tabcomplete.data, [])) {\r
// Get possible autocompletions\r
- var ac_data = [];\r
- $.each(_kiwi.app.panels.active.get('members').models, function (i, member) {\r
+ var ac_data = [],\r
+ members = _kiwi.app.panels().active.get('members');\r
+\r
+ // If we have a members list, get the models. Otherwise empty array\r
+ members = members ? members.models : [];\r
+\r
+ $.each(members, function (i, member) {\r
if (!member) return;\r
ac_data.push(member.get('nick'));\r
});\r
+\r
+ ac_data.push(_kiwi.app.panels().active.get('name'));\r
+\r
ac_data = _.sortBy(ac_data, function (nick) {\r
return nick;\r
});\r
command_raw = command_raw.replace(/^\/\//, '/');\r
\r
// Prepend the default command\r
- command_raw = '/msg ' + _kiwi.app.panels.active.get('name') + ' ' + command_raw;\r
+ command_raw = '/msg ' + _kiwi.app.panels().active.get('name') + ' ' + command_raw;\r
}\r
\r
// Process the raw command for any aliases\r
- this.preprocessor.vars.server = _kiwi.gateway.get('name');\r
- this.preprocessor.vars.channel = _kiwi.app.panels.active.get('name');\r
+ this.preprocessor.vars.server = _kiwi.app.connections.active_connection.get('name');\r
+ this.preprocessor.vars.channel = _kiwi.app.panels().active.get('name');\r
this.preprocessor.vars.destination = this.preprocessor.vars.channel;\r
command_raw = this.preprocessor.process(command_raw);\r
\r
} else {\r
// Default command\r
command = 'msg';\r
- params.unshift(_kiwi.app.panels.active.get('name'));\r
+ params.unshift(_kiwi.app.panels().active.get('name'));\r
}\r
\r
// Trigger the command events\r
\r
// If we didn't have any listeners for this event, fire a special case\r
// TODO: This feels dirty. Should this really be done..?\r
- if (!this._callbacks['command:' + command]) {\r
+ if (!this._events['command:' + command]) {\r
this.trigger('unknown_command', {command: command, params: params});\r
}\r
},\r
return html;\r
}\r
});\r
+\r
+\r
+\r
+_kiwi.view.MenuBox = Backbone.View.extend({\r
+ events: {\r
+ 'click .ui_menu_foot .close': 'dispose'\r
+ },\r
+\r
+ initialize: function(title) {\r
+ var that = this;\r
+\r
+ this.$el = $('<div class="ui_menu"></div>');\r
+\r
+ this._title = title || '';\r
+ this._items = {};\r
+ this._display_footer = true;\r
+ this._close_on_blur = true;\r
+\r
+ this._close_proxy = function(event) {\r
+ that.onDocumentClick(event);\r
+ };\r
+ $(document).on('click', this._close_proxy);\r
+ },\r
+\r
+\r
+ render: function() {\r
+ var that = this;\r
+\r
+ this.$el.find('*').remove();\r
+\r
+ if (this._title) {\r
+ $('<div class="ui_menu_title"></div>')\r
+ .text(this._title)\r
+ .appendTo(this.$el);\r
+ }\r
+\r
+\r
+ _.each(this._items, function(item) {\r
+ var $item = $('<div class="ui_menu_content hover"></div>')\r
+ .append(item);\r
+\r
+ that.$el.append($item);\r
+ });\r
+\r
+ if (this._display_footer)\r
+ this.$el.append('<div class="ui_menu_foot"><a class="close" onclick="">Close <i class="icon-remove"></i></a></div>');\r
+ },\r
+\r
+\r
+ onDocumentClick: function(event) {\r
+ var $target = $(event.target);\r
+\r
+ if (!this._close_on_blur)\r
+ return;\r
+\r
+ // If this is not itself AND we don't contain this element, dispose $el\r
+ if ($target[0] != this.$el[0] && this.$el.has($target).length === 0)\r
+ this.dispose();\r
+ },\r
+\r
+\r
+ dispose: function() {\r
+ _.each(this._items, function(item) {\r
+ item.dispose && item.dispose();\r
+ item.remove && item.remove();\r
+ });\r
+\r
+ this._items = null;\r
+ this.remove();\r
+\r
+ $(document).off('click', this._close_proxy);\r
+ },\r
+\r
+\r
+ addItem: function(item_name, $item) {\r
+ $item = $($item);\r
+ if ($item.is('a')) $item.addClass('icon-chevron-right');\r
+ this._items[item_name] = $item;\r
+ },\r
+\r
+\r
+ removeItem: function(item_name) {\r
+ delete this._items[item_name];\r
+ },\r
+\r
+\r
+ showFooter: function(show) {\r
+ this._display_footer = show;\r
+ },\r
+\r
+\r
+ closeOnBlur: function(close_it) {\r
+ this._close_on_blur = close_it;\r
+ },\r
+\r
+\r
+ show: function() {\r
+ this.render();\r
+ this.$el.appendTo(_kiwi.app.view.$el);\r
+ }\r
+});\r
-/*!
- Lo-Dash 0.9.1 lodash.com/license
- Underscore.js 1.4.2 underscorejs.org/LICENSE
-*/
-;(function(e,t){function s(e){if(e&&e.__wrapped__)return e;if(!(this instanceof s))return new s(e);this.__wrapped__=e}function o(e,t,n){t||(t=0);var r=e.length,i=r-t>=(n||Q),s=i?{}:e;if(i)for(n=t-1;++n<r;){var o=e[n]+"";(dt.call(s,o)?s[o]:s[o]=[]).push(e[n])}return function(e){if(i){var n=e+"";return dt.call(s,n)&&-1<j(s[n],e)}return-1<j(s,e,t)}}function u(e,n){var r=e.b,i=n.b,e=e.a,n=n.a;if(e!==n){if(e>n||e===t)return 1;if(e<n||n===t)return-1}return r<i?-1:1}function a(e,t,n){function r(){var u=arguments
-,a=s?this:t;return i||(e=t[o]),n.length&&(u=u.length?n.concat(gt.call(u)):n),this instanceof r?(p.prototype=e.prototype,a=new p,(u=e.apply(a,u))&&$t[typeof u]?u:a):e.apply(a,u)}var i=S(e),s=!n,o=e;return s&&(n=t),r}function f(e,n){return e?"function"!=typeof e?function(t){return t[e]}:n!==t?function(t,r,i){return e.call(n,t,r,i)}:e:U}function l(){for(var e={b:"",c:"",e:Ht,f:Xt,g:"",h:Ft,i:Rt,j:ft,k:"",l:n},t,r=0;t=arguments[r];r++)for(var i in t)e[i]=t[i];t=e.a,e.d=/^[^,]+/.exec(t)[0],r="var h,w,j="+
-e.d+",r="+e.d+";if(!"+e.d+")return r;"+e.k+";",e.b?(r+="var k=j.length;h=-1;if(typeof k=='number'){",e.i&&(r+="if(v.call(j)==t){j=j.split('')}"),r+="while(++h<k){w=j[h];"+e.b+"}}else {"):e.h&&(r+="var k=j.length;h=-1;if(k&&i(j)){while(++h<k){w=j[h+=''];"+e.g+"}}else {"),e.e||(r+="var s=typeof j=='function'&&q.call(j,'prototype');");if(e.f&&e.l)r+="var o=-1,p=n[typeof j]?l(j):[],k=p.length;while(++o<k){h=p[o];",e.e||(r+="if(!(s&&h=='prototype')){"),r+="w=j[h];"+e.g+"",e.e||(r+="}");else{r+="for(h in j){"
-;if(!e.e||e.l)r+="if(",e.e||(r+="!(s&&h=='prototype')"),!e.e&&e.l&&(r+="&&"),e.l&&(r+="g.call(j,h)"),r+="){";r+="w=j[h];"+e.g+";";if(!e.e||e.l)r+="}"}r+="}";if(e.e){r+="var f=j.constructor;";for(i=0;7>i;i++)r+="h='"+e.j[i]+"';if(","constructor"==e.j[i]&&(r+="!(f&&f.prototype===j)&&"),r+="g.call(j,h)){w=j[h];"+e.g+"}"}if(e.b||e.h)r+="}";return r+=e.c+";return r",Function("e,g,i,n,l,q,t,v","return function("+t+"){"+r+"}")(f,dt,v,$t,xt,mt,Pt,yt)}function c(e){return"\\"+Jt[e]}function h(e){return en
-[e]}function p(){}function d(e){return tn[e]}function v(e){return yt.call(e)==kt}function m(e){var t=i;if(!e||"object"!=typeof e||v(e))return t;var n=e.constructor;return(!Ut||"function"==typeof e.toString||"string"!=typeof (e+""))&&(!S(n)||n instanceof n)?Bt?(Yt(e,function(e,n,r){return t=!dt.call(r,n),i}),t===i):(Yt(e,function(e,n){t=n}),t===i||dt.call(e,t)):t}function g(e){var t=[];return Zt(e,function(e,n){t.push(n)}),t}function y(e,t,n,s,o){if(e==r)return e;n&&(t=i);if(n=$t[typeof e]){var u=
-yt.call(e);if(!Vt[u]||It&&v(e))return e;var a=u==Lt,n=a||(u==_t?on(e):n)}if(!n||!t)return n?a?gt.call(e):rn({},e):e;n=e.constructor;switch(u){case At:case Ot:return new n(+e);case Mt:case Pt:return new n(e);case Dt:return n(e.source,rt.exec(e))}s||(s=[]),o||(o=[]);for(u=s.length;u--;)if(s[u]==e)return o[u];var f=a?n(e.length):{};return s.push(e),o.push(f),(a?an:Zt)(e,function(e,n){f[n]=y(e,t,r,s,o)}),f}function b(e){var t=[];return Yt(e,function(e,n){S(e)&&t.push(n)}),t.sort()}function w(e){var t=
-{};return Zt(e,function(e,n){t[e]=n}),t}function E(e,t,s,o){if(e===t)return 0!==e||1/e==1/t;if(e==r||t==r)return e===t;var u=yt.call(e);if(u!=yt.call(t))return i;switch(u){case At:case Ot:return+e==+t;case Mt:return e!=+e?t!=+t:0==e?1/e==1/t:e==+t;case Dt:case Pt:return e==t+""}var a=u==Lt||u==kt;if(It&&!a&&(a=v(e))&&!v(t))return i;if(!a){if(e.__wrapped__||t.__wrapped__)return E(e.__wrapped__||e,t.__wrapped__||t);if(u!=_t||Ut&&("function"!=typeof e.toString&&"string"==typeof (e+"")||"function"!=typeof
-t.toString&&"string"==typeof (t+"")))return i;var u=e.constructor,f=t.constructor;if(u!=f&&(!S(u)||!(u instanceof u&&S(f)&&f instanceof f)))return i}s||(s=[]),o||(o=[]);for(u=s.length;u--;)if(s[u]==e)return o[u]==t;var u=-1,f=n,l=0;s.push(e),o.push(t);if(a){l=e.length;if(f=l==t.length)for(;l--&&(f=E(e[l],t[l],s,o)););return f}for(var c in e)if(dt.call(e,c)&&(l++,!dt.call(t,c)||!E(e[c],t[c],s,o)))return i;for(c in t)if(dt.call(t,c)&&!(l--))return i;if(Ht)for(;7>++u;)if(c=ft[u],dt.call(e,c)&&(!dt.call
-(t,c)||!E(e[c],t[c],s,o)))return i;return n}function S(e){return"function"==typeof e}function x(e,t,n){var i=arguments,s=0,o=2,u=i[3],a=i[4];n!==K&&(u=[],a=[],o=i.length);for(;++s<o;)Zt(i[s],function(t,n){var i,s,o;if(t&&((s=sn(t))||on(t))){for(var f=u.length;f--;)if(i=u[f]==t)break;i?e[n]=a[f]:(u.push(t),a.push(o=(o=e[n],s)?sn(o)?o:[]:on(o)?o:{}),e[n]=x(o,t,K,u,a))}else t!=r&&(e[n]=t)});return e}function T(e){var t=[];return Zt(e,function(e){t.push(e)}),t}function N(e,t){return"number"==typeof (
-e?e.length:0)?-1<(yt.call(e)==Pt?e.indexOf(t):j(e,t)):P(e,function(e){return e===t})}function C(e,t,r){var i=n,t=f(t,r);return an(e,function(e,n,r){return i=!!t(e,n,r)}),i}function k(e,t,n){var r=[],t=f(t,n);return an(e,function(e,n,i){t(e,n,i)&&r.push(e)}),r}function L(e,t,r){var i,t=f(t,r);return P(e,function(e,r,s){return t(e,r,s)&&(i=e,n)}),i}function A(e,t,n){var r=-1,i=e?e.length:0,s=Array("number"==typeof i?i:0),t=f(t,n);if(sn(e))for(;++r<i;)s[r]=t(e[r],r,e);else an(e,function(e,n,i){s[++r
-]=t(e,n,i)});return s}function O(e,t,n){var r=-Infinity,i=-1,s=e?e.length:0,o=r;if(t||"number"!=typeof s)t=f(t,n),an(e,function(e,n,i){n=t(e,n,i),n>r&&(r=n,o=e)});else for(;++i<s;)e[i]>o&&(o=e[i]);return o}function M(e,t){var n=[];return an(e,function(e){n.push(e[t])}),n}function _(e,t,n,r){var s=3>arguments.length,t=f(t,r);return an(e,function(e,r,o){n=s?(s=i,e):t(n,e,r,o)}),n}function D(e,t,n,r){var s=e,o=e?e.length:0,u=3>arguments.length;if("number"!=typeof o)var a=un(e),o=a.length;else Rt&&yt
-.call(e)==Pt&&(s=e.split(""));return an(e,function(e,f,l){f=a?a[--o]:--o,n=u?(u=i,s[f]):t.call(r,n,s[f],f,l)}),n}function P(e,t,n){var r,t=f(t,n);return an(e,function(e,n,i){return!(r=t(e,n,i))}),!!r}function H(e,t,n){if(e)return t==r||n?e[0]:gt.call(e,0,t)}function B(e,t){for(var n=-1,r=e?e.length:0,i=[];++n<r;){var s=e[n];sn(s)?vt.apply(i,t?s:B(s)):i.push(s)}return i}function j(e,t,n){var r=-1,i=e?e.length:0;if("number"==typeof n)r=(0>n?Tt(0,i+n):n||0)-1;else if(n)return r=I(e,t),e[r]===t?r:-1;
-for(;++r<i;)if(e[r]===t)return r;return-1}function F(e,t,n){return e?gt.call(e,t==r||n?1:t):[]}function I(e,t,n,r){for(var i=0,s=e?e.length:i,n=n?f(n,r):U,t=n(t);i<s;)r=i+s>>>1,n(e[r])<t?i=r+1:s=r;return i}function q(e,t,n,r){var s=-1,o=e?e.length:0,u=[],a=u;"function"==typeof t&&(r=n,n=t,t=i),n&&(a=[],n=f(n,r));for(;++s<o;){var r=e[s],l=n?n(r,s,e):r;if(t?!s||a[a.length-1]!==l:0>j(a,l))n&&a.push(l),u.push(r)}return u}function R(e,t){return Wt||bt&&2<arguments.length?bt.call.apply(bt,arguments):a(
-e,t,gt.call(arguments,2))}function U(e){return e}function z(e){an(b(e),function(t){var r=s[t]=e[t];s.prototype[t]=function(){var e=[this.__wrapped__];return vt.apply(e,arguments),e=r.apply(s,e),this.__chain__&&(e=new s(e),e.__chain__=n),e}})}var n=!0,r=null,i=!1,W="object"==typeof exports&&exports,X="object"==typeof global&&global;X.global===X&&(e=X);var V=[],$={},J=0,K={},Q=30,G=e._,Y=/[-?+=!~*%&^<>|{(\/]|\[\D|\b(?:delete|in|instanceof|new|typeof|void)\b/,Z=/&(?:amp|lt|gt|quot|#x27);/g,et=/\b__p\+='';/g
-,tt=/\b(__p\+=)''\+/g,nt=/(__e\(.*?\)|\b__t\))\+'';/g,rt=/\w*$/,it=/(?:__e|__t=)\(\s*(?![\d\s"']|this\.)/g,st=RegExp("^"+($.valueOf+"").replace(/[.*+?^=!:${}()|[\]\/\\]/g,"\\$&").replace(/valueOf|for [^\]]+/g,".+?")+"$"),ot=/($^)/,ut=/[&<>"']/g,at=/['\n\r\t\u2028\u2029\\]/g,ft="constructor hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf".split(" "),lt=Math.ceil,ct=V.concat,ht=Math.floor,pt=st.test(pt=Object.getPrototypeOf)&&pt,dt=$.hasOwnProperty,vt=V.push,mt=$.propertyIsEnumerable
-,gt=V.slice,yt=$.toString,bt=st.test(bt=gt.bind)&&bt,wt=st.test(wt=Array.isArray)&&wt,Et=e.isFinite,St=e.isNaN,xt=st.test(xt=Object.keys)&&xt,Tt=Math.max,Nt=Math.min,Ct=Math.random,kt="[object Arguments]",Lt="[object Array]",At="[object Boolean]",Ot="[object Date]",Mt="[object Number]",_t="[object Object]",Dt="[object RegExp]",Pt="[object String]",Ht,Bt,jt=(jt={0:1,length:1},V.splice.call(jt,0,1),jt[0]),Ft=n;(function(){function e(){this.x=1}var t=[];e.prototype={valueOf:1,y:1};for(var n in new e
-)t.push(n);for(n in arguments)Ft=!n;Ht=!/valueOf/.test(t),Bt="x"!=t[0]})(1);var It=!v(arguments),qt="x"!=gt.call("x")[0],Rt="xx"!="x"[0]+Object("x")[0];try{var Ut=("[object Object]",yt.call(e.document||0)==_t)}catch(zt){}var Wt=bt&&/\n|Opera/.test(bt+yt.call(e.opera)),Xt=xt&&/^.+$|true/.test(xt+!!e.attachEvent),Vt={};Vt[kt]=Vt["[object Function]"]=i,Vt[Lt]=Vt[At]=Vt[Ot]=Vt[Mt]=Vt[_t]=Vt[Dt]=Vt[Pt]=n;var $t={"boolean":i,"function":n,object:n,number:i,string:i,"undefined":i},Jt={"\\":"\\","'":"'","\n"
-:"n","\r":"r"," ":"t","\u2028":"u2028","\u2029":"u2029"};s.templateSettings={escape:/<%-([\s\S]+?)%>/g,evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,variable:""};var Kt={a:"d,c,u",k:"c=e(c,u)",b:"if(c(w,h,d)===false)return r",g:"if(c(w,h,d)===false)return r"},Qt={l:i,a:"m",k:"for(var a=1,b=arguments.length;a<b;a++){if(j=arguments[a]){",g:"r[h]=w",c:"}}"},Gt={b:r};It&&(v=function(e){return e?dt.call(e,"callee"):i});var Yt=l(Kt,Gt,{l:i}),Zt=l(Kt,Gt),en={"&":"&","<":"<",">":">"
-,'"':""","'":"'"},tn=w(en),nn=l(Qt,{g:"if(r[h]==null)"+Qt.g}),rn=l(Qt),sn=wt||function(e){return yt.call(e)==Lt};S(/x/)&&(S=function(e){return"[object Function]"==yt.call(e)});var on=pt?function(e){if(!e||"object"!=typeof e)return i;var t=e.valueOf,n="function"==typeof t&&(n=pt(t))&&pt(n);return n?e==n||pt(e)==n&&!v(e):m(e)}:m,un=xt?function(e){var t=typeof e;return"function"==t&&mt.call(e,"prototype")?g(e):e&&$t[t]?xt(e):[]}:g,an=l(Kt);s.VERSION="0.9.1",s.after=function(e,t){return 1>e?
-t():function(){if(1>--e)return t.apply(this,arguments)}},s.bind=R,s.bindAll=function(e){for(var t=arguments,n=1<t.length?0:(t=b(e),-1),r=t.length;++n<r;){var i=t[n];e[i]=R(e[i],e)}return e},s.chain=function(e){return e=new s(e),e.__chain__=n,e},s.clone=y,s.compact=function(e){for(var t=-1,n=e?e.length:0,r=[];++t<n;){var i=e[t];i&&r.push(i)}return r},s.compose=function(){var e=arguments;return function(){for(var t=arguments,n=e.length;n--;)t=[e[n].apply(this,t)];return t[0]}},s.contains=N,s.countBy=
-function(e,t,n){var r={},t=f(t,n);return an(e,function(e,n,i){n=t(e,n,i),dt.call(r,n)?r[n]++:r[n]=1}),r},s.debounce=function(e,t,n){function i(){a=r,n||(o=e.apply(u,s))}var s,o,u,a;return function(){var r=n&&!a;return s=arguments,u=this,clearTimeout(a),a=setTimeout(i,t),r&&(o=e.apply(u,s)),o}},s.defaults=nn,s.defer=function(e){var n=gt.call(arguments,1);return setTimeout(function(){e.apply(t,n)},1)},s.delay=function(e,n){var r=gt.call(arguments,2);return setTimeout(function(){e.apply(t,r)},n)},s.
-difference=function(e){for(var t=-1,n=e?e.length:0,r=ct.apply(V,arguments),r=o(r,n),i=[];++t<n;){var s=e[t];r(s)||i.push(s)}return i},s.escape=function(e){return e==r?"":(e+"").replace(ut,h)},s.every=C,s.extend=rn,s.filter=k,s.find=L,s.first=H,s.flatten=B,s.forEach=an,s.forIn=Yt,s.forOwn=Zt,s.functions=b,s.groupBy=function(e,t,n){var r={},t=f(t,n);return an(e,function(e,n,i){n=t(e,n,i),(dt.call(r,n)?r[n]:r[n]=[]).push(e)}),r},s.has=function(e,t){return e?dt.call(e,t):i},s.identity=U,s.indexOf=j,s
-.initial=function(e,t,n){return e?gt.call(e,0,-(t==r||n?1:t)):[]},s.intersection=function(e){var t=arguments,n=t.length,r={},i=[];return an(e,function(e){if(0>j(i,e)){for(var s=n;--s;)if(!(r[s]||(r[s]=o(t[s])))(e))return;i.push(e)}}),i},s.invert=w,s.invoke=function(e,t){var n=gt.call(arguments,2),r="function"==typeof t,i=[];return an(e,function(e){i.push((r?t:e[t]).apply(e,n))}),i},s.isArguments=v,s.isArray=sn,s.isBoolean=function(e){return e===n||e===i||yt.call(e)==At},s.isDate=function(e){return yt
-.call(e)==Ot},s.isElement=function(e){return e?1===e.nodeType:i},s.isEmpty=function(e){var t=n;if(!e)return t;var r=yt.call(e),s=e.length;return r==Lt||r==Pt||r==kt||It&&v(e)||r==_t&&"number"==typeof s&&S(e.splice)?!s:(Zt(e,function(){return t=i}),t)},s.isEqual=E,s.isFinite=function(e){return Et(e)&&!St(parseFloat(e))},s.isFunction=S,s.isNaN=function(e){return yt.call(e)==Mt&&e!=+e},s.isNull=function(e){return e===r},s.isNumber=function(e){return yt.call(e)==Mt},s.isObject=function(e){return e?$t
-[typeof e]:i},s.isPlainObject=on,s.isRegExp=function(e){return yt.call(e)==Dt},s.isString=function(e){return yt.call(e)==Pt},s.isUndefined=function(e){return e===t},s.keys=un,s.last=function(e,t,n){if(e){var i=e.length;return t==r||n?e[i-1]:gt.call(e,-t||i)}},s.lastIndexOf=function(e,t,n){var r=e?e.length:0;for("number"==typeof n&&(r=(0>n?Tt(0,r+n):Nt(n,r-1))+1);r--;)if(e[r]===t)return r;return-1},s.lateBind=function(e,t){return a(t,e,gt.call(arguments,2))},s.map=A,s.max=O,s.memoize=function(e,t)
-{var n={};return function(){var r=t?t.apply(this,arguments):arguments[0];return dt.call(n,r)?n[r]:n[r]=e.apply(this,arguments)}},s.merge=x,s.min=function(e,t,n){var r=Infinity,i=-1,s=e?e.length:0,o=r;if(t||"number"!=typeof s)t=f(t,n),an(e,function(e,n,i){n=t(e,n,i),n<r&&(r=n,o=e)});else for(;++i<s;)e[i]<o&&(o=e[i]);return o},s.mixin=z,s.noConflict=function(){return e._=G,this},s.object=function(e,t){for(var n=-1,r=e?e.length:0,i={};++n<r;){var s=e[n];t?i[s]=t[n]:i[s[0]]=s[1]}return i},s.omit=function(
-e,t,n){var r="function"==typeof t,i={};if(r)t=f(t,n);else var s=ct.apply(V,arguments);return Yt(e,function(e,n,o){if(r?!t(e,n,o):0>j(s,n,1))i[n]=e}),i},s.once=function(e){var t,s=i;return function(){return s?t:(s=n,t=e.apply(this,arguments),e=r,t)}},s.pairs=function(e){var t=[];return Zt(e,function(e,n){t.push([n,e])}),t},s.partial=function(e){return a(e,gt.call(arguments,1))},s.pick=function(e,t,n){var r={};if("function"!=typeof t)for(var i=0,s=ct.apply(V,arguments),o=s.length;++i<o;){var u=s[i]
-;u in e&&(r[u]=e[u])}else t=f(t,n),Yt(e,function(e,n,i){t(e,n,i)&&(r[n]=e)});return r},s.pluck=M,s.random=function(e,t){return e==r&&t==r&&(t=1),e=+e||0,t==r&&(t=e,e=0),e+ht(Ct()*((+t||0)-e+1))},s.range=function(e,t,n){e=+e||0,n=+n||1,t==r&&(t=e,e=0);for(var i=-1,t=Tt(0,lt((t-e)/n)),s=Array(t);++i<t;)s[i]=e,e+=n;return s},s.reduce=_,s.reduceRight=D,s.reject=function(e,t,n){return t=f(t,n),k(e,function(e,n,r){return!t(e,n,r)})},s.rest=F,s.result=function(e,t){var n=e?e[t]:r;return S(n)?e[t]():n},s
-.shuffle=function(e){var t=-1,n=Array(e?e.length:0);return an(e,function(e){var r=ht(Ct()*(++t+1));n[t]=n[r],n[r]=e}),n},s.size=function(e){var t=e?e.length:0;return"number"==typeof t?t:un(e).length},s.some=P,s.sortBy=function(e,t,n){var r=[],t=f(t,n);an(e,function(e,n,i){r.push({a:t(e,n,i),b:n,c:e})}),e=r.length;for(r.sort(u);e--;)r[e]=r[e].c;return r},s.sortedIndex=I,s.tap=function(e,t){return t(e),e},s.template=function(e,t,n){e||(e=""),n||(n={});var r,i,o=0,u=s.templateSettings,a="__p += '",f=
-n.variable||u.variable,l=f;e.replace(RegExp((n.escape||u.escape||ot).source+"|"+(n.interpolate||u.interpolate||ot).source+"|"+(n.evaluate||u.evaluate||ot).source+"|$","g"),function(t,n,i,s,u){a+=e.slice(o,u).replace(at,c),a+=n?"'+__e("+n+")+'":s?"';"+s+";__p+='":i?"'+((__t=("+i+"))==null?'':__t)+'":"",r||(r=s||Y.test(n||i)),o=u+t.length}),a+="';",l||(f="obj",r?a="with("+f+"){"+a+"}":(n=RegExp("(\\(\\s*)"+f+"\\."+f+"\\b","g"),a=a.replace(it,"$&"+f+".").replace(n,"$1__d"))),a=(r?a.replace(et,""):a)
-.replace(tt,"$1").replace(nt,"$1;"),a="function("+f+"){"+(l?"":f+"||("+f+"={});")+"var __t,__p='',__e=_.escape"+(r?",__j=[].join;function print(){__p+=__j.call(arguments,'')}":(l?"":",__d="+f+"."+f+"||"+f)+";")+a+"return __p}";try{i=Function("_","return "+a)(s)}catch(h){throw h.source=a,h}return t?i(t):(i.source=a,i)},s.throttle=function(e,t){function n(){a=new Date,u=r,s=e.apply(o,i)}var i,s,o,u,a=0;return function(){var r=new Date,f=t-(r-a);return i=arguments,o=this,0>=f?(clearTimeout
-(u),a=r,s=e.apply(o,i)):u||(u=setTimeout(n,f)),s}},s.times=function(e,t,n){for(var e=+e||0,r=-1,i=Array(e);++r<e;)i[r]=t.call(n,r);return i},s.toArray=function(e){return e&&"number"==typeof e.length?(qt?yt.call(e)==Pt:"string"==typeof e)?e.split(""):gt.call(e):T(e)},s.unescape=function(e){return e==r?"":(e+"").replace(Z,d)},s.union=function(){return q(ct.apply(V,arguments))},s.uniq=q,s.uniqueId=function(e){var t=J++;return e?e+t:t},s.values=T,s.where=function(e,t){var n=[];return Yt(t,function(e,
-t){n.push(t)}),k(e,function(e){for(var r=n.length;r--;){var i=e[n[r]]===t[n[r]];if(!i)break}return!!i})},s.without=function(e){for(var t=-1,n=e?e.length:0,r=o(arguments,1,20),i=[];++t<n;){var s=e[t];r(s)||i.push(s)}return i},s.wrap=function(e,t){return function(){var n=[e];return vt.apply(n,arguments),t.apply(this,n)}},s.zip=function(e){for(var t=-1,n=e?O(M(arguments,"length")):0,r=Array(n);++t<n;)r[t]=M(arguments,t);return r},s.all=C,s.any=P,s.collect=A,s.detect=L,s.drop=F,s.each=an,s.foldl=_,s.
-foldr=D,s.head=H,s.include=N,s.inject=_,s.methods=b,s.select=k,s.tail=F,s.take=H,s.unique=q,z(s),s.prototype.chain=function(){return this.__chain__=n,this},s.prototype.value=function(){return this.__wrapped__},an("pop push reverse shift sort splice unshift".split(" "),function(e){var t=V[e];s.prototype[e]=function(){var e=this.__wrapped__;return t.apply(e,arguments),jt&&e.length===0&&delete e[0],this.__chain__&&(e=new s(e),e.__chain__=n),e}}),an(["concat","join","slice"],function(e){var t=V[e];s.
-prototype[e]=function(){var e=t.apply(this.__wrapped__,arguments);return this.__chain__&&(e=new s(e),e.__chain__=n),e}}),typeof define=="function"&&typeof define.amd=="object"&&define.amd?(e._=s,define(function(){return s})):W?"object"==typeof module&&module&&module.exports==W?(module.exports=s)._=s:W._=s:e._=s})(this);
\ No newline at end of file
+/**
+ * @license
+ * Lo-Dash 1.2.1 (Custom Build) lodash.com/license
+ * Build: `lodash -o ./dist/lodash.compat.js`
+ * Underscore.js 1.4.4 underscorejs.org/LICENSE
+ */
+;(function(n){function t(r){function a(n){return n&&typeof n=="object"&&!me(n)&&Qt.call(n,"__wrapped__")?n:new V(n)}function R(n){var t=n.length,e=t>=l;if(e)for(var r={},u=-1;++u<t;){var a=f+n[u];(r[a]||(r[a]=[])).push(n[u])}return function(t){if(e){var u=f+t;return r[u]&&-1<_t(r[u],t)}return-1<_t(n,t)}}function T(n){return n.charCodeAt(0)}function D(n,t){var e=n.b,r=t.b;if(n=n.a,t=t.a,n!==t){if(n>t||typeof n=="undefined")return 1;if(n<t||typeof t=="undefined")return-1}return e<r?-1:1}function z(n,t,e,r){function u(){var r=arguments,l=o?this:t;
+return a||(n=t[i]),e.length&&(r=r.length?(r=le.call(r),f?r.concat(e):e.concat(r)):e),this instanceof u?(G.prototype=n.prototype,l=new G,G.prototype=null,r=n.apply(l,r),et(r)?r:l):n.apply(l,r)}var a=tt(n),o=!e,i=t;if(o){var f=r;e=t}else if(!a){if(!r)throw new Dt;t=n}return u}function L(){for(var n,t={g:j,b:"k(m)",c:"",e:"m",f:"",h:"",i:!0,j:!!be},e=0;n=arguments[e];e++)for(var r in n)t[r]=n[r];if(n=t.a,t.d=/^[^,]+/.exec(n)[0],e=$t,r="var i,m="+t.d+",u="+t.e+";if(!m)return u;"+t.h+";",t.b?(r+="var n=m.length;i=-1;if("+t.b+"){",ve.unindexedChars&&(r+="if(l(m)){m=m.split('')}"),r+="while(++i<n){"+t.f+"}}else{"):ve.nonEnumArgs&&(r+="var n=m.length;i=-1;if(n&&j(m)){while(++i<n){i+='';"+t.f+"}}else{"),ve.enumPrototypes&&(r+="var v=typeof m=='function';"),t.i&&t.j)r+="var s=-1,t=r[typeof m]?o(m):[],n=t.length;while(++s<n){i=t[s];",ve.enumPrototypes&&(r+="if(!(v&&i=='prototype')){"),r+=t.f,ve.enumPrototypes&&(r+="}"),r+="}";
+else if(r+="for(i in m){",(ve.enumPrototypes||t.i)&&(r+="if(",ve.enumPrototypes&&(r+="!(v&&i=='prototype')"),ve.enumPrototypes&&t.i&&(r+="&&"),t.i&&(r+="h.call(m,i)"),r+="){"),r+=t.f+";",(ve.enumPrototypes||t.i)&&(r+="}"),r+="}",ve.nonEnumShadows){r+="var f=m.constructor;";for(var u=0;7>u;u++)r+="i='"+t.g[u]+"';if(","constructor"==t.g[u]&&(r+="!(f&&f.prototype===m)&&"),r+="h.call(m,i)){"+t.f+"}"}return(t.b||ve.nonEnumArgs)&&(r+="}"),r+=t.c+";return u",e("h,j,k,l,o,p,r","return function("+n+"){"+r+"}")(Qt,W,me,ut,be,a,q)
+}function K(n){return"\\"+B[n]}function M(n){return we[n]}function U(n){return typeof n.toString!="function"&&typeof(n+"")=="string"}function V(n){this.__wrapped__=n}function G(){}function H(n){var t=!1;if(!n||Zt.call(n)!=I||!ve.argsClass&&W(n))return t;var e=n.constructor;return(tt(e)?e instanceof e:ve.nodeClass||!U(n))?ve.ownLast?(xe(n,function(n,e,r){return t=Qt.call(r,e),!1}),!0===t):(xe(n,function(n,e){t=e}),!1===t||Qt.call(n,t)):t}function J(n,t,e){t||(t=0),typeof e=="undefined"&&(e=n?n.length:0);
+var r=-1;e=e-t||0;for(var u=It(0>e?0:e);++r<e;)u[r]=n[t+r];return u}function Q(n){return Ce[n]}function W(n){return Zt.call(n)==k}function X(n,t,r,u,o,i){var f=n;if(typeof t=="function"&&(u=r,r=t,t=!1),typeof r=="function"){if(r=typeof u=="undefined"?r:a.createCallback(r,u,1),f=r(f),typeof f!="undefined")return f;f=n}if(u=et(f)){var l=Zt.call(f);if(!$[l]||!ve.nodeClass&&U(f))return f;var c=me(f)}if(!u||!t)return u?c?J(f):je({},f):f;switch(u=se[l],l){case O:case E:return new u(+f);case A:case N:return new u(f);
+case P:return u(f.source,y.exec(f))}for(o||(o=[]),i||(i=[]),l=o.length;l--;)if(o[l]==n)return i[l];return f=c?u(f.length):{},c&&(Qt.call(n,"index")&&(f.index=n.index),Qt.call(n,"input")&&(f.input=n.input)),o.push(n),i.push(f),(c?pt:Oe)(n,function(n,u){f[u]=X(n,t,r,e,o,i)}),f}function Y(n){var t=[];return xe(n,function(n,e){tt(n)&&t.push(e)}),t.sort()}function Z(n){for(var t=-1,e=be(n),r=e.length,u={};++t<r;){var a=e[t];u[n[a]]=a}return u}function nt(n,t,e,r,u,o){var f=e===i;if(typeof e=="function"&&!f){e=a.createCallback(e,r,2);
+var l=e(n,t);if(typeof l!="undefined")return!!l}if(n===t)return 0!==n||1/n==1/t;var c=typeof n,p=typeof t;if(n===n&&(!n||"function"!=c&&"object"!=c)&&(!t||"function"!=p&&"object"!=p))return!1;if(null==n||null==t)return n===t;if(p=Zt.call(n),c=Zt.call(t),p==k&&(p=I),c==k&&(c=I),p!=c)return!1;switch(p){case O:case E:return+n==+t;case A:return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case P:case N:return n==Tt(t)}if(c=p==x,!c){if(Qt.call(n,"__wrapped__")||Qt.call(t,"__wrapped__"))return nt(n.__wrapped__||n,t.__wrapped__||t,e,r,u,o);
+if(p!=I||!ve.nodeClass&&(U(n)||U(t)))return!1;var p=!ve.argsObject&&W(n)?Ft:n.constructor,s=!ve.argsObject&&W(t)?Ft:t.constructor;if(p!=s&&(!tt(p)||!(p instanceof p&&tt(s)&&s instanceof s)))return!1}for(u||(u=[]),o||(o=[]),p=u.length;p--;)if(u[p]==n)return o[p]==t;var v=0,l=!0;if(u.push(n),o.push(t),c){if(p=n.length,v=t.length,l=v==n.length,!l&&!f)return l;for(;v--;)if(c=p,s=t[v],f)for(;c--&&!(l=nt(n[c],s,e,r,u,o)););else if(!(l=nt(n[v],s,e,r,u,o)))break;return l}return xe(t,function(t,a,i){return Qt.call(i,a)?(v++,l=Qt.call(n,a)&&nt(n[a],t,e,r,u,o)):void 0
+}),l&&!f&&xe(n,function(n,t,e){return Qt.call(e,t)?l=-1<--v:void 0}),l}function tt(n){return typeof n=="function"}function et(n){return n?q[typeof n]:!1}function rt(n){return typeof n=="number"||Zt.call(n)==A}function ut(n){return typeof n=="string"||Zt.call(n)==N}function at(n,t,e){var r=arguments,u=0,o=2;if(!et(n))return n;if(e===i)var f=r[3],l=r[4],c=r[5];else l=[],c=[],typeof e!="number"&&(o=r.length),3<o&&"function"==typeof r[o-2]?f=a.createCallback(r[--o-1],r[o--],2):2<o&&"function"==typeof r[o-1]&&(f=r[--o]);
+for(;++u<o;)(me(r[u])?pt:Oe)(r[u],function(t,e){var r,u,a=t,o=n[e];if(t&&((u=me(t))||Ee(t))){for(a=l.length;a--;)if(r=l[a]==t){o=c[a];break}if(!r){var p;f&&(a=f(o,t),p=typeof a!="undefined")&&(o=a),p||(o=u?me(o)?o:[]:Ee(o)?o:{}),l.push(t),c.push(o),p||(o=at(o,t,i,f,l,c))}}else f&&(a=f(o,t),typeof a=="undefined"&&(a=t)),typeof a!="undefined"&&(o=a);n[e]=o});return n}function ot(n){for(var t=-1,e=be(n),r=e.length,u=It(r);++t<r;)u[t]=n[e[t]];return u}function it(n,t,e){var r=-1,u=n?n.length:0,a=!1;return e=(0>e?ae(0,u+e):e)||0,typeof u=="number"?a=-1<(ut(n)?n.indexOf(t,e):_t(n,t,e)):_e(n,function(n){return++r<e?void 0:!(a=n===t)
+}),a}function ft(n,t,e){var r=!0;if(t=a.createCallback(t,e),me(n)){e=-1;for(var u=n.length;++e<u&&(r=!!t(n[e],e,n)););}else _e(n,function(n,e,u){return r=!!t(n,e,u)});return r}function lt(n,t,e){var r=[];if(t=a.createCallback(t,e),me(n)){e=-1;for(var u=n.length;++e<u;){var o=n[e];t(o,e,n)&&r.push(o)}}else _e(n,function(n,e,u){t(n,e,u)&&r.push(n)});return r}function ct(n,t,e){if(t=a.createCallback(t,e),!me(n)){var r;return _e(n,function(n,e,u){return t(n,e,u)?(r=n,!1):void 0}),r}e=-1;for(var u=n.length;++e<u;){var o=n[e];
+if(t(o,e,n))return o}}function pt(n,t,e){if(t&&typeof e=="undefined"&&me(n)){e=-1;for(var r=n.length;++e<r&&!1!==t(n[e],e,n););}else _e(n,t,e);return n}function st(n,t,e){var r=-1,u=n?n.length:0,o=It(typeof u=="number"?u:0);if(t=a.createCallback(t,e),me(n))for(;++r<u;)o[r]=t(n[r],r,n);else _e(n,function(n,e,u){o[++r]=t(n,e,u)});return o}function vt(n,t,e){var r=-1/0,u=r;if(!t&&me(n)){e=-1;for(var o=n.length;++e<o;){var i=n[e];i>u&&(u=i)}}else t=!t&&ut(n)?T:a.createCallback(t,e),_e(n,function(n,e,a){e=t(n,e,a),e>r&&(r=e,u=n)
+});return u}function gt(n,t,e,r){var u=3>arguments.length;if(t=a.createCallback(t,r,4),me(n)){var o=-1,i=n.length;for(u&&(e=n[++o]);++o<i;)e=t(e,n[o],o,n)}else _e(n,function(n,r,a){e=u?(u=!1,n):t(e,n,r,a)});return e}function yt(n,t,e,r){var u=n,o=n?n.length:0,i=3>arguments.length;if(typeof o!="number")var f=be(n),o=f.length;else ve.unindexedChars&&ut(n)&&(u=n.split(""));return t=a.createCallback(t,r,4),pt(n,function(n,r,a){r=f?f[--o]:--o,e=i?(i=!1,u[r]):t(e,u[r],r,a)}),e}function ht(n,t,e){var r;
+if(t=a.createCallback(t,e),me(n)){e=-1;for(var u=n.length;++e<u&&!(r=t(n[e],e,n)););}else _e(n,function(n,e,u){return!(r=t(n,e,u))});return!!r}function mt(n){for(var t=-1,e=n?n.length:0,r=Gt.apply(zt,le.call(arguments,1)),r=R(r),u=[];++t<e;){var a=n[t];r(a)||u.push(a)}return u}function dt(n,t,e){if(n){var r=0,u=n.length;if(typeof t!="number"&&null!=t){var o=-1;for(t=a.createCallback(t,e);++o<u&&t(n[o],o,n);)r++}else if(r=t,null==r||e)return n[0];return J(n,0,oe(ae(0,r),u))}}function bt(n,t,e,r){var u=-1,o=n?n.length:0,i=[];
+for(typeof t!="boolean"&&null!=t&&(r=e,e=t,t=!1),null!=e&&(e=a.createCallback(e,r));++u<o;)r=n[u],e&&(r=e(r,u,n)),me(r)?Wt.apply(i,t?r:bt(r)):i.push(r);return i}function _t(n,t,e){var r=-1,u=n?n.length:0;if(typeof e=="number")r=(0>e?ae(0,u+e):e||0)-1;else if(e)return r=Ct(n,t),n[r]===t?r:-1;for(;++r<u;)if(n[r]===t)return r;return-1}function wt(n,t,e){if(typeof t!="number"&&null!=t){var r=0,u=-1,o=n?n.length:0;for(t=a.createCallback(t,e);++u<o&&t(n[u],u,n);)r++}else r=null==t||e?1:ae(0,t);return J(n,r)
+}function Ct(n,t,e,r){var u=0,o=n?n.length:u;for(e=e?a.createCallback(e,r,1):Et,t=e(t);u<o;)r=u+o>>>1,e(n[r])<t?u=r+1:o=r;return u}function jt(n,t,e,r){var u=-1,o=n?n.length:0,i=[],c=i;typeof t!="boolean"&&null!=t&&(r=e,e=t,t=!1);var p=!t&&o>=l;if(p)var s={};for(null!=e&&(c=[],e=a.createCallback(e,r));++u<o;){r=n[u];var v=e?e(r,u,n):r;if(p)var g=f+v,g=s[g]?!(c=s[g]):c=s[g]=[];(t?!u||c[c.length-1]!==v:g||0>_t(c,v))&&((e||p)&&c.push(v),i.push(r))}return i}function kt(n,t){for(var e=-1,r=n?n.length:0,u={};++e<r;){var a=n[e];
+t?u[a]=t[e]:u[a[0]]=a[1]}return u}function xt(n,t){return ve.fastBind||ne&&2<arguments.length?ne.call.apply(ne,arguments):z(n,t,le.call(arguments,2))}function Ot(n){var t=le.call(arguments,1);return Yt(function(){n.apply(e,t)},1)}function Et(n){return n}function St(n){pt(Y(n),function(t){var e=a[t]=n[t];a.prototype[t]=function(){var n=this.__wrapped__,t=[n];return Wt.apply(t,arguments),t=e.apply(a,t),n&&typeof n=="object"&&n==t?this:new V(t)}})}function At(){return this.__wrapped__}r=r?F.defaults(n.Object(),r,F.pick(n,C)):n;
+var It=r.Array,Pt=r.Boolean,Nt=r.Date,$t=r.Function,qt=r.Math,Bt=r.Number,Ft=r.Object,Rt=r.RegExp,Tt=r.String,Dt=r.TypeError,zt=It(),Lt=Ft(),Kt=r._,Mt=Rt("^"+Tt(Lt.valueOf).replace(/[.*+?^${}()|[\]\\]/g,"\\$&").replace(/valueOf|for [^\]]+/g,".+?")+"$"),Ut=qt.ceil,Vt=r.clearTimeout,Gt=zt.concat,Ht=qt.floor,Jt=Mt.test(Jt=Ft.getPrototypeOf)&&Jt,Qt=Lt.hasOwnProperty,Wt=zt.push,Xt=r.setImmediate,Yt=r.setTimeout,Zt=Lt.toString,ne=Mt.test(ne=Zt.bind)&&ne,te=Mt.test(te=It.isArray)&&te,ee=r.isFinite,re=r.isNaN,ue=Mt.test(ue=Ft.keys)&&ue,ae=qt.max,oe=qt.min,ie=r.parseInt,fe=qt.random,le=zt.slice,ce=Mt.test(r.attachEvent),pe=ne&&!/\n|true/.test(ne+ce),se={};
+se[x]=It,se[O]=Pt,se[E]=Nt,se[I]=Ft,se[A]=Bt,se[P]=Rt,se[N]=Tt;var ve=a.support={};(function(){var n=function(){this.x=1},t={0:1,length:1},e=[];n.prototype={valueOf:1,y:1};for(var r in new n)e.push(r);for(r in arguments);ve.argsObject=arguments.constructor==Ft&&!(arguments instanceof It),ve.argsClass=W(arguments),ve.enumPrototypes=n.propertyIsEnumerable("prototype"),ve.fastBind=ne&&!pe,ve.ownLast="x"!=e[0],ve.nonEnumArgs=0!=r,ve.nonEnumShadows=!/valueOf/.test(e),ve.spliceObjects=(zt.splice.call(t,0,1),!t[0]),ve.unindexedChars="xx"!="x"[0]+Ft("x")[0];
+try{ve.nodeClass=!(Zt.call(document)==I&&!({toString:0}+""))}catch(u){ve.nodeClass=!0}})(1),a.templateSettings={escape:/<%-([\s\S]+?)%>/g,evaluate:/<%([\s\S]+?)%>/g,interpolate:h,variable:"",imports:{_:a}};var ge={a:"q,w,g",h:"var a=arguments,b=0,c=typeof g=='number'?2:a.length;while(++b<c){m=a[b];if(m&&r[typeof m]){",f:"if(typeof u[i]=='undefined')u[i]=m[i]",c:"}}"},ye={a:"e,d,x",h:"d=d&&typeof x=='undefined'?d:p.createCallback(d,x)",b:"typeof n=='number'",f:"if(d(m[i],i,e)===false)return u"},he={h:"if(!r[typeof m])return u;"+ye.h,b:!1};
+V.prototype=a.prototype,ve.argsClass||(W=function(n){return n?Qt.call(n,"callee"):!1});var me=te||function(n){return n?typeof n=="object"&&Zt.call(n)==x:!1},de=L({a:"q",e:"[]",h:"if(!(r[typeof q]))return u",f:"u.push(i)",b:!1}),be=ue?function(n){return et(n)?ve.enumPrototypes&&typeof n=="function"||ve.nonEnumArgs&&n.length&&W(n)?de(n):ue(n):[]}:de,_e=L(ye),we={"&":"&","<":"<",">":">",'"':""","'":"'"},Ce=Z(we),je=L(ge,{h:ge.h.replace(";",";if(c>3&&typeof a[c-2]=='function'){var d=p.createCallback(a[--c-1],a[c--],2);}else if(c>2&&typeof a[c-1]=='function'){d=a[--c];}"),f:"u[i]=d?d(u[i],m[i]):m[i]"}),ke=L(ge),xe=L(ye,he,{i:!1}),Oe=L(ye,he);
+tt(/x/)&&(tt=function(n){return typeof n=="function"&&Zt.call(n)==S});var Ee=Jt?function(n){if(!n||Zt.call(n)!=I||!ve.argsClass&&W(n))return!1;var t=n.valueOf,e=typeof t=="function"&&(e=Jt(t))&&Jt(e);return e?n==e||Jt(n)==e:H(n)}:H;pe&&u&&typeof Xt=="function"&&(Ot=xt(Xt,r));var Se=8==ie(m+"08")?ie:function(n,t){return ie(ut(n)?n.replace(d,""):n,t||0)};return a.after=function(n,t){return 1>n?t():function(){return 1>--n?t.apply(this,arguments):void 0}},a.assign=je,a.at=function(n){var t=-1,e=Gt.apply(zt,le.call(arguments,1)),r=e.length,u=It(r);
+for(ve.unindexedChars&&ut(n)&&(n=n.split(""));++t<r;)u[t]=n[e[t]];return u},a.bind=xt,a.bindAll=function(n){for(var t=1<arguments.length?Gt.apply(zt,le.call(arguments,1)):Y(n),e=-1,r=t.length;++e<r;){var u=t[e];n[u]=xt(n[u],n)}return n},a.bindKey=function(n,t){return z(n,t,le.call(arguments,2),i)},a.compact=function(n){for(var t=-1,e=n?n.length:0,r=[];++t<e;){var u=n[t];u&&r.push(u)}return r},a.compose=function(){var n=arguments;return function(){for(var t=arguments,e=n.length;e--;)t=[n[e].apply(this,t)];
+return t[0]}},a.countBy=function(n,t,e){var r={};return t=a.createCallback(t,e),pt(n,function(n,e,u){e=Tt(t(n,e,u)),Qt.call(r,e)?r[e]++:r[e]=1}),r},a.createCallback=function(n,t,e){if(null==n)return Et;var r=typeof n;if("function"!=r){if("object"!=r)return function(t){return t[n]};var u=be(n);return function(t){for(var e=u.length,r=!1;e--&&(r=nt(t[u[e]],n[u[e]],i)););return r}}return typeof t!="undefined"?1===e?function(e){return n.call(t,e)}:2===e?function(e,r){return n.call(t,e,r)}:4===e?function(e,r,u,a){return n.call(t,e,r,u,a)
+}:function(e,r,u){return n.call(t,e,r,u)}:n},a.debounce=function(n,t,e){function r(){a=f=null,l&&(o=n.apply(i,u))}var u,a,o,i,f,l=!0;if(!0===e)var c=!0,l=!1;else e&&q[typeof e]&&(c=e.leading,l="trailing"in e?e.trailing:l);return function(){return u=arguments,i=this,Vt(f),!a&&c?(a=!0,o=n.apply(i,u)):f=Yt(r,t),o}},a.defaults=ke,a.defer=Ot,a.delay=function(n,t){var r=le.call(arguments,2);return Yt(function(){n.apply(e,r)},t)},a.difference=mt,a.filter=lt,a.flatten=bt,a.forEach=pt,a.forIn=xe,a.forOwn=Oe,a.functions=Y,a.groupBy=function(n,t,e){var r={};
+return t=a.createCallback(t,e),pt(n,function(n,e,u){e=Tt(t(n,e,u)),(Qt.call(r,e)?r[e]:r[e]=[]).push(n)}),r},a.initial=function(n,t,e){if(!n)return[];var r=0,u=n.length;if(typeof t!="number"&&null!=t){var o=u;for(t=a.createCallback(t,e);o--&&t(n[o],o,n);)r++}else r=null==t||e?1:t||r;return J(n,0,oe(ae(0,u-r),u))},a.intersection=function(n){var t=arguments,e=t.length,r={0:{}},u=-1,a=n?n.length:0,o=a>=l,i=[],c=i;n:for(;++u<a;){var p=n[u];if(o)var s=f+p,s=r[0][s]?!(c=r[0][s]):c=r[0][s]=[];if(s||0>_t(c,p)){o&&c.push(p);
+for(var v=e;--v;)if(!(r[v]||(r[v]=R(t[v])))(p))continue n;i.push(p)}}return i},a.invert=Z,a.invoke=function(n,t){var e=le.call(arguments,2),r=-1,u=typeof t=="function",a=n?n.length:0,o=It(typeof a=="number"?a:0);return pt(n,function(n){o[++r]=(u?t:n[t]).apply(n,e)}),o},a.keys=be,a.map=st,a.max=vt,a.memoize=function(n,t){var e={};return function(){var r=f+(t?t.apply(this,arguments):arguments[0]);return Qt.call(e,r)?e[r]:e[r]=n.apply(this,arguments)}},a.merge=at,a.min=function(n,t,e){var r=1/0,u=r;
+if(!t&&me(n)){e=-1;for(var o=n.length;++e<o;){var i=n[e];i<u&&(u=i)}}else t=!t&&ut(n)?T:a.createCallback(t,e),_e(n,function(n,e,a){e=t(n,e,a),e<r&&(r=e,u=n)});return u},a.omit=function(n,t,e){var r=typeof t=="function",u={};if(r)t=a.createCallback(t,e);else var o=Gt.apply(zt,le.call(arguments,1));return xe(n,function(n,e,a){(r?!t(n,e,a):0>_t(o,e))&&(u[e]=n)}),u},a.once=function(n){var t,e;return function(){return t?e:(t=!0,e=n.apply(this,arguments),n=null,e)}},a.pairs=function(n){for(var t=-1,e=be(n),r=e.length,u=It(r);++t<r;){var a=e[t];
+u[t]=[a,n[a]]}return u},a.partial=function(n){return z(n,le.call(arguments,1))},a.partialRight=function(n){return z(n,le.call(arguments,1),null,i)},a.pick=function(n,t,e){var r={};if(typeof t!="function")for(var u=-1,o=Gt.apply(zt,le.call(arguments,1)),i=et(n)?o.length:0;++u<i;){var f=o[u];f in n&&(r[f]=n[f])}else t=a.createCallback(t,e),xe(n,function(n,e,u){t(n,e,u)&&(r[e]=n)});return r},a.pluck=st,a.range=function(n,t,e){n=+n||0,e=+e||1,null==t&&(t=n,n=0);var r=-1;t=ae(0,Ut((t-n)/e));for(var u=It(t);++r<t;)u[r]=n,n+=e;
+return u},a.reject=function(n,t,e){return t=a.createCallback(t,e),lt(n,function(n,e,r){return!t(n,e,r)})},a.rest=wt,a.shuffle=function(n){var t=-1,e=n?n.length:0,r=It(typeof e=="number"?e:0);return pt(n,function(n){var e=Ht(fe()*(++t+1));r[t]=r[e],r[e]=n}),r},a.sortBy=function(n,t,e){var r=-1,u=n?n.length:0,o=It(typeof u=="number"?u:0);for(t=a.createCallback(t,e),pt(n,function(n,e,u){o[++r]={a:t(n,e,u),b:r,c:n}}),u=o.length,o.sort(D);u--;)o[u]=o[u].c;return o},a.tap=function(n,t){return t(n),n},a.throttle=function(n,t,e){function r(){i=null,c&&(f=new Nt,a=n.apply(o,u))
+}var u,a,o,i,f=0,l=!0,c=!0;return!1===e?l=!1:e&&q[typeof e]&&(l="leading"in e?e.leading:l,c="trailing"in e?e.trailing:c),function(){var e=new Nt;!i&&!l&&(f=e);var c=t-(e-f);return u=arguments,o=this,0<c?i||(i=Yt(r,c)):(Vt(i),i=null,f=e,a=n.apply(o,u)),a}},a.times=function(n,t,e){n=-1<(n=+n)?n:0;var r=-1,u=It(n);for(t=a.createCallback(t,e,1);++r<n;)u[r]=t(r);return u},a.toArray=function(n){return n&&typeof n.length=="number"?ve.unindexedChars&&ut(n)?n.split(""):J(n):ot(n)},a.union=function(n){return me(n)||(arguments[0]=n?le.call(n):zt),jt(Gt.apply(zt,arguments))
+},a.uniq=jt,a.unzip=function(n){for(var t=-1,e=n?n.length:0,r=e?vt(st(n,"length")):0,u=It(r);++t<e;)for(var a=-1,o=n[t];++a<r;)(u[a]||(u[a]=It(e)))[t]=o[a];return u},a.values=ot,a.where=lt,a.without=function(n){return mt(n,le.call(arguments,1))},a.wrap=function(n,t){return function(){var e=[n];return Wt.apply(e,arguments),t.apply(this,e)}},a.zip=function(n){for(var t=-1,e=n?vt(st(arguments,"length")):0,r=It(e);++t<e;)r[t]=st(arguments,t);return r},a.zipObject=kt,a.collect=st,a.drop=wt,a.each=pt,a.extend=je,a.methods=Y,a.object=kt,a.select=lt,a.tail=wt,a.unique=jt,St(a),a.clone=X,a.cloneDeep=function(n,t,e){return X(n,!0,t,e)
+},a.contains=it,a.escape=function(n){return null==n?"":Tt(n).replace(_,M)},a.every=ft,a.find=ct,a.findIndex=function(n,t,e){var r=-1,u=n?n.length:0;for(t=a.createCallback(t,e);++r<u;)if(t(n[r],r,n))return r;return-1},a.findKey=function(n,t,e){var r;return t=a.createCallback(t,e),Oe(n,function(n,e,u){return t(n,e,u)?(r=e,!1):void 0}),r},a.has=function(n,t){return n?Qt.call(n,t):!1},a.identity=Et,a.indexOf=_t,a.isArguments=W,a.isArray=me,a.isBoolean=function(n){return!0===n||!1===n||Zt.call(n)==O},a.isDate=function(n){return n?typeof n=="object"&&Zt.call(n)==E:!1
+},a.isElement=function(n){return n?1===n.nodeType:!1},a.isEmpty=function(n){var t=!0;if(!n)return t;var e=Zt.call(n),r=n.length;return e==x||e==N||(ve.argsClass?e==k:W(n))||e==I&&typeof r=="number"&&tt(n.splice)?!r:(Oe(n,function(){return t=!1}),t)},a.isEqual=nt,a.isFinite=function(n){return ee(n)&&!re(parseFloat(n))},a.isFunction=tt,a.isNaN=function(n){return rt(n)&&n!=+n},a.isNull=function(n){return null===n},a.isNumber=rt,a.isObject=et,a.isPlainObject=Ee,a.isRegExp=function(n){return n?q[typeof n]&&Zt.call(n)==P:!1
+},a.isString=ut,a.isUndefined=function(n){return typeof n=="undefined"},a.lastIndexOf=function(n,t,e){var r=n?n.length:0;for(typeof e=="number"&&(r=(0>e?ae(0,r+e):oe(e,r-1))+1);r--;)if(n[r]===t)return r;return-1},a.mixin=St,a.noConflict=function(){return r._=Kt,this},a.parseInt=Se,a.random=function(n,t){return null==n&&null==t&&(t=1),n=+n||0,null==t&&(t=n,n=0),n+Ht(fe()*((+t||0)-n+1))},a.reduce=gt,a.reduceRight=yt,a.result=function(n,t){var r=n?n[t]:e;return tt(r)?n[t]():r},a.runInContext=t,a.size=function(n){var t=n?n.length:0;
+return typeof t=="number"?t:be(n).length},a.some=ht,a.sortedIndex=Ct,a.template=function(n,t,r){var u=a.templateSettings;n||(n=""),r=ke({},r,u);var o,i=ke({},r.imports,u.imports),u=be(i),i=ot(i),f=0,l=r.interpolate||b,v="__p+='",l=Rt((r.escape||b).source+"|"+l.source+"|"+(l===h?g:b).source+"|"+(r.evaluate||b).source+"|$","g");n.replace(l,function(t,e,r,u,a,i){return r||(r=u),v+=n.slice(f,i).replace(w,K),e&&(v+="'+__e("+e+")+'"),a&&(o=!0,v+="';"+a+";__p+='"),r&&(v+="'+((__t=("+r+"))==null?'':__t)+'"),f=i+t.length,t
+}),v+="';\n",l=r=r.variable,l||(r="obj",v="with("+r+"){"+v+"}"),v=(o?v.replace(c,""):v).replace(p,"$1").replace(s,"$1;"),v="function("+r+"){"+(l?"":r+"||("+r+"={});")+"var __t,__p='',__e=_.escape"+(o?",__j=Array.prototype.join;function print(){__p+=__j.call(arguments,'')}":";")+v+"return __p}";try{var y=$t(u,"return "+v).apply(e,i)}catch(m){throw m.source=v,m}return t?y(t):(y.source=v,y)},a.unescape=function(n){return null==n?"":Tt(n).replace(v,Q)},a.uniqueId=function(n){var t=++o;return Tt(null==n?"":n)+t
+},a.all=ft,a.any=ht,a.detect=ct,a.foldl=gt,a.foldr=yt,a.include=it,a.inject=gt,Oe(a,function(n,t){a.prototype[t]||(a.prototype[t]=function(){var t=[this.__wrapped__];return Wt.apply(t,arguments),n.apply(a,t)})}),a.first=dt,a.last=function(n,t,e){if(n){var r=0,u=n.length;if(typeof t!="number"&&null!=t){var o=u;for(t=a.createCallback(t,e);o--&&t(n[o],o,n);)r++}else if(r=t,null==r||e)return n[u-1];return J(n,ae(0,u-r))}},a.take=dt,a.head=dt,Oe(a,function(n,t){a.prototype[t]||(a.prototype[t]=function(t,e){var r=n(this.__wrapped__,t,e);
+return null==t||e&&typeof t!="function"?r:new V(r)})}),a.VERSION="1.2.1",a.prototype.toString=function(){return Tt(this.__wrapped__)},a.prototype.value=At,a.prototype.valueOf=At,_e(["join","pop","shift"],function(n){var t=zt[n];a.prototype[n]=function(){return t.apply(this.__wrapped__,arguments)}}),_e(["push","reverse","sort","unshift"],function(n){var t=zt[n];a.prototype[n]=function(){return t.apply(this.__wrapped__,arguments),this}}),_e(["concat","slice","splice"],function(n){var t=zt[n];a.prototype[n]=function(){return new V(t.apply(this.__wrapped__,arguments))
+}}),ve.spliceObjects||_e(["pop","shift","splice"],function(n){var t=zt[n],e="splice"==n;a.prototype[n]=function(){var n=this.__wrapped__,r=t.apply(n,arguments);return 0===n.length&&delete n[0],e?new V(r):r}}),a}var e,r=typeof exports=="object"&&exports,u=typeof module=="object"&&module&&module.exports==r&&module,a=typeof global=="object"&&global;(a.global===a||a.window===a)&&(n=a);var o=0,i={},f=+new Date+"",l=200,c=/\b__p\+='';/g,p=/\b(__p\+=)''\+/g,s=/(__e\(.*?\)|\b__t\))\+'';/g,v=/&(?:amp|lt|gt|quot|#39);/g,g=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,y=/\w*$/,h=/<%=([\s\S]+?)%>/g,m=" \t\x0B\f\xa0\ufeff\n\r\u2028\u2029\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000",d=RegExp("^["+m+"]*0+(?=.$)"),b=/($^)/,_=/[&<>"']/g,w=/['\n\r\t\u2028\u2029\\]/g,C="Array Boolean Date Function Math Number Object RegExp String _ attachEvent clearTimeout isFinite isNaN parseInt setImmediate setTimeout".split(" "),j="constructor hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf".split(" "),k="[object Arguments]",x="[object Array]",O="[object Boolean]",E="[object Date]",S="[object Function]",A="[object Number]",I="[object Object]",P="[object RegExp]",N="[object String]",$={};
+$[S]=!1,$[k]=$[x]=$[O]=$[E]=$[A]=$[I]=$[P]=$[N]=!0;var q={"boolean":!1,"function":!0,object:!0,number:!1,string:!1,undefined:!1},B={"\\":"\\","'":"'","\n":"n","\r":"r","\t":"t","\u2028":"u2028","\u2029":"u2029"},F=t();typeof define=="function"&&typeof define.amd=="object"&&define.amd?(n._=F,define(function(){return F})):r&&!r.nodeType?u?(u.exports=F)._=F:r._=F:n._=F})(this);
\ No newline at end of file
var area = $("#kiwi #controlbox textarea");
for (var i = 0; files.length>i; i++) {
- area.val(area.val() + " " + files[i].url);
+ var f = files[i];
+ area.val(area.val() + " " + f.url + "+" + encodeURIComponent(f.filename));
}
}
control.addPluginIcon($icon);
});
-</script>
\ No newline at end of file
+</script>
--- /dev/null
+@echo off\r
+node %~dp0\server\server.js %*
\ No newline at end of file
"lodash": "0.9.1",\r
"daemonize2": "0.4.0-rc.5",\r
"eventemitter2": "0.4.11",\r
- "ipaddr.js": "0.1.1"\r
+ "ipaddr.js": "0.1.1",\r
+ "socksjs": "0.3.1"\r
}\r
}\r
topicsetby: onTopicSetBy,
mode: onMode
};
- EventBinder.bindIrcEvents('channel:' + this.name, this.irc_events, this, irc_connection);
+ EventBinder.bindIrcEvents('channel ' + this.name, this.irc_events, this, irc_connection);
}
IrcChannel.prototype.dispose = function (){
- EventBinder.unbindIrcEvents('channel:' + this.name, this.irc_events, this.irc_connection);
+ EventBinder.unbindIrcEvents('channel ' + this.name, this.irc_events, this.irc_connection);
this.irc_connection = undefined;
};
modes: event.modes
});
};
-
-
-/*
-server:event
-server:*
-channel:#channel:event
-channel:*:event
-user:event
-user:*
-
-Server disconnected:
- server:disconnect
- server:*
-
-Joining channel #kiwiirc:
- channel:#kiwiirc:join
- channel:*:join
-
-Channel message:
- channel:#kiwiirc:privmsg
- channel:*:privmsg
-
-Private message:
- user:privmsg
- user:*
-*/
\ No newline at end of file
var nick = command.params[0];
this.irc_connection.registered = true;
this.cap_negotation = false;
- this.irc_connection.emit('server:' + this.irc_connection.irc_host.hostname + ':connect', {
+ this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' connect', {
nick: nick
});
},
}
}
}
- this.irc_connection.emit('server:' + this.irc_connection.irc_host.hostname + ':options', {
+ this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' options', {
options: this.irc_connection.options,
cap: this.irc_connection.cap.enabled
});
},
'RPL_ENDOFWHOIS': function (command) {
- this.irc_connection.emit('user:' + command.params[1] + ':endofwhois', {
+ this.irc_connection.emit('user ' + command.params[1] + ' endofwhois', {
nick: command.params[1],
msg: command.trailing
});
},
'RPL_WHOISUSER': function (command) {
- this.irc_connection.emit('user:' + command.params[1] + ':whoisuser', {
+ this.irc_connection.emit('user ' + command.params[1] + ' whoisuser', {
nick: command.params[1],
ident: command.params[2],
host: command.params[3],
});
},
'RPL_WHOISSERVER': function (command) {
- this.irc_connection.emit('user:' + command.params[1] + ':whoisserver', {
+ this.irc_connection.emit('user ' + command.params[1] + ' whoisserver', {
nick: command.params[1],
irc_server: command.params[2]
});
},
'RPL_WHOISOPERATOR': function (command) {
- this.irc_connection.emit('user:' + command.params[1] + ':whoisoperator', {
+ this.irc_connection.emit('user ' + command.params[1] + ' whoisoperator', {
nick: command.params[1],
msg: command.trailing
});
},
'RPL_WHOISCHANNELS': function (command) {
- this.irc_connection.emit('user:' + command.params[1] + ':whoischannels', {
+ this.irc_connection.emit('user ' + command.params[1] + ' whoischannels', {
nick: command.params[1],
chans: command.trailing
});
},
'RPL_WHOISMODES': function (command) {
- this.irc_connection.emit('user:' + command.params[1] + ':whoismodes', {
+ this.irc_connection.emit('user ' + command.params[1] + ' whoismodes', {
nick: command.params[1],
msg: command.trailing
});
},
'RPL_WHOISIDLE': function (command) {
- this.irc_connection.emit('user:' + command.params[1] + ':whoisidle', {
+ this.irc_connection.emit('user ' + command.params[1] + ' whoisidle', {
nick: command.params[1],
idle: command.params[2],
logon: command.params[3] || undefined
});
},
'RPL_WHOISREGNICK': function (command) {
- this.irc_connection.emit('user:' + command.params[1] + ':whoisregnick', {
+ this.irc_connection.emit('user ' + command.params[1] + ' whoisregnick', {
nick: command.params[1],
msg: command.trailing
});
},
'RPL_LISTSTART': function (command) {
- this.irc_connection.emit('server:' + this.irc_connection.irc_host.hostname + ':list_start', {});
+ this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' list_start', {});
},
'RPL_LISTEND': function (command) {
- this.irc_connection.emit('server:' + this.irc_connection.irc_host.hostname + ':list_end', {});
+ this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' list_end', {});
},
'RPL_LIST': function (command) {
- this.irc_connection.emit('server:' + this.irc_connection.irc_host.hostname + ':list_channel', {
+ this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' list_channel', {
channel: command.params[1],
num_users: parseInt(command.params[2], 10),
topic: command.trailing
});
},
'RPL_MOTD': function (command) {
- this.irc_connection.emit('server:' + this.irc_connection.irc_host.hostname + ':motd', {
+ this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' motd', {
motd: command.trailing + '\n'
});
},
'RPL_MOTDSTART': function (command) {
- this.irc_connection.emit('server:' + this.irc_connection.irc_host.hostname + ':motd_start', {});
+ this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' motd_start', {});
},
'RPL_ENDOFMOTD': function (command) {
- this.irc_connection.emit('server:' + this.irc_connection.irc_host.hostname + ':motd_end', {});
+ this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' motd_end', {});
},
'RPL_NAMEREPLY': function (command) {
var members = command.trailing.split(' ');
member_list.push({nick: member, modes: modes});
});
- this.irc_connection.emit('channel:' + command.params[2] + ':userlist', {
+ this.irc_connection.emit('channel ' + command.params[2] + ' userlist', {
users: member_list,
channel: command.params[2]
});
'RPL_ENDOFNAMES': function (command) {
- this.irc_connection.emit('channel:' + command.params[1] + ':userlist_end', {
+ this.irc_connection.emit('channel ' + command.params[1] + ' userlist_end', {
channel: command.params[1]
});
},
'RPL_BANLIST': function (command) {
- this.irc_connection.emit('channel:' + command.params[1] + ':banlist', {
+ this.irc_connection.emit('channel ' + command.params[1] + ' banlist', {
channel: command.params[1],
banned: command.params[2],
banned_by: command.params[3],
});
},
'RPL_ENDOFBANLIST': function (command) {
- this.irc_connection.emit('channel:' + command.params[1] + ':banlist_end', {
+ this.irc_connection.emit('channel ' + command.params[1] + ' banlist_end', {
channel: command.params[1]
});
},
'RPL_TOPIC': function (command) {
- this.irc_connection.emit('channel:' + command.params[1] + ':topic', {
+ this.irc_connection.emit('channel ' + command.params[1] + ' topic', {
channel: command.params[1],
topic: command.trailing
});
},
'RPL_NOTOPIC': function (command) {
- this.irc_connection.emit('channel:' + command.params[1] + ':topic', {
+ this.irc_connection.emit('channel ' + command.params[1] + ' topic', {
channel: command.params[1],
topic: ''
});
},
'RPL_TOPICWHOTIME': function (command) {
- this.irc_connection.emit('channel:' + command.params[1] + ':topicsetby', {
+ this.irc_connection.emit('channel ' + command.params[1] + ' topicsetby', {
nick: command.params[2],
channel: command.params[1],
when: command.params[3]
channel = command.params[0];
}
- this.irc_connection.emit('channel:' + channel + ':join', {
+ this.irc_connection.emit('channel ' + channel + ' join', {
nick: command.nick,
ident: command.ident,
hostname: command.hostname,
'PART': function (command) {
- this.irc_connection.emit('channel:' + command.params[0] + ':part', {
+ this.irc_connection.emit('channel ' + command.params[0] + ' part', {
nick: command.nick,
ident: command.ident,
hostname: command.hostname,
'KICK': function (command) {
- this.irc_connection.emit('channel:' + command.params[0] + ':kick', {
+ this.irc_connection.emit('channel ' + command.params[0] + ' kick', {
kicked: command.params[1],
nick: command.nick,
ident: command.ident,
'QUIT': function (command) {
- this.irc_connection.emit('user:' + command.nick + ':quit', {
+ this.irc_connection.emit('user ' + command.nick + ' quit', {
nick: command.nick,
ident: command.ident,
hostname: command.hostname,
if ((command.trailing.charAt(0) === String.fromCharCode(1)) && (command.trailing.charAt(command.trailing.length - 1) === String.fromCharCode(1))) {
// It's a CTCP response
namespace = (command.params[0] == this.irc_connection.nick) ? 'user' : 'channel';
- this.irc_connection.emit(namespace + ':' + command.params[0] + ':ctcp_response', {
+ this.irc_connection.emit(namespace + ' ' + command.params[0] + ' ctcp_response', {
nick: command.nick,
ident: command.ident,
hostname: command.hostname,
});
} else {
namespace = (command.params[0] == this.irc_connection.nick) ? 'user' : 'channel';
- this.irc_connection.emit(namespace + ':' + command.params[0] + ':notice', {
+ this.irc_connection.emit(namespace + ' ' + command.params[0] + ' notice', {
nick: command.nick,
ident: command.ident,
hostname: command.hostname,
}
},
'NICK': function (command) {
- this.irc_connection.emit('user:' + command.nick + ':nick', {
+ this.irc_connection.emit('user ' + command.nick + ' nick', {
nick: command.nick,
ident: command.ident,
hostname: command.hostname,
var channel = command.params[0],
topic = command.trailing || '';
- this.irc_connection.emit('channel:' + channel + ':topic', {
+ this.irc_connection.emit('channel ' + channel + ' topic', {
nick: command.nick,
channel: channel,
topic: topic
});
},
- 'MODE': function (command) {
+ 'MODE': function (command) {
var chanmodes = this.irc_connection.options.CHANMODES || [],
prefixes = this.irc_connection.options.PREFIX || [],
always_param = (chanmodes[0] || '').concat((chanmodes[1] || '')),
}
}
- event = (_.contains(this.irc_connection.options.CHANTYPES, command.params[0][0]) ? 'channel:' : 'user:') + command.params[0] + ':mode';
+ event = (_.contains(this.irc_connection.options.CHANTYPES, command.params[0][0]) ? 'channel ' : 'user ') + command.params[0] + ' mode';
this.irc_connection.emit(event, {
target: command.params[0],
this.irc_connection.write('NOTICE ' + command.nick + ' :' + String.fromCharCode(1) + 'CLIENTINFO SOURCE VERSION TIME' + String.fromCharCode(1));
} else {
namespace = (command.params[0].toLowerCase() == this.irc_connection.nick.toLowerCase()) ? 'user' : 'channel';
- this.irc_connection.emit(namespace + ':' + command.nick + ':ctcp_request', {
+ this.irc_connection.emit(namespace + ' ' + command.nick + ' ctcp_request', {
nick: command.nick,
ident: command.ident,
hostname: command.hostname,
}
} else {
// A message to a user (private message) or to a channel?
- namespace = (command.params[0] === this.irc_connection.nick) ? 'user:' + command.nick : 'channel:' + command.params[0];
- this.irc_connection.emit(namespace + ':privmsg', {
+ namespace = (command.params[0] === this.irc_connection.nick) ? 'user ' + command.nick : 'channel ' + command.params[0];
+ this.irc_connection.emit(namespace + ' privmsg', {
nick: command.nick,
ident: command.ident,
hostname: command.hostname,
// i.e. - for disable, ~ for requires ACK, = for sticky
var capabilities = command.trailing.replace(/[\-~=]/, '').split(' ');
var request;
+
+ // Which capabilities we want to enable
var want = ['multi-prefix', 'away-notify'];
-
+
if (this.irc_connection.password) {
want.push('sasl');
}
-
+
switch (command.params[1]) {
case 'LS':
+ // Compute which of the available capabilities we want and request them
request = _.intersection(capabilities, want);
if (request.length > 0) {
this.irc_connection.cap.requested = request;
break;
case 'ACK':
if (capabilities.length > 0) {
+ // Update list of enabled capabilities
this.irc_connection.cap.enabled = capabilities;
+ // Update list of capabilities we would like to have but that aren't enabled
this.irc_connection.cap.requested = _.difference(this.irc_connection.cap.requested, capabilities);
}
- if (this.irc_connection.cap.requested.length > 0) {
+ if (this.irc_connection.cap.enabled.length > 0) {
if (_.contains(this.irc_connection.cap.enabled, 'sasl')) {
this.irc_connection.sasl = true;
this.irc_connection.write('AUTHENTICATE PLAIN');
}
},
'AWAY': function (command) {
- this.irc_connection.emit('user:' + command.nick + ':away', {
+ this.irc_connection.emit('user ' + command.nick + ' away', {
nick: command.nick,
msg: command.trailing
});
// noop
},
'ERROR': function (command) {
- this.irc_connection.emit('server:' + this.irc_connection.irc_host.hostname + ':error', {
+ this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' error', {
reason: command.trailing
});
},
ERR_PASSWDMISMATCH: function (command) {
- this.irc_connection.emit('server:' + this.irc_connection.irc_host.hostname + ':password_mismatch', {});
+ this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' password_mismatch', {});
},
ERR_LINKCHANNEL: function (command) {
- this.irc_connection.emit('server:' + this.irc_connection.irc_host.hostname + ':channel_redirect', {
+ this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' channel_redirect', {
from: command.params[1],
to: command.params[2]
});
},
ERR_NOSUCHNICK: function (command) {
- this.irc_connection.emit('server:' + this.irc_connection.irc_host.hostname + ':no_such_nick', {
+ this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' no_such_nick', {
nick: command.params[1],
reason: command.trailing
});
},
ERR_CANNOTSENDTOCHAN: function (command) {
- this.irc_connection.emit('server:' + this.irc_connection.irc_host.hostname + ':cannot_send_to_chan', {
+ this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' cannot_send_to_chan', {
channel: command.params[1],
reason: command.trailing
});
},
ERR_TOOMANYCHANNELS: function (command) {
- this.irc_connection.emit('server:' + this.irc_connection.irc_host.hostname + ':too_many_channels', {
+ this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' too_many_channels', {
channel: command.params[1],
reason: command.trailing
});
},
ERR_USERNOTINCHANNEL: function (command) {
- this.irc_connection.emit('server:' + this.irc_connection.irc_host.hostname + ':user_not_in_channel', {
+ this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' user_not_in_channel', {
nick: command.params[0],
channel: command.params[1],
reason: command.trailing
});
},
ERR_NOTONCHANNEL: function (command) {
- this.irc_connection.emit('server:' + this.irc_connection.irc_host.hostname + ':not_on_channel', {
+ this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' not_on_channel', {
channel: command.params[1],
reason: command.trailing
});
},
ERR_CHANNELISFULL: function (command) {
- this.irc_connection.emit('server:' + this.irc_connection.irc_host.hostname + ':channel_is_full', {
+ this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' channel_is_full', {
channel: command.params[1],
reason: command.trailing
});
},
ERR_INVITEONLYCHAN: function (command) {
- this.irc_connection.emit('server:' + this.irc_connection.irc_host.hostname + ':invite_only_channel', {
+ this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' invite_only_channel', {
channel: command.params[1],
reason: command.trailing
});
},
ERR_BANNEDFROMCHAN: function (command) {
- this.irc_connection.emit('server:' + this.irc_connection.irc_host.hostname + ':banned_from_channel', {
+ this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' banned_from_channel', {
channel: command.params[1],
reason: command.trailing
});
},
ERR_BADCHANNELKEY: function (command) {
- this.irc_connection.emit('server:' + this.irc_connection.irc_host.hostname + ':bad_channel_key', {
+ this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' bad_channel_key', {
channel: command.params[1],
reason: command.trailing
});
},
ERR_CHANOPRIVSNEEDED: function (command) {
- this.irc_connection.emit('server:' + this.irc_connection.irc_host.hostname + ':chanop_privs_needed', {
+ this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' chanop_privs_needed', {
channel: command.params[1],
reason: command.trailing
});
},
ERR_NICKNAMEINUSE: function (command) {
- this.irc_connection.emit('server:' + this.irc_connection.irc_host.hostname + ':nickname_in_use', {
+ this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' nickname_in_use', {
nick: command.params[1],
reason: command.trailing
});
// If we have a suitable Nodejs version, bring int he socks functionality
if (version_values[1] >= 10) {
- Socks = require('../socks.js');
+ Socks = require('socksjs');
}
var IrcConnection = function (hostname, port, ssl, nick, user, pass, state) {
EventEmitter2.call(this,{
wildcard: true,
- delimiter: ':'
+ delimiter: ' '
});
this.setMaxListeners(0);
// IrcUser objects
this.irc_users = Object.create(null);
-
+
+ // TODO: use `this.nick` instead of `'*'` when using an IrcUser per nick
+ this.irc_users[this.nick] = new IrcUser(this, '*');
+
// IrcChannel objects
this.irc_channels = Object.create(null);
this.applyIrcEvents();
// Call any modules before making the connection
- global.modules.emit('irc:connecting', {connection: this})
+ global.modules.emit('irc connecting', {connection: this})
.done(function () {
that.connect();
});
IrcConnection.prototype.applyIrcEvents = function () {
// Listen for events on the IRC connection
this.irc_events = {
- 'server:*:connect': onServerConnect,
- 'channel:*:join': onChannelJoin,
+ 'server * connect': onServerConnect,
+ 'channel * join': onChannelJoin,
// TODO: uncomment when using an IrcUser per nick
//'user:*:privmsg': onUserPrivmsg,
- 'user:*:nick': onUserNick,
- 'channel:*:part': onUserParts,
- 'channel:*:quit': onUserParts,
- 'channel:*:kick': onUserParts
+ 'user * nick': onUserNick,
+ 'channel * part': onUserParts,
+ 'channel * quit': onUserParts,
+ 'channel * kick': onUserKick
};
EventBinder.bindIrcEvents('', this.irc_events, this, this);
function onServerConnect(event) {
this.nick = event.nick;
-
- // TODO: use `event.nick` instead of `'*'` when using an IrcUser per nick
- this.irc_users[event.nick] = new IrcUser(this, '*');
}
}
}
+function onUserKick(event){
+ // Only deal with ourselves being kicked from a channel
+ if (event.kicked !== this.nick)
+ return;
+
+ if (this.irc_channels[event.channel]) {
+ this.irc_channels[event.channel].dispose();
+ delete this.irc_channels[event.channel];
+ }
+
+}
+
// Let the webirc/etc detection modify any required parameters
connect_data = findWebIrc.call(this, connect_data);
- global.modules.emit('irc:authorize', connect_data).done(function () {
+ global.modules.emit('irc authorize', connect_data).done(function () {
// Send any initial data for webirc/etc
if (connect_data.prepend_data) {
_.each(connect_data.prepend_data, function(data) {
module.exports.bindIrcEvents = function (events_scope, event_map, context, irc_connection) {
var namespace_prefix = events_scope ?
- events_scope + ':' :
+ events_scope + ' ' :
'';
// Make sure we have a holder for the bound events
module.exports.unbindIrcEvents = function (events_scope, event_map, irc_connection) {
var namespace_prefix = events_scope ?
- events_scope + ':' :
+ events_scope + ' ' :
'';
// No bound events? Then we have nothing to do
chanop_privs_needed: onChanopPrivsNeeded,
nickname_in_use: onNicknameInUse
};
- EventBinder.bindIrcEvents('server:*', this.irc_events, this, this.irc_connection);
+ EventBinder.bindIrcEvents('server *', this.irc_events, this, this.irc_connection);
};
IrcServer.prototype.dispose = function (){
- EventBinder.unbindIrcEvents('server:*', this.irc_events, this.irc_connection);
+ EventBinder.unbindIrcEvents('server *', this.irc_events, this.irc_connection);
this.irc_connection = undefined;
};
ctcp_request: onCtcpRequest,\r
mode: onMode\r
};\r
- EventBinder.bindIrcEvents('user:' + this.nick, this.irc_events, this, irc_connection);\r
+ EventBinder.bindIrcEvents('user ' + this.nick, this.irc_events, this, irc_connection);\r
};\r
\r
\r
\r
\r
IrcUser.prototype.dispose = function () {\r
- EventBinder.unbindIrcEvents('user:' + this.nick, this.irc_events, this.irc_connection);\r
+ EventBinder.unbindIrcEvents('user ' + this.nick, this.irc_events, this.irc_connection);\r
this.irc_connection = undefined;\r
};\r
\r
});\r
\r
// TODO: uncomment when using an IrcUser per nick\r
- //EventBinder.unbindIrcEvents('user:' + this.nick, this.irc_events, irc_connection);\r
+ //EventBinder.unbindIrcEvents('user ' + this.nick, this.irc_events, irc_connection);\r
//this.nick = event.newnick;\r
- //EventBinder.bindIrcEvents('user:' + this.nick, this.irc_events, this, irc_connection);\r
+ //EventBinder.bindIrcEvents('user ' + this.nick, this.irc_events, this, irc_connection);\r
};\r
\r
function onAway(event) {\r
clients.remove(client);
});
- wl.on('listening', webListenerRunning);
+ wl.on('listening', function () {
+ console.log('Listening on %s:%s %s SSL', server.address, server.port, (server.ssl ? 'with' : 'without'));
+ webListenerRunning();
+ });
+
+ wl.on('error', function (err) {
+ console.log('Error listening on %s:%s: %s', server.address, server.port, err.code);
+ // TODO: This should probably be refactored. ^JA
+ webListenerRunning();
+ });
});
// Once all the listeners are listening, set the processes UID/GID
+++ /dev/null
-var stream = require('stream'),
- util = require('util'),
- net = require('net'),
- tls = require('tls'),
- _ = require('lodash'),
- ipaddr = require('ipaddr.js');
-
-var SocksConnection = function (remote_options, socks_options) {
- var that = this;
- stream.Duplex.call(this);
-
- this.remote_options = _.defaults(remote_options, {
- host: 'localhost',
- ssl: false,
- rejectUnauthorized: false
- });
- socks_options = _.defaults(socks_options, {
- host: 'localhost',
- port: 1080,
- user: null,
- pass: null
- });
-
- this.socksAddress = null;
- this.socksPort = null;
-
- this.socksSocket = net.connect({host: socks_options.host, port: socks_options.port}, socksConnected.bind(this, !(!socks_options.user)));
- this.socksSocket.once('data', socksAuth.bind(this, {user: socks_options.user, pass: socks_options.pass}));
- this.socksSocket.on('error', function (err) {
- that.emit('error', err);
- });
-
- this.outSocket = this.socksSocket;
-};
-
-util.inherits(SocksConnection, stream.Duplex);
-
-SocksConnection.connect = function (remote_options, socks_options, connection_listener) {
- var socks_connection = new SocksConnection(remote_options, socks_options);
- if (typeof connection_listener === 'Function') {
- socks_connection.on('connect', connection_listener);
- }
- return socks_connection;
-};
-
-SocksConnection.prototype._read = function () {
- this.outSocket.resume();
-};
-
-SocksConnection.prototype._write = function (chunk, encoding, callback) {
- this.outSocket.write(chunk, 'utf8', callback);
-};
-
-SocksConnection.prototype.dispose = function () {
- this.outSocket.destroy();
- this.outSocket.removeAllListeners();
- if (this.outSocket !== this.socksSocket) {
- this.socksSocket.destroy();
- this.socksSocket.removeAllListeners();
- }
- this.removeAllListeners();
-};
-
-var socksConnected = function (auth) {
- if (auth) {
- this.socksSocket.write('\x05\x02\x02\x00'); // SOCKS version 5, supporting two auth methods
- // username/password and 'no authentication'
- } else {
- this.socksSocket.write('\x05\x01\x00'); // SOCKS version 5, only supporting 'no auth' scheme
- }
-};
-
-var socksAuth = function (auth, data) {
- var bufs = [];
- switch (data.readUInt8(1)) {
- case 255:
- this.emit('error', 'SOCKS: No acceptable authentication methods');
- this.socksSocket.destroy();
- break;
- case 2:
- bufs[0] = new Buffer([1]);
- bufs[1] = new Buffer([Buffer.byteLength(auth.user)]);
- bufs[2] = new Buffer(auth.user);
- bufs[3] = new Buffer([Buffer.byteLength(auth.pass)]);
- bufs[4] = new Buffer(auth.pass);
- this.socksSocket.write(Buffer.concat(bufs));
- this.socksSocket.once('data', socksAuthStatus.bind(this));
- break;
- default:
- socksRequest.call(this, this.remote_options.host, this.remote_options.port);
- }
-};
-
-var socksAuthStatus = function (data) {
- if (data.readUInt8(1) === 1) {
- socksRequest.call(this, this.remote_options.host, this.remote_options.port);
- } else {
- this.emit('error', 'SOCKS: Authentication failed');
- this.socksSocket.destroy();
- }
-};
-
-var socksRequest = function (host, port) {
- var header, type, hostBuf, portBuf;
- if (net.isIP(host)) {
- if (net.isIPv4(host)) {
- type = new Buffer([1]);
- } else if (net.isIPv6(host)) {
- type = new Buffer([4]);
- }
- host = new Buffer(ipaddr.parse(host).toByteArray());
- } else {
- type = new Buffer([3]);
- hostBuf = new Buffer(host);
- hostBuf = Buffer.concat([new Buffer([Buffer.byteLength(host)]), hostBuf]);
- }
- header = new Buffer([5, 1, 0]);
- portBuf = new Buffer(2);
- portBuf.writeUInt16BE(port, 0);
- this.socksSocket.write(Buffer.concat([header, type, hostBuf, portBuf]));
- this.socksSocket.once('data', socksReply.bind(this));
-};
-
-var socksReply = function (data) {
- var err, port, i, addr_len, addr = '';
- var status = data.readUInt8(1);
- if (status === 0) {
- switch (data.readUInt8(3)) {
- case 1:
- for (i = 0; i < 4; i++) {
- if (i !== 0) {
- addr += '.';
- }
- addr += data.readUInt8(4 + i);
- }
- port = data.readUInt16BE(8);
- break;
- case 4:
- for (i = 0; i < 16; i++) {
- if (i !== 0) {
- addr += ':';
- }
- addr += data.readUInt8(4 + i);
- }
- port = data.readUInt16BE(20);
- break;
- case 3:
- addr_len = data.readUInt8(4);
- addr = (data.slice(5, 5 + addr_len)).toString();
- port = data.readUInt16BE(5 + addr_len);
- }
- this.socksAddress = addr;
- this.socksPort = port;
-
- if (this.remote_options.ssl) {
- startTLS.call(this);
- } else {
- proxyData.call(this);
- this.emit('connect');
- }
-
- } else {
- switch (status) {
- case 1:
- err = 'SOCKS: general SOCKS server failure';
- break;
- case 2:
- err = 'SOCKS: Connection not allowed by ruleset';
- break;
- case 3:
- err = 'SOCKS: Network unreachable';
- break;
- case 4:
- err = 'SOCKS: Host unreachable';
- break;
- case 5:
- err = 'SOCKS: Connection refused';
- break;
- case 6:
- err = 'SOCKS: TTL expired';
- break;
- case 7:
- err = 'SOCKS: Command not supported';
- break;
- case 8:
- err = 'SOCKS: Address type not supported';
- break;
- default:
- err = 'SOCKS: Unknown error';
- }
- this.emit('error', err);
- }
-};
-
-var startTLS = function () {
- var that = this;
- var plaintext = tls.connect({
- socket: this.socksSocket,
- rejectUnauthorized: this.remote_options.rejectUnauthorized
- });
-
- plaintext.on('error', function (err) {
- that.emit('error', err);
- });
-
- plaintext.on('secureConnect', function () {
- that.emit('connect');
- });
- this.outSocket = plaintext;
- proxyData.call(this);
-};
-
-var proxyData = function () {
- var that = this;
-
- this.outSocket.on('data', function (data) {
- var buffer_not_full = that.push(data);
- if (!buffer_not_full) {
- this.pause();
- }
- });
-
- this.outSocket.on('end', function () {
- that.push(null);
- });
-};
-
-module.exports = SocksConnection;
if (web_config.ssl) {
opts = {
- key: fs.readFileSync(__dirname + '/' + web_config.ssl_key),
- cert: fs.readFileSync(__dirname + '/' + web_config.ssl_cert)
+ key: fs.readFileSync(web_config.ssl_key),
+ cert: fs.readFileSync(web_config.ssl_cert)
};
// Do we have an intermediate certificate?
if (typeof web_config.ssl_ca !== 'undefined') {
- opts.ca = fs.readFileSync(__dirname + '/' + web_config.ssl_ca);
- }
+ // An array of them?
+ if (typeof web_config.ssl_ca.map !== 'undefined') {
+ opts.ca = web_config.ssl_ca.map(function (f) { return fs.readFileSync(f); });
+ } else {
+ opts.ca = fs.readFileSync(web_config.ssl_ca);
+ }
+ }
hs = https.createServer(opts, handleHttpRequest);
hs.listen(web_config.port, web_config.address, function () {
that.emit('listening');
});
-
- console.log('Listening on ' + web_config.address + ':' + web_config.port.toString() + ' with SSL');
} else {
// Start some plain-text server up
hs.listen(web_config.port, web_config.address, function () {
that.emit('listening');
});
-
- console.log('Listening on ' + web_config.address + ':' + web_config.port.toString() + ' without SSL');
}
+
+ hs.on('error', function (err) {
+ that.emit('error', err);
+ })
this.ws.enable('browser client minification');
this.ws.enable('browser client etag');
return callback(null, false);
}
}
-
- dns.reverse(address, function (err, domains) {
- if (err || domains.length === 0) {
- handshakeData.revdns = address;
- } else {
- handshakeData.revdns = _.first(domains) || address;
- }
-
- // All is well, authorise the connection
+
+
+ try {
+ dns.reverse(address, function (err, domains) {
+ if (err || domains.length === 0) {
+ handshakeData.revdns = address;
+ } else {
+ handshakeData.revdns = _.first(domains) || address;
+ }
+
+ // All is well, authorise the connection
+ callback(null, true);
+ });
+ } catch (err) {
+ handshakeData.revdns = address;
callback(null, true);
- });
+ }
}
function newConnection(websocket) {
-module.exports = WebListener;
\ No newline at end of file
+module.exports = WebListener;
var module = new kiwiModules.Module('Example Module');
-module.on('client:connected', function(event, data) {
+module.on('client connected', function(event, data) {
console.log('Client connection:', data);
});
-module.on('client:commands:msg', function(event, data) {
+module.on('client commands msg', function(event, data) {
console.log('Client msg:', data.args.target, ': ', data.args.msg);
data.args.msg += ' - modified!';
});
\ No newline at end of file