Commit e71fcc06 authored by 神楽坂玲奈's avatar 神楽坂玲奈

candy fix

parent 4c4ca345
...@@ -42,7 +42,7 @@ $(document).ready -> ...@@ -42,7 +42,7 @@ $(document).ready ->
language: 'cn' language: 'cn'
) )
Candy.Core.connect('zh99998测试80@my-card.in', 'zh112998') if window.location.href.indexOf("candy") != -1 Candy.Core.connect('zh99998测试80@my-card.in', 'zh112998') if window.location.href.indexOf("candy") != -1
$('#candy').show() #$('#candy').show()
#$('#username').val '@my-card.in' #$('#username').val '@my-card.in'
#$('#username').focus() #$('#username').focus()
......
docs
example/.htaccess
.DS_Store
._*
.ndproj
.idea
[submodule "libs/jquery-i18n"]
path = libs/jquery-i18n
url = git://github.com/mweibel/jquery-i18n.git
[submodule "libs/strophejs"]
path = libs/strophejs
url = git://github.com/candy-chat/strophejs.git
[submodule "libs/strophejs-plugins"]
path = libs/strophejs-plugins
url = git://github.com/metajack/strophejs-plugins.git
[submodule "libs/mustache.js"]
path = libs/mustache.js
url = git://github.com/janl/mustache.js.git
Credits
=======
- [famfamfam silk icons](http://www.famfamfam.com/lab/icons/silk/) is a smooth, free icon set, containing over 700 16-by-16 pixel icons.
- [Simple Smileys](http://simplesmileys.org) are beautifully simple emoticons.
- [Flash MP3 Player](http://flash-mp3-player.net/players/js) is a very simple flash audio player used by Candy for audio notifications.
- [Colin Snover](http://zetafleet.com/blog/javascript-dateparse-for-iso-8601) provides a fix for browsers not supporting latest Date.parse().
- [Ben Cherry](http://www.adequatelygood.com/2010/3/JavaScript-Module-Pattern-In-Depth) wrote a great article about the JS module pattern.
- [Amiado Group](http://www.amiadogroup.com) allowed us to make Candy freely available for everyone! :)
\ No newline at end of file
Copyright (c) 2011 Amiado Group AG
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# Makefile for Candy
# Candy - Chats are not dead yet
#
# Copyright:
# (c) 2011 Amiado Group AG
#
# Authors:
# - Patrick Stadler <patrick.stadler@gmail.com>
# - Michael Weibel <michael.weibel@gmail.com>
#
SHELL=/bin/bash
DOC_DIR = docs
NDPROJ_DIR = .ndproj
SRC_DIR = src
LIBS_DIR = libs
CANDY_BUNDLE = candy.bundle.js
CANDY_BUNDLE_MIN = candy.min.js
CANDY_BUNDLE_LIBRARIES = libs/libs.bundle.js
CANDY_BUNDLE_LIBRARIES_MIN = libs/libs.min.js
CANDY_FILES = $(SRC_DIR)/candy.js $(SRC_DIR)/core.js $(SRC_DIR)/view.js $(SRC_DIR)/util.js $(SRC_DIR)/core/action.js $(SRC_DIR)/core/chatRoom.js $(SRC_DIR)/core/chatRoster.js $(SRC_DIR)/core/chatUser.js $(SRC_DIR)/core/event.js $(SRC_DIR)/view/event.js $(SRC_DIR)/view/observer.js $(SRC_DIR)/view/pane.js $(SRC_DIR)/view/template.js $(SRC_DIR)/view/translation.js
CANDY_LIBS_FILES = $(LIBS_DIR)/strophejs/strophe.js $(LIBS_DIR)/strophejs-plugins/muc/strophe.muc.js $(LIBS_DIR)/mustache.js/mustache.js $(LIBS_DIR)/jquery-i18n/jquery.i18n.js $(LIBS_DIR)/dateformat/dateFormat.js
CANDY_FILES_BUNDLE = $(CANDY_FILES:.js=.bundle)
CANDY_LIBS_FILES_BUNDLE = $(CANDY_LIBS_FILES:.js=.libs-bundle)
all: bundle min
bundle: clean-bundle $(CANDY_FILES_BUNDLE)
%.bundle: %.js
@@echo -n "Bundling" $< "..."
@@cat $< >> $(CANDY_BUNDLE)
@@echo "done"
min: $(CANDY_BUNDLE)
@@echo -n "Compressing" $(CANDY_BUNDLE) "..."
ifdef YUI_COMPRESSOR
@@java -jar $(YUI_COMPRESSOR) --type js $(CANDY_BUNDLE) -o $(CANDY_BUNDLE_MIN) --charset utf-8
@@echo "done ("$(CANDY_BUNDLE_MIN)")"
else
@@echo "aborted"
@@echo "** You can safely use the uncompressed bundle ("$(CANDY_BUNDLE)")"
@@echo "** YUI Compressor is required to build the minified version."
@@echo "** Please set YUI_COMPRESSOR to the path to the jar file."
endif
libs: libs-bundle libs-min
libs-bundle: clean-libs $(CANDY_LIBS_FILES_BUNDLE)
%.libs-bundle: %.js
@@echo -n "Bundling" $< "..."
@@cat $< >> $(CANDY_BUNDLE_LIBRARIES)
@@echo "done"
libs-min: $(CANDY_BUNDLE_LIBRARIES)
@@echo -n "Compressing" $(CANDY_BUNDLE_LIBRARIES) "..."
ifdef YUI_COMPRESSOR
@@java -jar $(YUI_COMPRESSOR) --type js $(CANDY_BUNDLE_LIBRARIES) -o $(CANDY_BUNDLE_LIBRARIES_MIN) --charset utf-8
@@echo "done ("$(CANDY_BUNDLE_LIBRARIES_MIN)")"
else
@@echo "aborted"
@@echo "** You can safely use the uncompressed bundle ("$(CANDY_BUNDLE_LIBRARIES)")"
@@echo "** YUI Compressor is required to build the minified version."
@@echo "** Please set YUI_COMPRESSOR to the path to the jar file."
endif
docs:
@@echo "Building candy documentation ..."
ifdef NATURALDOCS_DIR
@@if [ ! -d $(NDPROJ_DIR) ]; then mkdir $(NDPROJ_DIR); fi
@@if [ ! -d $(DOC_DIR) ]; then mkdir $(DOC_DIR); fi
@@$(NATURALDOCS_DIR)/NaturalDocs -q --exclude-source libs --exclude-source res --exclude-source candy.min.js --exclude-source candy.bundle.js -i . -o html $(DOC_DIR) -p $(NDPROJ_DIR)
@@rm -r $(NDPROJ_DIR)
@@echo "Documentation built."
@@echo
else
@@echo "aborted"
@@echo "** NaturalDocs is required to build the documentation."
@@echo "** Please set NATURALDOCS_DIR to the path to the NaturalDocs executable"
endif
clean: clean-bundle clean-libs
clean-bundle:
@@echo -n "Cleaning bundles ..."
@@rm -f $(CANDY_BUNDLE) $(CANDY_BUNDLE_MIN)
@@echo "done"
clean-libs:
@@echo -n "Cleaning library bundles ..."
@@rm -f $(CANDY_BUNDLE_LIBRARIES) $(CANDY_BUNDLE_LIBRARIES_MIN)
@@echo "done"
clean-docs:
@@echo -n "Cleaning documentation ..."
@@rm -rf $(NDPROJ_DIR) $(DOC_DIR)
@@echo "done"
.PHONY: all docs clean libs
Candy — a JavaScript-based multi-user chat client
==================================================
Visit the official project page: http://candy-chat.github.com/candy
Features
--------
- Focused on real-time multi-user chatting
- Easy to configure, easy to run, easy to use
- Highly customizable
- 100% well-documented JavaScript source code
- Built for Jabber (XMPP), using famous technologies
- Used and approved in a productive environment with up to 400 concurrent users
- Works with all major web browsers including IE7
Plugins
-------
If you wish to add new functionality (to your candy installation) or contribute plugins, take a look at our [plugin repository](http://github.com/candy-chat/candy-plugins).
Support & Community
-------------------
Take a look at our [FAQ](https://github.com/candy-chat/candy/wiki/Frequently-Asked-Questions). If it doesn't solve your questions, you're welcome to join our [Mailinglist on Google Groups](http://groups.google.com/group/candy-chat).
You don't need to have a Gmail account for it.
This source diff could not be displayed because it is too large. You can view the blob instead.
var Candy=(function(a,b){a.about={name:"Candy",version:"1.0.9"};a.init=function(c,d){a.View.init(b("#candy"),d.view);a.Core.init(c,d.core)};return a}(Candy||{},jQuery));Candy.Core=(function(l,e,f){var d=null,j=null,a=null,g={},c=false,k={autojoin:true,debug:false},b=function(m,n){e.addNamespace(m,n)},h=function(){b("PRIVATE","jabber:iq:private");b("BOOKMARKS","storage:bookmarks");b("PRIVACY","jabber:iq:privacy");b("DELAY","jabber:x:delay")},i=function(){l.addHandler(l.Event.Jabber.Version,e.NS.VERSION,"iq");l.addHandler(l.Event.Jabber.Presence,null,"presence");l.addHandler(l.Event.Jabber.Message,null,"message");l.addHandler(l.Event.Jabber.Bookmarks,e.NS.PRIVATE,"iq");l.addHandler(l.Event.Jabber.Room.Disco,e.NS.DISCO_INFO,"iq");l.addHandler(l.Event.Jabber.PrivacyList,e.NS.PRIVACY,"iq","result");l.addHandler(l.Event.Jabber.PrivacyListError,e.NS.PRIVACY,"iq","error")};l.init=function(m,n){j=m;f.extend(true,k,n);if(k.debug){l.log=function(p){try{if(typeof window.console!==undefined&&typeof window.console.log!==undefined){console.log(p)}}catch(o){}};l.log("[Init] Debugging enabled")}h();d=new e.Connection(j);d.rawInput=l.rawInput.bind(l);d.rawOutput=l.rawOutput.bind(l);window.onbeforeunload=l.onWindowUnload;if(f.browser.mozilla){f(document).keydown(function(o){if(o.which===27){o.preventDefault()}})}};l.connect=function(o,n,m){d.reset();i();c=!c?o&&o.indexOf("@")<0:true;if(o&&n){d.connect(_getEscapedJidFromJid(o)+"/"+Candy.about.name,n,Candy.Core.Event.Strophe.Connect);a=new l.ChatUser(o,e.getNodeFromJid(o))}else{if(o&&m){d.connect(_getEscapedJidFromJid(o)+"/"+Candy.about.name,null,Candy.Core.Event.Strophe.Connect);a=new l.ChatUser(null,m)}else{if(o){Candy.Core.Event.Login(o)}else{Candy.Core.Event.Login()}}}};_getEscapedJidFromJid=function(m){var n=e.getNodeFromJid(m),o=e.getDomainFromJid(m);return n?e.escapeNode(n)+"@"+o:o};l.attach=function(n,m,o){a=new l.ChatUser(n,e.getNodeFromJid(n));i();d.attach(n,m,o,Candy.Core.Event.Strophe.Connect)};l.disconnect=function(){if(d.connected){f.each(l.getRooms(),function(){Candy.Core.Action.Jabber.Room.Leave(this.getJid())});d.disconnect()}};l.addHandler=function(q,p,n,o,s,r,m){return d.addHandler(q,p,n,o,s,r,m)};l.getUser=function(){return a};l.setUser=function(m){a=m};l.getConnection=function(){return d};l.getRooms=function(){return g};l.isAnonymousConnection=function(){return c};l.getOptions=function(){return k};l.getRoom=function(m){if(g[m]){return g[m]}return null};l.onWindowUnload=function(){d.sync=true;l.disconnect();d.flush()};l.rawInput=function(m){this.log("RECV: "+m)};l.rawOutput=function(m){this.log("SENT: "+m)};l.log=function(){};return l}(Candy.Core||{},Strophe,jQuery));Candy.View=(function(i,b){var d={container:null,roomJid:null},h={language:"en",resources:"res/",messages:{limit:2000,remove:500},crop:{message:{nickname:15,body:1000},roster:{nickname:15}}},a=function(j){b.i18n.setDictionary(i.Translation[j])},g=function(){Candy.Core.Event.addObserver(Candy.Core.Event.KEYS.CHAT,i.Observer.Chat);Candy.Core.Event.addObserver(Candy.Core.Event.KEYS.PRESENCE,i.Observer.Presence);Candy.Core.Event.addObserver(Candy.Core.Event.KEYS.PRESENCE_ERROR,i.Observer.PresenceError);Candy.Core.Event.addObserver(Candy.Core.Event.KEYS.MESSAGE,i.Observer.Message);Candy.Core.Event.addObserver(Candy.Core.Event.KEYS.LOGIN,i.Observer.Login)},e=function(){if(b.browser.msie&&!b.browser.version.match("^9")){b(document).focusin(Candy.View.Pane.Window.onFocus).focusout(Candy.View.Pane.Window.onBlur)}else{b(window).focus(Candy.View.Pane.Window.onFocus).blur(Candy.View.Pane.Window.onBlur)}b(window).resize(Candy.View.Pane.Chat.fitTabs)},f=function(){b("#emoticons-icon").click(function(j){i.Pane.Chat.Context.showEmoticonsMenu(j.currentTarget);j.stopPropagation()});b("#chat-autoscroll-control").click(Candy.View.Pane.Chat.Toolbar.onAutoscrollControlClick);b("#chat-sound-control").click(Candy.View.Pane.Chat.Toolbar.onSoundControlClick);if(Candy.Util.cookieExists("candy-nosound")){b("#chat-sound-control").click()}b("#chat-statusmessage-control").click(Candy.View.Pane.Chat.Toolbar.onStatusMessageControlClick);if(Candy.Util.cookieExists("candy-nostatusmessages")){b("#chat-statusmessage-control").click()}},c=function(){b("body").delegate("li[data-tooltip]","mouseenter",Candy.View.Pane.Chat.Tooltip.show)};i.init=function(j,k){b.extend(true,h,k);a(h.language);Candy.Util.Parser.setEmoticonPath(this.getOptions().resources+"img/emoticons/");d.container=j;d.container.html(Mustache.to_html(Candy.View.Template.Chat.pane,{tooltipEmoticons:b.i18n._("tooltipEmoticons"),tooltipSound:b.i18n._("tooltipSound"),tooltipAutoscroll:b.i18n._("tooltipAutoscroll"),tooltipStatusmessage:b.i18n._("tooltipStatusmessage"),tooltipAdministration:b.i18n._("tooltipAdministration"),tooltipUsercount:b.i18n._("tooltipUsercount"),resourcesPath:this.getOptions().resources},{tabs:Candy.View.Template.Chat.tabs,rooms:Candy.View.Template.Chat.rooms,modal:Candy.View.Template.Chat.modal,toolbar:Candy.View.Template.Chat.toolbar,soundcontrol:Candy.View.Template.Chat.soundcontrol}));e();f();g();c()};i.getCurrent=function(){return d};i.getOptions=function(){return h};return i}(Candy.View||{},jQuery));Candy.Util=(function(a,b){a.jidToId=function(c){return MD5.hexdigest(c)};a.escapeJid=function(c){var d=Strophe.escapeNode(Strophe.getNodeFromJid(c)),f=Strophe.getDomainFromJid(c),e=Strophe.getResourceFromJid(c);c=d+"@"+f;if(e){c+="/"+Strophe.escapeNode(e)}return c};a.unescapeJid=function(c){var d=Strophe.unescapeNode(Strophe.getNodeFromJid(c)),f=Strophe.getDomainFromJid(c),e=Strophe.getResourceFromJid(c);c=d+"@"+f;if(e){c+="/"+Strophe.unescapeNode(e)}return c};a.crop=function(d,c){if(d.length>c){d=d.substr(0,c-3)+"..."}return d};a.setCookie=function(c,e,d){var f=new Date();f.setDate(new Date().getDate()+d);document.cookie=c+"="+e+";expires="+f.toUTCString()+";path=/"};a.cookieExists=function(c){return document.cookie.indexOf(c)>-1};a.getCookie=function(c){if(document.cookie){var d=new RegExp(escape(c)+"=([^;]*)","gm"),e=d.exec(document.cookie);if(e){return e[1]}}};a.deleteCookie=function(c){document.cookie=c+"=;expires=Thu, 01-Jan-70 00:00:01 GMT;path=/"};a.getPosLeftAccordingToWindowBounds=function(e,h){var d=b(document).width(),c=e.outerWidth(),f=c-e.outerWidth(true),g="left";if(h+c>=d){h-=c-f;g="right"}return{px:h,backgroundPositionAlignment:g}};a.getPosTopAccordingToWindowBounds=function(d,h){var g=b(document).height(),c=d.outerHeight(),e=c-d.outerHeight(true),f="top";if(h+c>=g){h-=c-e;f="bottom"}return{px:h,backgroundPositionAlignment:f}};a.localizedTime=function(d){if(d===undefined){return undefined}var c=a.iso8601toDate(d);if(c.toDateString()===new Date().toDateString()){return c.format(b.i18n._("timeFormat"))}else{return c.format(b.i18n._("dateFormat"))}};a.iso8601toDate=function(c){var e=Date.parse(c),d=0;if(isNaN(e)){var f=/^(\d{4}|[+\-]\d{6})-(\d{2})-(\d{2})(?:[T ](\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3,}))?)?(?:(Z)|([+\-])(\d{2})(?::?(\d{2}))?))?/.exec(c);if(f){if(f[8]!=="Z"){d=+f[10]*60+(+f[11]);if(f[9]==="+"){d=-d}}return new Date(+f[1],+f[2]-1,+f[3],+f[4],+f[5]+d,+f[6],f[7]?+f[7].substr(0,3):0)}else{e=Date.parse(c.replace(/^(\d{4})(\d{2})(\d{2})/,"$1-$2-$3")+"Z")}}return new Date(e)};a.isEmptyObject=function(c){var d;for(d in c){if(c.hasOwnProperty(d)){return false}}return true};a.forceRedraw=function(c){c.css({display:"none"});setTimeout(function(){this.css({display:"block"})}.bind(c),1)};a.Parser={_emoticonPath:"",setEmoticonPath:function(c){this._emoticonPath=c},emoticons:[{plain:":)",regex:/((\s):-?\)|:-?\)(\s|$))/gm,image:"Smiling.png"},{plain:";)",regex:/((\s);-?\)|;-?\)(\s|$))/gm,image:"Winking.png"},{plain:":D",regex:/((\s):-?D|:-?D(\s|$))/gm,image:"Grinning.png"},{plain:";D",regex:/((\s);-?D|;-?D(\s|$))/gm,image:"Grinning_Winking.png"},{plain:":(",regex:/((\s):-?\(|:-?\((\s|$))/gm,image:"Unhappy.png"},{plain:"^^",regex:/((\s)\^\^|\^\^(\s|$))/gm,image:"Happy_3.png"},{plain:":P",regex:/((\s):-?P|:-?P(\s|$))/igm,image:"Tongue_Out.png"},{plain:";P",regex:/((\s);-?P|;-?P(\s|$))/igm,image:"Tongue_Out_Winking.png"},{plain:":S",regex:/((\s):-?S|:-?S(\s|$))/igm,image:"Confused.png"},{plain:":/",regex:/((\s):-?\/|:-?\/(\s|$))/gm,image:"Uncertain.png"},{plain:"8)",regex:/((\s)8-?\)|8-?\)(\s|$))/gm,image:"Sunglasses.png"},{plain:"$)",regex:/((\s)\$-?\)|\$-?\)(\s|$))/gm,image:"Greedy.png"},{plain:"oO",regex:/((\s)oO|oO(\s|$))/gm,image:"Huh.png"},{plain:":x",regex:/((\s):x|:x(\s|$))/gm,image:"Lips_Sealed.png"},{plain:":666:",regex:/((\s):666:|:666:(\s|$))/gm,image:"Devil.png"},{plain:"<3",regex:/((\s)&lt;3|&lt;3(\s|$))/gm,image:"Heart.png"}],emotify:function(d){var c;for(c=this.emoticons.length-1;c>=0;c--){d=d.replace(this.emoticons[c].regex,'$2<img class="emoticon" alt="$1" src="'+this._emoticonPath+this.emoticons[c].image+'" />$3')}return d},linkify:function(c){c=c.replace(/(^|[^\/])(www\.[^\.]+\.[\S]+(\b|$))/gi,"$1http://$2");return c.replace(/(\b(https?|ftp|file):\/\/[\-A-Z0-9+&@#\/%?=~_|!:,.;]*[\-A-Z0-9+&@#\/%=~_|])/ig,'<a href="$1" target="_blank">$1</a>')},escape:function(c){return b("<div/>").text(c).html()},all:function(c){if(c){c=this.escape(c);c=this.linkify(c);c=this.emotify(c)}return c}};return a}(Candy.Util||{},jQuery));Candy.Util.Observable=(function(a){var b={};a.addObserver=function(c,d){if(b[c]===undefined){b[c]=[]}b[c].push(d)};a.deleteObserver=function(c,d){delete b[c][d]};a.clearObservers=function(c){if(c!==undefined){b[c]=[]}else{b={}}};a.notifyObservers=function(f,c){var d=b[f],e;for(e=d.length-1;e>=0;e--){d[e].update(a,c)}};return a}(Candy.Util.Observable||{}));Candy.Core.Action=(function(a,c,b){a.Jabber={Version:function(d){Candy.Core.getConnection().send($iq({type:"result",to:d.attr("from"),from:d.attr("to"),id:d.attr("id")}).c("query",{name:Candy.about.name,version:Candy.about.version,os:navigator.userAgent}))},Roster:function(){Candy.Core.getConnection().send($iq({type:"get",xmlns:c.NS.CLIENT}).c("query",{xmlns:c.NS.ROSTER}).tree())},Presence:function(d){Candy.Core.getConnection().send($pres(d).tree())},Services:function(){Candy.Core.getConnection().send($iq({type:"get",xmlns:c.NS.CLIENT}).c("query",{xmlns:c.NS.DISCO_ITEMS}).tree())},Autojoin:function(){if(Candy.Core.getOptions().autojoin===true){Candy.Core.getConnection().send($iq({type:"get",xmlns:c.NS.CLIENT}).c("query",{xmlns:c.NS.PRIVATE}).c("storage",{xmlns:c.NS.BOOKMARKS}).tree())}else{if(b.isArray(Candy.Core.getOptions().autojoin)){b.each(Candy.Core.getOptions().autojoin,function(){a.Jabber.Room.Join(this.valueOf())})}}},ResetIgnoreList:function(){Candy.Core.getConnection().send($iq({type:"set",from:Candy.Core.getUser().getJid(),id:"set1"}).c("query",{xmlns:c.NS.PRIVACY}).c("list",{name:"ignore"}).c("item",{action:"allow",order:"0"}).tree())},RemoveIgnoreList:function(){Candy.Core.getConnection().send($iq({type:"set",from:Candy.Core.getUser().getJid(),id:"remove1"}).c("query",{xmlns:c.NS.PRIVACY}).c("list",{name:"ignore"}).tree())},GetIgnoreList:function(){Candy.Core.getConnection().send($iq({type:"get",from:Candy.Core.getUser().getJid(),id:"get1"}).c("query",{xmlns:c.NS.PRIVACY}).c("list",{name:"ignore"}).tree())},SetIgnoreListActive:function(){Candy.Core.getConnection().send($iq({type:"set",from:Candy.Core.getUser().getJid(),id:"set2"}).c("query",{xmlns:c.NS.PRIVACY}).c("active",{name:"ignore"}).tree())},GetJidIfAnonymous:function(){if(!Candy.Core.getUser().getJid()){Candy.Core.log("[Jabber] Anonymous login");Candy.Core.getUser().data.jid=Candy.Core.getConnection().jid}},Room:{Join:function(d,e){a.Jabber.Room.Disco(d);Candy.Core.getConnection().muc.join(d,Candy.Core.getUser().getNick(),null,null,e)},Leave:function(d){Candy.Core.getConnection().muc.leave(d,Candy.Core.getRoom(d).getUser().getNick(),function(){})},Disco:function(d){Candy.Core.getConnection().send($iq({type:"get",from:Candy.Core.getUser().getJid(),to:d,id:"disco3"}).c("query",{xmlns:c.NS.DISCO_INFO}).tree())},Message:function(d,f,e){f=b.trim(f);if(f===""){return false}Candy.Core.getConnection().muc.message(Candy.Util.escapeJid(d),undefined,f,e);return true},IgnoreUnignore:function(d){Candy.Core.getUser().addToOrRemoveFromPrivacyList("ignore",d);Candy.Core.Action.Jabber.Room.UpdatePrivacyList()},UpdatePrivacyList:function(){var d=Candy.Core.getUser(),f=$iq({type:"set",from:d.getJid(),id:"edit1"}).c("query",{xmlns:"jabber:iq:privacy"}).c("list",{name:"ignore"}),e=d.getPrivacyList("ignore");if(e.length>0){b.each(e,function(g,h){f.c("item",{type:"jid",value:Candy.Util.escapeJid(h),action:"deny",order:g}).c("message").up().up()})}else{f.c("item",{action:"allow",order:"0"})}Candy.Core.getConnection().send(f.tree())},Admin:{UserAction:function(d,i,g,h){var f,e={nick:c.escapeNode(c.getResourceFromJid(i))};switch(g){case"kick":f="kick1";e.role="none";break;case"ban":f="ban1";e.affiliation="outcast";break;default:return false}Candy.Core.getConnection().send($iq({type:"set",from:Candy.Core.getUser().getJid(),to:d,id:f}).c("query",{xmlns:c.NS.MUC_ADMIN}).c("item",e).c("reason").t(h).tree());return true},SetSubject:function(d,e){Candy.Core.getConnection().muc.setTopic(d,e)}}}};return a}(Candy.Core.Action||{},Strophe,jQuery));Candy.Core.ChatRoom=function(a){this.room={jid:a,name:null};this.user=null;this.roster=new Candy.Core.ChatRoster();this.setUser=function(b){this.user=b};this.getUser=function(){return this.user};this.getJid=function(){return this.room.jid};this.setName=function(b){this.room.name=b};this.getName=function(){return this.room.name};this.setRoster=function(b){this.roster=b};this.getRoster=function(){return this.roster}};Candy.Core.ChatRoster=function(){this.items={};this.add=function(a){this.items[a.getJid()]=a};this.remove=function(a){delete this.items[a]};this.get=function(a){return this.items[a]};this.getAll=function(){return this.items}};Candy.Core.ChatUser=function(b,a,c,d){this.ROLE_MODERATOR="moderator";this.AFFILIATION_OWNER="owner";this.data={jid:b,nick:Strophe.unescapeNode(a),affiliation:c,role:d,privacyLists:{},customData:{}};this.getJid=function(){if(this.data.jid){return Candy.Util.unescapeJid(this.data.jid)}return};this.getEscapedJid=function(){return Candy.Util.escapeJid(this.data.jid)};this.getNick=function(){return Strophe.unescapeNode(this.data.nick)};this.getRole=function(){return this.data.role};this.getAffiliation=function(){return this.data.affiliation};this.isModerator=function(){return this.getRole()===this.ROLE_MODERATOR||this.getAffiliation()===this.AFFILIATION_OWNER};this.addToOrRemoveFromPrivacyList=function(g,f){if(!this.data.privacyLists[g]){this.data.privacyLists[g]=[]}var e=-1;if((e=this.data.privacyLists[g].indexOf(f))!==-1){this.data.privacyLists[g].splice(e,1)}else{this.data.privacyLists[g].push(f)}return this.data.privacyLists[g]};this.getPrivacyList=function(e){if(!this.data.privacyLists[e]){this.data.privacyLists[e]=[]}return this.data.privacyLists[e]};this.isInPrivacyList=function(f,e){if(!this.data.privacyLists[f]){return false}return this.data.privacyLists[f].indexOf(e)!==-1};this.setCustomData=function(e){this.data.customData=e};this.getCustomData=function(){return this.data.customData}};Candy.Core.Event=(function(a,e,c,d){var b;for(b in d){if(d.hasOwnProperty(b)){a[b]=d[b]}}a.KEYS={CHAT:1,PRESENCE:2,MESSAGE:3,LOGIN:4,PRESENCE_ERROR:5};a.Strophe={Connect:function(f){switch(f){case e.Status.CONNECTED:Candy.Core.log("[Connection] Connected");Candy.Core.Action.Jabber.GetJidIfAnonymous();case e.Status.ATTACHED:Candy.Core.log("[Connection] Attached");Candy.Core.Action.Jabber.Presence();Candy.Core.Action.Jabber.Autojoin();Candy.Core.Action.Jabber.GetIgnoreList();break;case e.Status.DISCONNECTED:Candy.Core.log("[Connection] Disconnected");break;case e.Status.AUTHFAIL:Candy.Core.log("[Connection] Authentication failed");break;case e.Status.CONNECTING:Candy.Core.log("[Connection] Connecting");break;case e.Status.DISCONNECTING:Candy.Core.log("[Connection] Disconnecting");break;case e.Status.AUTHENTICATING:Candy.Core.log("[Connection] Authenticating");break;case e.Status.ERROR:case e.Status.CONNFAIL:Candy.Core.log("[Connection] Failed ("+f+")");break;default:Candy.Core.log("[Connection] What?!");break}a.notifyObservers(a.KEYS.CHAT,{type:"connection",status:f})}};a.Login=function(f){a.notifyObservers(a.KEYS.LOGIN,{presetJid:f})};a.Jabber={Version:function(f){Candy.Core.log("[Jabber] Version");Candy.Core.Action.Jabber.Version(c(f));return true},Presence:function(f){Candy.Core.log("[Jabber] Presence");f=c(f);if(f.children('x[xmlns^="'+e.NS.MUC+'"]').length>0){if(f.attr("type")==="error"){a.Jabber.Room.PresenceError(f)}else{a.Jabber.Room.Presence(f)}}return true},Bookmarks:function(f){Candy.Core.log("[Jabber] Bookmarks");c("conference",f).each(function(){var g=c(this);if(g.attr("autojoin")){Candy.Core.Action.Jabber.Room.Join(g.attr("jid"))}});return true},PrivacyList:function(g){Candy.Core.log("[Jabber] PrivacyList");var f=Candy.Core.getUser();c('list[name="ignore"] item',g).each(function(){var h=c(this);if(h.attr("action")==="deny"){f.addToOrRemoveFromPrivacyList("ignore",h.attr("value"))}});Candy.Core.Action.Jabber.SetIgnoreListActive();return false},PrivacyListError:function(f){Candy.Core.log("[Jabber] PrivacyListError");if(c('error[code="404"][type="cancel"] item-not-found',f)){Candy.Core.Action.Jabber.ResetIgnoreList();Candy.Core.Action.Jabber.SetIgnoreListActive()}return false},Message:function(i){Candy.Core.log("[Jabber] Message");var i=c(i),h=i.attr("from"),g=i.attr("type"),f=i.attr("to");if(h!==e.getDomainFromJid(h)&&(g==="groupchat"||g==="chat"||g==="error")){a.Jabber.Room.Message(i)}else{if(!f&&h===e.getDomainFromJid(h)){a.notifyObservers(a.KEYS.CHAT,{type:(g||"message"),message:i.children("body").text()})}else{if(f&&h===e.getDomainFromJid(h)){a.notifyObservers(a.KEYS.CHAT,{type:(g||"message"),subject:i.children("subject").text(),message:i.children("body").text()})}}}return true},Room:{Leave:function(f){Candy.Core.log("[Jabber:Room] Leave");var f=c(f),l=f.attr("from"),n=e.getBareJidFromJid(l);if(!Candy.Core.getRoom(n)){return false}var j=Candy.Core.getRoom(n).getName(),m=f.find("item"),k="leave",i,h;delete Candy.Core.getRooms()[n];if(m.attr("role")==="none"){if(f.find("status").attr("code")==="307"){k="kick"}else{if(f.find("status").attr("code")==="301"){k="ban"}}i=m.find("reason").text();h=m.find("actor").attr("jid")}var g=new Candy.Core.ChatUser(l,e.getResourceFromJid(l),m.attr("affiliation"),m.attr("role"));a.notifyObservers(a.KEYS.PRESENCE,{roomJid:n,roomName:j,type:k,reason:i,actor:h,user:g});return true},Disco:function(i){Candy.Core.log("[Jabber:Room] Disco");var i=c(i),g=e.getBareJidFromJid(i.attr("from"));if(!Candy.Core.getRooms()[g]){Candy.Core.getRooms()[g]=new Candy.Core.ChatRoom(g)}var f=i.find("identity").attr("name"),h=Candy.Core.getRoom(g);if(h.getName()===null){h.setName(f)}return true},Presence:function(h){Candy.Core.log("[Jabber:Room] Presence");var l=Candy.Util.unescapeJid(h.attr("from")),o=e.getBareJidFromJid(l),m=h.attr("type");if(e.getResourceFromJid(l)===Candy.Core.getUser().getNick()&&m==="unavailable"){a.Jabber.Room.Leave(h);return true}var g=Candy.Core.getRoom(o);if(!g){Candy.Core.getRooms()[o]=new Candy.Core.ChatRoom(o);g=Candy.Core.getRoom(o)}var k=g.getRoster(),i,j,n=h.find("item");if(m!=="unavailable"){var f=e.getResourceFromJid(l);j=new Candy.Core.ChatUser(l,f,n.attr("affiliation"),n.attr("role"));if(g.getUser()===null&&Candy.Core.getUser().getNick()===f){g.setUser(j)}k.add(j);i="join"}else{i="leave";if(n.attr("role")==="none"){if(h.find("status").attr("code")==="307"){i="kick"}else{if(h.find("status").attr("code")==="301"){i="ban"}}}j=k.get(l);k.remove(l)}a.notifyObservers(a.KEYS.PRESENCE,{roomJid:o,roomName:g.getName(),user:j,action:i,currentUser:Candy.Core.getUser()});return true},PresenceError:function(i){Candy.Core.log("[Jabber:Room] Presence Error");var j=Candy.Util.unescapeJid(i.attr("from")),g=e.getBareJidFromJid(j),h=Candy.Core.getRooms()[g],f=h.getName();delete h;a.notifyObservers(a.KEYS.PRESENCE_ERROR,{msg:i,type:i.children("error").children()[0].tagName.toLowerCase(),roomJid:g,roomName:f})},Message:function(h){Candy.Core.log("[Jabber:Room] Message");var o,n;if(h.children("subject").length>0){o=Candy.Util.unescapeJid(e.getBareJidFromJid(h.attr("from")));n={name:e.getNodeFromJid(o),body:h.children("subject").text(),type:"subject"}}else{if(h.attr("type")==="error"){var m=h.children("error");if(m.attr("code")==="500"&&m.children("text").length>0){o=h.attr("from");n={type:"info",body:m.children("text").text()}}}else{if(h.children("body").length>0){if(h.attr("type")==="chat"){o=Candy.Util.unescapeJid(h.attr("from"));var f=e.getBareJidFromJid(o),i=!Candy.Core.getRoom(f),g=i?e.getNodeFromJid(o):e.getResourceFromJid(o);n={name:g,body:h.children("body").text(),type:h.attr("type"),isNoConferenceRoomJid:i}}else{o=Candy.Util.unescapeJid(e.getBareJidFromJid(h.attr("from")));var j=e.getResourceFromJid(h.attr("from"));if(j){j=e.unescapeNode(j);n={name:j,body:h.children("body").text(),type:h.attr("type")}}else{n={name:"",body:h.children("body").text(),type:"info"}}}}else{return true}}}var k=h.children("delay")?h.children("delay"):h.children('x[xmlns="'+e.NS.DELAY+'"]'),l=k!==undefined?k.attr("stamp"):null;a.notifyObservers(a.KEYS.MESSAGE,{roomJid:o,message:n,timestamp:l});return true}}};return a}(Candy.Core.Event||{},Strophe,jQuery,Candy.Util.Observable));Candy.View.Event=(function(a,b){a.Chat={onAdminMessage:function(c){return},onDisconnect:function(){return},onAuthfail:function(){return}};a.Room={onAdd:function(c){return},onShow:function(c){return},onHide:function(c){return},onSubjectChange:function(c){return},onClose:function(c){return},onPresenceChange:function(c){return}};a.Roster={onUpdate:function(c){return},onContextMenu:function(c){return{}},afterContextMenu:function(c){return}};a.Message={beforeShow:function(c){return c.message},onShow:function(c){return},beforeSend:function(c){return c}};return a}(Candy.View.Event||{},jQuery));Candy.View.Observer=(function(a,b){a.Chat={update:function(e,d){if(d.type==="connection"){switch(d.status){case Strophe.Status.CONNECTING:case Strophe.Status.AUTHENTICATING:Candy.View.Pane.Chat.Modal.show(b.i18n._("statusConnecting"),false,true);break;case Strophe.Status.ATTACHED:case Strophe.Status.CONNECTED:Candy.View.Pane.Chat.Modal.show(b.i18n._("statusConnected"));Candy.View.Pane.Chat.Modal.hide();break;case Strophe.Status.DISCONNECTING:Candy.View.Pane.Chat.Modal.show(b.i18n._("statusDisconnecting"),false,true);break;case Strophe.Status.DISCONNECTED:var c=Candy.Core.isAnonymousConnection()?Strophe.getDomainFromJid(Candy.Core.getUser().getJid()):null;Candy.View.Pane.Chat.Modal.showLoginForm(b.i18n._("statusDisconnected"),c);Candy.View.Event.Chat.onDisconnect();break;case Strophe.Status.AUTHFAIL:Candy.View.Pane.Chat.Modal.showLoginForm(b.i18n._("statusAuthfail"));Candy.View.Event.Chat.onAuthfail();break;default:Candy.View.Pane.Chat.Modal.show(b.i18n._("status",d.status));break}}else{if(d.type==="message"){Candy.View.Pane.Chat.adminMessage((d.subject||""),d.message)}else{if(d.type==="chat"||d.type==="groupchat"){Candy.View.Pane.Chat.onInfoMessage(Candy.View.getCurrent().roomJid,(d.subject||""),d.message)}}}}};a.Presence={update:function(h,e){if(e.type==="leave"){var c=Candy.View.Pane.Room.getUser(e.roomJid);Candy.View.Pane.Room.close(e.roomJid);a.Presence.notifyPrivateChats(c,e.type)}else{if(e.type==="kick"||e.type==="ban"){var g=e.actor?Strophe.getNodeFromJid(e.actor):null,f,d=[e.roomName];if(g){d.push(g)}switch(e.type){case"kick":f=b.i18n._((g?"youHaveBeenKickedBy":"youHaveBeenKicked"),d);break;case"ban":f=b.i18n._((g?"youHaveBeenBannedBy":"youHaveBeenBanned"),d);break}Candy.View.Pane.Chat.Modal.show(Mustache.to_html(Candy.View.Template.Chat.Context.adminMessageReason,{reason:e.reason,_action:f,_reason:b.i18n._("reasonWas",[e.reason])}));setTimeout(function(){Candy.View.Pane.Chat.Modal.hide(function(){Candy.View.Pane.Room.close(e.roomJid);a.Presence.notifyPrivateChats(e.user,e.type)})},5000);Candy.View.Event.Room.onPresenceChange({type:e.type,reason:e.reason,roomJid:e.roomJid,user:e.user})}else{if(!Candy.View.Pane.Chat.rooms[e.roomJid]){Candy.View.Pane.Room.init(e.roomJid,e.roomName);Candy.View.Pane.Room.show(e.roomJid)}Candy.View.Pane.Roster.update(e.roomJid,e.user,e.action,e.currentUser);if(Candy.View.Pane.Chat.rooms[e.user.getJid()]){Candy.View.Pane.Roster.update(e.user.getJid(),e.user,e.action,e.currentUser);Candy.View.Pane.PrivateRoom.setStatus(e.user.getJid(),e.action)}}}},notifyPrivateChats:function(d,e){Candy.Core.log("[View:Observer] notify Private Chats");var c;for(c in Candy.View.Pane.Chat.rooms){if(Candy.View.Pane.Chat.rooms.hasOwnProperty(c)&&Candy.View.Pane.Room.getUser(c)&&d.getJid()===Candy.View.Pane.Room.getUser(c).getJid()){Candy.View.Pane.Roster.update(c,d,e,d);Candy.View.Pane.PrivateRoom.setStatus(c,e)}}}};a.PresenceError={update:function(e,c){switch(c.type){case"not-authorized":var d;if(c.msg.children("x").children("password").length>0){d=b.i18n._("passwordEnteredInvalid",[c.roomName])}Candy.View.Pane.Chat.Modal.showEnterPasswordForm(c.roomJid,c.roomName,d);break;case"conflict":Candy.View.Pane.Chat.Modal.showNicknameConflictForm(c.roomJid);break;case"registration-required":Candy.View.Pane.Chat.Modal.showError("errorMembersOnly",[c.roomName]);break;case"service-unavailable":Candy.View.Pane.Chat.Modal.showError("errorMaxOccupantsReached",[c.roomName]);break}}};a.Message={update:function(d,c){if(c.message.type==="subject"){if(!Candy.View.Pane.Chat.rooms[c.roomJid]){Candy.View.Pane.Room.init(c.roomJid,c.message.name);Candy.View.Pane.Room.show(c.roomJid)}Candy.View.Pane.Room.setSubject(c.roomJid,c.message.body)}else{if(c.message.type==="info"){Candy.View.Pane.Chat.infoMessage(c.roomJid,c.message.body)}else{if(c.message.type==="chat"&&!Candy.View.Pane.Chat.rooms[c.roomJid]){Candy.View.Pane.PrivateRoom.open(c.roomJid,c.message.name,false,c.message.isNoConferenceRoomJid)}Candy.View.Pane.Message.show(c.roomJid,c.message.name,c.message.body,c.timestamp)}}}};a.Login={update:function(d,c){Candy.View.Pane.Chat.Modal.showLoginForm(null,c.presetJid)}};return a}(Candy.View.Observer||{},jQuery));Candy.View.Pane=(function(a,b){a.Window={_hasFocus:true,_plainTitle:document.title,_unreadMessagesCount:0,autoscroll:true,hasFocus:function(){return a.Window._hasFocus},increaseUnreadMessages:function(){a.Window.renderUnreadMessages(++a.Window._unreadMessagesCount)},reduceUnreadMessages:function(c){a.Window._unreadMessagesCount-=c;if(a.Window._unreadMessagesCount<=0){a.Window.clearUnreadMessages()}else{a.Window.renderUnreadMessages(a.Window._unreadMessagesCount)}},clearUnreadMessages:function(){a.Window._unreadMessagesCount=0;document.title=a.Window._plainTitle},renderUnreadMessages:function(c){document.title=Candy.View.Template.Window.unreadmessages.replace("{{count}}",c).replace("{{title}}",a.Window._plainTitle)},onFocus:function(){a.Window._hasFocus=true;if(Candy.View.getCurrent().roomJid){a.Room.setFocusToForm(Candy.View.getCurrent().roomJid);a.Chat.clearUnreadMessages(Candy.View.getCurrent().roomJid)}},onBlur:function(){a.Window._hasFocus=false}};a.Chat={rooms:[],addTab:function(d,c,e){var h=Candy.Util.jidToId(d),f=Mustache.to_html(Candy.View.Template.Chat.tab,{roomJid:d,roomId:h,name:c||Strophe.getNodeFromJid(d),privateUserChat:function(){return e==="chat"},roomType:e}),g=b(f).appendTo("#chat-tabs");g.click(a.Chat.tabClick);b("a.close",g).click(a.Chat.tabClose);a.Chat.fitTabs()},getTab:function(c){return b("#chat-tabs").children('li[data-roomjid="'+c+'"]')},removeTab:function(c){a.Chat.getTab(c).remove();a.Chat.fitTabs()},setActiveTab:function(c){b("#chat-tabs").children().each(function(){var d=b(this);if(d.attr("data-roomjid")===c){d.addClass("active")}else{d.removeClass("active")}})},increaseUnreadMessages:function(d){var c=this.getTab(d).find(".unread");c.show().text(c.text()!==""?parseInt(c.text(),10)+1:1);if(a.Chat.rooms[d].type==="chat"){a.Window.increaseUnreadMessages()}},clearUnreadMessages:function(d){var c=a.Chat.getTab(d).find(".unread");a.Window.reduceUnreadMessages(c.text());c.hide().text("")},tabClick:function(d){var c=Candy.View.getCurrent().roomJid;a.Chat.rooms[c].scrollPosition=a.Room.getPane(c,".message-pane-wrapper").scrollTop();a.Room.show(b(this).attr("data-roomjid"));d.preventDefault()},tabClose:function(d){var c=b(this).parent().attr("data-roomjid");if(a.Chat.rooms[c].type==="chat"){a.Room.close(c)}else{Candy.Core.Action.Jabber.Room.Leave(c)}return false},allTabsClosed:function(){Candy.Core.disconnect();a.Chat.Toolbar.hide();return},fitTabs:function(){var g=b("#chat-tabs").innerWidth(),f=0,e=b("#chat-tabs").children();e.each(function(){f+=b(this).css({width:"auto",overflow:"visible"}).outerWidth(true)});if(f>g){var c=e.outerWidth(true)-e.width(),d=Math.floor((g)/e.length)-c;e.css({width:d,overflow:"hidden"})}},updateToolbar:function(c){b("#chat-toolbar").find(".context").click(function(d){a.Chat.Context.show(d.currentTarget,c);d.stopPropagation()});Candy.View.Pane.Chat.Toolbar.updateUsercount(Candy.View.Pane.Chat.rooms[c].usercount)},adminMessage:function(d,e){if(Candy.View.getCurrent().roomJid){var c=Mustache.to_html(Candy.View.Template.Chat.adminMessage,{subject:d,message:e,sender:b.i18n._("administratorMessageSubject"),time:Candy.Util.localizedTime(new Date().toGMTString())});b("#chat-rooms").children().each(function(){a.Room.appendToMessagePane(b(this).attr("data-roomjid"),c)});a.Room.scrollToBottom(Candy.View.getCurrent().roomJid);Candy.View.Event.Chat.onAdminMessage({subject:d,message:e})}},infoMessage:function(c,d,e){a.Chat.onInfoMessage(c,d,e)},onInfoMessage:function(c,e,f){if(Candy.View.getCurrent().roomJid){var d=Mustache.to_html(Candy.View.Template.Chat.infoMessage,{subject:e,message:b.i18n._(f),time:Candy.Util.localizedTime(new Date().toGMTString())});a.Room.appendToMessagePane(c,d);if(Candy.View.getCurrent().roomJid===c){a.Room.scrollToBottom(Candy.View.getCurrent().roomJid)}}},Toolbar:{show:function(){b("#chat-toolbar").show()},hide:function(){b("#chat-toolbar").hide()},playSound:function(){a.Chat.Toolbar.onPlaySound()},onPlaySound:function(){var c=document.getElementById("chat-sound-player");c.SetVariable("method:stop","");c.SetVariable("method:play","")},onSoundControlClick:function(){var c=b("#chat-sound-control");if(c.hasClass("checked")){a.Chat.Toolbar.playSound=function(){};Candy.Util.setCookie("candy-nosound","1",365)}else{a.Chat.Toolbar.playSound=function(){a.Chat.Toolbar.onPlaySound()};Candy.Util.deleteCookie("candy-nosound")}c.toggleClass("checked")},onAutoscrollControlClick:function(){var c=b("#chat-autoscroll-control");if(c.hasClass("checked")){a.Room.scrollToBottom=function(d){a.Room.onScrollToStoredPosition(d)};a.Window.autoscroll=false}else{a.Room.scrollToBottom=function(d){a.Room.onScrollToBottom(d)};a.Room.scrollToBottom(Candy.View.getCurrent().roomJid);a.Window.autoscroll=true}c.toggleClass("checked")},onStatusMessageControlClick:function(){var c=b("#chat-statusmessage-control");if(c.hasClass("checked")){a.Chat.infoMessage=function(){};Candy.Util.setCookie("candy-nostatusmessages","1",365)}else{a.Chat.infoMessage=function(d,e,f){a.Chat.onInfoMessage(d,e,f)};Candy.Util.deleteCookie("candy-nostatusmessages")}c.toggleClass("checked")},updateUsercount:function(c){b("#chat-usercount").text(c)}},Modal:{show:function(d,e,c){if(e){a.Chat.Modal.showCloseControl()}else{a.Chat.Modal.hideCloseControl()}if(c){a.Chat.Modal.showSpinner()}else{a.Chat.Modal.hideSpinner()}b("#chat-modal").stop(false,true);b("#chat-modal-body").html(d);b("#chat-modal").fadeIn("fast");b("#chat-modal-overlay").show()},hide:function(c){b("#chat-modal").fadeOut("fast",function(){b("#chat-modal-body").text("");b("#chat-modal-overlay").hide()});b(document).keydown(function(d){if(d.which===27){d.preventDefault()}});if(c){c()}},showSpinner:function(){b("#chat-modal-spinner").show()},hideSpinner:function(){b("#chat-modal-spinner").hide()},showCloseControl:function(){b("#admin-message-cancel").show().click(function(c){a.Chat.Modal.hide();c.preventDefault()});b(document).keydown(function(c){if(c.which===27){a.Chat.Modal.hide();c.preventDefault()}})},hideCloseControl:function(){b("#admin-message-cancel").hide().click(function(){})},showLoginForm:function(d,c){a.Chat.Modal.show((d?d:"")+Mustache.to_html(Candy.View.Template.Login.form,{_labelUsername:b.i18n._("labelUsername"),_labelPassword:b.i18n._("labelPassword"),_loginSubmit:b.i18n._("loginSubmit"),displayPassword:!Candy.Core.isAnonymousConnection(),displayUsername:Candy.Core.isAnonymousConnection()||!c,presetJid:c?c:false}));b("#login-form").children()[0].focus();b("#login-form").submit(function(g){var h=b("#username").val(),e=b("#password").val();if(!Candy.Core.isAnonymousConnection()){var f=Candy.Core.getUser()&&h.indexOf("@")<0?h+"@"+Strophe.getDomainFromJid(Candy.Core.getUser().getJid()):h;if(f.indexOf("@")<0&&!Candy.Core.getUser()){Candy.View.Pane.Chat.Modal.showLoginForm(b.i18n._("loginInvalid"))}else{Candy.Core.connect(f,e)}}else{Candy.Core.connect(c,null,h)}return false})},showEnterPasswordForm:function(d,c,e){a.Chat.Modal.show(Mustache.to_html(Candy.View.Template.PresenceError.enterPasswordForm,{roomName:c,_labelPassword:b.i18n._("labelPassword"),_label:(e?e:b.i18n._("enterRoomPassword",[c])),_joinSubmit:b.i18n._("enterRoomPasswordSubmit")}),true);b("#password").focus();b("#enter-password-form").submit(function(){var f=b("#password").val();a.Chat.Modal.hide(function(){Candy.Core.Action.Jabber.Room.Join(d,f)});return false})},showNicknameConflictForm:function(c){a.Chat.Modal.show(Mustache.to_html(Candy.View.Template.PresenceError.nicknameConflictForm,{_labelNickname:b.i18n._("labelUsername"),_label:b.i18n._("nicknameConflict"),_loginSubmit:b.i18n._("loginSubmit")}));b("#nickname").focus();b("#nickname-conflict-form").submit(function(){var d=b("#nickname").val();a.Chat.Modal.hide(function(){Candy.Core.getUser().data.nick=d;Candy.Core.Action.Jabber.Room.Join(c)});return false})},showError:function(d,c){a.Chat.Modal.show(Mustache.to_html(Candy.View.Template.PresenceError.displayError,{_error:b.i18n._(d,c)}),true)}},Tooltip:{show:function(g,f){var h=b("#tooltip"),i=b(g.currentTarget);if(!f){f=i.attr("data-tooltip")}if(h.length===0){var d=Mustache.to_html(Candy.View.Template.Chat.tooltip);b("#chat-pane").append(d);h=b("#tooltip")}b("#context-menu").hide();h.stop(false,true);h.children("div").html(f);var j=i.offset(),c=Candy.Util.getPosLeftAccordingToWindowBounds(h,j.left),e=Candy.Util.getPosTopAccordingToWindowBounds(h,j.top);h.css({left:c.px,top:e.px,backgroundPosition:c.backgroundPositionAlignment+" "+e.backgroundPositionAlignment}).fadeIn("fast");i.mouseleave(function(k){k.stopPropagation();b("#tooltip").stop(false,true).fadeOut("fast",function(){b(this).css({top:0,left:0})})})}},Context:{init:function(){if(b("#context-menu").length===0){var c=Mustache.to_html(Candy.View.Template.Chat.Context.menu);b("#chat-pane").append(c);b("#context-menu").mouseleave(function(){b(this).fadeOut("fast")})}},show:function(e,p,h){e=b(e);var f=a.Chat.rooms[p].id,d=b("#context-menu"),o=b("ul li",d);b("#tooltip").hide();if(!h){h=Candy.Core.getUser()}o.remove();var k=this.getMenuLinks(p,h,e),c,l=function(r,q){return function(s){s.data.callback(s,r,q);b("#context-menu").hide()}};for(c in k){if(k.hasOwnProperty(c)){var n=k[c],j=Mustache.to_html(Candy.View.Template.Chat.Context.menulinks,{roomId:f,"class":n["class"],id:c,label:n.label});b("ul",d).append(j);b("#context-menu-"+c).bind("click",n,l(p,h))}}if(c){var m=e.offset(),g=Candy.Util.getPosLeftAccordingToWindowBounds(d,m.left),i=Candy.Util.getPosTopAccordingToWindowBounds(d,m.top);d.css({left:g.px,top:i.px,backgroundPosition:g.backgroundPositionAlignment+" "+i.backgroundPositionAlignment});d.fadeIn("fast");Candy.View.Event.Roster.afterContextMenu({roomJid:p,user:h,element:d});return true}},getMenuLinks:function(d,c,e){var f=b.extend(this.initialMenuLinks(e),Candy.View.Event.Roster.onContextMenu({roomJid:d,user:c,elem:e})),g;for(g in f){if(f.hasOwnProperty(g)&&f[g].requiredPermission!==undefined&&!f[g].requiredPermission(c,a.Room.getUser(d),e)){delete f[g]}}return f},initialMenuLinks:function(){return{"private":{requiredPermission:function(c,d){return d.getNick()!==c.getNick()&&Candy.Core.getRoom(Candy.View.getCurrent().roomJid)&&!Candy.Core.getUser().isInPrivacyList("ignore",c.getJid())},"class":"private",label:b.i18n._("privateActionLabel"),callback:function(f,d,c){b("#user-"+Candy.Util.jidToId(d)+"-"+Candy.Util.jidToId(c.getJid())).click()}},ignore:{requiredPermission:function(c,d){return d.getNick()!==c.getNick()&&!Candy.Core.getUser().isInPrivacyList("ignore",c.getJid())},"class":"ignore",label:b.i18n._("ignoreActionLabel"),callback:function(f,d,c){Candy.View.Pane.Room.ignoreUser(d,c.getJid())}},unignore:{requiredPermission:function(c,d){return d.getNick()!==c.getNick()&&Candy.Core.getUser().isInPrivacyList("ignore",c.getJid())},"class":"unignore",label:b.i18n._("unignoreActionLabel"),callback:function(f,d,c){Candy.View.Pane.Room.unignoreUser(d,c.getJid())}},kick:{requiredPermission:function(c,d){return d.getNick()!==c.getNick()&&d.isModerator()&&!c.isModerator()},"class":"kick",label:b.i18n._("kickActionLabel"),callback:function(f,d,c){a.Chat.Modal.show(Mustache.to_html(Candy.View.Template.Chat.Context.contextModalForm,{_label:b.i18n._("reason"),_submit:b.i18n._("kickActionLabel")}),true);b("#context-modal-field").focus();b("#context-modal-form").submit(function(e){Candy.Core.Action.Jabber.Room.Admin.UserAction(d,c.getJid(),"kick",b("#context-modal-field").val());a.Chat.Modal.hide();return false})}},ban:{requiredPermission:function(c,d){return d.getNick()!==c.getNick()&&d.isModerator()&&!c.isModerator()},"class":"ban",label:b.i18n._("banActionLabel"),callback:function(f,d,c){a.Chat.Modal.show(Mustache.to_html(Candy.View.Template.Chat.Context.contextModalForm,{_label:b.i18n._("reason"),_submit:b.i18n._("banActionLabel")}),true);b("#context-modal-field").focus();b("#context-modal-form").submit(function(g){Candy.Core.Action.Jabber.Room.Admin.UserAction(d,c.getJid(),"ban",b("#context-modal-field").val());a.Chat.Modal.hide();return false})}},subject:{requiredPermission:function(c,d){return d.getNick()===c.getNick()&&d.isModerator()},"class":"subject",label:b.i18n._("setSubjectActionLabel"),callback:function(f,d,c){a.Chat.Modal.show(Mustache.to_html(Candy.View.Template.Chat.Context.contextModalForm,{_label:b.i18n._("subject"),_submit:b.i18n._("setSubjectActionLabel")}),true);b("#context-modal-field").focus();b("#context-modal-form").submit(function(g){Candy.Core.Action.Jabber.Room.Admin.SetSubject(d,b("#context-modal-field").val());a.Chat.Modal.hide();g.preventDefault()})}}}},showEmoticonsMenu:function(h){h=b(h);var k=h.offset(),j=b("#context-menu"),g=b("ul",j),e="",d;b("#tooltip").hide();for(d=Candy.Util.Parser.emoticons.length-1;d>=0;d--){e='<img src="'+Candy.Util.Parser._emoticonPath+Candy.Util.Parser.emoticons[d].image+'" alt="'+Candy.Util.Parser.emoticons[d].plain+'" />'+e}g.html('<li class="emoticons">'+e+"</li>");g.find("img").click(function(){var i=Candy.View.Pane.Room.getPane(Candy.View.getCurrent().roomJid,".message-form").children(".field"),m=i.val(),l=b(this).attr("alt")+" ";i.val(m?m+" "+l:l).focus()});var c=Candy.Util.getPosLeftAccordingToWindowBounds(j,k.left),f=Candy.Util.getPosTopAccordingToWindowBounds(j,k.top);j.css({left:c.px,top:f.px,backgroundPosition:c.backgroundPositionAlignment+" "+f.backgroundPositionAlignment});j.fadeIn("fast");return true}}};a.Room={init:function(d,c,e){e=e||"groupchat";if(Candy.Util.isEmptyObject(a.Chat.rooms)){a.Chat.Toolbar.show()}var f=Candy.Util.jidToId(d);a.Chat.rooms[d]={id:f,usercount:0,name:c,type:e,messageCount:0,scrollPosition:-1};b("#chat-rooms").append(Mustache.to_html(Candy.View.Template.Room.pane,{roomId:f,roomJid:d,roomType:e,form:{_messageSubmit:b.i18n._("messageSubmit")},roster:{_userOnline:b.i18n._("userOnline")}},{roster:Candy.View.Template.Roster.pane,messages:Candy.View.Template.Message.pane,form:Candy.View.Template.Room.form}));a.Chat.addTab(d,c,e);a.Room.getPane(d,".message-form").submit(a.Message.submit);Candy.View.Event.Room.onAdd({roomJid:d,type:e,element:a.Room.getPane(d)});return f},show:function(c){var d=a.Chat.rooms[c].id;b(".room-pane").each(function(){var e=b(this);if(e.attr("id")===("chat-room-"+d)){e.show();Candy.View.getCurrent().roomJid=c;a.Chat.updateToolbar(c);a.Chat.setActiveTab(c);a.Chat.clearUnreadMessages(c);a.Room.setFocusToForm(c);a.Room.scrollToBottom(c);Candy.View.Event.Room.onShow({roomJid:c,element:e})}else{e.hide();Candy.View.Event.Room.onHide({roomJid:c,element:e})}})},setSubject:function(c,e){var d=Mustache.to_html(Candy.View.Template.Room.subject,{subject:e,roomName:a.Chat.rooms[c].name,_roomSubject:b.i18n._("roomSubject"),time:Candy.Util.localizedTime(new Date().toGMTString())});a.Room.appendToMessagePane(c,d);a.Room.scrollToBottom(c);Candy.View.Event.Room.onSubjectChange({roomJid:c,element:a.Room.getPane(c),subject:e})},close:function(c){a.Chat.removeTab(c);a.Window.clearUnreadMessages();a.Room.getPane(c).remove();var d=b("#chat-rooms").children();if(Candy.View.getCurrent().roomJid===c){Candy.View.getCurrent().roomJid=null;if(d.length===0){a.Chat.allTabsClosed()}else{a.Room.show(d.last().attr("data-roomjid"))}}delete a.Chat.rooms[c];Candy.View.Event.Room.onClose({roomJid:c})},appendToMessagePane:function(c,d){a.Room.getPane(c,".message-pane").append(d);a.Chat.rooms[c].messageCount++;a.Room.sliceMessagePane(c)},sliceMessagePane:function(c){if(a.Window.autoscroll){var d=Candy.View.getOptions().messages;if(a.Chat.rooms[c].messageCount>d.limit){a.Room.getPane(c,".message-pane").children().slice(0,d.remove*2).remove();a.Chat.rooms[c].messageCount-=d.remove}}},scrollToBottom:function(c){a.Room.onScrollToBottom(c)},onScrollToBottom:function(c){var d=a.Room.getPane(c,".message-pane-wrapper");d.scrollTop(d.prop("scrollHeight"))},onScrollToStoredPosition:function(c){if(a.Chat.rooms[c].scrollPosition>-1){var d=a.Room.getPane(c,".message-pane-wrapper");d.scrollTop(a.Chat.rooms[c].scrollPosition);a.Chat.rooms[c].scrollPosition=-1}},setFocusToForm:function(c){var f=a.Room.getPane(c,".message-form");if(f){try{f.children(".field")[0].focus()}catch(d){}}},setUser:function(d,c){a.Chat.rooms[d].user=c;var f=a.Room.getPane(d),e=b("#chat-pane");f.attr("data-userjid",c.getJid());if(c.isModerator()){if(c.getRole()===c.ROLE_MODERATOR){e.addClass("role-moderator")}if(c.getAffiliation()===c.AFFILIATION_OWNER){e.addClass("affiliation-owner")}}else{e.removeClass("role-moderator affiliation-owner")}a.Chat.Context.init()},getUser:function(c){return a.Chat.rooms[c].user},ignoreUser:function(c,d){Candy.Core.Action.Jabber.Room.IgnoreUnignore(d);Candy.View.Pane.Room.addIgnoreIcon(c,d)},unignoreUser:function(c,d){Candy.Core.Action.Jabber.Room.IgnoreUnignore(d);Candy.View.Pane.Room.removeIgnoreIcon(c,d)},addIgnoreIcon:function(c,d){if(Candy.View.Pane.Chat.rooms[d]){b("#user-"+Candy.View.Pane.Chat.rooms[d].id+"-"+Candy.Util.jidToId(d)).addClass("status-ignored")}if(Candy.View.Pane.Chat.rooms[Strophe.getBareJidFromJid(c)]){b("#user-"+Candy.View.Pane.Chat.rooms[Strophe.getBareJidFromJid(c)].id+"-"+Candy.Util.jidToId(d)).addClass("status-ignored")}},removeIgnoreIcon:function(c,d){if(Candy.View.Pane.Chat.rooms[d]){b("#user-"+Candy.View.Pane.Chat.rooms[d].id+"-"+Candy.Util.jidToId(d)).removeClass("status-ignored")}if(Candy.View.Pane.Chat.rooms[Strophe.getBareJidFromJid(c)]){b("#user-"+Candy.View.Pane.Chat.rooms[Strophe.getBareJidFromJid(c)].id+"-"+Candy.Util.jidToId(d)).removeClass("status-ignored")}},getPane:function(c,d){if(a.Chat.rooms[c]){if(d){if(a.Chat.rooms[c]["pane-"+d]){return a.Chat.rooms[c]["pane-"+d]}else{a.Chat.rooms[c]["pane-"+d]=b("#chat-room-"+a.Chat.rooms[c].id).find(d);return a.Chat.rooms[c]["pane-"+d]}}else{return b("#chat-room-"+a.Chat.rooms[c].id)}}}};a.PrivateRoom={open:function(e,c,f,g){var d=g?Candy.Core.getUser():a.Room.getUser(Strophe.getBareJidFromJid(e));if(Candy.Core.getUser().isInPrivacyList("ignore",e)){return false}if(!a.Chat.rooms[e]){a.Room.init(e,c,"chat")}if(f){a.Room.show(e)}a.Roster.update(e,new Candy.Core.ChatUser(e,c),"join",d);a.Roster.update(e,d,"join",d);a.PrivateRoom.setStatus(e,"join");if(g){a.Chat.infoMessage(e,b.i18n._("presenceUnknownWarningSubject"),b.i18n._("presenceUnknownWarning"))}Candy.View.Event.Room.onAdd({roomJid:e,type:"chat",element:a.Room.getPane(e)})},setStatus:function(d,c){var e=a.Room.getPane(d,".message-form");if(c==="join"){a.Chat.getTab(d).addClass("online").removeClass("offline");e.children(".field").removeAttr("disabled");e.children(".submit").removeAttr("disabled");a.Chat.getTab(d)}else{a.Chat.getTab(d).addClass("offline").removeClass("online");e.children(".field").attr("disabled",true);e.children(".submit").attr("disabled",true)}}};a.Roster={update:function(n,i,h,e){var f=a.Chat.rooms[n].id,k=Candy.Util.jidToId(i.getJid()),c=-1;if(h==="join"){c=1;var j=Mustache.to_html(Candy.View.Template.Roster.user,{roomId:f,userId:k,userJid:i.getJid(),nick:i.getNick(),displayNick:Candy.Util.crop(i.getNick(),Candy.View.getOptions().crop.roster.nickname),role:i.getRole(),affiliation:i.getAffiliation(),me:e!==undefined&&i.getNick()===e.getNick(),tooltipRole:b.i18n._("tooltipRole"),tooltipIgnored:b.i18n._("tooltipIgnored")}),m=b("#user-"+f+"-"+k);if(m.length<1){var d=false,l=a.Room.getPane(n,".roster-pane");if(l.children().length>0){var g=i.getNick().toUpperCase();l.children().each(function(){var o=b(this);if(o.attr("data-nick").toUpperCase()>g){o.before(j);d=true;return false}return true})}if(!d){l.append(j)}a.Roster.joinAnimation("user-"+f+"-"+k);if(e!==undefined&&i.getNick()!==e.getNick()&&a.Room.getUser(n)){if(a.Chat.rooms[n].type==="chat"){a.Chat.onInfoMessage(n,b.i18n._("userJoinedRoom",[i.getNick()]))}else{a.Chat.infoMessage(n,b.i18n._("userJoinedRoom",[i.getNick()]))}}}else{c=0;m.replaceWith(j);b("#user-"+f+"-"+k).css({opacity:1}).show()}if(e!==undefined&&e.getNick()===i.getNick()){a.Room.setUser(n,i)}else{b("#user-"+f+"-"+k).click(a.Roster.userClick)}b("#user-"+f+"-"+k+" .context").click(function(o){a.Chat.Context.show(o.currentTarget,n,i);o.stopPropagation()});if(e!==undefined&&e.isInPrivacyList("ignore",i.getJid())){Candy.View.Pane.Room.addIgnoreIcon(n,i.getJid())}}else{if(h==="leave"){a.Roster.leaveAnimation("user-"+f+"-"+k);if(a.Chat.rooms[n].type==="chat"){a.Chat.onInfoMessage(n,b.i18n._("userLeftRoom",[i.getNick()]))}else{a.Chat.infoMessage(n,b.i18n._("userLeftRoom",[i.getNick()]))}}else{if(h==="kick"){a.Roster.leaveAnimation("user-"+f+"-"+k);a.Chat.onInfoMessage(n,b.i18n._("userHasBeenKickedFromRoom",[i.getNick()]))}else{if(h==="ban"){a.Roster.leaveAnimation("user-"+f+"-"+k);a.Chat.onInfoMessage(n,b.i18n._("userHasBeenBannedFromRoom",[i.getNick()]))}}}}Candy.View.Pane.Chat.rooms[n].usercount+=c;if(n===Candy.View.getCurrent().roomJid){Candy.View.Pane.Chat.Toolbar.updateUsercount(Candy.View.Pane.Chat.rooms[n].usercount)}Candy.View.Event.Roster.onUpdate({roomJid:n,user:i,action:h,element:b("#user-"+f+"-"+k)})},userClick:function(){var c=b(this);a.PrivateRoom.open(c.attr("data-jid"),c.attr("data-nick"),true)},joinAnimation:function(c){b("#"+c).stop(true).slideDown("normal",function(){b(this).animate({opacity:1})})},leaveAnimation:function(c){b("#"+c).stop(true).attr("id","#"+c+"-leaving").animate({opacity:0},{complete:function(){b(this).slideUp("normal",function(){b(this).remove()})}})}};a.Message={submit:function(e){var c=Candy.View.Pane.Chat.rooms[Candy.View.getCurrent().roomJid].type,d=b(this).children(".field").val().substring(0,Candy.View.getOptions().crop.message.body);d=Candy.View.Event.Message.beforeSend(d);Candy.Core.Action.Jabber.Room.Message(Candy.View.getCurrent().roomJid,d,c);if(c==="chat"&&d){a.Message.show(Candy.View.getCurrent().roomJid,a.Room.getUser(Candy.View.getCurrent().roomJid).getNick(),d)}b(this).children(".field").val("").focus();e.preventDefault()},show:function(c,d,g,h){g=Candy.Util.Parser.all(g.substring(0,Candy.View.getOptions().crop.message.body));g=Candy.View.Event.Message.beforeShow({roomJid:c,nick:d,message:g});if(!g){return}var e=Mustache.to_html(Candy.View.Template.Message.item,{name:d,displayName:Candy.Util.crop(d,Candy.View.getOptions().crop.message.nickname),message:g,time:Candy.Util.localizedTime(h||new Date().toGMTString())});a.Room.appendToMessagePane(c,e);var f=a.Room.getPane(c,".message-pane").children().last();f.find("a.name").click(function(i){i.preventDefault();if(d!==a.Room.getUser(Candy.View.getCurrent().roomJid).getNick()&&Candy.Core.getRoom(c).getRoster().get(c+"/"+d)){Candy.View.Pane.PrivateRoom.open(c+"/"+d,d,true)}});if(Candy.View.getCurrent().roomJid!==c||!a.Window.hasFocus()){a.Chat.increaseUnreadMessages(c);if(Candy.View.Pane.Chat.rooms[c].type==="chat"&&!a.Window.hasFocus()){a.Chat.Toolbar.playSound()}}if(Candy.View.getCurrent().roomJid===c){a.Room.scrollToBottom(c)}Candy.View.Event.Message.onShow({roomJid:c,element:f,nick:d,message:g})}};return a}(Candy.View.Pane||{},jQuery));Candy.View.Template=(function(a){a.Window={unreadmessages:"({{count}}) {{title}}"};a.Chat={pane:'<div id="chat-pane">{{> tabs}}{{> toolbar}}{{> rooms}}</div>{{> modal}}',rooms:'<div id="chat-rooms" class="rooms"></div>',tabs:'<ul id="chat-tabs"></ul>',tab:'<li class="roomtype-{{roomType}}" data-roomjid="{{roomJid}}" data-roomtype="{{roomType}}"><a href="#" class="label">{{#privateUserChat}}@{{/privateUserChat}}{{name}}</a><a href="#" class="transition"></a><a href="#" class="close">\u00D7</a><small class="unread"></small></li>',modal:'<div id="chat-modal"><a id="admin-message-cancel" class="close" href="#">\u00D7</a><span id="chat-modal-body"></span><img src="{{resourcesPath}}img/modal-spinner.gif" id="chat-modal-spinner" /></div><div id="chat-modal-overlay"></div>',adminMessage:'<dt>{{time}}</dt><dd class="adminmessage"><span class="label">{{sender}}</span>{{subject}} {{message}}</dd>',infoMessage:'<dt>{{time}}</dt><dd class="infomessage">{{subject}} {{message}}</dd>',toolbar:'<ul id="chat-toolbar"><li id="emoticons-icon" data-tooltip="{{tooltipEmoticons}}"></li><li id="chat-sound-control" class="checked" data-tooltip="{{tooltipSound}}">{{> soundcontrol}}</li><li id="chat-autoscroll-control" class="checked" data-tooltip="{{tooltipAutoscroll}}"></li><li class="checked" id="chat-statusmessage-control" data-tooltip="{{tooltipStatusmessage}}"></li><li class="context" data-tooltip="{{tooltipAdministration}}"></li><li class="usercount" data-tooltip="{{tooltipUsercount}}"><span id="chat-usercount"></span></li></ul>',soundcontrol:'<script type="text/javascript">var audioplayerListener = new Object(); audioplayerListener.onInit = function() { };<\/script><object id="chat-sound-player" type="application/x-shockwave-flash" data="{{resourcesPath}}audioplayer.swf" width="0" height="0"><param name="movie" value="{{resourcesPath}}audioplayer.swf" /><param name="AllowScriptAccess" value="always" /><param name="FlashVars" value="listener=audioplayerListener&amp;mp3={{resourcesPath}}notify.mp3" /></object>',Context:{menu:'<div id="context-menu"><ul></ul></div>',menulinks:'<li class="{{class}}" id="context-menu-{{id}}">{{label}}</li>',contextModalForm:'<form action="#" id="context-modal-form"><label for="context-modal-label">{{_label}}</label><input type="text" name="contextModalField" id="context-modal-field" /><input type="submit" class="button" name="send" value="{{_submit}}" /></form>',adminMessageReason:'<a id="admin-message-cancel" class="close" href="#">×</a><p>{{_action}}</p>{{#reason}}<p>{{_reason}}</p>{{/reason}}'},tooltip:'<div id="tooltip"><div></div></div>'};a.Room={pane:'<div class="room-pane roomtype-{{roomType}}" id="chat-room-{{roomId}}" data-roomjid="{{roomJid}}" data-roomtype="{{roomType}}">{{> roster}}{{> messages}}{{> form}}</div>',subject:'<dt>{{time}}</dt><dd class="subject"><span class="label">{{roomName}}</span>{{_roomSubject}} {{subject}}</dd>',form:'<div class="message-form-wrapper"></div><form method="post" class="message-form"><input name="message" class="field" type="text" autocomplete="off" maxlength="1000" /><input type="submit" class="submit" name="submit" value="{{_messageSubmit}}" /></form>'};a.Roster={pane:'<div class="roster-pane"></div>',user:'<div class="user role-{{role}} affiliation-{{affiliation}}{{#me}} me{{/me}}" id="user-{{roomId}}-{{userId}}" data-jid="{{userJid}}" data-nick="{{nick}}" data-role="{{role}}" data-affiliation="{{affiliation}}"><div class="label">{{displayNick}}</div><ul><li class="context" id="context-{{roomId}}-{{userId}}"></li><li class="role role-{{role}} affiliation-{{affiliation}}" data-tooltip="{{tooltipRole}}"></li><li class="ignore" data-tooltip="{{tooltipIgnored}}"></li></ul></div>'};a.Message={pane:'<div class="message-pane-wrapper"><dl class="message-pane"></dl></div>',item:'<dt>{{time}}</dt><dd><span class="label"><a href="#" class="name">{{displayName}}</a></span>{{{message}}}</dd>'};a.Login={form:'<form method="post" id="login-form" class="login-form">{{#displayUsername}}<label for="username">{{_labelUsername}}</label><input type="text" id="username" name="username"/>{{/displayUsername}}{{#presetJid}}<input type="hidden" id="username" name="username" value="{{presetJid}}"/>{{/presetJid}}{{#displayPassword}}<label for="password">{{_labelPassword}}</label><input type="password" id="password" name="password" />{{/displayPassword}}<input type="submit" class="button" value="{{_loginSubmit}}" /></form>'};a.PresenceError={enterPasswordForm:'<strong>{{_label}}</strong><form method="post" id="enter-password-form" class="enter-password-form"><label for="password">{{_labelPassword}}</label><input type="password" id="password" name="password" /><input type="submit" class="button" value="{{_joinSubmit}}" /></form>',nicknameConflictForm:'<strong>{{_label}}</strong><form method="post" id="nickname-conflict-form" class="nickname-conflict-form"><label for="nickname">{{_labelNickname}}</label><input type="text" id="nickname" name="nickname" /><input type="submit" class="button" value="{{_loginSubmit}}" /></form>',displayError:"<strong>{{_error}}</strong>"};return a}(Candy.View.Template||{}));Candy.View.Translation={en:{status:"Status: %s",statusConnecting:"Connecting...",statusConnected:"Connected",statusDisconnecting:"Disconnecting...",statusDisconnected:"Disconnected",statusAuthfail:"Authentication failed",roomSubject:"Subject:",messageSubmit:"Send",labelUsername:"Username:",labelPassword:"Password:",loginSubmit:"Login",loginInvalid:"Invalid JID",reason:"Reason:",subject:"Subject:",reasonWas:"Reason was: %s.",kickActionLabel:"Kick",youHaveBeenKickedBy:"You have been kicked from %2$s by %1$s",youHaveBeenKicked:"You have been kicked from %s",banActionLabel:"Ban",youHaveBeenBannedBy:"You have been banned from %1$s by %2$s",youHaveBeenBanned:"You have been banned from %s",privateActionLabel:"Private chat",ignoreActionLabel:"Ignore",unignoreActionLabel:"Unignore",setSubjectActionLabel:"Change Subject",administratorMessageSubject:"Administrator",userJoinedRoom:"%s joined the room.",userLeftRoom:"%s left the room.",userHasBeenKickedFromRoom:"%s has been kicked from the room.",userHasBeenBannedFromRoom:"%s has been banned from the room.",presenceUnknownWarningSubject:"Notice:",presenceUnknownWarning:"This user might be offline. We can't track his presence.",dateFormat:"dd.mm.yyyy",timeFormat:"HH:MM:ss",tooltipRole:"Moderator",tooltipIgnored:"You ignore this user",tooltipEmoticons:"Emoticons",tooltipSound:"Play sound for new private messages",tooltipAutoscroll:"Autoscroll",tooltipStatusmessage:"Display status messages",tooltipAdministration:"Room Administration",tooltipUsercount:"Room Occupants",enterRoomPassword:'Room "%s" is password protected.',enterRoomPasswordSubmit:"Join room",passwordEnteredInvalid:'Invalid password for room "%s".',nicknameConflict:"Username already in use. Please choose another one.",errorMembersOnly:'You can\'t join room "%s": Insufficient rights.',errorMaxOccupantsReached:'You can\'t join room "%s": Too many occupants.',antiSpamMessage:"Please do not spam. You have been blocked for a short-time."},de:{status:"Status: %s",statusConnecting:"Verbinden...",statusConnected:"Verbunden",statusDisconnecting:"Verbindung trennen...",statusDisconnected:"Verbindung getrennt",statusAuthfail:"Authentifizierung fehlgeschlagen",roomSubject:"Thema:",messageSubmit:"Senden",labelUsername:"Benutzername:",labelPassword:"Passwort:",loginSubmit:"Anmelden",loginInvalid:"Ungültige JID",reason:"Begründung:",subject:"Titel:",reasonWas:"Begründung: %s.",kickActionLabel:"Kick",youHaveBeenKickedBy:"Du wurdest soeben aus dem Raum %1$s gekickt (%2$s)",youHaveBeenKicked:"Du wurdest soeben aus dem Raum %s gekickt",banActionLabel:"Ban",youHaveBeenBannedBy:"Du wurdest soeben aus dem Raum %1$s verbannt (%2$s)",youHaveBeenBanned:"Du wurdest soeben aus dem Raum %s verbannt",privateActionLabel:"Privater Chat",ignoreActionLabel:"Ignorieren",unignoreActionLabel:"Nicht mehr ignorieren",setSubjectActionLabel:"Thema ändern",administratorMessageSubject:"Administrator",userJoinedRoom:"%s hat soeben den Raum betreten.",userLeftRoom:"%s hat soeben den Raum verlassen.",userHasBeenKickedFromRoom:"%s ist aus dem Raum gekickt worden.",userHasBeenBannedFromRoom:"%s ist aus dem Raum verbannt worden.",presenceUnknownWarningSubject:"Hinweis:",presenceUnknownWarning:"Dieser Benutzer könnte bereits abgemeldet sein. Wir können seine Anwesenheit nicht verfolgen.",dateFormat:"dd.mm.yyyy",timeFormat:"HH:MM:ss",tooltipRole:"Moderator",tooltipIgnored:"Du ignorierst diesen Benutzer",tooltipEmoticons:"Smileys",tooltipSound:"Ton abspielen bei neuen privaten Nachrichten",tooltipAutoscroll:"Autoscroll",tooltipStatusmessage:"Statusnachrichten anzeigen",tooltipAdministration:"Raum Administration",tooltipUsercount:"Anzahl Benutzer im Raum",enterRoomPassword:'Raum "%s" ist durch ein Passwort geschützt.',enterRoomPasswordSubmit:"Raum betreten",passwordEnteredInvalid:'Inkorrektes Passwort für Raum "%s".',nicknameConflict:"Der Benutzername wird bereits verwendet. Bitte wähle einen anderen.",errorMembersOnly:'Du kannst den Raum "%s" nicht betreten: Ungenügende Rechte.',errorMaxOccupantsReached:'Du kannst den Raum "%s" nicht betreten: Benutzerlimit erreicht.',antiSpamMessage:"Bitte nicht spammen. Du wurdest für eine kurze Zeit blockiert."},fr:{status:"Status: %s",statusConnecting:"Connecter...",statusConnected:"Connecté.",statusDisconnecting:"Déconnecter....",statusDisconnected:"Déconnecté.",statusAuthfail:"Authentification a échoué",roomSubject:"Sujet:",messageSubmit:"Envoyer",labelUsername:"Nom d'utilisateur:",labelPassword:"Mot de passe:",loginSubmit:"Inscription",loginInvalid:"JID invalide",reason:"Justification:",subject:"Titre:",reasonWas:"Justification: %s.",kickActionLabel:"Kick",youHaveBeenKickedBy:"Tu as été expulsé de le salon %1$s (%2$s)",youHaveBeenKicked:"Tu as été expulsé de le salon %s",banActionLabel:"Ban",youHaveBeenBannedBy:"Tu as été banni de le salon %1$s (%2$s)",youHaveBeenBanned:"Tu as été banni de le salon %s",privateActionLabel:"Chat privé",ignoreActionLabel:"Ignorer",unignoreActionLabel:"Ne plus ignorer",setSubjectActionLabel:"Changer le sujet",administratorMessageSubject:"Administrateur",userJoinedRoom:"%s vient d'entrer dans le salon.",userLeftRoom:"%s vient de quitter le salon.",userHasBeenKickedFromRoom:"%s a été expulsé du salon.",userHasBeenBannedFromRoom:"%s a été banni du salon.",presenceUnknownWarningSubject:"Note:",presenceUnknownWarning:"Cet utilisateur n'est malheureusement plus connecté, le message ne sera pas envoyé.",dateFormat:"dd.mm.yyyy",timeFormat:"HH:MM:ss",tooltipRole:"Modérateur",tooltipIgnored:"Tu ignores cette personne",tooltipEmoticons:"Smileys",tooltipSound:"Jouer un son lorsque tu reçois de nouveaux messages privés",tooltipAutoscroll:"Auto-defilement",tooltipStatusmessage:"Messages d'état",tooltipAdministration:"Administrer le salon",tooltipUsercount:"Nombre d'utilisateurs dans le salon",enterRoomPassword:'Le salon "%s" est protégé par un mot de passe.',enterRoomPasswordSubmit:"Entrer dans le salon",passwordEnteredInvalid:'Le mot de passe four le salon "%s" est invalide.',nicknameConflict:"Le nom d'utilisateur est déjà utilisé. Choisi un autre.",errorMembersOnly:'Tu ne peut pas entrer de le salon "%s": droits insuffisants.',errorMaxOccupantsReached:'Tu ne peut pas entrer de le salon "%s": Limite d\'utilisateur atteint.',antiSpamMessage:"S'il te plaît, pas de spam. Tu as été bloqué pendant une courte période.."},nl:{status:"Status: %s",statusConnecting:"Verbinding maken...",statusConnected:"Verbinding is gereed",statusDisconnecting:"Verbinding verbreken...",statusDisconnected:"Verbinding is verbroken",statusAuthfail:"Authenticatie is mislukt",roomSubject:"Onderwerp:",messageSubmit:"Verstuur",labelUsername:"Gebruikersnaam:",labelPassword:"Wachtwoord:",loginSubmit:"Inloggen",loginInvalid:"JID is onjuist",reason:"Reden:",subject:"Onderwerp:",reasonWas:"De reden was: %s.",kickActionLabel:"Verwijderen",youHaveBeenKickedBy:"Je bent verwijderd van %1$s door %2$s",youHaveBeenKicked:"Je bent verwijderd van %s",banActionLabel:"Blokkeren",youHaveBeenBannedBy:"Je bent geblokkeerd van %1$s door %2$s",youHaveBeenBanned:"Je bent geblokkeerd van %s",privateActionLabel:"Prive gesprek",ignoreActionLabel:"Negeren",unignoreActionLabel:"Niet negeren",setSubjectActionLabel:"Onderwerp wijzigen",administratorMessageSubject:"Beheerder",userJoinedRoom:"%s komt de chat binnen.",userLeftRoom:"%s heeft de chat verlaten.",userHasBeenKickedFromRoom:"%s is verwijderd.",userHasBeenBannedFromRoom:"%s is geblokkeerd.",presenceUnknownWarningSubject:"Mededeling:",presenceUnknownWarning:"Deze gebruiker is waarschijnlijk offline, we kunnen zijn/haar aanwezigheid niet vaststellen.",dateFormat:"dd.mm.yyyy",timeFormat:"HH:MM:ss",tooltipRole:"Moderator",tooltipIgnored:"Je negeert deze gebruiker",tooltipEmoticons:"Emotie-iconen",tooltipSound:"Speel een geluid af bij nieuwe privé berichten.",tooltipAutoscroll:"Automatisch scrollen",tooltipStatusmessage:"Statusberichten weergeven",tooltipAdministration:"Instellingen",tooltipUsercount:"Gebruikers",enterRoomPassword:'De Chatroom "%s" is met een wachtwoord beveiligd.',enterRoomPasswordSubmit:"Ga naar Chatroom",passwordEnteredInvalid:'Het wachtwoord voor de Chatroom "%s" is onjuist.',nicknameConflict:"De gebruikersnaam is reeds in gebruik. Probeer a.u.b. een andere gebruikersnaam.",errorMembersOnly:'Je kunt niet deelnemen aan de Chatroom "%s": Je hebt onvoldoende rechten.',errorMaxOccupantsReached:'Je kunt niet deelnemen aan de Chatroom "%s": Het maximum aantal gebruikers is bereikt.',antiSpamMessage:"Het is niet toegestaan om veel berichten naar de server te versturen. Je bent voor een korte periode geblokkeerd."},es:{status:"Estado: %s",statusConnecting:"Conectando...",statusConnected:"Conectado",statusDisconnecting:"Desconectando...",statusDisconnected:"Desconectado",statusAuthfail:"Falló la autenticación",roomSubject:"Asunto:",messageSubmit:"Enviar",labelUsername:"Usuario:",labelPassword:"Clave:",loginSubmit:"Entrar",loginInvalid:"JID no válido",reason:"Razón:",subject:"Asunto:",reasonWas:"La razón fue: %s.",kickActionLabel:"Expulsar",youHaveBeenKickedBy:"Has sido expulsado de %1$s por %2$s",youHaveBeenKicked:"Has sido expulsado de %s",banActionLabel:"Prohibir",youHaveBeenBannedBy:"Has sido expulsado permanentemente de %1$s por %2$s",youHaveBeenBanned:"Has sido expulsado permanentemente de %s",privateActionLabel:"Chat privado",ignoreActionLabel:"Ignorar",unignoreActionLabel:"No ignorar",setSubjectActionLabel:"Cambiar asunto",administratorMessageSubject:"Administrador",userJoinedRoom:"%s se ha unido a la sala.",userLeftRoom:"%s ha dejado la sala.",userHasBeenKickedFromRoom:"%s ha sido expulsado de la sala.",userHasBeenBannedFromRoom:"%s ha sido expulsado permanentemente de la sala.",presenceUnknownWarningSubject:"Atención:",presenceUnknownWarning:"Éste usuario podría estar desconectado..",dateFormat:"dd.mm.yyyy",timeFormat:"HH:MM:ss",tooltipRole:"Moderador",tooltipIgnored:"Ignoras a éste usuario",tooltipEmoticons:"Emoticonos",tooltipSound:"Reproducir un sonido para nuevos mensajes privados",tooltipAutoscroll:"Desplazamiento automático",tooltipStatusmessage:"Mostrar mensajes de estado",tooltipAdministration:"Administración de la sala",tooltipUsercount:"Usuarios en la sala",enterRoomPassword:'La sala "%s" está protegida mediante contraseña.',enterRoomPasswordSubmit:"Unirse a la sala",passwordEnteredInvalid:'Contraseña incorrecta para la sala "%s".',nicknameConflict:"El nombre de usuario ya está siendo utilizado. Por favor elija otro.",errorMembersOnly:'No se puede unir a la sala "%s": no tiene privilegios suficientes.',errorMaxOccupantsReached:'No se puede unir a la sala "%s": demasiados participantes.',antiSpamMessage:"Por favor, no hagas spam. Has sido bloqueado temporalmente."},cn:{status:"状态: %s",statusConnecting:"连接中...",statusConnected:"已连接",statusDisconnecting:"断开连接中...",statusDisconnected:"已断开连接",statusAuthfail:"认证失败",roomSubject:"主题:",messageSubmit:"发送",labelUsername:"用户名:",labelPassword:"密码:",loginSubmit:"登录",loginInvalid:"用户名不合法",reason:"原因:",subject:"主题:",reasonWas:"原因是: %s.",kickActionLabel:"踢除",youHaveBeenKickedBy:"你在 %1$s 被管理者 %2$s 请出房间",banActionLabel:"禁言",youHaveBeenBannedBy:"你在 %1$s 被管理者 %2$s 禁言",privateActionLabel:"单独对话",ignoreActionLabel:"忽略",unignoreActionLabel:"不忽略",setSubjectActionLabel:"变更主题",administratorMessageSubject:"管理员",userJoinedRoom:"%s 加入房间",userLeftRoom:"%s 离开房间",userHasBeenKickedFromRoom:"%s 被请出这个房间",userHasBeenBannedFromRoom:"%s 被管理者禁言",presenceUnknownWarningSubject:"注意:",presenceUnknownWarning:"这个会员可能已经下线,不能追踪到他的连接信息",dateFormat:"dd.mm.yyyy",timeFormat:"HH:MM:ss",tooltipRole:"管理",tooltipIgnored:"你忽略了这个会员",tooltipEmoticons:"表情",tooltipSound:"新消息发音",tooltipAutoscroll:"滚动条",tooltipStatusmessage:"禁用状态消息",tooltipAdministration:"房间管理",tooltipUsercount:"房间占有者",enterRoomPassword:'登录房间 "%s" 需要密码.',enterRoomPasswordSubmit:"加入房间",passwordEnteredInvalid:'登录房间 "%s" 的密码不正确',nicknameConflict:"用户名已经存在,请另选一个",errorMembersOnly:'您的权限不够,不能登录房间 "%s" ',errorMaxOccupantsReached:'房间 "%s" 的人数已达上限,您不能登录',antiSpamMessage:"因为您在短时间内发送过多的消息 服务器要阻止您一小段时间。"},ja:{status:"ステータス: %s",statusConnecting:"接続中…",statusConnected:"接続されました",statusDisconnecting:"ディスコネクト中…",statusDisconnected:"ディスコネクトされました",statusAuthfail:"認証に失敗しました",roomSubject:"トピック:",messageSubmit:"送信",labelUsername:"ユーザーネーム:",labelPassword:"パスワード:",loginSubmit:"ログイン",loginInvalid:"ユーザーネームが正しくありません",reason:"理由:",subject:"トピック:",reasonWas:"理由: %s。",kickActionLabel:"キック",youHaveBeenKickedBy:"あなたは%2$sにより%1$sからキックされました。",youHaveBeenKicked:"あなたは%sからキックされました。",banActionLabel:"アカウントバン",youHaveBeenBannedBy:"あなたは%2$sにより%1$sからアカウントバンされました。",youHaveBeenBanned:"あなたは%sからアカウントバンされました。",privateActionLabel:"プライベートメッセージ",ignoreActionLabel:"無視する",unignoreActionLabel:"無視をやめる",setSubjectActionLabel:"トピックを変える",administratorMessageSubject:"管理者",userJoinedRoom:"%sは入室しました。",userLeftRoom:"%sは退室しました。",userHasBeenKickedFromRoom:"%sは部屋からキックされました。",userHasBeenBannedFromRoom:"%sは部屋からアカウントバンされました。",presenceUnknownWarningSubject:"忠告:",presenceUnknownWarning:"このユーザーのステータスは不明です。",dateFormat:"dd.mm.yyyy",timeFormat:"HH:MM:ss",tooltipRole:"モデレーター",tooltipIgnored:"このユーザーを無視設定にしている",tooltipEmoticons:"絵文字",tooltipSound:"新しいメッセージが届くたびに音を鳴らす",tooltipAutoscroll:"オートスクロール",tooltipStatusmessage:"ステータスメッセージを表示",tooltipAdministration:"部屋の管理",tooltipUsercount:"この部屋の参加者の数",enterRoomPassword:'"%s"の部屋に入るにはパスワードが必要です。',enterRoomPasswordSubmit:"部屋に入る",passwordEnteredInvalid:'"%s"のパスワードと異なるパスワードを入力しました。',nicknameConflict:"このユーザーネームはすでに利用されているため、別のユーザーネームを選んでください。",errorMembersOnly:'"%s"の部屋に入ることができません: 利用権限を満たしていません。',errorMaxOccupantsReached:'"%s"の部屋に入ることができません: 参加者の数はすでに上限に達しました。',antiSpamMessage:"スパムなどの行為はやめてください。あなたは一時的にブロックされました。"}};
\ No newline at end of file
AddDefaultCharset UTF-8
Options +MultiViews
RewriteEngine On
RewriteRule http-bind/ http://localhost:5280/http-bind/ [P]
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Candy - Chats are not dead yet</title>
<link rel="shortcut icon" href="../res/img/favicon.png" type="image/gif" />
<link rel="stylesheet" type="text/css" href="../res/default.css" />
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.min.js"></script>
<script type="text/javascript" src="../libs/libs.min.js"></script>
<script type="text/javascript" src="../candy.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
Candy.init('http://localhost/http-bind/', {
core: { debug: false},
view: { resources: '../res/', language: 'cn' }
});
Candy.Core.connect('zh99998@my-card.in', 'zh112998');
});
</script>
</head>
<body>
<div style="top:auto; height:260px">
<div id="candy"></div>
</div>
</body>
</html>
/*
* Date Format 1.2.3
* (c) 2007-2009 Steven Levithan <stevenlevithan.com>
* MIT license
*
* Includes enhancements by Scott Trenda <scott.trenda.net>
* and Kris Kowal <cixar.com/~kris.kowal/>
*
* Accepts a date, a mask, or a date and a mask.
* Returns a formatted version of the given date.
* The date defaults to the current date/time.
* The mask defaults to dateFormat.masks.default.
*
* @link http://blog.stevenlevithan.com/archives/date-time-format
*/
var dateFormat = function () {
var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g,
timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g,
timezoneClip = /[^-+\dA-Z]/g,
pad = function (val, len) {
val = String(val);
len = len || 2;
while (val.length < len) val = "0" + val;
return val;
};
// Regexes and supporting functions are cached through closure
return function (date, mask, utc) {
var dF = dateFormat;
// You can't provide utc if you skip other args (use the "UTC:" mask prefix)
if (arguments.length == 1 && Object.prototype.toString.call(date) == "[object String]" && !/\d/.test(date)) {
mask = date;
date = undefined;
}
// Passing date through Date applies Date.parse, if necessary
date = date ? new Date(date) : new Date;
if (isNaN(date)) throw SyntaxError("invalid date");
mask = String(dF.masks[mask] || mask || dF.masks["default"]);
// Allow setting the utc argument via the mask
if (mask.slice(0, 4) == "UTC:") {
mask = mask.slice(4);
utc = true;
}
var _ = utc ? "getUTC" : "get",
d = date[_ + "Date"](),
D = date[_ + "Day"](),
m = date[_ + "Month"](),
y = date[_ + "FullYear"](),
H = date[_ + "Hours"](),
M = date[_ + "Minutes"](),
s = date[_ + "Seconds"](),
L = date[_ + "Milliseconds"](),
o = utc ? 0 : date.getTimezoneOffset(),
flags = {
d: d,
dd: pad(d),
ddd: dF.i18n.dayNames[D],
dddd: dF.i18n.dayNames[D + 7],
m: m + 1,
mm: pad(m + 1),
mmm: dF.i18n.monthNames[m],
mmmm: dF.i18n.monthNames[m + 12],
yy: String(y).slice(2),
yyyy: y,
h: H % 12 || 12,
hh: pad(H % 12 || 12),
H: H,
HH: pad(H),
M: M,
MM: pad(M),
s: s,
ss: pad(s),
l: pad(L, 3),
L: pad(L > 99 ? Math.round(L / 10) : L),
t: H < 12 ? "a" : "p",
tt: H < 12 ? "am" : "pm",
T: H < 12 ? "A" : "P",
TT: H < 12 ? "AM" : "PM",
Z: utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""),
o: (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4),
S: ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10]
};
return mask.replace(token, function ($0) {
return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1);
});
};
}();
// Some common format strings
dateFormat.masks = {
"default": "ddd mmm dd yyyy HH:MM:ss",
shortDate: "m/d/yy",
mediumDate: "mmm d, yyyy",
longDate: "mmmm d, yyyy",
fullDate: "dddd, mmmm d, yyyy",
shortTime: "h:MM TT",
mediumTime: "h:MM:ss TT",
longTime: "h:MM:ss TT Z",
isoDate: "yyyy-mm-dd",
isoTime: "HH:MM:ss",
isoDateTime: "yyyy-mm-dd'T'HH:MM:ss",
isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'"
};
// Internationalization strings
dateFormat.i18n = {
dayNames: [
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
],
monthNames: [
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
]
};
// For convenience...
Date.prototype.format = function (mask, utc) {
return dateFormat(this, mask, utc);
};
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
var Base64=(function(){var a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";var b={encode:function(e){var c="";var m,k,h;var l,j,g,f;var d=0;do{m=e.charCodeAt(d++);k=e.charCodeAt(d++);h=e.charCodeAt(d++);l=m>>2;j=((m&3)<<4)|(k>>4);g=((k&15)<<2)|(h>>6);f=h&63;if(isNaN(k)){g=f=64}else{if(isNaN(h)){f=64}}c=c+a.charAt(l)+a.charAt(j)+a.charAt(g)+a.charAt(f)}while(d<e.length);return c},decode:function(e){var c="";var m,k,h;var l,j,g,f;var d=0;e=e.replace(/[^A-Za-z0-9\+\/\=]/g,"");do{l=a.indexOf(e.charAt(d++));j=a.indexOf(e.charAt(d++));g=a.indexOf(e.charAt(d++));f=a.indexOf(e.charAt(d++));m=(l<<2)|(j>>4);k=((j&15)<<4)|(g>>2);h=((g&3)<<6)|f;c=c+String.fromCharCode(m);if(g!=64){c=c+String.fromCharCode(k)}if(f!=64){c=c+String.fromCharCode(h)}}while(d<e.length);return c}};return b})();var MD5=(function(){var o=0;var a="";var l=8;var j=function(r,u){var t=(r&65535)+(u&65535);var s=(r>>16)+(u>>16)+(t>>16);return(s<<16)|(t&65535)};var n=function(r,s){return(r<<s)|(r>>>(32-s))};var b=function(u){var t=[];var r=(1<<l)-1;for(var s=0;s<u.length*l;s+=l){t[s>>5]|=(u.charCodeAt(s/l)&r)<<(s%32)}return t};var g=function(t){var u="";var r=(1<<l)-1;for(var s=0;s<t.length*32;s+=l){u+=String.fromCharCode((t[s>>5]>>>(s%32))&r)}return u};var q=function(t){var s=o?"0123456789ABCDEF":"0123456789abcdef";var u="";for(var r=0;r<t.length*4;r++){u+=s.charAt((t[r>>2]>>((r%4)*8+4))&15)+s.charAt((t[r>>2]>>((r%4)*8))&15)}return u};var p=function(u){var t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";var w="";var v,r;for(var s=0;s<u.length*4;s+=3){v=(((u[s>>2]>>8*(s%4))&255)<<16)|(((u[s+1>>2]>>8*((s+1)%4))&255)<<8)|((u[s+2>>2]>>8*((s+2)%4))&255);for(r=0;r<4;r++){if(s*8+r*6>u.length*32){w+=a}else{w+=t.charAt((v>>6*(3-r))&63)}}}return w};var d=function(z,v,u,r,y,w){return j(n(j(j(v,z),j(r,w)),y),u)};var k=function(v,u,A,z,r,y,w){return d((u&A)|((~u)&z),v,u,r,y,w)};var c=function(v,u,A,z,r,y,w){return d((u&z)|(A&(~z)),v,u,r,y,w)};var m=function(v,u,A,z,r,y,w){return d(u^A^z,v,u,r,y,w)};var i=function(v,u,A,z,r,y,w){return d(A^(u|(~z)),v,u,r,y,w)};var f=function(C,w){C[w>>5]|=128<<((w)%32);C[(((w+64)>>>9)<<4)+14]=w;var B=1732584193;var A=-271733879;var z=-1732584194;var y=271733878;var v,u,t,r;for(var s=0;s<C.length;s+=16){v=B;u=A;t=z;r=y;B=k(B,A,z,y,C[s+0],7,-680876936);y=k(y,B,A,z,C[s+1],12,-389564586);z=k(z,y,B,A,C[s+2],17,606105819);A=k(A,z,y,B,C[s+3],22,-1044525330);B=k(B,A,z,y,C[s+4],7,-176418897);y=k(y,B,A,z,C[s+5],12,1200080426);z=k(z,y,B,A,C[s+6],17,-1473231341);A=k(A,z,y,B,C[s+7],22,-45705983);B=k(B,A,z,y,C[s+8],7,1770035416);y=k(y,B,A,z,C[s+9],12,-1958414417);z=k(z,y,B,A,C[s+10],17,-42063);A=k(A,z,y,B,C[s+11],22,-1990404162);B=k(B,A,z,y,C[s+12],7,1804603682);y=k(y,B,A,z,C[s+13],12,-40341101);z=k(z,y,B,A,C[s+14],17,-1502002290);A=k(A,z,y,B,C[s+15],22,1236535329);B=c(B,A,z,y,C[s+1],5,-165796510);y=c(y,B,A,z,C[s+6],9,-1069501632);z=c(z,y,B,A,C[s+11],14,643717713);A=c(A,z,y,B,C[s+0],20,-373897302);B=c(B,A,z,y,C[s+5],5,-701558691);y=c(y,B,A,z,C[s+10],9,38016083);z=c(z,y,B,A,C[s+15],14,-660478335);A=c(A,z,y,B,C[s+4],20,-405537848);B=c(B,A,z,y,C[s+9],5,568446438);y=c(y,B,A,z,C[s+14],9,-1019803690);z=c(z,y,B,A,C[s+3],14,-187363961);A=c(A,z,y,B,C[s+8],20,1163531501);B=c(B,A,z,y,C[s+13],5,-1444681467);y=c(y,B,A,z,C[s+2],9,-51403784);z=c(z,y,B,A,C[s+7],14,1735328473);A=c(A,z,y,B,C[s+12],20,-1926607734);B=m(B,A,z,y,C[s+5],4,-378558);y=m(y,B,A,z,C[s+8],11,-2022574463);z=m(z,y,B,A,C[s+11],16,1839030562);A=m(A,z,y,B,C[s+14],23,-35309556);B=m(B,A,z,y,C[s+1],4,-1530992060);y=m(y,B,A,z,C[s+4],11,1272893353);z=m(z,y,B,A,C[s+7],16,-155497632);A=m(A,z,y,B,C[s+10],23,-1094730640);B=m(B,A,z,y,C[s+13],4,681279174);y=m(y,B,A,z,C[s+0],11,-358537222);z=m(z,y,B,A,C[s+3],16,-722521979);A=m(A,z,y,B,C[s+6],23,76029189);B=m(B,A,z,y,C[s+9],4,-640364487);y=m(y,B,A,z,C[s+12],11,-421815835);z=m(z,y,B,A,C[s+15],16,530742520);A=m(A,z,y,B,C[s+2],23,-995338651);B=i(B,A,z,y,C[s+0],6,-198630844);y=i(y,B,A,z,C[s+7],10,1126891415);z=i(z,y,B,A,C[s+14],15,-1416354905);A=i(A,z,y,B,C[s+5],21,-57434055);B=i(B,A,z,y,C[s+12],6,1700485571);y=i(y,B,A,z,C[s+3],10,-1894986606);z=i(z,y,B,A,C[s+10],15,-1051523);A=i(A,z,y,B,C[s+1],21,-2054922799);B=i(B,A,z,y,C[s+8],6,1873313359);y=i(y,B,A,z,C[s+15],10,-30611744);z=i(z,y,B,A,C[s+6],15,-1560198380);A=i(A,z,y,B,C[s+13],21,1309151649);B=i(B,A,z,y,C[s+4],6,-145523070);y=i(y,B,A,z,C[s+11],10,-1120210379);z=i(z,y,B,A,C[s+2],15,718787259);A=i(A,z,y,B,C[s+9],21,-343485551);B=j(B,v);A=j(A,u);z=j(z,t);y=j(y,r)}return[B,A,z,y]};var e=function(t,w){var v=b(t);if(v.length>16){v=f(v,t.length*l)}var r=new Array(16),u=new Array(16);for(var s=0;s<16;s++){r[s]=v[s]^909522486;u[s]=v[s]^1549556828}var x=f(r.concat(b(w)),512+w.length*l);return f(u.concat(x),512+128)};var h={hexdigest:function(r){return q(f(b(r),r.length*l))},b64digest:function(r){return p(f(b(r),r.length*l))},hash:function(r){return g(f(b(r),r.length*l))},hmac_hexdigest:function(r,s){return q(e(r,s))},hmac_b64digest:function(r,s){return p(e(r,s))},hmac_hash:function(r,s){return g(e(r,s))},test:function(){return MD5.hexdigest("abc")==="900150983cd24fb0d6963f7d28e17f72"}};return h})();if(!Function.prototype.bind){Function.prototype.bind=function(e){var d=this;var c=Array.prototype.slice;var b=Array.prototype.concat;var a=c.call(arguments,1);return function(){return d.apply(e?e:this,b.call(a,c.call(arguments,0)))}}}if(!Array.prototype.indexOf){Array.prototype.indexOf=function(b){var a=this.length;var c=Number(arguments[1])||0;c=(c<0)?Math.ceil(c):Math.floor(c);if(c<0){c+=a}for(;c<a;c++){if(c in this&&this[c]===b){return c}}return -1}}(function(f){var e;function c(h,g){return new e.Builder(h,g)}function a(g){return new e.Builder("message",g)}function d(g){return new e.Builder("iq",g)}function b(g){return new e.Builder("presence",g)}e={VERSION:"8d27954",NS:{HTTPBIND:"http://jabber.org/protocol/httpbind",BOSH:"urn:xmpp:xbosh",CLIENT:"jabber:client",AUTH:"jabber:iq:auth",ROSTER:"jabber:iq:roster",PROFILE:"jabber:iq:profile",DISCO_INFO:"http://jabber.org/protocol/disco#info",DISCO_ITEMS:"http://jabber.org/protocol/disco#items",MUC:"http://jabber.org/protocol/muc",SASL:"urn:ietf:params:xml:ns:xmpp-sasl",STREAM:"http://etherx.jabber.org/streams",BIND:"urn:ietf:params:xml:ns:xmpp-bind",SESSION:"urn:ietf:params:xml:ns:xmpp-session",VERSION:"jabber:iq:version",STANZAS:"urn:ietf:params:xml:ns:xmpp-stanzas"},addNamespace:function(g,h){e.NS[g]=h},Status:{ERROR:0,CONNECTING:1,CONNFAIL:2,AUTHENTICATING:3,AUTHFAIL:4,CONNECTED:5,DISCONNECTED:6,DISCONNECTING:7,ATTACHED:8},LogLevel:{DEBUG:0,INFO:1,WARN:2,ERROR:3,FATAL:4},ElementType:{NORMAL:1,TEXT:3,CDATA:4},TIMEOUT:1.1,SECONDARY_TIMEOUT:0.1,forEachChild:function(k,l,j){var h,g;for(h=0;h<k.childNodes.length;h++){g=k.childNodes[h];if(g.nodeType==e.ElementType.NORMAL&&(!l||this.isTagEqual(g,l))){j(g)}}},isTagEqual:function(h,g){return h.tagName.toLowerCase()==g.toLowerCase()},_xmlGenerator:null,_makeGenerator:function(){var g;if(document.implementation.createDocument===undefined){g=this._getIEXmlDom();g.appendChild(g.createElement("strophe"))}else{g=document.implementation.createDocument("jabber:client","strophe",null)}return g},xmlGenerator:function(){if(!e._xmlGenerator){e._xmlGenerator=e._makeGenerator()}return e._xmlGenerator},_getIEXmlDom:function(){var h=null;var j=["Msxml2.DOMDocument.6.0","Msxml2.DOMDocument.5.0","Msxml2.DOMDocument.4.0","MSXML2.DOMDocument.3.0","MSXML2.DOMDocument","MSXML.DOMDocument","Microsoft.XMLDOM"];for(var i=0;i<j.length;i++){if(h===null){try{h=new ActiveXObject(j[i])}catch(g){h=null}}else{break}}return h},xmlElement:function(j){if(!j){return null}var m=e.xmlGenerator().createElement(j);var g,l,h;for(g=1;g<arguments.length;g++){if(!arguments[g]){continue}if(typeof(arguments[g])=="string"||typeof(arguments[g])=="number"){m.appendChild(e.xmlTextNode(arguments[g]))}else{if(typeof(arguments[g])=="object"&&typeof(arguments[g].sort)=="function"){for(l=0;l<arguments[g].length;l++){if(typeof(arguments[g][l])=="object"&&typeof(arguments[g][l].sort)=="function"){m.setAttribute(arguments[g][l][0],arguments[g][l][1])}}}else{if(typeof(arguments[g])=="object"){for(h in arguments[g]){if(arguments[g].hasOwnProperty(h)){m.setAttribute(h,arguments[g][h])}}}}}}return m},xmlescape:function(g){g=g.replace(/\&/g,"&amp;");g=g.replace(/</g,"&lt;");g=g.replace(/>/g,"&gt;");g=g.replace(/'/g,"&apos;");g=g.replace(/"/g,"&quot;");return g},xmlTextNode:function(g){g=e.xmlescape(g);return e.xmlGenerator().createTextNode(g)},getText:function(h){if(!h){return null}var j="";if(h.childNodes.length===0&&h.nodeType==e.ElementType.TEXT){j+=h.nodeValue}for(var g=0;g<h.childNodes.length;g++){if(h.childNodes[g].nodeType==e.ElementType.TEXT){j+=h.childNodes[g].nodeValue}}return j},copyElement:function(j){var g,h;if(j.nodeType==e.ElementType.NORMAL){h=e.xmlElement(j.tagName);for(g=0;g<j.attributes.length;g++){h.setAttribute(j.attributes[g].nodeName.toLowerCase(),j.attributes[g].value)}for(g=0;g<j.childNodes.length;g++){h.appendChild(e.copyElement(j.childNodes[g]))}}else{if(j.nodeType==e.ElementType.TEXT){h=e.xmlGenerator().createTextNode(j.nodeValue)}}return h},escapeNode:function(g){return g.replace(/^\s+|\s+$/g,"").replace(/\\/g,"\\5c").replace(/ /g,"\\20").replace(/\"/g,"\\22").replace(/\&/g,"\\26").replace(/\'/g,"\\27").replace(/\//g,"\\2f").replace(/:/g,"\\3a").replace(/</g,"\\3c").replace(/>/g,"\\3e").replace(/@/g,"\\40")},unescapeNode:function(g){return g.replace(/\\20/g," ").replace(/\\22/g,'"').replace(/\\26/g,"&").replace(/\\27/g,"'").replace(/\\2f/g,"/").replace(/\\3a/g,":").replace(/\\3c/g,"<").replace(/\\3e/g,">").replace(/\\40/g,"@").replace(/\\5c/g,"\\")},getNodeFromJid:function(g){if(g.indexOf("@")<0){return null}return g.split("@")[0]},getDomainFromJid:function(g){var h=e.getBareJidFromJid(g);if(h.indexOf("@")<0){return h}else{var i=h.split("@");i.splice(0,1);return i.join("@")}},getResourceFromJid:function(g){var h=g.split("/");if(h.length<2){return null}h.splice(0,1);return h.join("/")},getBareJidFromJid:function(g){return g?g.split("/")[0]:null},log:function(h,g){return},debug:function(g){this.log(this.LogLevel.DEBUG,g)},info:function(g){this.log(this.LogLevel.INFO,g)},warn:function(g){this.log(this.LogLevel.WARN,g)},error:function(g){this.log(this.LogLevel.ERROR,g)},fatal:function(g){this.log(this.LogLevel.FATAL,g)},serialize:function(j){var g;if(!j){return null}if(typeof(j.tree)==="function"){j=j.tree()}var l=j.nodeName;var h,k;if(j.getAttribute("_realname")){l=j.getAttribute("_realname")}g="<"+l;for(h=0;h<j.attributes.length;h++){if(j.attributes[h].nodeName!="_realname"){g+=" "+j.attributes[h].nodeName.toLowerCase()+"='"+j.attributes[h].value.replace(/&/g,"&amp;").replace(/\'/g,"&apos;").replace(/</g,"&lt;")+"'"}}if(j.childNodes.length>0){g+=">";for(h=0;h<j.childNodes.length;h++){k=j.childNodes[h];switch(k.nodeType){case e.ElementType.NORMAL:g+=e.serialize(k);break;case e.ElementType.TEXT:g+=k.nodeValue;break;case e.ElementType.CDATA:g+="<![CDATA["+k.nodeValue+"]]>"}}g+="</"+l+">"}else{g+="/>"}return g},_requestId:0,_connectionPlugins:{},addConnectionPlugin:function(g,h){e._connectionPlugins[g]=h}};e.Builder=function(h,g){if(h=="presence"||h=="message"||h=="iq"){if(g&&!g.xmlns){g.xmlns=e.NS.CLIENT}else{if(!g){g={xmlns:e.NS.CLIENT}}}}this.nodeTree=e.xmlElement(h,g);this.node=this.nodeTree};e.Builder.prototype={tree:function(){return this.nodeTree},toString:function(){return e.serialize(this.nodeTree)},up:function(){this.node=this.node.parentNode;return this},attrs:function(h){for(var g in h){if(h.hasOwnProperty(g)){this.node.setAttribute(g,h[g])}}return this},c:function(h,g,i){var j=e.xmlElement(h,g,i);this.node.appendChild(j);if(!i){this.node=j}return this},cnode:function(i){var k=e.xmlGenerator();try{var h=(k.importNode!==undefined)}catch(j){var h=false}var g=h?k.importNode(i,true):e.copyElement(i);this.node.appendChild(g);this.node=g;return this},t:function(g){var h=e.xmlTextNode(g);this.node.appendChild(h);return this}};e.Handler=function(k,j,h,i,m,l,g){this.handler=k;this.ns=j;this.name=h;this.type=i;this.id=m;this.options=g||{matchbare:false};if(!this.options.matchBare){this.options.matchBare=false}if(this.options.matchBare){this.from=l?e.getBareJidFromJid(l):null}else{this.from=l}this.user=true};e.Handler.prototype={isMatch:function(h){var j;var i=null;if(this.options.matchBare){i=e.getBareJidFromJid(h.getAttribute("from"))}else{i=h.getAttribute("from")}j=false;if(!this.ns){j=true}else{var g=this;e.forEachChild(h,null,function(k){if(k.getAttribute("xmlns")==g.ns){j=true}});j=j||h.getAttribute("xmlns")==this.ns}if(j&&(!this.name||e.isTagEqual(h,this.name))&&(!this.type||h.getAttribute("type")==this.type)&&(!this.id||h.getAttribute("id")==this.id)&&(!this.from||i==this.from)){return true}return false},run:function(h){var g=null;try{g=this.handler(h)}catch(i){if(i.sourceURL){e.fatal("error: "+this.handler+" "+i.sourceURL+":"+i.line+" - "+i.name+": "+i.message)}else{if(i.fileName){if(typeof(console)!="undefined"){console.trace();console.error(this.handler," - error - ",i,i.message)}e.fatal("error: "+this.handler+" "+i.fileName+":"+i.lineNumber+" - "+i.name+": "+i.message)}else{e.fatal("error: "+this.handler)}}throw i}return g},toString:function(){return"{Handler: "+this.handler+"("+this.name+","+this.id+","+this.ns+")}"}};e.TimedHandler=function(h,g){this.period=h;this.handler=g;this.lastCalled=new Date().getTime();this.user=true};e.TimedHandler.prototype={run:function(){this.lastCalled=new Date().getTime();return this.handler()},reset:function(){this.lastCalled=new Date().getTime()},toString:function(){return"{TimedHandler: "+this.handler+"("+this.period+")}"}};e.Request=function(i,h,g,j){this.id=++e._requestId;this.xmlData=i;this.data=e.serialize(i);this.origFunc=h;this.func=h;this.rid=g;this.date=NaN;this.sends=j||0;this.abort=false;this.dead=null;this.age=function(){if(!this.date){return 0}var k=new Date();return(k-this.date)/1000};this.timeDead=function(){if(!this.dead){return 0}var k=new Date();return(k-this.dead)/1000};this.xhr=this._newXHR()};e.Request.prototype={getResponse:function(){var g=null;if(this.xhr.responseXML&&this.xhr.responseXML.documentElement){g=this.xhr.responseXML.documentElement;if(g.tagName=="parsererror"){e.error("invalid response received");e.error("responseText: "+this.xhr.responseText);e.error("responseXML: "+e.serialize(this.xhr.responseXML));throw"parsererror"}}else{if(this.xhr.responseText){e.error("invalid response received");e.error("responseText: "+this.xhr.responseText);e.error("responseXML: "+e.serialize(this.xhr.responseXML))}}return g},_newXHR:function(){var g=null;if(window.XMLHttpRequest){g=new XMLHttpRequest();if(g.overrideMimeType){g.overrideMimeType("text/xml")}}else{if(window.ActiveXObject){g=new ActiveXObject("Microsoft.XMLHTTP")}}g.onreadystatechange=this.func.bind(null,this);return g}};e.Connection=function(g){this.service=g;this.jid="";this.rid=Math.floor(Math.random()*4294967295);this.sid=null;this.streamId=null;this.features=null;this.do_session=false;this.do_bind=false;this.timedHandlers=[];this.handlers=[];this.removeTimeds=[];this.removeHandlers=[];this.addTimeds=[];this.addHandlers=[];this._idleTimeout=null;this._disconnectTimeout=null;this.authenticated=false;this.disconnecting=false;this.connected=false;this.errors=0;this.paused=false;this.hold=1;this.wait=60;this.window=5;this._data=[];this._requests=[];this._uniqueId=Math.round(Math.random()*10000);this._sasl_success_handler=null;this._sasl_failure_handler=null;this._sasl_challenge_handler=null;this._idleTimeout=setTimeout(this._onIdle.bind(this),100);for(var h in e._connectionPlugins){if(e._connectionPlugins.hasOwnProperty(h)){var j=e._connectionPlugins[h];var i=function(){};i.prototype=j;this[h]=new i();this[h].init(this)}}};e.Connection.prototype={reset:function(){this.rid=Math.floor(Math.random()*4294967295);this.sid=null;this.streamId=null;this.do_session=false;this.do_bind=false;this.timedHandlers=[];this.handlers=[];this.removeTimeds=[];this.removeHandlers=[];this.addTimeds=[];this.addHandlers=[];this.authenticated=false;this.disconnecting=false;this.connected=false;this.errors=0;this._requests=[];this._uniqueId=Math.round(Math.random()*10000)},pause:function(){this.paused=true},resume:function(){this.paused=false},getUniqueId:function(g){if(typeof(g)=="string"||typeof(g)=="number"){return ++this._uniqueId+":"+g}else{return ++this._uniqueId+""}},connect:function(h,i,l,k,j){this.jid=h;this.pass=i;this.connect_callback=l;this.disconnecting=false;this.connected=false;this.authenticated=false;this.errors=0;this.wait=k||this.wait;this.hold=j||this.hold;this.domain=e.getDomainFromJid(this.jid);var g=this._buildBody().attrs({to:this.domain,"xml:lang":"en",wait:this.wait,hold:this.hold,content:"text/xml; charset=utf-8",ver:"1.6","xmpp:version":"1.0","xmlns:xmpp":e.NS.BOSH});this._changeConnectStatus(e.Status.CONNECTING,null);this._requests.push(new e.Request(g.tree(),this._onRequestStateChange.bind(this,this._connect_cb.bind(this)),g.tree().getAttribute("rid")));this._throttledRequestHandler()},attach:function(i,g,j,m,l,k,h){this.jid=i;this.sid=g;this.rid=j;this.connect_callback=m;this.domain=e.getDomainFromJid(this.jid);this.authenticated=true;this.connected=true;this.wait=l||this.wait;this.hold=k||this.hold;this.window=h||this.window;this._changeConnectStatus(e.Status.ATTACHED,null)},xmlInput:function(g){return},xmlOutput:function(g){return},rawInput:function(g){return},rawOutput:function(g){return},send:function(h){if(h===null){return}if(typeof(h.sort)==="function"){for(var g=0;g<h.length;g++){this._queueData(h[g])}}else{if(typeof(h.tree)==="function"){this._queueData(h.tree())}else{this._queueData(h)}}this._throttledRequestHandler();clearTimeout(this._idleTimeout);this._idleTimeout=setTimeout(this._onIdle.bind(this),100)},flush:function(){clearTimeout(this._idleTimeout);this._onIdle()},sendIQ:function(j,n,g,k){var l=null;var i=this;if(typeof(j.tree)==="function"){j=j.tree()}var m=j.getAttribute("id");if(!m){m=this.getUniqueId("sendIQ");j.setAttribute("id",m)}var h=this.addHandler(function(p){if(l){i.deleteTimedHandler(l)}var o=p.getAttribute("type");if(o=="result"){if(n){n(p)}}else{if(o=="error"){if(g){g(p)}}else{throw {name:"StropheError",message:"Got bad IQ type of "+o}}}},null,"iq",null,m);if(k){l=this.addTimedHandler(k,function(){i.deleteHandler(h);if(g){g(null)}return false})}this.send(j);return m},_queueData:function(g){if(g===null||!g.tagName||!g.childNodes){throw {name:"StropheError",message:"Cannot queue non-DOMElement."}}this._data.push(g)},_sendRestart:function(){this._data.push("restart");this._throttledRequestHandler();clearTimeout(this._idleTimeout);this._idleTimeout=setTimeout(this._onIdle.bind(this),100)},addTimedHandler:function(i,h){var g=new e.TimedHandler(i,h);this.addTimeds.push(g);return g},deleteTimedHandler:function(g){this.removeTimeds.push(g)},addHandler:function(l,k,i,j,n,m,h){var g=new e.Handler(l,k,i,j,n,m,h);this.addHandlers.push(g);return g},deleteHandler:function(g){this.removeHandlers.push(g)},disconnect:function(g){this._changeConnectStatus(e.Status.DISCONNECTING,g);e.info("Disconnect was called because: "+g);if(this.connected){this._disconnectTimeout=this._addSysTimedHandler(3000,this._onDisconnectTimeout.bind(this));this._sendTerminate()}},_changeConnectStatus:function(g,m){for(var h in e._connectionPlugins){if(e._connectionPlugins.hasOwnProperty(h)){var j=this[h];if(j.statusChanged){try{j.statusChanged(g,m)}catch(i){e.error(""+h+" plugin caused an exception changing status: "+i)}}}}if(this.connect_callback){try{this.connect_callback(g,m)}catch(l){e.error("User connection callback caused an exception: "+l)}}},_buildBody:function(){var g=c("body",{rid:this.rid++,xmlns:e.NS.HTTPBIND});if(this.sid!==null){g.attrs({sid:this.sid})}return g},_removeRequest:function(h){e.debug("removing request");var g;for(g=this._requests.length-1;g>=0;g--){if(h==this._requests[g]){this._requests.splice(g,1)}}h.xhr.onreadystatechange=function(){};this._throttledRequestHandler()},_restartRequest:function(g){var h=this._requests[g];if(h.dead===null){h.dead=new Date()}this._processRequest(g)},_processRequest:function(k){var p=this._requests[k];var s=-1;try{if(p.xhr.readyState==4){s=p.xhr.status}}catch(n){e.error("caught an error in _requests["+k+"], reqStatus: "+s)}if(typeof(s)=="undefined"){s=-1}if(p.sends>5){this._onDisconnectTimeout();return}var j=p.age();var h=(!isNaN(j)&&j>Math.floor(e.TIMEOUT*this.wait));var l=(p.dead!==null&&p.timeDead()>Math.floor(e.SECONDARY_TIMEOUT*this.wait));var r=(p.xhr.readyState==4&&(s<1||s>=500));if(h||l||r){if(l){e.error("Request "+this._requests[k].id+" timed out (secondary), restarting")}p.abort=true;p.xhr.abort();p.xhr.onreadystatechange=function(){};this._requests[k]=new e.Request(p.xmlData,p.origFunc,p.rid,p.sends);p=this._requests[k]}if(p.xhr.readyState===0){e.debug("request id "+p.id+"."+p.sends+" posting");try{var g=!("sync" in this&&this.sync===true);p.xhr.open("POST",this.service,g)}catch(o){e.error("XHR open failed.");if(!this.connected){this._changeConnectStatus(e.Status.CONNFAIL,"bad-service")}this.disconnect();return}var q=function(){p.date=new Date();p.xhr.send(p.data)};if(p.sends>1){var m=Math.min(Math.floor(e.TIMEOUT*this.wait),Math.pow(p.sends,3))*1000;setTimeout(q,m)}else{q()}p.sends++;if(this.xmlOutput!==e.Connection.prototype.xmlOutput){this.xmlOutput(p.xmlData)}if(this.rawOutput!==e.Connection.prototype.rawOutput){this.rawOutput(p.data)}}else{e.debug("_processRequest: "+(k===0?"first":"second")+" request has readyState of "+p.xhr.readyState)}},_throttledRequestHandler:function(){if(!this._requests){e.debug("_throttledRequestHandler called with undefined requests")}else{e.debug("_throttledRequestHandler called with "+this._requests.length+" requests")}if(!this._requests||this._requests.length===0){return}if(this._requests.length>0){this._processRequest(0)}if(this._requests.length>1&&Math.abs(this._requests[0].rid-this._requests[1].rid)<this.window){this._processRequest(1)}},_onRequestStateChange:function(j,i){e.debug("request id "+i.id+"."+i.sends+" state changed to "+i.xhr.readyState);if(i.abort){i.abort=false;return}var h;if(i.xhr.readyState==4){h=0;try{h=i.xhr.status}catch(k){}if(typeof(h)=="undefined"){h=0}if(this.disconnecting){if(h>=400){this._hitError(h);return}}var g=(this._requests[0]==i);var l=(this._requests[1]==i);if((h>0&&h<500)||i.sends>5){this._removeRequest(i);e.debug("request id "+i.id+" should now be removed")}if(h==200){if(l||(g&&this._requests.length>0&&this._requests[0].age()>Math.floor(e.SECONDARY_TIMEOUT*this.wait))){this._restartRequest(0)}e.debug("request id "+i.id+"."+i.sends+" got 200");j(i);this.errors=0}else{e.error("request id "+i.id+"."+i.sends+" error "+h+" happened");if(h===0||(h>=400&&h<600)||h>=12000){this._hitError(h);if(h>=400&&h<500){this._changeConnectStatus(e.Status.DISCONNECTING,null);this._doDisconnect()}}}if(!((h>0&&h<500)||i.sends>5)){this._throttledRequestHandler()}}},_hitError:function(g){this.errors++;e.warn("request errored, status: "+g+", number of errors: "+this.errors);if(this.errors>4){this._onDisconnectTimeout()}},_doDisconnect:function(){e.info("_doDisconnect was called");this.authenticated=false;this.disconnecting=false;this.sid=null;this.streamId=null;this.rid=Math.floor(Math.random()*4294967295);if(this.connected){this._changeConnectStatus(e.Status.DISCONNECTED,null);this.connected=false}this.handlers=[];this.timedHandlers=[];this.removeTimeds=[];this.removeHandlers=[];this.addTimeds=[];this.addHandlers=[]},_dataRecv:function(p){try{var g=p.getResponse()}catch(n){if(n!="parsererror"){throw n}this.disconnect("strophe-parsererror")}if(g===null){return}if(this.xmlInput!==e.Connection.prototype.xmlInput){this.xmlInput(g)}if(this.rawInput!==e.Connection.prototype.rawInput){this.rawInput(e.serialize(g))}var l,j;while(this.removeHandlers.length>0){j=this.removeHandlers.pop();l=this.handlers.indexOf(j);if(l>=0){this.handlers.splice(l,1)}}while(this.addHandlers.length>0){this.handlers.push(this.addHandlers.pop())}if(this.disconnecting&&this._requests.length===0){this.deleteTimedHandler(this._disconnectTimeout);this._disconnectTimeout=null;this._doDisconnect();return}var h=g.getAttribute("type");var o,k;if(h!==null&&h=="terminate"){if(this.disconnecting){return}o=g.getAttribute("condition");k=g.getElementsByTagName("conflict");if(o!==null){if(o=="remote-stream-error"&&k.length>0){o="conflict"}this._changeConnectStatus(e.Status.CONNFAIL,o)}else{this._changeConnectStatus(e.Status.CONNFAIL,"unknown")}this.disconnect();return}var m=this;e.forEachChild(g,null,function(u){var r,s;s=m.handlers;m.handlers=[];for(r=0;r<s.length;r++){var q=s[r];try{if(q.isMatch(u)&&(m.authenticated||!q.user)){if(q.run(u)){m.handlers.push(q)}}else{m.handlers.push(q)}}catch(t){}}})},_sendTerminate:function(){e.info("_sendTerminate was called");var g=this._buildBody().attrs({type:"terminate"});if(this.authenticated){g.c("presence",{xmlns:e.NS.CLIENT,type:"unavailable"})}this.disconnecting=true;var h=new e.Request(g.tree(),this._onRequestStateChange.bind(this,this._dataRecv.bind(this)),g.tree().getAttribute("rid"));this._requests.push(h);this._throttledRequestHandler()},_connect_cb:function(v){e.info("_connect_cb was called");this.connected=true;var h=v.getResponse();if(!h){return}if(this.xmlInput!==e.Connection.prototype.xmlInput){this.xmlInput(h)}if(this.rawInput!==e.Connection.prototype.rawInput){this.rawInput(e.serialize(h))}var m=h.getAttribute("type");var u,o;if(m!==null&&m=="terminate"){u=h.getAttribute("condition");o=h.getElementsByTagName("conflict");if(u!==null){if(u=="remote-stream-error"&&o.length>0){u="conflict"}this._changeConnectStatus(e.Status.CONNFAIL,u)}else{this._changeConnectStatus(e.Status.CONNFAIL,"unknown")}return}if(!this.sid){this.sid=h.getAttribute("sid")}if(!this.stream_id){this.stream_id=h.getAttribute("authid")}var j=h.getAttribute("requests");if(j){this.window=parseInt(j,10)}var g=h.getAttribute("hold");if(g){this.hold=parseInt(g,10)}var q=h.getAttribute("wait");if(q){this.wait=parseInt(q,10)}var w=false;var l=false;var t=false;var x=h.getElementsByTagName("mechanism");var n,s,p,k;if(x.length>0){for(n=0;n<x.length;n++){s=e.getText(x[n]);if(s=="DIGEST-MD5"){l=true}else{if(s=="PLAIN"){w=true}else{if(s=="ANONYMOUS"){t=true}}}}}else{var r=this._buildBody();this._requests.push(new e.Request(r.tree(),this._onRequestStateChange.bind(this,this._connect_cb.bind(this)),r.tree().getAttribute("rid")));this._throttledRequestHandler();return}if(e.getNodeFromJid(this.jid)===null&&t){this._changeConnectStatus(e.Status.AUTHENTICATING,null);this._sasl_success_handler=this._addSysHandler(this._sasl_success_cb.bind(this),null,"success",null,null);this._sasl_failure_handler=this._addSysHandler(this._sasl_failure_cb.bind(this),null,"failure",null,null);this.send(c("auth",{xmlns:e.NS.SASL,mechanism:"ANONYMOUS"}).tree())}else{if(e.getNodeFromJid(this.jid)===null){this._changeConnectStatus(e.Status.CONNFAIL,"x-strophe-bad-non-anon-jid");this.disconnect()}else{if(l){this._changeConnectStatus(e.Status.AUTHENTICATING,null);this._sasl_challenge_handler=this._addSysHandler(this._sasl_challenge1_cb.bind(this),null,"challenge",null,null);this._sasl_failure_handler=this._addSysHandler(this._sasl_failure_cb.bind(this),null,"failure",null,null);this.send(c("auth",{xmlns:e.NS.SASL,mechanism:"DIGEST-MD5"}).tree())}else{if(w){p=unescape(encodeURIComponent(e.getBareJidFromJid(this.jid)));p=p+"\u0000";p=p+unescape(encodeURIComponent(e.getNodeFromJid(this.jid)));p=p+"\u0000";p=p+this.pass;this._changeConnectStatus(e.Status.AUTHENTICATING,null);this._sasl_success_handler=this._addSysHandler(this._sasl_success_cb.bind(this),null,"success",null,null);this._sasl_failure_handler=this._addSysHandler(this._sasl_failure_cb.bind(this),null,"failure",null,null);k=Base64.encode(p);this.send(c("auth",{xmlns:e.NS.SASL,mechanism:"PLAIN"}).t(k).tree())}else{this._changeConnectStatus(e.Status.AUTHENTICATING,null);this._addSysHandler(this._auth1_cb.bind(this),null,null,null,"_auth_1");this.send(d({type:"get",to:this.domain,id:"_auth_1"}).c("query",{xmlns:e.NS.AUTH}).c("username",{}).t(e.getNodeFromJid(this.jid)).tree())}}}}},_sasl_challenge1_cb:function(k){var h=/([a-z]+)=("[^"]+"|[^,"]+)(?:,|$)/;var q=Base64.decode(e.getText(k));var r=MD5.hexdigest(""+(Math.random()*1234567890));var n="";var s=null;var o="";var g="";var m;this.deleteHandler(this._sasl_failure_handler);while(q.match(h)){m=q.match(h);q=q.replace(m[0],"");m[2]=m[2].replace(/^"(.+)"$/,"$1");switch(m[1]){case"realm":n=m[2];break;case"nonce":o=m[2];break;case"qop":g=m[2];break;case"host":s=m[2];break}}var l="xmpp/"+this.domain;if(s!==null){l=l+"/"+s}var j=MD5.hash(unescape(encodeURIComponent(e.getNodeFromJid(this.jid)))+":"+n+":"+this.pass)+":"+o+":"+r;var i="AUTHENTICATE:"+l;var p="";p+="username="+this._quote(unescape(encodeURIComponent(e.getNodeFromJid(this.jid))))+",";p+="realm="+this._quote(n)+",";p+="nonce="+this._quote(o)+",";p+="cnonce="+this._quote(r)+",";p+='nc="00000001",';p+='qop="auth",';p+="digest-uri="+this._quote(l)+",";p+="response="+this._quote(MD5.hexdigest(MD5.hexdigest(j)+":"+o+":00000001:"+r+":auth:"+MD5.hexdigest(i)))+",";p+='charset="utf-8"';this._sasl_challenge_handler=this._addSysHandler(this._sasl_challenge2_cb.bind(this),null,"challenge",null,null);this._sasl_success_handler=this._addSysHandler(this._sasl_success_cb.bind(this),null,"success",null,null);this._sasl_failure_handler=this._addSysHandler(this._sasl_failure_cb.bind(this),null,"failure",null,null);this.send(c("response",{xmlns:e.NS.SASL}).t(Base64.encode(p)).tree());return false},_quote:function(g){return'"'+g.replace(/\\/g,"\\\\").replace(/"/g,'\\"')+'"'},_sasl_challenge2_cb:function(g){this.deleteHandler(this._sasl_success_handler);this.deleteHandler(this._sasl_failure_handler);this._sasl_success_handler=this._addSysHandler(this._sasl_success_cb.bind(this),null,"success",null,null);this._sasl_failure_handler=this._addSysHandler(this._sasl_failure_cb.bind(this),null,"failure",null,null);this.send(c("response",{xmlns:e.NS.SASL}).tree());return false},_auth1_cb:function(g){var h=d({type:"set",id:"_auth_2"}).c("query",{xmlns:e.NS.AUTH}).c("username",{}).t(e.getNodeFromJid(this.jid)).up().c("password").t(this.pass);if(!e.getResourceFromJid(this.jid)){this.jid=e.getBareJidFromJid(this.jid)+"/strophe"}h.up().c("resource",{}).t(e.getResourceFromJid(this.jid));this._addSysHandler(this._auth2_cb.bind(this),null,null,null,"_auth_2");this.send(h.tree());return false},_sasl_success_cb:function(g){e.info("SASL authentication succeeded.");this.deleteHandler(this._sasl_failure_handler);this._sasl_failure_handler=null;if(this._sasl_challenge_handler){this.deleteHandler(this._sasl_challenge_handler);this._sasl_challenge_handler=null}this._addSysHandler(this._sasl_auth1_cb.bind(this),null,"stream:features",null,null);this._sendRestart();return false},_sasl_auth1_cb:function(h){this.features=h;var g,k;for(g=0;g<h.childNodes.length;g++){k=h.childNodes[g];if(k.nodeName=="bind"){this.do_bind=true}if(k.nodeName=="session"){this.do_session=true}}if(!this.do_bind){this._changeConnectStatus(e.Status.AUTHFAIL,null);return false}else{this._addSysHandler(this._sasl_bind_cb.bind(this),null,null,null,"_bind_auth_2");var j=e.getResourceFromJid(this.jid);if(j){this.send(d({type:"set",id:"_bind_auth_2"}).c("bind",{xmlns:e.NS.BIND}).c("resource",{}).t(j).tree())}else{this.send(d({type:"set",id:"_bind_auth_2"}).c("bind",{xmlns:e.NS.BIND}).tree())}}return false},_sasl_bind_cb:function(g){if(g.getAttribute("type")=="error"){e.info("SASL binding failed.");this._changeConnectStatus(e.Status.AUTHFAIL,null);return false}var i=g.getElementsByTagName("bind");var h;if(i.length>0){h=i[0].getElementsByTagName("jid");if(h.length>0){this.jid=e.getText(h[0]);if(this.do_session){this._addSysHandler(this._sasl_session_cb.bind(this),null,null,null,"_session_auth_2");this.send(d({type:"set",id:"_session_auth_2"}).c("session",{xmlns:e.NS.SESSION}).tree())}else{this.authenticated=true;this._changeConnectStatus(e.Status.CONNECTED,null)}}}else{e.info("SASL binding failed.");this._changeConnectStatus(e.Status.AUTHFAIL,null);return false}},_sasl_session_cb:function(g){if(g.getAttribute("type")=="result"){this.authenticated=true;this._changeConnectStatus(e.Status.CONNECTED,null)}else{if(g.getAttribute("type")=="error"){e.info("Session creation failed.");this._changeConnectStatus(e.Status.AUTHFAIL,null);return false}}return false},_sasl_failure_cb:function(g){if(this._sasl_success_handler){this.deleteHandler(this._sasl_success_handler);this._sasl_success_handler=null}if(this._sasl_challenge_handler){this.deleteHandler(this._sasl_challenge_handler);this._sasl_challenge_handler=null}this._changeConnectStatus(e.Status.AUTHFAIL,null);return false},_auth2_cb:function(g){if(g.getAttribute("type")=="result"){this.authenticated=true;this._changeConnectStatus(e.Status.CONNECTED,null)}else{if(g.getAttribute("type")=="error"){this._changeConnectStatus(e.Status.AUTHFAIL,null);this.disconnect()}}return false},_addSysTimedHandler:function(i,h){var g=new e.TimedHandler(i,h);g.user=false;this.addTimeds.push(g);return g},_addSysHandler:function(k,j,h,i,l){var g=new e.Handler(k,j,h,i,l);g.user=false;this.addHandlers.push(g);return g},_onDisconnectTimeout:function(){e.info("_onDisconnectTimeout was called");var g;while(this._requests.length>0){g=this._requests.pop();g.abort=true;g.xhr.abort();g.xhr.onreadystatechange=function(){}}this._doDisconnect();return false},_onIdle:function(){var j,l,n,k;while(this.addTimeds.length>0){this.timedHandlers.push(this.addTimeds.pop())}while(this.removeTimeds.length>0){l=this.removeTimeds.pop();j=this.timedHandlers.indexOf(l);if(j>=0){this.timedHandlers.splice(j,1)}}var h=new Date().getTime();k=[];for(j=0;j<this.timedHandlers.length;j++){l=this.timedHandlers[j];if(this.authenticated||!l.user){n=l.lastCalled+l.period;if(n-h<=0){if(l.run()){k.push(l)}}else{k.push(l)}}}this.timedHandlers=k;var g,m;if(this.authenticated&&this._requests.length===0&&this._data.length===0&&!this.disconnecting){e.info("no requests during idle cycle, sending blank request");this._data.push(null)}if(this._requests.length<2&&this._data.length>0&&!this.paused){g=this._buildBody();for(j=0;j<this._data.length;j++){if(this._data[j]!==null){if(this._data[j]==="restart"){g.attrs({to:this.domain,"xml:lang":"en","xmpp:restart":"true","xmlns:xmpp":e.NS.BOSH})}else{g.cnode(this._data[j]).up()}}}delete this._data;this._data=[];this._requests.push(new e.Request(g.tree(),this._onRequestStateChange.bind(this,this._dataRecv.bind(this)),g.tree().getAttribute("rid")));this._processRequest(this._requests.length-1)}if(this._requests.length>0){m=this._requests[0].age();if(this._requests[0].dead!==null){if(this._requests[0].timeDead()>Math.floor(e.SECONDARY_TIMEOUT*this.wait)){this._throttledRequestHandler()}}if(m>Math.floor(e.TIMEOUT*this.wait)){e.warn("Request "+this._requests[0].id+" timed out, over "+Math.floor(e.TIMEOUT*this.wait)+" seconds since last activity");this._throttledRequestHandler()}}clearTimeout(this._idleTimeout);if(this.connected){this._idleTimeout=setTimeout(this._onIdle.bind(this),100)}}};if(f){f(e,c,a,d,b)}})(function(){window.Strophe=arguments[0];window.$build=arguments[1];window.$msg=arguments[2];window.$iq=arguments[3];window.$pres=arguments[4]});Strophe.addConnectionPlugin("muc",{_connection:null,init:function(a){this._connection=a;Strophe.addNamespace("MUC_OWNER",Strophe.NS.MUC+"#owner");Strophe.addNamespace("MUC_ADMIN",Strophe.NS.MUC+"#admin")},join:function(g,a,c,d,b){var f=this.test_append_nick(g,a);var h=$pres({from:this._connection.jid,to:f}).c("x",{xmlns:Strophe.NS.MUC});if(b){var e=Strophe.xmlElement("password",[],b);h.cnode(e)}if(c){this._connection.addHandler(function(j){var k=j.getAttribute("from");var i=k.split("/");if(i[0]==g){return c(j)}else{return true}},null,"message",null,null,null)}if(d){this._connection.addHandler(function(k){var m=k.getElementsByTagName("x");if(m.length>0){for(var j=0;j<m.length;j++){var l=m[j].getAttribute("xmlns");if(l&&l.match(Strophe.NS.MUC)){return d(k)}}}return true},null,"presence",null,null,null)}this._connection.send(h)},leave:function(f,a,c){var e=this.test_append_nick(f,a);var d=this._connection.getUniqueId();var b=$pres({type:"unavailable",id:d,from:this._connection.jid,to:e}).c("x",{xmlns:Strophe.NS.MUC});this._connection.addHandler(c,null,"presence",null,d,null);this._connection.send(b);return d},message:function(f,a,d,b){var e=this.test_append_nick(f,a);b=b||"groupchat";var c=this._connection.getUniqueId();var g=$msg({to:e,from:this._connection.jid,type:b,id:c}).c("body",{xmlns:Strophe.NS.CLIENT}).t(d);g.up().c("x",{xmlns:"jabber:x:event"}).c("composing");this._connection.send(g);return c},configure:function(b){var a=$iq({to:b,type:"get"}).c("query",{xmlns:Strophe.NS.MUC_OWNER});var c=a.tree();return this._connection.sendIQ(c,function(){},function(){})},cancelConfigure:function(b){var a=$iq({to:b,type:"set"}).c("query",{xmlns:Strophe.NS.MUC_OWNER}).c("x",{xmlns:"jabber:x:data",type:"cancel"});var c=a.tree();return this._connection.sendIQ(c,function(){},function(){})},saveConfiguration:function(d,c){var a=$iq({to:d,type:"set"}).c("query",{xmlns:Strophe.NS.MUC_OWNER}).c("x",{xmlns:"jabber:x:data",type:"submit"});for(var b=0;b<c.length;b++){a.cnode(c[b]);a.up()}var e=a.tree();return this._connection.sendIQ(e,function(){},function(){})},createInstantRoom:function(b){var a=$iq({to:b,type:"set"}).c("query",{xmlns:Strophe.NS.MUC_OWNER}).c("x",{xmlns:"jabber:x:data",type:"submit"});return this._connection.sendIQ(a.tree(),function(){},function(){})},setTopic:function(b,a){var c=$msg({to:b,from:this._connection.jid,type:"groupchat"}).c("subject",{xmlns:"jabber:client"}).t(a);this._connection.send(c.tree())},modifyUser:function(g,b,h,d,f){var a={nick:Strophe.escapeNode(b)};if(h!==null){a.role=h}if(d!==null){a.affiliation=d}var e=$build("item",a);if(f!==null){e.cnode(Strophe.xmlElement("reason",f))}var c=$iq({to:g,type:"set"}).c("query",{xmlns:Strophe.NS.MUC_OWNER}).cnode(e.tree());return this._connection.sendIQ(c.tree(),function(){},function(){})},changeNick:function(d,a){var c=this.test_append_nick(d,a);var b=$pres({from:this._connection.jid,to:c}).c("x",{xmlns:Strophe.NS.MUC});this._connection.send(b.tree())},listRooms:function(c,a){var b=$iq({to:c,from:this._connection.jid,type:"get"}).c("query",{xmlns:Strophe.NS.DISCO_ITEMS});this._connection.sendIQ(b,a,function(){})},test_append_nick:function(c,a){var b=c;if(a){b+="/"+Strophe.escapeNode(a)}return b}});var Mustache=function(){var a=function(){};a.prototype={otag:"{{",ctag:"}}",pragmas:{},buffer:[],pragmas_implemented:{"IMPLICIT-ITERATOR":true},context:{},render:function(e,d,c,f){if(!f){this.context=d;this.buffer=[]}if(!this.includes("",e)){if(f){return e}else{this.send(e);return}}e=this.render_pragmas(e);var b=this.render_section(e,d,c);if(f){return this.render_tags(b,d,c,f)}this.render_tags(b,d,c,f)},send:function(b){if(b!==""){this.buffer.push(b)}},render_pragmas:function(b){if(!this.includes("%",b)){return b}var d=this;var c=new RegExp(this.otag+"%([\\w-]+) ?([\\w]+=[\\w]+)?"+this.ctag,"g");return b.replace(c,function(g,e,f){if(!d.pragmas_implemented[e]){throw ({message:"This implementation of mustache doesn't understand the '"+e+"' pragma"})}d.pragmas[e]={};if(f){var h=f.split("=");d.pragmas[e][h[0]]=h[1]}return""})},render_partial:function(b,d,c){b=this.trim(b);if(!c||c[b]===undefined){throw ({message:"unknown_partial '"+b+"'"})}if(typeof(d[b])!="object"){return this.render(c[b],d,c,true)}return this.render(c[b],d[b],c,true)},render_section:function(d,c,b){if(!this.includes("#",d)&&!this.includes("^",d)){return d}var f=this;var e=new RegExp(this.otag+"(\\^|\\#)\\s*(.+)\\s*"+this.ctag+"\n*([\\s\\S]+?)"+this.otag+"\\/\\s*\\2\\s*"+this.ctag+"\\s*","mg");return d.replace(e,function(h,i,g,j){var k=f.find(g,c);if(i=="^"){if(!k||f.is_array(k)&&k.length===0){return f.render(j,c,b,true)}else{return""}}else{if(i=="#"){if(f.is_array(k)){return f.map(k,function(l){return f.render(j,f.create_context(l),b,true)}).join("")}else{if(f.is_object(k)){return f.render(j,f.create_context(k),b,true)}else{if(typeof k==="function"){return k.call(c,j,function(l){return f.render(l,c,b,true)})}else{if(k){return f.render(j,c,b,true)}else{return""}}}}}}})},render_tags:function(k,b,d,f){var e=this;var j=function(){return new RegExp(e.otag+"(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?"+e.ctag+"+","g")};var g=j();var h=function(n,i,m){switch(i){case"!":return"";case"=":e.set_delimiters(m);g=j();return"";case">":return e.render_partial(m,b,d);case"{":return e.find(m,b);default:return e.escape(e.find(m,b))}};var l=k.split("\n");for(var c=0;c<l.length;c++){l[c]=l[c].replace(g,h,this);if(!f){this.send(l[c])}}if(f){return l.join("\n")}},set_delimiters:function(c){var b=c.split(" ");this.otag=this.escape_regex(b[0]);this.ctag=this.escape_regex(b[1])},escape_regex:function(c){if(!arguments.callee.sRE){var b=["/",".","*","+","?","|","(",")","[","]","{","}","\\"];arguments.callee.sRE=new RegExp("(\\"+b.join("|\\")+")","g")}return c.replace(arguments.callee.sRE,"\\$1")},find:function(c,d){c=this.trim(c);function b(f){return f===false||f===0||f}var e;if(b(d[c])){e=d[c]}else{if(b(this.context[c])){e=this.context[c]}}if(typeof e==="function"){return e.apply(d)}if(e!==undefined){return e}return""},includes:function(c,b){return b.indexOf(this.otag+c)!=-1},escape:function(b){b=String(b===null?"":b);return b.replace(/&(?!\w+;)|["'<>\\]/g,function(c){switch(c){case"&":return"&amp;";case"\\":return"\\\\";case'"':return"&quot;";case"'":return"&#39;";case"<":return"&lt;";case">":return"&gt;";default:return c}})},create_context:function(c){if(this.is_object(c)){return c}else{var d=".";if(this.pragmas["IMPLICIT-ITERATOR"]){d=this.pragmas["IMPLICIT-ITERATOR"].iterator}var b={};b[d]=c;return b}},is_object:function(b){return b&&typeof b=="object"},is_array:function(b){return Object.prototype.toString.call(b)==="[object Array]"},trim:function(b){return b.replace(/^\s*|\s*$/g,"")},map:function(f,d){if(typeof f.map=="function"){return f.map(d)}else{var e=[];var b=f.length;for(var c=0;c<b;c++){e.push(d(f[c]))}return e}}};return({name:"mustache.js",version:"0.3.1-dev",to_html:function(d,b,c,f){var e=new a();if(f){e.send=f}e.render(d,b,c);if(!f){return e.buffer.join("\n")}}})}();(function(a){a.i18n={dict:null,setDictionary:function(b){this.dict=b},_:function(d,c){var b=d;if(this.dict&&this.dict[d]){b=this.dict[d]}return this.printf(b,c)},toEntity:function(d){var b="";for(var c=0;c<d.length;c++){if(d.charCodeAt(c)>128){b+="&#"+d.charCodeAt(c)+";"}else{b+=d.charAt(c)}}return b},stripStr:function(b){return b.replace(/^\s*/,"").replace(/\s*$/,"")},stripStrML:function(d){var c=d.split("\n");for(var b=0;b<c.length;b++){c[b]=stripStr(c[b])}return stripStr(c.join(" "))},printf:function(g,b){if(!b){return g}var f="",e=/%(\d+)\$s/g;while(result=e.exec(g)){var c=parseInt(result[1],10)-1;g=g.replace("%"+result[1]+"$s",(b[c]));b.splice(c,1)}var h=g.split("%s");if(h.length>1){for(var d=0;d<b.length;d++){if(h[d].lastIndexOf("%")==h[d].length-1&&d!=b.length-1){h[d]+="s"+h.splice(d+1,1)[0]}f+=h[d]+b[d]}}return f+h[h.length-1]}};a.fn._t=function(c,b){return a(this).text(a.i18n._(c,b))}})(jQuery);var dateFormat=function(){var a=/d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g,b=/\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g,d=/[^-+\dA-Z]/g,c=function(f,e){f=String(f);e=e||2;while(f.length<e){f="0"+f}return f};return function(i,v,q){var g=dateFormat;if(arguments.length==1&&Object.prototype.toString.call(i)=="[object String]"&&!/\d/.test(i)){v=i;i=undefined}i=i?new Date(i):new Date;if(isNaN(i)){throw SyntaxError("invalid date")}v=String(g.masks[v]||v||g.masks["default"]);if(v.slice(0,4)=="UTC:"){v=v.slice(4);q=true}var t=q?"getUTC":"get",l=i[t+"Date"](),e=i[t+"Day"](),j=i[t+"Month"](),p=i[t+"FullYear"](),r=i[t+"Hours"](),k=i[t+"Minutes"](),u=i[t+"Seconds"](),n=i[t+"Milliseconds"](),f=q?0:i.getTimezoneOffset(),h={d:l,dd:c(l),ddd:g.i18n.dayNames[e],dddd:g.i18n.dayNames[e+7],m:j+1,mm:c(j+1),mmm:g.i18n.monthNames[j],mmmm:g.i18n.monthNames[j+12],yy:String(p).slice(2),yyyy:p,h:r%12||12,hh:c(r%12||12),H:r,HH:c(r),M:k,MM:c(k),s:u,ss:c(u),l:c(n,3),L:c(n>99?Math.round(n/10):n),t:r<12?"a":"p",tt:r<12?"am":"pm",T:r<12?"A":"P",TT:r<12?"AM":"PM",Z:q?"UTC":(String(i).match(b)||[""]).pop().replace(d,""),o:(f>0?"-":"+")+c(Math.floor(Math.abs(f)/60)*100+Math.abs(f)%60,4),S:["th","st","nd","rd"][l%10>3?0:(l%100-l%10!=10)*l%10]};return v.replace(a,function(m){return m in h?h[m]:m.slice(1,m.length-1)})}}();dateFormat.masks={"default":"ddd mmm dd yyyy HH:MM:ss",shortDate:"m/d/yy",mediumDate:"mmm d, yyyy",longDate:"mmmm d, yyyy",fullDate:"dddd, mmmm d, yyyy",shortTime:"h:MM TT",mediumTime:"h:MM:ss TT",longTime:"h:MM:ss TT Z",isoDate:"yyyy-mm-dd",isoTime:"HH:MM:ss",isoDateTime:"yyyy-mm-dd'T'HH:MM:ss",isoUtcDateTime:"UTC:yyyy-mm-dd'T'HH:MM:ss'Z'"};dateFormat.i18n={dayNames:["Sun","Mon","Tue","Wed","Thu","Fri","Sat","Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],monthNames:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec","January","February","March","April","May","June","July","August","September","October","November","December"]};Date.prototype.format=function(a,b){return dateFormat(this,a,b)};
\ No newline at end of file
/**
* Chat CSS
*
* @author Michael <michael.weibel@gmail.com>
* @author Patrick <patrick.stadler@gmail.com>
* @copyright 2011 Amiado Group AG, All rights reserved.
*/
html, body {
margin: 0;
padding: 0;
font-family: Arial, Helvetica, sans-serif;
}
#candy {
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: 0;
background-color: #ccc;
color: #333;
overflow: hidden;
}
a {
color: #333;
text-decoration: none;
}
ul {
list-style: none;
padding: 0;
margin: 0;
}
#chat-tabs {
list-style: none;
margin: 0 200px 0 0;
padding: 0;
overflow: auto;
overflow-y: hidden;
}
#chat-tabs li {
margin: 0;
float: left;
position: relative;
border-right: 1px solid #aaa;
white-space: nowrap;
}
#chat-tabs li a {
background-color: #ddd;
padding: 6px 50px 4px 10px;
display: inline-block;
color: #666;
height: 20px;
}
#chat-tabs li.active a {
background-color: white;
color: black;
}
#chat-tabs li a.transition {
position: absolute;
top: 0;
right: 0;
padding: 0;
width: 35px;
height: 30px;
background: url(img/tab-transitions.png) repeat-y left;
}
#chat-tabs li a.close {
background-color: transparent;
position: absolute;
top: 0;
right: 0;
height: auto;
padding: 5px;
margin: 1px 5px 0 2px;
color: #999;
}
#chat-tabs li.active a.transition {
color: gray;
background: url(img/tab-transitions.png) repeat-y -50px;
}
#chat-tabs li a.close:hover, #chat-tabs li.active a.close:hover {
color: black;
}
#chat-tabs li .unread {
color: white;
background-color: #9b1414;
padding: 2px 4px;
font-weight: bold;
font-size: 10px;
position: absolute;
top: 6px;
right: 22px;
border-radius: 3px;
}
#chat-tabs li.offline a.label {
text-decoration: line-through;
}
#chat-toolbar {
position: fixed;
bottom: 0;
right: 0;
font-size: 11px;
color: #666;
width: 200px;
height: 24px;
padding-top: 7px;
border-top: 1px solid #aaa;
background-color: #d9d9d9;
display: none;
}
#chat-toolbar li {
width: 16px;
height: 16px;
margin-left: 5px;
float: left;
display: inline-block;
cursor: pointer;
background-position: top left;
background-repeat: no-repeat;
}
#chat-toolbar #emoticons-icon {
background-image: url(img/action/emoticons.png);
}
#chat-toolbar .context {
background-image: url(img/action/settings.png);
display: none;
}
.role-moderator #chat-toolbar .context, .affiliation-owner #chat-toolbar .context {
display: inline-block;
}
#chat-sound-control {
background-image: url(img/action/sound-off.png);
}
#chat-sound-control.checked {
background-image: url(img/action/sound-on.png);
}
#chat-autoscroll-control {
background-image: url(img/action/autoscroll-off.png);
}
#chat-autoscroll-control.checked {
background-image: url(img/action/autoscroll-on.png);
}
#chat-statusmessage-control {
background: url(img/action/statusmessage-off.png);
}
#chat-statusmessage-control.checked {
background: url(img/action/statusmessage-on.png);
}
#chat-toolbar .usercount {
background-image: url(img/action/usercount.png);
cursor: default;
padding-left: 20px;
width: auto;
margin-right: 5px;
float: right;
}
.usercount span {
display: inline-block;
padding: 1px 3px;
background-color: #ccc;
font-weight: bold;
border-radius: 3px;
}
.room-pane {
display: none;
}
.roster-pane {
position: absolute;
overflow: auto;
top: 0;
right: 0;
bottom: 0;
width: 200px;
margin: 30px 0 32px 0;
}
.roster-pane .user {
cursor: pointer;
padding: 4px 7px;
font-size: 12px;
margin: 0 4px 2px 4px;
opacity: 0;
display: none;
color: #666;
clear: both;
height: 15px;
background-color: #ddd;
}
.roster-pane .user:hover {
background-color: #eee;
}
.roster-pane .user.status-ignored {
cursor: default;
}
.roster-pane .user.me {
font-weight: bold;
cursor: default;
}
.roster-pane .user.me:hover {
background-color: #ddd;
}
.roster-pane .label {
float: left;
width: 110px;
overflow: hidden;
white-space: nowrap;
}
.roster-pane li {
width: 16px;
height: 16px;
float: right;
display: block;
margin-left: 3px;
background-repeat: no-repeat;
background-position: center;
}
.roster-pane li.role {
cursor: default;
display: none;
}
.roster-pane li.role-moderator {
background-image: url(img/roster/role-moderator.png);
display: block;
}
.roster-pane li.affiliation-owner {
background-image: url(img/roster/affiliation-owner.png);
display: block;
}
.roster-pane li.ignore {
background-image: url(img/roster/ignore.png);
display: none;
}
.roster-pane .status-ignored li.ignore {
display: block;
}
.roster-pane .me li.context {
display: none;
}
.roster-pane li.context {
background-image: url(img/action/menu.png);
cursor: pointer;
}
.roster-pane li.context:hover {
background-color: #ccc;
border-radius: 4px;
}
.message-pane-wrapper {
clear: both;
overflow: auto;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
height: auto;
width: auto;
margin: 30px 200px 32px 0;
background-color: white;
font-size: 13px;
}
.message-pane {
margin: 0;
padding: 5px 10px 2px 10px;
}
.message-pane dt {
width: 55px;
float: left;
color: #888;
font-size: 10px;
text-align: right;
padding-top: 4px;
}
.message-pane dd {
overflow: auto;
padding: 2px 0 1px 130px;
margin: 0 0 2px 0;
white-space: -o-pre-wrap; /* Opera */
word-wrap: break-word; /* Internet Explorer 5.5+ */
}
.message-pane dd .label {
font-weight: bold;
white-space: nowrap;
display: block;
margin-left: -125px;
width: 120px;
float: left;
overflow: hidden;
}
.message-pane .subject {
color: #a00;
font-weight: bold;
}
.message-pane .adminmessage {
color: #a00;
font-weight: bold;
}
.message-pane .infomessage {
color: #888;
font-style: italic;
padding-left: 5px;
}
.message-pane .emoticon {
vertical-align: text-bottom;
height: 15px;
width: 15px;
}
.message-form-wrapper {
position: fixed;
bottom: 0;
left: 0;
right: 0;
width: auto;
margin-right: 200px;
border-top: 1px solid #ccc;
background-color: #f2f2f2;
height: 31px;
}
.message-form {
position: fixed;
bottom: 0;
left: 0;
right: 0;
margin-right: 320px;
padding: 0;
}
.message-form input {
border: 0 none;
padding: 5px 10px;
font-size: 14px;
width: 100%;
display: block;
outline-width: 0;
background-color: #f2f2f2;
}
.message-form input.submit {
cursor: pointer;
background-color: #ccc;
color: #666;
position: fixed;
bottom: 0;
right: 0;
margin: 3px 203px 3px 3px;
padding: 5px 7px;
width: auto;
font-size: 12px;
line-height: 12px;
height: 25px;
font-weight: bold;
border-radius: 10px;
}
#tooltip {
position: absolute;
z-index: 10;
display: none;
margin: 18px -18px 2px -2px;
color: white;
font-size: 11px;
padding: 5px 0;
background: url(img/tooltip-arrows.gif) no-repeat left bottom;
}
#tooltip div {
background-color: black;
padding: 2px 5px;
zoom: 1;
}
#context-menu {
position: absolute;
z-index: 10;
display: none;
padding: 15px 10px;
margin: 8px -28px -8px -12px;
background: url(img/context-arrows.gif) no-repeat left bottom;
}
#context-menu ul {
background-color: black;
color: white;
font-size: 12px;
padding: 2px;
zoom: 1;
}
#context-menu li {
padding: 3px 5px 3px 20px;
line-height: 12px;
cursor: pointer;
margin-bottom: 2px;
background: 1px no-repeat;
white-space: nowrap;
}
#context-menu li:hover {
background-color: #666;
}
#context-menu li:last-child {
margin-bottom: 0;
}
#context-menu .private {
background-image: url(img/action/private.png);
}
#context-menu .ignore {
background-image: url(img/action/ignore.png);
}
#context-menu .unignore {
background-image: url(img/action/unignore.png);
}
#context-menu .kick {
background-image: url(img/action/kick.png);
}
#context-menu .ban {
background-image: url(img/action/ban.png);
}
#context-menu .subject {
background-image: url(img/action/subject.png);
}
#context-menu .emoticons {
padding-left: 5px;
width: 85px;
white-space: normal;
}
#context-menu .emoticons:hover {
background-color: transparent;
}
#context-menu .emoticons img {
cursor: pointer;
margin: 3px;
height: 15px;
width: 15px;
}
#chat-modal {
background: url(img/modal-bg.png);
width: 300px;
padding: 20px 5px;
color: white;
font-size: 16px;
position: fixed;
left: 50%;
top: 50%;
margin-left: -155px;
margin-top: -45px;
text-align: center;
display: none;
z-index: 100;
border-radius: 5px;
}
#chat-modal-overlay {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
z-index: 90;
background-image: url(img/overlay.png);
}
#chat-modal.modal-login {
display: block;
margin-top: -100px;
}
#chat-modal-spinner {
display: none;
margin-left: 15px;
}
#chat-modal form {
margin: 15px 0;
}
#chat-modal label, #chat-modal input, #chat-modal select {
display: block;
float: left;
line-height: 26px;
font-size: 16px;
margin: 5px 0;
}
#chat-modal input, #chat-modal select {
padding: 2px;
line-height: 16px;
width: 150px;
}
#chat-modal label {
text-align: right;
padding-right: 1em;
clear: both;
width: 100px;
}
#chat-modal input.button {
float: none;
display: block;
margin: 5px auto;
clear: both;
position: relative;
top: 10px;
width: 200px;
}
#chat-modal .close {
position: absolute;
right: 0;
display: none;
padding: 0 5px;
margin: -17px 3px 0 0;
color: white;
border-radius: 3px;
}
#chat-modal .close:hover {
background-color: #333;
}
Simple Smileys is a set of 49 clean, free as in freedom, Public Domain smileys.
For more packages or older versions, visit http://simplesmileys.org
/** File: candy.js
* Candy - Chats are not dead yet.
*
* Authors:
* - Patrick Stadler <patrick.stadler@gmail.com>
* - Michael Weibel <michael.weibel@gmail.com>
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
*/
/*jslint regexp: true, browser: true, confusion: true, sloppy: true, white: true, nomen: true, plusplus: true, maxerr: 50, indent: 4 */
/*global jQuery: true, MD5: true, escape: true, Mustache: true, console: true, Strophe: true, $iq: true, $pres: true */
/** Class: Candy
* Candy base class for initalizing the view and the core
*
* Parameters:
* (Candy) self - itself
* (jQuery) $ - jQuery
*/
var Candy = (function(self, $) {
/** Object: about
* About candy
*
* Contains:
* (String) name - Candy
* (Float) version - Candy version
*/
self.about = {
name: 'Candy',
version: '1.0.9'
};
/** Function: init
* Init view & core
*
* Parameters:
* (String) service - URL to the BOSH interface
* (Object) options - Options for candy
*
* Options:
* (Boolean) debug - Debug (Default: false)
* (Array|Boolean) autojoin - Autojoin these channels. When boolean true, do not autojoin, wait if the server sends something.
*/
self.init = function(service, options) {
self.View.init($('#candy'), options.view);
self.Core.init(service, options.core);
};
return self;
}(Candy || {}, jQuery));
/** File: core.js
* Candy - Chats are not dead yet.
*
* Authors:
* - Patrick Stadler <patrick.stadler@gmail.com>
* - Michael Weibel <michael.weibel@gmail.com>
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
*/
/** Class: Candy.Core
* Candy Chat Core
*
* Parameters:
* (Candy.Core) self - itself
* (Strophe) Strophe - Strophe JS
* (jQuery) $ - jQuery
*/
Candy.Core = (function(self, Strophe, $) {
/** PrivateVariable: _connection
* Strophe connection
*/
var _connection = null,
/** PrivateVariable: _service
* URL of BOSH service
*/
_service = null,
/** PrivateVariable: _user
* Current user (me)
*/
_user = null,
/** PrivateVariable: _rooms
* Opened rooms, containing instances of Candy.Core.ChatRooms
*/
_rooms = {},
/** PrivateVariable: _anonymousConnection
* Set in <Candy.Core.connect> when jidOrHost doesn't contain a @-char.
*/
_anonymousConnection = false,
/** PrivateVariable: _options
* Options:
* (Boolean) debug - Debug (Default: false)
* (Array|Boolean) autojoin - Autojoin these channels. When boolean true, do not autojoin, wait if the server sends something.
*/
_options = {
/** Boolean: autojoin
* If set to `true` try to get the bookmarks and autojoin the rooms (supported by Openfire).
* You may want to define an array of rooms to autojoin: `['room1@conference.host.tld', 'room2...]` (ejabberd, Openfire, ...)
*/
autojoin: true,
debug: false
},
/** PrivateFunction: _addNamespace
* Adds a namespace.
*
* Parameters:
* (String) name - namespace name (will become a constant living in Strophe.NS.*)
* (String) value - XML Namespace
*/
_addNamespace = function(name, value) {
Strophe.addNamespace(name, value);
},
/** PrivateFunction: _addNamespaces
* Adds namespaces needed by Candy.
*/
_addNamespaces = function() {
_addNamespace('PRIVATE', 'jabber:iq:private');
_addNamespace('BOOKMARKS', 'storage:bookmarks');
_addNamespace('PRIVACY', 'jabber:iq:privacy');
_addNamespace('DELAY', 'jabber:x:delay');
},
/** PrivateFunction: _registerEventHandlers
* Adds listening handlers to the connection.
*/
_registerEventHandlers = function() {
self.addHandler(self.Event.Jabber.Version, Strophe.NS.VERSION, 'iq');
self.addHandler(self.Event.Jabber.Presence, null, 'presence');
self.addHandler(self.Event.Jabber.Message, null, 'message');
self.addHandler(self.Event.Jabber.Bookmarks, Strophe.NS.PRIVATE, 'iq');
self.addHandler(self.Event.Jabber.Room.Disco, Strophe.NS.DISCO_INFO, 'iq');
self.addHandler(self.Event.Jabber.PrivacyList, Strophe.NS.PRIVACY, 'iq', 'result');
self.addHandler(self.Event.Jabber.PrivacyListError, Strophe.NS.PRIVACY, 'iq', 'error');
};
/** Function: init
* Initialize Core.
*
* Parameters:
* (String) service - URL of BOSH service
* (Object) options - Options for candy
*/
self.init = function(service, options) {
_service = service;
// Apply options
$.extend(true, _options, options);
// Enable debug logging
if(_options.debug) {
self.log = function(str) {
try { // prevent erroring
if(typeof window.console !== undefined && typeof window.console.log !== undefined) {
console.log(str);
}
} catch(e) {
//console.error(e);
}
};
self.log('[Init] Debugging enabled');
}
_addNamespaces();
// Connect to BOSH service
_connection = new Strophe.Connection(_service);
_connection.rawInput = self.rawInput.bind(self);
_connection.rawOutput = self.rawOutput.bind(self);
// Window unload handler... works on all browsers but Opera. There is NO workaround.
// Opera clients getting disconnected 1-2 minutes delayed.
window.onbeforeunload = self.onWindowUnload;
// Prevent Firefox from aborting AJAX requests when pressing ESC
if($.browser.mozilla) {
$(document).keydown(function(e) {
if(e.which === 27) {
e.preventDefault();
}
});
}
};
/** Function: connect
* Connect to the jabber host.
*
* There are four different procedures to login:
* connect('JID', 'password') - Connect a registered user
* connect('domain') - Connect anonymously to the domain. The user should receive a random JID.
* connect('domain', null, 'nick') - Connect anonymously to the domain. The user should receive a random JID but with a nick set.
* connect('JID') - Show login form and prompt for password. JID input is hidden.
* connect() - Show login form and prompt for JID and password.
*
* See:
* <Candy.Core.attach()> for attaching an already established session.
*
* Parameters:
* (String) jidOrHost - JID or Host
* (String) password - Password of the user
* (String) nick - Nick of the user. Set one if you want to anonymously connect but preset a nick. If jidOrHost is a domain
* and this param is not set, Candy will prompt for a nick.
*/
self.connect = function(jidOrHost, password, nick) {
// Reset before every connection attempt to make sure reconnections work after authfail, alltabsclosed, ...
_connection.reset();
_registerEventHandlers();
_anonymousConnection = !_anonymousConnection ? jidOrHost && jidOrHost.indexOf("@") < 0 : true;
if(jidOrHost && password) {
// authentication
_connection.connect(_getEscapedJidFromJid(jidOrHost) + '/' + Candy.about.name, password, Candy.Core.Event.Strophe.Connect);
_user = new self.ChatUser(jidOrHost, Strophe.getNodeFromJid(jidOrHost));
} else if(jidOrHost && nick) {
// anonymous connect
_connection.connect(_getEscapedJidFromJid(jidOrHost) + '/' + Candy.about.name, null, Candy.Core.Event.Strophe.Connect);
_user = new self.ChatUser(null, nick); // set jid to null because we'll later receive it
} else if(jidOrHost) {
Candy.Core.Event.Login(jidOrHost);
} else {
// display login modal
Candy.Core.Event.Login();
}
};
_getEscapedJidFromJid = function(jid) {
var node = Strophe.getNodeFromJid(jid),
domain = Strophe.getDomainFromJid(jid);
return node ? Strophe.escapeNode(node) + '@' + domain : domain;
};
/** Function: attach
* Attach an already binded & connected session to the server
*
* _See_ Strophe.Connection.attach
*
* Parameters:
* (String) jid - Jabber ID
* (Integer) sid - Session ID
* (Integer) rid - rid
*/
self.attach = function(jid, sid, rid) {
_user = new self.ChatUser(jid, Strophe.getNodeFromJid(jid));
_registerEventHandlers();
_connection.attach(jid, sid, rid, Candy.Core.Event.Strophe.Connect);
};
/** Function: disconnect
* Leave all rooms and disconnect
*/
self.disconnect = function() {
if(_connection.connected) {
$.each(self.getRooms(), function() {
Candy.Core.Action.Jabber.Room.Leave(this.getJid());
});
_connection.disconnect();
}
};
/** Function: addHandler
* Wrapper for Strophe.Connection.addHandler() to add a stanza handler for the connection.
*
* Parameters:
* (Function) handler - The user callback.
* (String) ns - The namespace to match.
* (String) name - The stanza name to match.
* (String) type - The stanza type attribute to match.
* (String) id - The stanza id attribute to match.
* (String) from - The stanza from attribute to match.
* (String) options - The handler options
*
* Returns:
* A reference to the handler that can be used to remove it.
*/
self.addHandler = function(handler, ns, name, type, id, from, options) {
return _connection.addHandler(handler, ns, name, type, id, from, options);
};
/** Function: getUser
* Gets current user
*
* Returns:
* Instance of Candy.Core.ChatUser
*/
self.getUser = function() {
return _user;
};
/** Function: setUser
* Set current user. Needed when anonymous login is used, as jid gets retrieved later.
*
* Parameters:
* (Candy.Core.ChatUser) user - User instance
*/
self.setUser = function(user) {
_user = user;
};
/** Function: getConnection
* Gets Strophe connection
*
* Returns:
* Instance of Strophe.Connection
*/
self.getConnection = function() {
return _connection;
};
/** Function: getRooms
* Gets all joined rooms
*
* Returns:
* Object containing instances of Candy.Core.ChatRoom
*/
self.getRooms = function() {
return _rooms;
};
/** Function: isAnonymousConnection
* Returns true if <Candy.Core.connect> was first called with a domain instead of a jid as the first param.
*
* Returns:
* (Boolean)
*/
self.isAnonymousConnection = function() {
return _anonymousConnection;
};
/** Function: getOptions
* Gets options
*
* Returns:
* Object
*/
self.getOptions = function() {
return _options;
};
/** Function: getRoom
* Gets a specific room
*
* Parameters:
* (String) roomJid - JID of the room
*
* Returns:
* If the room is joined, instance of Candy.Core.ChatRoom, otherwise null.
*/
self.getRoom = function(roomJid) {
if (_rooms[roomJid]) {
return _rooms[roomJid];
}
return null;
};
/** Function: onWindowUnload
* window.onbeforeunload event which disconnects the client from the Jabber server.
*/
self.onWindowUnload = function() {
// Enable synchronous requests because Safari doesn't send asynchronous requests within unbeforeunload events.
// Only works properly when following patch is applied to strophejs: https://github.com/metajack/strophejs/issues/16/#issuecomment-600266
_connection.sync = true;
self.disconnect();
_connection.flush();
};
/** Function: rawInput
* (Overridden from Strophe.Connection.rawInput)
*
* Logs all raw input if debug is set to true.
*/
self.rawInput = function(data) {
this.log('RECV: ' + data);
};
/** Function rawOutput
* (Overridden from Strophe.Connection.rawOutput)
*
* Logs all raw output if debug is set to true.
*/
self.rawOutput = function(data) {
this.log('SENT: ' + data);
};
/** Function: log
* Overridden to do something useful if debug is set to true.
*
* See: Candy.Core#init
*/
self.log = function() {};
return self;
}(Candy.Core || {}, Strophe, jQuery));
/** File: action.js
* Candy - Chats are not dead yet.
*
* Authors:
* - Patrick Stadler <patrick.stadler@gmail.com>
* - Michael Weibel <michael.weibel@gmail.com>
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
*/
/** Class: Candy.Core.Action
* Chat Actions (basicly a abstraction of Jabber commands)
*
* Parameters:
* (Candy.Core.Action) self - itself
* (Strophe) Strophe - Strophe
* (jQuery) $ - jQuery
*/
Candy.Core.Action = (function(self, Strophe, $) {
/** Class: Candy.Core.Action.Jabber
* Jabber actions
*/
self.Jabber = {
/** Function: Version
* Replies to a version request
*
* Parameters:
* (jQuery.element) msg - jQuery element
*/
Version: function(msg) {
Candy.Core.getConnection().send($iq({type: 'result', to: msg.attr('from'), from: msg.attr('to'), id: msg.attr('id')}).c('query', {name: Candy.about.name, version: Candy.about.version, os: navigator.userAgent}));
},
/** Function: Roster
* Sends a request for a roster
*/
Roster: function() {
Candy.Core.getConnection().send($iq({type: 'get', xmlns: Strophe.NS.CLIENT}).c('query', {xmlns: Strophe.NS.ROSTER}).tree());
},
/** Function: Presence
* Sends a request for presence
*
* Parameters:
* (Object) attr - Optional attributes
*/
Presence: function(attr) {
Candy.Core.getConnection().send($pres(attr).tree());
},
/** Function: Services
* Sends a request for disco items
*/
Services: function() {
Candy.Core.getConnection().send($iq({type: 'get', xmlns: Strophe.NS.CLIENT}).c('query', {xmlns: Strophe.NS.DISCO_ITEMS}).tree());
},
/** Function: Autojoin
* When Candy.Core.getOptions().autojoin is true, request autojoin bookmarks (OpenFire)
*
* Otherwise, if Candy.Core.getOptions().autojoin is an array, join each channel specified.
*/
Autojoin: function() {
// Request bookmarks
if(Candy.Core.getOptions().autojoin === true) {
Candy.Core.getConnection().send($iq({type: 'get', xmlns: Strophe.NS.CLIENT}).c('query', {xmlns: Strophe.NS.PRIVATE}).c('storage', {xmlns: Strophe.NS.BOOKMARKS}).tree());
// Join defined rooms
} else if($.isArray(Candy.Core.getOptions().autojoin)) {
$.each(Candy.Core.getOptions().autojoin, function() {
self.Jabber.Room.Join(this.valueOf());
});
}
},
/** Function: ResetIgnoreList
* Create new ignore privacy list (and reset the old one, if it exists).
*/
ResetIgnoreList: function() {
Candy.Core.getConnection().send($iq({type: 'set', from: Candy.Core.getUser().getJid(), id: 'set1'})
.c('query', {xmlns: Strophe.NS.PRIVACY }).c('list', {name: 'ignore'}).c('item', {'action': 'allow', 'order': '0'}).tree());
},
/** Function: RemoveIgnoreList
* Remove an existing ignore list.
*/
RemoveIgnoreList: function() {
Candy.Core.getConnection().send($iq({type: 'set', from: Candy.Core.getUser().getJid(), id: 'remove1'})
.c('query', {xmlns: Strophe.NS.PRIVACY }).c('list', {name: 'ignore'}).tree());
},
/** Function: GetIgnoreList
* Get existing ignore privacy list when connecting.
*/
GetIgnoreList: function() {
Candy.Core.getConnection().send($iq({type: 'get', from: Candy.Core.getUser().getJid(), id: 'get1'})
.c('query', {xmlns: Strophe.NS.PRIVACY }).c('list', {name: 'ignore'}).tree());
},
/** Function: SetIgnoreListActive
* Set ignore privacy list active
*/
SetIgnoreListActive: function() {
Candy.Core.getConnection().send($iq({type: 'set', from: Candy.Core.getUser().getJid(), id: 'set2'})
.c('query', {xmlns: Strophe.NS.PRIVACY }).c('active', {name:'ignore'}).tree());
},
/** Function: GetJidIfAnonymous
* On anonymous login, initially we don't know the jid and as a result, Candy.Core._user doesn't have a jid.
* Check if user doesn't have a jid and get it if necessary from the connection.
*/
GetJidIfAnonymous: function() {
if (!Candy.Core.getUser().getJid()) {
Candy.Core.log("[Jabber] Anonymous login");
Candy.Core.getUser().data.jid = Candy.Core.getConnection().jid;
}
},
/** Class: Candy.Core.Action.Jabber.Room
* Room-specific commands
*/
Room: {
/** Function: Join
* Requests disco of specified room and joins afterwards.
*
* TODO:
* maybe we should wait for disco and later join the room?
* but what if we send disco but don't want/can join the room
*
* Parameters:
* (String) roomJid - Room to join
* (String) password - [optional] Password for the room
*/
Join: function(roomJid, password) {
self.Jabber.Room.Disco(roomJid);
Candy.Core.getConnection().muc.join(roomJid, Candy.Core.getUser().getNick(), null, null, password);
},
/** Function: Leave
* Leaves a room.
*
* Parameters:
* (String) roomJid - Room to leave
*/
Leave: function(roomJid) {
Candy.Core.getConnection().muc.leave(roomJid, Candy.Core.getRoom(roomJid).getUser().getNick(), function() {});
},
/** Function: Disco
* Requests <disco info of a room at http://xmpp.org/extensions/xep-0045.html#disco-roominfo>.
*
* Parameters:
* (String) roomJid - Room to get info for
*/
Disco: function(roomJid) {
Candy.Core.getConnection().send($iq({type: 'get', from: Candy.Core.getUser().getJid(), to: roomJid, id: 'disco3'}).c('query', {xmlns: Strophe.NS.DISCO_INFO}).tree());
},
/** Function: Message
* Send message
*
* Parameters:
* (String) roomJid - Room to which send the message into
* (String) msg - Message
* (String) type - "groupchat" or "chat" ("chat" is for private messages)
*
* Returns:
* (Boolean) - true if message is not empty after trimming, false otherwise.
*/
Message: function(roomJid, msg, type) {
// Trim message
msg = $.trim(msg);
if(msg === '') {
return false;
}
Candy.Core.getConnection().muc.message(Candy.Util.escapeJid(roomJid), undefined, msg, type);
return true;
},
/** Function: IgnoreUnignore
* Checks if the user is already ignoring the target user, if yes: unignore him, if no: ignore him.
*
* Uses the ignore privacy list set on connecting.
*
* Parameters:
* (String) userJid - Target user jid
*/
IgnoreUnignore: function(userJid) {
Candy.Core.getUser().addToOrRemoveFromPrivacyList('ignore', userJid);
Candy.Core.Action.Jabber.Room.UpdatePrivacyList();
},
/** Function: UpdatePrivacyList
* Updates privacy list according to the privacylist in the currentUser
*/
UpdatePrivacyList: function() {
var currentUser = Candy.Core.getUser(),
iq = $iq({type: 'set', from: currentUser.getJid(), id: 'edit1'})
.c('query', {xmlns: 'jabber:iq:privacy' })
.c('list', {name: 'ignore'}),
privacyList = currentUser.getPrivacyList('ignore');
if (privacyList.length > 0) {
$.each(privacyList, function(index, jid) {
iq.c('item', {type:'jid', value: Candy.Util.escapeJid(jid), action: 'deny', order : index})
.c('message').up().up();
});
} else {
iq.c('item', {action: 'allow', order : '0'});
}
Candy.Core.getConnection().send(iq.tree());
},
/** Class: Candy.Core.Action.Jabber.Room.Admin
* Room administration commands
*/
Admin: {
/** Function: UserAction
* Kick or ban a user
*
* Parameters:
* (String) roomJid - Room in which the kick/ban should be done
* (String) userJid - Victim
* (String) type - "kick" or "ban"
* (String) msg - Reason
*
* Returns:
* (Boolean) - true if sent successfully, false if type is not one of "kick" or "ban".
*/
UserAction: function(roomJid, userJid, type, reason) {
var iqId,
itemObj = {nick: Strophe.escapeNode(Strophe.getResourceFromJid(userJid))};
switch(type) {
case 'kick':
iqId = 'kick1';
itemObj.role = 'none';
break;
case 'ban':
iqId = 'ban1';
itemObj.affiliation = 'outcast';
break;
default:
return false;
}
Candy.Core.getConnection().send($iq({type: 'set', from: Candy.Core.getUser().getJid(), to: roomJid, id: iqId}).c('query', {xmlns: Strophe.NS.MUC_ADMIN }).c('item', itemObj).c('reason').t(reason).tree());
return true;
},
/** Function: SetSubject
* Sets subject (topic) of a room.
*
* Parameters:
* (String) roomJid - Room
* (String) subject - Subject to set
*/
SetSubject: function(roomJid, subject) {
Candy.Core.getConnection().muc.setTopic(roomJid, subject);
}
}
}
};
return self;
}(Candy.Core.Action || {}, Strophe, jQuery));
/** File: chatRoom.js
* Candy - Chats are not dead yet.
*
* Authors:
* - Patrick Stadler <patrick.stadler@gmail.com>
* - Michael Weibel <michael.weibel@gmail.com>
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
*/
/** Class: Candy.Core.ChatRoom
* Candy Chat Room
*
* Parameters:
* (String) roomJid - Room jid
*/
Candy.Core.ChatRoom = function(roomJid) {
/** Object: room
* Object containing roomJid and name.
*/
this.room = {
jid: roomJid,
name: null
};
/** Variable: user
* Current local user of this room.
*/
this.user = null;
/** Variable: Roster
* Candy.Core.ChatRoster instance
*/
this.roster = new Candy.Core.ChatRoster();
/** Function: setUser
* Set user of this room.
*
* Parameters:
* (Candy.Core.ChatUser) user - Chat user
*/
this.setUser = function(user) {
this.user = user;
};
/** Function: getUser
* Get current local user
*
* Returns:
* (Object) - Candy.Core.ChatUser instance or null
*/
this.getUser = function() {
return this.user;
};
/** Function: getJid
* Get room jid
*
* Returns:
* (String) - Room jid
*/
this.getJid = function() {
return this.room.jid;
};
/** Function: setName
* Set room name
*
* Parameters:
* (String) name - Room name
*/
this.setName = function(name) {
this.room.name = name;
};
/** Function: getName
* Get room name
*
* Returns:
* (String) - Room name
*/
this.getName = function() {
return this.room.name;
};
/** Function: setRoster
* Set roster of room
*
* Parameters:
* (Candy.Core.ChatRoster) roster - Chat roster
*/
this.setRoster = function(roster) {
this.roster = roster;
};
/** Function: getRoster
* Get roster
*
* Returns
* (Candy.Core.ChatRoster) - instance
*/
this.getRoster = function() {
return this.roster;
};
};
/** File: chatRoster.js
* Candy - Chats are not dead yet.
*
* Authors:
* - Patrick Stadler <patrick.stadler@gmail.com>
* - Michael Weibel <michael.weibel@gmail.com>
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
*/
/** Class: Candy.Core.ChatRoster
* Chat Roster
*/
Candy.Core.ChatRoster = function () {
/** Object: items
* Roster items
*/
this.items = {};
/** Function: add
* Add user to roster
*
* Parameters:
* (Candy.Core.ChatUser) user - User to add
*/
this.add = function(user) {
this.items[user.getJid()] = user;
};
/** Function: remove
* Remove user from roster
*
* Parameters:
* (String) jid - User jid
*/
this.remove = function(jid) {
delete this.items[jid];
};
/** Function: get
* Get user from roster
*
* Parameters:
* (String) jid - User jid
*
* Returns:
* (Candy.Core.ChatUser) - User
*/
this.get = function(jid) {
return this.items[jid];
};
/** Function: getAll
* Get all items
*
* Returns:
* (Object) - all roster items
*/
this.getAll = function() {
return this.items;
};
};
/** File: chatUser.js
* Candy - Chats are not dead yet.
*
* Authors:
* - Patrick Stadler <patrick.stadler@gmail.com>
* - Michael Weibel <michael.weibel@gmail.com>
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
*/
/** Class: Candy.Core.ChatUser
* Chat User
*/
Candy.Core.ChatUser = function(jid, nick, affiliation, role) {
/** Constant: ROLE_MODERATOR
* Moderator role
*/
this.ROLE_MODERATOR = 'moderator';
/** Constant: AFFILIATION_OWNER
* Affiliation owner
*/
this.AFFILIATION_OWNER = 'owner';
/** Object: data
* User data containing:
* - jid
* - nick
* - affiliation
* - role
* - privacyLists
* - customData to be used by e.g. plugins
*/
this.data = {
jid: jid,
nick: Strophe.unescapeNode(nick),
affiliation: affiliation,
role: role,
privacyLists: {},
customData: {}
};
/** Function: getJid
* Gets an unescaped user jid
*
* See:
* <Candy.Util.unescapeJid>
*
* Returns:
* (String) - jid
*/
this.getJid = function() {
if(this.data.jid) {
return Candy.Util.unescapeJid(this.data.jid);
}
return;
};
/** Function: getEscapedJid
* Escapes the user's jid (node & resource get escaped)
*
* See:
* <Candy.Util.escapeJid>
*
* Returns:
* (String) - escaped jid
*/
this.getEscapedJid = function() {
return Candy.Util.escapeJid(this.data.jid);
};
/** Function: getNick
* Gets user nick
*
* Returns:
* (String) - nick
*/
this.getNick = function() {
return Strophe.unescapeNode(this.data.nick);
};
/** Function: getRole
* Gets user role
*
* Returns:
* (String) - role
*/
this.getRole = function() {
return this.data.role;
};
/** Function: getAffiliation
* Gets user affiliation
*
* Returns:
* (String) - affiliation
*/
this.getAffiliation = function() {
return this.data.affiliation;
};
/** Function: isModerator
* Check if user is moderator. Depends on the room.
*
* Returns:
* (Boolean) - true if user has role moderator or affiliation owner
*/
this.isModerator = function() {
return this.getRole() === this.ROLE_MODERATOR || this.getAffiliation() === this.AFFILIATION_OWNER;
};
/** Function: addToOrRemoveFromPrivacyList
* Convenience function for adding/removing users from ignore list.
*
* Check if user is already in privacy list. If yes, remove it. If no, add it.
*
* Parameters:
* (String) list - To which privacy list the user should be added / removed from. Candy supports curently only the "ignore" list.
* (String) jid - User jid to add/remove
*
* Returns:
* (Array) - Current privacy list.
*/
this.addToOrRemoveFromPrivacyList = function(list, jid) {
if (!this.data.privacyLists[list]) {
this.data.privacyLists[list] = [];
}
var index = -1;
if ((index = this.data.privacyLists[list].indexOf(jid)) !== -1) {
this.data.privacyLists[list].splice(index, 1);
} else {
this.data.privacyLists[list].push(jid);
}
return this.data.privacyLists[list];
};
/** Function: getPrivacyList
* Returns the privacy list of the listname of the param.
*
* Parameters:
* (String) list - To which privacy list the user should be added / removed from. Candy supports curently only the "ignore" list.
*
* Returns:
* (Array) - Privacy List
*/
this.getPrivacyList = function(list) {
if (!this.data.privacyLists[list]) {
this.data.privacyLists[list] = [];
}
return this.data.privacyLists[list];
};
/** Function: isInPrivacyList
* Tests if this user ignores the user provided by jid.
*
* Parameters:
* (String) list - Privacy list
* (String) jid - Jid to test for
*
* Returns:
* (Boolean)
*/
this.isInPrivacyList = function(list, jid) {
if (!this.data.privacyLists[list]) {
return false;
}
return this.data.privacyLists[list].indexOf(jid) !== -1;
};
/** Function: setCustomData
* Stores custom data
*
* Parameter:
* (Object) data - Object containing custom data
*/
this.setCustomData = function(data) {
this.data.customData = data;
};
/** Function: getCustomData
* Retrieve custom data
*
* Returns:
* (Object) - Object containing custom data
*/
this.getCustomData = function() {
return this.data.customData;
};
};
/** File: event.js
* Candy - Chats are not dead yet.
*
* Authors:
* - Patrick Stadler <patrick.stadler@gmail.com>
* - Michael Weibel <michael.weibel@gmail.com>
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
*/
/** Class: Candy.Core.Event
* Chat Events
*
* Parameters:
* (Candy.Core.Event) self - itself
* (Strophe) Strophe - Strophe
* (jQuery) $ - jQuery
* (Candy.Util.Observable) observable - Observable to mixin
*/
Candy.Core.Event = (function(self, Strophe, $, observable) {
/**
* Mixin observable
*/
var i;
for (i in observable) {
if (observable.hasOwnProperty(i)) {
self[i] = observable[i];
}
}
/** Enum: KEYS
* Observer keys
*
* CHAT - Chat events
* PRESENCE - Presence events
* MESSAGE - Message events
* LOGIN - Login event
*/
self.KEYS = {
CHAT: 1,
PRESENCE: 2,
MESSAGE: 3,
LOGIN: 4,
PRESENCE_ERROR: 5
};
/** Class: Candy.Core.Event.Strophe
* Strophe-related events
*/
self.Strophe = {
/** Function: Connect
* Acts on strophe status events and notifies view.
*
* Parameters:
* (Strophe.Status) status - Strophe statuses
*/
Connect: function(status) {
switch(status) {
case Strophe.Status.CONNECTED:
Candy.Core.log('[Connection] Connected');
Candy.Core.Action.Jabber.GetJidIfAnonymous();
// fall through because the same things need to be done :)
case Strophe.Status.ATTACHED:
Candy.Core.log('[Connection] Attached');
Candy.Core.Action.Jabber.Presence();
Candy.Core.Action.Jabber.Autojoin();
Candy.Core.Action.Jabber.GetIgnoreList();
break;
case Strophe.Status.DISCONNECTED:
Candy.Core.log('[Connection] Disconnected');
break;
case Strophe.Status.AUTHFAIL:
Candy.Core.log('[Connection] Authentication failed');
break;
case Strophe.Status.CONNECTING:
Candy.Core.log('[Connection] Connecting');
break;
case Strophe.Status.DISCONNECTING:
Candy.Core.log('[Connection] Disconnecting');
break;
case Strophe.Status.AUTHENTICATING:
Candy.Core.log('[Connection] Authenticating');
break;
case Strophe.Status.ERROR:
case Strophe.Status.CONNFAIL:
Candy.Core.log('[Connection] Failed (' + status + ')');
break;
default:
Candy.Core.log('[Connection] What?!');
break;
}
self.notifyObservers(self.KEYS.CHAT, { type: 'connection', status: status } );
}
};
/** Function: Login
* Notify view that the login window should be displayed
*
* Parameters:
* (String) presetJid - Preset user JID
*/
self.Login = function(presetJid) {
self.notifyObservers(self.KEYS.LOGIN, { presetJid: presetJid } );
};
/** Class: Candy.Core.Event.Jabber
* Jabber related events
*/
self.Jabber = {
/** Function: Version
* Responds to a version request
*
* Parameters:
* (String) msg - Raw XML Message
*
* Returns:
* (Boolean) - true
*/
Version: function(msg) {
Candy.Core.log('[Jabber] Version');
Candy.Core.Action.Jabber.Version($(msg));
return true;
},
/** Function: Presence
* Acts on a presence event
*
* Parameters:
* (String) msg - Raw XML Message
*
* Returns:
* (Boolean) - true
*/
Presence: function(msg) {
Candy.Core.log('[Jabber] Presence');
msg = $(msg);
if(msg.children('x[xmlns^="' + Strophe.NS.MUC + '"]').length > 0) {
if (msg.attr('type') === 'error') {
self.Jabber.Room.PresenceError(msg);
} else {
self.Jabber.Room.Presence(msg);
}
}
return true;
},
/** Function: Bookmarks
* Acts on a bookmarks event. When a bookmark has the attribute autojoin set, joins this room.
*
* Parameters:
* (String) msg - Raw XML Message
*
* Returns:
* (Boolean) - true
*/
Bookmarks: function(msg) {
Candy.Core.log('[Jabber] Bookmarks');
// Autojoin bookmarks (Openfire)
$('conference', msg).each(function() {
var item = $(this);
if(item.attr('autojoin')) {
Candy.Core.Action.Jabber.Room.Join(item.attr('jid'));
}
});
return true;
},
/** Function: PrivacyList
* Acts on a privacy list event and sets up the current privacy list of this user.
*
* If no privacy list has been added yet, create the privacy list and listen again to this event.
*
* Parameters:
* (String) msg - Raw XML Message
*
* Returns:
* (Boolean) - false to disable the handler after first call.
*/
PrivacyList: function(msg) {
Candy.Core.log('[Jabber] PrivacyList');
var currentUser = Candy.Core.getUser();
$('list[name="ignore"] item', msg).each(function() {
var item = $(this);
if (item.attr('action') === 'deny') {
currentUser.addToOrRemoveFromPrivacyList('ignore', item.attr('value'));
}
});
Candy.Core.Action.Jabber.SetIgnoreListActive();
return false;
},
/** Function: PrivacyListError
* Acts when a privacy list error has been received.
*
* Currently only handles the case, when a privacy list doesn't exist yet and creates one.
*
* Parameters:
* (String) msg - Raw XML Message
*
* Returns:
* (Boolean) - false to disable the handler after first call.
*/
PrivacyListError: function(msg) {
Candy.Core.log('[Jabber] PrivacyListError');
// check if msg says that privacyList doesn't exist
if ($('error[code="404"][type="cancel"] item-not-found', msg)) {
Candy.Core.Action.Jabber.ResetIgnoreList();
Candy.Core.Action.Jabber.SetIgnoreListActive();
}
return false;
},
/** Function: Message
* Acts on room, admin and server messages and notifies the view if required.
*
* Parameters:
* (String) msg - Raw XML Message
*
* Returns:
* (Boolean) - true
*/
Message: function(msg) {
Candy.Core.log('[Jabber] Message');
var msg = $(msg),
fromJid = msg.attr('from'),
type = msg.attr('type'),
toJid = msg.attr('to');
// Room message
if(fromJid !== Strophe.getDomainFromJid(fromJid) && (type === 'groupchat' || type === 'chat' || type === 'error')) {
self.Jabber.Room.Message(msg);
// Admin message
} else if(!toJid && fromJid === Strophe.getDomainFromJid(fromJid)) {
self.notifyObservers(self.KEYS.CHAT, { type: (type || 'message'), message: msg.children('body').text() });
// Server Message
} else if(toJid && fromJid === Strophe.getDomainFromJid(fromJid)) {
self.notifyObservers(self.KEYS.CHAT, { type: (type || 'message'), subject: msg.children('subject').text(), message: msg.children('body').text() });
}
return true;
},
/** Class: Candy.Core.Event.Jabber.Room
* Room specific events
*/
Room: {
/** Function: Leave
* Leaves a room and cleans up related data and notifies view.
*
* Parameters:
* (String) msg - Raw XML Message
*
* Returns:
* (Boolean) - true
*/
Leave: function(msg) {
Candy.Core.log('[Jabber:Room] Leave');
var msg = $(msg),
from = msg.attr('from'),
roomJid = Strophe.getBareJidFromJid(from);
// if room is not joined yet, ignore.
if (!Candy.Core.getRoom(roomJid)) {
return false;
}
var roomName = Candy.Core.getRoom(roomJid).getName(),
item = msg.find('item'),
type = 'leave',
reason,
actor;
delete Candy.Core.getRooms()[roomJid];
// if user gets kicked, role is none and there's a status code 307
if(item.attr('role') === 'none') {
if(msg.find('status').attr('code') === '307') {
type = 'kick';
} else if(msg.find('status').attr('code') === '301') {
type = 'ban';
}
reason = item.find('reason').text();
actor = item.find('actor').attr('jid');
}
var user = new Candy.Core.ChatUser(from, Strophe.getResourceFromJid(from), item.attr('affiliation'), item.attr('role'));
self.notifyObservers(self.KEYS.PRESENCE, { 'roomJid': roomJid, 'roomName': roomName, 'type': type, 'reason': reason, 'actor': actor, 'user': user } );
return true;
},
/** Function: Disco
* Sets informations to rooms according to the disco info received.
*
* Parameters:
* (String) msg - Raw XML Message
*
* Returns:
* (Boolean) - true
*/
Disco: function(msg) {
Candy.Core.log('[Jabber:Room] Disco');
var msg = $(msg),
roomJid = Strophe.getBareJidFromJid(msg.attr('from'));
// Client joined a room
if(!Candy.Core.getRooms()[roomJid]) {
Candy.Core.getRooms()[roomJid] = new Candy.Core.ChatRoom(roomJid);
}
// Room existed but room name was unknown
var roomName = msg.find('identity').attr('name'),
room = Candy.Core.getRoom(roomJid);
if(room.getName() === null) {
room.setName(roomName);
// Room name changed
}/*else if(room.getName() !== roomName && room.getUser() !== null) {
// NOTE: We want to notify the View here but jabber doesn't send anything when the room name changes :-(
}*/
return true;
},
/** Function: Presence
* Acts on various presence messages (room leaving, room joining, error presence) and notifies view.
*
* Parameters:
* (Object) msg - jQuery object of XML message
*
* Returns:
* (Boolean) - true
*/
Presence: function(msg) {
Candy.Core.log('[Jabber:Room] Presence');
var from = Candy.Util.unescapeJid(msg.attr('from')),
roomJid = Strophe.getBareJidFromJid(from),
presenceType = msg.attr('type');
// Client left a room
if(Strophe.getResourceFromJid(from) === Candy.Core.getUser().getNick() && presenceType === 'unavailable') {
self.Jabber.Room.Leave(msg);
return true;
}
// Client joined a room
var room = Candy.Core.getRoom(roomJid);
if(!room) {
Candy.Core.getRooms()[roomJid] = new Candy.Core.ChatRoom(roomJid);
room = Candy.Core.getRoom(roomJid);
}
var roster = room.getRoster(),
action, user,
item = msg.find('item');
// User joined a room
if(presenceType !== 'unavailable') {
var nick = Strophe.getResourceFromJid(from);
user = new Candy.Core.ChatUser(from, nick, item.attr('affiliation'), item.attr('role'));
// Room existed but client (myself) is not yet registered
if(room.getUser() === null && Candy.Core.getUser().getNick() === nick) {
room.setUser(user);
}
roster.add(user);
action = 'join';
// User left a room
} else {
action = 'leave';
if(item.attr('role') === 'none') {
if(msg.find('status').attr('code') === '307') {
action = 'kick';
} else if(msg.find('status').attr('code') === '301') {
action = 'ban';
}
}
user = roster.get(from);
roster.remove(from);
}
self.notifyObservers(self.KEYS.PRESENCE, {'roomJid': roomJid, 'roomName': room.getName(), 'user': user, 'action': action, 'currentUser': Candy.Core.getUser() } );
return true;
},
/** Function: PresenceError
* Acts when a presence of type error has been retrieved.
*
* Parameters:
* (Object) msg - jQuery object of XML message
*
* Returns:
* (Boolean) - true
*/
PresenceError: function(msg) {
Candy.Core.log('[Jabber:Room] Presence Error');
var from = Candy.Util.unescapeJid(msg.attr('from')),
roomJid = Strophe.getBareJidFromJid(from),
room = Candy.Core.getRooms()[roomJid],
roomName = room.getName();
// Presence error: Remove room from array to prevent error when disconnecting
delete room;
self.notifyObservers(self.KEYS.PRESENCE_ERROR, {'msg' : msg, 'type': msg.children('error').children()[0].tagName.toLowerCase(), 'roomJid': roomJid, 'roomName': roomName});
},
/** Function: Message
* Acts on various message events (subject changed, private chat message, multi-user chat message)
* and notifies view.
*
* Parameters:
* (String) msg - jQuery object of XML message
*
* Returns:
* (Boolean) - true
*/
Message: function(msg) {
Candy.Core.log('[Jabber:Room] Message');
// Room subject
var roomJid, message;
if(msg.children('subject').length > 0) {
roomJid = Candy.Util.unescapeJid(Strophe.getBareJidFromJid(msg.attr('from')));
message = { name: Strophe.getNodeFromJid(roomJid), body: msg.children('subject').text(), type: 'subject' };
// Error messsage
} else if(msg.attr('type') === 'error') {
var error = msg.children('error');
if(error.attr('code') === '500' && error.children('text').length > 0) {
roomJid = msg.attr('from');
message = { type: 'info', body: error.children('text').text() };
}
// Chat message
} else if(msg.children('body').length > 0) {
// Private chat message
if(msg.attr('type') === 'chat') {
roomJid = Candy.Util.unescapeJid(msg.attr('from'));
var bareRoomJid = Strophe.getBareJidFromJid(roomJid),
// if a 3rd-party client sends a direct message to this user (not via the room) then the username is the node and not the resource.
isNoConferenceRoomJid = !Candy.Core.getRoom(bareRoomJid),
name = isNoConferenceRoomJid ? Strophe.getNodeFromJid(roomJid) : Strophe.getResourceFromJid(roomJid);
message = { name: name, body: msg.children('body').text(), type: msg.attr('type'), isNoConferenceRoomJid: isNoConferenceRoomJid };
// Multi-user chat message
} else {
roomJid = Candy.Util.unescapeJid(Strophe.getBareJidFromJid(msg.attr('from')));
var resource = Strophe.getResourceFromJid(msg.attr('from'));
// Message from a user
if(resource) {
resource = Strophe.unescapeNode(resource);
message = { name: resource, body: msg.children('body').text(), type: msg.attr('type') };
// Message from server (XEP-0045#registrar-statuscodes)
} else {
message = { name: '', body: msg.children('body').text(), type: 'info' };
}
}
// Unhandled message
} else {
return true;
}
// besides the delayed delivery (XEP-0203), there exists also XEP-0091 which is the legacy delayed delivery.
// the x[xmlns=jabber:x:delay] is the format in XEP-0091.
var delay = msg.children('delay') ? msg.children('delay') : msg.children('x[xmlns="' + Strophe.NS.DELAY +'"]'),
timestamp = delay !== undefined ? delay.attr('stamp') : null;
self.notifyObservers(self.KEYS.MESSAGE, {roomJid: roomJid, message: message, timestamp: timestamp } );
return true;
}
}
};
return self;
}(Candy.Core.Event || {}, Strophe, jQuery, Candy.Util.Observable));
/** File: util.js
* Candy - Chats are not dead yet.
*
* Authors:
* - Patrick Stadler <patrick.stadler@gmail.com>
* - Michael Weibel <michael.weibel@gmail.com>
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
*/
/** Class: Candy.Util
* Candy utils
*
* Parameters:
* (Candy.Util) self - itself
* (jQuery) $ - jQuery
*/
Candy.Util = (function(self, $){
/** Function: jidToId
* Translates a jid to a MD5-Id
*
* Parameters:
* (String) jid - Jid
*
* Returns:
* MD5-ified jid
*/
self.jidToId = function(jid) {
return MD5.hexdigest(jid);
};
/** Function: escapeJid
* Escapes a jid (node & resource get escaped)
*
* See:
* XEP-0106
*
* Parameters:
* (String) jid - Jid
*
* Returns:
* (String) - escaped jid
*/
self.escapeJid = function(jid) {
var node = Strophe.escapeNode(Strophe.getNodeFromJid(jid)),
domain = Strophe.getDomainFromJid(jid),
resource = Strophe.getResourceFromJid(jid);
jid = node + '@' + domain;
if (resource) {
jid += '/' + Strophe.escapeNode(resource);
}
return jid;
};
/** Function: unescapeJid
* Unescapes a jid (node & resource get unescaped)
*
* See:
* XEP-0106
*
* Parameters:
* (String) jid - Jid
*
* Returns:
* (String) - unescaped Jid
*/
self.unescapeJid = function(jid) {
var node = Strophe.unescapeNode(Strophe.getNodeFromJid(jid)),
domain = Strophe.getDomainFromJid(jid),
resource = Strophe.getResourceFromJid(jid);
jid = node + '@' + domain;
if(resource) {
jid += '/' + Strophe.unescapeNode(resource);
}
return jid;
};
/** Function: crop
* Crop a string with the specified length
*
* Parameters:
* (String) str - String to crop
* (Integer) len - Max length
*/
self.crop = function(str, len) {
if (str.length > len) {
str = str.substr(0, len - 3) + '...';
}
return str;
};
/** Function: setCookie
* Sets a new cookie
*
* Parameters:
* (String) name - cookie name
* (String) value - Value
* (Integer) lifetime_days - Lifetime in days
*/
self.setCookie = function(name, value, lifetime_days) {
var exp = new Date();
exp.setDate(new Date().getDate() + lifetime_days);
document.cookie = name + '=' + value + ';expires=' + exp.toUTCString() + ';path=/';
};
/** Function: cookieExists
* Tests if a cookie with the given name exists
*
* Parameters:
* (String) name - Cookie name
*
* Returns:
* (Boolean) - true/false
*/
self.cookieExists = function(name) {
return document.cookie.indexOf(name) > -1;
};
/** Function: getCookie
* Returns the cookie value if there's one with this name, otherwise returns undefined
*
* Parameters:
* (String) name - Cookie name
*
* Returns:
* Cookie value or undefined
*/
self.getCookie = function(name) {
if(document.cookie) {
var regex = new RegExp(escape(name) + '=([^;]*)', 'gm'),
matches = regex.exec(document.cookie);
if(matches) {
return matches[1];
}
}
};
/** Function: deleteCookie
* Deletes a cookie with the given name
*
* Parameters:
* (String) name - cookie name
*/
self.deleteCookie = function(name) {
document.cookie = name + '=;expires=Thu, 01-Jan-70 00:00:01 GMT;path=/';
};
/** Function: getPosLeftAccordingToWindowBounds
* Fetches the window width and element width
* and checks if specified position + element width is bigger
* than the window width.
*
* If this evaluates to true, the position gets substracted by the element width.
*
* Parameters:
* (jQuery.Element) elem - Element to position
* (Integer) pos - Position left
*
* Returns:
* Object containing `px` (calculated position in pixel) and `alignment` (alignment of the element in relation to pos, either 'left' or 'right')
*/
self.getPosLeftAccordingToWindowBounds = function(elem, pos) {
var windowWidth = $(document).width(),
elemWidth = elem.outerWidth(),
marginDiff = elemWidth - elem.outerWidth(true),
backgroundPositionAlignment = 'left';
if (pos + elemWidth >= windowWidth) {
pos -= elemWidth - marginDiff;
backgroundPositionAlignment = 'right';
}
return { px: pos, backgroundPositionAlignment: backgroundPositionAlignment };
};
/** Function: getPosTopAccordingToWindowBounds
* Fetches the window height and element height
* and checks if specified position + element height is bigger
* than the window height.
*
* If this evaluates to true, the position gets substracted by the element height.
*
* Parameters:
* (jQuery.Element) elem - Element to position
* (Integer) pos - Position top
*
* Returns:
* Object containing `px` (calculated position in pixel) and `alignment` (alignment of the element in relation to pos, either 'top' or 'bottom')
*/
self.getPosTopAccordingToWindowBounds = function(elem, pos) {
var windowHeight = $(document).height(),
elemHeight = elem.outerHeight(),
marginDiff = elemHeight - elem.outerHeight(true),
backgroundPositionAlignment = 'top';
if (pos + elemHeight >= windowHeight) {
pos -= elemHeight - marginDiff;
backgroundPositionAlignment = 'bottom';
}
return { px: pos, backgroundPositionAlignment: backgroundPositionAlignment };
};
/** Function: localizedTime
* Localizes ISO-8610 Date with the time/dateformat specified in the translation.
*
* See: libs/dateformat/dateFormat.js
* See: src/view/translation.js
* See: jquery-i18n/jquery.i18n.js
*
* Parameters:
* (String) dateTime - ISO-8610 Datetime
*
* Returns:
* If current date is equal to the date supplied, format with timeFormat, otherwise with dateFormat
*/
self.localizedTime = function(dateTime) {
if (dateTime === undefined) {
return undefined;
}
var date = self.iso8601toDate(dateTime);
if(date.toDateString() === new Date().toDateString()) {
return date.format($.i18n._('timeFormat'));
} else {
return date.format($.i18n._('dateFormat'));
}
};
/** Function: iso8610toDate
* Parses a ISO-8610 Date to a Date-Object.
*
* Uses a fallback if the client's browser doesn't support it.
*
* Quote:
* ECMAScript revision 5 adds native support for ISO-8601 dates in the Date.parse method,
* but many browsers currently on the market (Safari 4, Chrome 4, IE 6-8) do not support it.
*
* Credits:
* <Colin Snover at http://zetafleet.com/blog/javascript-dateparse-for-iso-8601>
*
* Parameters:
* (String) date - ISO-8610 Date
*
* Returns:
* Date-Object
*/
self.iso8601toDate = function(date) {
var timestamp = Date.parse(date), minutesOffset = 0;
if(isNaN(timestamp)) {
var struct = /^(\d{4}|[+\-]\d{6})-(\d{2})-(\d{2})(?:[T ](\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3,}))?)?(?:(Z)|([+\-])(\d{2})(?::?(\d{2}))?))?/.exec(date);
if(struct) {
if(struct[8] !== 'Z') {
minutesOffset = +struct[10] * 60 + (+struct[11]);
if(struct[9] === '+') {
minutesOffset = -minutesOffset;
}
}
return new Date(+struct[1], +struct[2] - 1, +struct[3], +struct[4], +struct[5] + minutesOffset, +struct[6], struct[7] ? +struct[7].substr(0, 3) : 0);
} else {
// XEP-0091 date
timestamp = Date.parse(date.replace(/^(\d{4})(\d{2})(\d{2})/, '$1-$2-$3') + 'Z');
}
}
return new Date(timestamp);
};
/** Function: isEmptyObject
* IE7 doesn't work with jQuery.isEmptyObject (<=1.5.1), workaround.
*
* Parameters:
* (Object) obj - the object to test for
*
* Returns:
* Boolean true or false.
*/
self.isEmptyObject = function(obj) {
var prop;
for(prop in obj) {
if (obj.hasOwnProperty(prop)) {
return false;
}
}
return true;
};
/** Function: forceRedraw
* Fix IE7 not redrawing under some circumstances.
*
* Parameters:
* (jQuery.element) elem - jQuery element to redraw
*/
self.forceRedraw = function(elem) {
elem.css({display:'none'});
setTimeout(function() {
this.css({display:'block'});
}.bind(elem), 1);
};
/** Class: Candy.Util.Parser
* Parser for emoticons, links and also supports escaping.
*/
self.Parser = {
/** PrivateVariable: _emoticonPath
* Path to emoticons.
*
* Use setEmoticonPath() to change it
*/
_emoticonPath: '',
/** Function: setEmoticonPath
* Set emoticons location.
*
* Parameters:
* (String) path - location of emoticons with trailing slash
*/
setEmoticonPath: function(path) {
this._emoticonPath = path;
},
/** Array: emoticons
* Array containing emoticons to be replaced by their images.
*
* Can be overridden/extended.
*/
emoticons: [
{
plain: ':)',
regex: /((\s):-?\)|:-?\)(\s|$))/gm,
image: 'Smiling.png'
},
{
plain: ';)',
regex: /((\s);-?\)|;-?\)(\s|$))/gm,
image: 'Winking.png'
},
{
plain: ':D',
regex: /((\s):-?D|:-?D(\s|$))/gm,
image: 'Grinning.png'
},
{
plain: ';D',
regex: /((\s);-?D|;-?D(\s|$))/gm,
image: 'Grinning_Winking.png'
},
{
plain: ':(',
regex: /((\s):-?\(|:-?\((\s|$))/gm,
image: 'Unhappy.png'
},
{
plain: '^^',
regex: /((\s)\^\^|\^\^(\s|$))/gm,
image: 'Happy_3.png'
},
{
plain: ':P',
regex: /((\s):-?P|:-?P(\s|$))/igm,
image: 'Tongue_Out.png'
},
{
plain: ';P',
regex: /((\s);-?P|;-?P(\s|$))/igm,
image: 'Tongue_Out_Winking.png'
},
{
plain: ':S',
regex: /((\s):-?S|:-?S(\s|$))/igm,
image: 'Confused.png'
},
{
plain: ':/',
regex: /((\s):-?\/|:-?\/(\s|$))/gm,
image: 'Uncertain.png'
},
{
plain: '8)',
regex: /((\s)8-?\)|8-?\)(\s|$))/gm,
image: 'Sunglasses.png'
},
{
plain: '$)',
regex: /((\s)\$-?\)|\$-?\)(\s|$))/gm,
image: 'Greedy.png'
},
{
plain: 'oO',
regex: /((\s)oO|oO(\s|$))/gm,
image: 'Huh.png'
},
{
plain: ':x',
regex: /((\s):x|:x(\s|$))/gm,
image: 'Lips_Sealed.png'
},
{
plain: ':666:',
regex: /((\s):666:|:666:(\s|$))/gm,
image: 'Devil.png'
},
{
plain: '<3',
regex: /((\s)&lt;3|&lt;3(\s|$))/gm,
image: 'Heart.png'
}
],
/** Function: emotify
* Replaces text-emoticons with their image equivalent.
*
* Parameters:
* (String) text - Text to emotify
*
* Returns:
* Emotified text
*/
emotify: function(text) {
var i;
for(i = this.emoticons.length-1; i >= 0; i--) {
text = text.replace(this.emoticons[i].regex, '$2<img class="emoticon" alt="$1" src="' + this._emoticonPath + this.emoticons[i].image + '" />$3');
}
return text;
},
/** Function: linkify
* Replaces URLs with a HTML-link.
*
* Parameters:
* (String) text - Text to linkify
*
* Returns:
* Linkified text
*/
linkify: function(text) {
text = text.replace(/(^|[^\/])(www\.[^\.]+\.[\S]+(\b|$))/gi, '$1http://$2');
return text.replace(/(\b(https?|ftp|file):\/\/[\-A-Z0-9+&@#\/%?=~_|!:,.;]*[\-A-Z0-9+&@#\/%=~_|])/ig, '<a href="$1" target="_blank">$1</a>');
},
/** Function: escape
* Escapes a text using a jQuery function (like htmlspecialchars in PHP)
*
* Parameters:
* (String) text - Text to escape
*
* Returns:
* Escaped text
*/
escape: function(text) {
return $('<div/>').text(text).html();
},
/** Function: all
* Does everything of the parser: escaping, linkifying and emotifying.
*
* Parameters:
* (String) text - Text to parse
*
* Returns:
* Parsed text
*/
all: function(text) {
if(text) {
text = this.escape(text);
text = this.linkify(text);
text = this.emotify(text);
}
return text;
}
};
return self;
}(Candy.Util || {}, jQuery));
/** Class: Candy.Util.Observable
* A class can be extended with the observable to be able to notify observers
*/
Candy.Util.Observable = (function(self) {
/** PrivateObject: _observers
* List of observers
*/
var _observers = {};
/** Function: addObserver
* Add an observer to the observer list
*
* Parameters:
* (String) key - The key the observer listens to
* (Callback) obj - The observer callback
*/
self.addObserver = function(key, obj) {
if (_observers[key] === undefined) {
_observers[key] = [];
}
_observers[key].push(obj);
};
/** Function: deleteObserver
* Delete observer from list
*
* Parameters:
* (String) key - Key in which the observer lies
* (Callback) obj - The observer callback to be deleted
*/
self.deleteObserver = function(key, obj) {
delete _observers[key][obj];
};
/** Function: clearObservers
* Deletes all observers in list
*
* Parameters:
* (String) key - If defined, remove observers of this key, otherwise remove all including all keys.
*/
self.clearObservers = function(key) {
if (key !== undefined) {
_observers[key] = [];
} else {
_observers = {};
}
};
/** Function: notifyObservers
* Notify all of its observers of a certain event.
*
* Parameters:
* (String) key - Key to notify
* (Object) arg - An argument passed to the update-method of the observers
*/
self.notifyObservers = function(key, arg) {
var observer = _observers[key], i;
for(i = observer.length-1; i >= 0; i--) {
observer[i].update(self, arg);
}
};
return self;
}(Candy.Util.Observable || {}));
/** File: view.js
* Candy - Chats are not dead yet.
*
* Authors:
* - Patrick Stadler <patrick.stadler@gmail.com>
* - Michael Weibel <michael.weibel@gmail.com>
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
*/
/** Class: Candy.View
* The Candy View Class
*
* Parameters:
* (Candy.View) self - itself
* (jQuery) $ - jQuery
*/
Candy.View = (function(self, $) {
/** PrivateObject: _current
* Object containing current container & roomJid which the client sees.
*/
var _current = { container: null, roomJid: null },
/** PrivateObject: _options
*
* Options:
* (String) language - language to use
* (String) resources - path to resources directory (with trailing slash)
* (Object) messages - limit: clean up message pane when n is reached / remove: remove n messages after limit has been reached
* (Object) crop - crop if longer than defined: message.nickname=15, message.body=1000, roster.nickname=15
*/
_options = {
language: 'en',
resources: 'res/',
messages: { limit: 2000, remove: 500 },
crop: {
message: { nickname: 15, body: 1000 },
roster: { nickname: 15 }
}
},
/** PrivateFunction: _setupTranslation
* Set dictionary using jQuery.i18n plugin.
*
* See: view/translation.js
* See: libs/jquery-i18n/jquery.i18n.js
*
* Parameters:
* (String) language - Language identifier
*/
_setupTranslation = function(language) {
$.i18n.setDictionary(self.Translation[language]);
},
/** PrivateFunction: _registerObservers
* Register observers. Candy core will now notify the View on changes.
*/
_registerObservers = function() {
Candy.Core.Event.addObserver(Candy.Core.Event.KEYS.CHAT, self.Observer.Chat);
Candy.Core.Event.addObserver(Candy.Core.Event.KEYS.PRESENCE, self.Observer.Presence);
Candy.Core.Event.addObserver(Candy.Core.Event.KEYS.PRESENCE_ERROR, self.Observer.PresenceError);
Candy.Core.Event.addObserver(Candy.Core.Event.KEYS.MESSAGE, self.Observer.Message);
Candy.Core.Event.addObserver(Candy.Core.Event.KEYS.LOGIN, self.Observer.Login);
},
/** PrivateFunction: _registerWindowHandlers
* Register window focus / blur / resize handlers.
*
* jQuery.focus()/.blur() <= 1.5.1 do not work for IE < 9. Fortunately onfocusin/onfocusout will work for them.
*/
_registerWindowHandlers = function() {
// Cross-browser focus handling
if($.browser.msie && !$.browser.version.match('^9')) {
$(document).focusin(Candy.View.Pane.Window.onFocus).focusout(Candy.View.Pane.Window.onBlur);
} else {
$(window).focus(Candy.View.Pane.Window.onFocus).blur(Candy.View.Pane.Window.onBlur);
}
$(window).resize(Candy.View.Pane.Chat.fitTabs);
},
/** PrivateFunction: _registerToolbarHandlers
* Register toolbar handlers and disable sound if cookie says so.
*/
_registerToolbarHandlers = function() {
$('#emoticons-icon').click(function(e) {
self.Pane.Chat.Context.showEmoticonsMenu(e.currentTarget);
e.stopPropagation();
});
$('#chat-autoscroll-control').click(Candy.View.Pane.Chat.Toolbar.onAutoscrollControlClick);
$('#chat-sound-control').click(Candy.View.Pane.Chat.Toolbar.onSoundControlClick);
if(Candy.Util.cookieExists('candy-nosound')) {
$('#chat-sound-control').click();
}
$('#chat-statusmessage-control').click(Candy.View.Pane.Chat.Toolbar.onStatusMessageControlClick);
if(Candy.Util.cookieExists('candy-nostatusmessages')) {
$('#chat-statusmessage-control').click();
}
},
/** PrivateFunction: _delegateTooltips
* Delegate mouseenter on tooltipified element to <Candy.View.Pane.Chat.Tooltip.show>.
*/
_delegateTooltips = function() {
$('body').delegate('li[data-tooltip]', 'mouseenter', Candy.View.Pane.Chat.Tooltip.show);
};
/** Function: init
* Initialize chat view (setup DOM, register handlers & observers)
*
* Parameters:
* (jQuery.element) container - Container element of the whole chat view
* (Object) options - Options: see _options field (value passed here gets extended by the default value in _options field)
*/
self.init = function(container, options) {
$.extend(true, _options, options);
_setupTranslation(_options.language);
// Set path to emoticons
Candy.Util.Parser.setEmoticonPath(this.getOptions().resources + 'img/emoticons/');
// Start DOMination...
_current.container = container;
_current.container.html(Mustache.to_html(Candy.View.Template.Chat.pane, {
tooltipEmoticons : $.i18n._('tooltipEmoticons'),
tooltipSound : $.i18n._('tooltipSound'),
tooltipAutoscroll : $.i18n._('tooltipAutoscroll'),
tooltipStatusmessage : $.i18n._('tooltipStatusmessage'),
tooltipAdministration : $.i18n._('tooltipAdministration'),
tooltipUsercount : $.i18n._('tooltipUsercount'),
resourcesPath : this.getOptions().resources
}, {
tabs: Candy.View.Template.Chat.tabs,
rooms: Candy.View.Template.Chat.rooms,
modal: Candy.View.Template.Chat.modal,
toolbar: Candy.View.Template.Chat.toolbar,
soundcontrol: Candy.View.Template.Chat.soundcontrol
}));
// ... and let the elements dance.
_registerWindowHandlers();
_registerToolbarHandlers();
_registerObservers();
_delegateTooltips();
};
/** Function: getCurrent
* Get current container & roomJid in an object.
*
* Returns:
* Object containing container & roomJid
*/
self.getCurrent = function() {
return _current;
};
/** Function: getOptions
* Gets options
*
* Returns:
* Object
*/
self.getOptions = function() {
return _options;
};
return self;
}(Candy.View || {}, jQuery));
/** File: event.js
* Candy - Chats are not dead yet.
*
* Authors:
* - Patrick Stadler <patrick.stadler@gmail.com>
* - Michael Weibel <michael.weibel@gmail.com>
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
*/
/** Class: Candy.View.Event
* Empty hooks to capture events and inject custom code.
*
* Parameters:
* (Candy.View.Event) self - itself
* (jQuery) $ - jQuery
*/
Candy.View.Event = (function(self, $) {
/** Class: Candy.View.Event.Chat
* Chat-related events
*/
self.Chat = {
/** Function: onAdminMessage
* Called when receiving admin messages
*
* Parameters:
* (Object) args - {subject, message}
*/
onAdminMessage: function(args) {
return;
},
/** Function: onDisconnect
* Called when client disconnects
*/
onDisconnect: function() {
return;
},
/** Function: onAuthfail
* Called when authentication fails
*/
onAuthfail: function() {
return;
}
};
/** Class: Candy.View.Event.Room
* Room-related events
*/
self.Room = {
/** Function: onAdd
* Called when a new room gets added
*
* Parameters:
* (Object) args - {roomJid, type=chat|groupchat, element}
*/
onAdd: function(args) {
return;
},
/** Function: onShow
* Called when a room gets shown
*
* Parameters:
* (Object) args - {roomJid, element}
*/
onShow: function(args) {
return;
},
/** Function: onHide
* Called when a room gets hidden
*
* Parameters:
* (Object) args - {roomJid, element}
*/
onHide: function(args) {
return;
},
/** Function: onSubjectChange
* Called when a subject of a room gets changed
*
* Parameters:
* (Object) args - {roomJid, element, subject}
*/
onSubjectChange: function(args) {
return;
},
/** Function: onClose
* Called after a room has been left/closed
*
* Parameters:
* (Object) args - {roomJid}
*/
onClose: function(args) {
return;
},
/** Function: onPresenceChange
* Called when presence of user changes (kick, ban)
*
* Parameters:
* (Object) args - {roomJid, user, reason, type}
*/
onPresenceChange: function(args) {
return;
}
};
/** Class: Candy.View.Event.Roster
* Roster-related events
*/
self.Roster = {
/** Function: onUpdate
* Called after a user have been added to the roster
*
* Parameters:
* (Object) args - {roomJid, user, action, element}
*/
onUpdate: function(args) {
return;
},
/** Function: onContextMenu
* Called when a user clicks on the action menu arrow.
* The return value is getting appended to the menulinks.
*
* Parameters:
* (Object) args - {roomJid, user}
*
* Returns:
* (Object) - containing menulinks
*/
onContextMenu: function(args) {
return {};
},
/** Function: afterContextMenu
* Called when after a the context menu is rendered
*
* Parameters:
* (Object) args - {roomJid, element, user}
*/
afterContextMenu: function(args) {
return;
}
};
/** Class: Candy.View.Event.Message
* Message-related events
*/
self.Message = {
/** Function: beforeShow
* Called before a new message will be shown.
*
* Parameters:
* (Object) args - {roomJid, nick, message}
*
* Returns:
* (String) message
*/
beforeShow: function(args) {
return args.message;
},
/** Function: onShow
* Called after a new message has been shown
*
* Parameters:
* (Object) args - {roomJid, element, nick, message}
*/
onShow: function(args) {
return;
},
/** Function: beforeSend
* Called before a message get sent
*
* Parameters:
* (String) message
*
* Returns:
* (String) message
*/
beforeSend: function(message) {
return message;
}
};
return self;
}(Candy.View.Event || {}, jQuery));
\ No newline at end of file
/** File: observer.js
* Candy - Chats are not dead yet.
*
* Authors:
* - Patrick Stadler <patrick.stadler@gmail.com>
* - Michael Weibel <michael.weibel@gmail.com>
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
*/
/** Class: Candy.View.Observer
* Observes Candy core events
*
* Parameters:
* (Candy.View.Observer) self - itself
* (jQuery) $ - jQuery
*/
Candy.View.Observer = (function(self, $) {
/** Class: Candy.View.Observer.Chat
* Chat events
*/
self.Chat = {
/** Function: update
* The update method gets called whenever an event to which "Chat" is subscribed.
*
* Currently listens for connection status updates & admin messages / motd
*
* Parameters:
* (Candy.Core.Event) obj - Candy core event object
* (Object) args - {type, connection or message & subject}
*/
update: function(obj, args) {
if(args.type === 'connection') {
switch(args.status) {
case Strophe.Status.CONNECTING:
case Strophe.Status.AUTHENTICATING:
Candy.View.Pane.Chat.Modal.show($.i18n._('statusConnecting'), false, true);
break;
case Strophe.Status.ATTACHED:
case Strophe.Status.CONNECTED:
Candy.View.Pane.Chat.Modal.show($.i18n._('statusConnected'));
Candy.View.Pane.Chat.Modal.hide();
break;
case Strophe.Status.DISCONNECTING:
Candy.View.Pane.Chat.Modal.show($.i18n._('statusDisconnecting'), false, true);
break;
case Strophe.Status.DISCONNECTED:
var presetJid = Candy.Core.isAnonymousConnection() ? Strophe.getDomainFromJid(Candy.Core.getUser().getJid()) : null;
Candy.View.Pane.Chat.Modal.showLoginForm($.i18n._('statusDisconnected'), presetJid);
Candy.View.Event.Chat.onDisconnect();
break;
case Strophe.Status.AUTHFAIL:
Candy.View.Pane.Chat.Modal.showLoginForm($.i18n._('statusAuthfail'));
Candy.View.Event.Chat.onAuthfail();
break;
default:
Candy.View.Pane.Chat.Modal.show($.i18n._('status', args.status));
break;
}
} else if(args.type === 'message') {
Candy.View.Pane.Chat.adminMessage((args.subject || ''), args.message);
} else if(args.type === 'chat' || args.type === 'groupchat') {
// use onInfoMessage as infos from the server shouldn't be hidden by the infoMessage switch.
Candy.View.Pane.Chat.onInfoMessage(Candy.View.getCurrent().roomJid, (args.subject || ''), args.message);
}
}
};
/** Class: Candy.View.Observer.Presence
* Presence update events
*/
self.Presence = {
/** Function: update
* Every presence update gets dispatched from this method.
*
* Parameters:
* (Candy.Core.Event) obj - Candy core event object
* (Object) args - Arguments differ on each type
*
* Uses:
* - <notifyPrivateChats>
*/
update: function(obj, args) {
// Client left
if(args.type === 'leave') {
var user = Candy.View.Pane.Room.getUser(args.roomJid);
Candy.View.Pane.Room.close(args.roomJid);
self.Presence.notifyPrivateChats(user, args.type);
// Client has been kicked or banned
} else if (args.type === 'kick' || args.type === 'ban') {
var actorName = args.actor ? Strophe.getNodeFromJid(args.actor) : null,
actionLabel,
translationParams = [args.roomName];
if (actorName) {
translationParams.push(actorName);
}
switch(args.type) {
case 'kick':
actionLabel = $.i18n._((actorName ? 'youHaveBeenKickedBy' : 'youHaveBeenKicked'), translationParams);
break;
case 'ban':
actionLabel = $.i18n._((actorName ? 'youHaveBeenBannedBy' : 'youHaveBeenBanned'), translationParams);
break;
}
Candy.View.Pane.Chat.Modal.show(Mustache.to_html(Candy.View.Template.Chat.Context.adminMessageReason, {
reason: args.reason,
_action: actionLabel,
_reason: $.i18n._('reasonWas', [args.reason])
}));
setTimeout(function() {
Candy.View.Pane.Chat.Modal.hide(function() {
Candy.View.Pane.Room.close(args.roomJid);
self.Presence.notifyPrivateChats(args.user, args.type);
});
}, 5000);
Candy.View.Event.Room.onPresenceChange({ type: args.type, reason: args.reason, roomJid: args.roomJid, user: args.user });
// A user changed presence
} else {
// Initialize room if not yet existing
if(!Candy.View.Pane.Chat.rooms[args.roomJid]) {
Candy.View.Pane.Room.init(args.roomJid, args.roomName);
Candy.View.Pane.Room.show(args.roomJid);
}
Candy.View.Pane.Roster.update(args.roomJid, args.user, args.action, args.currentUser);
// Notify private user chats if existing
if(Candy.View.Pane.Chat.rooms[args.user.getJid()]) {
Candy.View.Pane.Roster.update(args.user.getJid(), args.user, args.action, args.currentUser);
Candy.View.Pane.PrivateRoom.setStatus(args.user.getJid(), args.action);
}
}
},
/** Function: notifyPrivateChats
* Notify private user chats if existing
*
* Parameters:
* (Candy.Core.chatUser) user - User which has done the event
* (String) type - Event type (leave, join, kick/ban)
*/
notifyPrivateChats: function(user, type) {
Candy.Core.log('[View:Observer] notify Private Chats');
var roomJid;
for(roomJid in Candy.View.Pane.Chat.rooms) {
if(Candy.View.Pane.Chat.rooms.hasOwnProperty(roomJid) && Candy.View.Pane.Room.getUser(roomJid) && user.getJid() === Candy.View.Pane.Room.getUser(roomJid).getJid()) {
Candy.View.Pane.Roster.update(roomJid, user, type, user);
Candy.View.Pane.PrivateRoom.setStatus(roomJid, type);
}
}
}
};
/** Class: Candy.View.Observer.PresenceError
* Presence error events
*/
self.PresenceError = {
/** Function: update
* Presence errors get handled in this method
*
* Parameters:
* (Candy.Core.Event) obj - Candy core event object
* (Object) args - {msg, type, roomJid, roomName}
*/
update: function(obj, args) {
switch(args.type) {
case 'not-authorized':
var message;
if (args.msg.children('x').children('password').length > 0) {
message = $.i18n._('passwordEnteredInvalid', [args.roomName]);
}
Candy.View.Pane.Chat.Modal.showEnterPasswordForm(args.roomJid, args.roomName, message);
break;
case 'conflict':
Candy.View.Pane.Chat.Modal.showNicknameConflictForm(args.roomJid);
break;
case 'registration-required':
Candy.View.Pane.Chat.Modal.showError('errorMembersOnly', [args.roomName]);
break;
case 'service-unavailable':
Candy.View.Pane.Chat.Modal.showError('errorMaxOccupantsReached', [args.roomName]);
break;
}
}
}
/** Class: Candy.View.Observer.Message
* Message related events
*/
self.Message = {
/** Function: update
* Messages received get dispatched from this method.
*
* Parameters:
* (Candy.Core.Event) obj - Candy core event object
* (Object) args - {message, roomJid}
*/
update: function(obj, args) {
if(args.message.type === 'subject') {
if (!Candy.View.Pane.Chat.rooms[args.roomJid]) {
Candy.View.Pane.Room.init(args.roomJid, args.message.name);
Candy.View.Pane.Room.show(args.roomJid);
}
Candy.View.Pane.Room.setSubject(args.roomJid, args.message.body);
} else if(args.message.type === 'info') {
Candy.View.Pane.Chat.infoMessage(args.roomJid, args.message.body);
} else {
// Initialize room if it's a message for a new private user chat
if(args.message.type === 'chat' && !Candy.View.Pane.Chat.rooms[args.roomJid]) {
Candy.View.Pane.PrivateRoom.open(args.roomJid, args.message.name, false, args.message.isNoConferenceRoomJid);
}
Candy.View.Pane.Message.show(args.roomJid, args.message.name, args.message.body, args.timestamp);
}
}
};
/** Class: Candy.View.Observer.Login
* Handles when display login window should appear
*/
self.Login = {
/** Function: update
* The login event gets dispatched to this method
*
* Parameters:
* (Candy.Core.Event) obj - Candy core event object
* (Object) args - {presetJid}
*/
update: function(obj, args) {
Candy.View.Pane.Chat.Modal.showLoginForm(null, args.presetJid);
}
};
return self;
}(Candy.View.Observer || {}, jQuery));
\ No newline at end of file
/** File: pane.js
* Candy - Chats are not dead yet.
*
* Authors:
* - Patrick Stadler <patrick.stadler@gmail.com>
* - Michael Weibel <michael.weibel@gmail.com>
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
*/
/** Class: Candy.View.Pane
* Candy view pane handles everything regarding DOM updates etc.
*
* Parameters:
* (Candy.View.Pane) self - itself
* (jQuery) $ - jQuery
*/
Candy.View.Pane = (function(self, $) {
/** Class: Candy.View.Pane.Window
* Window related view updates
*/
self.Window = {
/** PrivateVariable: _hasFocus
* Window has focus
*/
_hasFocus: true,
/** PrivateVariable: _plainTitle
* Document title
*/
_plainTitle: document.title,
/** PrivateVariable: _unreadMessagesCount
* Unread messages count
*/
_unreadMessagesCount: 0,
/** Variable: autoscroll
* Boolean whether autoscroll is enabled
*/
autoscroll: true,
/** Function: hasFocus
* Checks if window has focus
*
* Returns:
* (Boolean)
*/
hasFocus: function() {
return self.Window._hasFocus;
},
/** Function: increaseUnreadMessages
* Increases unread message count in window title by one.
*/
increaseUnreadMessages: function() {
self.Window.renderUnreadMessages(++self.Window._unreadMessagesCount);
},
/** Function: reduceUnreadMessages
* Reduce unread message count in window title by `num`.
*
* Parameters:
* (Integer) num - Unread message count will be reduced by this value
*/
reduceUnreadMessages: function(num) {
self.Window._unreadMessagesCount -= num;
if(self.Window._unreadMessagesCount <= 0) {
self.Window.clearUnreadMessages();
} else {
self.Window.renderUnreadMessages(self.Window._unreadMessagesCount);
}
},
/** Function: clearUnreadMessages
* Clear unread message count in window title.
*/
clearUnreadMessages: function() {
self.Window._unreadMessagesCount = 0;
document.title = self.Window._plainTitle;
},
/** Function: renderUnreadMessages
* Update window title to show message count.
*
* Parameters:
* (Integer) count - Number of unread messages to show in window title
*/
renderUnreadMessages: function(count) {
document.title = Candy.View.Template.Window.unreadmessages.replace('{{count}}', count).replace('{{title}}', self.Window._plainTitle);
},
/** Function: onFocus
* Window focus event handler.
*/
onFocus: function() {
self.Window._hasFocus = true;
if (Candy.View.getCurrent().roomJid) {
self.Room.setFocusToForm(Candy.View.getCurrent().roomJid);
self.Chat.clearUnreadMessages(Candy.View.getCurrent().roomJid);
}
},
/** Function: onBlur
* Window blur event handler.
*/
onBlur: function() {
self.Window._hasFocus = false;
}
};
/** Class: Candy.View.Pane.Chat
* Chat-View related view updates
*/
self.Chat = {
/** Variable: rooms
* Contains opened room elements
*/
rooms: [],
/** Function: addTab
* Add a tab to the chat pane.
*
* Parameters:
* (String) roomJid - JID of room
* (String) roomName - Tab label
* (String) roomType - Type of room: `groupchat` or `chat`
*/
addTab: function(roomJid, roomName, roomType) {
var roomId = Candy.Util.jidToId(roomJid),
html = Mustache.to_html(Candy.View.Template.Chat.tab, {
roomJid: roomJid,
roomId: roomId,
name: roomName || Strophe.getNodeFromJid(roomJid),
privateUserChat: function() { return roomType === 'chat'; },
roomType: roomType
}),
tab = $(html).appendTo('#chat-tabs');
tab.click(self.Chat.tabClick);
// TODO: maybe we find a better way to get the close element.
$('a.close', tab).click(self.Chat.tabClose);
self.Chat.fitTabs();
},
/** Function: getTab
* Get tab by JID.
*
* Parameters:
* (String) roomJid - JID of room
*
* Returns:
* (jQuery object) - Tab element
*/
getTab: function(roomJid) {
return $('#chat-tabs').children('li[data-roomjid="' + roomJid + '"]');
},
/** Function: removeTab
* Remove tab element.
*
* Parameters:
* (String) roomJid - JID of room
*/
removeTab: function(roomJid) {
self.Chat.getTab(roomJid).remove();
self.Chat.fitTabs();
},
/** Function: setActiveTab
* Set the active tab.
*
* Add CSS classname `active` to the choosen tab and remove `active` from all other.
*
* Parameters:
* (String) roomJid - JID of room
*/
setActiveTab: function(roomJid) {
$('#chat-tabs').children().each(function() {
var tab = $(this);
if(tab.attr('data-roomjid') === roomJid) {
tab.addClass('active');
} else {
tab.removeClass('active');
}
});
},
/** Function: increaseUnreadMessages
* Increase unread message count in a tab by one.
*
* Parameters:
* (String) roomJid - JID of room
*
* Uses:
* - <Window.increaseUnreadMessages>
*/
increaseUnreadMessages: function(roomJid) {
var unreadElem = this.getTab(roomJid).find('.unread');
unreadElem.show().text(unreadElem.text() !== '' ? parseInt(unreadElem.text(), 10) + 1 : 1);
// only increase window unread messages in private chats
if (self.Chat.rooms[roomJid].type === 'chat') {
self.Window.increaseUnreadMessages();
}
},
/** Function: clearUnreadMessages
* Clear unread message count in a tab.
*
* Parameters:
* (String) roomJid - JID of room
*
* Uses:
* - <Window.reduceUnreadMessages>
*/
clearUnreadMessages: function(roomJid) {
var unreadElem = self.Chat.getTab(roomJid).find('.unread');
self.Window.reduceUnreadMessages(unreadElem.text());
unreadElem.hide().text('');
},
/** Function: tabClick
* Tab click event: show the room associated with the tab and stops the event from doing the default.
*/
tabClick: function(e) {
// remember scroll position of current room
var currentRoomJid = Candy.View.getCurrent().roomJid;
self.Chat.rooms[currentRoomJid].scrollPosition = self.Room.getPane(currentRoomJid, '.message-pane-wrapper').scrollTop();
self.Room.show($(this).attr('data-roomjid'));
e.preventDefault();
},
/** Function: tabClose
* Tab close (click) event: Leave the room (groupchat) or simply close the tab (chat).
*
* Parameters:
* (DOMEvent) e - Event triggered
*
* Returns:
* (Boolean) - false, this will stop the event from bubbling
*/
tabClose: function(e) {
var roomJid = $(this).parent().attr('data-roomjid');
// close private user tab
if(self.Chat.rooms[roomJid].type === 'chat') {
self.Room.close(roomJid);
// close multi-user room tab
} else {
Candy.Core.Action.Jabber.Room.Leave(roomJid);
}
return false;
},
/** Function: allTabsClosed
* All tabs closed event: Disconnect from service. Hide sound control.
*
* TODO: Handle window close
*
* Returns:
* (Boolean) - false, this will stop the event from bubbling
*/
allTabsClosed: function() {
Candy.Core.disconnect();
self.Chat.Toolbar.hide();
return;
// this is a workaround because browsers prevent to close non-js-opened windows
/*if($.browser.msie) {
this.focus();
self.opener = this;
self.close();
} else {
window.open(location.href, '_self');
window.close();
}*/
},
/** Function: fitTabs
* Fit tab size according to window size
*/
fitTabs: function() {
var availableWidth = $('#chat-tabs').innerWidth(),
tabsWidth = 0,
tabs = $('#chat-tabs').children();
tabs.each(function() {
tabsWidth += $(this).css({ width: 'auto', overflow: 'visible' }).outerWidth(true);
});
if(tabsWidth > availableWidth) {
// tabs.[outer]Width() measures the first element in `tabs`. It's no very readable but nearly two times faster than using :first
var tabDiffToRealWidth = tabs.outerWidth(true) - tabs.width(),
tabWidth = Math.floor((availableWidth) / tabs.length) - tabDiffToRealWidth;
tabs.css({ width: tabWidth, overflow: 'hidden' });
}
},
/** Function: updateToolbar
* Show toolbar
*/
updateToolbar: function(roomJid) {
$('#chat-toolbar').find('.context').click(function(e) {
self.Chat.Context.show(e.currentTarget, roomJid);
e.stopPropagation();
});
Candy.View.Pane.Chat.Toolbar.updateUsercount(Candy.View.Pane.Chat.rooms[roomJid].usercount);
},
/** Function: adminMessage
* Display admin message
*
* Parameters:
* (String) subject - Admin message subject
* (String) message - Message to be displayed
*/
adminMessage: function(subject, message) {
if(Candy.View.getCurrent().roomJid) { // Simply dismiss admin message if no room joined so far. TODO: maybe we should show those messages on a dedicated pane?
var html = Mustache.to_html(Candy.View.Template.Chat.adminMessage, {
subject: subject,
message: message,
sender: $.i18n._('administratorMessageSubject'),
time: Candy.Util.localizedTime(new Date().toGMTString())
});
$('#chat-rooms').children().each(function() {
self.Room.appendToMessagePane($(this).attr('data-roomjid'), html);
});
self.Room.scrollToBottom(Candy.View.getCurrent().roomJid);
Candy.View.Event.Chat.onAdminMessage({'subject' : subject, 'message' : message});
}
},
/** Function: infoMessage
* Display info message. This is a wrapper for <onInfoMessage> to be able to disable certain info messages.
*
* Parameters:
* (String) roomJid - Room JID
* (String) subject - Subject
* (String) message - Message
*/
infoMessage: function(roomJid, subject, message) {
self.Chat.onInfoMessage(roomJid, subject, message);
},
/** Function: onInfoMessage
* Display info message. Used by <infoMessage> and several other functions which do not wish that their info message
* can be disabled (such as kick/ban message or leave/join message in private chats).
*
* Parameters:
* (String) roomJid - Room JID
* (String) subject - Subject
* (String) message - Message
*/
onInfoMessage: function(roomJid, subject, message) {
if(Candy.View.getCurrent().roomJid) { // Simply dismiss info message if no room joined so far. TODO: maybe we should show those messages on a dedicated pane?
var html = Mustache.to_html(Candy.View.Template.Chat.infoMessage, {
subject: subject,
message: $.i18n._(message),
time: Candy.Util.localizedTime(new Date().toGMTString())
});
self.Room.appendToMessagePane(roomJid, html);
if (Candy.View.getCurrent().roomJid === roomJid) {
self.Room.scrollToBottom(Candy.View.getCurrent().roomJid);
}
}
},
/** Class: Candy.View.Pane.Toolbar
* Chat toolbar for things like emoticons toolbar, room management etc.
*/
Toolbar: {
/** Function: show
* Show toolbar.
*/
show: function() {
$('#chat-toolbar').show();
},
/** Function: hide
* Hide toolbar.
*/
hide: function() {
$('#chat-toolbar').hide();
},
/** Function: playSound
* Play sound (default method).
*/
playSound: function() {
self.Chat.Toolbar.onPlaySound();
},
/** Function: onPlaySound
* Sound play event handler.
*
* Don't call this method directly. Call `playSound()` instead.
* `playSound()` will only call this method if sound is enabled.
*/
onPlaySound: function() {
var chatSoundPlayer = document.getElementById('chat-sound-player');
chatSoundPlayer.SetVariable('method:stop', '');
chatSoundPlayer.SetVariable('method:play', '');
},
/** Function: onSoundControlClick
* Sound control click event handler.
*
* Toggle sound (overwrite `playSound()`) and handle cookies.
*/
onSoundControlClick: function() {
var control = $('#chat-sound-control');
if(control.hasClass('checked')) {
self.Chat.Toolbar.playSound = function() {};
Candy.Util.setCookie('candy-nosound', '1', 365);
} else {
self.Chat.Toolbar.playSound = function() {
self.Chat.Toolbar.onPlaySound();
};
Candy.Util.deleteCookie('candy-nosound');
}
control.toggleClass('checked');
},
/** Function: onAutoscrollControlClick
* Autoscroll control event handler.
*
* Toggle autoscroll
*/
onAutoscrollControlClick: function() {
var control = $('#chat-autoscroll-control');
if(control.hasClass('checked')) {
self.Room.scrollToBottom = function(roomJid) {
self.Room.onScrollToStoredPosition(roomJid);
};
self.Window.autoscroll = false;
} else {
self.Room.scrollToBottom = function(roomJid) {
self.Room.onScrollToBottom(roomJid);
};
self.Room.scrollToBottom(Candy.View.getCurrent().roomJid);
self.Window.autoscroll = true;
}
control.toggleClass('checked');
},
/** Function: onStatusMessageControlClick
* Status message control event handler.
*
* Toggle status message
*/
onStatusMessageControlClick: function() {
var control = $('#chat-statusmessage-control');
if(control.hasClass('checked')) {
self.Chat.infoMessage = function() {};
Candy.Util.setCookie('candy-nostatusmessages', '1', 365);
} else {
self.Chat.infoMessage = function(roomJid, subject, message) {
self.Chat.onInfoMessage(roomJid, subject, message);
};
Candy.Util.deleteCookie('candy-nostatusmessages');
}
control.toggleClass('checked');
},
/** Function: updateUserCount
* Update usercount element with count.
*
* Parameters:
* (Integer) count - Current usercount
*/
updateUsercount: function(count) {
$('#chat-usercount').text(count);
}
},
/** Class: Candy.View.Pane.Modal
* Modal window
*/
Modal: {
/** Function: show
* Display modal window
*
* Parameters:
* (String) html - HTML code to put into the modal window
* (Boolean) showCloseControl - set to true if a close button should be displayed [default false]
* (Boolean) showSpinner - set to true if a loading spinner should be shown [default false]
*/
show: function(html, showCloseControl, showSpinner) {
if(showCloseControl) {
self.Chat.Modal.showCloseControl();
} else {
self.Chat.Modal.hideCloseControl();
}
if(showSpinner) {
self.Chat.Modal.showSpinner();
} else {
self.Chat.Modal.hideSpinner();
}
$('#chat-modal').stop(false, true);
$('#chat-modal-body').html(html);
$('#chat-modal').fadeIn('fast');
$('#chat-modal-overlay').show();
},
/** Function: hide
* Hide modal window
*
* Parameters:
* (Function) callback - Calls the specified function after modal window has been hidden.
*/
hide: function(callback) {
$('#chat-modal').fadeOut('fast', function() {
$('#chat-modal-body').text('');
$('#chat-modal-overlay').hide();
});
// restore initial esc handling
$(document).keydown(function(e) {
if(e.which === 27) {
e.preventDefault();
}
});
if (callback) {
callback();
}
},
/** Function: showSpinner
* Show loading spinner
*/
showSpinner: function() {
$('#chat-modal-spinner').show();
},
/** Function: hideSpinner
* Hide loading spinner
*/
hideSpinner: function() {
$('#chat-modal-spinner').hide();
},
/** Function: showCloseControl
* Show a close button
*/
showCloseControl: function() {
$('#admin-message-cancel').show().click(function(e) {
self.Chat.Modal.hide();
// some strange behaviour on IE7 (and maybe other browsers) triggers onWindowUnload when clicking on the close button.
// prevent this.
e.preventDefault();
});
// enable esc to close modal
$(document).keydown(function(e) {
if(e.which === 27) {
self.Chat.Modal.hide();
e.preventDefault();
}
});
},
/** Function: hideCloseControl
* Hide the close button
*/
hideCloseControl: function() {
$('#admin-message-cancel').hide().click(function() {});
},
/** Function: showLoginForm
* Show the login form modal
*
* Parameters:
* (String) message - optional message to display above the form
* (String) presetJid - optional user jid. if set, the user will only be prompted for password.
*/
showLoginForm: function(message, presetJid) {
self.Chat.Modal.show((message ? message : '') + Mustache.to_html(Candy.View.Template.Login.form, {
_labelUsername: $.i18n._('labelUsername'),
_labelPassword: $.i18n._('labelPassword'),
_loginSubmit: $.i18n._('loginSubmit'),
displayPassword: !Candy.Core.isAnonymousConnection(),
displayUsername: Candy.Core.isAnonymousConnection() || !presetJid,
presetJid: presetJid ? presetJid : false
}));
$('#login-form').children()[0].focus();
// register submit handler
$('#login-form').submit(function(event) {
var username = $('#username').val(),
password = $('#password').val();
if (!Candy.Core.isAnonymousConnection()) {
// guess the input and create a jid out of it
var jid = Candy.Core.getUser() && username.indexOf("@") < 0 ?
username + '@' + Strophe.getDomainFromJid(Candy.Core.getUser().getJid()) : username;
if(jid.indexOf("@") < 0 && !Candy.Core.getUser()) {
Candy.View.Pane.Chat.Modal.showLoginForm($.i18n._('loginInvalid'));
} else {
//Candy.View.Pane.Chat.Modal.hide();
Candy.Core.connect(jid, password);
}
} else { // anonymous login
Candy.Core.connect(presetJid, null, username);
}
return false;
});
},
/** Function: showEnterPasswordForm
* Shows a form for entering room password
*
* Parameters:
* (String) roomJid - Room jid to join
* (String) roomName - Room name
* (String) message - [optional] Message to show as the label
*/
showEnterPasswordForm: function(roomJid, roomName, message) {
self.Chat.Modal.show(Mustache.to_html(Candy.View.Template.PresenceError.enterPasswordForm, {
roomName: roomName,
_labelPassword: $.i18n._('labelPassword'),
_label: (message ? message : $.i18n._('enterRoomPassword', [roomName])),
_joinSubmit: $.i18n._('enterRoomPasswordSubmit')
}), true);
$('#password').focus();
// register submit handler
$('#enter-password-form').submit(function() {
var password = $('#password').val();
self.Chat.Modal.hide(function() {
Candy.Core.Action.Jabber.Room.Join(roomJid, password);
});
return false;
});
},
/** Function: showNicknameConflictForm
* Shows a form indicating that the nickname is already taken and
* for chosing a new nickname
*
* Parameters:
* (String) roomJid - Room jid to join
*/
showNicknameConflictForm: function(roomJid) {
self.Chat.Modal.show(Mustache.to_html(Candy.View.Template.PresenceError.nicknameConflictForm, {
_labelNickname: $.i18n._('labelUsername'),
_label: $.i18n._('nicknameConflict'),
_loginSubmit: $.i18n._('loginSubmit')
}));
$('#nickname').focus();
// register submit handler
$('#nickname-conflict-form').submit(function() {
var nickname = $('#nickname').val();
self.Chat.Modal.hide(function() {
Candy.Core.getUser().data.nick = nickname;
Candy.Core.Action.Jabber.Room.Join(roomJid);
});
return false;
});
},
/** Function: showError
* Show modal containing error message
*
* Parameters:
* (String) message - key of translation to display
* (Array) replacements - array containing replacements for translation (%s)
*/
showError: function(message, replacements) {
self.Chat.Modal.show(Mustache.to_html(Candy.View.Template.PresenceError.displayError, {
_error: $.i18n._(message, replacements)
}), true);
}
},
/** Class: Candy.View.Pane.Tooltip
* Class to display tooltips over specific elements
*/
Tooltip: {
/** Function: show
* Show a tooltip on event.currentTarget with content specified or content within the target's attribute data-tooltip.
*
* On mouseleave on the target, hide the tooltip.
*
* Parameters:
* (Event) event - Triggered event
* (String) content - Content to display [optional]
*/
show: function(event, content) {
var tooltip = $('#tooltip'),
target = $(event.currentTarget);
if(!content) {
content = target.attr('data-tooltip');
}
if(tooltip.length === 0) {
var html = Mustache.to_html(Candy.View.Template.Chat.tooltip);
$('#chat-pane').append(html);
tooltip = $('#tooltip');
}
$('#context-menu').hide();
tooltip.stop(false, true);
tooltip.children('div').html(content);
var pos = target.offset(),
posLeft = Candy.Util.getPosLeftAccordingToWindowBounds(tooltip, pos.left),
posTop = Candy.Util.getPosTopAccordingToWindowBounds(tooltip, pos.top);
tooltip.css({'left': posLeft.px, 'top': posTop.px, backgroundPosition: posLeft.backgroundPositionAlignment + ' ' + posTop.backgroundPositionAlignment}).fadeIn('fast');
target.mouseleave(function(event) {
event.stopPropagation();
$('#tooltip').stop(false, true).fadeOut('fast', function() { $(this).css({'top': 0, 'left': 0}); });
});
}
},
/** Class: Candy.View.Pane.Context
* Context menu for actions and settings
*/
Context: {
/** Function: init
* Initialize context menu and setup mouseleave handler.
*/
init: function() {
if ($('#context-menu').length === 0) {
var html = Mustache.to_html(Candy.View.Template.Chat.Context.menu);
$('#chat-pane').append(html);
$('#context-menu').mouseleave(function() {
$(this).fadeOut('fast');
});
}
},
/** Function: show
* Show context menu (positions it according to the window height/width)
*
* Uses:
* <getMenuLinks> for getting menulinks the user has access to
* <Candy.Util.getPosLeftAccordingToWindowBounds> for positioning
* <Candy.Util.getPosTopAccordingToWindowBounds> for positioning
*
* Calls:
* <Candy.View.Event.Roster.afterContextMenu> after showing the context menu
*
* Parameters:
* (Element) elem - On which element it should be shown
* (String) roomJid - Room Jid of the room it should be shown
* (Candy.Core.chatUser) user - User
*/
show: function(elem, roomJid, user) {
elem = $(elem);
var roomId = self.Chat.rooms[roomJid].id,
menu = $('#context-menu'),
links = $('ul li', menu);
$('#tooltip').hide();
// add specific context-user class if a user is available (when context menu should be opened next to a user)
if(!user) {
user = Candy.Core.getUser();
}
links.remove();
var menulinks = this.getMenuLinks(roomJid, user, elem),
id,
clickHandler = function(roomJid, user) {
return function(event) {
event.data.callback(event, roomJid, user);
$('#context-menu').hide();
};
};
for(id in menulinks) {
if(menulinks.hasOwnProperty(id)) {
var link = menulinks[id],
html = Mustache.to_html(Candy.View.Template.Chat.Context.menulinks, {
'roomId' : roomId,
'class' : link['class'],
'id' : id,
'label' : link.label
});
$('ul', menu).append(html);
$('#context-menu-' + id).bind('click', link, clickHandler(roomJid, user));
}
}
// if `id` is set the menu is not empty
if(id) {
var pos = elem.offset(),
posLeft = Candy.Util.getPosLeftAccordingToWindowBounds(menu, pos.left),
posTop = Candy.Util.getPosTopAccordingToWindowBounds(menu, pos.top);
menu.css({'left': posLeft.px, 'top': posTop.px, backgroundPosition: posLeft.backgroundPositionAlignment + ' ' + posTop.backgroundPositionAlignment});
menu.fadeIn('fast');
Candy.View.Event.Roster.afterContextMenu({'roomJid' : roomJid, 'user' : user, 'element': menu });
return true;
}
},
/** Function: getMenuLinks
* Extends <initialMenuLinks> with <Candy.View.Event.Roster.onContextMenu> links and returns those.
*
* Returns:
* (Object) - object containing the extended menulinks.
*/
getMenuLinks: function(roomJid, user, elem) {
var menulinks = $.extend(this.initialMenuLinks(elem), Candy.View.Event.Roster.onContextMenu({'roomJid' : roomJid, 'user' : user, 'elem': elem })),
id;
for(id in menulinks) {
if(menulinks.hasOwnProperty(id) && menulinks[id].requiredPermission !== undefined && !menulinks[id].requiredPermission(user, self.Room.getUser(roomJid), elem)) {
delete menulinks[id];
}
}
return menulinks;
},
/** Function: initialMenuLinks
* Returns initial menulinks. The following are initial:
*
* - Private Chat
* - Ignore
* - Unignore
* - Kick
* - Ban
* - Change Subject
*
* Returns:
* (Object) - object containing those menulinks
*/
initialMenuLinks: function() {
return {
'private': {
requiredPermission: function(user, me) {
return me.getNick() !== user.getNick() && Candy.Core.getRoom(Candy.View.getCurrent().roomJid) && !Candy.Core.getUser().isInPrivacyList('ignore', user.getJid());
},
'class' : 'private',
'label' : $.i18n._('privateActionLabel'),
'callback' : function(e, roomJid, user) {
$('#user-' + Candy.Util.jidToId(roomJid) + '-' + Candy.Util.jidToId(user.getJid())).click();
}
},
'ignore': {
requiredPermission: function(user, me) {
return me.getNick() !== user.getNick() && !Candy.Core.getUser().isInPrivacyList('ignore', user.getJid());
},
'class' : 'ignore',
'label' : $.i18n._('ignoreActionLabel'),
'callback' : function(e, roomJid, user) {
Candy.View.Pane.Room.ignoreUser(roomJid, user.getJid());
}
},
'unignore': {
requiredPermission: function(user, me) {
return me.getNick() !== user.getNick() && Candy.Core.getUser().isInPrivacyList('ignore', user.getJid());
},
'class' : 'unignore',
'label' : $.i18n._('unignoreActionLabel'),
'callback' : function(e, roomJid, user) {
Candy.View.Pane.Room.unignoreUser(roomJid, user.getJid());
}
},
'kick': {
requiredPermission: function(user, me) {
return me.getNick() !== user.getNick() && me.isModerator() && !user.isModerator();
},
'class' : 'kick',
'label' : $.i18n._('kickActionLabel'),
'callback' : function(e, roomJid, user) {
self.Chat.Modal.show(Mustache.to_html(Candy.View.Template.Chat.Context.contextModalForm, {
_label: $.i18n._('reason'),
_submit: $.i18n._('kickActionLabel')
}), true);
$('#context-modal-field').focus();
$('#context-modal-form').submit(function(event) {
Candy.Core.Action.Jabber.Room.Admin.UserAction(roomJid, user.getJid(), 'kick', $('#context-modal-field').val());
self.Chat.Modal.hide();
return false; // stop propagation & preventDefault, as otherwise you get disconnected (wtf?)
});
}
},
'ban': {
requiredPermission: function(user, me) {
return me.getNick() !== user.getNick() && me.isModerator() && !user.isModerator();
},
'class' : 'ban',
'label' : $.i18n._('banActionLabel'),
'callback' : function(e, roomJid, user) {
self.Chat.Modal.show(Mustache.to_html(Candy.View.Template.Chat.Context.contextModalForm, {
_label: $.i18n._('reason'),
_submit: $.i18n._('banActionLabel')
}), true);
$('#context-modal-field').focus();
$('#context-modal-form').submit(function(e) {
Candy.Core.Action.Jabber.Room.Admin.UserAction(roomJid, user.getJid(), 'ban', $('#context-modal-field').val());
self.Chat.Modal.hide();
return false; // stop propagation & preventDefault, as otherwise you get disconnected (wtf?)
});
}
},
'subject': {
requiredPermission: function(user, me) {
return me.getNick() === user.getNick() && me.isModerator();
},
'class': 'subject',
'label' : $.i18n._('setSubjectActionLabel'),
'callback': function(e, roomJid, user) {
self.Chat.Modal.show(Mustache.to_html(Candy.View.Template.Chat.Context.contextModalForm, {
_label: $.i18n._('subject'),
_submit: $.i18n._('setSubjectActionLabel')
}), true);
$('#context-modal-field').focus();
$('#context-modal-form').submit(function(e) {
Candy.Core.Action.Jabber.Room.Admin.SetSubject(roomJid, $('#context-modal-field').val());
self.Chat.Modal.hide();
e.preventDefault();
});
}
}
};
},
/** Function: showEmoticonsMenu
* Shows the special emoticons menu
*
* Parameters:
* (Element) elem - Element on which it should be positioned to.
*
* Returns:
* (Boolean) - true
*/
showEmoticonsMenu: function(elem) {
elem = $(elem);
var pos = elem.offset(),
menu = $('#context-menu'),
content = $('ul', menu),
emoticons = '',
i;
$('#tooltip').hide();
for(i = Candy.Util.Parser.emoticons.length-1; i >= 0; i--) {
emoticons = '<img src="' + Candy.Util.Parser._emoticonPath + Candy.Util.Parser.emoticons[i].image + '" alt="' + Candy.Util.Parser.emoticons[i].plain + '" />' + emoticons;
}
content.html('<li class="emoticons">' + emoticons + '</li>');
content.find('img').click(function() {
var input = Candy.View.Pane.Room.getPane(Candy.View.getCurrent().roomJid, '.message-form').children('.field'),
value = input.val(),
emoticon = $(this).attr('alt') + ' ';
input.val(value ? value + ' ' + emoticon : emoticon).focus();
});
var posLeft = Candy.Util.getPosLeftAccordingToWindowBounds(menu, pos.left),
posTop = Candy.Util.getPosTopAccordingToWindowBounds(menu, pos.top);
menu.css({'left': posLeft.px, 'top': posTop.px, backgroundPosition: posLeft.backgroundPositionAlignment + ' ' + posTop.backgroundPositionAlignment});
menu.fadeIn('fast');
return true;
}
}
};
/** Class: Candy.View.Pane.Room
* Everything which belongs to room view things belongs here.
*/
self.Room = {
/** Function: init
* Initialize a new room and inserts the room html into the DOM
*
* Parameters:
* (String) roomJid - Room JID
* (String) roomName - Room name
* (String) roomType - Type: either "groupchat" or "chat" (private chat)
*
* Uses:
* - <Candy.Util.jidToId>
* - <Candy.View.Pane.Chat.addTab>
* - <getPane>
*
* Calls:
* - <Candy.View.Event.Room.onAdd>
*
* Returns:
* (String) - the room id of the element created.
*/
init: function(roomJid, roomName, roomType) {
roomType = roomType || 'groupchat';
// First room, show sound control
if(Candy.Util.isEmptyObject(self.Chat.rooms)) {
self.Chat.Toolbar.show();
}
var roomId = Candy.Util.jidToId(roomJid);
self.Chat.rooms[roomJid] = { id: roomId, usercount: 0, name: roomName, type: roomType, messageCount: 0, scrollPosition: -1 };
$('#chat-rooms').append(Mustache.to_html(Candy.View.Template.Room.pane, {
roomId: roomId,
roomJid: roomJid,
roomType: roomType,
form: {
_messageSubmit: $.i18n._('messageSubmit')
},
roster: {
_userOnline: $.i18n._('userOnline')
}
}, {
roster: Candy.View.Template.Roster.pane,
messages: Candy.View.Template.Message.pane,
form: Candy.View.Template.Room.form
}));
self.Chat.addTab(roomJid, roomName, roomType);
self.Room.getPane(roomJid, '.message-form').submit(self.Message.submit);
Candy.View.Event.Room.onAdd({'roomJid': roomJid, 'type': roomType, 'element': self.Room.getPane(roomJid)});
return roomId;
},
/** Function: show
* Show a specific room and hides the other rooms (if there are any)
*
* Parameters:
* (String) roomJid - room jid to show
*/
show: function(roomJid) {
var roomId = self.Chat.rooms[roomJid].id;
$('.room-pane').each(function() {
var elem = $(this);
if(elem.attr('id') === ('chat-room-' + roomId)) {
elem.show();
Candy.View.getCurrent().roomJid = roomJid;
self.Chat.updateToolbar(roomJid);
self.Chat.setActiveTab(roomJid);
self.Chat.clearUnreadMessages(roomJid);
self.Room.setFocusToForm(roomJid);
self.Room.scrollToBottom(roomJid);
Candy.View.Event.Room.onShow({'roomJid': roomJid, 'element' : elem});
} else {
elem.hide();
Candy.View.Event.Room.onHide({'roomJid': roomJid, 'element' : elem});
}
});
},
/** Function: setSubject
* Called when someone changes the subject in the channel
*
* Parameters:
* (String) roomJid - Room Jid
* (String) subject - The new subject
*/
setSubject: function(roomJid, subject) {
var html = Mustache.to_html(Candy.View.Template.Room.subject, {
subject: subject,
roomName: self.Chat.rooms[roomJid].name,
_roomSubject: $.i18n._('roomSubject'),
time: Candy.Util.localizedTime(new Date().toGMTString())
});
self.Room.appendToMessagePane(roomJid, html);
self.Room.scrollToBottom(roomJid);
Candy.View.Event.Room.onSubjectChange({'roomJid': roomJid, 'element' : self.Room.getPane(roomJid), 'subject' : subject});
},
/** Function: close
* Close a room and remove everything in the DOM belonging to this room.
*
* NOTICE: There's a rendering bug in Opera when all rooms have been closed. (Take a look in the source for a more detailed description)
*
* Parameters:
* (String) roomJid - Room to close
*/
close: function(roomJid) {
self.Chat.removeTab(roomJid);
self.Window.clearUnreadMessages();
/* TODO:
There's a rendering bug in Opera which doesn't redraw (remove) the message form.
Only a cosmetical issue (when all tabs are closed) but it's annoying...
This happens when form has no focus too. Maybe it's because of CSS positioning.
*/
self.Room.getPane(roomJid).remove();
var openRooms = $('#chat-rooms').children();
if(Candy.View.getCurrent().roomJid === roomJid) {
Candy.View.getCurrent().roomJid = null;
if(openRooms.length === 0) {
self.Chat.allTabsClosed();
} else {
self.Room.show(openRooms.last().attr('data-roomjid'));
}
}
delete self.Chat.rooms[roomJid];
Candy.View.Event.Room.onClose({'roomJid' : roomJid});
},
/** Function: appendToMessagePane
* Append a new message to the message pane.
*
* Parameters:
* (String) roomJid - Room JID
* (String) html - rendered message html
*/
appendToMessagePane: function(roomJid, html) {
self.Room.getPane(roomJid, '.message-pane').append(html);
self.Chat.rooms[roomJid].messageCount++;
self.Room.sliceMessagePane(roomJid);
},
/** Function: sliceMessagePane
* Slices the message pane after the max amount of messages specified in the Candy View options (limit setting).
*
* This is done to hopefully prevent browsers from getting slow after a certain amount of messages in the DOM.
*
* The slice is only done when autoscroll is on, because otherwise someone might lose exactly the message he want to look for.
*
* Parameters:
* (String) roomJid - Room JID
*/
sliceMessagePane: function(roomJid) {
// Only clean if autoscroll is enabled
if(self.Window.autoscroll) {
var options = Candy.View.getOptions().messages;
if(self.Chat.rooms[roomJid].messageCount > options.limit) {
self.Room.getPane(roomJid, '.message-pane').children().slice(0, options.remove*2).remove();
self.Chat.rooms[roomJid].messageCount -= options.remove;
}
}
},
/** Function: scrollToBottom
* Scroll to bottom wrapper for <onScrollToBottom> to be able to disable it by overwriting the function.
*
* Parameters:
* (String) roomJid - Room JID
*
* Uses:
* - <onScrollToBottom>
*/
scrollToBottom: function(roomJid) {
self.Room.onScrollToBottom(roomJid);
},
/** Function: onScrollToBottom
* Scrolls to the latest message received/sent.
*
* Parameters:
* (String) roomJid - Room JID
*/
onScrollToBottom: function(roomJid) {
var messagePane = self.Room.getPane(roomJid, '.message-pane-wrapper');
messagePane.scrollTop(messagePane.prop('scrollHeight'));
},
/** Function: onScrollToStoredPosition
* When autoscroll is off, the position where the scrollbar is has to be stored for each room, because it otherwise
* goes to the top in the message window.
*
* Parameters:
* (String) roomJid - Room JID
*/
onScrollToStoredPosition: function(roomJid) {
// This should only apply when entering a room...
// ... therefore we set scrollPosition to -1 after execution.
if(self.Chat.rooms[roomJid].scrollPosition > -1) {
var messagePane = self.Room.getPane(roomJid, '.message-pane-wrapper');
messagePane.scrollTop(self.Chat.rooms[roomJid].scrollPosition);
self.Chat.rooms[roomJid].scrollPosition = -1;
}
},
/** Function: setFocusToForm
* Set focus to the message input field within the message form.
*
* Parameters:
* (String) roomJid - Room JID
*/
setFocusToForm: function(roomJid) {
var pane = self.Room.getPane(roomJid, '.message-form');
if (pane) {
// IE8 will fail maybe, because the field isn't there yet.
try {
pane.children('.field')[0].focus();
} catch(e) {
// fail silently
}
}
},
/** Function: setUser
* Sets or updates the current user in the specified room (called by <Candy.View.Pane.Roster.update>) and set specific informations
* (roles and affiliations) on the room tab (chat-pane).
*
* Parameters:
* (String) roomJid - Room in which the user is set to.
* (Candy.Core.ChatUser) user - The user
*/
setUser: function(roomJid, user) {
self.Chat.rooms[roomJid].user = user;
var roomPane = self.Room.getPane(roomJid),
chatPane = $('#chat-pane');
roomPane.attr('data-userjid', user.getJid());
// Set classes based on user role / affiliation
if(user.isModerator()) {
if (user.getRole() === user.ROLE_MODERATOR) {
chatPane.addClass('role-moderator');
}
if (user.getAffiliation() === user.AFFILIATION_OWNER) {
chatPane.addClass('affiliation-owner');
}
} else {
chatPane.removeClass('role-moderator affiliation-owner');
}
self.Chat.Context.init();
},
/** Function: getUser
* Get the current user in the room specified with the jid
*
* Parameters:
* (String) roomJid - Room of which the user should be returned from
*
* Returns:
* (Candy.Core.ChatUser) - user
*/
getUser: function(roomJid) {
return self.Chat.rooms[roomJid].user;
},
/** Function: ignoreUser
* Ignore specified user and add the ignore icon to the roster item of the user
*
* Parameters:
* (String) roomJid - Room in which the user should be ignored
* (String) userJid - User which should be ignored
*/
ignoreUser: function(roomJid, userJid) {
Candy.Core.Action.Jabber.Room.IgnoreUnignore(userJid);
Candy.View.Pane.Room.addIgnoreIcon(roomJid, userJid);
},
/** Function: unignoreUser
* Unignore an ignored user and remove the ignore icon of the roster item.
*
* Parameters:
* (String) roomJid - Room in which the user should be unignored
* (String) userJid - User which should be unignored
*/
unignoreUser: function(roomJid, userJid) {
Candy.Core.Action.Jabber.Room.IgnoreUnignore(userJid);
Candy.View.Pane.Room.removeIgnoreIcon(roomJid, userJid);
},
/** Function: addIgnoreIcon
* Add the ignore icon to the roster item of the specified user
*
* Parameters:
* (String) roomJid - Room in which the roster item should be updated
* (String) userJid - User of which the roster item should be updated
*/
addIgnoreIcon: function(roomJid, userJid) {
if (Candy.View.Pane.Chat.rooms[userJid]) {
$('#user-' + Candy.View.Pane.Chat.rooms[userJid].id + '-' + Candy.Util.jidToId(userJid)).addClass('status-ignored');
}
if (Candy.View.Pane.Chat.rooms[Strophe.getBareJidFromJid(roomJid)]) {
$('#user-' + Candy.View.Pane.Chat.rooms[Strophe.getBareJidFromJid(roomJid)].id + '-' + Candy.Util.jidToId(userJid)).addClass('status-ignored');
}
},
/** Function: removeIgnoreIcon
* Remove the ignore icon to the roster item of the specified user
*
* Parameters:
* (String) roomJid - Room in which the roster item should be updated
* (String) userJid - User of which the roster item should be updated
*/
removeIgnoreIcon: function(roomJid, userJid) {
if (Candy.View.Pane.Chat.rooms[userJid]) {
$('#user-' + Candy.View.Pane.Chat.rooms[userJid].id + '-' + Candy.Util.jidToId(userJid)).removeClass('status-ignored');
}
if (Candy.View.Pane.Chat.rooms[Strophe.getBareJidFromJid(roomJid)]) {
$('#user-' + Candy.View.Pane.Chat.rooms[Strophe.getBareJidFromJid(roomJid)].id + '-' + Candy.Util.jidToId(userJid)).removeClass('status-ignored');
}
},
/** Function: getPane
* Get the chat room pane or a subPane of it (if subPane is specified)
*
* Parameters:
* (String) roomJid - Room in which the pane lies
* (String) subPane - Sub pane of the chat room pane if needed [optional]
*/
getPane: function(roomJid, subPane) {
if (self.Chat.rooms[roomJid]) {
if(subPane) {
if(self.Chat.rooms[roomJid]['pane-' + subPane]) {
return self.Chat.rooms[roomJid]['pane-' + subPane];
} else {
self.Chat.rooms[roomJid]['pane-' + subPane] = $('#chat-room-' + self.Chat.rooms[roomJid].id).find(subPane);
return self.Chat.rooms[roomJid]['pane-' + subPane];
}
} else {
return $('#chat-room-' + self.Chat.rooms[roomJid].id);
}
}
}
};
/** Class: Candy.View.Pane.PrivateRoom
* Private room handling
*/
self.PrivateRoom = {
/** Function: open
* Opens a new private room
*
* Parameters:
* (String) roomJid - Room jid to open
* (String) roomName - Room name
* (Boolean) switchToRoom - If true, displayed room switches automatically to this room
* (e.g. when user clicks itself on another user to open a private chat)
* (Boolean) isNoConferenceRoomJid - true if a 3rd-party client sends a direct message to this user (not via the room)
* then the username is the node and not the resource. This param addresses this case.
*
* Calls:
* - <Candy.View.Event.Room.onAdd>
*/
open: function(roomJid, roomName, switchToRoom, isNoConferenceRoomJid) {
var user = isNoConferenceRoomJid ? Candy.Core.getUser() : self.Room.getUser(Strophe.getBareJidFromJid(roomJid));
// if target user is in privacy list, don't open the private chat.
if (Candy.Core.getUser().isInPrivacyList('ignore', roomJid)) {
return false;
}
if(!self.Chat.rooms[roomJid]) {
self.Room.init(roomJid, roomName, 'chat');
}
if(switchToRoom) {
self.Room.show(roomJid);
}
self.Roster.update(roomJid, new Candy.Core.ChatUser(roomJid, roomName), 'join', user);
self.Roster.update(roomJid, user, 'join', user);
self.PrivateRoom.setStatus(roomJid, 'join');
// We can't track the presence of a user if it's not a conference jid
if(isNoConferenceRoomJid) {
self.Chat.infoMessage(roomJid, $.i18n._('presenceUnknownWarningSubject'), $.i18n._('presenceUnknownWarning'));
}
Candy.View.Event.Room.onAdd({'roomJid': roomJid, type: 'chat', 'element': self.Room.getPane(roomJid)});
},
/** Function: setStatus
* Set offline or online status for private rooms (when one of the participants leaves the room)
*
* Parameters:
* (String) roomJid - Private room jid
* (String) status - "leave"/"join"
*/
setStatus: function(roomJid, status) {
var messageForm = self.Room.getPane(roomJid, '.message-form');
if(status === 'join') {
self.Chat.getTab(roomJid).addClass('online').removeClass('offline');
messageForm.children('.field').removeAttr('disabled');
messageForm.children('.submit').removeAttr('disabled');
self.Chat.getTab(roomJid);
} else {
self.Chat.getTab(roomJid).addClass('offline').removeClass('online');
messageForm.children('.field').attr('disabled', true);
messageForm.children('.submit').attr('disabled', true);
}
}
};
/** Class Candy.View.Pane.Roster
* Handles everyhing regarding roster updates.
*/
self.Roster = {
/** Function: update
* Called by <Candy.View.Observer.Presence.update> to update the roster if needed.
* Adds/removes users from the roster list or updates informations on their items (roles, affiliations etc.)
*
* TODO: Refactoring, this method has too much LOC.
*
* Parameters:
* (String) roomJid - Room JID in which the update happens
* (Candy.Core.ChatUser) user - User on which the update happens
* (String) action - one of "join", "leave", "kick" and "ban"
* (Candy.Core.ChatUser) currentUser - Current user
*/
update: function(roomJid, user, action, currentUser) {
var roomId = self.Chat.rooms[roomJid].id,
userId = Candy.Util.jidToId(user.getJid()),
usercountDiff = -1;
// a user joined the room
if(action === 'join') {
usercountDiff = 1;
var html = Mustache.to_html(Candy.View.Template.Roster.user, {
roomId: roomId,
userId : userId,
userJid: user.getJid(),
nick: user.getNick(),
displayNick: Candy.Util.crop(user.getNick(), Candy.View.getOptions().crop.roster.nickname),
role: user.getRole(),
affiliation: user.getAffiliation(),
me: currentUser !== undefined && user.getNick() === currentUser.getNick(),
tooltipRole: $.i18n._('tooltipRole'),
tooltipIgnored: $.i18n._('tooltipIgnored')
}),
userElem = $('#user-' + roomId + '-' + userId);
if(userElem.length < 1) {
var userInserted = false,
rosterPane = self.Room.getPane(roomJid, '.roster-pane');
// there are already users in the roster
if(rosterPane.children().length > 0) {
// insert alphabetically
var userSortCompare = user.getNick().toUpperCase();
rosterPane.children().each(function() {
var elem = $(this);
if(elem.attr('data-nick').toUpperCase() > userSortCompare) {
elem.before(html);
userInserted = true;
return false;
}
return true;
});
}
// first user in roster
if(!userInserted) {
rosterPane.append(html);
}
self.Roster.joinAnimation('user-' + roomId + '-' + userId);
// only show other users joining & don't show if there's no message in the room.
if(currentUser !== undefined && user.getNick() !== currentUser.getNick() && self.Room.getUser(roomJid)) {
// always show join message in private room, even if status messages have been disabled
if (self.Chat.rooms[roomJid].type === 'chat') {
self.Chat.onInfoMessage(roomJid, $.i18n._('userJoinedRoom', [user.getNick()]));
} else {
self.Chat.infoMessage(roomJid, $.i18n._('userJoinedRoom', [user.getNick()]));
}
}
// user is in room but maybe the affiliation/role has changed
} else {
usercountDiff = 0;
userElem.replaceWith(html);
$('#user-' + roomId + '-' + userId).css({opacity: 1}).show();
}
// Presence of client
if (currentUser !== undefined && currentUser.getNick() === user.getNick()) {
self.Room.setUser(roomJid, user);
// add click handler for private chat
} else {
$('#user-' + roomId + '-' + userId).click(self.Roster.userClick);
}
$('#user-' + roomId + '-' + userId + ' .context').click(function(e) {
self.Chat.Context.show(e.currentTarget, roomJid, user);
e.stopPropagation();
});
// check if current user is ignoring the user who has joined.
if (currentUser !== undefined && currentUser.isInPrivacyList('ignore', user.getJid())) {
Candy.View.Pane.Room.addIgnoreIcon(roomJid, user.getJid());
}
// a user left the room
} else if(action === 'leave') {
self.Roster.leaveAnimation('user-' + roomId + '-' + userId);
// always show leave message in private room, even if status messages have been disabled
if (self.Chat.rooms[roomJid].type === 'chat') {
self.Chat.onInfoMessage(roomJid, $.i18n._('userLeftRoom', [user.getNick()]));
} else {
self.Chat.infoMessage(roomJid, $.i18n._('userLeftRoom', [user.getNick()]));
}
// user has been kicked
} else if(action === 'kick') {
self.Roster.leaveAnimation('user-' + roomId + '-' + userId);
self.Chat.onInfoMessage(roomJid, $.i18n._('userHasBeenKickedFromRoom', [user.getNick()]));
// user has been banned
} else if(action === 'ban') {
self.Roster.leaveAnimation('user-' + roomId + '-' + userId);
self.Chat.onInfoMessage(roomJid, $.i18n._('userHasBeenBannedFromRoom', [user.getNick()]));
}
// Update user count
Candy.View.Pane.Chat.rooms[roomJid].usercount += usercountDiff;
if(roomJid === Candy.View.getCurrent().roomJid) {
Candy.View.Pane.Chat.Toolbar.updateUsercount(Candy.View.Pane.Chat.rooms[roomJid].usercount);
}
Candy.View.Event.Roster.onUpdate({'roomJid' : roomJid, 'user' : user, 'action': action, 'element': $('#user-' + roomId + '-' + userId)});
},
/** Function: userClick
* Click handler for opening a private room
*/
userClick: function() {
var elem = $(this);
self.PrivateRoom.open(elem.attr('data-jid'), elem.attr('data-nick'), true);
},
/** Function: joinAnimation
* Animates specified elementId on join
*
* Parameters:
* (String) elementId - Specific element to do the animation on
*/
joinAnimation: function(elementId) {
$('#' + elementId).stop(true).slideDown('normal', function() { $(this).animate({ opacity: 1 }); });
},
/** Function: leaveAnimation
* Leave animation for specified element id and removes the DOM element on completion.
*
* Parameters:
* (String) elementId - Specific element to do the animation on
*/
leaveAnimation: function(elementId) {
$('#' + elementId).stop(true).attr('id', '#' + elementId + '-leaving').animate({ opacity: 0 }, {
complete: function() {
$(this).slideUp('normal', function() { $(this).remove(); });
}
});
}
};
/** Class: Candy.View.Pane.Message
* Message submit/show handling
*/
self.Message = {
/** Function: submit
* on submit handler for message field sends the message to the server and if it's a private chat, shows the message
* immediately because the server doesn't send back those message.
*
* Parameters:
* (Event) event - Triggered event
*/
submit: function(event) {
var roomType = Candy.View.Pane.Chat.rooms[Candy.View.getCurrent().roomJid].type,
message = $(this).children('.field').val().substring(0, Candy.View.getOptions().crop.message.body);
message = Candy.View.Event.Message.beforeSend(message);
Candy.Core.Action.Jabber.Room.Message(Candy.View.getCurrent().roomJid, message, roomType);
// Private user chat. Jabber won't notify the user who has sent the message. Just show it as the user hits the button...
if(roomType === 'chat' && message) {
self.Message.show(Candy.View.getCurrent().roomJid, self.Room.getUser(Candy.View.getCurrent().roomJid).getNick(), message);
}
// Clear input and set focus to it
$(this).children('.field').val('').focus();
event.preventDefault();
},
/** Function: show
* Show a message in the message pane
*
* Parameters:
* (String) roomJid - room in which the message has been sent to
* (String) name - Name of the user which sent the message
* (String) message - Message
* (String) timestamp - [optional] Timestamp of the message, if not present, current date.
*/
show: function(roomJid, name, message, timestamp) {
message = Candy.Util.Parser.all(message.substring(0, Candy.View.getOptions().crop.message.body));
message = Candy.View.Event.Message.beforeShow({'roomJid': roomJid, 'nick': name, 'message': message});
if(!message) {
return;
}
var html = Mustache.to_html(Candy.View.Template.Message.item, {
name: name,
displayName: Candy.Util.crop(name, Candy.View.getOptions().crop.message.nickname),
message: message,
time: Candy.Util.localizedTime(timestamp || new Date().toGMTString())
});
self.Room.appendToMessagePane(roomJid, html);
var elem = self.Room.getPane(roomJid, '.message-pane').children().last();
// click on username opens private chat
elem.find('a.name').click(function(event) {
event.preventDefault();
// Check if user is online and not myself
if(name !== self.Room.getUser(Candy.View.getCurrent().roomJid).getNick() && Candy.Core.getRoom(roomJid).getRoster().get(roomJid + '/' + name)) {
Candy.View.Pane.PrivateRoom.open(roomJid + '/' + name, name, true);
}
});
// Notify the user about a new private message
if(Candy.View.getCurrent().roomJid !== roomJid || !self.Window.hasFocus()) {
self.Chat.increaseUnreadMessages(roomJid);
if(Candy.View.Pane.Chat.rooms[roomJid].type === 'chat' && !self.Window.hasFocus()) {
self.Chat.Toolbar.playSound();
}
}
if(Candy.View.getCurrent().roomJid === roomJid) {
self.Room.scrollToBottom(roomJid);
}
Candy.View.Event.Message.onShow({'roomJid': roomJid, 'element': elem, 'nick': name, 'message': message});
}
};
return self;
}(Candy.View.Pane || {}, jQuery));
/** File: template.js
* Candy - Chats are not dead yet.
*
* Authors:
* - Patrick Stadler <patrick.stadler@gmail.com>
* - Michael Weibel <michael.weibel@gmail.com>
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
*/
/** Class: Candy.View.Template
* Contains mustache.js templates
*/
Candy.View.Template = (function(self){
self.Window = {
/**
* Unread messages - used to extend the window title
*/
unreadmessages: '({{count}}) {{title}}'
};
self.Chat = {
pane: '<div id="chat-pane">{{> tabs}}{{> toolbar}}{{> rooms}}</div>{{> modal}}',
rooms: '<div id="chat-rooms" class="rooms"></div>',
tabs: '<ul id="chat-tabs"></ul>',
tab: '<li class="roomtype-{{roomType}}" data-roomjid="{{roomJid}}" data-roomtype="{{roomType}}"><a href="#" class="label">{{#privateUserChat}}@{{/privateUserChat}}{{name}}</a><a href="#" class="transition"></a><a href="#" class="close">\u00D7</a><small class="unread"></small></li>',
modal: '<div id="chat-modal"><a id="admin-message-cancel" class="close" href="#">\u00D7</a><span id="chat-modal-body"></span><img src="{{resourcesPath}}img/modal-spinner.gif" id="chat-modal-spinner" /></div><div id="chat-modal-overlay"></div>',
adminMessage: '<dt>{{time}}</dt><dd class="adminmessage"><span class="label">{{sender}}</span>{{subject}} {{message}}</dd>',
infoMessage: '<dt>{{time}}</dt><dd class="infomessage">{{subject}} {{message}}</dd>',
toolbar: '<ul id="chat-toolbar"><li id="emoticons-icon" data-tooltip="{{tooltipEmoticons}}"></li><li id="chat-sound-control" class="checked" data-tooltip="{{tooltipSound}}">{{> soundcontrol}}</li><li id="chat-autoscroll-control" class="checked" data-tooltip="{{tooltipAutoscroll}}"></li><li class="checked" id="chat-statusmessage-control" data-tooltip="{{tooltipStatusmessage}}"></li><li class="context" data-tooltip="{{tooltipAdministration}}"></li><li class="usercount" data-tooltip="{{tooltipUsercount}}"><span id="chat-usercount"></span></li></ul>',
soundcontrol: '<script type="text/javascript">var audioplayerListener = new Object(); audioplayerListener.onInit = function() { };'
+ '</script><object id="chat-sound-player" type="application/x-shockwave-flash" data="{{resourcesPath}}audioplayer.swf"'
+ ' width="0" height="0"><param name="movie" value="{{resourcesPath}}audioplayer.swf" /><param name="AllowScriptAccess"'
+ ' value="always" /><param name="FlashVars" value="listener=audioplayerListener&amp;mp3={{resourcesPath}}notify.mp3" />'
+ '</object>',
Context: {
menu: '<div id="context-menu"><ul></ul></div>',
menulinks: '<li class="{{class}}" id="context-menu-{{id}}">{{label}}</li>',
contextModalForm: '<form action="#" id="context-modal-form"><label for="context-modal-label">{{_label}}</label><input type="text" name="contextModalField" id="context-modal-field" /><input type="submit" class="button" name="send" value="{{_submit}}" /></form>',
adminMessageReason: '<a id="admin-message-cancel" class="close" href="#">×</a><p>{{_action}}</p>{{#reason}}<p>{{_reason}}</p>{{/reason}}'
},
tooltip: '<div id="tooltip"><div></div></div>'
};
self.Room = {
pane: '<div class="room-pane roomtype-{{roomType}}" id="chat-room-{{roomId}}" data-roomjid="{{roomJid}}" data-roomtype="{{roomType}}">{{> roster}}{{> messages}}{{> form}}</div>',
subject: '<dt>{{time}}</dt><dd class="subject"><span class="label">{{roomName}}</span>{{_roomSubject}} {{subject}}</dd>',
form: '<div class="message-form-wrapper"></div><form method="post" class="message-form"><input name="message" class="field" type="text" autocomplete="off" maxlength="1000" /><input type="submit" class="submit" name="submit" value="{{_messageSubmit}}" /></form>'
};
self.Roster = {
pane: '<div class="roster-pane"></div>',
user: '<div class="user role-{{role}} affiliation-{{affiliation}}{{#me}} me{{/me}}" id="user-{{roomId}}-{{userId}}" data-jid="{{userJid}}" data-nick="{{nick}}" data-role="{{role}}" data-affiliation="{{affiliation}}"><div class="label">{{displayNick}}</div><ul><li class="context" id="context-{{roomId}}-{{userId}}"></li><li class="role role-{{role}} affiliation-{{affiliation}}" data-tooltip="{{tooltipRole}}"></li><li class="ignore" data-tooltip="{{tooltipIgnored}}"></li></ul></div>'
};
self.Message = {
pane: '<div class="message-pane-wrapper"><dl class="message-pane"></dl></div>',
item: '<dt>{{time}}</dt><dd><span class="label"><a href="#" class="name">{{displayName}}</a></span>{{{message}}}</dd>'
};
self.Login = {
form: '<form method="post" id="login-form" class="login-form">'
+ '{{#displayUsername}}<label for="username">{{_labelUsername}}</label><input type="text" id="username" name="username"/>{{/displayUsername}}'
+ '{{#presetJid}}<input type="hidden" id="username" name="username" value="{{presetJid}}"/>{{/presetJid}}'
+ '{{#displayPassword}}<label for="password">{{_labelPassword}}</label><input type="password" id="password" name="password" />{{/displayPassword}}'
+ '<input type="submit" class="button" value="{{_loginSubmit}}" /></form>'
};
self.PresenceError = {
enterPasswordForm: '<strong>{{_label}}</strong>'
+ '<form method="post" id="enter-password-form" class="enter-password-form">'
+ '<label for="password">{{_labelPassword}}</label><input type="password" id="password" name="password" />'
+ '<input type="submit" class="button" value="{{_joinSubmit}}" /></form>',
nicknameConflictForm: '<strong>{{_label}}</strong>'
+ '<form method="post" id="nickname-conflict-form" class="nickname-conflict-form">'
+ '<label for="nickname">{{_labelNickname}}</label><input type="text" id="nickname" name="nickname" />'
+ '<input type="submit" class="button" value="{{_loginSubmit}}" /></form>',
displayError: '<strong>{{_error}}</strong>'
};
return self;
}(Candy.View.Template || {}));
/** File: translation.js
* Candy - Chats are not dead yet.
*
* Authors:
* - Patrick Stadler <patrick.stadler@gmail.com>
* - Michael Weibel <michael.weibel@gmail.com>
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
*/
/** Class: Candy.View.Translation
* Contains translations
*/
Candy.View.Translation = {
'en' : {
'status': 'Status: %s',
'statusConnecting': 'Connecting...',
'statusConnected' : 'Connected',
'statusDisconnecting': 'Disconnecting...',
'statusDisconnected' : 'Disconnected',
'statusAuthfail': 'Authentication failed',
'roomSubject' : 'Subject:',
'messageSubmit': 'Send',
'labelUsername': 'Username:',
'labelPassword': 'Password:',
'loginSubmit' : 'Login',
'loginInvalid' : 'Invalid JID',
'reason' : 'Reason:',
'subject' : 'Subject:',
'reasonWas' : 'Reason was: %s.',
'kickActionLabel' : 'Kick',
'youHaveBeenKickedBy' : 'You have been kicked from %2$s by %1$s',
'youHaveBeenKicked' : 'You have been kicked from %s',
'banActionLabel' : 'Ban',
'youHaveBeenBannedBy' : 'You have been banned from %1$s by %2$s',
'youHaveBeenBanned' : 'You have been banned from %s',
'privateActionLabel' : 'Private chat',
'ignoreActionLabel' : 'Ignore',
'unignoreActionLabel' : 'Unignore',
'setSubjectActionLabel': 'Change Subject',
'administratorMessageSubject' : 'Administrator',
'userJoinedRoom' : '%s joined the room.',
'userLeftRoom' : '%s left the room.',
'userHasBeenKickedFromRoom': '%s has been kicked from the room.',
'userHasBeenBannedFromRoom': '%s has been banned from the room.',
'presenceUnknownWarningSubject': 'Notice:',
'presenceUnknownWarning' : 'This user might be offline. We can\'t track his presence.',
'dateFormat': 'dd.mm.yyyy',
'timeFormat': 'HH:MM:ss',
'tooltipRole' : 'Moderator',
'tooltipIgnored' : 'You ignore this user',
'tooltipEmoticons' : 'Emoticons',
'tooltipSound' : 'Play sound for new private messages',
'tooltipAutoscroll' : 'Autoscroll',
'tooltipStatusmessage' : 'Display status messages',
'tooltipAdministration' : 'Room Administration',
'tooltipUsercount' : 'Room Occupants',
'enterRoomPassword' : 'Room "%s" is password protected.',
'enterRoomPasswordSubmit' : 'Join room',
'passwordEnteredInvalid' : 'Invalid password for room "%s".',
'nicknameConflict': 'Username already in use. Please choose another one.',
'errorMembersOnly': 'You can\'t join room "%s": Insufficient rights.',
'errorMaxOccupantsReached': 'You can\'t join room "%s": Too many occupants.',
'antiSpamMessage' : 'Please do not spam. You have been blocked for a short-time.'
},
'de' : {
'status': 'Status: %s',
'statusConnecting': 'Verbinden...',
'statusConnected' : 'Verbunden',
'statusDisconnecting': 'Verbindung trennen...',
'statusDisconnected' : 'Verbindung getrennt',
'statusAuthfail': 'Authentifizierung fehlgeschlagen',
'roomSubject' : 'Thema:',
'messageSubmit': 'Senden',
'labelUsername': 'Benutzername:',
'labelPassword': 'Passwort:',
'loginSubmit' : 'Anmelden',
'loginInvalid' : 'Ungültige JID',
'reason' : 'Begründung:',
'subject' : 'Titel:',
'reasonWas' : 'Begründung: %s.',
'kickActionLabel' : 'Kick',
'youHaveBeenKickedBy' : 'Du wurdest soeben aus dem Raum %1$s gekickt (%2$s)',
'youHaveBeenKicked' : 'Du wurdest soeben aus dem Raum %s gekickt',
'banActionLabel' : 'Ban',
'youHaveBeenBannedBy' : 'Du wurdest soeben aus dem Raum %1$s verbannt (%2$s)',
'youHaveBeenBanned' : 'Du wurdest soeben aus dem Raum %s verbannt',
'privateActionLabel' : 'Privater Chat',
'ignoreActionLabel' : 'Ignorieren',
'unignoreActionLabel' : 'Nicht mehr ignorieren',
'setSubjectActionLabel': 'Thema ändern',
'administratorMessageSubject' : 'Administrator',
'userJoinedRoom' : '%s hat soeben den Raum betreten.',
'userLeftRoom' : '%s hat soeben den Raum verlassen.',
'userHasBeenKickedFromRoom': '%s ist aus dem Raum gekickt worden.',
'userHasBeenBannedFromRoom': '%s ist aus dem Raum verbannt worden.',
'presenceUnknownWarningSubject': 'Hinweis:',
'presenceUnknownWarning' : 'Dieser Benutzer könnte bereits abgemeldet sein. Wir können seine Anwesenheit nicht verfolgen.',
'dateFormat': 'dd.mm.yyyy',
'timeFormat': 'HH:MM:ss',
'tooltipRole' : 'Moderator',
'tooltipIgnored' : 'Du ignorierst diesen Benutzer',
'tooltipEmoticons' : 'Smileys',
'tooltipSound' : 'Ton abspielen bei neuen privaten Nachrichten',
'tooltipAutoscroll' : 'Autoscroll',
'tooltipStatusmessage' : 'Statusnachrichten anzeigen',
'tooltipAdministration' : 'Raum Administration',
'tooltipUsercount' : 'Anzahl Benutzer im Raum',
'enterRoomPassword' : 'Raum "%s" ist durch ein Passwort geschützt.',
'enterRoomPasswordSubmit' : 'Raum betreten',
'passwordEnteredInvalid' : 'Inkorrektes Passwort für Raum "%s".',
'nicknameConflict': 'Der Benutzername wird bereits verwendet. Bitte wähle einen anderen.',
'errorMembersOnly': 'Du kannst den Raum "%s" nicht betreten: Ungenügende Rechte.',
'errorMaxOccupantsReached': 'Du kannst den Raum "%s" nicht betreten: Benutzerlimit erreicht.',
'antiSpamMessage' : 'Bitte nicht spammen. Du wurdest für eine kurze Zeit blockiert.'
},
'fr' : {
'status': 'Status: %s',
'statusConnecting': 'Connecter...',
'statusConnected' : 'Connecté.',
'statusDisconnecting': 'Déconnecter....',
'statusDisconnected' : 'Déconnecté.',
'statusAuthfail': 'Authentification a échoué',
'roomSubject' : 'Sujet:',
'messageSubmit': 'Envoyer',
'labelUsername': 'Nom d\'utilisateur:',
'labelPassword': 'Mot de passe:',
'loginSubmit' : 'Inscription',
'loginInvalid' : 'JID invalide',
'reason' : 'Justification:',
'subject' : 'Titre:',
'reasonWas' : 'Justification: %s.',
'kickActionLabel' : 'Kick',
'youHaveBeenKickedBy' : 'Tu as été expulsé de le salon %1$s (%2$s)',
'youHaveBeenKicked' : 'Tu as été expulsé de le salon %s',
'banActionLabel' : 'Ban',
'youHaveBeenBannedBy' : 'Tu as été banni de le salon %1$s (%2$s)',
'youHaveBeenBanned' : 'Tu as été banni de le salon %s',
'privateActionLabel' : 'Chat privé',
'ignoreActionLabel' : 'Ignorer',
'unignoreActionLabel' : 'Ne plus ignorer',
'setSubjectActionLabel': 'Changer le sujet',
'administratorMessageSubject' : 'Administrateur',
'userJoinedRoom' : '%s vient d\'entrer dans le salon.',
'userLeftRoom' : '%s vient de quitter le salon.',
'userHasBeenKickedFromRoom': '%s a été expulsé du salon.',
'userHasBeenBannedFromRoom': '%s a été banni du salon.',
'presenceUnknownWarningSubject': 'Note:',
'presenceUnknownWarning' : 'Cet utilisateur n\'est malheureusement plus connecté, le message ne sera pas envoyé.',
'dateFormat': 'dd.mm.yyyy',
'timeFormat': 'HH:MM:ss',
'tooltipRole' : 'Modérateur',
'tooltipIgnored' : 'Tu ignores cette personne',
'tooltipEmoticons' : 'Smileys',
'tooltipSound' : 'Jouer un son lorsque tu reçois de nouveaux messages privés',
'tooltipAutoscroll' : 'Auto-defilement',
'tooltipStatusmessage' : 'Messages d\'état',
'tooltipAdministration' : 'Administrer le salon',
'tooltipUsercount' : 'Nombre d\'utilisateurs dans le salon',
'enterRoomPassword' : 'Le salon "%s" est protégé par un mot de passe.',
'enterRoomPasswordSubmit' : 'Entrer dans le salon',
'passwordEnteredInvalid' : 'Le mot de passe four le salon "%s" est invalide.',
'nicknameConflict': 'Le nom d\'utilisateur est déjà utilisé. Choisi un autre.',
'errorMembersOnly': 'Tu ne peut pas entrer de le salon "%s": droits insuffisants.',
'errorMaxOccupantsReached': 'Tu ne peut pas entrer de le salon "%s": Limite d\'utilisateur atteint.',
'antiSpamMessage' : 'S\'il te plaît, pas de spam. Tu as été bloqué pendant une courte période..'
},
'nl' : {
'status': 'Status: %s',
'statusConnecting': 'Verbinding maken...',
'statusConnected' : 'Verbinding is gereed',
'statusDisconnecting': 'Verbinding verbreken...',
'statusDisconnected' : 'Verbinding is verbroken',
'statusAuthfail': 'Authenticatie is mislukt',
'roomSubject' : 'Onderwerp:',
'messageSubmit': 'Verstuur',
'labelUsername': 'Gebruikersnaam:',
'labelPassword': 'Wachtwoord:',
'loginSubmit' : 'Inloggen',
'loginInvalid' : 'JID is onjuist',
'reason' : 'Reden:',
'subject' : 'Onderwerp:',
'reasonWas' : 'De reden was: %s.',
'kickActionLabel' : 'Verwijderen',
'youHaveBeenKickedBy' : 'Je bent verwijderd van %1$s door %2$s',
'youHaveBeenKicked' : 'Je bent verwijderd van %s',
'banActionLabel' : 'Blokkeren',
'youHaveBeenBannedBy' : 'Je bent geblokkeerd van %1$s door %2$s',
'youHaveBeenBanned' : 'Je bent geblokkeerd van %s',
'privateActionLabel' : 'Prive gesprek',
'ignoreActionLabel' : 'Negeren',
'unignoreActionLabel' : 'Niet negeren',
'setSubjectActionLabel': 'Onderwerp wijzigen',
'administratorMessageSubject' : 'Beheerder',
'userJoinedRoom' : '%s komt de chat binnen.',
'userLeftRoom' : '%s heeft de chat verlaten.',
'userHasBeenKickedFromRoom': '%s is verwijderd.',
'userHasBeenBannedFromRoom': '%s is geblokkeerd.',
'presenceUnknownWarningSubject': 'Mededeling:',
'presenceUnknownWarning' : 'Deze gebruiker is waarschijnlijk offline, we kunnen zijn/haar aanwezigheid niet vaststellen.',
'dateFormat': 'dd.mm.yyyy',
'timeFormat': 'HH:MM:ss',
'tooltipRole' : 'Moderator',
'tooltipIgnored' : 'Je negeert deze gebruiker',
'tooltipEmoticons' : 'Emotie-iconen',
'tooltipSound' : 'Speel een geluid af bij nieuwe privé berichten.',
'tooltipAutoscroll' : 'Automatisch scrollen',
'tooltipStatusmessage' : 'Statusberichten weergeven',
'tooltipAdministration' : 'Instellingen',
'tooltipUsercount' : 'Gebruikers',
'enterRoomPassword' : 'De Chatroom "%s" is met een wachtwoord beveiligd.',
'enterRoomPasswordSubmit' : 'Ga naar Chatroom',
'passwordEnteredInvalid' : 'Het wachtwoord voor de Chatroom "%s" is onjuist.',
'nicknameConflict': 'De gebruikersnaam is reeds in gebruik. Probeer a.u.b. een andere gebruikersnaam.',
'errorMembersOnly': 'Je kunt niet deelnemen aan de Chatroom "%s": Je hebt onvoldoende rechten.',
'errorMaxOccupantsReached': 'Je kunt niet deelnemen aan de Chatroom "%s": Het maximum aantal gebruikers is bereikt.',
'antiSpamMessage' : 'Het is niet toegestaan om veel berichten naar de server te versturen. Je bent voor een korte periode geblokkeerd.'
},
'es': {
'status': 'Estado: %s',
'statusConnecting': 'Conectando...',
'statusConnected' : 'Conectado',
'statusDisconnecting': 'Desconectando...',
'statusDisconnected' : 'Desconectado',
'statusAuthfail': 'Falló la autenticación',
'roomSubject' : 'Asunto:',
'messageSubmit': 'Enviar',
'labelUsername': 'Usuario:',
'labelPassword': 'Clave:',
'loginSubmit' : 'Entrar',
'loginInvalid' : 'JID no válido',
'reason' : 'Razón:',
'subject' : 'Asunto:',
'reasonWas' : 'La razón fue: %s.',
'kickActionLabel' : 'Expulsar',
'youHaveBeenKickedBy' : 'Has sido expulsado de %1$s por %2$s',
'youHaveBeenKicked' : 'Has sido expulsado de %s',
'banActionLabel' : 'Prohibir',
'youHaveBeenBannedBy' : 'Has sido expulsado permanentemente de %1$s por %2$s',
'youHaveBeenBanned' : 'Has sido expulsado permanentemente de %s',
'privateActionLabel' : 'Chat privado',
'ignoreActionLabel' : 'Ignorar',
'unignoreActionLabel' : 'No ignorar',
'setSubjectActionLabel': 'Cambiar asunto',
'administratorMessageSubject' : 'Administrador',
'userJoinedRoom' : '%s se ha unido a la sala.',
'userLeftRoom' : '%s ha dejado la sala.',
'userHasBeenKickedFromRoom': '%s ha sido expulsado de la sala.',
'userHasBeenBannedFromRoom': '%s ha sido expulsado permanentemente de la sala.',
'presenceUnknownWarningSubject': 'Atención:',
'presenceUnknownWarning' : 'Éste usuario podría estar desconectado..',
'dateFormat': 'dd.mm.yyyy',
'timeFormat': 'HH:MM:ss',
'tooltipRole' : 'Moderador',
'tooltipIgnored' : 'Ignoras a éste usuario',
'tooltipEmoticons' : 'Emoticonos',
'tooltipSound' : 'Reproducir un sonido para nuevos mensajes privados',
'tooltipAutoscroll' : 'Desplazamiento automático',
'tooltipStatusmessage' : 'Mostrar mensajes de estado',
'tooltipAdministration' : 'Administración de la sala',
'tooltipUsercount' : 'Usuarios en la sala',
'enterRoomPassword' : 'La sala "%s" está protegida mediante contraseña.',
'enterRoomPasswordSubmit' : 'Unirse a la sala',
'passwordEnteredInvalid' : 'Contraseña incorrecta para la sala "%s".',
'nicknameConflict': 'El nombre de usuario ya está siendo utilizado. Por favor elija otro.',
'errorMembersOnly': 'No se puede unir a la sala "%s": no tiene privilegios suficientes.',
'errorMaxOccupantsReached': 'No se puede unir a la sala "%s": demasiados participantes.',
'antiSpamMessage' : 'Por favor, no hagas spam. Has sido bloqueado temporalmente.'
},
'cn': {
'status': '状态: %s',
'statusConnecting': '连接中...',
'statusConnected': '已连接',
'statusDisconnecting': '断开连接中...',
'statusDisconnected': '已断开连接',
'statusAuthfail': '认证失败',
'roomSubject': '主题:',
'messageSubmit': '发送',
'labelUsername': '用户名:',
'labelPassword': '密码:',
'loginSubmit': '登录',
'loginInvalid': '用户名不合法',
'reason': '原因:',
'subject': '主题:',
'reasonWas': '原因是: %s.',
'kickActionLabel': '踢除',
'youHaveBeenKickedBy': '你在 %1$s 被管理者 %2$s 请出房间',
'banActionLabel': '禁言',
'youHaveBeenBannedBy': '你在 %1$s 被管理者 %2$s 禁言',
'privateActionLabel': '单独对话',
'ignoreActionLabel': '忽略',
'unignoreActionLabel': '不忽略',
'setSubjectActionLabel': '变更主题',
'administratorMessageSubject': '管理员',
'userJoinedRoom': '%s 加入房间',
'userLeftRoom': '%s 离开房间',
'userHasBeenKickedFromRoom': '%s 被请出这个房间',
'userHasBeenBannedFromRoom': '%s 被管理者禁言',
'presenceUnknownWarningSubject': '注意:',
'presenceUnknownWarning': '这个会员可能已经下线,不能追踪到他的连接信息',
'dateFormat': 'dd.mm.yyyy',
'timeFormat': 'HH:MM:ss',
'tooltipRole': '管理',
'tooltipIgnored': '你忽略了这个会员',
'tooltipEmoticons': '表情',
'tooltipSound': '新消息发音',
'tooltipAutoscroll': '滚动条',
'tooltipStatusmessage': '禁用状态消息',
'tooltipAdministration': '房间管理',
'tooltipUsercount': '房间占有者',
'enterRoomPassword': '登录房间 "%s" 需要密码.',
'enterRoomPasswordSubmit': '加入房间',
'passwordEnteredInvalid': '登录房间 "%s" 的密码不正确',
'nicknameConflict': '用户名已经存在,请另选一个',
'errorMembersOnly': '您的权限不够,不能登录房间 "%s" ',
'errorMaxOccupantsReached': '房间 "%s" 的人数已达上限,您不能登录',
'antiSpamMessage': '因为您在短时间内发送过多的消息 服务器要阻止您一小段时间。'
},
'ja' : {
'status' : 'ステータス: %s',
'statusConnecting' : '接続中…',
'statusConnected' : '接続されました',
'statusDisconnecting' : 'ディスコネクト中…',
'statusDisconnected' : 'ディスコネクトされました',
'statusAuthfail' : '認証に失敗しました',
'roomSubject' : 'トピック:',
'messageSubmit' : '送信',
'labelUsername' : 'ユーザーネーム:',
'labelPassword' : 'パスワード:',
'loginSubmit' : 'ログイン',
'loginInvalid' : 'ユーザーネームが正しくありません',
'reason' : '理由:',
'subject' : 'トピック:',
'reasonWas' : '理由: %s。',
'kickActionLabel' : 'キック',
'youHaveBeenKickedBy' : 'あなたは%2$sにより%1$sからキックされました。',
'youHaveBeenKicked' : 'あなたは%sからキックされました。',
'banActionLabel' : 'アカウントバン',
'youHaveBeenBannedBy' : 'あなたは%2$sにより%1$sからアカウントバンされました。',
'youHaveBeenBanned' : 'あなたは%sからアカウントバンされました。',
'privateActionLabel' : 'プライベートメッセージ',
'ignoreActionLabel' : '無視する',
'unignoreActionLabel' : '無視をやめる',
'setSubjectActionLabel' : 'トピックを変える',
'administratorMessageSubject' : '管理者',
'userJoinedRoom' : '%sは入室しました。',
'userLeftRoom' : '%sは退室しました。',
'userHasBeenKickedFromRoom' : '%sは部屋からキックされました。',
'userHasBeenBannedFromRoom' : '%sは部屋からアカウントバンされました。',
'presenceUnknownWarningSubject' : '忠告:',
'presenceUnknownWarning' : 'このユーザーのステータスは不明です。',
'dateFormat' : 'dd.mm.yyyy',
'timeFormat' : 'HH:MM:ss',
'tooltipRole' : 'モデレーター',
'tooltipIgnored' : 'このユーザーを無視設定にしている',
'tooltipEmoticons' : '絵文字',
'tooltipSound' : '新しいメッセージが届くたびに音を鳴らす',
'tooltipAutoscroll' : 'オートスクロール',
'tooltipStatusmessage' : 'ステータスメッセージを表示',
'tooltipAdministration' : '部屋の管理',
'tooltipUsercount' : 'この部屋の参加者の数',
'enterRoomPassword' : '"%s"の部屋に入るにはパスワードが必要です。',
'enterRoomPasswordSubmit' : '部屋に入る',
'passwordEnteredInvalid' : '"%s"のパスワードと異なるパスワードを入力しました。',
'nicknameConflict' : 'このユーザーネームはすでに利用されているため、別のユーザーネームを選んでください。',
'errorMembersOnly' : '"%s"の部屋に入ることができません: 利用権限を満たしていません。',
'errorMaxOccupantsReached' : '"%s"の部屋に入ることができません: 参加者の数はすでに上限に達しました。',
'antiSpamMessage' : 'スパムなどの行為はやめてください。あなたは一時的にブロックされました。'
}
};
\ No newline at end of file
...@@ -23,6 +23,26 @@ ...@@ -23,6 +23,26 @@
<link rel="stylesheet" href="/vendor/stylesheets/smoothness/jquery-ui-1.9.1.custom.min.css"/> <link rel="stylesheet" href="/vendor/stylesheets/smoothness/jquery-ui-1.9.1.custom.min.css"/>
<link rel="canonical" href="http://my-card.in/rooms">
<!-- Google Analytics -->
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-29582484-1']);
_gaq.push(['_trackPageview']);
var pluginUrl = '//www.google-analytics.com/plugins/ga/inpage_linkid.js';
_gaq.push(['_require', 'inpage_linkid', pluginUrl]);
(function () {
var ga = document.createElement('script');
ga.type = 'text/javascript';
ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(ga, s);
})();
</script>
<script id="room_template" type="text/x-jquery-tmpl"> <script id="room_template" type="text/x-jquery-tmpl">
<div class="room" id="room_${id}"> <div class="room" id="room_${id}">
<span class="room_name">${name}</span> <span class="room_name">${name}</span>
...@@ -67,8 +87,8 @@ ...@@ -67,8 +87,8 @@
<a class="login" href="">登录</a> <a class="login" href="">登录</a>
</div> </div>
</div> </div>
<div class="card_center"> <div class="card_center" style="height:200px;overflow: hidden;">
<div id="rooms"></div> <div id="rooms" ></div>
<div class="online_list"> <div class="online_list">
<div class="online_title">在线用户</div> <div class="online_title">在线用户</div>
<ul> <ul>
...@@ -147,15 +167,15 @@ ...@@ -147,15 +167,15 @@
<hr/> <hr/>
<label for="rule">卡片允许</label> <label for="rule">卡片允许</label>
<select id="rule" name="rule"> <select id="rule" name="rule">
<option value ="0" selected="selected">OCG</option> <option value="0" selected="selected">OCG</option>
<option value ="1">TCG</option> <option value="1">TCG</option>
<option value="2">OCG&TCG</option> <option value="2">OCG&TCG</option>
<option value="3">专有卡禁止</option> <option value="3">专有卡禁止</option>
</select><br /> </select><br/>
<label for="mode">决斗模式</label> <label for="mode">决斗模式</label>
<select id="mode" name="mode"> <select id="mode" name="mode">
<option value ="0">单局模式</option> <option value="0">单局模式</option>
<option value ="1" selected="selected">比赛模式</option> <option value="1" selected="selected">比赛模式</option>
<option value="2">TAG</option> <option value="2">TAG</option>
</select> </select>
<hr/> <hr/>
...@@ -189,6 +209,7 @@ ...@@ -189,6 +209,7 @@
<link rel="stylesheet" type="text/css" href="/vendor/stylesheets/candy/default.css"/> <link rel="stylesheet" type="text/css" href="/vendor/stylesheets/candy/default.css"/>
<script type="text/javascript" src="/vendor/javascripts/candy.libs.min.js"></script> <script type="text/javascript" src="/vendor/javascripts/candy.libs.min.js"></script>
<script type="text/javascript" src="/vendor/javascripts/candy.min.js"></script> <script type="text/javascript" src="/vendor/javascripts/candy.min.js"></script>
<script type="text/javascript" src="/vendor/javascripts/candy.fix.js"></script>
<script src="/assets/javascripts/rooms.js"></script> <script src="/assets/javascripts/rooms.js"></script>
......
/** Function: getPosTopAccordingToWindowBounds
* Fetches the window height and element height
* and checks if specified position + element height is bigger
* than the window height.
*
* If this evaluates to true, the position gets substracted by the element height.
*
* Parameters:
* (jQuery.Element) elem - Element to position
* (Integer) pos - Position top
*
* Returns:
* Object containing `px` (calculated position in pixel) and `alignment` (alignment of the element in relation to pos, either 'top' or 'bottom')
*/
Candy.Util.getPosTopAccordingToWindowBounds = function(elem, pos) {
var windowHeight = $(document).height(),
elemHeight = elem.outerHeight(),
marginDiff = elemHeight - elem.outerHeight(true),
backgroundPositionAlignment = 'top';
pos -= relative = $('#candy').offset().top
if (pos + elemHeight >= windowHeight - relative) {
pos -= elemHeight - marginDiff;
backgroundPositionAlignment = 'bottom';
}
return { px: pos, backgroundPositionAlignment: backgroundPositionAlignment };
};
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment