Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/candy-chat/candy.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPatrick Stadler <patrick.stadler@gmail.com>2013-12-15 00:09:37 +0400
committerPatrick Stadler <patrick.stadler@gmail.com>2013-12-15 00:09:37 +0400
commit527545b60aab76190a65c702353a8775c71e9c3b (patch)
tree57ca3c6f91dde893f4bec5ae8c92214053e85064
parent41a6c63928c8ed8fce1377f1ff42ce9e3293c0aa (diff)
parent166ff5e423eae32df2e34b6737c04fef8b7222c4 (diff)
Merge branch 'dev'v1.5.0
-rw-r--r--.gitignore3
-rw-r--r--.ndproj/Languages.txt113
-rw-r--r--.ndproj/Topics.txt100
-rw-r--r--CREDITS.md1
-rw-r--r--LICENSE1
-rw-r--r--Makefile6
-rw-r--r--README.md2
-rw-r--r--candy.bundle.js1607
-rw-r--r--candy.min.js2
-rw-r--r--libs/libs.bundle.js2215
-rw-r--r--libs/libs.min.js2
m---------libs/strophejs0
m---------libs/strophejs-plugins0
-rw-r--r--res/default.css232
-rw-r--r--res/img/context-arrows.gifbin91 -> 0 bytes
-rw-r--r--res/img/modal-bg.pngbin109 -> 0 bytes
-rw-r--r--res/img/modal-spinner.gifbin723 -> 723 bytes
-rw-r--r--res/img/tab-transitions.pngbin490 -> 151 bytes
-rw-r--r--res/img/tooltip-arrows.gifbin66 -> 66 bytes
-rw-r--r--src/candy.js8
-rw-r--r--src/core.js93
-rw-r--r--src/core/action.js33
-rw-r--r--src/core/chatRoom.js1
-rw-r--r--src/core/chatRoster.js1
-rw-r--r--src/core/chatUser.js1
-rw-r--r--src/core/event.js226
-rw-r--r--src/util.js119
-rw-r--r--src/view.js41
-rw-r--r--src/view/event.js4
-rw-r--r--src/view/observer.js218
-rw-r--r--src/view/pane.js455
-rw-r--r--src/view/template.js21
-rw-r--r--src/view/translation.js386
33 files changed, 4728 insertions, 1163 deletions
diff --git a/.gitignore b/.gitignore
index 8b0b314..777bf33 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,5 +2,6 @@ docs
example/.htaccess
.DS_Store
._*
-.ndproj
.idea
+.ndproj/Data
+.ndproj/Menu.txt
diff --git a/.ndproj/Languages.txt b/.ndproj/Languages.txt
new file mode 100644
index 0000000..42b197c
--- /dev/null
+++ b/.ndproj/Languages.txt
@@ -0,0 +1,113 @@
+Format: 1.52
+
+# This is the Natural Docs languages file for this project. If you change
+# anything here, it will apply to THIS PROJECT ONLY. If you'd like to change
+# something for all your projects, edit the Languages.txt in Natural Docs'
+# Config directory instead.
+
+
+# You can prevent certain file extensions from being scanned like this:
+# Ignore Extensions: [extension] [extension] ...
+
+
+#-------------------------------------------------------------------------------
+# SYNTAX:
+#
+# Unlike other Natural Docs configuration files, in this file all comments
+# MUST be alone on a line. Some languages deal with the # character, so you
+# cannot put comments on the same line as content.
+#
+# Also, all lists are separated with spaces, not commas, again because some
+# languages may need to use them.
+#
+# Language: [name]
+# Alter Language: [name]
+# Defines a new language or alters an existing one. Its name can use any
+# characters. If any of the properties below have an add/replace form, you
+# must use that when using Alter Language.
+#
+# The language Shebang Script is special. It's entry is only used for
+# extensions, and files with those extensions have their shebang (#!) lines
+# read to determine the real language of the file. Extensionless files are
+# always treated this way.
+#
+# The language Text File is also special. It's treated as one big comment
+# so you can put Natural Docs content in them without special symbols. Also,
+# if you don't specify a package separator, ignored prefixes, or enum value
+# behavior, it will copy those settings from the language that is used most
+# in the source tree.
+#
+# Extensions: [extension] [extension] ...
+# [Add/Replace] Extensions: [extension] [extension] ...
+# Defines the file extensions of the language's source files. You can
+# redefine extensions found in the main languages file. You can use * to
+# mean any undefined extension.
+#
+# Shebang Strings: [string] [string] ...
+# [Add/Replace] Shebang Strings: [string] [string] ...
+# Defines a list of strings that can appear in the shebang (#!) line to
+# designate that it's part of the language. You can redefine strings found
+# in the main languages file.
+#
+# Ignore Prefixes in Index: [prefix] [prefix] ...
+# [Add/Replace] Ignored Prefixes in Index: [prefix] [prefix] ...
+#
+# Ignore [Topic Type] Prefixes in Index: [prefix] [prefix] ...
+# [Add/Replace] Ignored [Topic Type] Prefixes in Index: [prefix] [prefix] ...
+# Specifies prefixes that should be ignored when sorting symbols in an
+# index. Can be specified in general or for a specific topic type.
+#
+#------------------------------------------------------------------------------
+# For basic language support only:
+#
+# Line Comments: [symbol] [symbol] ...
+# Defines a space-separated list of symbols that are used for line comments,
+# if any.
+#
+# Block Comments: [opening sym] [closing sym] [opening sym] [closing sym] ...
+# Defines a space-separated list of symbol pairs that are used for block
+# comments, if any.
+#
+# Package Separator: [symbol]
+# Defines the default package separator symbol. The default is a dot.
+#
+# [Topic Type] Prototype Enders: [symbol] [symbol] ...
+# When defined, Natural Docs will attempt to get a prototype from the code
+# immediately following the topic type. It stops when it reaches one of
+# these symbols. Use \n for line breaks.
+#
+# Line Extender: [symbol]
+# Defines the symbol that allows a prototype to span multiple lines if
+# normally a line break would end it.
+#
+# Enum Values: [global|under type|under parent]
+# Defines how enum values are referenced. The default is global.
+# global - Values are always global, referenced as 'value'.
+# under type - Values are under the enum type, referenced as
+# 'package.enum.value'.
+# under parent - Values are under the enum's parent, referenced as
+# 'package.value'.
+#
+# Perl Package: [perl package]
+# Specifies the Perl package used to fine-tune the language behavior in ways
+# too complex to do in this file.
+#
+#------------------------------------------------------------------------------
+# For full language support only:
+#
+# Full Language Support: [perl package]
+# Specifies the Perl package that has the parsing routines necessary for full
+# language support.
+#
+#-------------------------------------------------------------------------------
+
+# The following languages are defined in the main file, if you'd like to alter
+# them:
+#
+# Text File, Shebang Script, C/C++, C#, Java, JavaScript, Perl, Python,
+# PHP, SQL, Visual Basic, Pascal, Assembly, Ada, Tcl, Ruby, Makefile,
+# ActionScript, ColdFusion, R, Fortran
+
+# If you add a language that you think would be useful to other developers
+# and should be included in Natural Docs by default, please e-mail it to
+# languages [at] naturaldocs [dot] org.
diff --git a/.ndproj/Topics.txt b/.ndproj/Topics.txt
new file mode 100644
index 0000000..f6d3851
--- /dev/null
+++ b/.ndproj/Topics.txt
@@ -0,0 +1,100 @@
+Format: 1.52
+
+# This is the Natural Docs topics file for this project. If you change anything
+# here, it will apply to THIS PROJECT ONLY. If you'd like to change something
+# for all your projects, edit the Topics.txt in Natural Docs' Config directory
+# instead.
+
+
+# If you'd like to prevent keywords from being recognized by Natural Docs, you
+# can do it like this:
+# Ignore Keywords: [keyword], [keyword], ...
+#
+# Or you can use the list syntax like how they are defined:
+# Ignore Keywords:
+# [keyword]
+# [keyword], [plural keyword]
+# ...
+
+
+#-------------------------------------------------------------------------------
+# SYNTAX:
+#
+# Topic Type: [name]
+# Alter Topic Type: [name]
+# Creates a new topic type or alters one from the main file. Each type gets
+# its own index and behavior settings. Its name can have letters, numbers,
+# spaces, and these charaters: - / . '
+#
+# Plural: [name]
+# Sets the plural name of the topic type, if different.
+#
+# Keywords:
+# [keyword]
+# [keyword], [plural keyword]
+# ...
+# Defines or adds to the list of keywords for the topic type. They may only
+# contain letters, numbers, and spaces and are not case sensitive. Plural
+# keywords are used for list topics. You can redefine keywords found in the
+# main topics file.
+#
+# Index: [yes|no]
+# Whether the topics get their own index. Defaults to yes. Everything is
+# included in the general index regardless of this setting.
+#
+# Scope: [normal|start|end|always global]
+# How the topics affects scope. Defaults to normal.
+# normal - Topics stay within the current scope.
+# start - Topics start a new scope for all the topics beneath it,
+# like class topics.
+# end - Topics reset the scope back to global for all the topics
+# beneath it.
+# always global - Topics are defined as global, but do not change the scope
+# for any other topics.
+#
+# Class Hierarchy: [yes|no]
+# Whether the topics are part of the class hierarchy. Defaults to no.
+#
+# Page Title If First: [yes|no]
+# Whether the topic's title becomes the page title if it's the first one in
+# a file. Defaults to no.
+#
+# Break Lists: [yes|no]
+# Whether list topics should be broken into individual topics in the output.
+# Defaults to no.
+#
+# Can Group With: [type], [type], ...
+# Defines a list of topic types that this one can possibly be grouped with.
+# Defaults to none.
+#-------------------------------------------------------------------------------
+
+# The following topics are defined in the main file, if you'd like to alter
+# their behavior or add keywords:
+#
+# Generic, Class, Interface, Section, File, Group, Function, Variable,
+# Property, Type, Constant, Enumeration, Event, Delegate, Macro,
+# Database, Database Table, Database View, Database Index, Database
+# Cursor, Database Trigger, Cookie, Build Target
+
+# If you add something that you think would be useful to other developers
+# and should be included in Natural Docs by default, please e-mail it to
+# topics [at] naturaldocs [dot] org.
+
+
+Topic Type: Private Member
+
+ Plural: Private Members
+ Keywords:
+ privatemember
+
+
+Topic Type: Private Function
+
+ Plural: Private Functions
+ Keywords:
+ privatefunction
+
+
+Alter Topic Type: Event
+
+ Scope: Always global
diff --git a/CREDITS.md b/CREDITS.md
index ef705e8..4728ec1 100644
--- a/CREDITS.md
+++ b/CREDITS.md
@@ -1,5 +1,6 @@
Credits
=======
+- [Special thanks to our contributors](https://github.com/candy-chat/candy/graphs/contributors)
- [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.
diff --git a/LICENSE b/LICENSE
index f8a5c64..0fdf441 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,5 @@
Copyright (c) 2011 Amiado Group AG
+Copyright (c) 2012 Patrick Stadler & Michael Weibel
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:
diff --git a/Makefile b/Makefile
index 0c74a52..d705273 100644
--- a/Makefile
+++ b/Makefile
@@ -22,7 +22,7 @@ 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_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)/strophejs-plugins/disco/strophe.disco.js $(LIBS_DIR)/strophejs-plugins/caps/strophe.caps.jsonly.js $(LIBS_DIR)/dateformat/dateFormat.js
CANDY_FILES_BUNDLE = $(CANDY_FILES:.js=.bundle)
CANDY_LIBS_FILES_BUNDLE = $(CANDY_LIBS_FILES:.js=.libs-bundle)
@@ -71,10 +71,8 @@ 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)
+ @@$(NATURALDOCS_DIR)/NaturalDocs -r -i ./src -o html $(DOC_DIR) -p $(NDPROJ_DIR)
@@echo "Documentation built."
@@echo
else
diff --git a/README.md b/README.md
index 6b34688..ee31a9e 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
Candy — a JavaScript-based multi-user chat client
==================================================
-Visit the official project page: http://candy-chat.github.com/candy
+Visit the official project page: http://candy-chat.github.io/candy
Features
--------
diff --git a/candy.bundle.js b/candy.bundle.js
index 214e269..3cd20f3 100644
--- a/candy.bundle.js
+++ b/candy.bundle.js
@@ -7,6 +7,7 @@
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
+ * (c) 2012 Patrick Stadler & Michael Weibel. All rights reserved.
*/
/*jslint regexp: true, browser: true, confusion: true, sloppy: true, white: true, nomen: true, plusplus: true, maxerr: 50, indent: 4 */
@@ -29,7 +30,7 @@ var Candy = (function(self, $) {
*/
self.about = {
name: 'Candy',
- version: '1.0.9'
+ version: '1.5.0'
};
/** Function: init
@@ -44,7 +45,10 @@ var Candy = (function(self, $) {
* (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);
+ if (!options.viewClass) {
+ options.viewClass = self.View;
+ }
+ options.viewClass.init($('#candy'), options.view);
self.Core.init(service, options.core);
};
@@ -59,6 +63,7 @@ var Candy = (function(self, $) {
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
+ * (c) 2012 Patrick Stadler & Michael Weibel. All rights reserved.
*/
/** Class: Candy.Core
@@ -90,6 +95,10 @@ Candy.Core = (function(self, Strophe, $) {
* Set in <Candy.Core.connect> when jidOrHost doesn't contain a @-char.
*/
_anonymousConnection = false,
+ /** PrivateVariable: _status
+ * Current Strophe connection state
+ */
+ _status,
/** PrivateVariable: _options
* Options:
* (Boolean) debug - Debug (Default: false)
@@ -101,7 +110,12 @@ Candy.Core = (function(self, Strophe, $) {
* You may want to define an array of rooms to autojoin: `['room1@conference.host.tld', 'room2...]` (ejabberd, Openfire, ...)
*/
autojoin: true,
- debug: false
+ debug: false,
+ disableWindowUnload: false,
+ /** Integer: presencePriority
+ * Default priority for presence messages in order to receive messages across different resources
+ */
+ presencePriority: 1
},
/** PrivateFunction: _addNamespace
@@ -125,17 +139,10 @@ Candy.Core = (function(self, Strophe, $) {
_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');
+ _getEscapedJidFromJid = function(jid) {
+ var node = Strophe.getNodeFromJid(jid),
+ domain = Strophe.getDomainFromJid(jid);
+ return node ? Strophe.escapeNode(node) + '@' + domain : domain;
};
/** Function: init
@@ -157,9 +164,7 @@ Candy.Core = (function(self, Strophe, $) {
if(typeof window.console !== undefined && typeof window.console.log !== undefined) {
console.log(str);
}
- } catch(e) {
- //console.error(e);
- }
+ } catch(e) {}
};
self.log('[Init] Debugging enabled');
}
@@ -172,7 +177,9 @@ Candy.Core = (function(self, Strophe, $) {
// 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;
+ if (!_options.disableWindowUnload) {
+ window.onbeforeunload = self.onWindowUnload;
+ }
// Prevent Firefox from aborting AJAX requests when pressing ESC
if($.browser.mozilla) {
@@ -184,6 +191,25 @@ Candy.Core = (function(self, Strophe, $) {
}
};
+ /** Function: registerEventHandlers
+ * Adds listening handlers to the connection.
+ *
+ * Use with caution from outside of Candy.
+ */
+ self.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', 'result');
+ self.addHandler(self.Event.Jabber.PrivacyList, Strophe.NS.PRIVACY, 'iq', 'result');
+ self.addHandler(self.Event.Jabber.PrivacyListError, Strophe.NS.PRIVACY, 'iq', 'error');
+
+ self.addHandler(_connection.disco._onDiscoInfo.bind(_connection.disco), Strophe.NS.DISCO_INFO, 'iq', 'get');
+ self.addHandler(_connection.disco._onDiscoItems.bind(_connection.disco), Strophe.NS.DISCO_ITEMS, 'iq', 'get');
+ self.addHandler(_connection.caps._delegateCapabilities.bind(_connection.caps), Strophe.NS.CAPS);
+ };
+
/** Function: connect
* Connect to the jabber host.
*
@@ -206,14 +232,18 @@ Candy.Core = (function(self, Strophe, $) {
self.connect = function(jidOrHost, password, nick) {
// Reset before every connection attempt to make sure reconnections work after authfail, alltabsclosed, ...
_connection.reset();
- _registerEventHandlers();
+ self.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));
+ if (nick) {
+ _user = new self.ChatUser(jidOrHost, nick);
+ } else {
+ _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);
@@ -225,12 +255,6 @@ Candy.Core = (function(self, Strophe, $) {
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
@@ -244,7 +268,7 @@ Candy.Core = (function(self, Strophe, $) {
*/
self.attach = function(jid, sid, rid) {
_user = new self.ChatUser(jid, Strophe.getNodeFromJid(jid));
- _registerEventHandlers();
+ self.registerEventHandlers();
_connection.attach(jid, sid, rid, Candy.Core.Event.Strophe.Connect);
};
@@ -319,6 +343,29 @@ Candy.Core = (function(self, Strophe, $) {
return _rooms;
};
+ /** Function: getStropheStatus
+ * Get the status set by Strophe.
+ *
+ * Returns:
+ * (Strophe.Status.*) - one of Strophe's statuses
+ */
+ self.getStropheStatus = function() {
+ return _status;
+ };
+
+ /** Function: setStropheStatus
+ * Set the strophe status
+ *
+ * Called by:
+ * Candy.Core.Event.Strophe.Connect
+ *
+ * Parameters:
+ * (Strophe.Status.*) status - Strophe's status
+ */
+ self.setStropheStatus = function(status) {
+ _status = status;
+ };
+
/** Function: isAnonymousConnection
* Returns true if <Candy.Core.connect> was first called with a domain instead of a jid as the first param.
*
@@ -402,6 +449,7 @@ Candy.Core = (function(self, Strophe, $) {
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
+ * (c) 2012 Patrick Stadler & Michael Weibel. All rights reserved.
*/
/** Class: Candy.View
@@ -451,11 +499,14 @@ Candy.View = (function(self, $) {
* 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);
+ $(Candy).on('candy:core.chat.connection', self.Observer.Chat.Connection);
+ $(Candy).on('candy:core.chat.message', self.Observer.Chat.Message);
+ $(Candy).on('candy:core.login', self.Observer.Login);
+ $(Candy).on('candy:core.presence', self.Observer.Presence.update);
+ $(Candy).on('candy:core.presence.leave', self.Observer.Presence.update);
+ $(Candy).on('candy:core.presence.room', self.Observer.Presence.update);
+ $(Candy).on('candy:core.presence.error', self.Observer.PresenceError);
+ $(Candy).on('candy:core.message', self.Observer.Message);
},
/** PrivateFunction: _registerWindowHandlers
@@ -465,7 +516,7 @@ Candy.View = (function(self, $) {
*/
_registerWindowHandlers = function() {
// Cross-browser focus handling
- if($.browser.msie && !$.browser.version.match('^9')) {
+ 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);
@@ -473,24 +524,11 @@ Candy.View = (function(self, $) {
$(window).resize(Candy.View.Pane.Chat.fitTabs);
},
- /** PrivateFunction: _registerToolbarHandlers
- * Register toolbar handlers and disable sound if cookie says so.
+ /** PrivateFunction: _initToolbar
+ * Initialize toolbar.
*/
- _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();
- }
+ _initToolbar = function() {
+ self.Pane.Chat.Toolbar.init();
},
/** PrivateFunction: _delegateTooltips
@@ -510,7 +548,7 @@ Candy.View = (function(self, $) {
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/');
@@ -534,7 +572,7 @@ Candy.View = (function(self, $) {
// ... and let the elements dance.
_registerWindowHandlers();
- _registerToolbarHandlers();
+ _initToolbar();
_registerObservers();
_delegateTooltips();
};
@@ -570,6 +608,7 @@ Candy.View = (function(self, $) {
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
+ * (c) 2012 Patrick Stadler & Michael Weibel. All rights reserved.
*/
/** Class: Candy.Util
@@ -592,7 +631,7 @@ Candy.Util = (function(self, $){
self.jidToId = function(jid) {
return MD5.hexdigest(jid);
};
-
+
/** Function: escapeJid
* Escapes a jid (node & resource get escaped)
*
@@ -609,15 +648,15 @@ Candy.Util = (function(self, $){
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)
*
@@ -634,12 +673,12 @@ Candy.Util = (function(self, $){
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;
};
@@ -694,13 +733,13 @@ Candy.Util = (function(self, $){
* 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];
- }
- }
+ if(document.cookie) {
+ var regex = new RegExp(escape(name) + '=([^;]*)', 'gm'),
+ matches = regex.exec(document.cookie);
+ if(matches) {
+ return matches[1];
+ }
+ }
};
/** Function: deleteCookie
@@ -814,23 +853,25 @@ Candy.Util = (function(self, $){
* Date-Object
*/
self.iso8601toDate = function(date) {
- var timestamp = Date.parse(date), minutesOffset = 0;
- if(isNaN(timestamp)) {
+ var timestamp = Date.parse(date);
+ 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) {
+ var minutesOffset = 0;
if(struct[8] !== 'Z') {
minutesOffset = +struct[10] * 60 + (+struct[11]);
if(struct[9] === '+') {
minutesOffset = -minutesOffset;
}
}
+ minutesOffset -= new Date().getTimezoneOffset();
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);
+ }
+ return new Date(timestamp);
};
/** Function: isEmptyObject
@@ -875,7 +916,7 @@ Candy.Util = (function(self, $){
* Use setEmoticonPath() to change it
*/
_emoticonPath: '',
-
+
/** Function: setEmoticonPath
* Set emoticons location.
*
@@ -1018,6 +1059,19 @@ Candy.Util = (function(self, $){
return $('<div/>').text(text).html();
},
+ /** Function: nl2br
+ * replaces newline characters with a <br/> to make multi line messages look nice
+ *
+ * Parameters:
+ * (String) text - Text to process
+ *
+ * Returns:
+ * Processed text
+ */
+ nl2br: function(text) {
+ return text.replace(/\r\n|\r|\n/g, '<br />');
+ },
+
/** Function: all
* Does everything of the parser: escaping, linkifying and emotifying.
*
@@ -1032,6 +1086,7 @@ Candy.Util = (function(self, $){
text = this.escape(text);
text = this.linkify(text);
text = this.emotify(text);
+ text = this.nl2br(text);
}
return text;
}
@@ -1039,72 +1094,6 @@ Candy.Util = (function(self, $){
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: action.js
* Candy - Chats are not dead yet.
*
@@ -1114,6 +1103,7 @@ Candy.Util.Observable = (function(self) {
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
+ * (c) 2012 Patrick Stadler & Michael Weibel. All rights reserved.
*/
/** Class: Candy.Core.Action
@@ -1151,9 +1141,16 @@ Candy.Core.Action = (function(self, Strophe, $) {
*
* Parameters:
* (Object) attr - Optional attributes
+ * (Strophe.Builder) el - Optional element to include in presence stanza
*/
- Presence: function(attr) {
- Candy.Core.getConnection().send($pres(attr).tree());
+ Presence: function(attr, el) {
+ var pres = $pres(attr).c('priority').t(Candy.Core.getOptions().presencePriority.toString())
+ .up().c('c', Candy.Core.getConnection().caps.generateCapsAttrs())
+ .up();
+ if(el) {
+ pres.node.appendChild(el.node);
+ }
+ Candy.Core.getConnection().send(pres.tree());
},
/** Function: Services
@@ -1167,15 +1164,16 @@ Candy.Core.Action = (function(self, Strophe, $) {
* When Candy.Core.getOptions().autojoin is true, request autojoin bookmarks (OpenFire)
*
* Otherwise, if Candy.Core.getOptions().autojoin is an array, join each channel specified.
+ * Channel can be in jid:password format to pass room password if needed.
*/
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());
+ Candy.Core.getConnection().sendIQ($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());
+ self.Jabber.Room.Join.apply(null, this.valueOf().split(':',2));
});
}
},
@@ -1241,6 +1239,15 @@ Candy.Core.Action = (function(self, Strophe, $) {
Join: function(roomJid, password) {
self.Jabber.Room.Disco(roomJid);
Candy.Core.getConnection().muc.join(roomJid, Candy.Core.getUser().getNick(), null, null, password);
+ var conn = Candy.Core.getConnection(),
+ room_nick = conn.muc.test_append_nick(roomJid, Candy.Core.getUser().getNick()),
+ pres = $pres({ from: conn.jid, to: room_nick })
+ .c('x', {xmlns: Strophe.NS.MUC});
+ if (password != null) {
+ pres.c('password').t(password);
+ }
+ pres.up().c('c', conn.caps.generateCapsAttrs());
+ conn.send(pres.tree());
},
/** Function: Leave
@@ -1250,7 +1257,10 @@ Candy.Core.Action = (function(self, Strophe, $) {
* (String) roomJid - Room to leave
*/
Leave: function(roomJid) {
- Candy.Core.getConnection().muc.leave(roomJid, Candy.Core.getRoom(roomJid).getUser().getNick(), function() {});
+ var user = Candy.Core.getRoom(roomJid).getUser();
+ if (user) {
+ Candy.Core.getConnection().muc.leave(roomJid, user.getNick(), function() {});
+ }
},
/** Function: Disco
@@ -1280,7 +1290,7 @@ Candy.Core.Action = (function(self, Strophe, $) {
if(msg === '') {
return false;
}
- Candy.Core.getConnection().muc.message(Candy.Util.escapeJid(roomJid), undefined, msg, type);
+ Candy.Core.getConnection().muc.message(Candy.Util.escapeJid(roomJid), null, msg, null, type);
return true;
},
@@ -1377,6 +1387,7 @@ Candy.Core.Action = (function(self, Strophe, $) {
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
+ * (c) 2012 Patrick Stadler & Michael Weibel. All rights reserved.
*/
/** Class: Candy.Core.ChatRoom
@@ -1483,6 +1494,7 @@ Candy.Core.ChatRoom = function(roomJid) {
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
+ * (c) 2012 Patrick Stadler & Michael Weibel. All rights reserved.
*/
/** Class: Candy.Core.ChatRoster
@@ -1546,6 +1558,7 @@ Candy.Core.ChatRoster = function () {
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
+ * (c) 2012 Patrick Stadler & Michael Weibel. All rights reserved.
*/
/** Class: Candy.Core.ChatUser
@@ -1736,6 +1749,7 @@ Candy.Core.ChatUser = function(jid, nick, affiliation, role) {
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
+ * (c) 2012 Patrick Stadler & Michael Weibel. All rights reserved.
*/
/** Class: Candy.Core.Event
@@ -1745,33 +1759,25 @@ Candy.Core.ChatUser = function(jid, nick, affiliation, role) {
* (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
+Candy.Core.Event = (function(self, Strophe, $) {
+ /** Function: Login
+ * Notify view that the login window should be displayed
*
- * CHAT - Chat events
- * PRESENCE - Presence events
- * MESSAGE - Message events
- * LOGIN - Login event
+ * Parameters:
+ * (String) presetJid - Preset user JID
+ *
+ * Triggers:
+ * candy:core.login using {presetJid}
*/
- self.KEYS = {
- CHAT: 1,
- PRESENCE: 2,
- MESSAGE: 3,
- LOGIN: 4,
- PRESENCE_ERROR: 5
+ self.Login = function(presetJid) {
+ /** Event: candy:core.login
+ * Triggered when the login window should be displayed
+ *
+ * Parameters:
+ * (String) presetJid - Preset user JID
+ */
+ $(Candy).triggerHandler('candy:core.login', { presetJid: presetJid } );
};
/** Class: Candy.Core.Event.Strophe
@@ -1783,8 +1789,12 @@ Candy.Core.Event = (function(self, Strophe, $, observable) {
*
* Parameters:
* (Strophe.Status) status - Strophe statuses
+ *
+ * Triggers:
+ * candy:core.chat.connection using {status}
*/
Connect: function(status) {
+ Candy.Core.setStropheStatus(status);
switch(status) {
case Strophe.Status.CONNECTED:
Candy.Core.log('[Connection] Connected');
@@ -1826,21 +1836,16 @@ Candy.Core.Event = (function(self, Strophe, $, observable) {
Candy.Core.log('[Connection] What?!');
break;
}
-
- self.notifyObservers(self.KEYS.CHAT, { type: 'connection', status: status } );
+ /** Event: candy:core.chat.connection
+ * Connection status updates
+ *
+ * Parameters:
+ * (Strophe.Status) status - Strophe status
+ */
+ $(Candy).triggerHandler('candy:core.chat.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
*/
@@ -1866,6 +1871,9 @@ Candy.Core.Event = (function(self, Strophe, $, observable) {
* Parameters:
* (String) msg - Raw XML Message
*
+ * Triggers:
+ * candy:core.presence using {from, stanza}
+ *
* Returns:
* (Boolean) - true
*/
@@ -1878,6 +1886,15 @@ Candy.Core.Event = (function(self, Strophe, $, observable) {
} else {
self.Jabber.Room.Presence(msg);
}
+ } else {
+ /** Event: candy:core.presence
+ * Presence updates. Emitted only when not a muc presence.
+ *
+ * Parameters:
+ * (JID) from - From Jid
+ * (String) stanza - Stanza
+ */
+ $(Candy).triggerHandler('candy:core.presence', {'from': msg.attr('from'), 'stanza': msg});
}
return true;
},
@@ -1955,6 +1972,10 @@ Candy.Core.Event = (function(self, Strophe, $, observable) {
* Parameters:
* (String) msg - Raw XML Message
*
+ * Triggers:
+ * candy:core.chat.message.admin using {type, message}
+ * candy:core.chat.message.server {type, subject, message}
+ *
* Returns:
* (Boolean) - true
*/
@@ -1965,14 +1986,33 @@ Candy.Core.Event = (function(self, Strophe, $, observable) {
type = msg.attr('type'),
toJid = msg.attr('to');
// Room message
- if(fromJid !== Strophe.getDomainFromJid(fromJid) && (type === 'groupchat' || type === 'chat' || type === 'error')) {
+ if(fromJid !== Strophe.getDomainFromJid(fromJid) && (type === 'groupchat' || type === 'chat' || type === 'error' || type === 'normal')) {
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() });
+ /** Event: candy:core.chat.message.admin
+ * Admin message
+ *
+ * Parameters:
+ * (String) type - Type of the message [default: message]
+ * (String) message - Message text
+ */
+ $(Candy).triggerHandler('candy:core.chat.message.admin', { 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() });
+ /** Event: candy:core.chat.message.server
+ * Server message (e.g. subject)
+ *
+ * Parameters:
+ * (String) type - Message type [default: message]
+ * (String) subject - Subject text
+ * (String) message - Message text
+ */
+ $(Candy).triggerHandler('candy:core.chat.message.server', {
+ type: (type || 'message'),
+ subject: msg.children('subject').text(),
+ message: msg.children('body').text()
+ });
}
return true;
},
@@ -1987,6 +2027,9 @@ Candy.Core.Event = (function(self, Strophe, $, observable) {
* Parameters:
* (String) msg - Raw XML Message
*
+ * Triggers:
+ * candy:core.presence.leave using {roomJid, roomName, type, reason, actor, user}
+ *
* Returns:
* (Boolean) - true
*/
@@ -2021,7 +2064,27 @@ Candy.Core.Event = (function(self, Strophe, $, observable) {
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 } );
+ /** Event: candy:core.presence.leave
+ * When the local client leaves a room
+ *
+ * Also triggered when the local client gets kicked or banned from a room.
+ *
+ * Parameters:
+ * (String) roomJid - Room
+ * (String) roomName - Name of room
+ * (String) type - Presence type [kick, ban, leave]
+ * (String) reason - When type equals kick|ban, this is the reason the moderator has supplied.
+ * (String) actor - When type equals kick|ban, this is the moderator which did the kick
+ * (Candy.Core.ChatUser) user - user which leaves the room
+ */
+ $(Candy).triggerHandler('candy:core.presence.leave', {
+ 'roomJid': roomJid,
+ 'roomName': roomName,
+ 'type': type,
+ 'reason': reason,
+ 'actor': actor,
+ 'user': user
+ });
return true;
},
@@ -2061,6 +2124,9 @@ Candy.Core.Event = (function(self, Strophe, $, observable) {
* Parameters:
* (Object) msg - jQuery object of XML message
*
+ * Triggers:
+ * candy:core.presence.room using {roomJid, roomName, user, action, currentUser}
+ *
* Returns:
* (Boolean) - true
*/
@@ -2110,16 +2176,35 @@ Candy.Core.Event = (function(self, Strophe, $, observable) {
roster.remove(from);
}
- self.notifyObservers(self.KEYS.PRESENCE, {'roomJid': roomJid, 'roomName': room.getName(), 'user': user, 'action': action, 'currentUser': Candy.Core.getUser() } );
+ /** Event: candy:core.presence.room
+ * Room presence updates
+ *
+ * Parameters:
+ * (String) roomJid - Room JID
+ * (String) roomName - Room name
+ * (Candy.Core.ChatUser) user - User which does the presence update
+ * (String) action - Action [kick, ban, leave, join]
+ * (Candy.Core.ChatUser) currentUser - Current local user
+ */
+ $(Candy).triggerHandler('candy:core.presence.room', {
+ '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
*
+ * Triggers:
+ * candy:core.presence.error using {msg, type, roomJid, roomName}
+ *
* Returns:
* (Boolean) - true
*/
@@ -2129,11 +2214,25 @@ Candy.Core.Event = (function(self, Strophe, $, observable) {
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});
+
+ /** Event: candy:core.presence.error
+ * Triggered when a presence error happened
+ *
+ * Parameters:
+ * (Object) msg - jQuery object of XML message
+ * (String) type - Error type
+ * (String) roomJid - Room jid
+ * (String) roomName - Room name
+ */
+ $(Candy).triggerHandler('candy:core.presence.error', {
+ 'msg' : msg,
+ 'type': msg.children('error').children()[0].tagName.toLowerCase(),
+ 'roomJid': roomJid,
+ 'roomName': roomName
+ });
},
/** Function: Message
@@ -2143,6 +2242,9 @@ Candy.Core.Event = (function(self, Strophe, $, observable) {
* Parameters:
* (String) msg - jQuery object of XML message
*
+ * Triggers:
+ * candy:core.message using {roomJid, message, timestamp}
+ *
* Returns:
* (Boolean) - true
*/
@@ -2156,14 +2258,14 @@ Candy.Core.Event = (function(self, Strophe, $, observable) {
// Error messsage
} else if(msg.attr('type') === 'error') {
var error = msg.children('error');
- if(error.attr('code') === '500' && error.children('text').length > 0) {
+ if(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') {
+ if(msg.attr('type') === 'chat' || msg.attr('type') === 'normal') {
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.
@@ -2180,6 +2282,10 @@ Candy.Core.Event = (function(self, Strophe, $, observable) {
message = { name: resource, body: msg.children('body').text(), type: msg.attr('type') };
// Message from server (XEP-0045#registrar-statuscodes)
} else {
+ // we are not yet present in the room, let's just drop this message (issue #105)
+ if(!Candy.View.Pane.Chat.rooms[msg.attr('from')]) {
+ return true;
+ }
message = { name: '', body: msg.children('body').text(), type: 'info' };
}
}
@@ -2193,14 +2299,49 @@ Candy.Core.Event = (function(self, Strophe, $, observable) {
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 } );
+ /** Event: candy:core.message
+ * Triggers on various message events (subject changed, private chat message, multi-user chat message).
+ *
+ * The resulting message object can contain different key-value pairs as stated in the documentation
+ * of the parameters itself.
+ *
+ * The following lists explain those parameters:
+ *
+ * Message Object Parameters:
+ * (String) name - Room name
+ * (String) body - Message text
+ * (String) type - Message type ([normal, chat, groupchat])
+ * or 'info' which is used internally for displaying informational messages
+ * (Boolean) isNoConferenceRoomJid - 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 flag tells if this is the case.
+ *
+ * Parameters:
+ * (String) roomJid - Room jid
+ * (Object) message - Depending on what kind of message, the object consists of different key-value pairs:
+ * - Room Subject: {name, body, type}
+ * - Error message: {type = 'info', body}
+ * - Private chat message: {name, body, type, isNoConferenceRoomJid}
+ * - MUC msg from a user: {name, body, type}
+ * - MUC msg from server: {name = '', body, type = 'info'}
+ * (String) timestamp - Timestamp, only when it's an offline message
+ *
+ * TODO:
+ * Streamline those events sent and rename the parameters.
+ */
+ $(Candy).triggerHandler('candy:core.message', {
+ roomJid: roomJid,
+ message: message,
+ timestamp: timestamp
+ });
return true;
}
}
};
return self;
-}(Candy.Core.Event || {}, Strophe, jQuery, Candy.Util.Observable));
+}(Candy.Core.Event || {}, Strophe, jQuery));
/** File: event.js
* Candy - Chats are not dead yet.
*
@@ -2210,11 +2351,15 @@ Candy.Core.Event = (function(self, Strophe, $, observable) {
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
+ * (c) 2012 Patrick Stadler & Michael Weibel
*/
/** Class: Candy.View.Event
* Empty hooks to capture events and inject custom code.
*
+ * Deprecated:
+ * Don't use this anymore. Bind on the triggered events on Candy.View.*
+ *
* Parameters:
* (Candy.View.Event) self - itself
* (jQuery) $ - jQuery
@@ -2404,6 +2549,7 @@ Candy.View.Event = (function(self, $) {
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
+ * (c) 2012 Patrick Stadler & Michael Weibel
*/
/** Class: Candy.View.Observer
@@ -2418,49 +2564,57 @@ Candy.View.Observer = (function(self, $) {
* Chat events
*/
self.Chat = {
- /** Function: update
+ /** Function: Connection
* The update method gets called whenever an event to which "Chat" is subscribed.
*
- * Currently listens for connection status updates & admin messages / motd
+ * Currently listens for connection status updates
*
* Parameters:
- * (Candy.Core.Event) obj - Candy core event object
- * (Object) args - {type, connection or message & subject}
+ * (jQuery.Event) event - jQuery Event object
+ * (Object) args - {status (Strophe.Status.*)}
*/
- 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;
+ Connection: function(event, args) {
+ 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.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.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.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;
- 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;
+ }
+ },
- default:
- Candy.View.Pane.Chat.Modal.show($.i18n._('status', args.status));
- break;
- }
- } else if(args.type === 'message') {
+ /** Function: Message
+ * Dispatches admin and info messages
+ *
+ * Parameters:
+ * (jQuery.Event) event - jQuery Event object
+ * (Object) args - {type (message/chat/groupchat), subject (if type = message), message}
+ */
+ Message: function(event, args) {
+ 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.
@@ -2477,13 +2631,13 @@ Candy.View.Observer = (function(self, $) {
* Every presence update gets dispatched from this method.
*
* Parameters:
- * (Candy.Core.Event) obj - Candy core event object
+ * (jQuery.Event) event - jQuery.Event object
* (Object) args - Arguments differ on each type
*
* Uses:
* - <notifyPrivateChats>
*/
- update: function(obj, args) {
+ update: function(event, args) {
// Client left
if(args.type === 'leave') {
var user = Candy.View.Pane.Room.getUser(args.roomJid);
@@ -2518,9 +2672,23 @@ Candy.View.Observer = (function(self, $) {
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 });
+
+ var evtData = { type: args.type, reason: args.reason, roomJid: args.roomJid, user: args.user };
+ Candy.View.Event.Room.onPresenceChange(evtData);
+
+ /** Event: candy:view.presence
+ * Presence update when kicked or banned
+ *
+ * Parameters:
+ * (String) type - Presence type [kick, ban]
+ * (String) reason - Reason for the kick|ban [optional]
+ * (String) roomJid - Room JID
+ * (Candy.Core.ChatUser) user - User which has been kicked or banned
+ */
+ $(Candy).triggerHandler('candy:view.presence', [evtData]);
+
// A user changed presence
- } else {
+ } else if(args.roomJid) {
// Initialize room if not yet existing
if(!Candy.View.Pane.Chat.rooms[args.roomJid]) {
Candy.View.Pane.Room.init(args.roomJid, args.roomName);
@@ -2532,6 +2700,8 @@ Candy.View.Observer = (function(self, $) {
Candy.View.Pane.Roster.update(args.user.getJid(), args.user, args.action, args.currentUser);
Candy.View.Pane.PrivateRoom.setStatus(args.user.getJid(), args.action);
}
+ } else {
+ // Unhandled type of presence
}
},
@@ -2539,7 +2709,7 @@ Candy.View.Observer = (function(self, $) {
* Notify private user chats if existing
*
* Parameters:
- * (Candy.Core.chatUser) user - User which has done the event
+ * (Candy.Core.ChatUser) user - User which has done the event
* (String) type - Event type (leave, join, kick/ban)
*/
notifyPrivateChats: function(user, type) {
@@ -2553,84 +2723,69 @@ Candy.View.Observer = (function(self, $) {
}
}
};
-
+
/** Class: Candy.View.Observer.PresenceError
- * Presence error events
+ * Presence errors get handled in this method
+ *
+ * Parameters:
+ * (jQuery.Event) event - jQuery.Event object
+ * (Object) args - {msg, type, roomJid, roomName}
*/
- 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;
- }
+ self.PresenceError = 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
+ * Messages received get dispatched from this method.
+ *
+ * Parameters:
+ * (jQuery.Event) event - jQuery Event object
+ * (Object) args - {message, roomJid}
*/
- 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);
+ self.Message = function(event, 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
+ * The login event gets dispatched to this method
+ *
+ * Parameters:
+ * (jQuery.Event) event - jQuery Event object
+ * (Object) args - {presetJid}
*/
- 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);
- }
+ self.Login = function(event, args) {
+ Candy.View.Pane.Chat.Modal.showLoginForm(null, args.presetJid);
};
return self;
@@ -2643,6 +2798,7 @@ Candy.View.Observer = (function(self, $) {
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
+ * (c) 2012 Patrick Stadler & Michael Weibel. All rights reserved.
*/
/** Class: Candy.View.Pane
@@ -2768,7 +2924,7 @@ Candy.View.Pane = (function(self, $) {
roomJid: roomJid,
roomId: roomId,
name: roomName || Strophe.getNodeFromJid(roomJid),
- privateUserChat: function() { return roomType === 'chat'; },
+ privateUserChat: function() {return roomType === 'chat';},
roomType: roomType
}),
tab = $(html).appendTo('#chat-tabs');
@@ -2920,33 +3076,25 @@ Candy.View.Pane = (function(self, $) {
tabsWidth = 0,
tabs = $('#chat-tabs').children();
tabs.each(function() {
- tabsWidth += $(this).css({ width: 'auto', overflow: 'visible' }).outerWidth(true);
+ 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' });
+ 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
+ *
+ * Triggers:
+ * candy:view.chat.admin-message using {subject, message}
*/
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?
@@ -2961,7 +3109,18 @@ Candy.View.Pane = (function(self, $) {
});
self.Room.scrollToBottom(Candy.View.getCurrent().roomJid);
- Candy.View.Event.Chat.onAdminMessage({'subject' : subject, 'message' : message});
+ var evtData = {'subject' : subject, 'message' : message};
+
+ // deprecated
+ Candy.View.Event.Chat.onAdminMessage(evtData);
+
+ /** Event: candy:view.chat.admin-message
+ * After admin message display
+ *
+ * Parameters:
+ * (String) presetJid - Preset user JID
+ */
+ $(Candy).triggerHandler('candy:view.chat.admin-message', evtData);
}
},
@@ -3004,6 +3163,30 @@ Candy.View.Pane = (function(self, $) {
* Chat toolbar for things like emoticons toolbar, room management etc.
*/
Toolbar: {
+ _supportsNativeAudio: false,
+
+ /** Function: init
+ * Register handler and enable or disable sound and status messages.
+ */
+ init: function() {
+ $('#emoticons-icon').click(function(e) {
+ self.Chat.Context.showEmoticonsMenu(e.currentTarget);
+ e.stopPropagation();
+ });
+ $('#chat-autoscroll-control').click(self.Chat.Toolbar.onAutoscrollControlClick);
+
+ var a = document.createElement('audio');
+ self.Chat.Toolbar._supportsNativeAudio = !!(a.canPlayType && a.canPlayType('audio/mpeg;').replace(/no/, ''));
+ $('#chat-sound-control').click(self.Chat.Toolbar.onSoundControlClick);
+ if(Candy.Util.cookieExists('candy-nosound')) {
+ $('#chat-sound-control').click();
+ }
+ $('#chat-statusmessage-control').click(self.Chat.Toolbar.onStatusMessageControlClick);
+ if(Candy.Util.cookieExists('candy-nostatusmessages')) {
+ $('#chat-statusmessage-control').click();
+ }
+ },
+
/** Function: show
* Show toolbar.
*/
@@ -3018,6 +3201,23 @@ Candy.View.Pane = (function(self, $) {
$('#chat-toolbar').hide();
},
+ /* Function: update
+ * Update toolbar for specific room
+ */
+ update: function(roomJid) {
+ var context = $('#chat-toolbar').find('.context'),
+ me = self.Room.getUser(roomJid);
+ if(!me || !me.isModerator()) {
+ context.hide();
+ } else {
+ context.show().click(function(e) {
+ self.Chat.Context.show(e.currentTarget, roomJid);
+ e.stopPropagation();
+ });
+ }
+ self.Chat.Toolbar.updateUsercount(self.Chat.rooms[roomJid].usercount);
+ },
+
/** Function: playSound
* Play sound (default method).
*/
@@ -3026,15 +3226,21 @@ Candy.View.Pane = (function(self, $) {
},
/** Function: onPlaySound
- * Sound play event handler.
+ * Sound play event handler. Uses native (HTML5) audio if supported
*
* 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', '');
+ try {
+ if(self.Chat.Toolbar._supportsNativeAudio) {
+ new Audio(Candy.View.getOptions().resources + 'notify.mp3').play();
+ } else {
+ var chatSoundPlayer = document.getElementById('chat-sound-player');
+ chatSoundPlayer.SetVariable('method:stop', '');
+ chatSoundPlayer.SetVariable('method:play', '');
+ }
+ } catch (e) {}
},
/** Function: onSoundControlClick
@@ -3145,7 +3351,7 @@ Candy.View.Pane = (function(self, $) {
*/
hide: function(callback) {
$('#chat-modal').fadeOut('fast', function() {
- $('#chat-modal-body').text('');
+ $('#chat-modal-body').text('');
$('#chat-modal-overlay').hide();
});
// restore initial esc handling
@@ -3216,7 +3422,7 @@ Candy.View.Pane = (function(self, $) {
displayUsername: Candy.Core.isAnonymousConnection() || !presetJid,
presetJid: presetJid ? presetJid : false
}));
- $('#login-form').children()[0].focus();
+ $('#login-form').children(':input:first').focus();
// register submit handler
$('#login-form').submit(function(event) {
@@ -3240,7 +3446,7 @@ Candy.View.Pane = (function(self, $) {
return false;
});
},
-
+
/** Function: showEnterPasswordForm
* Shows a form for entering room password
*
@@ -3257,18 +3463,18 @@ Candy.View.Pane = (function(self, $) {
_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
@@ -3283,7 +3489,7 @@ Candy.View.Pane = (function(self, $) {
_loginSubmit: $.i18n._('loginSubmit')
}));
$('#nickname').focus();
-
+
// register submit handler
$('#nickname-conflict-form').submit(function() {
var nickname = $('#nickname').val();
@@ -3295,7 +3501,7 @@ Candy.View.Pane = (function(self, $) {
return false;
});
},
-
+
/** Function: showError
* Show modal containing error message
*
@@ -3346,11 +3552,15 @@ Candy.View.Pane = (function(self, $) {
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');
+ tooltip
+ .css({'left': posLeft.px, 'top': posTop.px})
+ .removeClass('left-top left-bottom right-top right-bottom')
+ .addClass(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}); });
+ $('#tooltip').stop(false, true).fadeOut('fast', function() {$(this).css({'top': 0, 'left': 0});});
});
}
},
@@ -3375,18 +3585,18 @@ Candy.View.Pane = (function(self, $) {
/** Function: show
* Show context menu (positions it according to the window height/width)
*
+ * 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
+ *
* 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
+ * Triggers:
+ * candy:view.roster.after-context-menu using {roomJid, user, elements}
*/
show: function(elem, roomJid, user) {
elem = $(elem);
@@ -3431,24 +3641,68 @@ Candy.View.Pane = (function(self, $) {
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');
+ menu
+ .css({'left': posLeft.px, 'top': posTop.px})
+ .removeClass('left-top left-bottom right-top right-bottom')
+ .addClass(posLeft.backgroundPositionAlignment + '-' + posTop.backgroundPositionAlignment)
+ .fadeIn('fast');
+
+ var evtData = {'roomJid' : roomJid, 'user' : user, 'element': menu};
+
+ // deprecated
+ Candy.View.Event.Roster.afterContextMenu(evtData);
- Candy.View.Event.Roster.afterContextMenu({'roomJid' : roomJid, 'user' : user, 'element': menu });
+ /** Event: candy:view.roster.after-context-menu
+ * After context menu display
+ *
+ * Parameters:
+ * (String) roomJid - room where the context menu has been triggered
+ * (Candy.Core.ChatUser) user - User
+ * (jQuery.Element) element - Menu element
+ */
+ $(Candy).triggerHandler('candy:view.roster.after-context-menu', evtData);
return true;
}
},
/** Function: getMenuLinks
- * Extends <initialMenuLinks> with <Candy.View.Event.Roster.onContextMenu> links and returns those.
+ * Extends <initialMenuLinks> with menu links gathered from candy:view.roster.contextmenu
+ *
+ * Parameters:
+ * (String) roomJid - Room in which the menu will be displayed
+ * (Candy.Core.ChatUser) user - User
+ * (jQuery.Element) elem - Parent element of the context menu
+ *
+ * Triggers:
+ * candy:view.roster.context-menu using {roomJid, user, elem}
*
* 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;
+ var menulinks, extramenulinks, id;
+
+ var evtData = {'roomJid' : roomJid, 'user' : user, 'elem': elem};
+ // deprecated
+ extramenulinks = Candy.View.Event.Roster.onContextMenu(evtData);
+
+ evtData.menulinks = $.extend(this.initialMenuLinks(elem), extramenulinks);
+
+ /** Event: candy:view.roster.context-menu
+ * Modify existing menu links (add links)
+ *
+ * In order to modify the links you need to change the object passed with an additional
+ * key "menulinks" containing the menulink object.
+ *
+ * Parameters:
+ * (String) roomJid - Room on which the menu should be displayed
+ * (Candy.Core.ChatUser) user - User
+ * (jQuery.Element) elem - Parent element of the context menu
+ */
+ $(Candy).triggerHandler('candy:view.roster.context-menu', evtData);
+
+ menulinks = evtData.menulinks;
for(id in menulinks) {
if(menulinks.hasOwnProperty(id) && menulinks[id].requiredPermission !== undefined && !menulinks[id].requiredPermission(user, self.Room.getUser(roomJid), elem)) {
@@ -3596,8 +3850,11 @@ Candy.View.Pane = (function(self, $) {
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');
+ menu
+ .css({'left': posLeft.px, 'top': posTop.px})
+ .removeClass('left-top left-bottom right-top right-bottom')
+ .addClass(posLeft.backgroundPositionAlignment + '-' + posTop.backgroundPositionAlignment)
+ .fadeIn('fast');
return true;
}
@@ -3621,8 +3878,8 @@ Candy.View.Pane = (function(self, $) {
* - <Candy.View.Pane.Chat.addTab>
* - <getPane>
*
- * Calls:
- * - <Candy.View.Event.Room.onAdd>
+ * Triggers:
+ * candy:view.room.after-add using {roomJid, type, element}
*
* Returns:
* (String) - the room id of the element created.
@@ -3635,7 +3892,7 @@ Candy.View.Pane = (function(self, $) {
}
var roomId = Candy.Util.jidToId(roomJid);
- self.Chat.rooms[roomJid] = { id: roomId, usercount: 0, name: roomName, type: roomType, messageCount: 0, scrollPosition: -1 };
+ 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,
@@ -3655,7 +3912,20 @@ Candy.View.Pane = (function(self, $) {
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)});
+ var evtData = {'roomJid': roomJid, 'type': roomType, 'element': self.Room.getPane(roomJid)};
+
+ // deprecated
+ Candy.View.Event.Room.onAdd(evtData);
+
+ /** Event: candy:view.room.after-add
+ * After initialising a room
+ *
+ * Parameters:
+ * (String) roomJid - Room JID
+ * (String) type - Room Type
+ * (jQuery.Element) element - Room element
+ */
+ $(Candy).triggerHandler('candy:view.room.after-add', evtData);
return roomId;
},
@@ -3665,6 +3935,10 @@ Candy.View.Pane = (function(self, $) {
*
* Parameters:
* (String) roomJid - room jid to show
+ *
+ * Triggers:
+ * candy:view.room.after-show using {roomJid, element}
+ * candy:view.room.after-hide using {roomJid, element}
*/
show: function(roomJid) {
var roomId = self.Chat.rooms[roomJid].id;
@@ -3673,17 +3947,41 @@ Candy.View.Pane = (function(self, $) {
if(elem.attr('id') === ('chat-room-' + roomId)) {
elem.show();
Candy.View.getCurrent().roomJid = roomJid;
- self.Chat.updateToolbar(roomJid);
self.Chat.setActiveTab(roomJid);
+ self.Chat.Toolbar.update(roomJid);
self.Chat.clearUnreadMessages(roomJid);
self.Room.setFocusToForm(roomJid);
self.Room.scrollToBottom(roomJid);
- Candy.View.Event.Room.onShow({'roomJid': roomJid, 'element' : elem});
+ var evtData = {'roomJid': roomJid, 'element' : elem};
+
+ // deprecated
+ Candy.View.Event.Room.onShow(evtData);
+
+ /** Event: candy:view.room.after-show
+ * After showing a room
+ *
+ * Parameters:
+ * (String) roomJid - Room JID
+ * (jQuery.Element) element - Room element
+ */
+ $(Candy).triggerHandler('candy:view.room.after-show', evtData);
+
} else {
elem.hide();
- Candy.View.Event.Room.onHide({'roomJid': roomJid, 'element' : elem});
+ var evtData = {'roomJid': roomJid, 'element' : elem};
+ // deprecated
+ Candy.View.Event.Room.onHide(evtData);
+
+ /** Event: candy:view.room.after-hide
+ * After hiding a room
+ *
+ * Parameters:
+ * (String) roomJid - Room JID
+ * (jQuery.Element) element - Room element
+ */
+ $(Candy).triggerHandler('candy:view.room.after-hide', evtData);
}
});
},
@@ -3691,6 +3989,9 @@ Candy.View.Pane = (function(self, $) {
/** Function: setSubject
* Called when someone changes the subject in the channel
*
+ * Triggers:
+ * candy:view.room.after-subject-change using {roomJid, element, subject}
+ *
* Parameters:
* (String) roomJid - Room Jid
* (String) subject - The new subject
@@ -3705,13 +4006,30 @@ Candy.View.Pane = (function(self, $) {
self.Room.appendToMessagePane(roomJid, html);
self.Room.scrollToBottom(roomJid);
- Candy.View.Event.Room.onSubjectChange({'roomJid': roomJid, 'element' : self.Room.getPane(roomJid), 'subject' : subject});
+ var evtData = {'roomJid': roomJid, 'element' : self.Room.getPane(roomJid), 'subject' : subject};
+
+ // deprecated
+ Candy.View.Event.Room.onSubjectChange(evtData);
+
+ /** Event: candy:view.room.after-subject-change
+ * After changing the subject of a room
+ *
+ * Parameters:
+ * (String) roomJid - Room JID
+ * (jQuery.Element) element - Room element
+ * (String) subject - New subject
+ */
+ $(Candy).triggerHandler('candy:view.room.after-subject-change', evtData);
},
/** 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)
+ * 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)
+ *
+ * Triggers:
+ * candy:view.room.after-close using {roomJid}
*
* Parameters:
* (String) roomJid - Room to close
@@ -3737,7 +4055,18 @@ Candy.View.Pane = (function(self, $) {
}
delete self.Chat.rooms[roomJid];
- Candy.View.Event.Room.onClose({'roomJid' : roomJid});
+ var evtData = {'roomJid' : roomJid};
+
+ // deprecated
+ Candy.View.Event.Room.onClose(evtData);
+
+ /** Event: candy:view.room.after-close
+ * After closing a room
+ *
+ * Parameters:
+ * (String) roomJid - Room JID
+ */
+ $(Candy).triggerHandler('candy:view.room.after-close', evtData);
},
/** Function: appendToMessagePane
@@ -3768,7 +4097,7 @@ Candy.View.Pane = (function(self, $) {
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.Room.getPane(roomJid, '.message-pane').children().slice(0, options.remove).remove();
self.Chat.rooms[roomJid].messageCount -= options.remove;
}
}
@@ -3968,8 +4297,8 @@ Candy.View.Pane = (function(self, $) {
* (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>
+ * Triggers:
+ * candy:view.private-room.after-open using {roomJid, type, element}
*/
open: function(roomJid, roomName, switchToRoom, isNoConferenceRoomJid) {
var user = isNoConferenceRoomJid ? Candy.Core.getUser() : self.Room.getUser(Strophe.getBareJidFromJid(roomJid));
@@ -3983,16 +4312,32 @@ Candy.View.Pane = (function(self, $) {
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)});
+ var evtData = {'roomJid': roomJid, type: 'chat', 'element': self.Room.getPane(roomJid)};
+
+ // deprecated
+ Candy.View.Event.Room.onAdd(evtData);
+
+ /** Event: candy:view.private-room.after-open
+ * After opening a new private room
+ *
+ * Parameters:
+ * (String) roomJid - Room JID
+ * (String) type - 'chat'
+ * (jQuery.Element) element - User element
+ */
+ $(Candy).triggerHandler('candy:view.private-room.after-open', evtData);
},
/** Function: setStatus
@@ -4035,11 +4380,34 @@ Candy.View.Pane = (function(self, $) {
* (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
+ *
+ * Triggers:
+ * candy:view.roster.before-update using {roomJid, user, action, element}
+ * candy:view.roster.after-update using {roomJid, user, action, element}
*/
update: function(roomJid, user, action, currentUser) {
var roomId = self.Chat.rooms[roomJid].id,
userId = Candy.Util.jidToId(user.getJid()),
- usercountDiff = -1;
+ usercountDiff = -1,
+ userElem = $('#user-' + roomId + '-' + userId);
+
+ var evtData = {'roomJid': roomJid, type: null, 'user': user};
+
+ /** Event: candy:view.roster.before-update
+ * Before updating the roster of a room
+ *
+ * Parameters:
+ * (String) roomJid - Room JID
+ * (Candy.Core.ChatUser) user - User
+ * (String) action - [join, leave, kick, ban]
+ * (jQuery.Element) element - User element
+ */
+ $(Candy).triggerHandler('candy:view.roster.before-update', {
+ 'roomJid' : roomJid,
+ 'user' : user,
+ 'action': action,
+ 'element': userElem
+ });
// a user joined the room
if(action === 'join') {
@@ -4055,8 +4423,7 @@ Candy.View.Pane = (function(self, $) {
me: currentUser !== undefined && user.getNick() === currentUser.getNick(),
tooltipRole: $.i18n._('tooltipRole'),
tooltipIgnored: $.i18n._('tooltipIgnored')
- }),
- userElem = $('#user-' + roomId + '-' + userId);
+ });
if(userElem.length < 1) {
var userInserted = false,
@@ -4095,6 +4462,10 @@ Candy.View.Pane = (function(self, $) {
usercountDiff = 0;
userElem.replaceWith(html);
$('#user-' + roomId + '-' + userId).css({opacity: 1}).show();
+ // it's me, update the toolbar
+ if(currentUser !== undefined && user.getNick() === currentUser.getNick() && self.Room.getUser(roomJid)) {
+ self.Chat.Toolbar.update(roomJid);
+ }
}
// Presence of client
@@ -4141,7 +4512,26 @@ Candy.View.Pane = (function(self, $) {
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)});
+ var evtData = {
+ 'roomJid' : roomJid,
+ 'user' : user,
+ 'action': action,
+ 'element': $('#user-' + roomId + '-' + userId)
+ };
+
+ // deprecated
+ Candy.View.Event.Roster.onUpdate(evtData);
+
+ /** Event: candy:view.roster.after-update
+ * After updating a room's roster
+ *
+ * Parameters:
+ * (String) roomJid - Room JID
+ * (Candy.Core.ChatUser) user - User
+ * (String) action - [join, leave, kick, ban]
+ * (jQuery.Element) element - User element
+ */
+ $(Candy).triggerHandler('candy:view.roster.after-update', evtData);
},
/** Function: userClick
@@ -4159,7 +4549,7 @@ Candy.View.Pane = (function(self, $) {
* (String) elementId - Specific element to do the animation on
*/
joinAnimation: function(elementId) {
- $('#' + elementId).stop(true).slideDown('normal', function() { $(this).animate({ opacity: 1 }); });
+ $('#' + elementId).stop(true).slideDown('normal', function() {$(this).animate({opacity: 1});});
},
/** Function: leaveAnimation
@@ -4169,9 +4559,9 @@ Candy.View.Pane = (function(self, $) {
* (String) elementId - Specific element to do the animation on
*/
leaveAnimation: function(elementId) {
- $('#' + elementId).stop(true).attr('id', '#' + elementId + '-leaving').animate({ opacity: 0 }, {
+ $('#' + elementId).stop(true).attr('id', '#' + elementId + '-leaving').animate({opacity: 0}, {
complete: function() {
- $(this).slideUp('normal', function() { $(this).remove(); });
+ $(this).slideUp('normal', function() {$(this).remove();});
}
});
}
@@ -4187,13 +4577,29 @@ Candy.View.Pane = (function(self, $) {
*
* Parameters:
* (Event) event - Triggered event
+ *
+ * Triggers:
+ * candy:view.message.before-send using {message}
*/
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);
+ // deprecated
message = Candy.View.Event.Message.beforeSend(message);
+ var evtData = {message: message};
+
+ /** Event: candy:view.message.before-send
+ * Before sending a message
+ *
+ * Parameters:
+ * (String) message - Message text
+ */
+ $(Candy).triggerHandler('candy:view.message.before-send', evtData);
+
+ message = evtData.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) {
@@ -4212,27 +4618,67 @@ Candy.View.Pane = (function(self, $) {
* (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.
+ *
+ * Triggers:
+ * candy:view.message.before-show using {roomJid, name, message}
+ * candy.view.message.before-render using {template, templateData}
+ * candy:view.message.after-show using {roomJid, name, message, element}
*/
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});
+
+ var evtData = {'roomJid': roomJid, 'name': name, 'message': message};
+ // deprecated
+ evtData.message = Candy.View.Event.Message.beforeShow(evtData);
+
+ /** Event: candy:view.message.before-show
+ * Before showing a new message
+ *
+ * Parameters:
+ * (String) roomJid - Room JID
+ * (String) name - Name of the sending user
+ * (String) message - Message text
+ */
+ $(Candy).triggerHandler('candy:view.message.before-show', evtData);
+
+ message = evtData.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())
- });
+ var renderEvtData = {
+ template: Candy.View.Template.Message.item,
+ templateData: {
+ name: name,
+ displayName: Candy.Util.crop(name, Candy.View.getOptions().crop.message.nickname),
+ message: message,
+ time: Candy.Util.localizedTime(timestamp || new Date().toGMTString())
+ }
+ };
+
+ /** Event: candy:view.message.before-render
+ * Before rendering the message element
+ *
+ * Parameters:
+ * (String) template - Template to use
+ * (Object) templateData - Template data consists of:
+ * - (String) name - Name of the sending user
+ * - (String) displayName - Cropped name of the sending user
+ * - (String) message - Message text
+ * - (String) time - Localized time
+ */
+ $(Candy).triggerHandler('candy:view.message.before-render', renderEvtData);
+
+ var html = Mustache.to_html(renderEvtData.template, renderEvtData.templateData);
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) {
+ elem.find('a.label').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)) {
+ var room = Candy.Core.getRoom(roomJid);
+ if(room && name !== self.Room.getUser(Candy.View.getCurrent().roomJid).getNick() && room.getRoster().get(roomJid + '/' + name)) {
Candy.View.Pane.PrivateRoom.open(roomJid + '/' + name, name, true);
}
});
@@ -4248,7 +4694,21 @@ Candy.View.Pane = (function(self, $) {
self.Room.scrollToBottom(roomJid);
}
- Candy.View.Event.Message.onShow({'roomJid': roomJid, 'element': elem, 'nick': name, 'message': message});
+ var evtData = {'roomJid': roomJid, 'element': elem, 'name': name, 'message': message};
+
+ // deprecated
+ Candy.View.Event.Message.onShow(evtData);
+
+ /** Event: candy:view.message.after-show
+ * Triggered after showing a message
+ *
+ * Parameters:
+ * (String) roomJid - Room JID
+ * (jQuery.Element) element - User element
+ * (String) name - Name of the sending user
+ * (String) message - Message text
+ */
+ $(Candy).triggerHandler('candy:view.message.after-show', evtData);
}
};
@@ -4263,6 +4723,7 @@ Candy.View.Pane = (function(self, $) {
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
+ * (c) 2012 Patrick Stadler & Michael Weibel. All rights reserved.
*/
/** Class: Candy.View.Template
@@ -4282,8 +4743,8 @@ Candy.View.Template = (function(self){
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>',
+ adminMessage: '<li><small>{{time}}</small><div class="adminmessage"><span class="label">{{sender}}</span><span class="spacer">▸</span>{{subject}} {{message}}</div></li>',
+ infoMessage: '<li><small>{{time}}</small><div class="infomessage"><span class="spacer">•</span>{{subject}} {{message}}</div></li>',
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"'
@@ -4291,28 +4752,28 @@ Candy.View.Template = (function(self){
+ ' value="always" /><param name="FlashVars" value="listener=audioplayerListener&amp;mp3={{resourcesPath}}notify.mp3" />'
+ '</object>',
Context: {
- menu: '<div id="context-menu"><ul></ul></div>',
+ menu: '<div id="context-menu"><i class="arrow arrow-top"></i><ul></ul><i class="arrow arrow-bottom"></i></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>'
+ tooltip: '<div id="tooltip"><i class="arrow arrow-top"></i><div></div><i class="arrow arrow-bottom"></i></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>'
+ subject: '<li><small>{{time}}</small><div class="subject"><span class="label">{{roomName}}</span><span class="spacer">▸</span>{{_roomSubject}} {{subject}}</div></li>',
+ form: '<div class="message-form-wrapper"><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></div>'
};
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>'
+ 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}}">&#x25BE;</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>'
+ pane: '<div class="message-pane-wrapper"><ul class="message-pane"></ul></div>',
+ item: '<li><small>{{time}}</small><div><a class="label" href="#" class="name">{{displayName}}</a><span class="spacer">▸</span>{{{message}}}</div></li>'
};
self.Login = {
@@ -4322,7 +4783,7 @@ Candy.View.Template = (function(self){
+ '{{#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">'
@@ -4346,6 +4807,7 @@ Candy.View.Template = (function(self){
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
+ * (c) 2012 Patrick Stadler & Michael Weibel. All rights reserved.
*/
/** Class: Candy.View.Translation
@@ -4417,7 +4879,6 @@ Candy.View.Translation = {
'antiSpamMessage' : 'Please do not spam. You have been blocked for a short-time.'
},
-
'de' : {
'status': 'Status: %s',
'statusConnecting': 'Verbinden...',
@@ -4483,32 +4944,31 @@ Candy.View.Translation = {
'antiSpamMessage' : 'Bitte nicht spammen. Du wurdest für eine kurze Zeit blockiert.'
},
-
'fr' : {
- 'status': 'Status: %s',
- 'statusConnecting': 'Connecter...',
+ 'status': 'Status : %s',
+ 'statusConnecting': 'Connexion…',
'statusConnected' : 'Connecté.',
- 'statusDisconnecting': 'Déconnecter....',
+ 'statusDisconnecting': 'Déconnexion…',
'statusDisconnected' : 'Déconnecté.',
- 'statusAuthfail': 'Authentification a échoué',
+ 'statusAuthfail': 'L\'authentification a échoué',
- 'roomSubject' : 'Sujet:',
+ 'roomSubject' : 'Sujet :',
'messageSubmit': 'Envoyer',
- 'labelUsername': 'Nom d\'utilisateur:',
- 'labelPassword': 'Mot de passe:',
- 'loginSubmit' : 'Inscription',
+ 'labelUsername': 'Nom d\'utilisateur :',
+ 'labelPassword': 'Mot de passe :',
+ 'loginSubmit' : 'Connexion',
'loginInvalid' : 'JID invalide',
- 'reason' : 'Justification:',
- 'subject' : 'Titre:',
- 'reasonWas' : 'Justification: %s.',
+ 'reason' : 'Motif :',
+ 'subject' : 'Titre :',
+ 'reasonWas' : 'Motif : %s.',
'kickActionLabel' : 'Kick',
- 'youHaveBeenKickedBy' : 'Tu as été expulsé de le salon %1$s (%2$s)',
- 'youHaveBeenKicked' : 'Tu as été expulsé de le salon %s',
+ 'youHaveBeenKickedBy' : 'Vous avez été expulsé du salon %1$s (%2$s)',
+ 'youHaveBeenKicked' : 'Vous avez été expulsé du 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',
+ 'youHaveBeenBannedBy' : 'Vous avez été banni du salon %1$s (%2$s)',
+ 'youHaveBeenBanned' : 'Vous avez été banni du salon %s',
'privateActionLabel' : 'Chat privé',
'ignoreActionLabel' : 'Ignorer',
@@ -4523,33 +4983,32 @@ Candy.View.Translation = {
'userHasBeenKickedFromRoom': '%s a été expulsé du salon.',
'userHasBeenBannedFromRoom': '%s a été banni du salon.',
- 'presenceUnknownWarningSubject': 'Note:',
+ 'presenceUnknownWarningSubject': 'Note :',
'presenceUnknownWarning' : 'Cet utilisateur n\'est malheureusement plus connecté, le message ne sera pas envoyé.',
- 'dateFormat': 'dd.mm.yyyy',
+ 'dateFormat': 'dd/mm/yyyy',
'timeFormat': 'HH:MM:ss',
'tooltipRole' : 'Modérateur',
- 'tooltipIgnored' : 'Tu ignores cette personne',
+ 'tooltipIgnored' : 'Vous ignorez cette personne',
'tooltipEmoticons' : 'Smileys',
- 'tooltipSound' : 'Jouer un son lorsque tu reçois de nouveaux messages privés',
- 'tooltipAutoscroll' : 'Auto-defilement',
+ 'tooltipSound' : 'Jouer un son lors de la réception de nouveaux messages privés',
+ 'tooltipAutoscroll' : 'Défilement automatique',
'tooltipStatusmessage' : 'Messages d\'état',
- 'tooltipAdministration' : 'Administrer le salon',
+ 'tooltipAdministration' : 'Administration du 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.',
+ 'passwordEnteredInvalid' : 'Le mot de passe pour le salon "%s" est invalide.',
- 'nicknameConflict': 'Le nom d\'utilisateur est déjà utilisé. Choisi un autre.',
+ 'nicknameConflict': 'Le nom d\'utilisateur est déjà utilisé. Veuillez en choisir 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.',
+ 'errorMembersOnly': 'Vous ne pouvez pas entrer dans le salon "%s" : droits insuffisants.',
+ 'errorMaxOccupantsReached': 'Vous ne pouvez pas entrer dans 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..'
+ 'antiSpamMessage' : 'Merci de ne pas envoyer de spam. Vous avez été bloqué pendant une courte période..'
},
-
'nl' : {
'status': 'Status: %s',
'statusConnecting': 'Verbinding maken...',
@@ -4615,7 +5074,6 @@ Candy.View.Translation = {
'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...',
@@ -4681,7 +5139,6 @@ Candy.View.Translation = {
'antiSpamMessage' : 'Por favor, no hagas spam. Has sido bloqueado temporalmente.'
},
-
'cn': {
'status': '状态: %s',
'statusConnecting': '连接中...',
@@ -4745,7 +5202,6 @@ Candy.View.Translation = {
'antiSpamMessage': '因为您在短时间内发送过多的消息 服务器要阻止您一小段时间。'
},
-
'ja' : {
'status' : 'ステータス: %s',
'statusConnecting' : '接続中…',
@@ -4810,5 +5266,330 @@ Candy.View.Translation = {
'errorMaxOccupantsReached' : '"%s"の部屋に入ることができません: 参加者の数はすでに上限に達しました。',
'antiSpamMessage' : 'スパムなどの行為はやめてください。あなたは一時的にブロックされました。'
+ },
+ 'sv' : {
+ 'status': 'Status: %s',
+ 'statusConnecting': 'Ansluter...',
+ 'statusConnected' : 'Ansluten',
+ 'statusDisconnecting': 'Kopplar från...',
+ 'statusDisconnected' : 'Frånkopplad',
+ 'statusAuthfail': 'Autentisering misslyckades',
+
+ 'roomSubject' : 'Ämne:',
+ 'messageSubmit': 'Skicka',
+
+ 'labelUsername': 'Användarnamn:',
+ 'labelPassword': 'Lösenord:',
+ 'loginSubmit' : 'Logga in',
+ 'loginInvalid' : 'Ogiltigt JID',
+
+ 'reason' : 'Anledning:',
+ 'subject' : 'Ämne:',
+ 'reasonWas' : 'Anledningen var: %s.',
+ 'kickActionLabel' : 'Sparka ut',
+ 'youHaveBeenKickedBy' : 'Du har blivit utsparkad från %2$s av %1$s',
+ 'youHaveBeenKicked' : 'Du har blivit utsparkad från %s',
+ 'banActionLabel' : 'Bannlys',
+ 'youHaveBeenBannedBy' : 'Du har blivit bannlyst från %1$s av %2$s',
+ 'youHaveBeenBanned' : 'Du har blivit bannlyst från %s',
+
+ 'privateActionLabel' : 'Privat chatt',
+ 'ignoreActionLabel' : 'Blockera',
+ 'unignoreActionLabel' : 'Avblockera',
+
+ 'setSubjectActionLabel': 'Ändra ämne',
+
+ 'administratorMessageSubject' : 'Administratör',
+
+ 'userJoinedRoom' : '%s kom in i rummet.',
+ 'userLeftRoom' : '%s har lämnat rummet.',
+ 'userHasBeenKickedFromRoom': '%s har blivit utsparkad ur rummet.',
+ 'userHasBeenBannedFromRoom': '%s har blivit bannlyst från rummet.',
+
+ 'presenceUnknownWarningSubject': 'Notera:',
+ 'presenceUnknownWarning' : 'Denna användare kan vara offline. Vi kan inte följa dennes närvaro.',
+
+ 'dateFormat': 'yyyy-mm-dd',
+ 'timeFormat': 'HH:MM:ss',
+
+ 'tooltipRole' : 'Moderator',
+ 'tooltipIgnored' : 'Du blockerar denna användare',
+ 'tooltipEmoticons' : 'Smilies',
+ 'tooltipSound' : 'Spela upp ett ljud vid nytt privat meddelande',
+ 'tooltipAutoscroll' : 'Autoskrolla',
+ 'tooltipStatusmessage' : 'Visa statusmeddelanden',
+ 'tooltipAdministration' : 'Rumadministrering',
+ 'tooltipUsercount' : 'Antal användare i rummet',
+
+ 'enterRoomPassword' : 'Rummet "%s" är lösenordsskyddat.',
+ 'enterRoomPasswordSubmit' : 'Anslut till rum',
+ 'passwordEnteredInvalid' : 'Ogiltigt lösenord för rummet "%s".',
+
+ 'nicknameConflict': 'Upptaget användarnamn. Var god välj ett annat.',
+
+ 'errorMembersOnly': 'Du kan inte ansluta till rummet "%s": Otillräckliga rättigheter.',
+ 'errorMaxOccupantsReached': 'Du kan inte ansluta till rummet "%s": Rummet är fullt.',
+
+ 'antiSpamMessage' : 'Var god avstå från att spamma. Du har blivit blockerad för en kort stund.'
+ },
+ 'it' : {
+ 'status': 'Stato: %s',
+ 'statusConnecting': 'Connessione...',
+ 'statusConnected' : 'Connessione',
+ 'statusDisconnecting': 'Disconnessione...',
+ 'statusDisconnected' : 'Disconnesso',
+ 'statusAuthfail': 'Autenticazione fallita',
+
+ 'roomSubject' : 'Oggetto:',
+ 'messageSubmit': 'Invia',
+
+ 'labelUsername': 'Nome utente:',
+ 'labelPassword': 'Password:',
+ 'loginSubmit' : 'Login',
+ 'loginInvalid' : 'JID non valido',
+
+ 'reason' : 'Ragione:',
+ 'subject' : 'Oggetto:',
+ 'reasonWas' : 'Ragione precedente: %s.',
+ 'kickActionLabel' : 'Espelli',
+ 'youHaveBeenKickedBy' : 'Sei stato espulso da %2$s da %1$s',
+ 'youHaveBeenKicked' : 'Sei stato espulso da %s',
+ 'banActionLabel' : 'Escluso',
+ 'youHaveBeenBannedBy' : 'Sei stato escluso da %1$s da %2$s',
+ 'youHaveBeenBanned' : 'Sei stato escluso da %s',
+
+ 'privateActionLabel' : 'Stanza privata',
+ 'ignoreActionLabel' : 'Ignora',
+ 'unignoreActionLabel' : 'Non ignorare',
+
+ 'setSubjectActionLabel': 'Cambia oggetto',
+
+ 'administratorMessageSubject' : 'Amministratore',
+
+ 'userJoinedRoom' : '%s si è unito alla stanza.',
+ 'userLeftRoom' : '%s ha lasciato la stanza.',
+ 'userHasBeenKickedFromRoom': '%s è stato espulso dalla stanza.',
+ 'userHasBeenBannedFromRoom': '%s è stato escluso dalla stanza.',
+
+ 'presenceUnknownWarningSubject': 'Nota:',
+ 'presenceUnknownWarning' : 'Questo utente potrebbe essere offline. Non possiamo tracciare la sua presenza.',
+
+ 'dateFormat': 'dd/mm/yyyy',
+ 'timeFormat': 'HH:MM:ss',
+
+ 'tooltipRole' : 'Moderatore',
+ 'tooltipIgnored' : 'Stai ignorando questo utente',
+ 'tooltipEmoticons' : 'Emoticons',
+ 'tooltipSound' : 'Riproduci un suono quando arrivano messaggi privati',
+ 'tooltipAutoscroll' : 'Autoscroll',
+ 'tooltipStatusmessage' : 'Mostra messaggi di stato',
+ 'tooltipAdministration' : 'Amministrazione stanza',
+ 'tooltipUsercount' : 'Partecipanti alla stanza',
+
+ 'enterRoomPassword' : 'La stanza "%s" è protetta da password.',
+ 'enterRoomPasswordSubmit' : 'Unisciti alla stanza',
+ 'passwordEnteredInvalid' : 'Password non valida per la stanza "%s".',
+
+ 'nicknameConflict': 'Nome utente già in uso. Scegline un altro.',
+
+ 'errorMembersOnly': 'Non puoi unirti alla stanza "%s": Permessi insufficienti.',
+ 'errorMaxOccupantsReached': 'Non puoi unirti alla stanza "%s": Troppi partecipanti.',
+
+ 'antiSpamMessage' : 'Per favore non scrivere messaggi pubblicitari. Sei stato bloccato per un po\' di tempo.'
+ },
+ 'pt': {
+ 'status': 'Status: %s',
+ 'statusConnecting': 'Conectando...',
+ 'statusConnected' : 'Conectado',
+ 'statusDisconnecting': 'Desligando...',
+ 'statusDisconnected' : 'Desligado',
+ 'statusAuthfail': 'Falha na autenticação',
+
+ 'roomSubject' : 'Assunto:',
+ 'messageSubmit': 'Enviar',
+
+ 'labelUsername': 'Usuário:',
+ 'labelPassword': 'Senha:',
+ 'loginSubmit' : 'Entrar',
+ 'loginInvalid' : 'JID inválido',
+
+ 'reason' : 'Motivo:',
+ 'subject' : 'Assunto:',
+ 'reasonWas' : 'O motivo foi: %s.',
+ 'kickActionLabel' : 'Excluir',
+ 'youHaveBeenKickedBy' : 'Você foi excluido de %1$s por %2$s',
+ 'youHaveBeenKicked' : 'Você foi excluido de %s',
+ 'banActionLabel' : 'Bloquear',
+ 'youHaveBeenBannedBy' : 'Você foi excluido permanentemente de %1$s por %2$s',
+ 'youHaveBeenBanned' : 'Você foi excluido permanentemente de %s',
+
+ 'privateActionLabel' : 'Bate-papo privado',
+ 'ignoreActionLabel' : 'Ignorar',
+ 'unignoreActionLabel' : 'Não ignorar',
+
+ 'setSubjectActionLabel': 'Trocar Assunto',
+
+ 'administratorMessageSubject' : 'Administrador',
+
+ 'userJoinedRoom' : '%s entrou na sala.',
+ 'userLeftRoom' : '%s saiu da sala.',
+ 'userHasBeenKickedFromRoom': '%s foi excluido da sala.',
+ 'userHasBeenBannedFromRoom': '%s foi excluido permanentemente da sala.',
+
+ 'presenceUnknownWarning' : 'Este usuário pode estar desconectado. Não é possível determinar o status.',
+ 'presenceUnknownWarning' : 'Este usuário poderá ser desligado..',
+
+ 'dateFormat': 'dd.mm.yyyy',
+ 'timeFormat': 'HH:MM:ss',
+
+ 'tooltipRole' : 'Moderador',
+ 'tooltipIgnored' : 'Você ignora este usuário',
+ 'tooltipEmoticons' : 'Emoticons',
+ 'tooltipSound' : 'Reproduzir o som para novas mensagens privados',
+ 'tooltipAutoscroll' : 'Deslocamento automático',
+ 'tooltipStatusmessage' : 'Mostrar mensagens de status',
+ 'tooltipAdministration' : 'Administração da sala',
+ 'tooltipUsercount' : 'Usuários na sala',
+
+ 'enterRoomPassword' : 'A sala "%s" é protegida por senha.',
+ 'enterRoomPasswordSubmit' : 'Junte-se à sala',
+ 'passwordEnteredInvalid' : 'Senha incorreta para a sala "%s".',
+
+ 'nicknameConflict': 'O nome de usuário já está em uso. Por favor, escolha outro.',
+
+ 'errorMembersOnly': 'Você não pode participar da sala "%s": privilégios insuficientes.',
+ 'errorMaxOccupantsReached': 'Você não pode participar da sala "%s": muitos participantes.',
+
+ 'antiSpamMessage' : 'Por favor, não envie spam. Você foi bloqueado temporariamente.'
+ },
+ 'ru' : {
+ 'status': 'Статус: %s',
+ 'statusConnecting': 'Подключение...',
+ 'statusConnected' : 'Подключено',
+ 'statusDisconnecting': 'Отключение...',
+ 'statusDisconnected' : 'Отключено',
+ 'statusAuthfail': 'Неверный логин',
+
+ 'roomSubject' : 'Топик:',
+ 'messageSubmit': 'Послать',
+
+ 'labelUsername': 'Имя:',
+ 'labelPassword': 'Пароль:',
+ 'loginSubmit' : 'Логин',
+ 'loginInvalid' : 'Неверный JID',
+
+ 'reason' : 'Причина:',
+ 'subject' : 'Топик:',
+ 'reasonWas' : 'Причина была: %s.',
+ 'kickActionLabel' : 'Выбросить',
+ 'youHaveBeenKickedBy' : 'Пользователь %1$s выбросил вас из чата %2$s',
+ 'youHaveBeenKicked' : 'Вас выбросили из чата %s',
+ 'banActionLabel' : 'Запретить доступ',
+ 'youHaveBeenBannedBy' : 'Пользователь %1$s запретил вам доступ в чат %2$s',
+ 'youHaveBeenBanned' : 'Вам запретили доступ в чат %s',
+
+ 'privateActionLabel' : 'Один-на-один чат',
+ 'ignoreActionLabel' : 'Игнорировать',
+ 'unignoreActionLabel' : 'Отменить игнорирование',
+
+ 'setSubjectActionLabel': 'Изменить топик',
+
+ 'administratorMessageSubject' : 'Администратор',
+
+ 'userJoinedRoom' : '%s вошёл в чат.',
+ 'userLeftRoom' : '%s вышел из чата.',
+ 'userHasBeenKickedFromRoom': '%s выброшен из чата.',
+ 'userHasBeenBannedFromRoom': '%s запрещён доступ в чат.',
+
+ 'presenceUnknownWarningSubject': 'Уведомление:',
+ 'presenceUnknownWarning' : 'Этот пользователь вероятнее всего оффлайн.',
+
+ 'dateFormat': 'mm.dd.yyyy',
+ 'timeFormat': 'HH:MM:ss',
+
+ 'tooltipRole' : 'Модератор',
+ 'tooltipIgnored' : 'Вы игнорируете этого пользователя.',
+ 'tooltipEmoticons' : 'Смайлики',
+ 'tooltipSound' : 'Озвучивать новое частное сообщение',
+ 'tooltipAutoscroll' : 'Авто-прокручивание',
+ 'tooltipStatusmessage' : 'Показывать статус сообщения',
+ 'tooltipAdministration' : 'Администрирование чат комнаты',
+ 'tooltipUsercount' : 'Участники чата',
+
+ 'enterRoomPassword' : 'Чат комната "%s" защищена паролем.',
+ 'enterRoomPasswordSubmit' : 'Войти в чат',
+ 'passwordEnteredInvalid' : 'Неверный пароль для комнаты "%s".',
+
+ 'nicknameConflict': 'Это имя уже используется. Пожалуйста выберите другое имя.',
+
+ 'errorMembersOnly': 'Вы не можете войти в чат "%s": Недостаточно прав доступа.',
+ 'errorMaxOccupantsReached': 'Вы не можете войти в чат "%s": Слишком много участников.',
+
+ 'antiSpamMessage' : 'Пожалуйста не рассылайте спам. Вас заблокировали на короткое время.'
+ },
+ 'ca': {
+ 'status': 'Estat: %s',
+ 'statusConnecting': 'Connectant...',
+ 'statusConnected' : 'Connectat',
+ 'statusDisconnecting': 'Desconnectant...',
+ 'statusDisconnected' : 'Desconnectat',
+ 'statusAuthfail': 'Ha fallat la autenticació',
+
+ 'roomSubject' : 'Assumpte:',
+ 'messageSubmit': 'Enviar',
+
+ 'labelUsername': 'Usuari:',
+ 'labelPassword': 'Clau:',
+ 'loginSubmit' : 'Entrar',
+ 'loginInvalid' : 'JID no vàlid',
+
+ 'reason' : 'Raó:',
+ 'subject' : 'Assumpte:',
+ 'reasonWas' : 'La raó ha estat: %s.',
+ 'kickActionLabel' : 'Expulsar',
+ 'youHaveBeenKickedBy' : 'Has estat expulsat de %1$s per %2$s',
+ 'youHaveBeenKicked' : 'Has estat expulsat de %s',
+ 'banActionLabel' : 'Prohibir',
+ 'youHaveBeenBannedBy' : 'Has estat expulsat permanentment de %1$s per %2$s',
+ 'youHaveBeenBanned' : 'Has estat expulsat permanentment de %s',
+
+ 'privateActionLabel' : 'Xat privat',
+ 'ignoreActionLabel' : 'Ignorar',
+ 'unignoreActionLabel' : 'No ignorar',
+
+ 'setSubjectActionLabel': 'Canviar assumpte',
+
+ 'administratorMessageSubject' : 'Administrador',
+
+ 'userJoinedRoom' : '%s ha entrat a la sala.',
+ 'userLeftRoom' : '%s ha deixat la sala.',
+ 'userHasBeenKickedFromRoom': '%s ha estat expulsat de la sala.',
+ 'userHasBeenBannedFromRoom': '%s ha estat expulsat permanentment de la sala.',
+
+ 'presenceUnknownWarningSubject': 'Atenció:',
+ 'presenceUnknownWarning' : 'Aquest usuari podria estar desconnectat ...',
+
+ 'dateFormat': 'dd.mm.yyyy',
+ 'timeFormat': 'HH:MM:ss',
+
+ 'tooltipRole' : 'Moderador',
+ 'tooltipIgnored' : 'Estàs ignorant aquest usuari',
+ 'tooltipEmoticons' : 'Emoticones',
+ 'tooltipSound' : 'Reproduir un so per a nous missatges',
+ 'tooltipAutoscroll' : 'Desplaçament automàtic',
+ 'tooltipStatusmessage' : 'Mostrar missatges d\'estat',
+ 'tooltipAdministration' : 'Administració de la sala',
+ 'tooltipUsercount' : 'Usuaris dins la sala',
+
+ 'enterRoomPassword' : 'La sala "%s" està protegida amb contrasenya.',
+ 'enterRoomPasswordSubmit' : 'Entrar a la sala',
+ 'passwordEnteredInvalid' : 'Contrasenya incorrecta per a la sala "%s".',
+
+ 'nicknameConflict': 'El nom d\'usuari ja s\'està utilitzant. Si us plau, escolleix-ne un altre.',
+
+ 'errorMembersOnly': 'No pots unir-te a la sala "%s": no tens prous privilegis.',
+ 'errorMaxOccupantsReached': 'No pots unir-te a la sala "%s": hi ha masses participants.',
+
+ 'antiSpamMessage' : 'Si us plau, no facis spam. Has estat bloquejat temporalment.'
}
-}; \ No newline at end of file
+};
diff --git a/candy.min.js b/candy.min.js
index 91923cc..a2f0394 100644
--- a/candy.min.js
+++ b/candy.min.js
@@ -1 +1 @@
-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
+var Candy=(function(a,b){a.about={name:"Candy",version:"1.5.0"};a.init=function(c,d){if(!d.viewClass){d.viewClass=a.View}d.viewClass.init(b("#candy"),d.view);a.Core.init(c,d.core)};return a}(Candy||{},jQuery));Candy.Core=(function(m,e,f){var d=null,j=null,a=null,g={},c=false,k,l={autojoin:true,debug:false,disableWindowUnload:false,presencePriority:1},b=function(n,o){e.addNamespace(n,o)},h=function(){b("PRIVATE","jabber:iq:private");b("BOOKMARKS","storage:bookmarks");b("PRIVACY","jabber:iq:privacy");b("DELAY","jabber:x:delay")},i=function(n){var o=e.getNodeFromJid(n),p=e.getDomainFromJid(n);return o?e.escapeNode(o)+"@"+p:p};m.init=function(n,o){j=n;f.extend(true,l,o);if(l.debug){m.log=function(q){try{if(typeof window.console!==undefined&&typeof window.console.log!==undefined){console.log(q)}}catch(p){}};m.log("[Init] Debugging enabled")}h();d=new e.Connection(j);d.rawInput=m.rawInput.bind(m);d.rawOutput=m.rawOutput.bind(m);if(!l.disableWindowUnload){window.onbeforeunload=m.onWindowUnload}if(f.browser.mozilla){f(document).keydown(function(p){if(p.which===27){p.preventDefault()}})}};m.registerEventHandlers=function(){m.addHandler(m.Event.Jabber.Version,e.NS.VERSION,"iq");m.addHandler(m.Event.Jabber.Presence,null,"presence");m.addHandler(m.Event.Jabber.Message,null,"message");m.addHandler(m.Event.Jabber.Bookmarks,e.NS.PRIVATE,"iq");m.addHandler(m.Event.Jabber.Room.Disco,e.NS.DISCO_INFO,"iq","result");m.addHandler(m.Event.Jabber.PrivacyList,e.NS.PRIVACY,"iq","result");m.addHandler(m.Event.Jabber.PrivacyListError,e.NS.PRIVACY,"iq","error");m.addHandler(d.disco._onDiscoInfo.bind(d.disco),e.NS.DISCO_INFO,"iq","get");m.addHandler(d.disco._onDiscoItems.bind(d.disco),e.NS.DISCO_ITEMS,"iq","get");m.addHandler(d.caps._delegateCapabilities.bind(d.caps),e.NS.CAPS)};m.connect=function(p,o,n){d.reset();m.registerEventHandlers();c=!c?p&&p.indexOf("@")<0:true;if(p&&o){d.connect(i(p)+"/"+Candy.about.name,o,Candy.Core.Event.Strophe.Connect);if(n){a=new m.ChatUser(p,n)}else{a=new m.ChatUser(p,e.getNodeFromJid(p))}}else{if(p&&n){d.connect(i(p)+"/"+Candy.about.name,null,Candy.Core.Event.Strophe.Connect);a=new m.ChatUser(null,n)}else{if(p){Candy.Core.Event.Login(p)}else{Candy.Core.Event.Login()}}}};m.attach=function(o,n,p){a=new m.ChatUser(o,e.getNodeFromJid(o));m.registerEventHandlers();d.attach(o,n,p,Candy.Core.Event.Strophe.Connect)};m.disconnect=function(){if(d.connected){f.each(m.getRooms(),function(){Candy.Core.Action.Jabber.Room.Leave(this.getJid())});d.disconnect()}};m.addHandler=function(r,q,o,p,t,s,n){return d.addHandler(r,q,o,p,t,s,n)};m.getUser=function(){return a};m.setUser=function(n){a=n};m.getConnection=function(){return d};m.getRooms=function(){return g};m.getStropheStatus=function(){return k};m.setStropheStatus=function(n){k=n};m.isAnonymousConnection=function(){return c};m.getOptions=function(){return l};m.getRoom=function(n){if(g[n]){return g[n]}return null};m.onWindowUnload=function(){d.sync=true;m.disconnect();d.flush()};m.rawInput=function(n){this.log("RECV: "+n)};m.rawOutput=function(n){this.log("SENT: "+n)};m.log=function(){};return m}(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(){b(Candy).on("candy:core.chat.connection",i.Observer.Chat.Connection);b(Candy).on("candy:core.chat.message",i.Observer.Chat.Message);b(Candy).on("candy:core.login",i.Observer.Login);b(Candy).on("candy:core.presence",i.Observer.Presence.update);b(Candy).on("candy:core.presence.leave",i.Observer.Presence.update);b(Candy).on("candy:core.presence.room",i.Observer.Presence.update);b(Candy).on("candy:core.presence.error",i.Observer.PresenceError);b(Candy).on("candy:core.message",i.Observer.Message)},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(){i.Pane.Chat.Toolbar.init()},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);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){var d=0;if(f[8]!=="Z"){d=+f[10]*60+(+f[11]);if(f[9]==="+"){d=-d}}d-=new Date().getTimezoneOffset();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()},nl2br:function(c){return c.replace(/\r\n|\r|\n/g,"<br />")},all:function(c){if(c){c=this.escape(c);c=this.linkify(c);c=this.emotify(c);c=this.nl2br(c)}return c}};return a}(Candy.Util||{},jQuery));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,e){var f=$pres(d).c("priority").t(Candy.Core.getOptions().presencePriority.toString()).up().c("c",Candy.Core.getConnection().caps.generateCapsAttrs()).up();if(e){f.node.appendChild(e.node)}Candy.Core.getConnection().send(f.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().sendIQ($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.apply(null,this.valueOf().split(":",2))})}}},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);var f=Candy.Core.getConnection(),g=f.muc.test_append_nick(d,Candy.Core.getUser().getNick()),h=$pres({from:f.jid,to:g}).c("x",{xmlns:c.NS.MUC});if(e!=null){h.c("password").t(e)}h.up().c("c",f.caps.generateCapsAttrs());f.send(h.tree())},Leave:function(e){var d=Candy.Core.getRoom(e).getUser();if(d){Candy.Core.getConnection().muc.leave(e,d.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),null,f,null,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,c,b){a.Login=function(d){b(Candy).triggerHandler("candy:core.login",{presetJid:d})};a.Strophe={Connect:function(d){Candy.Core.setStropheStatus(d);switch(d){case c.Status.CONNECTED:Candy.Core.log("[Connection] Connected");Candy.Core.Action.Jabber.GetJidIfAnonymous();case c.Status.ATTACHED:Candy.Core.log("[Connection] Attached");Candy.Core.Action.Jabber.Presence();Candy.Core.Action.Jabber.Autojoin();Candy.Core.Action.Jabber.GetIgnoreList();break;case c.Status.DISCONNECTED:Candy.Core.log("[Connection] Disconnected");break;case c.Status.AUTHFAIL:Candy.Core.log("[Connection] Authentication failed");break;case c.Status.CONNECTING:Candy.Core.log("[Connection] Connecting");break;case c.Status.DISCONNECTING:Candy.Core.log("[Connection] Disconnecting");break;case c.Status.AUTHENTICATING:Candy.Core.log("[Connection] Authenticating");break;case c.Status.ERROR:case c.Status.CONNFAIL:Candy.Core.log("[Connection] Failed ("+d+")");break;default:Candy.Core.log("[Connection] What?!");break}b(Candy).triggerHandler("candy:core.chat.connection",{status:d})}};a.Jabber={Version:function(d){Candy.Core.log("[Jabber] Version");Candy.Core.Action.Jabber.Version(b(d));return true},Presence:function(d){Candy.Core.log("[Jabber] Presence");d=b(d);if(d.children('x[xmlns^="'+c.NS.MUC+'"]').length>0){if(d.attr("type")==="error"){a.Jabber.Room.PresenceError(d)}else{a.Jabber.Room.Presence(d)}}else{b(Candy).triggerHandler("candy:core.presence",{from:d.attr("from"),stanza:d})}return true},Bookmarks:function(d){Candy.Core.log("[Jabber] Bookmarks");b("conference",d).each(function(){var e=b(this);if(e.attr("autojoin")){Candy.Core.Action.Jabber.Room.Join(e.attr("jid"))}});return true},PrivacyList:function(e){Candy.Core.log("[Jabber] PrivacyList");var d=Candy.Core.getUser();b('list[name="ignore"] item',e).each(function(){var f=b(this);if(f.attr("action")==="deny"){d.addToOrRemoveFromPrivacyList("ignore",f.attr("value"))}});Candy.Core.Action.Jabber.SetIgnoreListActive();return false},PrivacyListError:function(d){Candy.Core.log("[Jabber] PrivacyListError");if(b('error[code="404"][type="cancel"] item-not-found',d)){Candy.Core.Action.Jabber.ResetIgnoreList();Candy.Core.Action.Jabber.SetIgnoreListActive()}return false},Message:function(g){Candy.Core.log("[Jabber] Message");var g=b(g),f=g.attr("from"),e=g.attr("type"),d=g.attr("to");if(f!==c.getDomainFromJid(f)&&(e==="groupchat"||e==="chat"||e==="error"||e==="normal")){a.Jabber.Room.Message(g)}else{if(!d&&f===c.getDomainFromJid(f)){b(Candy).triggerHandler("candy:core.chat.message.admin",{type:(e||"message"),message:g.children("body").text()})}else{if(d&&f===c.getDomainFromJid(f)){b(Candy).triggerHandler("candy:core.chat.message.server",{type:(e||"message"),subject:g.children("subject").text(),message:g.children("body").text()})}}}return true},Room:{Leave:function(d){Candy.Core.log("[Jabber:Room] Leave");var d=b(d),j=d.attr("from"),l=c.getBareJidFromJid(j);if(!Candy.Core.getRoom(l)){return false}var h=Candy.Core.getRoom(l).getName(),k=d.find("item"),i="leave",g,f;delete Candy.Core.getRooms()[l];if(k.attr("role")==="none"){if(d.find("status").attr("code")==="307"){i="kick"}else{if(d.find("status").attr("code")==="301"){i="ban"}}g=k.find("reason").text();f=k.find("actor").attr("jid")}var e=new Candy.Core.ChatUser(j,c.getResourceFromJid(j),k.attr("affiliation"),k.attr("role"));b(Candy).triggerHandler("candy:core.presence.leave",{roomJid:l,roomName:h,type:i,reason:g,actor:f,user:e});return true},Disco:function(g){Candy.Core.log("[Jabber:Room] Disco");var g=b(g),e=c.getBareJidFromJid(g.attr("from"));if(!Candy.Core.getRooms()[e]){Candy.Core.getRooms()[e]=new Candy.Core.ChatRoom(e)}var d=g.find("identity").attr("name"),f=Candy.Core.getRoom(e);if(f.getName()===null){f.setName(d)}return true},Presence:function(f){Candy.Core.log("[Jabber:Room] Presence");var j=Candy.Util.unescapeJid(f.attr("from")),m=c.getBareJidFromJid(j),k=f.attr("type");if(c.getResourceFromJid(j)===Candy.Core.getUser().getNick()&&k==="unavailable"){a.Jabber.Room.Leave(f);return true}var e=Candy.Core.getRoom(m);if(!e){Candy.Core.getRooms()[m]=new Candy.Core.ChatRoom(m);e=Candy.Core.getRoom(m)}var i=e.getRoster(),g,h,l=f.find("item");if(k!=="unavailable"){var d=c.getResourceFromJid(j);h=new Candy.Core.ChatUser(j,d,l.attr("affiliation"),l.attr("role"));if(e.getUser()===null&&Candy.Core.getUser().getNick()===d){e.setUser(h)}i.add(h);g="join"}else{g="leave";if(l.attr("role")==="none"){if(f.find("status").attr("code")==="307"){g="kick"}else{if(f.find("status").attr("code")==="301"){g="ban"}}}h=i.get(j);i.remove(j)}b(Candy).triggerHandler("candy:core.presence.room",{roomJid:m,roomName:e.getName(),user:h,action:g,currentUser:Candy.Core.getUser()});return true},PresenceError:function(g){Candy.Core.log("[Jabber:Room] Presence Error");var h=Candy.Util.unescapeJid(g.attr("from")),e=c.getBareJidFromJid(h),f=Candy.Core.getRooms()[e],d=f.getName();delete f;b(Candy).triggerHandler("candy:core.presence.error",{msg:g,type:g.children("error").children()[0].tagName.toLowerCase(),roomJid:e,roomName:d})},Message:function(f){Candy.Core.log("[Jabber:Room] Message");var m,l;if(f.children("subject").length>0){m=Candy.Util.unescapeJid(c.getBareJidFromJid(f.attr("from")));l={name:c.getNodeFromJid(m),body:f.children("subject").text(),type:"subject"}}else{if(f.attr("type")==="error"){var k=f.children("error");if(k.children("text").length>0){m=f.attr("from");l={type:"info",body:k.children("text").text()}}}else{if(f.children("body").length>0){if(f.attr("type")==="chat"||f.attr("type")==="normal"){m=Candy.Util.unescapeJid(f.attr("from"));var d=c.getBareJidFromJid(m),g=!Candy.Core.getRoom(d),e=g?c.getNodeFromJid(m):c.getResourceFromJid(m);l={name:e,body:f.children("body").text(),type:f.attr("type"),isNoConferenceRoomJid:g}}else{m=Candy.Util.unescapeJid(c.getBareJidFromJid(f.attr("from")));var h=c.getResourceFromJid(f.attr("from"));if(h){h=c.unescapeNode(h);l={name:h,body:f.children("body").text(),type:f.attr("type")}}else{if(!Candy.View.Pane.Chat.rooms[f.attr("from")]){return true}l={name:"",body:f.children("body").text(),type:"info"}}}}else{return true}}}var i=f.children("delay")?f.children("delay"):f.children('x[xmlns="'+c.NS.DELAY+'"]'),j=i!==undefined?i.attr("stamp"):null;b(Candy).triggerHandler("candy:core.message",{roomJid:m,message:l,timestamp:j});return true}}};return a}(Candy.Core.Event||{},Strophe,jQuery));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={Connection:function(e,d){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}},Message:function(d,c){if(c.type==="message"){Candy.View.Pane.Chat.adminMessage((c.subject||""),c.message)}else{if(c.type==="chat"||c.type==="groupchat"){Candy.View.Pane.Chat.onInfoMessage(Candy.View.getCurrent().roomJid,(c.subject||""),c.message)}}}};a.Presence={update:function(h,f){if(f.type==="leave"){var d=Candy.View.Pane.Room.getUser(f.roomJid);Candy.View.Pane.Room.close(f.roomJid);a.Presence.notifyPrivateChats(d,f.type)}else{if(f.type==="kick"||f.type==="ban"){var i=f.actor?Strophe.getNodeFromJid(f.actor):null,g,e=[f.roomName];if(i){e.push(i)}switch(f.type){case"kick":g=b.i18n._((i?"youHaveBeenKickedBy":"youHaveBeenKicked"),e);break;case"ban":g=b.i18n._((i?"youHaveBeenBannedBy":"youHaveBeenBanned"),e);break}Candy.View.Pane.Chat.Modal.show(Mustache.to_html(Candy.View.Template.Chat.Context.adminMessageReason,{reason:f.reason,_action:g,_reason:b.i18n._("reasonWas",[f.reason])}));setTimeout(function(){Candy.View.Pane.Chat.Modal.hide(function(){Candy.View.Pane.Room.close(f.roomJid);a.Presence.notifyPrivateChats(f.user,f.type)})},5000);var c={type:f.type,reason:f.reason,roomJid:f.roomJid,user:f.user};Candy.View.Event.Room.onPresenceChange(c);b(Candy).triggerHandler("candy:view.presence",[c])}else{if(f.roomJid){if(!Candy.View.Pane.Chat.rooms[f.roomJid]){Candy.View.Pane.Room.init(f.roomJid,f.roomName);Candy.View.Pane.Room.show(f.roomJid)}Candy.View.Pane.Roster.update(f.roomJid,f.user,f.action,f.currentUser);if(Candy.View.Pane.Chat.rooms[f.user.getJid()]){Candy.View.Pane.Roster.update(f.user.getJid(),f.user,f.action,f.currentUser);Candy.View.Pane.PrivateRoom.setStatus(f.user.getJid(),f.action)}}else{}}}},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=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=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=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"})}},adminMessage:function(e,f){if(Candy.View.getCurrent().roomJid){var d=Mustache.to_html(Candy.View.Template.Chat.adminMessage,{subject:e,message:f,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"),d)});a.Room.scrollToBottom(Candy.View.getCurrent().roomJid);var c={subject:e,message:f};Candy.View.Event.Chat.onAdminMessage(c);b(Candy).triggerHandler("candy:view.chat.admin-message",c)}},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:{_supportsNativeAudio:false,init:function(){b("#emoticons-icon").click(function(d){a.Chat.Context.showEmoticonsMenu(d.currentTarget);d.stopPropagation()});b("#chat-autoscroll-control").click(a.Chat.Toolbar.onAutoscrollControlClick);var c=document.createElement("audio");a.Chat.Toolbar._supportsNativeAudio=!!(c.canPlayType&&c.canPlayType("audio/mpeg;").replace(/no/,""));b("#chat-sound-control").click(a.Chat.Toolbar.onSoundControlClick);if(Candy.Util.cookieExists("candy-nosound")){b("#chat-sound-control").click()}b("#chat-statusmessage-control").click(a.Chat.Toolbar.onStatusMessageControlClick);if(Candy.Util.cookieExists("candy-nostatusmessages")){b("#chat-statusmessage-control").click()}},show:function(){b("#chat-toolbar").show()},hide:function(){b("#chat-toolbar").hide()},update:function(c){var d=b("#chat-toolbar").find(".context"),e=a.Room.getUser(c);if(!e||!e.isModerator()){d.hide()}else{d.show().click(function(f){a.Chat.Context.show(f.currentTarget,c);f.stopPropagation()})}a.Chat.Toolbar.updateUsercount(a.Chat.rooms[c].usercount)},playSound:function(){a.Chat.Toolbar.onPlaySound()},onPlaySound:function(){try{if(a.Chat.Toolbar._supportsNativeAudio){new Audio(Candy.View.getOptions().resources+"notify.mp3").play()}else{var c=document.getElementById("chat-sound-player");c.SetVariable("method:stop","");c.SetVariable("method:play","")}}catch(d){}},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(":input:first").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}).removeClass("left-top left-bottom right-top right-bottom").addClass(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,q,h){e=b(e);var f=a.Chat.rooms[q].id,d=b("#context-menu"),p=b("ul li",d);b("#tooltip").hide();if(!h){h=Candy.Core.getUser()}p.remove();var k=this.getMenuLinks(q,h,e),c,l=function(s,r){return function(t){t.data.callback(t,s,r);b("#context-menu").hide()}};for(c in k){if(k.hasOwnProperty(c)){var o=k[c],j=Mustache.to_html(Candy.View.Template.Chat.Context.menulinks,{roomId:f,"class":o["class"],id:c,label:o.label});b("ul",d).append(j);b("#context-menu-"+c).bind("click",o,l(q,h))}}if(c){var n=e.offset(),g=Candy.Util.getPosLeftAccordingToWindowBounds(d,n.left),i=Candy.Util.getPosTopAccordingToWindowBounds(d,n.top);d.css({left:g.px,top:i.px}).removeClass("left-top left-bottom right-top right-bottom").addClass(g.backgroundPositionAlignment+"-"+i.backgroundPositionAlignment).fadeIn("fast");var m={roomJid:q,user:h,element:d};Candy.View.Event.Roster.afterContextMenu(m);b(Candy).triggerHandler("candy:view.roster.after-context-menu",m);return true}},getMenuLinks:function(e,d,f){var g,i,h;var c={roomJid:e,user:d,elem:f};i=Candy.View.Event.Roster.onContextMenu(c);c.menulinks=b.extend(this.initialMenuLinks(f),i);b(Candy).triggerHandler("candy:view.roster.context-menu",c);g=c.menulinks;for(h in g){if(g.hasOwnProperty(h)&&g[h].requiredPermission!==undefined&&!g[h].requiredPermission(d,a.Room.getUser(e),f)){delete g[h]}}return g},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}).removeClass("left-top left-bottom right-top right-bottom").addClass(c.backgroundPositionAlignment+"-"+f.backgroundPositionAlignment).fadeIn("fast");return true}}};a.Room={init:function(e,d,f){f=f||"groupchat";if(Candy.Util.isEmptyObject(a.Chat.rooms)){a.Chat.Toolbar.show()}var g=Candy.Util.jidToId(e);a.Chat.rooms[e]={id:g,usercount:0,name:d,type:f,messageCount:0,scrollPosition:-1};b("#chat-rooms").append(Mustache.to_html(Candy.View.Template.Room.pane,{roomId:g,roomJid:e,roomType:f,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(e,d,f);a.Room.getPane(e,".message-form").submit(a.Message.submit);var c={roomJid:e,type:f,element:a.Room.getPane(e)};Candy.View.Event.Room.onAdd(c);b(Candy).triggerHandler("candy:view.room.after-add",c);return g},show:function(c){var d=a.Chat.rooms[c].id;b(".room-pane").each(function(){var f=b(this);if(f.attr("id")===("chat-room-"+d)){f.show();Candy.View.getCurrent().roomJid=c;a.Chat.setActiveTab(c);a.Chat.Toolbar.update(c);a.Chat.clearUnreadMessages(c);a.Room.setFocusToForm(c);a.Room.scrollToBottom(c);var e={roomJid:c,element:f};Candy.View.Event.Room.onShow(e);b(Candy).triggerHandler("candy:view.room.after-show",e)}else{f.hide();var e={roomJid:c,element:f};Candy.View.Event.Room.onHide(e);b(Candy).triggerHandler("candy:view.room.after-hide",e)}})},setSubject:function(d,f){var e=Mustache.to_html(Candy.View.Template.Room.subject,{subject:f,roomName:a.Chat.rooms[d].name,_roomSubject:b.i18n._("roomSubject"),time:Candy.Util.localizedTime(new Date().toGMTString())});a.Room.appendToMessagePane(d,e);a.Room.scrollToBottom(d);var c={roomJid:d,element:a.Room.getPane(d),subject:f};Candy.View.Event.Room.onSubjectChange(c);b(Candy).triggerHandler("candy:view.room.after-subject-change",c)},close:function(d){a.Chat.removeTab(d);a.Window.clearUnreadMessages();a.Room.getPane(d).remove();var e=b("#chat-rooms").children();if(Candy.View.getCurrent().roomJid===d){Candy.View.getCurrent().roomJid=null;if(e.length===0){a.Chat.allTabsClosed()}else{a.Room.show(e.last().attr("data-roomjid"))}}delete a.Chat.rooms[d];var c={roomJid:d};Candy.View.Event.Room.onClose(c);b(Candy).triggerHandler("candy:view.room.after-close",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).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(f,d,g,h){var e=h?Candy.Core.getUser():a.Room.getUser(Strophe.getBareJidFromJid(f));if(Candy.Core.getUser().isInPrivacyList("ignore",f)){return false}if(!a.Chat.rooms[f]){a.Room.init(f,d,"chat")}if(g){a.Room.show(f)}a.Roster.update(f,new Candy.Core.ChatUser(f,d),"join",e);a.Roster.update(f,e,"join",e);a.PrivateRoom.setStatus(f,"join");if(h){a.Chat.infoMessage(f,b.i18n._("presenceUnknownWarningSubject"),b.i18n._("presenceUnknownWarning"))}var c={roomJid:f,type:"chat",element:a.Room.getPane(f)};Candy.View.Event.Room.onAdd(c);b(Candy).triggerHandler("candy:view.private-room.after-open",c)},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(o,i,h,e){var f=a.Chat.rooms[o].id,k=Candy.Util.jidToId(i.getJid()),c=-1,n=b("#user-"+f+"-"+k);var m={roomJid:o,type:null,user:i};b(Candy).triggerHandler("candy:view.roster.before-update",{roomJid:o,user:i,action:h,element:n});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")});if(n.length<1){var d=false,l=a.Room.getPane(o,".roster-pane");if(l.children().length>0){var g=i.getNick().toUpperCase();l.children().each(function(){var p=b(this);if(p.attr("data-nick").toUpperCase()>g){p.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(o)){if(a.Chat.rooms[o].type==="chat"){a.Chat.onInfoMessage(o,b.i18n._("userJoinedRoom",[i.getNick()]))}else{a.Chat.infoMessage(o,b.i18n._("userJoinedRoom",[i.getNick()]))}}}else{c=0;n.replaceWith(j);b("#user-"+f+"-"+k).css({opacity:1}).show();if(e!==undefined&&i.getNick()===e.getNick()&&a.Room.getUser(o)){a.Chat.Toolbar.update(o)}}if(e!==undefined&&e.getNick()===i.getNick()){a.Room.setUser(o,i)}else{b("#user-"+f+"-"+k).click(a.Roster.userClick)}b("#user-"+f+"-"+k+" .context").click(function(p){a.Chat.Context.show(p.currentTarget,o,i);p.stopPropagation()});if(e!==undefined&&e.isInPrivacyList("ignore",i.getJid())){Candy.View.Pane.Room.addIgnoreIcon(o,i.getJid())}}else{if(h==="leave"){a.Roster.leaveAnimation("user-"+f+"-"+k);if(a.Chat.rooms[o].type==="chat"){a.Chat.onInfoMessage(o,b.i18n._("userLeftRoom",[i.getNick()]))}else{a.Chat.infoMessage(o,b.i18n._("userLeftRoom",[i.getNick()]))}}else{if(h==="kick"){a.Roster.leaveAnimation("user-"+f+"-"+k);a.Chat.onInfoMessage(o,b.i18n._("userHasBeenKickedFromRoom",[i.getNick()]))}else{if(h==="ban"){a.Roster.leaveAnimation("user-"+f+"-"+k);a.Chat.onInfoMessage(o,b.i18n._("userHasBeenBannedFromRoom",[i.getNick()]))}}}}Candy.View.Pane.Chat.rooms[o].usercount+=c;if(o===Candy.View.getCurrent().roomJid){Candy.View.Pane.Chat.Toolbar.updateUsercount(Candy.View.Pane.Chat.rooms[o].usercount)}var m={roomJid:o,user:i,action:h,element:b("#user-"+f+"-"+k)};Candy.View.Event.Roster.onUpdate(m);b(Candy).triggerHandler("candy:view.roster.after-update",m)},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(f){var d=Candy.View.Pane.Chat.rooms[Candy.View.getCurrent().roomJid].type,e=b(this).children(".field").val().substring(0,Candy.View.getOptions().crop.message.body);e=Candy.View.Event.Message.beforeSend(e);var c={message:e};b(Candy).triggerHandler("candy:view.message.before-send",c);e=c.message;Candy.Core.Action.Jabber.Room.Message(Candy.View.getCurrent().roomJid,e,d);if(d==="chat"&&e){a.Message.show(Candy.View.getCurrent().roomJid,a.Room.getUser(Candy.View.getCurrent().roomJid).getNick(),e)}b(this).children(".field").val("").focus();f.preventDefault()},show:function(d,e,h,i){h=Candy.Util.Parser.all(h.substring(0,Candy.View.getOptions().crop.message.body));var c={roomJid:d,name:e,message:h};c.message=Candy.View.Event.Message.beforeShow(c);b(Candy).triggerHandler("candy:view.message.before-show",c);h=c.message;if(!h){return}var j={template:Candy.View.Template.Message.item,templateData:{name:e,displayName:Candy.Util.crop(e,Candy.View.getOptions().crop.message.nickname),message:h,time:Candy.Util.localizedTime(i||new Date().toGMTString())}};b(Candy).triggerHandler("candy:view.message.before-render",j);var f=Mustache.to_html(j.template,j.templateData);a.Room.appendToMessagePane(d,f);var g=a.Room.getPane(d,".message-pane").children().last();g.find("a.label").click(function(k){k.preventDefault();var l=Candy.Core.getRoom(d);if(l&&e!==a.Room.getUser(Candy.View.getCurrent().roomJid).getNick()&&l.getRoster().get(d+"/"+e)){Candy.View.Pane.PrivateRoom.open(d+"/"+e,e,true)}});if(Candy.View.getCurrent().roomJid!==d||!a.Window.hasFocus()){a.Chat.increaseUnreadMessages(d);if(Candy.View.Pane.Chat.rooms[d].type==="chat"&&!a.Window.hasFocus()){a.Chat.Toolbar.playSound()}}if(Candy.View.getCurrent().roomJid===d){a.Room.scrollToBottom(d)}var c={roomJid:d,element:g,name:e,message:h};Candy.View.Event.Message.onShow(c);b(Candy).triggerHandler("candy:view.message.after-show",c)}};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:'<li><small>{{time}}</small><div class="adminmessage"><span class="label">{{sender}}</span><span class="spacer">▸</span>{{subject}} {{message}}</div></li>',infoMessage:'<li><small>{{time}}</small><div class="infomessage"><span class="spacer">•</span>{{subject}} {{message}}</div></li>',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"><i class="arrow arrow-top"></i><ul></ul><i class="arrow arrow-bottom"></i></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"><i class="arrow arrow-top"></i><div></div><i class="arrow arrow-bottom"></i></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:'<li><small>{{time}}</small><div class="subject"><span class="label">{{roomName}}</span><span class="spacer">▸</span>{{_roomSubject}} {{subject}}</div></li>',form:'<div class="message-form-wrapper"><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></div>'};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}}">&#x25BE;</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"><ul class="message-pane"></ul></div>',item:'<li><small>{{time}}</small><div><a class="label" href="#" class="name">{{displayName}}</a><span class="spacer">▸</span>{{{message}}}</div></li>'};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:"Connexion…",statusConnected:"Connecté.",statusDisconnecting:"Déconnexion…",statusDisconnected:"Déconnecté.",statusAuthfail:"L'authentification a échoué",roomSubject:"Sujet :",messageSubmit:"Envoyer",labelUsername:"Nom d'utilisateur :",labelPassword:"Mot de passe :",loginSubmit:"Connexion",loginInvalid:"JID invalide",reason:"Motif :",subject:"Titre :",reasonWas:"Motif : %s.",kickActionLabel:"Kick",youHaveBeenKickedBy:"Vous avez été expulsé du salon %1$s (%2$s)",youHaveBeenKicked:"Vous avez été expulsé du salon %s",banActionLabel:"Ban",youHaveBeenBannedBy:"Vous avez été banni du salon %1$s (%2$s)",youHaveBeenBanned:"Vous avez été banni du 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:"Vous ignorez cette personne",tooltipEmoticons:"Smileys",tooltipSound:"Jouer un son lors de la réception de nouveaux messages privés",tooltipAutoscroll:"Défilement automatique",tooltipStatusmessage:"Messages d'état",tooltipAdministration:"Administration du 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 pour le salon "%s" est invalide.',nicknameConflict:"Le nom d'utilisateur est déjà utilisé. Veuillez en choisir un autre.",errorMembersOnly:'Vous ne pouvez pas entrer dans le salon "%s" : droits insuffisants.',errorMaxOccupantsReached:'Vous ne pouvez pas entrer dans le salon "%s": Limite d\'utilisateur atteint.',antiSpamMessage:"Merci de ne pas envoyer de spam. Vous avez é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:"スパムなどの行為はやめてください。あなたは一時的にブロックされました。"},sv:{status:"Status: %s",statusConnecting:"Ansluter...",statusConnected:"Ansluten",statusDisconnecting:"Kopplar från...",statusDisconnected:"Frånkopplad",statusAuthfail:"Autentisering misslyckades",roomSubject:"Ämne:",messageSubmit:"Skicka",labelUsername:"Användarnamn:",labelPassword:"Lösenord:",loginSubmit:"Logga in",loginInvalid:"Ogiltigt JID",reason:"Anledning:",subject:"Ämne:",reasonWas:"Anledningen var: %s.",kickActionLabel:"Sparka ut",youHaveBeenKickedBy:"Du har blivit utsparkad från %2$s av %1$s",youHaveBeenKicked:"Du har blivit utsparkad från %s",banActionLabel:"Bannlys",youHaveBeenBannedBy:"Du har blivit bannlyst från %1$s av %2$s",youHaveBeenBanned:"Du har blivit bannlyst från %s",privateActionLabel:"Privat chatt",ignoreActionLabel:"Blockera",unignoreActionLabel:"Avblockera",setSubjectActionLabel:"Ändra ämne",administratorMessageSubject:"Administratör",userJoinedRoom:"%s kom in i rummet.",userLeftRoom:"%s har lämnat rummet.",userHasBeenKickedFromRoom:"%s har blivit utsparkad ur rummet.",userHasBeenBannedFromRoom:"%s har blivit bannlyst från rummet.",presenceUnknownWarningSubject:"Notera:",presenceUnknownWarning:"Denna användare kan vara offline. Vi kan inte följa dennes närvaro.",dateFormat:"yyyy-mm-dd",timeFormat:"HH:MM:ss",tooltipRole:"Moderator",tooltipIgnored:"Du blockerar denna användare",tooltipEmoticons:"Smilies",tooltipSound:"Spela upp ett ljud vid nytt privat meddelande",tooltipAutoscroll:"Autoskrolla",tooltipStatusmessage:"Visa statusmeddelanden",tooltipAdministration:"Rumadministrering",tooltipUsercount:"Antal användare i rummet",enterRoomPassword:'Rummet "%s" är lösenordsskyddat.',enterRoomPasswordSubmit:"Anslut till rum",passwordEnteredInvalid:'Ogiltigt lösenord för rummet "%s".',nicknameConflict:"Upptaget användarnamn. Var god välj ett annat.",errorMembersOnly:'Du kan inte ansluta till rummet "%s": Otillräckliga rättigheter.',errorMaxOccupantsReached:'Du kan inte ansluta till rummet "%s": Rummet är fullt.',antiSpamMessage:"Var god avstå från att spamma. Du har blivit blockerad för en kort stund."},it:{status:"Stato: %s",statusConnecting:"Connessione...",statusConnected:"Connessione",statusDisconnecting:"Disconnessione...",statusDisconnected:"Disconnesso",statusAuthfail:"Autenticazione fallita",roomSubject:"Oggetto:",messageSubmit:"Invia",labelUsername:"Nome utente:",labelPassword:"Password:",loginSubmit:"Login",loginInvalid:"JID non valido",reason:"Ragione:",subject:"Oggetto:",reasonWas:"Ragione precedente: %s.",kickActionLabel:"Espelli",youHaveBeenKickedBy:"Sei stato espulso da %2$s da %1$s",youHaveBeenKicked:"Sei stato espulso da %s",banActionLabel:"Escluso",youHaveBeenBannedBy:"Sei stato escluso da %1$s da %2$s",youHaveBeenBanned:"Sei stato escluso da %s",privateActionLabel:"Stanza privata",ignoreActionLabel:"Ignora",unignoreActionLabel:"Non ignorare",setSubjectActionLabel:"Cambia oggetto",administratorMessageSubject:"Amministratore",userJoinedRoom:"%s si è unito alla stanza.",userLeftRoom:"%s ha lasciato la stanza.",userHasBeenKickedFromRoom:"%s è stato espulso dalla stanza.",userHasBeenBannedFromRoom:"%s è stato escluso dalla stanza.",presenceUnknownWarningSubject:"Nota:",presenceUnknownWarning:"Questo utente potrebbe essere offline. Non possiamo tracciare la sua presenza.",dateFormat:"dd/mm/yyyy",timeFormat:"HH:MM:ss",tooltipRole:"Moderatore",tooltipIgnored:"Stai ignorando questo utente",tooltipEmoticons:"Emoticons",tooltipSound:"Riproduci un suono quando arrivano messaggi privati",tooltipAutoscroll:"Autoscroll",tooltipStatusmessage:"Mostra messaggi di stato",tooltipAdministration:"Amministrazione stanza",tooltipUsercount:"Partecipanti alla stanza",enterRoomPassword:'La stanza "%s" è protetta da password.',enterRoomPasswordSubmit:"Unisciti alla stanza",passwordEnteredInvalid:'Password non valida per la stanza "%s".',nicknameConflict:"Nome utente già in uso. Scegline un altro.",errorMembersOnly:'Non puoi unirti alla stanza "%s": Permessi insufficienti.',errorMaxOccupantsReached:'Non puoi unirti alla stanza "%s": Troppi partecipanti.',antiSpamMessage:"Per favore non scrivere messaggi pubblicitari. Sei stato bloccato per un po' di tempo."},pt:{status:"Status: %s",statusConnecting:"Conectando...",statusConnected:"Conectado",statusDisconnecting:"Desligando...",statusDisconnected:"Desligado",statusAuthfail:"Falha na autenticação",roomSubject:"Assunto:",messageSubmit:"Enviar",labelUsername:"Usuário:",labelPassword:"Senha:",loginSubmit:"Entrar",loginInvalid:"JID inválido",reason:"Motivo:",subject:"Assunto:",reasonWas:"O motivo foi: %s.",kickActionLabel:"Excluir",youHaveBeenKickedBy:"Você foi excluido de %1$s por %2$s",youHaveBeenKicked:"Você foi excluido de %s",banActionLabel:"Bloquear",youHaveBeenBannedBy:"Você foi excluido permanentemente de %1$s por %2$s",youHaveBeenBanned:"Você foi excluido permanentemente de %s",privateActionLabel:"Bate-papo privado",ignoreActionLabel:"Ignorar",unignoreActionLabel:"Não ignorar",setSubjectActionLabel:"Trocar Assunto",administratorMessageSubject:"Administrador",userJoinedRoom:"%s entrou na sala.",userLeftRoom:"%s saiu da sala.",userHasBeenKickedFromRoom:"%s foi excluido da sala.",userHasBeenBannedFromRoom:"%s foi excluido permanentemente da sala.",presenceUnknownWarning:"Este usuário pode estar desconectado. Não é possível determinar o status.",presenceUnknownWarning:"Este usuário poderá ser desligado..",dateFormat:"dd.mm.yyyy",timeFormat:"HH:MM:ss",tooltipRole:"Moderador",tooltipIgnored:"Você ignora este usuário",tooltipEmoticons:"Emoticons",tooltipSound:"Reproduzir o som para novas mensagens privados",tooltipAutoscroll:"Deslocamento automático",tooltipStatusmessage:"Mostrar mensagens de status",tooltipAdministration:"Administração da sala",tooltipUsercount:"Usuários na sala",enterRoomPassword:'A sala "%s" é protegida por senha.',enterRoomPasswordSubmit:"Junte-se à sala",passwordEnteredInvalid:'Senha incorreta para a sala "%s".',nicknameConflict:"O nome de usuário já está em uso. Por favor, escolha outro.",errorMembersOnly:'Você não pode participar da sala "%s": privilégios insuficientes.',errorMaxOccupantsReached:'Você não pode participar da sala "%s": muitos participantes.',antiSpamMessage:"Por favor, não envie spam. Você foi bloqueado temporariamente."},ru:{status:"Статус: %s",statusConnecting:"Подключение...",statusConnected:"Подключено",statusDisconnecting:"Отключение...",statusDisconnected:"Отключено",statusAuthfail:"Неверный логин",roomSubject:"Топик:",messageSubmit:"Послать",labelUsername:"Имя:",labelPassword:"Пароль:",loginSubmit:"Логин",loginInvalid:"Неверный JID",reason:"Причина:",subject:"Топик:",reasonWas:"Причина была: %s.",kickActionLabel:"Выбросить",youHaveBeenKickedBy:"Пользователь %1$s выбросил вас из чата %2$s",youHaveBeenKicked:"Вас выбросили из чата %s",banActionLabel:"Запретить доступ",youHaveBeenBannedBy:"Пользователь %1$s запретил вам доступ в чат %2$s",youHaveBeenBanned:"Вам запретили доступ в чат %s",privateActionLabel:"Один-на-один чат",ignoreActionLabel:"Игнорировать",unignoreActionLabel:"Отменить игнорирование",setSubjectActionLabel:"Изменить топик",administratorMessageSubject:"Администратор",userJoinedRoom:"%s вошёл в чат.",userLeftRoom:"%s вышел из чата.",userHasBeenKickedFromRoom:"%s выброшен из чата.",userHasBeenBannedFromRoom:"%s запрещён доступ в чат.",presenceUnknownWarningSubject:"Уведомление:",presenceUnknownWarning:"Этот пользователь вероятнее всего оффлайн.",dateFormat:"mm.dd.yyyy",timeFormat:"HH:MM:ss",tooltipRole:"Модератор",tooltipIgnored:"Вы игнорируете этого пользователя.",tooltipEmoticons:"Смайлики",tooltipSound:"Озвучивать новое частное сообщение",tooltipAutoscroll:"Авто-прокручивание",tooltipStatusmessage:"Показывать статус сообщения",tooltipAdministration:"Администрирование чат комнаты",tooltipUsercount:"Участники чата",enterRoomPassword:'Чат комната "%s" защищена паролем.',enterRoomPasswordSubmit:"Войти в чат",passwordEnteredInvalid:'Неверный пароль для комнаты "%s".',nicknameConflict:"Это имя уже используется. Пожалуйста выберите другое имя.",errorMembersOnly:'Вы не можете войти в чат "%s": Недостаточно прав доступа.',errorMaxOccupantsReached:'Вы не можете войти в чат "%s": Слишком много участников.',antiSpamMessage:"Пожалуйста не рассылайте спам. Вас заблокировали на короткое время."},ca:{status:"Estat: %s",statusConnecting:"Connectant...",statusConnected:"Connectat",statusDisconnecting:"Desconnectant...",statusDisconnected:"Desconnectat",statusAuthfail:"Ha fallat la autenticació",roomSubject:"Assumpte:",messageSubmit:"Enviar",labelUsername:"Usuari:",labelPassword:"Clau:",loginSubmit:"Entrar",loginInvalid:"JID no vàlid",reason:"Raó:",subject:"Assumpte:",reasonWas:"La raó ha estat: %s.",kickActionLabel:"Expulsar",youHaveBeenKickedBy:"Has estat expulsat de %1$s per %2$s",youHaveBeenKicked:"Has estat expulsat de %s",banActionLabel:"Prohibir",youHaveBeenBannedBy:"Has estat expulsat permanentment de %1$s per %2$s",youHaveBeenBanned:"Has estat expulsat permanentment de %s",privateActionLabel:"Xat privat",ignoreActionLabel:"Ignorar",unignoreActionLabel:"No ignorar",setSubjectActionLabel:"Canviar assumpte",administratorMessageSubject:"Administrador",userJoinedRoom:"%s ha entrat a la sala.",userLeftRoom:"%s ha deixat la sala.",userHasBeenKickedFromRoom:"%s ha estat expulsat de la sala.",userHasBeenBannedFromRoom:"%s ha estat expulsat permanentment de la sala.",presenceUnknownWarningSubject:"Atenció:",presenceUnknownWarning:"Aquest usuari podria estar desconnectat ...",dateFormat:"dd.mm.yyyy",timeFormat:"HH:MM:ss",tooltipRole:"Moderador",tooltipIgnored:"Estàs ignorant aquest usuari",tooltipEmoticons:"Emoticones",tooltipSound:"Reproduir un so per a nous missatges",tooltipAutoscroll:"Desplaçament automàtic",tooltipStatusmessage:"Mostrar missatges d'estat",tooltipAdministration:"Administració de la sala",tooltipUsercount:"Usuaris dins la sala",enterRoomPassword:'La sala "%s" està protegida amb contrasenya.',enterRoomPasswordSubmit:"Entrar a la sala",passwordEnteredInvalid:'Contrasenya incorrecta per a la sala "%s".',nicknameConflict:"El nom d'usuari ja s'està utilitzant. Si us plau, escolleix-ne un altre.",errorMembersOnly:'No pots unir-te a la sala "%s": no tens prous privilegis.',errorMaxOccupantsReached:'No pots unir-te a la sala "%s": hi ha masses participants.',antiSpamMessage:"Si us plau, no facis spam. Has estat bloquejat temporalment."}}; \ No newline at end of file
diff --git a/libs/libs.bundle.js b/libs/libs.bundle.js
index 5c27f43..78b5754 100644
--- a/libs/libs.bundle.js
+++ b/libs/libs.bundle.js
@@ -79,6 +79,213 @@ var Base64 = (function () {
return obj;
})();
/*
+ * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined
+ * in FIPS PUB 180-1
+ * Version 2.1a Copyright Paul Johnston 2000 - 2002.
+ * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
+ * Distributed under the BSD License
+ * See http://pajhome.org.uk/crypt/md5 for details.
+ */
+
+/*
+ * Configurable variables. You may need to tweak these to be compatible with
+ * the server-side, but the defaults work in most cases.
+ */
+var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
+var b64pad = "="; /* base-64 pad character. "=" for strict RFC compliance */
+var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */
+
+/*
+ * These are the functions you'll usually want to call
+ * They take string arguments and return either hex or base-64 encoded strings
+ */
+function hex_sha1(s){return binb2hex(core_sha1(str2binb(s),s.length * chrsz));}
+function b64_sha1(s){return binb2b64(core_sha1(str2binb(s),s.length * chrsz));}
+function str_sha1(s){return binb2str(core_sha1(str2binb(s),s.length * chrsz));}
+function hex_hmac_sha1(key, data){ return binb2hex(core_hmac_sha1(key, data));}
+function b64_hmac_sha1(key, data){ return binb2b64(core_hmac_sha1(key, data));}
+function str_hmac_sha1(key, data){ return binb2str(core_hmac_sha1(key, data));}
+
+/*
+ * Perform a simple self-test to see if the VM is working
+ */
+function sha1_vm_test()
+{
+ return hex_sha1("abc") == "a9993e364706816aba3e25717850c26c9cd0d89d";
+}
+
+/*
+ * Calculate the SHA-1 of an array of big-endian words, and a bit length
+ */
+function core_sha1(x, len)
+{
+ /* append padding */
+ x[len >> 5] |= 0x80 << (24 - len % 32);
+ x[((len + 64 >> 9) << 4) + 15] = len;
+
+ var w = new Array(80);
+ var a = 1732584193;
+ var b = -271733879;
+ var c = -1732584194;
+ var d = 271733878;
+ var e = -1009589776;
+
+ var i, j, t, olda, oldb, oldc, oldd, olde;
+ for (i = 0; i < x.length; i += 16)
+ {
+ olda = a;
+ oldb = b;
+ oldc = c;
+ oldd = d;
+ olde = e;
+
+ for (j = 0; j < 80; j++)
+ {
+ if (j < 16) { w[j] = x[i + j]; }
+ else { w[j] = rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1); }
+ t = safe_add(safe_add(rol(a, 5), sha1_ft(j, b, c, d)),
+ safe_add(safe_add(e, w[j]), sha1_kt(j)));
+ e = d;
+ d = c;
+ c = rol(b, 30);
+ b = a;
+ a = t;
+ }
+
+ a = safe_add(a, olda);
+ b = safe_add(b, oldb);
+ c = safe_add(c, oldc);
+ d = safe_add(d, oldd);
+ e = safe_add(e, olde);
+ }
+ return [a, b, c, d, e];
+}
+
+/*
+ * Perform the appropriate triplet combination function for the current
+ * iteration
+ */
+function sha1_ft(t, b, c, d)
+{
+ if (t < 20) { return (b & c) | ((~b) & d); }
+ if (t < 40) { return b ^ c ^ d; }
+ if (t < 60) { return (b & c) | (b & d) | (c & d); }
+ return b ^ c ^ d;
+}
+
+/*
+ * Determine the appropriate additive constant for the current iteration
+ */
+function sha1_kt(t)
+{
+ return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 :
+ (t < 60) ? -1894007588 : -899497514;
+}
+
+/*
+ * Calculate the HMAC-SHA1 of a key and some data
+ */
+function core_hmac_sha1(key, data)
+{
+ var bkey = str2binb(key);
+ if (bkey.length > 16) { bkey = core_sha1(bkey, key.length * chrsz); }
+
+ var ipad = new Array(16), opad = new Array(16);
+ for (var i = 0; i < 16; i++)
+ {
+ ipad[i] = bkey[i] ^ 0x36363636;
+ opad[i] = bkey[i] ^ 0x5C5C5C5C;
+ }
+
+ var hash = core_sha1(ipad.concat(str2binb(data)), 512 + data.length * chrsz);
+ return core_sha1(opad.concat(hash), 512 + 160);
+}
+
+/*
+ * Add integers, wrapping at 2^32. This uses 16-bit operations internally
+ * to work around bugs in some JS interpreters.
+ */
+function safe_add(x, y)
+{
+ var lsw = (x & 0xFFFF) + (y & 0xFFFF);
+ var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
+ return (msw << 16) | (lsw & 0xFFFF);
+}
+
+/*
+ * Bitwise rotate a 32-bit number to the left.
+ */
+function rol(num, cnt)
+{
+ return (num << cnt) | (num >>> (32 - cnt));
+}
+
+/*
+ * Convert an 8-bit or 16-bit string to an array of big-endian words
+ * In 8-bit function, characters >255 have their hi-byte silently ignored.
+ */
+function str2binb(str)
+{
+ var bin = [];
+ var mask = (1 << chrsz) - 1;
+ for (var i = 0; i < str.length * chrsz; i += chrsz)
+ {
+ bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (32 - chrsz - i%32);
+ }
+ return bin;
+}
+
+/*
+ * Convert an array of big-endian words to a string
+ */
+function binb2str(bin)
+{
+ var str = "";
+ var mask = (1 << chrsz) - 1;
+ for (var i = 0; i < bin.length * 32; i += chrsz)
+ {
+ str += String.fromCharCode((bin[i>>5] >>> (32 - chrsz - i%32)) & mask);
+ }
+ return str;
+}
+
+/*
+ * Convert an array of big-endian words to a hex string.
+ */
+function binb2hex(binarray)
+{
+ var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
+ var str = "";
+ for (var i = 0; i < binarray.length * 4; i++)
+ {
+ str += hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8+4)) & 0xF) +
+ hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8 )) & 0xF);
+ }
+ return str;
+}
+
+/*
+ * Convert an array of big-endian words to a base-64 string
+ */
+function binb2b64(binarray)
+{
+ var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ var str = "";
+ var triplet, j;
+ for (var i = 0; i < binarray.length * 4; i += 3)
+ {
+ triplet = (((binarray[i >> 2] >> 8 * (3 - i %4)) & 0xFF) << 16) |
+ (((binarray[i+1 >> 2] >> 8 * (3 - (i+1)%4)) & 0xFF) << 8 ) |
+ ((binarray[i+2 >> 2] >> 8 * (3 - (i+2)%4)) & 0xFF);
+ for (j = 0; j < 4; j++)
+ {
+ if (i * 8 + j * 6 > binarray.length * 32) { str += b64pad; }
+ else { str += tab.charAt((triplet >> 6*(3-j)) & 0x3F); }
+ }
+ }
+ return str;
+}
+/*
* A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
* Digest Algorithm, as defined in RFC 1321.
* Version 2.1 Copyright (C) Paul Johnston 1999 - 2002.
@@ -405,7 +612,7 @@ if (!Function.prototype.bind) {
var _slice = Array.prototype.slice;
var _concat = Array.prototype.concat;
var _args = _slice.call(arguments, 1);
-
+
return function () {
return func.apply(obj ? obj : this,
_concat.call(_args,
@@ -514,7 +721,7 @@ Strophe = {
* The version of the Strophe library. Unreleased builds will have
* a version of head-HASH where HASH is a partial revision.
*/
- VERSION: "8d27954",
+ VERSION: "8861616",
/** Constants: XMPP Namespace Constants
* Common namespace constants from the XMPP RFCs and XEPs.
@@ -532,6 +739,8 @@ Strophe = {
* NS.STREAM - XMPP Streams namespace from RFC 3920.
* NS.BIND - XMPP Binding namespace from RFC 3920.
* NS.SESSION - XMPP Session namespace from RFC 3920.
+ * NS.XHTML_IM - XHTML-IM namespace from XEP 71.
+ * NS.XHTML - XHTML body namespace from XEP 71.
*/
NS: {
HTTPBIND: "http://jabber.org/protocol/httpbind",
@@ -548,10 +757,68 @@ Strophe = {
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"
+ STANZAS: "urn:ietf:params:xml:ns:xmpp-stanzas",
+ XHTML_IM: "http://jabber.org/protocol/xhtml-im",
+ XHTML: "http://www.w3.org/1999/xhtml"
},
- /** Function: addNamespace
+
+ /** Constants: XHTML_IM Namespace
+ * contains allowed tags, tag attributes, and css properties.
+ * Used in the createHtml function to filter incoming html into the allowed XHTML-IM subset.
+ * See http://xmpp.org/extensions/xep-0071.html#profile-summary for the list of recommended
+ * allowed tags and their attributes.
+ */
+ XHTML: {
+ tags: ['a','blockquote','br','cite','em','img','li','ol','p','span','strong','ul','body'],
+ attributes: {
+ 'a': ['href'],
+ 'blockquote': ['style'],
+ 'br': [],
+ 'cite': ['style'],
+ 'em': [],
+ 'img': ['src', 'alt', 'style', 'height', 'width'],
+ 'li': ['style'],
+ 'ol': ['style'],
+ 'p': ['style'],
+ 'span': ['style'],
+ 'strong': [],
+ 'ul': ['style'],
+ 'body': []
+ },
+ css: ['background-color','color','font-family','font-size','font-style','font-weight','margin-left','margin-right','text-align','text-decoration'],
+ validTag: function(tag)
+ {
+ for(var i = 0; i < Strophe.XHTML.tags.length; i++) {
+ if(tag == Strophe.XHTML.tags[i]) {
+ return true;
+ }
+ }
+ return false;
+ },
+ validAttribute: function(tag, attribute)
+ {
+ if(typeof Strophe.XHTML.attributes[tag] !== 'undefined' && Strophe.XHTML.attributes[tag].length > 0) {
+ for(var i = 0; i < Strophe.XHTML.attributes[tag].length; i++) {
+ if(attribute == Strophe.XHTML.attributes[tag][i]) {
+ return true;
+ }
+ }
+ }
+ return false;
+ },
+ validCSS: function(style)
+ {
+ for(var i = 0; i < Strophe.XHTML.css.length; i++) {
+ if(style == Strophe.XHTML.css[i]) {
+ return true;
+ }
+ }
+ return false;
+ }
+ },
+
+ /** Function: addNamespace
* This function is used to extend the current namespaces in
* Strophe.NS. It takes a key and a value with the key being the
* name of the new namespace, with its actual value.
@@ -565,7 +832,7 @@ Strophe = {
*/
addNamespace: function (name, value)
{
- Strophe.NS[name] = value;
+ Strophe.NS[name] = value;
},
/** Constants: Connection Status Constants
@@ -616,11 +883,13 @@ Strophe = {
*
* ElementType.NORMAL - Normal element.
* ElementType.TEXT - Text data element.
+ * ElementType.FRAGMENT - XHTML fragment element.
*/
ElementType: {
NORMAL: 1,
TEXT: 3,
- CDATA: 4
+ CDATA: 4,
+ FRAGMENT: 11
},
/** PrivateConstants: Timeout Values
@@ -698,7 +967,11 @@ Strophe = {
_makeGenerator: function () {
var doc;
- if (document.implementation.createDocument === undefined) {
+ // IE9 does implement createDocument(); however, using it will cause the browser to leak memory on page unload.
+ // Here, we test for presence of createDocument() plus IE's proprietary documentMode attribute, which would be
+ // less than 10 in the case of IE9 and below.
+ if (document.implementation.createDocument === undefined ||
+ document.implementation.createDocument && document.documentMode && document.documentMode < 10) {
doc = this._getIEXmlDom();
doc.appendChild(doc.createElement('strophe'));
} else {
@@ -842,12 +1115,34 @@ Strophe = {
*/
xmlTextNode: function (text)
{
- //ensure text is escaped
- text = Strophe.xmlescape(text);
-
+ //ensure text is escaped
+ text = Strophe.xmlescape(text);
return Strophe.xmlGenerator().createTextNode(text);
},
+ /** Function: xmlHtmlNode
+ * Creates an XML DOM html node.
+ *
+ * Parameters:
+ * (String) html - The content of the html node.
+ *
+ * Returns:
+ * A new XML DOM text node.
+ */
+ xmlHtmlNode: function (html)
+ {
+ //ensure text is escaped
+ if (window.DOMParser) {
+ parser = new DOMParser();
+ node = parser.parseFromString(html, "text/xml");
+ } else {
+ node = new ActiveXObject("Microsoft.XMLDOM");
+ node.async="false";
+ node.loadXML(html);
+ }
+ return node;
+ },
+
/** Function: getText
* Get the concatenation of all text children of an element.
*
@@ -873,7 +1168,7 @@ Strophe = {
}
}
- return str;
+ return Strophe.xmlescape(str);
},
/** Function: copyElement
@@ -909,6 +1204,83 @@ Strophe = {
return el;
},
+
+ /** Function: createHtml
+ * Copy an HTML DOM element into an XML DOM.
+ *
+ * This function copies a DOM element and all its descendants and returns
+ * the new copy.
+ *
+ * Parameters:
+ * (HTMLElement) elem - A DOM element.
+ *
+ * Returns:
+ * A new, copied DOM element tree.
+ */
+ createHtml: function (elem)
+ {
+ var i, el, j, tag, attribute, value, css, cssAttrs, attr, cssName, cssValue, children, child;
+ if (elem.nodeType == Strophe.ElementType.NORMAL) {
+ tag = elem.nodeName.toLowerCase();
+ if(Strophe.XHTML.validTag(tag)) {
+ try {
+ el = Strophe.xmlElement(tag);
+ for(i = 0; i < Strophe.XHTML.attributes[tag].length; i++) {
+ attribute = Strophe.XHTML.attributes[tag][i];
+ value = elem.getAttribute(attribute);
+ if(typeof value == 'undefined' || value === null || value === '' || value === false || value === 0) {
+ continue;
+ }
+ if(attribute == 'style' && typeof value == 'object') {
+ if(typeof value.cssText != 'undefined') {
+ value = value.cssText; // we're dealing with IE, need to get CSS out
+ }
+ }
+ // filter out invalid css styles
+ if(attribute == 'style') {
+ css = [];
+ cssAttrs = value.split(';');
+ for(j = 0; j < cssAttrs.length; j++) {
+ attr = cssAttrs[j].split(':');
+ cssName = attr[0].replace(/^\s*/, "").replace(/\s*$/, "").toLowerCase();
+ if(Strophe.XHTML.validCSS(cssName)) {
+ cssValue = attr[1].replace(/^\s*/, "").replace(/\s*$/, "");
+ css.push(cssName + ': ' + cssValue);
+ }
+ }
+ if(css.length > 0) {
+ value = css.join('; ');
+ el.setAttribute(attribute, value);
+ }
+ } else {
+ el.setAttribute(attribute, value);
+ }
+ }
+
+ for (i = 0; i < elem.childNodes.length; i++) {
+ el.appendChild(Strophe.createHtml(elem.childNodes[i]));
+ }
+ } catch(e) { // invalid elements
+ el = Strophe.xmlTextNode('');
+ }
+ } else {
+ el = Strophe.xmlGenerator().createDocumentFragment();
+ for (i = 0; i < elem.childNodes.length; i++) {
+ el.appendChild(Strophe.createHtml(elem.childNodes[i]));
+ }
+ }
+ } else if (elem.nodeType == Strophe.ElementType.FRAGMENT) {
+ el = Strophe.xmlGenerator().createDocumentFragment();
+ for (i = 0; i < elem.childNodes.length; i++) {
+ el.appendChild(Strophe.createHtml(elem.childNodes[i]));
+ }
+ } else if (elem.nodeType == Strophe.ElementType.TEXT) {
+ el = Strophe.xmlTextNode(elem.nodeValue);
+ }
+
+ return el;
+ },
+
/** Function: escapeNode
* Escape the node part (also called local part) of a JID.
*
@@ -1145,6 +1517,7 @@ Strophe = {
"='" + elem.attributes[i].value
.replace(/&/g, "&amp;")
.replace(/\'/g, "&apos;")
+ .replace(/>/g, "&gt;")
.replace(/</g, "&lt;") + "'";
}
}
@@ -1401,10 +1774,36 @@ Strophe.Builder.prototype = {
var child = Strophe.xmlTextNode(text);
this.node.appendChild(child);
return this;
+ },
+
+ /** Function: h
+ * Replace current element contents with the HTML passed in.
+ *
+ * This *does not* make the child the new current element
+ *
+ * Parameters:
+ * (String) html - The html to insert as contents of current element.
+ *
+ * Returns:
+ * The Strophe.Builder object.
+ */
+ h: function (html)
+ {
+ var fragment = document.createElement('body');
+
+ // force the browser to try and fix any invalid HTML tags
+ fragment.innerHTML = html;
+
+ // copy cleaned html into an xml dom
+ var xhtml = Strophe.createHtml(fragment);
+
+ while(xhtml.childNodes.length > 0) {
+ this.node.appendChild(xhtml.childNodes[0]);
+ }
+ return this;
}
};
-
/** PrivateClass: Strophe.Handler
* _Private_ helper class for managing stanza handlers.
*
@@ -1442,7 +1841,7 @@ Strophe.Handler = function (handler, ns, name, type, id, from, options)
this.type = type;
this.id = id;
this.options = options || {matchbare: false};
-
+
// default matchBare to false if undefined
if (!this.options.matchBare) {
this.options.matchBare = false;
@@ -1472,7 +1871,7 @@ Strophe.Handler.prototype = {
{
var nsMatch;
var from = null;
-
+
if (this.options.matchBare) {
from = Strophe.getBareJidFromJid(elem.getAttribute('from'));
} else {
@@ -1533,7 +1932,7 @@ Strophe.Handler.prototype = {
e.fileName + ":" + e.lineNumber + " - " +
e.name + ": " + e.message);
} else {
- Strophe.fatal("error: " + this.handler);
+ Strophe.fatal("error: " + e.message + "\n" + e.stack);
}
throw e;
@@ -1734,7 +2133,7 @@ Strophe.Request.prototype = {
/** Class: Strophe.Connection
* XMPP Connection manager.
*
- * Thie class is the main part of Strophe. It manages a BOSH connection
+ * This class is the main part of Strophe. It manages a BOSH connection
* to an XMPP server and dispatches events to the user callbacks as
* data arrives. It supports SASL PLAIN, SASL DIGEST-MD5, and legacy
* authentication.
@@ -1768,6 +2167,8 @@ Strophe.Connection = function (service)
this.service = service;
/* The connected JID. */
this.jid = "";
+ /* the JIDs domain */
+ this.domain = null;
/* request id for body tags */
this.rid = Math.floor(Math.random() * 4294967295);
/* The current session ID. */
@@ -1777,6 +2178,7 @@ Strophe.Connection = function (service)
this.features = null;
// SASL
+ this._sasl_data = [];
this.do_session = false;
this.do_bind = false;
@@ -1788,9 +2190,11 @@ Strophe.Connection = function (service)
this.addTimeds = [];
this.addHandlers = [];
+ this._authentication = {};
this._idleTimeout = null;
this._disconnectTimeout = null;
+ this.do_authentication = true;
this.authenticated = false;
this.disconnecting = false;
this.connected = false;
@@ -1812,6 +2216,9 @@ Strophe.Connection = function (service)
this._sasl_failure_handler = null;
this._sasl_challenge_handler = null;
+ // Max retries before disconnecting
+ this.maxRetries = 5;
+
// setup onIdle callback every 1/10th of a second
this._idleTimeout = setTimeout(this._onIdle.bind(this), 100);
@@ -1853,6 +2260,7 @@ Strophe.Connection.prototype = {
this.removeHandlers = [];
this.addTimeds = [];
this.addHandlers = [];
+ this._authentication = {};
this.authenticated = false;
this.disconnecting = false;
@@ -1945,8 +2353,9 @@ Strophe.Connection.prototype = {
* (Integer) hold - The optional HTTPBIND hold value. This is the
* number of connections the server will hold at one time. This
* should almost always be set to 1 (the default).
+ * (String) route
*/
- connect: function (jid, pass, callback, wait, hold)
+ connect: function (jid, pass, callback, wait, hold, route)
{
this.jid = jid;
this.pass = pass;
@@ -1960,7 +2369,7 @@ Strophe.Connection.prototype = {
this.hold = hold || this.hold;
// parse jid for domain and resource
- this.domain = Strophe.getDomainFromJid(this.jid);
+ this.domain = this.domain || Strophe.getDomainFromJid(this.jid);
// build the body tag
var body = this._buildBody().attrs({
@@ -1974,12 +2383,21 @@ Strophe.Connection.prototype = {
"xmlns:xmpp": Strophe.NS.BOSH
});
+ if(route){
+ body.attrs({
+ route: route
+ });
+ }
+
this._changeConnectStatus(Strophe.Status.CONNECTING, null);
+ var _connect_cb = this._connect_callback || this._connect_cb;
+ this._connect_callback = null;
+
this._requests.push(
new Strophe.Request(body.tree(),
this._onRequestStateChange.bind(
- this, this._connect_cb.bind(this)),
+ this, _connect_cb.bind(this)),
body.tree().getAttribute("rid")));
this._throttledRequestHandler();
},
@@ -2225,7 +2643,7 @@ Strophe.Connection.prototype = {
message: "Cannot queue non-DOMElement."
};
}
-
+
this._data.push(element);
},
@@ -2500,7 +2918,7 @@ Strophe.Connection.prototype = {
}
// make sure we limit the number of retries
- if (req.sends > 5) {
+ if (req.sends > this.maxRetries) {
this._onDisconnectTimeout();
return;
}
@@ -2905,8 +3323,11 @@ Strophe.Connection.prototype = {
*
* Parameters:
* (Strophe.Request) req - The current request.
+ * (Function) _callback - low level (xmpp) connect callback function.
+ * Useful for plugins with their own xmpp connect callback (when their)
+ * want to do something special).
*/
- _connect_cb: function (req)
+ _connect_cb: function (req, _callback)
{
Strophe.info("_connect_cb was called");
@@ -2953,39 +3374,74 @@ Strophe.Connection.prototype = {
var wait = bodyWrap.getAttribute('wait');
if (wait) { this.wait = parseInt(wait, 10); }
+ this._authentication.sasl_scram_sha1 = false;
+ this._authentication.sasl_plain = false;
+ this._authentication.sasl_digest_md5 = false;
+ this._authentication.sasl_anonymous = false;
+ this._authentication.legacy_auth = false;
- var do_sasl_plain = false;
- var do_sasl_digest_md5 = false;
- var do_sasl_anonymous = false;
+ // Check for the stream:features tag
+ var hasFeatures = bodyWrap.getElementsByTagName("stream:features").length > 0;
+ if (!hasFeatures) {
+ hasFeatures = bodyWrap.getElementsByTagName("features").length > 0;
+ }
var mechanisms = bodyWrap.getElementsByTagName("mechanism");
- var i, mech, auth_str, hashed_auth_str;
- if (mechanisms.length > 0) {
+ var i, mech, auth_str, hashed_auth_str,
+ found_authentication = false;
+ if (hasFeatures && mechanisms.length > 0) {
+ var missmatchedmechs = 0;
for (i = 0; i < mechanisms.length; i++) {
mech = Strophe.getText(mechanisms[i]);
- if (mech == 'DIGEST-MD5') {
- do_sasl_digest_md5 = true;
+ if (mech == 'SCRAM-SHA-1') {
+ this._authentication.sasl_scram_sha1 = true;
+ } else if (mech == 'DIGEST-MD5') {
+ this._authentication.sasl_digest_md5 = true;
} else if (mech == 'PLAIN') {
- do_sasl_plain = true;
+ this._authentication.sasl_plain = true;
} else if (mech == 'ANONYMOUS') {
- do_sasl_anonymous = true;
- }
+ this._authentication.sasl_anonymous = true;
+ } else missmatchedmechs++;
}
- } else {
+
+ this._authentication.legacy_auth =
+ bodyWrap.getElementsByTagName("auth").length > 0;
+
+ found_authentication =
+ this._authentication.legacy_auth ||
+ missmatchedmechs < mechanisms.length;
+ }
+ if (!found_authentication) {
+ _callback = _callback || this._connect_cb;
// we didn't get stream:features yet, so we need wait for it
// by sending a blank poll request
var body = this._buildBody();
this._requests.push(
new Strophe.Request(body.tree(),
this._onRequestStateChange.bind(
- this, this._connect_cb.bind(this)),
+ this, _callback.bind(this)),
body.tree().getAttribute("rid")));
this._throttledRequestHandler();
return;
}
+ if (this.do_authentication !== false)
+ this.authenticate();
+ },
+ /** Function: authenticate
+ * Set up authentication
+ *
+ * Contiunues the initial connection request by setting up authentication
+ * handlers and start the authentication process.
+ *
+ * SASL authentication will be attempted if available, otherwise
+ * the code will fall back to legacy authentication.
+ *
+ */
+ authenticate: function ()
+ {
if (Strophe.getNodeFromJid(this.jid) === null &&
- do_sasl_anonymous) {
+ this._authentication.sasl_anonymous) {
this._changeConnectStatus(Strophe.Status.AUTHENTICATING, null);
this._sasl_success_handler = this._addSysHandler(
this._sasl_success_cb.bind(this), null,
@@ -3004,10 +3460,34 @@ Strophe.Connection.prototype = {
this._changeConnectStatus(Strophe.Status.CONNFAIL,
'x-strophe-bad-non-anon-jid');
this.disconnect();
- } else if (do_sasl_digest_md5) {
+ } else if (this._authentication.sasl_scram_sha1) {
+ var cnonce = MD5.hexdigest(Math.random() * 1234567890);
+
+ var auth_str = "n=" + Strophe.getNodeFromJid(this.jid);
+ auth_str += ",r=";
+ auth_str += cnonce;
+
+ this._sasl_data["cnonce"] = cnonce;
+ this._sasl_data["client-first-message-bare"] = auth_str;
+
+ auth_str = "n,," + auth_str;
+
this._changeConnectStatus(Strophe.Status.AUTHENTICATING, null);
this._sasl_challenge_handler = this._addSysHandler(
- this._sasl_challenge1_cb.bind(this), null,
+ this._sasl_scram_challenge_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($build("auth", {
+ xmlns: Strophe.NS.SASL,
+ mechanism: "SCRAM-SHA-1"
+ }).t(Base64.encode(auth_str)).tree());
+ } else if (this._authentication.sasl_digest_md5) {
+ this._changeConnectStatus(Strophe.Status.AUTHENTICATING, null);
+ this._sasl_challenge_handler = this._addSysHandler(
+ this._sasl_digest_challenge1_cb.bind(this), null,
"challenge", null, null);
this._sasl_failure_handler = this._addSysHandler(
this._sasl_failure_cb.bind(this), null,
@@ -3017,7 +3497,7 @@ Strophe.Connection.prototype = {
xmlns: Strophe.NS.SASL,
mechanism: "DIGEST-MD5"
}).tree());
- } else if (do_sasl_plain) {
+ } else if (this._authentication.sasl_plain) {
// Build the plain auth string (barejid null
// username null password) and base 64 encoded.
auth_str = unescape(encodeURIComponent(Strophe.getBareJidFromJid(this.jid)));
@@ -3054,7 +3534,7 @@ Strophe.Connection.prototype = {
}
},
- /** PrivateFunction: _sasl_challenge1_cb
+ /** PrivateFunction: _sasl_digest_challenge1_cb
* _Private_ handler for DIGEST-MD5 SASL authentication.
*
* Parameters:
@@ -3063,7 +3543,7 @@ Strophe.Connection.prototype = {
* Returns:
* false to remove the handler.
*/
- _sasl_challenge1_cb: function (elem)
+ _sasl_digest_challenge1_cb: function (elem)
{
var attribMatch = /([a-z]+)=("[^"]+"|[^,"]+)(?:,|$)/;
@@ -3125,7 +3605,7 @@ Strophe.Connection.prototype = {
responseText += 'charset="utf-8"';
this._sasl_challenge_handler = this._addSysHandler(
- this._sasl_challenge2_cb.bind(this), null,
+ this._sasl_digest_challenge2_cb.bind(this), null,
"challenge", null, null);
this._sasl_success_handler = this._addSysHandler(
this._sasl_success_cb.bind(this), null,
@@ -3157,7 +3637,7 @@ Strophe.Connection.prototype = {
},
- /** PrivateFunction: _sasl_challenge2_cb
+ /** PrivateFunction: _sasl_digest_challenge2_cb
* _Private_ handler for second step of DIGEST-MD5 SASL authentication.
*
* Parameters:
@@ -3166,7 +3646,7 @@ Strophe.Connection.prototype = {
* Returns:
* false to remove the handler.
*/
- _sasl_challenge2_cb: function (elem)
+ _sasl_digest_challenge2_cb: function (elem)
{
// remove unneeded handlers
this.deleteHandler(this._sasl_success_handler);
@@ -3182,6 +3662,91 @@ Strophe.Connection.prototype = {
return false;
},
+ /** PrivateFunction: _sasl_scram_challenge_cb
+ * _Private_ handler for SCRAM-SHA-1 SASL authentication.
+ *
+ * Parameters:
+ * (XMLElement) elem - The challenge stanza.
+ *
+ * Returns:
+ * false to remove the handler.
+ */
+ _sasl_scram_challenge_cb: function (elem)
+ {
+ var nonce, salt, iter, Hi, U, U_old;
+ var clientKey, serverKey, clientSignature;
+ var responseText = "c=biws,";
+ var challenge = Base64.decode(Strophe.getText(elem));
+ var authMessage = this._sasl_data["client-first-message-bare"] + "," +
+ challenge + ",";
+ var cnonce = this._sasl_data["cnonce"]
+ var attribMatch = /([a-z]+)=([^,]+)(,|$)/;
+
+ // remove unneeded handlers
+ this.deleteHandler(this._sasl_failure_handler);
+
+ while (challenge.match(attribMatch)) {
+ matches = challenge.match(attribMatch);
+ challenge = challenge.replace(matches[0], "");
+ switch (matches[1]) {
+ case "r":
+ nonce = matches[2];
+ break;
+ case "s":
+ salt = matches[2];
+ break;
+ case "i":
+ iter = matches[2];
+ break;
+ }
+ }
+
+ if (!(nonce.substr(0, cnonce.length) === cnonce)) {
+ this._sasl_data = [];
+ return this._sasl_failure_cb(null);
+ }
+
+ responseText += "r=" + nonce;
+ authMessage += responseText;
+
+ salt = Base64.decode(salt);
+ salt += "\0\0\0\1";
+
+ Hi = U_old = core_hmac_sha1(this.pass, salt);
+ for (i = 1; i < iter; i++) {
+ U = core_hmac_sha1(this.pass, binb2str(U_old));
+ for (k = 0; k < 5; k++) {
+ Hi[k] ^= U[k];
+ }
+ U_old = U;
+ }
+ Hi = binb2str(Hi);
+
+ clientKey = core_hmac_sha1(Hi, "Client Key");
+ serverKey = str_hmac_sha1(Hi, "Server Key");
+ clientSignature = core_hmac_sha1(str_sha1(binb2str(clientKey)), authMessage);
+ this._sasl_data["server-signature"] = b64_hmac_sha1(serverKey, authMessage);
+
+ for (k = 0; k < 5; k++) {
+ clientKey[k] ^= clientSignature[k];
+ }
+
+ responseText += ",p=" + Base64.encode(binb2str(clientKey));
+
+ 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($build('response', {
+ xmlns: Strophe.NS.SASL
+ }).t(Base64.encode(responseText)).tree());
+
+ return false;
+ },
+
/** PrivateFunction: _auth1_cb
* _Private_ handler for legacy authentication.
*
@@ -3232,7 +3797,29 @@ Strophe.Connection.prototype = {
*/
_sasl_success_cb: function (elem)
{
- Strophe.info("SASL authentication succeeded.");
+ if (this._sasl_data["server-signature"]) {
+ var serverSignature;
+ var success = Base64.decode(Strophe.getText(elem));
+ var attribMatch = /([a-z]+)=([^,]+)(,|$)/;
+ matches = success.match(attribMatch);
+ if (matches[1] == "v") {
+ serverSignature = matches[2];
+ }
+ if (serverSignature != this._sasl_data["server-signature"]) {
+ // remove old handlers
+ 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._sasl_data = [];
+ return this._sasl_failure_cb(null);
+ }
+ }
+
+ Strophe.info("SASL authentication succeeded.");
// remove old handlers
this.deleteHandler(this._sasl_failure_handler);
@@ -3313,7 +3900,11 @@ Strophe.Connection.prototype = {
{
if (elem.getAttribute("type") == "error") {
Strophe.info("SASL binding failed.");
- this._changeConnectStatus(Strophe.Status.AUTHFAIL, null);
+ var conflict = elem.getElementsByTagName("conflict"), condition;
+ if (conflict.length > 0) {
+ condition = 'conflict';
+ }
+ this._changeConnectStatus(Strophe.Status.AUTHFAIL, condition);
return false;
}
@@ -3611,34 +4202,36 @@ if (callback) {
window.$iq = arguments[3];
window.$pres = arguments[4];
});
+
/*
-Plugin to implement the MUC extension. http://xmpp.org/extensions/xep-0045.html
-*/
-/* jslint configuration: */
-/* global document, window, setTimeout, clearTimeout, console,
- XMLHttpRequest, ActiveXObject,
- Base64, MD5,
- Strophe, $build, $msg, $iq, $pres
+ *Plugin to implement the MUC extension.
+ http://xmpp.org/extensions/xep-0045.html
+ *Previous Author:
+ Nathan Zorn <nathan.zorn@gmail.com>
+ *Complete CoffeeScript rewrite:
+ Andreas Guth <guth@dbis.rwth-aachen.de>
*/
-Strophe.addConnectionPlugin('muc', {
+(function() {
+ var Occupant, RoomConfig, XmppRoom,
+ __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+
+ Strophe.addConnectionPlugin('muc', {
_connection: null,
- // The plugin must have the init function
- /***Function
+ rooms: [],
+ /*Function
Initialize the MUC plugin. Sets the correct connection object and
extends the namesace.
*/
init: function(conn) {
- this._connection = conn;
- /* extend name space
- * NS.MUC - XMPP Multi-user chat namespace
- * from XEP 45.
- *
- */
- Strophe.addNamespace('MUC_OWNER', Strophe.NS.MUC+"#owner");
- Strophe.addNamespace('MUC_ADMIN', Strophe.NS.MUC+"#admin");
+ this._connection = conn;
+ this._muc_handler = null;
+ Strophe.addNamespace('MUC_OWNER', Strophe.NS.MUC + "#owner");
+ Strophe.addNamespace('MUC_ADMIN', Strophe.NS.MUC + "#admin");
+ Strophe.addNamespace('MUC_USER', Strophe.NS.MUC + "#user");
+ return Strophe.addNamespace('MUC_ROOMCONF', Strophe.NS.MUC + "#roomconfig");
},
- /***Function
+ /*Function
Join a multi-user chat room
Parameters:
(String) room - The multi-user chat room to join.
@@ -3650,132 +4243,247 @@ Strophe.addConnectionPlugin('muc', {
(String) password - The optional password to use. (password protected
rooms only)
*/
- join: function(room, nick, msg_handler_cb, pres_handler_cb, password) {
- var room_nick = this.test_append_nick(room, nick);
- var msg = $pres({from: this._connection.jid,
- to: room_nick})
- .c("x",{xmlns: Strophe.NS.MUC});
- if (password)
- {
- var password_elem = Strophe.xmlElement("password",
- [],
- password);
- msg.cnode(password_elem);
- }
- if (msg_handler_cb)
- {
- this._connection.addHandler(function(stanza) {
- var from = stanza.getAttribute('from');
- var roomname = from.split("/");
- // filter on room name
- if (roomname[0] == room)
- {
- return msg_handler_cb(stanza);
- }
- else
- {
- return true;
- }
- },
- null,
- "message",
- null,
- null,
- null);
- }
- if (pres_handler_cb)
- {
- this._connection.addHandler(function(stanza) {
- var xquery = stanza.getElementsByTagName("x");
- if (xquery.length > 0)
- {
- //Handle only MUC user protocol
- for (var i = 0; i < xquery.length; i++)
- {
- var xmlns = xquery[i].getAttribute("xmlns");
-
- if (xmlns && xmlns.match(Strophe.NS.MUC))
- {
- return pres_handler_cb(stanza);
- }
- }
+ join: function(room, nick, msg_handler_cb, pres_handler_cb, roster_cb, password) {
+ var msg, room_nick, _base,
+ _this = this;
+ room_nick = this.test_append_nick(room, nick);
+ msg = $pres({
+ from: this._connection.jid,
+ to: room_nick
+ }).c("x", {
+ xmlns: Strophe.NS.MUC
+ });
+ if (password != null) {
+ msg.cnode(Strophe.xmlElement("password", [], password));
+ }
+ if (this._muc_handler == null) {
+ this._muc_handler = this._connection.addHandler(function(stanza) {
+ var from, handler, handlers, id, roomname, x, xmlns, xquery, _i, _len;
+ from = stanza.getAttribute('from');
+ roomname = from.split("/")[0];
+ if (!_this.rooms[roomname]) return true;
+ room = _this.rooms[roomname];
+ handlers = {};
+ if (stanza.nodeName === "message") {
+ handlers = room._message_handlers;
+ } else if (stanza.nodeName === "presence") {
+ xquery = stanza.getElementsByTagName("x");
+ if (xquery.length > 0) {
+ for (_i = 0, _len = xquery.length; _i < _len; _i++) {
+ x = xquery[_i];
+ xmlns = x.getAttribute("xmlns");
+ if (xmlns && xmlns.match(Strophe.NS.MUC)) {
+ handlers = room._presence_handlers;
+ break;
}
- return true;
- },
- null,
- "presence",
- null,
- null,
- null);
- }
- this._connection.send(msg);
+ }
+ }
+ }
+ for (id in handlers) {
+ handler = handlers[id];
+ if (!handler(stanza, room)) delete handlers[id];
+ }
+ return true;
+ });
+ }
+ if ((_base = this.rooms)[room] == null) {
+ _base[room] = new XmppRoom(this, room, nick, password);
+ }
+ if (pres_handler_cb) {
+ this.rooms[room].addHandler('presence', pres_handler_cb);
+ }
+ if (msg_handler_cb) this.rooms[room].addHandler('message', msg_handler_cb);
+ if (roster_cb) this.rooms[room].addHandler('roster', roster_cb);
+ return this._connection.send(msg);
},
- /***Function
+ /*Function
Leave a multi-user chat room
Parameters:
(String) room - The multi-user chat room to leave.
(String) nick - The nick name used in the room.
(Function) handler_cb - Optional function to handle the successful leave.
+ (String) exit_msg - optional exit message.
Returns:
iqid - The unique id for the room leave.
*/
- leave: function(room, nick, handler_cb) {
- var room_nick = this.test_append_nick(room, nick);
- var presenceid = this._connection.getUniqueId();
- var presence = $pres({type: "unavailable",
- id: presenceid,
- from: this._connection.jid,
- to: room_nick})
- .c("x",{xmlns: Strophe.NS.MUC});
- this._connection.addHandler(handler_cb,
- null,
- "presence",
- null,
- presenceid,
- null);
- this._connection.send(presence);
- return presenceid;
- },
- /***Function
+ leave: function(room, nick, handler_cb, exit_msg) {
+ var presence, presenceid, room_nick;
+ delete this.rooms[room];
+ if (this.rooms.length === 0) {
+ this._connection.deleteHandler(this._muc_handler);
+ this._muc_handler = null;
+ }
+ room_nick = this.test_append_nick(room, nick);
+ presenceid = this._connection.getUniqueId();
+ presence = $pres({
+ type: "unavailable",
+ id: presenceid,
+ from: this._connection.jid,
+ to: room_nick
+ });
+ if (exit_msg != null) presence.c("status", exit_msg);
+ if (handler_cb != null) {
+ this._connection.addHandler(handler_cb, null, "presence", null, presenceid);
+ }
+ this._connection.send(presence);
+ return presenceid;
+ },
+ /*Function
Parameters:
(String) room - The multi-user chat room name.
(String) nick - The nick name used in the chat room.
- (String) message - The message to send to the room.
- (String) type - "groupchat" for group chat messages or "chat" for private chat messages
+ (String) message - The plaintext message to send to the room.
+ (String) html_message - The message to send to the room with html markup.
+ (String) type - "groupchat" for group chat messages o
+ "chat" for private chat messages
Returns:
msgiq - the unique id used to send the message
*/
- message: function(room, nick, message, type) {
- var room_nick = this.test_append_nick(room, nick);
- type = type || "groupchat";
- var msgid = this._connection.getUniqueId();
- var msg = $msg({to: room_nick,
- from: this._connection.jid,
- type: type,
- id: msgid}).c("body",
- {xmlns: Strophe.NS.CLIENT}).t(message);
- msg.up().c("x", {xmlns: "jabber:x:event"}).c("composing");
- this._connection.send(msg);
- return msgid;
- },
- /***Function
+ message: function(room, nick, message, html_message, type) {
+ var msg, msgid, parent, room_nick;
+ room_nick = this.test_append_nick(room, nick);
+ type = type || (nick != null ? "chat" : "groupchat");
+ msgid = this._connection.getUniqueId();
+ msg = $msg({
+ to: room_nick,
+ from: this._connection.jid,
+ type: type,
+ id: msgid
+ }).c("body", {
+ xmlns: Strophe.NS.CLIENT
+ }).t(message);
+ msg.up();
+ if (html_message != null) {
+ msg.c("html", {
+ xmlns: Strophe.NS.XHTML_IM
+ }).c("body", {
+ xmlns: Strophe.NS.XHTML
+ }).h(html_message);
+ if (msg.node.childNodes.length === 0) {
+ parent = msg.node.parentNode;
+ msg.up().up();
+ msg.node.removeChild(parent);
+ } else {
+ msg.up().up();
+ }
+ }
+ msg.c("x", {
+ xmlns: "jabber:x:event"
+ }).c("composing");
+ this._connection.send(msg);
+ return msgid;
+ },
+ /*Function
+ Convenience Function to send a Message to all Occupants
+ Parameters:
+ (String) room - The multi-user chat room name.
+ (String) message - The plaintext message to send to the room.
+ (String) html_message - The message to send to the room with html markup.
+ Returns:
+ msgiq - the unique id used to send the message
+ */
+ groupchat: function(room, message, html_message) {
+ return this.message(room, null, message, html_message);
+ },
+ /*Function
+ Send a mediated invitation.
+ Parameters:
+ (String) room - The multi-user chat room name.
+ (String) receiver - The invitation's receiver.
+ (String) reason - Optional reason for joining the room.
+ Returns:
+ msgiq - the unique id used to send the invitation
+ */
+ invite: function(room, receiver, reason) {
+ var invitation, msgid;
+ msgid = this._connection.getUniqueId();
+ invitation = $msg({
+ from: this._connection.jid,
+ to: room,
+ id: msgid
+ }).c('x', {
+ xmlns: Strophe.NS.MUC_USER
+ }).c('invite', {
+ to: receiver
+ });
+ if (reason != null) invitation.c('reason', reason);
+ this._connection.send(invitation);
+ return msgid;
+ },
+ /*Function
+ Send a direct invitation.
+ Parameters:
+ (String) room - The multi-user chat room name.
+ (String) receiver - The invitation's receiver.
+ (String) reason - Optional reason for joining the room.
+ (String) password - Optional password for the room.
+ Returns:
+ msgiq - the unique id used to send the invitation
+ */
+ directInvite: function(room, receiver, reason, password) {
+ var attrs, invitation, msgid;
+ msgid = this._connection.getUniqueId();
+ attrs = {
+ xmlns: 'jabber:x:conference',
+ jid: room
+ };
+ if (reason != null) attrs.reason = reason;
+ if (password != null) attrs.password = password;
+ invitation = $msg({
+ from: this._connection.jid,
+ to: receiver,
+ id: msgid
+ }).c('x', attrs);
+ this._connection.send(invitation);
+ return msgid;
+ },
+ /*Function
+ Queries a room for a list of occupants
+ (String) room - The multi-user chat room name.
+ (Function) success_cb - Optional function to handle the info.
+ (Function) error_cb - Optional function to handle an error.
+ Returns:
+ id - the unique id used to send the info request
+ */
+ queryOccupants: function(room, success_cb, error_cb) {
+ var attrs, info;
+ attrs = {
+ xmlns: Strophe.NS.DISCO_ITEMS
+ };
+ info = $iq({
+ from: this._connection.jid,
+ to: room,
+ type: 'get'
+ }).c('query', attrs);
+ return this._connection.sendIQ(info, success_cb, error_cb);
+ },
+ /*Function
Start a room configuration.
Parameters:
(String) room - The multi-user chat room name.
+ (Function) handler_cb - Optional function to handle the config form.
Returns:
id - the unique id used to send the configuration request
*/
- configure: function(room) {
- //send iq to start room configuration
- var config = $iq({to:room,
- type: "get"}).c("query",
- {xmlns: Strophe.NS.MUC_OWNER});
- var stanza = config.tree();
- return this._connection.sendIQ(stanza,
- function(){},
- function(){});
- },
- /***Function
+ configure: function(room, handler_cb) {
+ var config, id, stanza;
+ config = $iq({
+ to: room,
+ type: "get"
+ }).c("query", {
+ xmlns: Strophe.NS.MUC_OWNER
+ });
+ stanza = config.tree();
+ id = this._connection.sendIQ(stanza);
+ if (handler_cb != null) {
+ this._connection.addHandler(function(stanza) {
+ handler_cb(stanza);
+ return false;
+ }, Strophe.NS.MUC_OWNER, "iq", null, id);
+ }
+ return id;
+ },
+ /*Function
Cancel the room configuration
Parameters:
(String) room - The multi-user chat room name.
@@ -3783,17 +4491,20 @@ Strophe.addConnectionPlugin('muc', {
id - the unique id used to cancel the configuration.
*/
cancelConfigure: function(room) {
- //send iq to start room configuration
- var config = $iq({to: room,
- type: "set"})
- .c("query", {xmlns: Strophe.NS.MUC_OWNER})
- .c("x", {xmlns: "jabber:x:data", type: "cancel"});
- var stanza = config.tree();
- return this._connection.sendIQ(stanza,
- function(){},
- function(){});
- },
- /***Function
+ var config, stanza;
+ config = $iq({
+ to: room,
+ type: "set"
+ }).c("query", {
+ xmlns: Strophe.NS.MUC_OWNER
+ }).c("x", {
+ xmlns: "jabber:x:data",
+ type: "cancel"
+ });
+ stanza = config.tree();
+ return this._connection.sendIQ(stanza);
+ },
+ /*Function
Save a room configuration.
Parameters:
(String) room - The multi-user chat room name.
@@ -3802,50 +4513,86 @@ Strophe.addConnectionPlugin('muc', {
id - the unique id used to save the configuration.
*/
saveConfiguration: function(room, configarray) {
- var config = $iq({to: room,
- type: "set"})
- .c("query", {xmlns: Strophe.NS.MUC_OWNER})
- .c("x", {xmlns: "jabber:x:data", type: "submit"});
- for (var i = 0; i < configarray.length; i++) {
- config.cnode(configarray[i]);
- config.up();
- }
- var stanza = config.tree();
- return this._connection.sendIQ(stanza,
- function(){},
- function(){});
+ var conf, config, stanza, _i, _len;
+ config = $iq({
+ to: room,
+ type: "set"
+ }).c("query", {
+ xmlns: Strophe.NS.MUC_OWNER
+ }).c("x", {
+ xmlns: "jabber:x:data",
+ type: "submit"
+ });
+ for (_i = 0, _len = configarray.length; _i < _len; _i++) {
+ conf = configarray[_i];
+ config.cnode(conf).up();
+ }
+ stanza = config.tree();
+ return this._connection.sendIQ(stanza);
},
- /***Function
+ /*Function
Parameters:
(String) room - The multi-user chat room name.
Returns:
id - the unique id used to create the chat room.
*/
createInstantRoom: function(room) {
- var roomiq = $iq({to: room,
- type: "set"})
- .c("query", {xmlns: Strophe.NS.MUC_OWNER})
- .c("x", {xmlns: "jabber:x:data",
- type: "submit"});
- return this._connection.sendIQ(roomiq.tree(),
- function() {},
- function() {});
- },
- /***
- Set the topic of the chat room.
- Parameters:
- (String) room - The multi-user chat room name.
- (String) topic - Topic message.
- */
+ var roomiq;
+ roomiq = $iq({
+ to: room,
+ type: "set"
+ }).c("query", {
+ xmlns: Strophe.NS.MUC_OWNER
+ }).c("x", {
+ xmlns: "jabber:x:data",
+ type: "submit"
+ });
+ return this._connection.sendIQ(roomiq.tree());
+ },
+ /*Function
+ Set the topic of the chat room.
+ Parameters:
+ (String) room - The multi-user chat room name.
+ (String) topic - Topic message.
+ */
setTopic: function(room, topic) {
- var msg = $msg({to: room,
- from: this._connection.jid,
- type: "groupchat"})
- .c("subject", {xmlns: "jabber:client"}).t(topic);
- this._connection.send(msg.tree());
- },
- /***Function
- Changes the role and affiliation of a member of a MUC room.
+ var msg;
+ msg = $msg({
+ to: room,
+ from: this._connection.jid,
+ type: "groupchat"
+ }).c("subject", {
+ xmlns: "jabber:client"
+ }).t(topic);
+ return this._connection.send(msg.tree());
+ },
+ /*Function
+ Internal Function that Changes the role or affiliation of a member
+ of a MUC room. This function is used by modifyRole and modifyAffiliation.
+ The modification can only be done by a room moderator. An error will be
+ returned if the user doesn't have permission.
+ Parameters:
+ (String) room - The multi-user chat room name.
+ (Object) item - Object with nick and role or jid and affiliation attribute
+ (String) reason - Optional reason for the change.
+ (Function) handler_cb - Optional callback for success
+ (Function) errer_cb - Optional callback for error
+ Returns:
+ iq - the id of the mode change request.
+ */
+ _modifyPrivilege: function(room, item, reason, handler_cb, error_cb) {
+ var iq;
+ iq = $iq({
+ to: room,
+ type: "set"
+ }).c("query", {
+ xmlns: Strophe.NS.MUC_ADMIN
+ }).cnode(item.node);
+ if (reason != null) iq.c("reason", reason);
+ return this._connection.sendIQ(iq.tree(), handler_cb, error_cb);
+ },
+ /*Function
+ Changes the role of a member of a MUC room.
The modification can only be done by a room moderator. An error will be
returned if the user doesn't have permission.
Parameters:
@@ -3853,67 +4600,551 @@ Strophe.addConnectionPlugin('muc', {
(String) nick - The nick name of the user to modify.
(String) role - The new role of the user.
(String) affiliation - The new affiliation of the user.
- (String) reason - The reason for the change.
+ (String) reason - Optional reason for the change.
+ (Function) handler_cb - Optional callback for success
+ (Function) errer_cb - Optional callback for error
Returns:
iq - the id of the mode change request.
*/
- modifyUser: function(room, nick, role, affiliation, reason) {
- var item_attrs = {nick: Strophe.escapeNode(nick)};
- if (role !== null)
- {
- item_attrs.role = role;
- }
- if (affiliation !== null)
- {
- item_attrs.affiliation = affiliation;
- }
- var item = $build("item", item_attrs);
- if (reason !== null)
- {
- item.cnode(Strophe.xmlElement("reason", reason));
- }
- var roomiq = $iq({to: room,
- type: "set"})
- .c("query", {xmlns: Strophe.NS.MUC_OWNER}).cnode(item.tree());
- return this._connection.sendIQ(roomiq.tree(),
- function() {},
- function() {});
- },
- /***Function
+ modifyRole: function(room, nick, role, reason, handler_cb, error_cb) {
+ var item;
+ item = $build("item", {
+ nick: nick,
+ role: role
+ });
+ return this._modifyPrivilege(room, item, reason, handler_cb, error_cb);
+ },
+ kick: function(room, nick, reason, handler_cb, error_cb) {
+ return this.modifyRole(room, nick, 'none', reason, handler_cb, error_cb);
+ },
+ voice: function(room, nick, reason, handler_cb, error_cb) {
+ return this.modifyRole(room, nick, 'participant', reason, handler_cb, error_cb);
+ },
+ mute: function(room, nick, reason, handler_cb, error_cb) {
+ return this.modifyRole(room, nick, 'visitor', reason, handler_cb, error_cb);
+ },
+ op: function(room, nick, reason, handler_cb, error_cb) {
+ return this.modifyRole(room, nick, 'moderator', reason, handler_cb, error_cb);
+ },
+ deop: function(room, nick, reason, handler_cb, error_cb) {
+ return this.modifyRole(room, nick, 'participant', reason, handler_cb, error_cb);
+ },
+ /*Function
+ Changes the affiliation of a member of a MUC room.
+ The modification can only be done by a room moderator. An error will be
+ returned if the user doesn't have permission.
+ Parameters:
+ (String) room - The multi-user chat room name.
+ (String) jid - The jid of the user to modify.
+ (String) affiliation - The new affiliation of the user.
+ (String) reason - Optional reason for the change.
+ (Function) handler_cb - Optional callback for success
+ (Function) errer_cb - Optional callback for error
+ Returns:
+ iq - the id of the mode change request.
+ */
+ modifyAffiliation: function(room, jid, affiliation, reason, handler_cb, error_cb) {
+ var item;
+ item = $build("item", {
+ jid: jid,
+ affiliation: affiliation
+ });
+ return this._modifyPrivilege(room, item, reason, handler_cb, error_cb);
+ },
+ ban: function(room, jid, reason, handler_cb, error_cb) {
+ return this.modifyAffiliation(room, jid, 'outcast', reason, handler_cb, error_cb);
+ },
+ member: function(room, jid, reason, handler_cb, error_cb) {
+ return this.modifyAffiliation(room, jid, 'member', reason, handler_cb, error_cb);
+ },
+ revoke: function(room, jid, reason, handler_cb, error_cb) {
+ return this.modifyAffiliation(room, jid, 'none', reason, handler_cb, error_cb);
+ },
+ owner: function(room, jid, reason, handler_cb, error_cb) {
+ return this.modifyAffiliation(room, jid, 'owner', reason, handler_cb, error_cb);
+ },
+ admin: function(room, jid, reason, handler_cb, error_cb) {
+ return this.modifyAffiliation(room, jid, 'admin', reason, handler_cb, error_cb);
+ },
+ /*Function
Change the current users nick name.
Parameters:
(String) room - The multi-user chat room name.
(String) user - The new nick name.
*/
changeNick: function(room, user) {
- var room_nick = this.test_append_nick(room, user);
- var presence = $pres({from: this._connection.jid,
- to: room_nick})
- .c("x",{xmlns: Strophe.NS.MUC});
- this._connection.send(presence.tree());
+ var presence, room_nick;
+ room_nick = this.test_append_nick(room, user);
+ presence = $pres({
+ from: this._connection.jid,
+ to: room_nick,
+ id: this._connection.getUniqueId()
+ });
+ return this._connection.send(presence.tree());
+ },
+ /*Function
+ Change the current users status.
+ Parameters:
+ (String) room - The multi-user chat room name.
+ (String) user - The current nick.
+ (String) show - The new show-text.
+ (String) status - The new status-text.
+ */
+ setStatus: function(room, user, show, status) {
+ var presence, room_nick;
+ room_nick = this.test_append_nick(room, user);
+ presence = $pres({
+ from: this._connection.jid,
+ to: room_nick
+ });
+ if (show != null) presence.c('show', show).up();
+ if (status != null) presence.c('status', status);
+ return this._connection.send(presence.tree());
},
- /***Function
+ /*Function
List all chat room available on a server.
Parameters:
(String) server - name of chat server.
(String) handle_cb - Function to call for room list return.
*/
listRooms: function(server, handle_cb) {
- var iq = $iq({to: server,
- from: this._connection.jid,
- type: "get"})
- .c("query",{xmlns: Strophe.NS.DISCO_ITEMS});
- this._connection.sendIQ(iq, handle_cb, function(){});
+ var iq;
+ iq = $iq({
+ to: server,
+ from: this._connection.jid,
+ type: "get"
+ }).c("query", {
+ xmlns: Strophe.NS.DISCO_ITEMS
+ });
+ return this._connection.sendIQ(iq, handle_cb);
},
test_append_nick: function(room, nick) {
- var room_nick = room;
- if (nick)
- {
- room_nick += "/" + Strophe.escapeNode(nick);
+ return room + (nick != null ? "/" + (Strophe.escapeNode(nick)) : "");
+ }
+ });
+
+ XmppRoom = (function() {
+
+ XmppRoom.prototype.roster = {};
+
+ XmppRoom.prototype._message_handlers = {};
+
+ XmppRoom.prototype._presence_handlers = {};
+
+ XmppRoom.prototype._roster_handlers = {};
+
+ XmppRoom.prototype._handler_ids = 0;
+
+ function XmppRoom(client, name, nick, password) {
+ this.client = client;
+ this.name = name;
+ this.nick = nick;
+ this.password = password;
+ this._roomRosterHandler = __bind(this._roomRosterHandler, this);
+ this._addOccupant = __bind(this._addOccupant, this);
+ if (client.muc) this.client = client.muc;
+ this.name = Strophe.getBareJidFromJid(name);
+ this.client.rooms[this.name] = this;
+ this.addHandler('presence', this._roomRosterHandler);
+ }
+
+ XmppRoom.prototype.join = function(msg_handler_cb, pres_handler_cb) {
+ if (!this.client.rooms[this.name]) {
+ return this.client.join(this.name, this.nick, msg_handler_cb, pres_handler_cb, this.password);
+ }
+ };
+
+ XmppRoom.prototype.leave = function(handler_cb, message) {
+ this.client.leave(this.name, this.nick, handler_cb, message);
+ return delete this.client.rooms[this.name];
+ };
+
+ XmppRoom.prototype.message = function(nick, message, html_message, type) {
+ return this.client.message(this.name, nick, message, html_message, type);
+ };
+
+ XmppRoom.prototype.groupchat = function(message, html_message) {
+ return this.client.groupchat(this.name, message, html_message);
+ };
+
+ XmppRoom.prototype.invite = function(receiver, reason) {
+ return this.client.invite(this.name, receiver, reason);
+ };
+
+ XmppRoom.prototype.directInvite = function(receiver, reason) {
+ return this.client.directInvite(this.name, receiver, reason, this.password);
+ };
+
+ XmppRoom.prototype.configure = function(handler_cb) {
+ return this.client.configure(this.name, handler_cb);
+ };
+
+ XmppRoom.prototype.cancelConfigure = function() {
+ return this.client.cancelConfigure(this.name);
+ };
+
+ XmppRoom.prototype.saveConfiguration = function(configarray) {
+ return this.client.saveConfiguration(this.name, configarray);
+ };
+
+ XmppRoom.prototype.queryOccupants = function(success_cb, error_cb) {
+ return this.client.queryOccupants(this.name, success_cb, error_cb);
+ };
+
+ XmppRoom.prototype.setTopic = function(topic) {
+ return this.client.setTopic(this.name, topic);
+ };
+
+ XmppRoom.prototype.modifyRole = function(nick, role, reason, success_cb, error_cb) {
+ return this.client.modifyRole(this.name, nick, role, reason, success_cb, error_cb);
+ };
+
+ XmppRoom.prototype.kick = function(nick, reason, handler_cb, error_cb) {
+ return this.client.kick(this.name, nick, reason, handler_cb, error_cb);
+ };
+
+ XmppRoom.prototype.voice = function(nick, reason, handler_cb, error_cb) {
+ return this.client.voice(this.name, nick, reason, handler_cb, error_cb);
+ };
+
+ XmppRoom.prototype.mute = function(nick, reason, handler_cb, error_cb) {
+ return this.client.mute(this.name, nick, reason, handler_cb, error_cb);
+ };
+
+ XmppRoom.prototype.op = function(nick, reason, handler_cb, error_cb) {
+ return this.client.op(this.name, nick, reason, handler_cb, error_cb);
+ };
+
+ XmppRoom.prototype.deop = function(nick, reason, handler_cb, error_cb) {
+ return this.client.deop(this.name, nick, reason, handler_cb, error_cb);
+ };
+
+ XmppRoom.prototype.modifyAffiliation = function(jid, affiliation, reason, success_cb, error_cb) {
+ return this.client.modifyAffiliation(this.name, jid, affiliation, reason, success_cb, error_cb);
+ };
+
+ XmppRoom.prototype.ban = function(jid, reason, handler_cb, error_cb) {
+ return this.client.ban(this.name, jid, reason, handler_cb, error_cb);
+ };
+
+ XmppRoom.prototype.member = function(jid, reason, handler_cb, error_cb) {
+ return this.client.member(this.name, jid, reason, handler_cb, error_cb);
+ };
+
+ XmppRoom.prototype.revoke = function(jid, reason, handler_cb, error_cb) {
+ return this.client.revoke(this.name, jid, reason, handler_cb, error_cb);
+ };
+
+ XmppRoom.prototype.owner = function(jid, reason, handler_cb, error_cb) {
+ return this.client.owner(this.name, jid, reason, handler_cb, error_cb);
+ };
+
+ XmppRoom.prototype.admin = function(jid, reason, handler_cb, error_cb) {
+ return this.client.admin(this.name, jid, reason, handler_cb, error_cb);
+ };
+
+ XmppRoom.prototype.changeNick = function(nick) {
+ this.nick = nick;
+ return this.client.changeNick(this.name, nick);
+ };
+
+ XmppRoom.prototype.setStatus = function(show, status) {
+ return this.client.setStatus(this.name, this.nick, show, status);
+ };
+
+ /*Function
+ Adds a handler to the MUC room.
+ Parameters:
+ (String) handler_type - 'message', 'presence' or 'roster'.
+ (Function) handler - The handler function.
+ Returns:
+ id - the id of handler.
+ */
+
+ XmppRoom.prototype.addHandler = function(handler_type, handler) {
+ var id;
+ id = this._handler_ids++;
+ switch (handler_type) {
+ case 'presence':
+ this._presence_handlers[id] = handler;
+ break;
+ case 'message':
+ this._message_handlers[id] = handler;
+ break;
+ case 'roster':
+ this._roster_handlers[id] = handler;
+ break;
+ default:
+ this._handler_ids--;
+ return null;
+ }
+ return id;
+ };
+
+ /*Function
+ Removes a handler from the MUC room.
+ This function takes ONLY ids returned by the addHandler function
+ of this room. passing handler ids returned by connection.addHandler
+ may brake things!
+ Parameters:
+ (number) id - the id of the handler
+ */
+
+ XmppRoom.prototype.removeHandler = function(id) {
+ delete this._presence_handlers[id];
+ delete this._message_handlers[id];
+ return delete this._roster_handlers[id];
+ };
+
+ /*Function
+ Creates and adds an Occupant to the Room Roster.
+ Parameters:
+ (Object) data - the data the Occupant is filled with
+ Returns:
+ occ - the created Occupant.
+ */
+
+ XmppRoom.prototype._addOccupant = function(data) {
+ var occ;
+ occ = new Occupant(data, this);
+ this.roster[occ.nick] = occ;
+ return occ;
+ };
+
+ /*Function
+ The standard handler that managed the Room Roster.
+ Parameters:
+ (Object) pres - the presence stanza containing user information
+ */
+
+ XmppRoom.prototype._roomRosterHandler = function(pres) {
+ var data, handler, id, newnick, nick, _ref;
+ data = XmppRoom._parsePresence(pres);
+ nick = data.nick;
+ newnick = data.newnick || null;
+ switch (data.type) {
+ case 'error':
+ return;
+ case 'unavailable':
+ if (newnick) {
+ data.nick = newnick;
+ if (this.roster[nick] && this.roster[newnick]) {
+ this.roster[nick].update(this.roster[newnick]);
+ this.roster[newnick] = this.roster[nick];
+ }
+ if (this.roster[nick] && !this.roster[newnick]) {
+ this.roster[newnick] = this.roster[nick].update(data);
+ }
+ }
+ delete this.roster[nick];
+ break;
+ default:
+ if (this.roster[nick]) {
+ this.roster[nick].update(data);
+ } else {
+ this._addOccupant(data);
+ }
+ }
+ _ref = this._roster_handlers;
+ for (id in _ref) {
+ handler = _ref[id];
+ if (!handler(this.roster, this)) delete this._roster_handlers[id];
+ }
+ return true;
+ };
+
+ /*Function
+ Parses a presence stanza
+ Parameters:
+ (Object) data - the data extracted from the presence stanza
+ */
+
+ XmppRoom._parsePresence = function(pres) {
+ var a, c, c2, data, _i, _j, _len, _len2, _ref, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7, _ref8;
+ data = {};
+ a = pres.attributes;
+ data.nick = Strophe.getResourceFromJid(a.from.textContent);
+ data.type = ((_ref = a.type) != null ? _ref.textContent : void 0) || null;
+ data.states = [];
+ _ref2 = pres.childNodes;
+ for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
+ c = _ref2[_i];
+ switch (c.nodeName) {
+ case "status":
+ data.status = c.textContent || null;
+ break;
+ case "show":
+ data.show = c.textContent || null;
+ break;
+ case "x":
+ a = c.attributes;
+ if (((_ref3 = a.xmlns) != null ? _ref3.textContent : void 0) === Strophe.NS.MUC_USER) {
+ _ref4 = c.childNodes;
+ for (_j = 0, _len2 = _ref4.length; _j < _len2; _j++) {
+ c2 = _ref4[_j];
+ switch (c2.nodeName) {
+ case "item":
+ a = c2.attributes;
+ data.affiliation = ((_ref5 = a.affiliation) != null ? _ref5.textContent : void 0) || null;
+ data.role = ((_ref6 = a.role) != null ? _ref6.textContent : void 0) || null;
+ data.jid = ((_ref7 = a.jid) != null ? _ref7.textContent : void 0) || null;
+ data.newnick = ((_ref8 = a.nick) != null ? _ref8.textContent : void 0) || null;
+ break;
+ case "status":
+ if (c2.attributes.code) {
+ data.states.push(c2.attributes.code.textContent);
+ }
+ }
+ }
+ }
}
- return room_nick;
+ }
+ return data;
+ };
+
+ return XmppRoom;
+
+ })();
+
+ RoomConfig = (function() {
+
+ function RoomConfig(info) {
+ this.parse = __bind(this.parse, this); if (info != null) this.parse(info);
}
-});
+
+ RoomConfig.prototype.parse = function(result) {
+ var attr, attrs, child, field, identity, query, _i, _j, _k, _len, _len2, _len3, _ref;
+ query = result.getElementsByTagName("query")[0].childNodes;
+ this.identities = [];
+ this.features = [];
+ this.x = [];
+ for (_i = 0, _len = query.length; _i < _len; _i++) {
+ child = query[_i];
+ attrs = child.attributes;
+ switch (child.nodeName) {
+ case "identity":
+ identity = {};
+ for (_j = 0, _len2 = attrs.length; _j < _len2; _j++) {
+ attr = attrs[_j];
+ identity[attr.name] = attr.textContent;
+ }
+ this.identities.push(identity);
+ break;
+ case "feature":
+ this.features.push(attrs["var"].textContent);
+ break;
+ case "x":
+ attrs = child.childNodes[0].attributes;
+ if ((!attrs["var"].textContent === 'FORM_TYPE') || (!attrs.type.textContent === 'hidden')) {
+ break;
+ }
+ _ref = child.childNodes;
+ for (_k = 0, _len3 = _ref.length; _k < _len3; _k++) {
+ field = _ref[_k];
+ if (!(!field.attributes.type)) continue;
+ attrs = field.attributes;
+ this.x.push({
+ "var": attrs["var"].textContent,
+ label: attrs.label.textContent || "",
+ value: field.firstChild.textContent || ""
+ });
+ }
+ }
+ }
+ return {
+ "identities": this.identities,
+ "features": this.features,
+ "x": this.x
+ };
+ };
+
+ return RoomConfig;
+
+ })();
+
+ Occupant = (function() {
+
+ function Occupant(data, room) {
+ this.room = room;
+ this.update = __bind(this.update, this);
+ this.admin = __bind(this.admin, this);
+ this.owner = __bind(this.owner, this);
+ this.revoke = __bind(this.revoke, this);
+ this.member = __bind(this.member, this);
+ this.ban = __bind(this.ban, this);
+ this.modifyAffiliation = __bind(this.modifyAffiliation, this);
+ this.deop = __bind(this.deop, this);
+ this.op = __bind(this.op, this);
+ this.mute = __bind(this.mute, this);
+ this.voice = __bind(this.voice, this);
+ this.kick = __bind(this.kick, this);
+ this.modifyRole = __bind(this.modifyRole, this);
+ this.update(data);
+ }
+
+ Occupant.prototype.modifyRole = function(role, reason, success_cb, error_cb) {
+ return this.room.modifyRole(this.nick, role, reason, success_cb, error_cb);
+ };
+
+ Occupant.prototype.kick = function(reason, handler_cb, error_cb) {
+ return this.room.kick(this.nick, reason, handler_cb, error_cb);
+ };
+
+ Occupant.prototype.voice = function(reason, handler_cb, error_cb) {
+ return this.room.voice(this.nick, reason, handler_cb, error_cb);
+ };
+
+ Occupant.prototype.mute = function(reason, handler_cb, error_cb) {
+ return this.room.mute(this.nick, reason, handler_cb, error_cb);
+ };
+
+ Occupant.prototype.op = function(reason, handler_cb, error_cb) {
+ return this.room.op(this.nick, reason, handler_cb, error_cb);
+ };
+
+ Occupant.prototype.deop = function(reason, handler_cb, error_cb) {
+ return this.room.deop(this.nick, reason, handler_cb, error_cb);
+ };
+
+ Occupant.prototype.modifyAffiliation = function(affiliation, reason, success_cb, error_cb) {
+ return this.room.modifyAffiliation(this.jid, affiliation, reason, success_cb, error_cb);
+ };
+
+ Occupant.prototype.ban = function(reason, handler_cb, error_cb) {
+ return this.room.ban(this.jid, reason, handler_cb, error_cb);
+ };
+
+ Occupant.prototype.member = function(reason, handler_cb, error_cb) {
+ return this.room.member(this.jid, reason, handler_cb, error_cb);
+ };
+
+ Occupant.prototype.revoke = function(reason, handler_cb, error_cb) {
+ return this.room.revoke(this.jid, reason, handler_cb, error_cb);
+ };
+
+ Occupant.prototype.owner = function(reason, handler_cb, error_cb) {
+ return this.room.owner(this.jid, reason, handler_cb, error_cb);
+ };
+
+ Occupant.prototype.admin = function(reason, handler_cb, error_cb) {
+ return this.room.admin(this.jid, reason, handler_cb, error_cb);
+ };
+
+ Occupant.prototype.update = function(data) {
+ this.nick = data.nick || null;
+ this.affiliation = data.affiliation || null;
+ this.role = data.role || null;
+ this.jid = data.jid || null;
+ this.status = data.status || null;
+ this.show = data.show || null;
+ return this;
+ };
+
+ return Occupant;
+
+ })();
+
+}).call(this);
/*
mustache.js — Logic-less templates in JavaScript
@@ -4393,6 +5624,478 @@ $.fn._t = function(str, params) {
})(jQuery);
/*
+ Copyright 2010, François de Metz <francois@2metz.fr>
+*/
+
+/**
+ * Disco Strophe Plugin
+ * Implement http://xmpp.org/extensions/xep-0030.html
+ * TODO: manage node hierarchies, and node on info request
+ */
+Strophe.addConnectionPlugin('disco',
+{
+ _connection: null,
+ _identities : [],
+ _features : [],
+ _items : [],
+ /** Function: init
+ * Plugin init
+ *
+ * Parameters:
+ * (Strophe.Connection) conn - Strophe connection
+ */
+ init: function(conn)
+ {
+ this._connection = conn;
+ this._identities = [];
+ this._features = [];
+ this._items = [];
+ // disco info
+ conn.addHandler(this._onDiscoInfo.bind(this), Strophe.NS.DISCO_INFO, 'iq', 'get', null, null);
+ // disco items
+ conn.addHandler(this._onDiscoItems.bind(this), Strophe.NS.DISCO_ITEMS, 'iq', 'get', null, null);
+ },
+ /** Function: addIdentity
+ * See http://xmpp.org/registrar/disco-categories.html
+ * Parameters:
+ * (String) category - category of identity (like client, automation, etc ...)
+ * (String) type - type of identity (like pc, web, bot , etc ...)
+ * (String) name - name of identity in natural language
+ * (String) lang - lang of name parameter
+ *
+ * Returns:
+ * Boolean
+ */
+ addIdentity: function(category, type, name, lang)
+ {
+ for (var i=0; i<this._identities.length; i++)
+ {
+ if (this._identities[i].category == category &&
+ this._identities[i].type == type &&
+ this._identities[i].name == name &&
+ this._identities[i].lang == lang)
+ {
+ return false;
+ }
+ }
+ this._identities.push({category: category, type: type, name: name, lang: lang});
+ return true;
+ },
+ /** Function: addFeature
+ *
+ * Parameters:
+ * (String) var_name - feature name (like jabber:iq:version)
+ *
+ * Returns:
+ * boolean
+ */
+ addFeature: function(var_name)
+ {
+ for (var i=0; i<this._features.length; i++)
+ {
+ if (this._features[i] == var_name)
+ return false;
+ }
+ this._features.push(var_name);
+ return true;
+ },
+ /** Function: removeFeature
+ *
+ * Parameters:
+ * (String) var_name - feature name (like jabber:iq:version)
+ *
+ * Returns:
+ * boolean
+ */
+ removeFeature: function(var_name)
+ {
+ for (var i=0; i<this._features.length; i++)
+ {
+ if (this._features[i] === var_name){
+ this._features.splice(i,1)
+ return true;
+ }
+ }
+ return false;
+ },
+ /** Function: addItem
+ *
+ * Parameters:
+ * (String) jid
+ * (String) name
+ * (String) node
+ * (Function) call_back
+ *
+ * Returns:
+ * boolean
+ */
+ addItem: function(jid, name, node, call_back)
+ {
+ if (node && !call_back)
+ return false;
+ this._items.push({jid: jid, name: name, node: node, call_back: call_back});
+ return true;
+ },
+ /** Function: info
+ * Info query
+ *
+ * Parameters:
+ * (Function) call_back
+ * (String) jid
+ * (String) node
+ */
+ info: function(jid, node, success, error, timeout)
+ {
+ var attrs = {xmlns: Strophe.NS.DISCO_INFO};
+ if (node)
+ attrs.node = node;
+
+ var info = $iq({from:this._connection.jid,
+ to:jid, type:'get'}).c('query', attrs);
+ this._connection.sendIQ(info, success, error, timeout);
+ },
+ /** Function: items
+ * Items query
+ *
+ * Parameters:
+ * (Function) call_back
+ * (String) jid
+ * (String) node
+ */
+ items: function(jid, node, success, error, timeout)
+ {
+ var attrs = {xmlns: Strophe.NS.DISCO_ITEMS};
+ if (node)
+ attrs.node = node;
+
+ var items = $iq({from:this._connection.jid,
+ to:jid, type:'get'}).c('query', attrs);
+ this._connection.sendIQ(items, success, error, timeout);
+ },
+
+ /** PrivateFunction: _buildIQResult
+ */
+ _buildIQResult: function(stanza, query_attrs)
+ {
+ var id = stanza.getAttribute('id');
+ var from = stanza.getAttribute('from');
+ var iqresult = $iq({type: 'result', id: id});
+
+ if (from !== null) {
+ iqresult.attrs({to: from});
+ }
+
+ return iqresult.c('query', query_attrs);
+ },
+
+ /** PrivateFunction: _onDiscoInfo
+ * Called when receive info request
+ */
+ _onDiscoInfo: function(stanza)
+ {
+ var node = stanza.getElementsByTagName('query')[0].getAttribute('node');
+ var attrs = {xmlns: Strophe.NS.DISCO_INFO};
+ if (node)
+ {
+ attrs.node = node;
+ }
+ var iqresult = this._buildIQResult(stanza, attrs);
+ for (var i=0; i<this._identities.length; i++)
+ {
+ var attrs = {category: this._identities[i].category,
+ type : this._identities[i].type};
+ if (this._identities[i].name)
+ attrs.name = this._identities[i].name;
+ if (this._identities[i].lang)
+ attrs['xml:lang'] = this._identities[i].lang;
+ iqresult.c('identity', attrs).up();
+ }
+ for (var i=0; i<this._features.length; i++)
+ {
+ iqresult.c('feature', {'var':this._features[i]}).up();
+ }
+ this._connection.send(iqresult.tree());
+ return true;
+ },
+ /** PrivateFunction: _onDiscoItems
+ * Called when receive items request
+ */
+ _onDiscoItems: function(stanza)
+ {
+ var query_attrs = {xmlns: Strophe.NS.DISCO_ITEMS};
+ var node = stanza.getElementsByTagName('query')[0].getAttribute('node');
+ if (node)
+ {
+ query_attrs.node = node;
+ var items = [];
+ for (var i = 0; i < this._items.length; i++)
+ {
+ if (this._items[i].node == node)
+ {
+ items = this._items[i].call_back(stanza);
+ break;
+ }
+ }
+ }
+ else
+ {
+ var items = this._items;
+ }
+ var iqresult = this._buildIQResult(stanza, query_attrs);
+ for (var i = 0; i < items.length; i++)
+ {
+ var attrs = {jid: items[i].jid};
+ if (items[i].name)
+ attrs.name = items[i].name;
+ if (items[i].node)
+ attrs.node = items[i].node;
+ iqresult.c('item', attrs).up();
+ }
+ this._connection.send(iqresult.tree());
+ return true;
+ }
+});
+/**
+ * Entity Capabilities (XEP-0115)
+ *
+ * Depends on disco plugin.
+ *
+ * See: http://xmpp.org/extensions/xep-0115.html
+ *
+ * Authors:
+ * - Michael Weibel <michael.weibel@gmail.com>
+ *
+ * Copyright:
+ * - Michael Weibel <michael.weibel@gmail.com>
+ */
+
+ Strophe.addConnectionPlugin('caps', {
+ /** Constant: HASH
+ * Hash used
+ *
+ * Currently only sha-1 is supported.
+ */
+ HASH: 'sha-1',
+ /** Variable: node
+ * Client which is being used.
+ *
+ * Can be overwritten as soon as Strophe has been initialized.
+ */
+ node: 'http://strophe.im/strophejs/',
+ /** PrivateVariable: _ver
+ * Own generated version string
+ */
+ _ver: '',
+ /** PrivateVariable: _connection
+ * Strophe connection
+ */
+ _connection: null,
+ /** PrivateVariable: _knownCapabilities
+ * A hashtable containing version-strings and their capabilities, serialized
+ * as string.
+ *
+ * TODO: Maybe those caps shouldn't be serialized.
+ */
+ _knownCapabilities: {},
+ /** PrivateVariable: _jidVerIndex
+ * A hashtable containing jids and their versions for better lookup of capabilities.
+ */
+ _jidVerIndex: {},
+
+ /** Function: init
+ * Initialize plugin:
+ * - Add caps namespace
+ * - Add caps feature to disco plugin
+ * - Add handler for caps stanzas
+ *
+ * Parameters:
+ * (Strophe.Connection) conn - Strophe connection
+ */
+ init: function(conn) {
+ this._connection = conn;
+
+ Strophe.addNamespace('CAPS', 'http://jabber.org/protocol/caps');
+
+ if (!this._connection.disco) {
+ throw "Caps plugin requires the disco plugin to be installed.";
+ }
+
+ this._connection.disco.addFeature(Strophe.NS.CAPS);
+ this._connection.addHandler(this._delegateCapabilities.bind(this), Strophe.NS.CAPS);
+ },
+
+ /** Function: generateCapsAttrs
+ * Returns the attributes for generating the "c"-stanza containing the own version
+ *
+ * Returns:
+ * (Object) - attributes
+ */
+ generateCapsAttrs: function() {
+ return {
+ 'xmlns': Strophe.NS.CAPS,
+ 'hash': this.HASH,
+ 'node': this.node,
+ 'ver': this.generateVer()
+ };
+ },
+
+ /** Function: generateVer
+ * Returns the base64 encoded version string (encoded itself with sha1)
+ *
+ * Returns:
+ * (String) - version
+ */
+ generateVer: function() {
+ if (this._ver !== "") {
+ return this._ver;
+ }
+
+ var ver = "",
+ identities = this._connection.disco._identities.sort(this._sortIdentities),
+ identitiesLen = identities.length,
+ features = this._connection.disco._features.sort(),
+ featuresLen = features.length;
+ for(var i = 0; i < identitiesLen; i++) {
+ var curIdent = identities[i];
+ ver += curIdent.category + "/" + curIdent.type + "/" + curIdent.lang + "/" + curIdent.name + "<";
+ }
+ for(var i = 0; i < featuresLen; i++) {
+ ver += features[i] + '<';
+ }
+
+ this._ver = b64_sha1(ver);
+ return this._ver;
+ },
+
+ /** Function: getCapabilitiesByJid
+ * Returns serialized capabilities of a jid (if available).
+ * Otherwise null.
+ *
+ * Parameters:
+ * (String) jid - Jabber id
+ *
+ * Returns:
+ * (String|null) - capabilities, serialized; or null when not available.
+ */
+ getCapabilitiesByJid: function(jid) {
+ if (this._jidVerIndex[jid]) {
+ return this._knownCapabilities[this._jidVerIndex[jid]];
+ }
+ return null;
+ },
+
+ /** PrivateFunction: _delegateCapabilities
+ * Checks if the version has already been saved.
+ * If yes: do nothing.
+ * If no: Request capabilities
+ *
+ * Parameters:
+ * (Strophe.Builder) stanza - Stanza
+ *
+ * Returns:
+ * (Boolean)
+ */
+ _delegateCapabilities: function(stanza) {
+ var from = stanza.getAttribute('from'),
+ c = stanza.querySelector('c'),
+ ver = c.getAttribute('ver'),
+ node = c.getAttribute('node');
+ if (!this._knownCapabilities[ver]) {
+ return this._requestCapabilities(from, node, ver);
+ } else {
+ this._jidVerIndex[from] = ver;
+ }
+ if (!this._jidVerIndex[from] || !this._jidVerIndex[from] !== ver) {
+ this._jidVerIndex[from] = ver;
+ }
+ return true;
+ },
+
+ /** PrivateFunction: _requestCapabilities
+ * Requests capabilities from the one which sent the caps-info stanza.
+ * This is done using disco info.
+ *
+ * Additionally, it registers a handler for handling the reply.
+ *
+ * Parameters:
+ * (String) to - Destination jid
+ * (String) node - Node attribute of the caps-stanza
+ * (String) ver - Version of the caps-stanza
+ *
+ * Returns:
+ * (Boolean) - true
+ */
+ _requestCapabilities: function(to, node, ver) {
+ if (to !== this._connection.jid) {
+ var id = this._connection.disco.info(to, node + '#' + ver);
+ this._connection.addHandler(this._handleDiscoInfoReply.bind(this), Strophe.NS.DISCO_INFO, 'iq', 'result', id, to);
+ }
+ return true;
+ },
+
+ /** PrivateFunction: _handleDiscoInfoReply
+ * Parses the disco info reply and adds the version & it's capabilities to the _knownCapabilities variable.
+ * Additionally, it adds the jid & the version to the _jidVerIndex variable for a better lookup.
+ *
+ * Parameters:
+ * (Strophe.Builder) stanza - Disco info stanza
+ *
+ * Returns:
+ * (Boolean) - false, to automatically remove the handler.
+ */
+ _handleDiscoInfoReply: function(stanza) {
+ var query = stanza.querySelector('query'),
+ node = query.getAttribute('node').split('#'),
+ ver = node[1],
+ from = stanza.getAttribute('from');
+ if (!this._knownCapabilities[ver]) {
+ var childNodes = query.childNodes,
+ childNodesLen = childNodes.length;
+ this._knownCapabilities[ver] = [];
+ for(var i = 0; i < childNodesLen; i++) {
+ var node = childNodes[i];
+ this._knownCapabilities[ver].push({name: node.nodeName, attributes: node.attributes});
+ }
+ this._jidVerIndex[from] = ver;
+ } else if (!this._jidVerIndex[from] || !this._jidVerIndex[from] !== ver) {
+ this._jidVerIndex[from] = ver;
+ }
+ return false;
+ },
+
+ /** PrivateFunction: _sortIdentities
+ * Sorts two identities according the sorting requirements in XEP-0115.
+ *
+ * Parameters:
+ * (Object) a - Identity a
+ * (Object) b - Identity b
+ *
+ * Returns:
+ * (Integer) - 1, 0 or -1; according to which one's greater.
+ */
+ _sortIdentities: function(a, b) {
+ if (a.category > b.category) {
+ return 1;
+ }
+ if (a.category < b.category) {
+ return -1;
+ }
+ if (a.type > b.type) {
+ return 1;
+ }
+ if (a.type < b.type) {
+ return -1;
+ }
+ if (a.lang > b.lang) {
+ return 1;
+ }
+ if (a.lang < b.lang) {
+ return -1;
+ }
+ return 0;
+ }
+ });
+/*
* Date Format 1.2.3
* (c) 2007-2009 Steven Levithan <stevenlevithan.com>
* MIT license
diff --git a/libs/libs.min.js b/libs/libs.min.js
index 2e65006..134c95b 100644
--- a/libs/libs.min.js
+++ b/libs/libs.min.js
@@ -1 +1 @@
-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
+var Base64=(function(){var a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";var b={encode:function(e){var c="";var n,l,h;var m,j,g,f;var d=0;do{n=e.charCodeAt(d++);l=e.charCodeAt(d++);h=e.charCodeAt(d++);m=n>>2;j=((n&3)<<4)|(l>>4);g=((l&15)<<2)|(h>>6);f=h&63;if(isNaN(l)){g=f=64}else{if(isNaN(h)){f=64}}c=c+a.charAt(m)+a.charAt(j)+a.charAt(g)+a.charAt(f)}while(d<e.length);return c},decode:function(e){var c="";var n,l,h;var m,j,g,f;var d=0;e=e.replace(/[^A-Za-z0-9\+\/\=]/g,"");do{m=a.indexOf(e.charAt(d++));j=a.indexOf(e.charAt(d++));g=a.indexOf(e.charAt(d++));f=a.indexOf(e.charAt(d++));n=(m<<2)|(j>>4);l=((j&15)<<4)|(g>>2);h=((g&3)<<6)|f;c=c+String.fromCharCode(n);if(g!=64){c=c+String.fromCharCode(l)}if(f!=64){c=c+String.fromCharCode(h)}}while(d<e.length);return c}};return b})();var hexcase=0;var b64pad="=";var chrsz=8;function hex_sha1(a){return binb2hex(core_sha1(str2binb(a),a.length*chrsz))}function b64_sha1(a){return binb2b64(core_sha1(str2binb(a),a.length*chrsz))}function str_sha1(a){return binb2str(core_sha1(str2binb(a),a.length*chrsz))}function hex_hmac_sha1(a,b){return binb2hex(core_hmac_sha1(a,b))}function b64_hmac_sha1(a,b){return binb2b64(core_hmac_sha1(a,b))}function str_hmac_sha1(a,b){return binb2str(core_hmac_sha1(a,b))}function sha1_vm_test(){return hex_sha1("abc")=="a9993e364706816aba3e25717850c26c9cd0d89d"}function core_sha1(y,p){y[p>>5]|=128<<(24-p%32);y[((p+64>>9)<<4)+15]=p;var z=new Array(80);var v=1732584193;var u=-271733879;var s=-1732584194;var r=271733878;var q=-1009589776;var m,h,A,o,n,l,g,f;for(m=0;m<y.length;m+=16){o=v;n=u;l=s;g=r;f=q;for(h=0;h<80;h++){if(h<16){z[h]=y[m+h]}else{z[h]=rol(z[h-3]^z[h-8]^z[h-14]^z[h-16],1)}A=safe_add(safe_add(rol(v,5),sha1_ft(h,u,s,r)),safe_add(safe_add(q,z[h]),sha1_kt(h)));q=r;r=s;s=rol(u,30);u=v;v=A}v=safe_add(v,o);u=safe_add(u,n);s=safe_add(s,l);r=safe_add(r,g);q=safe_add(q,f)}return[v,u,s,r,q]}function sha1_ft(e,a,g,f){if(e<20){return(a&g)|((~a)&f)}if(e<40){return a^g^f}if(e<60){return(a&g)|(a&f)|(g&f)}return a^g^f}function sha1_kt(a){return(a<20)?1518500249:(a<40)?1859775393:(a<60)?-1894007588:-899497514}function core_hmac_sha1(c,f){var e=str2binb(c);if(e.length>16){e=core_sha1(e,c.length*chrsz)}var a=new Array(16),d=new Array(16);for(var b=0;b<16;b++){a[b]=e[b]^909522486;d[b]=e[b]^1549556828}var g=core_sha1(a.concat(str2binb(f)),512+f.length*chrsz);return core_sha1(d.concat(g),512+160)}function safe_add(a,d){var c=(a&65535)+(d&65535);var b=(a>>16)+(d>>16)+(c>>16);return(b<<16)|(c&65535)}function rol(a,b){return(a<<b)|(a>>>(32-b))}function str2binb(d){var c=[];var a=(1<<chrsz)-1;for(var b=0;b<d.length*chrsz;b+=chrsz){c[b>>5]|=(d.charCodeAt(b/chrsz)&a)<<(32-chrsz-b%32)}return c}function binb2str(c){var d="";var a=(1<<chrsz)-1;for(var b=0;b<c.length*32;b+=chrsz){d+=String.fromCharCode((c[b>>5]>>>(32-chrsz-b%32))&a)}return d}function binb2hex(c){var b=hexcase?"0123456789ABCDEF":"0123456789abcdef";var d="";for(var a=0;a<c.length*4;a++){d+=b.charAt((c[a>>2]>>((3-a%4)*8+4))&15)+b.charAt((c[a>>2]>>((3-a%4)*8))&15)}return d}function binb2b64(d){var c="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";var f="";var e,a;for(var b=0;b<d.length*4;b+=3){e=(((d[b>>2]>>8*(3-b%4))&255)<<16)|(((d[b+1>>2]>>8*(3-(b+1)%4))&255)<<8)|((d[b+2>>2]>>8*(3-(b+2)%4))&255);for(a=0;a<4;a++){if(b*8+a*6>d.length*32){f+=b64pad}else{f+=c.charAt((e>>6*(3-a))&63)}}}return f}var MD5=(function(){var q=0;var a="";var n=8;var l=function(t,w){var v=(t&65535)+(w&65535);var u=(t>>16)+(w>>16)+(v>>16);return(u<<16)|(v&65535)};var p=function(t,u){return(t<<u)|(t>>>(32-u))};var b=function(w){var v=[];var t=(1<<n)-1;for(var u=0;u<w.length*n;u+=n){v[u>>5]|=(w.charCodeAt(u/n)&t)<<(u%32)}return v};var g=function(v){var w="";var t=(1<<n)-1;for(var u=0;u<v.length*32;u+=n){w+=String.fromCharCode((v[u>>5]>>>(u%32))&t)}return w};var s=function(v){var u=q?"0123456789ABCDEF":"0123456789abcdef";var w="";for(var t=0;t<v.length*4;t++){w+=u.charAt((v[t>>2]>>((t%4)*8+4))&15)+u.charAt((v[t>>2]>>((t%4)*8))&15)}return w};var r=function(w){var v="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";var y="";var x,t;for(var u=0;u<w.length*4;u+=3){x=(((w[u>>2]>>8*(u%4))&255)<<16)|(((w[u+1>>2]>>8*((u+1)%4))&255)<<8)|((w[u+2>>2]>>8*((u+2)%4))&255);for(t=0;t<4;t++){if(u*8+t*6>w.length*32){y+=a}else{y+=v.charAt((x>>6*(3-t))&63)}}}return y};var d=function(A,w,v,u,z,y){return l(p(l(l(w,A),l(u,y)),z),v)};var m=function(w,v,B,A,u,z,y){return d((v&B)|((~v)&A),w,v,u,z,y)};var c=function(w,v,B,A,u,z,y){return d((v&A)|(B&(~A)),w,v,u,z,y)};var o=function(w,v,B,A,u,z,y){return d(v^B^A,w,v,u,z,y)};var j=function(w,v,B,A,u,z,y){return d(B^(v|(~A)),w,v,u,z,y)};var f=function(E,z){E[z>>5]|=128<<((z)%32);E[(((z+64)>>>9)<<4)+14]=z;var D=1732584193;var C=-271733879;var B=-1732584194;var A=271733878;var y,w,v,t;for(var u=0;u<E.length;u+=16){y=D;w=C;v=B;t=A;D=m(D,C,B,A,E[u+0],7,-680876936);A=m(A,D,C,B,E[u+1],12,-389564586);B=m(B,A,D,C,E[u+2],17,606105819);C=m(C,B,A,D,E[u+3],22,-1044525330);D=m(D,C,B,A,E[u+4],7,-176418897);A=m(A,D,C,B,E[u+5],12,1200080426);B=m(B,A,D,C,E[u+6],17,-1473231341);C=m(C,B,A,D,E[u+7],22,-45705983);D=m(D,C,B,A,E[u+8],7,1770035416);A=m(A,D,C,B,E[u+9],12,-1958414417);B=m(B,A,D,C,E[u+10],17,-42063);C=m(C,B,A,D,E[u+11],22,-1990404162);D=m(D,C,B,A,E[u+12],7,1804603682);A=m(A,D,C,B,E[u+13],12,-40341101);B=m(B,A,D,C,E[u+14],17,-1502002290);C=m(C,B,A,D,E[u+15],22,1236535329);D=c(D,C,B,A,E[u+1],5,-165796510);A=c(A,D,C,B,E[u+6],9,-1069501632);B=c(B,A,D,C,E[u+11],14,643717713);C=c(C,B,A,D,E[u+0],20,-373897302);D=c(D,C,B,A,E[u+5],5,-701558691);A=c(A,D,C,B,E[u+10],9,38016083);B=c(B,A,D,C,E[u+15],14,-660478335);C=c(C,B,A,D,E[u+4],20,-405537848);D=c(D,C,B,A,E[u+9],5,568446438);A=c(A,D,C,B,E[u+14],9,-1019803690);B=c(B,A,D,C,E[u+3],14,-187363961);C=c(C,B,A,D,E[u+8],20,1163531501);D=c(D,C,B,A,E[u+13],5,-1444681467);A=c(A,D,C,B,E[u+2],9,-51403784);B=c(B,A,D,C,E[u+7],14,1735328473);C=c(C,B,A,D,E[u+12],20,-1926607734);D=o(D,C,B,A,E[u+5],4,-378558);A=o(A,D,C,B,E[u+8],11,-2022574463);B=o(B,A,D,C,E[u+11],16,1839030562);C=o(C,B,A,D,E[u+14],23,-35309556);D=o(D,C,B,A,E[u+1],4,-1530992060);A=o(A,D,C,B,E[u+4],11,1272893353);B=o(B,A,D,C,E[u+7],16,-155497632);C=o(C,B,A,D,E[u+10],23,-1094730640);D=o(D,C,B,A,E[u+13],4,681279174);A=o(A,D,C,B,E[u+0],11,-358537222);B=o(B,A,D,C,E[u+3],16,-722521979);C=o(C,B,A,D,E[u+6],23,76029189);D=o(D,C,B,A,E[u+9],4,-640364487);A=o(A,D,C,B,E[u+12],11,-421815835);B=o(B,A,D,C,E[u+15],16,530742520);C=o(C,B,A,D,E[u+2],23,-995338651);D=j(D,C,B,A,E[u+0],6,-198630844);A=j(A,D,C,B,E[u+7],10,1126891415);B=j(B,A,D,C,E[u+14],15,-1416354905);C=j(C,B,A,D,E[u+5],21,-57434055);D=j(D,C,B,A,E[u+12],6,1700485571);A=j(A,D,C,B,E[u+3],10,-1894986606);B=j(B,A,D,C,E[u+10],15,-1051523);C=j(C,B,A,D,E[u+1],21,-2054922799);D=j(D,C,B,A,E[u+8],6,1873313359);A=j(A,D,C,B,E[u+15],10,-30611744);B=j(B,A,D,C,E[u+6],15,-1560198380);C=j(C,B,A,D,E[u+13],21,1309151649);D=j(D,C,B,A,E[u+4],6,-145523070);A=j(A,D,C,B,E[u+11],10,-1120210379);B=j(B,A,D,C,E[u+2],15,718787259);C=j(C,B,A,D,E[u+9],21,-343485551);D=l(D,y);C=l(C,w);B=l(B,v);A=l(A,t)}return[D,C,B,A]};var e=function(v,y){var x=b(v);if(x.length>16){x=f(x,v.length*n)}var t=new Array(16),w=new Array(16);for(var u=0;u<16;u++){t[u]=x[u]^909522486;w[u]=x[u]^1549556828}var z=f(t.concat(b(y)),512+y.length*n);return f(w.concat(z),512+128)};var h={hexdigest:function(t){return s(f(b(t),t.length*n))},b64digest:function(t){return r(f(b(t),t.length*n))},hash:function(t){return g(f(b(t),t.length*n))},hmac_hexdigest:function(t,u){return s(e(t,u))},hmac_b64digest:function(t,u){return r(e(t,u))},hmac_hash:function(t,u){return g(e(t,u))},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:"8861616",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",XHTML_IM:"http://jabber.org/protocol/xhtml-im",XHTML:"http://www.w3.org/1999/xhtml"},XHTML:{tags:["a","blockquote","br","cite","em","img","li","ol","p","span","strong","ul","body"],attributes:{a:["href"],blockquote:["style"],br:[],cite:["style"],em:[],img:["src","alt","style","height","width"],li:["style"],ol:["style"],p:["style"],span:["style"],strong:[],ul:["style"],body:[]},css:["background-color","color","font-family","font-size","font-style","font-weight","margin-left","margin-right","text-align","text-decoration"],validTag:function(g){for(var h=0;h<e.XHTML.tags.length;h++){if(g==e.XHTML.tags[h]){return true}}return false},validAttribute:function(g,j){if(typeof e.XHTML.attributes[g]!=="undefined"&&e.XHTML.attributes[g].length>0){for(var h=0;h<e.XHTML.attributes[g].length;h++){if(j==e.XHTML.attributes[g][h]){return true}}}return false},validCSS:function(h){for(var g=0;g<e.XHTML.css.length;g++){if(h==e.XHTML.css[g]){return true}}return false}},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,FRAGMENT:11},TIMEOUT:1.1,SECONDARY_TIMEOUT:0.1,forEachChild:function(l,m,j){var h,g;for(h=0;h<l.childNodes.length;h++){g=l.childNodes[h];if(g.nodeType==e.ElementType.NORMAL&&(!m||this.isTagEqual(g,m))){j(g)}}},isTagEqual:function(h,g){return h.tagName.toLowerCase()==g.toLowerCase()},_xmlGenerator:null,_makeGenerator:function(){var g;if(document.implementation.createDocument===undefined||document.implementation.createDocument&&document.documentMode&&document.documentMode<10){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 l=["Msxml2.DOMDocument.6.0","Msxml2.DOMDocument.5.0","Msxml2.DOMDocument.4.0","MSXML2.DOMDocument.3.0","MSXML2.DOMDocument","MSXML.DOMDocument","Microsoft.XMLDOM"];for(var j=0;j<l.length;j++){if(h===null){try{h=new ActiveXObject(l[j])}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)},xmlHtmlNode:function(g){if(window.DOMParser){parser=new DOMParser();node=parser.parseFromString(g,"text/xml")}else{node=new ActiveXObject("Microsoft.XMLDOM");node.async="false";node.loadXML(g)}return node},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 e.xmlescape(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},createHtml:function(n){var q,m,o,x,l,w,s,t,v,p,r,h,g;if(n.nodeType==e.ElementType.NORMAL){x=n.nodeName.toLowerCase();if(e.XHTML.validTag(x)){try{m=e.xmlElement(x);for(q=0;q<e.XHTML.attributes[x].length;q++){l=e.XHTML.attributes[x][q];w=n.getAttribute(l);if(typeof w=="undefined"||w===null||w===""||w===false||w===0){continue}if(l=="style"&&typeof w=="object"){if(typeof w.cssText!="undefined"){w=w.cssText}}if(l=="style"){s=[];t=w.split(";");for(o=0;o<t.length;o++){v=t[o].split(":");p=v[0].replace(/^\s*/,"").replace(/\s*$/,"").toLowerCase();if(e.XHTML.validCSS(p)){r=v[1].replace(/^\s*/,"").replace(/\s*$/,"");s.push(p+": "+r)}}if(s.length>0){w=s.join("; ");m.setAttribute(l,w)}}else{m.setAttribute(l,w)}}for(q=0;q<n.childNodes.length;q++){m.appendChild(e.createHtml(n.childNodes[q]))}}catch(u){m=e.xmlTextNode("")}}else{m=e.xmlGenerator().createDocumentFragment();for(q=0;q<n.childNodes.length;q++){m.appendChild(e.createHtml(n.childNodes[q]))}}}else{if(n.nodeType==e.ElementType.FRAGMENT){m=e.xmlGenerator().createDocumentFragment();for(q=0;q<n.childNodes.length;q++){m.appendChild(e.createHtml(n.childNodes[q]))}}else{if(n.nodeType==e.ElementType.TEXT){m=e.xmlTextNode(n.nodeValue)}}}return m},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 j=h.split("@");j.splice(0,1);return j.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 m=j.nodeName;var h,l;if(j.getAttribute("_realname")){m=j.getAttribute("_realname")}g="<"+m;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,"&gt;").replace(/</g,"&lt;")+"'"}}if(j.childNodes.length>0){g+=">";for(h=0;h<j.childNodes.length;h++){l=j.childNodes[h];switch(l.nodeType){case e.ElementType.NORMAL:g+=e.serialize(l);break;case e.ElementType.TEXT:g+=l.nodeValue;break;case e.ElementType.CDATA:g+="<![CDATA["+l.nodeValue+"]]>"}}g+="</"+m+">"}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,j){var l=e.xmlElement(h,g,j);this.node.appendChild(l);if(!j){this.node=l}return this},cnode:function(j){var m=e.xmlGenerator();try{var h=(m.importNode!==undefined)}catch(l){var h=false}var g=h?m.importNode(j,true):e.copyElement(j);this.node.appendChild(g);this.node=g;return this},t:function(g){var h=e.xmlTextNode(g);this.node.appendChild(h);return this},h:function(h){var g=document.createElement("body");g.innerHTML=h;var j=e.createHtml(g);while(j.childNodes.length>0){this.node.appendChild(j.childNodes[0])}return this}};e.Handler=function(m,l,h,j,o,n,g){this.handler=m;this.ns=l;this.name=h;this.type=j;this.id=o;this.options=g||{matchbare:false};if(!this.options.matchBare){this.options.matchBare=false}if(this.options.matchBare){this.from=n?e.getBareJidFromJid(n):null}else{this.from=n}this.user=true};e.Handler.prototype={isMatch:function(h){var l;var j=null;if(this.options.matchBare){j=e.getBareJidFromJid(h.getAttribute("from"))}else{j=h.getAttribute("from")}l=false;if(!this.ns){l=true}else{var g=this;e.forEachChild(h,null,function(m){if(m.getAttribute("xmlns")==g.ns){l=true}});l=l||h.getAttribute("xmlns")==this.ns}if(l&&(!this.name||e.isTagEqual(h,this.name))&&(!this.type||h.getAttribute("type")==this.type)&&(!this.id||h.getAttribute("id")==this.id)&&(!this.from||j==this.from)){return true}return false},run:function(h){var g=null;try{g=this.handler(h)}catch(j){if(j.sourceURL){e.fatal("error: "+this.handler+" "+j.sourceURL+":"+j.line+" - "+j.name+": "+j.message)}else{if(j.fileName){if(typeof(console)!="undefined"){console.trace();console.error(this.handler," - error - ",j,j.message)}e.fatal("error: "+this.handler+" "+j.fileName+":"+j.lineNumber+" - "+j.name+": "+j.message)}else{e.fatal("error: "+j.message+"\n"+j.stack)}}throw j}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(j,h,g,l){this.id=++e._requestId;this.xmlData=j;this.data=e.serialize(j);this.origFunc=h;this.func=h;this.rid=g;this.date=NaN;this.sends=l||0;this.abort=false;this.dead=null;this.age=function(){if(!this.date){return 0}var m=new Date();return(m-this.date)/1000};this.timeDead=function(){if(!this.dead){return 0}var m=new Date();return(m-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.domain=null;this.rid=Math.floor(Math.random()*4294967295);this.sid=null;this.streamId=null;this.features=null;this._sasl_data=[];this.do_session=false;this.do_bind=false;this.timedHandlers=[];this.handlers=[];this.removeTimeds=[];this.removeHandlers=[];this.addTimeds=[];this.addHandlers=[];this._authentication={};this._idleTimeout=null;this._disconnectTimeout=null;this.do_authentication=true;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.maxRetries=5;this._idleTimeout=setTimeout(this._onIdle.bind(this),100);for(var h in e._connectionPlugins){if(e._connectionPlugins.hasOwnProperty(h)){var l=e._connectionPlugins[h];var j=function(){};j.prototype=l;this[h]=new j();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._authentication={};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(j,m,p,o,n,h){this.jid=j;this.pass=m;this.connect_callback=p;this.disconnecting=false;this.connected=false;this.authenticated=false;this.errors=0;this.wait=o||this.wait;this.hold=n||this.hold;this.domain=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});if(h){g.attrs({route:h})}this._changeConnectStatus(e.Status.CONNECTING,null);var l=this._connect_callback||this._connect_cb;this._connect_callback=null;this._requests.push(new e.Request(g.tree(),this._onRequestStateChange.bind(this,l.bind(this)),g.tree().getAttribute("rid")));this._throttledRequestHandler()},attach:function(j,g,l,o,n,m,h){this.jid=j;this.sid=g;this.rid=l;this.connect_callback=o;this.domain=e.getDomainFromJid(this.jid);this.authenticated=true;this.connected=true;this.wait=n||this.wait;this.hold=m||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(l,p,g,m){var n=null;var j=this;if(typeof(l.tree)==="function"){l=l.tree()}var o=l.getAttribute("id");if(!o){o=this.getUniqueId("sendIQ");l.setAttribute("id",o)}var h=this.addHandler(function(r){if(n){j.deleteTimedHandler(n)}var q=r.getAttribute("type");if(q=="result"){if(p){p(r)}}else{if(q=="error"){if(g){g(r)}}else{throw {name:"StropheError",message:"Got bad IQ type of "+q}}}},null,"iq",null,o);if(m){n=this.addTimedHandler(m,function(){j.deleteHandler(h);if(g){g(null)}return false})}this.send(l);return o},_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(j,h){var g=new e.TimedHandler(j,h);this.addTimeds.push(g);return g},deleteTimedHandler:function(g){this.removeTimeds.push(g)},addHandler:function(n,m,j,l,p,o,h){var g=new e.Handler(n,m,j,l,p,o,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,n){for(var h in e._connectionPlugins){if(e._connectionPlugins.hasOwnProperty(h)){var l=this[h];if(l.statusChanged){try{l.statusChanged(g,n)}catch(j){e.error(""+h+" plugin caused an exception changing status: "+j)}}}}if(this.connect_callback){try{this.connect_callback(g,n)}catch(m){e.error("User connection callback caused an exception: "+m)}}},_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(l){var q=this._requests[l];var t=-1;try{if(q.xhr.readyState==4){t=q.xhr.status}}catch(o){e.error("caught an error in _requests["+l+"], reqStatus: "+t)}if(typeof(t)=="undefined"){t=-1}if(q.sends>this.maxRetries){this._onDisconnectTimeout();return}var j=q.age();var h=(!isNaN(j)&&j>Math.floor(e.TIMEOUT*this.wait));var m=(q.dead!==null&&q.timeDead()>Math.floor(e.SECONDARY_TIMEOUT*this.wait));var s=(q.xhr.readyState==4&&(t<1||t>=500));if(h||m||s){if(m){e.error("Request "+this._requests[l].id+" timed out (secondary), restarting")}q.abort=true;q.xhr.abort();q.xhr.onreadystatechange=function(){};this._requests[l]=new e.Request(q.xmlData,q.origFunc,q.rid,q.sends);q=this._requests[l]}if(q.xhr.readyState===0){e.debug("request id "+q.id+"."+q.sends+" posting");try{var g=!("sync" in this&&this.sync===true);q.xhr.open("POST",this.service,g)}catch(p){e.error("XHR open failed.");if(!this.connected){this._changeConnectStatus(e.Status.CONNFAIL,"bad-service")}this.disconnect();return}var r=function(){q.date=new Date();q.xhr.send(q.data)};if(q.sends>1){var n=Math.min(Math.floor(e.TIMEOUT*this.wait),Math.pow(q.sends,3))*1000;setTimeout(r,n)}else{r()}q.sends++;if(this.xmlOutput!==e.Connection.prototype.xmlOutput){this.xmlOutput(q.xmlData)}if(this.rawOutput!==e.Connection.prototype.rawOutput){this.rawOutput(q.data)}}else{e.debug("_processRequest: "+(l===0?"first":"second")+" request has readyState of "+q.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(l,j){e.debug("request id "+j.id+"."+j.sends+" state changed to "+j.xhr.readyState);if(j.abort){j.abort=false;return}var h;if(j.xhr.readyState==4){h=0;try{h=j.xhr.status}catch(m){}if(typeof(h)=="undefined"){h=0}if(this.disconnecting){if(h>=400){this._hitError(h);return}}var g=(this._requests[0]==j);var n=(this._requests[1]==j);if((h>0&&h<500)||j.sends>5){this._removeRequest(j);e.debug("request id "+j.id+" should now be removed")}if(h==200){if(n||(g&&this._requests.length>0&&this._requests[0].age()>Math.floor(e.SECONDARY_TIMEOUT*this.wait))){this._restartRequest(0)}e.debug("request id "+j.id+"."+j.sends+" got 200");l(j);this.errors=0}else{e.error("request id "+j.id+"."+j.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)||j.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(q){try{var g=q.getResponse()}catch(o){if(o!="parsererror"){throw o}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 m,j;while(this.removeHandlers.length>0){j=this.removeHandlers.pop();m=this.handlers.indexOf(j);if(m>=0){this.handlers.splice(m,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 p,l;if(h!==null&&h=="terminate"){if(this.disconnecting){return}p=g.getAttribute("condition");l=g.getElementsByTagName("conflict");if(p!==null){if(p=="remote-stream-error"&&l.length>0){p="conflict"}this._changeConnectStatus(e.Status.CONNFAIL,p)}else{this._changeConnectStatus(e.Status.CONNFAIL,"unknown")}this.disconnect();return}var n=this;e.forEachChild(g,null,function(v){var s,t;t=n.handlers;n.handlers=[];for(s=0;s<t.length;s++){var r=t[s];try{if(r.isMatch(v)&&(n.authenticated||!r.user)){if(r.run(v)){n.handlers.push(r)}}else{n.handlers.push(r)}}catch(u){}}})},_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(g,n){e.info("_connect_cb was called");this.connected=true;var y=g.getResponse();if(!y){return}if(this.xmlInput!==e.Connection.prototype.xmlInput){this.xmlInput(y)}if(this.rawInput!==e.Connection.prototype.rawInput){this.rawInput(e.serialize(y))}var z=y.getAttribute("type");var p,u;if(z!==null&&z=="terminate"){p=y.getAttribute("condition");u=y.getElementsByTagName("conflict");if(p!==null){if(p=="remote-stream-error"&&u.length>0){p="conflict"}this._changeConnectStatus(e.Status.CONNFAIL,p)}else{this._changeConnectStatus(e.Status.CONNFAIL,"unknown")}return}if(!this.sid){this.sid=y.getAttribute("sid")}if(!this.stream_id){this.stream_id=y.getAttribute("authid")}var h=y.getAttribute("requests");if(h){this.window=parseInt(h,10)}var r=y.getAttribute("hold");if(r){this.hold=parseInt(r,10)}var v=y.getAttribute("wait");if(v){this.wait=parseInt(v,10)}this._authentication.sasl_scram_sha1=false;this._authentication.sasl_plain=false;this._authentication.sasl_digest_md5=false;this._authentication.sasl_anonymous=false;this._authentication.legacy_auth=false;var w=y.getElementsByTagName("stream:features").length>0;if(!w){w=y.getElementsByTagName("features").length>0}var q=y.getElementsByTagName("mechanism");var x,t,j,l,s=false;if(w&&q.length>0){var m=0;for(x=0;x<q.length;x++){t=e.getText(q[x]);if(t=="SCRAM-SHA-1"){this._authentication.sasl_scram_sha1=true}else{if(t=="DIGEST-MD5"){this._authentication.sasl_digest_md5=true}else{if(t=="PLAIN"){this._authentication.sasl_plain=true}else{if(t=="ANONYMOUS"){this._authentication.sasl_anonymous=true}else{m++}}}}}this._authentication.legacy_auth=y.getElementsByTagName("auth").length>0;s=this._authentication.legacy_auth||m<q.length}if(!s){n=n||this._connect_cb;var o=this._buildBody();this._requests.push(new e.Request(o.tree(),this._onRequestStateChange.bind(this,n.bind(this)),o.tree().getAttribute("rid")));this._throttledRequestHandler();return}if(this.do_authentication!==false){this.authenticate()}},authenticate:function(){if(e.getNodeFromJid(this.jid)===null&&this._authentication.sasl_anonymous){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(this._authentication.sasl_scram_sha1){var h=MD5.hexdigest(Math.random()*1234567890);var g="n="+e.getNodeFromJid(this.jid);g+=",r=";g+=h;this._sasl_data.cnonce=h;this._sasl_data["client-first-message-bare"]=g;g="n,,"+g;this._changeConnectStatus(e.Status.AUTHENTICATING,null);this._sasl_challenge_handler=this._addSysHandler(this._sasl_scram_challenge_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:"SCRAM-SHA-1"}).t(Base64.encode(g)).tree())}else{if(this._authentication.sasl_digest_md5){this._changeConnectStatus(e.Status.AUTHENTICATING,null);this._sasl_challenge_handler=this._addSysHandler(this._sasl_digest_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(this._authentication.sasl_plain){g=unescape(encodeURIComponent(e.getBareJidFromJid(this.jid)));g=g+"\u0000";g=g+unescape(encodeURIComponent(e.getNodeFromJid(this.jid)));g=g+"\u0000";g=g+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);hashed_auth_str=Base64.encode(g);this.send(c("auth",{xmlns:e.NS.SASL,mechanism:"PLAIN"}).t(hashed_auth_str).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_digest_challenge1_cb:function(m){var h=/([a-z]+)=("[^"]+"|[^,"]+)(?:,|$)/;var s=Base64.decode(e.getText(m));var t=MD5.hexdigest(""+(Math.random()*1234567890));var p="";var u=null;var q="";var g="";var o;this.deleteHandler(this._sasl_failure_handler);while(s.match(h)){o=s.match(h);s=s.replace(o[0],"");o[2]=o[2].replace(/^"(.+)"$/,"$1");switch(o[1]){case"realm":p=o[2];break;case"nonce":q=o[2];break;case"qop":g=o[2];break;case"host":u=o[2];break}}var n="xmpp/"+this.domain;if(u!==null){n=n+"/"+u}var l=MD5.hash(unescape(encodeURIComponent(e.getNodeFromJid(this.jid)))+":"+p+":"+this.pass)+":"+q+":"+t;var j="AUTHENTICATE:"+n;var r="";r+="username="+this._quote(unescape(encodeURIComponent(e.getNodeFromJid(this.jid))))+",";r+="realm="+this._quote(p)+",";r+="nonce="+this._quote(q)+",";r+="cnonce="+this._quote(t)+",";r+='nc="00000001",';r+='qop="auth",';r+="digest-uri="+this._quote(n)+",";r+="response="+this._quote(MD5.hexdigest(MD5.hexdigest(l)+":"+q+":00000001:"+t+":auth:"+MD5.hexdigest(j)))+",";r+='charset="utf-8"';this._sasl_challenge_handler=this._addSysHandler(this._sasl_digest_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(r)).tree());return false},_quote:function(g){return'"'+g.replace(/\\/g,"\\\\").replace(/"/g,'\\"')+'"'},_sasl_digest_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},_sasl_scram_challenge_cb:function(j){var q,o,s,n,l,g;var p,w,m;var r="c=biws,";var t=Base64.decode(e.getText(j));var u=this._sasl_data["client-first-message-bare"]+","+t+",";var v=this._sasl_data.cnonce;var h=/([a-z]+)=([^,]+)(,|$)/;this.deleteHandler(this._sasl_failure_handler);while(t.match(h)){matches=t.match(h);t=t.replace(matches[0],"");switch(matches[1]){case"r":q=matches[2];break;case"s":o=matches[2];break;case"i":s=matches[2];break}}if(!(q.substr(0,v.length)===v)){this._sasl_data=[];return this._sasl_failure_cb(null)}r+="r="+q;u+=r;o=Base64.decode(o);o+="\0\0\0\1";n=g=core_hmac_sha1(this.pass,o);for(i=1;i<s;i++){l=core_hmac_sha1(this.pass,binb2str(g));for(k=0;k<5;k++){n[k]^=l[k]}g=l}n=binb2str(n);p=core_hmac_sha1(n,"Client Key");w=str_hmac_sha1(n,"Server Key");m=core_hmac_sha1(str_sha1(binb2str(p)),u);this._sasl_data["server-signature"]=b64_hmac_sha1(w,u);for(k=0;k<5;k++){p[k]^=m[k]}r+=",p="+Base64.encode(binb2str(p));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(r)).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(h){if(this._sasl_data["server-signature"]){var g;var l=Base64.decode(e.getText(h));var j=/([a-z]+)=([^,]+)(,|$)/;matches=l.match(j);if(matches[1]=="v"){g=matches[2]}if(g!=this._sasl_data["server-signature"]){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._sasl_data=[];return this._sasl_failure_cb(null)}}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,l;for(g=0;g<h.childNodes.length;g++){l=h.childNodes[g];if(l.nodeName=="bind"){this.do_bind=true}if(l.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.");var h=g.getElementsByTagName("conflict"),m;if(h.length>0){m="conflict"}this._changeConnectStatus(e.Status.AUTHFAIL,m);return false}var l=g.getElementsByTagName("bind");var j;if(l.length>0){j=l[0].getElementsByTagName("jid");if(j.length>0){this.jid=e.getText(j[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(j,h){var g=new e.TimedHandler(j,h);g.user=false;this.addTimeds.push(g);return g},_addSysHandler:function(m,l,h,j,n){var g=new e.Handler(m,l,h,j,n);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,m,o,l;while(this.addTimeds.length>0){this.timedHandlers.push(this.addTimeds.pop())}while(this.removeTimeds.length>0){m=this.removeTimeds.pop();j=this.timedHandlers.indexOf(m);if(j>=0){this.timedHandlers.splice(j,1)}}var h=new Date().getTime();l=[];for(j=0;j<this.timedHandlers.length;j++){m=this.timedHandlers[j];if(this.authenticated||!m.user){o=m.lastCalled+m.period;if(o-h<=0){if(m.run()){l.push(m)}}else{l.push(m)}}}this.timedHandlers=l;var g,n;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){n=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(n>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]});(function(){var b,c,d,a=function(e,f){return function(){return e.apply(f,arguments)}};Strophe.addConnectionPlugin("muc",{_connection:null,rooms:[],init:function(e){this._connection=e;this._muc_handler=null;Strophe.addNamespace("MUC_OWNER",Strophe.NS.MUC+"#owner");Strophe.addNamespace("MUC_ADMIN",Strophe.NS.MUC+"#admin");Strophe.addNamespace("MUC_USER",Strophe.NS.MUC+"#user");return Strophe.addNamespace("MUC_ROOMCONF",Strophe.NS.MUC+"#roomconfig")},join:function(e,f,l,j,o,p){var g,m,n,h=this;m=this.test_append_nick(e,f);g=$pres({from:this._connection.jid,to:m}).c("x",{xmlns:Strophe.NS.MUC});if(p!=null){g.cnode(Strophe.xmlElement("password",[],p))}if(this._muc_handler==null){this._muc_handler=this._connection.addHandler(function(r){var A,B,s,q,t,z,u,w,v,y;A=r.getAttribute("from");t=A.split("/")[0];if(!h.rooms[t]){return true}e=h.rooms[t];s={};if(r.nodeName==="message"){s=e._message_handlers}else{if(r.nodeName==="presence"){w=r.getElementsByTagName("x");if(w.length>0){for(v=0,y=w.length;v<y;v++){z=w[v];u=z.getAttribute("xmlns");if(u&&u.match(Strophe.NS.MUC)){s=e._presence_handlers;break}}}}}for(q in s){B=s[q];if(!B(r,e)){delete s[q]}}return true})}if((n=this.rooms)[e]==null){n[e]=new d(this,e,f,p)}if(j){this.rooms[e].addHandler("presence",j)}if(l){this.rooms[e].addHandler("message",l)}if(o){this.rooms[e].addHandler("roster",o)}return this._connection.send(g)},leave:function(m,e,h,f){var g,j,l;delete this.rooms[m];if(this.rooms.length===0){this._connection.deleteHandler(this._muc_handler);this._muc_handler=null}l=this.test_append_nick(m,e);j=this._connection.getUniqueId();g=$pres({type:"unavailable",id:j,from:this._connection.jid,to:l});if(f!=null){g.c("status",f)}if(h!=null){this._connection.addHandler(h,null,"presence",null,j)}this._connection.send(g);return j},message:function(e,h,o,f,l){var j,g,n,m;m=this.test_append_nick(e,h);l=l||(h!=null?"chat":"groupchat");g=this._connection.getUniqueId();j=$msg({to:m,from:this._connection.jid,type:l,id:g}).c("body",{xmlns:Strophe.NS.CLIENT}).t(o);j.up();if(f!=null){j.c("html",{xmlns:Strophe.NS.XHTML_IM}).c("body",{xmlns:Strophe.NS.XHTML}).h(f);if(j.node.childNodes.length===0){n=j.node.parentNode;j.up().up();j.node.removeChild(n)}else{j.up().up()}}j.c("x",{xmlns:"jabber:x:event"}).c("composing");this._connection.send(j);return g},groupchat:function(g,e,f){return this.message(g,null,e,f)},invite:function(j,f,h){var g,e;e=this._connection.getUniqueId();g=$msg({from:this._connection.jid,to:j,id:e}).c("x",{xmlns:Strophe.NS.MUC_USER}).c("invite",{to:f});if(h!=null){g.c("reason",h)}this._connection.send(g);return e},directInvite:function(m,h,l,f){var e,j,g;g=this._connection.getUniqueId();e={xmlns:"jabber:x:conference",jid:m};if(l!=null){e.reason=l}if(f!=null){e.password=f}j=$msg({from:this._connection.jid,to:h,id:g}).c("x",e);this._connection.send(j);return g},queryOccupants:function(j,e,g){var f,h;f={xmlns:Strophe.NS.DISCO_ITEMS};h=$iq({from:this._connection.jid,to:j,type:"get"}).c("query",f);return this._connection.sendIQ(h,e,g)},configure:function(g,f){var e,j,h;e=$iq({to:g,type:"get"}).c("query",{xmlns:Strophe.NS.MUC_OWNER});h=e.tree();j=this._connection.sendIQ(h);if(f!=null){this._connection.addHandler(function(l){f(l);return false},Strophe.NS.MUC_OWNER,"iq",null,j)}return j},cancelConfigure:function(f){var e,g;e=$iq({to:f,type:"set"}).c("query",{xmlns:Strophe.NS.MUC_OWNER}).c("x",{xmlns:"jabber:x:data",type:"cancel"});g=e.tree();return this._connection.sendIQ(g)},saveConfiguration:function(l,j){var g,f,m,h,e;f=$iq({to:l,type:"set"}).c("query",{xmlns:Strophe.NS.MUC_OWNER}).c("x",{xmlns:"jabber:x:data",type:"submit"});for(h=0,e=j.length;h<e;h++){g=j[h];f.cnode(g).up()}m=f.tree();return this._connection.sendIQ(m)},createInstantRoom:function(f){var e;e=$iq({to:f,type:"set"}).c("query",{xmlns:Strophe.NS.MUC_OWNER}).c("x",{xmlns:"jabber:x:data",type:"submit"});return this._connection.sendIQ(e.tree())},setTopic:function(f,e){var g;g=$msg({to:f,from:this._connection.jid,type:"groupchat"}).c("subject",{xmlns:"jabber:client"}).t(e);return this._connection.send(g.tree())},_modifyPrivilege:function(l,g,j,e,f){var h;h=$iq({to:l,type:"set"}).c("query",{xmlns:Strophe.NS.MUC_ADMIN}).cnode(g.node);if(j!=null){h.c("reason",j)}return this._connection.sendIQ(h.tree(),e,f)},modifyRole:function(l,e,m,j,f,h){var g;g=$build("item",{nick:e,role:m});return this._modifyPrivilege(l,g,j,f,h)},kick:function(j,e,h,f,g){return this.modifyRole(j,e,"none",h,f,g)},voice:function(j,e,h,f,g){return this.modifyRole(j,e,"participant",h,f,g)},mute:function(j,e,h,f,g){return this.modifyRole(j,e,"visitor",h,f,g)},op:function(j,e,h,f,g){return this.modifyRole(j,e,"moderator",h,f,g)},deop:function(j,e,h,f,g){return this.modifyRole(j,e,"participant",h,f,g)},modifyAffiliation:function(m,f,g,l,e,j){var h;h=$build("item",{jid:f,affiliation:g});return this._modifyPrivilege(m,h,l,e,j)},ban:function(j,f,h,e,g){return this.modifyAffiliation(j,f,"outcast",h,e,g)},member:function(j,f,h,e,g){return this.modifyAffiliation(j,f,"member",h,e,g)},revoke:function(j,f,h,e,g){return this.modifyAffiliation(j,f,"none",h,e,g)},owner:function(j,f,h,e,g){return this.modifyAffiliation(j,f,"owner",h,e,g)},admin:function(j,f,h,e,g){return this.modifyAffiliation(j,f,"admin",h,e,g)},changeNick:function(h,e){var f,g;g=this.test_append_nick(h,e);f=$pres({from:this._connection.jid,to:g,id:this._connection.getUniqueId()});return this._connection.send(f.tree())},setStatus:function(l,g,f,e){var h,j;j=this.test_append_nick(l,g);h=$pres({from:this._connection.jid,to:j});if(f!=null){h.c("show",f).up()}if(e!=null){h.c("status",e)}return this._connection.send(h.tree())},listRooms:function(g,e){var f;f=$iq({to:g,from:this._connection.jid,type:"get"}).c("query",{xmlns:Strophe.NS.DISCO_ITEMS});return this._connection.sendIQ(f,e)},test_append_nick:function(f,e){return f+(e!=null?"/"+(Strophe.escapeNode(e)):"")}});d=(function(){e.prototype.roster={};e.prototype._message_handlers={};e.prototype._presence_handlers={};e.prototype._roster_handlers={};e.prototype._handler_ids=0;function e(g,j,f,h){this.client=g;this.name=j;this.nick=f;this.password=h;this._roomRosterHandler=a(this._roomRosterHandler,this);this._addOccupant=a(this._addOccupant,this);if(g.muc){this.client=g.muc}this.name=Strophe.getBareJidFromJid(j);this.client.rooms[this.name]=this;this.addHandler("presence",this._roomRosterHandler)}e.prototype.join=function(f,g){if(!this.client.rooms[this.name]){return this.client.join(this.name,this.nick,f,g,this.password)}};e.prototype.leave=function(f,g){this.client.leave(this.name,this.nick,f,g);return delete this.client.rooms[this.name]};e.prototype.message=function(f,h,j,g){return this.client.message(this.name,f,h,j,g)};e.prototype.groupchat=function(f,g){return this.client.groupchat(this.name,f,g)};e.prototype.invite=function(f,g){return this.client.invite(this.name,f,g)};e.prototype.directInvite=function(f,g){return this.client.directInvite(this.name,f,g,this.password)};e.prototype.configure=function(f){return this.client.configure(this.name,f)};e.prototype.cancelConfigure=function(){return this.client.cancelConfigure(this.name)};e.prototype.saveConfiguration=function(f){return this.client.saveConfiguration(this.name,f)};e.prototype.queryOccupants=function(f,g){return this.client.queryOccupants(this.name,f,g)};e.prototype.setTopic=function(f){return this.client.setTopic(this.name,f)};e.prototype.modifyRole=function(f,l,j,g,h){return this.client.modifyRole(this.name,f,l,j,g,h)};e.prototype.kick=function(f,j,g,h){return this.client.kick(this.name,f,j,g,h)};e.prototype.voice=function(f,j,g,h){return this.client.voice(this.name,f,j,g,h)};e.prototype.mute=function(f,j,g,h){return this.client.mute(this.name,f,j,g,h)};e.prototype.op=function(f,j,g,h){return this.client.op(this.name,f,j,g,h)};e.prototype.deop=function(f,j,g,h){return this.client.deop(this.name,f,j,g,h)};e.prototype.modifyAffiliation=function(g,h,l,f,j){return this.client.modifyAffiliation(this.name,g,h,l,f,j)};e.prototype.ban=function(g,j,f,h){return this.client.ban(this.name,g,j,f,h)};e.prototype.member=function(g,j,f,h){return this.client.member(this.name,g,j,f,h)};e.prototype.revoke=function(g,j,f,h){return this.client.revoke(this.name,g,j,f,h)};e.prototype.owner=function(g,j,f,h){return this.client.owner(this.name,g,j,f,h)};e.prototype.admin=function(g,j,f,h){return this.client.admin(this.name,g,j,f,h)};e.prototype.changeNick=function(f){this.nick=f;return this.client.changeNick(this.name,f)};e.prototype.setStatus=function(g,f){return this.client.setStatus(this.name,this.nick,g,f)};e.prototype.addHandler=function(g,f){var h;h=this._handler_ids++;switch(g){case"presence":this._presence_handlers[h]=f;break;case"message":this._message_handlers[h]=f;break;case"roster":this._roster_handlers[h]=f;break;default:this._handler_ids--;return null}return h};e.prototype.removeHandler=function(f){delete this._presence_handlers[f];delete this._message_handlers[f];return delete this._roster_handlers[f]};e.prototype._addOccupant=function(g){var f;f=new b(g,this);this.roster[f.nick]=f;return f};e.prototype._roomRosterHandler=function(m){var l,h,n,g,f,j;l=e._parsePresence(m);f=l.nick;g=l.newnick||null;switch(l.type){case"error":return;case"unavailable":if(g){l.nick=g;if(this.roster[f]&&this.roster[g]){this.roster[f].update(this.roster[g]);this.roster[g]=this.roster[f]}if(this.roster[f]&&!this.roster[g]){this.roster[g]=this.roster[f].update(l)}}delete this.roster[f];break;default:if(this.roster[f]){this.roster[f].update(l)}else{this._addOccupant(l)}}j=this._roster_handlers;for(n in j){h=j[n];if(!h(this.roster,this)){delete this._roster_handlers[n]}}return true};e._parsePresence=function(t){var w,v,r,u,q,p,x,h,s,o,n,m,l,j,g,f;u={};w=t.attributes;u.nick=Strophe.getResourceFromJid(w.from.textContent);u.type=((s=w.type)!=null?s.textContent:void 0)||null;u.states=[];o=t.childNodes;for(q=0,x=o.length;q<x;q++){v=o[q];switch(v.nodeName){case"status":u.status=v.textContent||null;break;case"show":u.show=v.textContent||null;break;case"x":w=v.attributes;if(((n=w.xmlns)!=null?n.textContent:void 0)===Strophe.NS.MUC_USER){m=v.childNodes;for(p=0,h=m.length;p<h;p++){r=m[p];switch(r.nodeName){case"item":w=r.attributes;u.affiliation=((l=w.affiliation)!=null?l.textContent:void 0)||null;u.role=((j=w.role)!=null?j.textContent:void 0)||null;u.jid=((g=w.jid)!=null?g.textContent:void 0)||null;u.newnick=((f=w.nick)!=null?f.textContent:void 0)||null;break;case"status":if(r.attributes.code){u.states.push(r.attributes.code.textContent)}}}}}}return u};return e})();c=(function(){function e(f){this.parse=a(this.parse,this);if(f!=null){this.parse(f)}}e.prototype.parse=function(u){var o,t,h,s,p,q,m,l,j,r,g,f,n;q=u.getElementsByTagName("query")[0].childNodes;this.identities=[];this.features=[];this.x=[];for(m=0,r=q.length;m<r;m++){h=q[m];t=h.attributes;switch(h.nodeName){case"identity":p={};for(l=0,g=t.length;l<g;l++){o=t[l];p[o.name]=o.textContent}this.identities.push(p);break;case"feature":this.features.push(t["var"].textContent);break;case"x":t=h.childNodes[0].attributes;if((!t["var"].textContent==="FORM_TYPE")||(!t.type.textContent==="hidden")){break}n=h.childNodes;for(j=0,f=n.length;j<f;j++){s=n[j];if(!(!s.attributes.type)){continue}t=s.attributes;this.x.push({"var":t["var"].textContent,label:t.label.textContent||"",value:s.firstChild.textContent||""})}}}return{identities:this.identities,features:this.features,x:this.x}};return e})();b=(function(){function e(f,g){this.room=g;this.update=a(this.update,this);this.admin=a(this.admin,this);this.owner=a(this.owner,this);this.revoke=a(this.revoke,this);this.member=a(this.member,this);this.ban=a(this.ban,this);this.modifyAffiliation=a(this.modifyAffiliation,this);this.deop=a(this.deop,this);this.op=a(this.op,this);this.mute=a(this.mute,this);this.voice=a(this.voice,this);this.kick=a(this.kick,this);this.modifyRole=a(this.modifyRole,this);this.update(f)}e.prototype.modifyRole=function(j,h,f,g){return this.room.modifyRole(this.nick,j,h,f,g)};e.prototype.kick=function(h,f,g){return this.room.kick(this.nick,h,f,g)};e.prototype.voice=function(h,f,g){return this.room.voice(this.nick,h,f,g)};e.prototype.mute=function(h,f,g){return this.room.mute(this.nick,h,f,g)};e.prototype.op=function(h,f,g){return this.room.op(this.nick,h,f,g)};e.prototype.deop=function(h,f,g){return this.room.deop(this.nick,h,f,g)};e.prototype.modifyAffiliation=function(g,j,f,h){return this.room.modifyAffiliation(this.jid,g,j,f,h)};e.prototype.ban=function(h,f,g){return this.room.ban(this.jid,h,f,g)};e.prototype.member=function(h,f,g){return this.room.member(this.jid,h,f,g)};e.prototype.revoke=function(h,f,g){return this.room.revoke(this.jid,h,f,g)};e.prototype.owner=function(h,f,g){return this.room.owner(this.jid,h,f,g)};e.prototype.admin=function(h,f,g){return this.room.admin(this.jid,h,f,g)};e.prototype.update=function(f){this.nick=f.nick||null;this.affiliation=f.affiliation||null;this.role=f.role||null;this.jid=f.jid||null;this.status=f.status||null;this.show=f.show||null;return this};return e})()}).call(this);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,j,g,l){var m=f.find(g,c);if(j=="^"){if(!m||f.is_array(m)&&m.length===0){return f.render(l,c,b,true)}else{return""}}else{if(j=="#"){if(f.is_array(m)){return f.map(m,function(n){return f.render(l,f.create_context(n),b,true)}).join("")}else{if(f.is_object(m)){return f.render(l,f.create_context(m),b,true)}else{if(typeof m==="function"){return m.call(c,l,function(n){return f.render(n,c,b,true)})}else{if(m){return f.render(l,c,b,true)}else{return""}}}}}}})},render_tags:function(l,b,d,f){var e=this;var j=function(){return new RegExp(e.otag+"(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?"+e.ctag+"+","g")};var g=j();var h=function(p,n,o){switch(n){case"!":return"";case"=":e.set_delimiters(o);g=j();return"";case">":return e.render_partial(o,b,d);case"{":return e.find(o,b);default:return e.escape(e.find(o,b))}};var m=l.split("\n");for(var c=0;c<m.length;c++){m[c]=m[c].replace(g,h,this);if(!f){this.send(m[c])}}if(f){return m.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);Strophe.addConnectionPlugin("disco",{_connection:null,_identities:[],_features:[],_items:[],init:function(a){this._connection=a;this._identities=[];this._features=[];this._items=[];a.addHandler(this._onDiscoInfo.bind(this),Strophe.NS.DISCO_INFO,"iq","get",null,null);a.addHandler(this._onDiscoItems.bind(this),Strophe.NS.DISCO_ITEMS,"iq","get",null,null)},addIdentity:function(d,c,a,e){for(var b=0;b<this._identities.length;b++){if(this._identities[b].category==d&&this._identities[b].type==c&&this._identities[b].name==a&&this._identities[b].lang==e){return false}}this._identities.push({category:d,type:c,name:a,lang:e});return true},addFeature:function(b){for(var a=0;a<this._features.length;a++){if(this._features[a]==b){return false}}this._features.push(b);return true},removeFeature:function(b){for(var a=0;a<this._features.length;a++){if(this._features[a]===b){this._features.splice(a,1);return true}}return false},addItem:function(b,a,c,d){if(c&&!d){return false}this._items.push({jid:b,name:a,node:c,call_back:d});return true},info:function(c,d,g,b,e){var a={xmlns:Strophe.NS.DISCO_INFO};if(d){a.node=d}var f=$iq({from:this._connection.jid,to:c,type:"get"}).c("query",a);this._connection.sendIQ(f,g,b,e)},items:function(d,e,g,c,f){var b={xmlns:Strophe.NS.DISCO_ITEMS};if(e){b.node=e}var a=$iq({from:this._connection.jid,to:d,type:"get"}).c("query",b);this._connection.sendIQ(a,g,c,f)},_buildIQResult:function(c,b){var e=c.getAttribute("id");var d=c.getAttribute("from");var a=$iq({type:"result",id:e});if(d!==null){a.attrs({to:d})}return a.c("query",b)},_onDiscoInfo:function(e){var d=e.getElementsByTagName("query")[0].getAttribute("node");var a={xmlns:Strophe.NS.DISCO_INFO};if(d){a.node=d}var c=this._buildIQResult(e,a);for(var b=0;b<this._identities.length;b++){var a={category:this._identities[b].category,type:this._identities[b].type};if(this._identities[b].name){a.name=this._identities[b].name}if(this._identities[b].lang){a["xml:lang"]=this._identities[b].lang}c.c("identity",a).up()}for(var b=0;b<this._features.length;b++){c.c("feature",{"var":this._features[b]}).up()}this._connection.send(c.tree());return true},_onDiscoItems:function(g){var f={xmlns:Strophe.NS.DISCO_ITEMS};var e=g.getElementsByTagName("query")[0].getAttribute("node");if(e){f.node=e;var a=[];for(var c=0;c<this._items.length;c++){if(this._items[c].node==e){a=this._items[c].call_back(g);break}}}else{var a=this._items}var d=this._buildIQResult(g,f);for(var c=0;c<a.length;c++){var b={jid:a[c].jid};if(a[c].name){b.name=a[c].name}if(a[c].node){b.node=a[c].node}d.c("item",b).up()}this._connection.send(d.tree());return true}});Strophe.addConnectionPlugin("caps",{HASH:"sha-1",node:"http://strophe.im/strophejs/",_ver:"",_connection:null,_knownCapabilities:{},_jidVerIndex:{},init:function(a){this._connection=a;Strophe.addNamespace("CAPS","http://jabber.org/protocol/caps");if(!this._connection.disco){throw"Caps plugin requires the disco plugin to be installed."}this._connection.disco.addFeature(Strophe.NS.CAPS);this._connection.addHandler(this._delegateCapabilities.bind(this),Strophe.NS.CAPS)},generateCapsAttrs:function(){return{xmlns:Strophe.NS.CAPS,hash:this.HASH,node:this.node,ver:this.generateVer()}},generateVer:function(){if(this._ver!==""){return this._ver}var a="",c=this._connection.disco._identities.sort(this._sortIdentities),e=c.length,d=this._connection.disco._features.sort(),f=d.length;for(var b=0;b<e;b++){var g=c[b];a+=g.category+"/"+g.type+"/"+g.lang+"/"+g.name+"<"}for(var b=0;b<f;b++){a+=d[b]+"<"}this._ver=b64_sha1(a);return this._ver},getCapabilitiesByJid:function(a){if(this._jidVerIndex[a]){return this._knownCapabilities[this._jidVerIndex[a]]}return null},_delegateCapabilities:function(d){var f=d.getAttribute("from"),e=d.querySelector("c"),a=e.getAttribute("ver"),b=e.getAttribute("node");if(!this._knownCapabilities[a]){return this._requestCapabilities(f,b,a)}else{this._jidVerIndex[f]=a}if(!this._jidVerIndex[f]||!this._jidVerIndex[f]!==a){this._jidVerIndex[f]=a}return true},_requestCapabilities:function(d,b,a){if(d!==this._connection.jid){var c=this._connection.disco.info(d,b+"#"+a);this._connection.addHandler(this._handleDiscoInfoReply.bind(this),Strophe.NS.DISCO_INFO,"iq","result",c,d)}return true},_handleDiscoInfoReply:function(g){var e=g.querySelector("query"),d=e.getAttribute("node").split("#"),a=d[1],h=g.getAttribute("from");if(!this._knownCapabilities[a]){var f=e.childNodes,c=f.length;this._knownCapabilities[a]=[];for(var b=0;b<c;b++){var d=f[b];this._knownCapabilities[a].push({name:d.nodeName,attributes:d.attributes})}this._jidVerIndex[h]=a}else{if(!this._jidVerIndex[h]||!this._jidVerIndex[h]!==a){this._jidVerIndex[h]=a}}return false},_sortIdentities:function(d,c){if(d.category>c.category){return 1}if(d.category<c.category){return -1}if(d.type>c.type){return 1}if(d.type<c.type){return -1}if(d.lang>c.lang){return 1}if(d.lang<c.lang){return -1}return 0}});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(j,x,t){var g=dateFormat;if(arguments.length==1&&Object.prototype.toString.call(j)=="[object String]"&&!/\d/.test(j)){x=j;j=undefined}j=j?new Date(j):new Date;if(isNaN(j)){throw SyntaxError("invalid date")}x=String(g.masks[x]||x||g.masks["default"]);if(x.slice(0,4)=="UTC:"){x=x.slice(4);t=true}var v=t?"getUTC":"get",p=j[v+"Date"](),e=j[v+"Day"](),l=j[v+"Month"](),r=j[v+"FullYear"](),u=j[v+"Hours"](),n=j[v+"Minutes"](),w=j[v+"Seconds"](),q=j[v+"Milliseconds"](),f=t?0:j.getTimezoneOffset(),h={d:p,dd:c(p),ddd:g.i18n.dayNames[e],dddd:g.i18n.dayNames[e+7],m:l+1,mm:c(l+1),mmm:g.i18n.monthNames[l],mmmm:g.i18n.monthNames[l+12],yy:String(r).slice(2),yyyy:r,h:u%12||12,hh:c(u%12||12),H:u,HH:c(u),M:n,MM:c(n),s:w,ss:c(w),l:c(q,3),L:c(q>99?Math.round(q/10):q),t:u<12?"a":"p",tt:u<12?"am":"pm",T:u<12?"A":"P",TT:u<12?"AM":"PM",Z:t?"UTC":(String(j).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"][p%10>3?0:(p%100-p%10!=10)*p%10]};return x.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
diff --git a/libs/strophejs b/libs/strophejs
-Subproject 8d27954f8b91c2343bff3b5b6529abe6d8621b3
+Subproject 88616167104f8ea19874aad5bcf8a081ebecc4a
diff --git a/libs/strophejs-plugins b/libs/strophejs-plugins
-Subproject ebcd033da647e966320b404f4ae1e68443d22c8
+Subproject 403bc737ed90dc357329f7164705f71127d0c95
diff --git a/res/default.css b/res/default.css
index 48caf48..42f1264 100644
--- a/res/default.css
+++ b/res/default.css
@@ -8,7 +8,7 @@
html, body {
margin: 0;
padding: 0;
- font-family: Arial, Helvetica, sans-serif;
+ font-family: 'Helvetica Neue', Helvetica, sans-serif;
}
#candy {
@@ -17,7 +17,7 @@ html, body {
bottom: 0;
right: 0;
left: 0;
- background-color: #ccc;
+ background-color: #444;
color: #333;
overflow: hidden;
}
@@ -45,24 +45,25 @@ ul {
margin: 0;
float: left;
position: relative;
- border-right: 1px solid #aaa;
white-space: nowrap;
+ margin: 3px 0 0 3px;
}
-#chat-tabs li a {
- background-color: #ddd;
- padding: 6px 50px 4px 10px;
+#chat-tabs a {
+ padding: 4px 50px 4px 10px;
display: inline-block;
- color: #666;
+ color: #ccc;
height: 20px;
+ background-color: #666;
+ border-radius: 3px 3px 0 0;
}
-#chat-tabs li.active a {
- background-color: white;
+#chat-tabs .active a {
+ background-color: #eee;
color: black;
}
-#chat-tabs li a.transition {
+#chat-tabs .transition {
position: absolute;
top: 0;
right: 0;
@@ -70,41 +71,41 @@ ul {
width: 35px;
height: 30px;
background: url(img/tab-transitions.png) repeat-y left;
+ border-radius: 0 3px 0 0;
}
-#chat-tabs li a.close {
+#chat-tabs a.close {
background-color: transparent;
position: absolute;
- top: 0;
- right: 0;
+ right: -2px;
+ top: -3px;
height: auto;
padding: 5px;
- margin: 1px 5px 0 2px;
+ margin: 0 5px 0 2px;
color: #999;
}
-#chat-tabs li.active a.transition {
- color: gray;
+#chat-tabs .active .transition {
background: url(img/tab-transitions.png) repeat-y -50px;
}
-#chat-tabs li a.close:hover, #chat-tabs li.active a.close:hover {
+#chat-tabs .close:hover {
color: black;
}
-#chat-tabs li .unread {
+#chat-tabs .unread {
color: white;
background-color: #9b1414;
padding: 2px 4px;
font-weight: bold;
font-size: 10px;
position: absolute;
- top: 6px;
+ top: 5px;
right: 22px;
border-radius: 3px;
}
-#chat-tabs li.offline a.label {
+#chat-tabs .offline .label {
text-decoration: line-through;
}
@@ -117,9 +118,10 @@ ul {
width: 200px;
height: 24px;
padding-top: 7px;
- border-top: 1px solid #aaa;
- background-color: #d9d9d9;
+ background-color: #444;
display: none;
+ border-top: 1px solid black;
+ box-shadow: 0 1px 0 0 #555 inset;
}
#chat-toolbar li {
@@ -182,9 +184,10 @@ ul {
.usercount span {
display: inline-block;
padding: 1px 3px;
- background-color: #ccc;
+ background-color: #666;
font-weight: bold;
border-radius: 3px;
+ color: #ccc;
}
.room-pane {
@@ -199,23 +202,26 @@ ul {
bottom: 0;
width: 200px;
margin: 30px 0 32px 0;
+ background-color: #333;
+ border-top: 1px solid black;
+ box-shadow: inset 0 1px 0 0 #555;
}
.roster-pane .user {
cursor: pointer;
- padding: 4px 7px;
+ padding: 7px 10px;
font-size: 12px;
- margin: 0 4px 2px 4px;
opacity: 0;
display: none;
- color: #666;
+ color: #ccc;
clear: both;
- height: 15px;
- background-color: #ddd;
+ height: 14px;
+ border-bottom: 1px solid black;
+ box-shadow: 0 1px 0 0 #555;
}
-
+
.roster-pane .user:hover {
- background-color: #eee;
+ background-color: #222;
}
.roster-pane .user.status-ignored {
@@ -228,7 +234,7 @@ ul {
}
.roster-pane .user.me:hover {
- background-color: #ddd;
+ background-color: transparent;
}
.roster-pane .label {
@@ -236,6 +242,7 @@ ul {
width: 110px;
overflow: hidden;
white-space: nowrap;
+ text-shadow: 1px 1px black;
}
.roster-pane li {
@@ -272,20 +279,21 @@ ul {
display: block;
}
-.roster-pane .me li.context {
- display: none;
-}
-
.roster-pane li.context {
- background-image: url(img/action/menu.png);
+ color: #999;
+ text-align: center;
cursor: pointer;
}
.roster-pane li.context:hover {
- background-color: #ccc;
+ background-color: #666;
border-radius: 4px;
}
+.roster-pane .me li.context {
+ display: none;
+}
+
.message-pane-wrapper {
clear: both;
overflow: auto;
@@ -296,44 +304,71 @@ ul {
left: 0;
height: auto;
width: auto;
- margin: 30px 200px 32px 0;
- background-color: white;
+ margin: 30px 200px 31px 0;
+ background-color: #eee;
font-size: 13px;
+ padding: 0 5px;
}
.message-pane {
- margin: 0;
- padding: 5px 10px 2px 10px;
+ padding-top: 1px;
}
-.message-pane dt {
- width: 55px;
- float: left;
- color: #888;
+.message-pane li {
+ cursor: default;
+ border-bottom: 1px solid #ccc;
+ box-shadow: 0 1px 0 0 white;
+}
+
+.message-pane small {
+ display: none;
+ color: #a00;
font-size: 10px;
- text-align: right;
- padding-top: 4px;
+ position: absolute;
+ background-color: #f7f7f7;
+ text-align: center;
+ line-height: 20px;
+ margin: 4px 0;
+ padding: 0 5px;
+ right: 5px;
+}
+
+.message-pane li:hover {
+ background-color: #f7f7f7;
+}
+
+.message-pane li:hover small {
+ display: block;
}
-.message-pane dd {
+.message-pane li>div {
overflow: auto;
- padding: 2px 0 1px 130px;
- margin: 0 0 2px 0;
+ padding: 2px 0 2px 130px;
+ line-height: 24px;
white-space: -o-pre-wrap; /* Opera */
word-wrap: break-word; /* Internet Explorer 5.5+ */
}
-.message-pane dd .label {
+.message-pane .label {
font-weight: bold;
white-space: nowrap;
display: block;
- margin-left: -125px;
- width: 120px;
+ margin-left: -130px;
+ width: 110px;
float: left;
overflow: hidden;
+ text-align: right;
+ color: black;
+}
+
+.message-pane .spacer {
+ color: #aaa;
+ font-weight: bold;
+ margin-left: -14px;
+ float: left;
}
-.message-pane .subject {
+.message-pane .subject, .message-pane .subject .label {
color: #a00;
font-weight: bold;
}
@@ -346,7 +381,14 @@ ul {
.message-pane .infomessage {
color: #888;
font-style: italic;
- padding-left: 5px;
+}
+
+.message-pane div>a {
+ color: #a00;
+}
+
+.message-pane a:hover {
+ text-decoration: underline;
}
.message-pane .emoticon {
@@ -363,7 +405,7 @@ ul {
width: auto;
margin-right: 200px;
border-top: 1px solid #ccc;
- background-color: #f2f2f2;
+ background-color: white;
height: 31px;
}
@@ -383,7 +425,7 @@ ul {
width: 100%;
display: block;
outline-width: 0;
- background-color: #f2f2f2;
+ background-color: white;
}
.message-form input.submit {
@@ -400,41 +442,73 @@ ul {
line-height: 12px;
height: 25px;
font-weight: bold;
- border-radius: 10px;
+ border-radius: 3px;
}
#tooltip {
position: absolute;
z-index: 10;
display: none;
- margin: 18px -18px 2px -2px;
- color: white;
+ margin: 13px -18px -3px -2px;
+ color: #333;
font-size: 11px;
padding: 5px 0;
- background: url(img/tooltip-arrows.gif) no-repeat left bottom;
}
#tooltip div {
- background-color: black;
+ background-color: #f7f7f7;
padding: 2px 5px;
zoom: 1;
+ box-shadow: 0 1px 2px rgba(0, 0, 0, .75);
+}
+
+.arrow {
+ background: url(img/tooltip-arrows.gif) no-repeat left bottom;
+ height: 5px;
+ display: block;
+ position: relative;
+ z-index: 11;
+}
+
+.right-bottom .arrow-bottom {
+ background-position: right bottom;
+}
+
+.arrow-top {
+ display: none;
+ background-position: left top;
+}
+
+.right-top .arrow-top {
+ display: block;
+ background-position: right top;
+}
+
+.left-top .arrow-top {
+ display: block;
+}
+
+
+.left-top .arrow-bottom,
+.right-top .arrow-bottom {
+ display: none;
}
#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;
+ padding: 5px 10px;
+ margin: 13px -28px -3px -12px;
}
#context-menu ul {
- background-color: black;
- color: white;
+ background-color: #f7f7f7;
+ color: #333;
font-size: 12px;
padding: 2px;
zoom: 1;
+ box-shadow: 0 1px 2px rgba(0, 0, 0, .75);
}
#context-menu li {
@@ -447,7 +521,7 @@ ul {
}
#context-menu li:hover {
- background-color: #666;
+ background-color: #ccc;
}
#context-menu li:last-child {
@@ -496,20 +570,22 @@ ul {
}
#chat-modal {
- background: url(img/modal-bg.png);
+ background: #eee;
width: 300px;
padding: 20px 5px;
- color: white;
+ color: #333;
font-size: 16px;
position: fixed;
left: 50%;
top: 50%;
- margin-left: -155px;
+ margin-left: -160px;
margin-top: -45px;
text-align: center;
display: none;
z-index: 100;
+ border: 5px solid #888;
border-radius: 5px;
+ box-shadow: 0 0 5px black;
}
#chat-modal-overlay {
@@ -550,6 +626,15 @@ ul {
width: 150px;
}
+#chat-modal input[type='text'],
+#chat-modal input[type='password'] {
+ background-color: white;
+ border: 1px solid #ccc;
+ padding: 4px;
+ font-size: 14px;
+ color: #333;
+}
+
#chat-modal label {
text-align: right;
padding-right: 1em;
@@ -573,10 +658,11 @@ ul {
display: none;
padding: 0 5px;
margin: -17px 3px 0 0;
- color: white;
+ color: #999;
border-radius: 3px;
}
#chat-modal .close:hover {
- background-color: #333;
+ color: #333;
+ background-color: #aaa;
}
diff --git a/res/img/context-arrows.gif b/res/img/context-arrows.gif
deleted file mode 100644
index d02df8c..0000000
--- a/res/img/context-arrows.gif
+++ /dev/null
Binary files differ
diff --git a/res/img/modal-bg.png b/res/img/modal-bg.png
deleted file mode 100644
index 9fef73a..0000000
--- a/res/img/modal-bg.png
+++ /dev/null
Binary files differ
diff --git a/res/img/modal-spinner.gif b/res/img/modal-spinner.gif
index f8593d5..5b6a68d 100644
--- a/res/img/modal-spinner.gif
+++ b/res/img/modal-spinner.gif
Binary files differ
diff --git a/res/img/tab-transitions.png b/res/img/tab-transitions.png
index 1a361e4..c45d24c 100644
--- a/res/img/tab-transitions.png
+++ b/res/img/tab-transitions.png
Binary files differ
diff --git a/res/img/tooltip-arrows.gif b/res/img/tooltip-arrows.gif
index ef4b05c..6faa6fd 100644
--- a/res/img/tooltip-arrows.gif
+++ b/res/img/tooltip-arrows.gif
Binary files differ
diff --git a/src/candy.js b/src/candy.js
index 8c96d00..a3ed466 100644
--- a/src/candy.js
+++ b/src/candy.js
@@ -7,6 +7,7 @@
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
+ * (c) 2012 Patrick Stadler & Michael Weibel. All rights reserved.
*/
/*jslint regexp: true, browser: true, confusion: true, sloppy: true, white: true, nomen: true, plusplus: true, maxerr: 50, indent: 4 */
@@ -29,7 +30,7 @@ var Candy = (function(self, $) {
*/
self.about = {
name: 'Candy',
- version: '1.0.9'
+ version: '1.5.0'
};
/** Function: init
@@ -44,7 +45,10 @@ var Candy = (function(self, $) {
* (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);
+ if (!options.viewClass) {
+ options.viewClass = self.View;
+ }
+ options.viewClass.init($('#candy'), options.view);
self.Core.init(service, options.core);
};
diff --git a/src/core.js b/src/core.js
index 3ea00bc..ec424c7 100644
--- a/src/core.js
+++ b/src/core.js
@@ -7,6 +7,7 @@
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
+ * (c) 2012 Patrick Stadler & Michael Weibel. All rights reserved.
*/
/** Class: Candy.Core
@@ -38,6 +39,10 @@ Candy.Core = (function(self, Strophe, $) {
* Set in <Candy.Core.connect> when jidOrHost doesn't contain a @-char.
*/
_anonymousConnection = false,
+ /** PrivateVariable: _status
+ * Current Strophe connection state
+ */
+ _status,
/** PrivateVariable: _options
* Options:
* (Boolean) debug - Debug (Default: false)
@@ -49,7 +54,12 @@ Candy.Core = (function(self, Strophe, $) {
* You may want to define an array of rooms to autojoin: `['room1@conference.host.tld', 'room2...]` (ejabberd, Openfire, ...)
*/
autojoin: true,
- debug: false
+ debug: false,
+ disableWindowUnload: false,
+ /** Integer: presencePriority
+ * Default priority for presence messages in order to receive messages across different resources
+ */
+ presencePriority: 1
},
/** PrivateFunction: _addNamespace
@@ -73,17 +83,10 @@ Candy.Core = (function(self, Strophe, $) {
_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');
+ _getEscapedJidFromJid = function(jid) {
+ var node = Strophe.getNodeFromJid(jid),
+ domain = Strophe.getDomainFromJid(jid);
+ return node ? Strophe.escapeNode(node) + '@' + domain : domain;
};
/** Function: init
@@ -105,9 +108,7 @@ Candy.Core = (function(self, Strophe, $) {
if(typeof window.console !== undefined && typeof window.console.log !== undefined) {
console.log(str);
}
- } catch(e) {
- //console.error(e);
- }
+ } catch(e) {}
};
self.log('[Init] Debugging enabled');
}
@@ -120,7 +121,9 @@ Candy.Core = (function(self, Strophe, $) {
// 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;
+ if (!_options.disableWindowUnload) {
+ window.onbeforeunload = self.onWindowUnload;
+ }
// Prevent Firefox from aborting AJAX requests when pressing ESC
if($.browser.mozilla) {
@@ -132,6 +135,25 @@ Candy.Core = (function(self, Strophe, $) {
}
};
+ /** Function: registerEventHandlers
+ * Adds listening handlers to the connection.
+ *
+ * Use with caution from outside of Candy.
+ */
+ self.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', 'result');
+ self.addHandler(self.Event.Jabber.PrivacyList, Strophe.NS.PRIVACY, 'iq', 'result');
+ self.addHandler(self.Event.Jabber.PrivacyListError, Strophe.NS.PRIVACY, 'iq', 'error');
+
+ self.addHandler(_connection.disco._onDiscoInfo.bind(_connection.disco), Strophe.NS.DISCO_INFO, 'iq', 'get');
+ self.addHandler(_connection.disco._onDiscoItems.bind(_connection.disco), Strophe.NS.DISCO_ITEMS, 'iq', 'get');
+ self.addHandler(_connection.caps._delegateCapabilities.bind(_connection.caps), Strophe.NS.CAPS);
+ };
+
/** Function: connect
* Connect to the jabber host.
*
@@ -154,14 +176,18 @@ Candy.Core = (function(self, Strophe, $) {
self.connect = function(jidOrHost, password, nick) {
// Reset before every connection attempt to make sure reconnections work after authfail, alltabsclosed, ...
_connection.reset();
- _registerEventHandlers();
+ self.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));
+ if (nick) {
+ _user = new self.ChatUser(jidOrHost, nick);
+ } else {
+ _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);
@@ -173,12 +199,6 @@ Candy.Core = (function(self, Strophe, $) {
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
@@ -192,7 +212,7 @@ Candy.Core = (function(self, Strophe, $) {
*/
self.attach = function(jid, sid, rid) {
_user = new self.ChatUser(jid, Strophe.getNodeFromJid(jid));
- _registerEventHandlers();
+ self.registerEventHandlers();
_connection.attach(jid, sid, rid, Candy.Core.Event.Strophe.Connect);
};
@@ -267,6 +287,29 @@ Candy.Core = (function(self, Strophe, $) {
return _rooms;
};
+ /** Function: getStropheStatus
+ * Get the status set by Strophe.
+ *
+ * Returns:
+ * (Strophe.Status.*) - one of Strophe's statuses
+ */
+ self.getStropheStatus = function() {
+ return _status;
+ };
+
+ /** Function: setStropheStatus
+ * Set the strophe status
+ *
+ * Called by:
+ * Candy.Core.Event.Strophe.Connect
+ *
+ * Parameters:
+ * (Strophe.Status.*) status - Strophe's status
+ */
+ self.setStropheStatus = function(status) {
+ _status = status;
+ };
+
/** Function: isAnonymousConnection
* Returns true if <Candy.Core.connect> was first called with a domain instead of a jid as the first param.
*
diff --git a/src/core/action.js b/src/core/action.js
index cc78594..8221510 100644
--- a/src/core/action.js
+++ b/src/core/action.js
@@ -7,6 +7,7 @@
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
+ * (c) 2012 Patrick Stadler & Michael Weibel. All rights reserved.
*/
/** Class: Candy.Core.Action
@@ -44,9 +45,16 @@ Candy.Core.Action = (function(self, Strophe, $) {
*
* Parameters:
* (Object) attr - Optional attributes
+ * (Strophe.Builder) el - Optional element to include in presence stanza
*/
- Presence: function(attr) {
- Candy.Core.getConnection().send($pres(attr).tree());
+ Presence: function(attr, el) {
+ var pres = $pres(attr).c('priority').t(Candy.Core.getOptions().presencePriority.toString())
+ .up().c('c', Candy.Core.getConnection().caps.generateCapsAttrs())
+ .up();
+ if(el) {
+ pres.node.appendChild(el.node);
+ }
+ Candy.Core.getConnection().send(pres.tree());
},
/** Function: Services
@@ -60,15 +68,16 @@ Candy.Core.Action = (function(self, Strophe, $) {
* When Candy.Core.getOptions().autojoin is true, request autojoin bookmarks (OpenFire)
*
* Otherwise, if Candy.Core.getOptions().autojoin is an array, join each channel specified.
+ * Channel can be in jid:password format to pass room password if needed.
*/
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());
+ Candy.Core.getConnection().sendIQ($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());
+ self.Jabber.Room.Join.apply(null, this.valueOf().split(':',2));
});
}
},
@@ -134,6 +143,15 @@ Candy.Core.Action = (function(self, Strophe, $) {
Join: function(roomJid, password) {
self.Jabber.Room.Disco(roomJid);
Candy.Core.getConnection().muc.join(roomJid, Candy.Core.getUser().getNick(), null, null, password);
+ var conn = Candy.Core.getConnection(),
+ room_nick = conn.muc.test_append_nick(roomJid, Candy.Core.getUser().getNick()),
+ pres = $pres({ from: conn.jid, to: room_nick })
+ .c('x', {xmlns: Strophe.NS.MUC});
+ if (password != null) {
+ pres.c('password').t(password);
+ }
+ pres.up().c('c', conn.caps.generateCapsAttrs());
+ conn.send(pres.tree());
},
/** Function: Leave
@@ -143,7 +161,10 @@ Candy.Core.Action = (function(self, Strophe, $) {
* (String) roomJid - Room to leave
*/
Leave: function(roomJid) {
- Candy.Core.getConnection().muc.leave(roomJid, Candy.Core.getRoom(roomJid).getUser().getNick(), function() {});
+ var user = Candy.Core.getRoom(roomJid).getUser();
+ if (user) {
+ Candy.Core.getConnection().muc.leave(roomJid, user.getNick(), function() {});
+ }
},
/** Function: Disco
@@ -173,7 +194,7 @@ Candy.Core.Action = (function(self, Strophe, $) {
if(msg === '') {
return false;
}
- Candy.Core.getConnection().muc.message(Candy.Util.escapeJid(roomJid), undefined, msg, type);
+ Candy.Core.getConnection().muc.message(Candy.Util.escapeJid(roomJid), null, msg, null, type);
return true;
},
diff --git a/src/core/chatRoom.js b/src/core/chatRoom.js
index 10eecac..da1107c 100644
--- a/src/core/chatRoom.js
+++ b/src/core/chatRoom.js
@@ -7,6 +7,7 @@
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
+ * (c) 2012 Patrick Stadler & Michael Weibel. All rights reserved.
*/
/** Class: Candy.Core.ChatRoom
diff --git a/src/core/chatRoster.js b/src/core/chatRoster.js
index f1a8617..7998e20 100644
--- a/src/core/chatRoster.js
+++ b/src/core/chatRoster.js
@@ -7,6 +7,7 @@
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
+ * (c) 2012 Patrick Stadler & Michael Weibel. All rights reserved.
*/
/** Class: Candy.Core.ChatRoster
diff --git a/src/core/chatUser.js b/src/core/chatUser.js
index 7b071c3..188a7ac 100644
--- a/src/core/chatUser.js
+++ b/src/core/chatUser.js
@@ -7,6 +7,7 @@
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
+ * (c) 2012 Patrick Stadler & Michael Weibel. All rights reserved.
*/
/** Class: Candy.Core.ChatUser
diff --git a/src/core/event.js b/src/core/event.js
index 36899f1..0b79b32 100644
--- a/src/core/event.js
+++ b/src/core/event.js
@@ -7,6 +7,7 @@
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
+ * (c) 2012 Patrick Stadler & Michael Weibel. All rights reserved.
*/
/** Class: Candy.Core.Event
@@ -16,33 +17,25 @@
* (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
+Candy.Core.Event = (function(self, Strophe, $) {
+ /** Function: Login
+ * Notify view that the login window should be displayed
*
- * CHAT - Chat events
- * PRESENCE - Presence events
- * MESSAGE - Message events
- * LOGIN - Login event
+ * Parameters:
+ * (String) presetJid - Preset user JID
+ *
+ * Triggers:
+ * candy:core.login using {presetJid}
*/
- self.KEYS = {
- CHAT: 1,
- PRESENCE: 2,
- MESSAGE: 3,
- LOGIN: 4,
- PRESENCE_ERROR: 5
+ self.Login = function(presetJid) {
+ /** Event: candy:core.login
+ * Triggered when the login window should be displayed
+ *
+ * Parameters:
+ * (String) presetJid - Preset user JID
+ */
+ $(Candy).triggerHandler('candy:core.login', { presetJid: presetJid } );
};
/** Class: Candy.Core.Event.Strophe
@@ -54,8 +47,12 @@ Candy.Core.Event = (function(self, Strophe, $, observable) {
*
* Parameters:
* (Strophe.Status) status - Strophe statuses
+ *
+ * Triggers:
+ * candy:core.chat.connection using {status}
*/
Connect: function(status) {
+ Candy.Core.setStropheStatus(status);
switch(status) {
case Strophe.Status.CONNECTED:
Candy.Core.log('[Connection] Connected');
@@ -97,21 +94,16 @@ Candy.Core.Event = (function(self, Strophe, $, observable) {
Candy.Core.log('[Connection] What?!');
break;
}
-
- self.notifyObservers(self.KEYS.CHAT, { type: 'connection', status: status } );
+ /** Event: candy:core.chat.connection
+ * Connection status updates
+ *
+ * Parameters:
+ * (Strophe.Status) status - Strophe status
+ */
+ $(Candy).triggerHandler('candy:core.chat.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
*/
@@ -137,6 +129,9 @@ Candy.Core.Event = (function(self, Strophe, $, observable) {
* Parameters:
* (String) msg - Raw XML Message
*
+ * Triggers:
+ * candy:core.presence using {from, stanza}
+ *
* Returns:
* (Boolean) - true
*/
@@ -149,6 +144,15 @@ Candy.Core.Event = (function(self, Strophe, $, observable) {
} else {
self.Jabber.Room.Presence(msg);
}
+ } else {
+ /** Event: candy:core.presence
+ * Presence updates. Emitted only when not a muc presence.
+ *
+ * Parameters:
+ * (JID) from - From Jid
+ * (String) stanza - Stanza
+ */
+ $(Candy).triggerHandler('candy:core.presence', {'from': msg.attr('from'), 'stanza': msg});
}
return true;
},
@@ -226,6 +230,10 @@ Candy.Core.Event = (function(self, Strophe, $, observable) {
* Parameters:
* (String) msg - Raw XML Message
*
+ * Triggers:
+ * candy:core.chat.message.admin using {type, message}
+ * candy:core.chat.message.server {type, subject, message}
+ *
* Returns:
* (Boolean) - true
*/
@@ -236,14 +244,33 @@ Candy.Core.Event = (function(self, Strophe, $, observable) {
type = msg.attr('type'),
toJid = msg.attr('to');
// Room message
- if(fromJid !== Strophe.getDomainFromJid(fromJid) && (type === 'groupchat' || type === 'chat' || type === 'error')) {
+ if(fromJid !== Strophe.getDomainFromJid(fromJid) && (type === 'groupchat' || type === 'chat' || type === 'error' || type === 'normal')) {
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() });
+ /** Event: candy:core.chat.message.admin
+ * Admin message
+ *
+ * Parameters:
+ * (String) type - Type of the message [default: message]
+ * (String) message - Message text
+ */
+ $(Candy).triggerHandler('candy:core.chat.message.admin', { 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() });
+ /** Event: candy:core.chat.message.server
+ * Server message (e.g. subject)
+ *
+ * Parameters:
+ * (String) type - Message type [default: message]
+ * (String) subject - Subject text
+ * (String) message - Message text
+ */
+ $(Candy).triggerHandler('candy:core.chat.message.server', {
+ type: (type || 'message'),
+ subject: msg.children('subject').text(),
+ message: msg.children('body').text()
+ });
}
return true;
},
@@ -258,6 +285,9 @@ Candy.Core.Event = (function(self, Strophe, $, observable) {
* Parameters:
* (String) msg - Raw XML Message
*
+ * Triggers:
+ * candy:core.presence.leave using {roomJid, roomName, type, reason, actor, user}
+ *
* Returns:
* (Boolean) - true
*/
@@ -292,7 +322,27 @@ Candy.Core.Event = (function(self, Strophe, $, observable) {
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 } );
+ /** Event: candy:core.presence.leave
+ * When the local client leaves a room
+ *
+ * Also triggered when the local client gets kicked or banned from a room.
+ *
+ * Parameters:
+ * (String) roomJid - Room
+ * (String) roomName - Name of room
+ * (String) type - Presence type [kick, ban, leave]
+ * (String) reason - When type equals kick|ban, this is the reason the moderator has supplied.
+ * (String) actor - When type equals kick|ban, this is the moderator which did the kick
+ * (Candy.Core.ChatUser) user - user which leaves the room
+ */
+ $(Candy).triggerHandler('candy:core.presence.leave', {
+ 'roomJid': roomJid,
+ 'roomName': roomName,
+ 'type': type,
+ 'reason': reason,
+ 'actor': actor,
+ 'user': user
+ });
return true;
},
@@ -332,6 +382,9 @@ Candy.Core.Event = (function(self, Strophe, $, observable) {
* Parameters:
* (Object) msg - jQuery object of XML message
*
+ * Triggers:
+ * candy:core.presence.room using {roomJid, roomName, user, action, currentUser}
+ *
* Returns:
* (Boolean) - true
*/
@@ -381,16 +434,35 @@ Candy.Core.Event = (function(self, Strophe, $, observable) {
roster.remove(from);
}
- self.notifyObservers(self.KEYS.PRESENCE, {'roomJid': roomJid, 'roomName': room.getName(), 'user': user, 'action': action, 'currentUser': Candy.Core.getUser() } );
+ /** Event: candy:core.presence.room
+ * Room presence updates
+ *
+ * Parameters:
+ * (String) roomJid - Room JID
+ * (String) roomName - Room name
+ * (Candy.Core.ChatUser) user - User which does the presence update
+ * (String) action - Action [kick, ban, leave, join]
+ * (Candy.Core.ChatUser) currentUser - Current local user
+ */
+ $(Candy).triggerHandler('candy:core.presence.room', {
+ '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
*
+ * Triggers:
+ * candy:core.presence.error using {msg, type, roomJid, roomName}
+ *
* Returns:
* (Boolean) - true
*/
@@ -400,11 +472,25 @@ Candy.Core.Event = (function(self, Strophe, $, observable) {
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});
+
+ /** Event: candy:core.presence.error
+ * Triggered when a presence error happened
+ *
+ * Parameters:
+ * (Object) msg - jQuery object of XML message
+ * (String) type - Error type
+ * (String) roomJid - Room jid
+ * (String) roomName - Room name
+ */
+ $(Candy).triggerHandler('candy:core.presence.error', {
+ 'msg' : msg,
+ 'type': msg.children('error').children()[0].tagName.toLowerCase(),
+ 'roomJid': roomJid,
+ 'roomName': roomName
+ });
},
/** Function: Message
@@ -414,6 +500,9 @@ Candy.Core.Event = (function(self, Strophe, $, observable) {
* Parameters:
* (String) msg - jQuery object of XML message
*
+ * Triggers:
+ * candy:core.message using {roomJid, message, timestamp}
+ *
* Returns:
* (Boolean) - true
*/
@@ -427,14 +516,14 @@ Candy.Core.Event = (function(self, Strophe, $, observable) {
// Error messsage
} else if(msg.attr('type') === 'error') {
var error = msg.children('error');
- if(error.attr('code') === '500' && error.children('text').length > 0) {
+ if(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') {
+ if(msg.attr('type') === 'chat' || msg.attr('type') === 'normal') {
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.
@@ -451,6 +540,10 @@ Candy.Core.Event = (function(self, Strophe, $, observable) {
message = { name: resource, body: msg.children('body').text(), type: msg.attr('type') };
// Message from server (XEP-0045#registrar-statuscodes)
} else {
+ // we are not yet present in the room, let's just drop this message (issue #105)
+ if(!Candy.View.Pane.Chat.rooms[msg.attr('from')]) {
+ return true;
+ }
message = { name: '', body: msg.children('body').text(), type: 'info' };
}
}
@@ -464,11 +557,46 @@ Candy.Core.Event = (function(self, Strophe, $, observable) {
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 } );
+ /** Event: candy:core.message
+ * Triggers on various message events (subject changed, private chat message, multi-user chat message).
+ *
+ * The resulting message object can contain different key-value pairs as stated in the documentation
+ * of the parameters itself.
+ *
+ * The following lists explain those parameters:
+ *
+ * Message Object Parameters:
+ * (String) name - Room name
+ * (String) body - Message text
+ * (String) type - Message type ([normal, chat, groupchat])
+ * or 'info' which is used internally for displaying informational messages
+ * (Boolean) isNoConferenceRoomJid - 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 flag tells if this is the case.
+ *
+ * Parameters:
+ * (String) roomJid - Room jid
+ * (Object) message - Depending on what kind of message, the object consists of different key-value pairs:
+ * - Room Subject: {name, body, type}
+ * - Error message: {type = 'info', body}
+ * - Private chat message: {name, body, type, isNoConferenceRoomJid}
+ * - MUC msg from a user: {name, body, type}
+ * - MUC msg from server: {name = '', body, type = 'info'}
+ * (String) timestamp - Timestamp, only when it's an offline message
+ *
+ * TODO:
+ * Streamline those events sent and rename the parameters.
+ */
+ $(Candy).triggerHandler('candy:core.message', {
+ roomJid: roomJid,
+ message: message,
+ timestamp: timestamp
+ });
return true;
}
}
};
return self;
-}(Candy.Core.Event || {}, Strophe, jQuery, Candy.Util.Observable));
+}(Candy.Core.Event || {}, Strophe, jQuery));
diff --git a/src/util.js b/src/util.js
index 4b375ce..499d5ee 100644
--- a/src/util.js
+++ b/src/util.js
@@ -7,6 +7,7 @@
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
+ * (c) 2012 Patrick Stadler & Michael Weibel. All rights reserved.
*/
/** Class: Candy.Util
@@ -29,7 +30,7 @@ Candy.Util = (function(self, $){
self.jidToId = function(jid) {
return MD5.hexdigest(jid);
};
-
+
/** Function: escapeJid
* Escapes a jid (node & resource get escaped)
*
@@ -46,15 +47,15 @@ Candy.Util = (function(self, $){
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)
*
@@ -71,12 +72,12 @@ Candy.Util = (function(self, $){
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;
};
@@ -131,13 +132,13 @@ Candy.Util = (function(self, $){
* 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];
- }
- }
+ if(document.cookie) {
+ var regex = new RegExp(escape(name) + '=([^;]*)', 'gm'),
+ matches = regex.exec(document.cookie);
+ if(matches) {
+ return matches[1];
+ }
+ }
};
/** Function: deleteCookie
@@ -251,23 +252,25 @@ Candy.Util = (function(self, $){
* Date-Object
*/
self.iso8601toDate = function(date) {
- var timestamp = Date.parse(date), minutesOffset = 0;
- if(isNaN(timestamp)) {
+ var timestamp = Date.parse(date);
+ 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) {
+ var minutesOffset = 0;
if(struct[8] !== 'Z') {
minutesOffset = +struct[10] * 60 + (+struct[11]);
if(struct[9] === '+') {
minutesOffset = -minutesOffset;
}
}
+ minutesOffset -= new Date().getTimezoneOffset();
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);
+ }
+ return new Date(timestamp);
};
/** Function: isEmptyObject
@@ -312,7 +315,7 @@ Candy.Util = (function(self, $){
* Use setEmoticonPath() to change it
*/
_emoticonPath: '',
-
+
/** Function: setEmoticonPath
* Set emoticons location.
*
@@ -455,6 +458,19 @@ Candy.Util = (function(self, $){
return $('<div/>').text(text).html();
},
+ /** Function: nl2br
+ * replaces newline characters with a <br/> to make multi line messages look nice
+ *
+ * Parameters:
+ * (String) text - Text to process
+ *
+ * Returns:
+ * Processed text
+ */
+ nl2br: function(text) {
+ return text.replace(/\r\n|\r|\n/g, '<br />');
+ },
+
/** Function: all
* Does everything of the parser: escaping, linkifying and emotifying.
*
@@ -469,6 +485,7 @@ Candy.Util = (function(self, $){
text = this.escape(text);
text = this.linkify(text);
text = this.emotify(text);
+ text = this.nl2br(text);
}
return text;
}
@@ -476,69 +493,3 @@ Candy.Util = (function(self, $){
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 || {}));
diff --git a/src/view.js b/src/view.js
index 9b7274c..f63eed0 100644
--- a/src/view.js
+++ b/src/view.js
@@ -7,6 +7,7 @@
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
+ * (c) 2012 Patrick Stadler & Michael Weibel. All rights reserved.
*/
/** Class: Candy.View
@@ -56,11 +57,14 @@ Candy.View = (function(self, $) {
* 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);
+ $(Candy).on('candy:core.chat.connection', self.Observer.Chat.Connection);
+ $(Candy).on('candy:core.chat.message', self.Observer.Chat.Message);
+ $(Candy).on('candy:core.login', self.Observer.Login);
+ $(Candy).on('candy:core.presence', self.Observer.Presence.update);
+ $(Candy).on('candy:core.presence.leave', self.Observer.Presence.update);
+ $(Candy).on('candy:core.presence.room', self.Observer.Presence.update);
+ $(Candy).on('candy:core.presence.error', self.Observer.PresenceError);
+ $(Candy).on('candy:core.message', self.Observer.Message);
},
/** PrivateFunction: _registerWindowHandlers
@@ -70,7 +74,7 @@ Candy.View = (function(self, $) {
*/
_registerWindowHandlers = function() {
// Cross-browser focus handling
- if($.browser.msie && !$.browser.version.match('^9')) {
+ 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);
@@ -78,24 +82,11 @@ Candy.View = (function(self, $) {
$(window).resize(Candy.View.Pane.Chat.fitTabs);
},
- /** PrivateFunction: _registerToolbarHandlers
- * Register toolbar handlers and disable sound if cookie says so.
+ /** PrivateFunction: _initToolbar
+ * Initialize toolbar.
*/
- _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();
- }
+ _initToolbar = function() {
+ self.Pane.Chat.Toolbar.init();
},
/** PrivateFunction: _delegateTooltips
@@ -115,7 +106,7 @@ Candy.View = (function(self, $) {
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/');
@@ -139,7 +130,7 @@ Candy.View = (function(self, $) {
// ... and let the elements dance.
_registerWindowHandlers();
- _registerToolbarHandlers();
+ _initToolbar();
_registerObservers();
_delegateTooltips();
};
diff --git a/src/view/event.js b/src/view/event.js
index 11b458d..b1e4cf9 100644
--- a/src/view/event.js
+++ b/src/view/event.js
@@ -7,11 +7,15 @@
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
+ * (c) 2012 Patrick Stadler & Michael Weibel
*/
/** Class: Candy.View.Event
* Empty hooks to capture events and inject custom code.
*
+ * Deprecated:
+ * Don't use this anymore. Bind on the triggered events on Candy.View.*
+ *
* Parameters:
* (Candy.View.Event) self - itself
* (jQuery) $ - jQuery
diff --git a/src/view/observer.js b/src/view/observer.js
index e158e85..b52c267 100644
--- a/src/view/observer.js
+++ b/src/view/observer.js
@@ -7,6 +7,7 @@
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
+ * (c) 2012 Patrick Stadler & Michael Weibel
*/
/** Class: Candy.View.Observer
@@ -21,49 +22,57 @@ Candy.View.Observer = (function(self, $) {
* Chat events
*/
self.Chat = {
- /** Function: update
+ /** Function: Connection
* The update method gets called whenever an event to which "Chat" is subscribed.
*
- * Currently listens for connection status updates & admin messages / motd
+ * Currently listens for connection status updates
*
* Parameters:
- * (Candy.Core.Event) obj - Candy core event object
- * (Object) args - {type, connection or message & subject}
+ * (jQuery.Event) event - jQuery Event object
+ * (Object) args - {status (Strophe.Status.*)}
*/
- 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;
+ Connection: function(event, args) {
+ 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.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.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.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;
- 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;
+ }
+ },
- default:
- Candy.View.Pane.Chat.Modal.show($.i18n._('status', args.status));
- break;
- }
- } else if(args.type === 'message') {
+ /** Function: Message
+ * Dispatches admin and info messages
+ *
+ * Parameters:
+ * (jQuery.Event) event - jQuery Event object
+ * (Object) args - {type (message/chat/groupchat), subject (if type = message), message}
+ */
+ Message: function(event, args) {
+ 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.
@@ -80,13 +89,13 @@ Candy.View.Observer = (function(self, $) {
* Every presence update gets dispatched from this method.
*
* Parameters:
- * (Candy.Core.Event) obj - Candy core event object
+ * (jQuery.Event) event - jQuery.Event object
* (Object) args - Arguments differ on each type
*
* Uses:
* - <notifyPrivateChats>
*/
- update: function(obj, args) {
+ update: function(event, args) {
// Client left
if(args.type === 'leave') {
var user = Candy.View.Pane.Room.getUser(args.roomJid);
@@ -121,9 +130,23 @@ Candy.View.Observer = (function(self, $) {
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 });
+
+ var evtData = { type: args.type, reason: args.reason, roomJid: args.roomJid, user: args.user };
+ Candy.View.Event.Room.onPresenceChange(evtData);
+
+ /** Event: candy:view.presence
+ * Presence update when kicked or banned
+ *
+ * Parameters:
+ * (String) type - Presence type [kick, ban]
+ * (String) reason - Reason for the kick|ban [optional]
+ * (String) roomJid - Room JID
+ * (Candy.Core.ChatUser) user - User which has been kicked or banned
+ */
+ $(Candy).triggerHandler('candy:view.presence', [evtData]);
+
// A user changed presence
- } else {
+ } else if(args.roomJid) {
// Initialize room if not yet existing
if(!Candy.View.Pane.Chat.rooms[args.roomJid]) {
Candy.View.Pane.Room.init(args.roomJid, args.roomName);
@@ -135,6 +158,8 @@ Candy.View.Observer = (function(self, $) {
Candy.View.Pane.Roster.update(args.user.getJid(), args.user, args.action, args.currentUser);
Candy.View.Pane.PrivateRoom.setStatus(args.user.getJid(), args.action);
}
+ } else {
+ // Unhandled type of presence
}
},
@@ -142,7 +167,7 @@ Candy.View.Observer = (function(self, $) {
* Notify private user chats if existing
*
* Parameters:
- * (Candy.Core.chatUser) user - User which has done the event
+ * (Candy.Core.ChatUser) user - User which has done the event
* (String) type - Event type (leave, join, kick/ban)
*/
notifyPrivateChats: function(user, type) {
@@ -156,84 +181,69 @@ Candy.View.Observer = (function(self, $) {
}
}
};
-
+
/** Class: Candy.View.Observer.PresenceError
- * Presence error events
+ * Presence errors get handled in this method
+ *
+ * Parameters:
+ * (jQuery.Event) event - jQuery.Event object
+ * (Object) args - {msg, type, roomJid, roomName}
*/
- 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;
- }
+ self.PresenceError = 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
+ * Messages received get dispatched from this method.
+ *
+ * Parameters:
+ * (jQuery.Event) event - jQuery Event object
+ * (Object) args - {message, roomJid}
*/
- 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);
+ self.Message = function(event, 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
+ * The login event gets dispatched to this method
+ *
+ * Parameters:
+ * (jQuery.Event) event - jQuery Event object
+ * (Object) args - {presetJid}
*/
- 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);
- }
+ self.Login = function(event, args) {
+ Candy.View.Pane.Chat.Modal.showLoginForm(null, args.presetJid);
};
return self;
diff --git a/src/view/pane.js b/src/view/pane.js
index b5ea4d7..cc7fb01 100644
--- a/src/view/pane.js
+++ b/src/view/pane.js
@@ -7,6 +7,7 @@
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
+ * (c) 2012 Patrick Stadler & Michael Weibel. All rights reserved.
*/
/** Class: Candy.View.Pane
@@ -132,7 +133,7 @@ Candy.View.Pane = (function(self, $) {
roomJid: roomJid,
roomId: roomId,
name: roomName || Strophe.getNodeFromJid(roomJid),
- privateUserChat: function() { return roomType === 'chat'; },
+ privateUserChat: function() {return roomType === 'chat';},
roomType: roomType
}),
tab = $(html).appendTo('#chat-tabs');
@@ -284,33 +285,25 @@ Candy.View.Pane = (function(self, $) {
tabsWidth = 0,
tabs = $('#chat-tabs').children();
tabs.each(function() {
- tabsWidth += $(this).css({ width: 'auto', overflow: 'visible' }).outerWidth(true);
+ 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' });
+ 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
+ *
+ * Triggers:
+ * candy:view.chat.admin-message using {subject, message}
*/
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?
@@ -325,7 +318,18 @@ Candy.View.Pane = (function(self, $) {
});
self.Room.scrollToBottom(Candy.View.getCurrent().roomJid);
- Candy.View.Event.Chat.onAdminMessage({'subject' : subject, 'message' : message});
+ var evtData = {'subject' : subject, 'message' : message};
+
+ // deprecated
+ Candy.View.Event.Chat.onAdminMessage(evtData);
+
+ /** Event: candy:view.chat.admin-message
+ * After admin message display
+ *
+ * Parameters:
+ * (String) presetJid - Preset user JID
+ */
+ $(Candy).triggerHandler('candy:view.chat.admin-message', evtData);
}
},
@@ -368,6 +372,30 @@ Candy.View.Pane = (function(self, $) {
* Chat toolbar for things like emoticons toolbar, room management etc.
*/
Toolbar: {
+ _supportsNativeAudio: false,
+
+ /** Function: init
+ * Register handler and enable or disable sound and status messages.
+ */
+ init: function() {
+ $('#emoticons-icon').click(function(e) {
+ self.Chat.Context.showEmoticonsMenu(e.currentTarget);
+ e.stopPropagation();
+ });
+ $('#chat-autoscroll-control').click(self.Chat.Toolbar.onAutoscrollControlClick);
+
+ var a = document.createElement('audio');
+ self.Chat.Toolbar._supportsNativeAudio = !!(a.canPlayType && a.canPlayType('audio/mpeg;').replace(/no/, ''));
+ $('#chat-sound-control').click(self.Chat.Toolbar.onSoundControlClick);
+ if(Candy.Util.cookieExists('candy-nosound')) {
+ $('#chat-sound-control').click();
+ }
+ $('#chat-statusmessage-control').click(self.Chat.Toolbar.onStatusMessageControlClick);
+ if(Candy.Util.cookieExists('candy-nostatusmessages')) {
+ $('#chat-statusmessage-control').click();
+ }
+ },
+
/** Function: show
* Show toolbar.
*/
@@ -382,6 +410,23 @@ Candy.View.Pane = (function(self, $) {
$('#chat-toolbar').hide();
},
+ /* Function: update
+ * Update toolbar for specific room
+ */
+ update: function(roomJid) {
+ var context = $('#chat-toolbar').find('.context'),
+ me = self.Room.getUser(roomJid);
+ if(!me || !me.isModerator()) {
+ context.hide();
+ } else {
+ context.show().click(function(e) {
+ self.Chat.Context.show(e.currentTarget, roomJid);
+ e.stopPropagation();
+ });
+ }
+ self.Chat.Toolbar.updateUsercount(self.Chat.rooms[roomJid].usercount);
+ },
+
/** Function: playSound
* Play sound (default method).
*/
@@ -390,15 +435,21 @@ Candy.View.Pane = (function(self, $) {
},
/** Function: onPlaySound
- * Sound play event handler.
+ * Sound play event handler. Uses native (HTML5) audio if supported
*
* 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', '');
+ try {
+ if(self.Chat.Toolbar._supportsNativeAudio) {
+ new Audio(Candy.View.getOptions().resources + 'notify.mp3').play();
+ } else {
+ var chatSoundPlayer = document.getElementById('chat-sound-player');
+ chatSoundPlayer.SetVariable('method:stop', '');
+ chatSoundPlayer.SetVariable('method:play', '');
+ }
+ } catch (e) {}
},
/** Function: onSoundControlClick
@@ -509,7 +560,7 @@ Candy.View.Pane = (function(self, $) {
*/
hide: function(callback) {
$('#chat-modal').fadeOut('fast', function() {
- $('#chat-modal-body').text('');
+ $('#chat-modal-body').text('');
$('#chat-modal-overlay').hide();
});
// restore initial esc handling
@@ -580,7 +631,7 @@ Candy.View.Pane = (function(self, $) {
displayUsername: Candy.Core.isAnonymousConnection() || !presetJid,
presetJid: presetJid ? presetJid : false
}));
- $('#login-form').children()[0].focus();
+ $('#login-form').children(':input:first').focus();
// register submit handler
$('#login-form').submit(function(event) {
@@ -604,7 +655,7 @@ Candy.View.Pane = (function(self, $) {
return false;
});
},
-
+
/** Function: showEnterPasswordForm
* Shows a form for entering room password
*
@@ -621,18 +672,18 @@ Candy.View.Pane = (function(self, $) {
_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
@@ -647,7 +698,7 @@ Candy.View.Pane = (function(self, $) {
_loginSubmit: $.i18n._('loginSubmit')
}));
$('#nickname').focus();
-
+
// register submit handler
$('#nickname-conflict-form').submit(function() {
var nickname = $('#nickname').val();
@@ -659,7 +710,7 @@ Candy.View.Pane = (function(self, $) {
return false;
});
},
-
+
/** Function: showError
* Show modal containing error message
*
@@ -710,11 +761,15 @@ Candy.View.Pane = (function(self, $) {
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');
+ tooltip
+ .css({'left': posLeft.px, 'top': posTop.px})
+ .removeClass('left-top left-bottom right-top right-bottom')
+ .addClass(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}); });
+ $('#tooltip').stop(false, true).fadeOut('fast', function() {$(this).css({'top': 0, 'left': 0});});
});
}
},
@@ -739,18 +794,18 @@ Candy.View.Pane = (function(self, $) {
/** Function: show
* Show context menu (positions it according to the window height/width)
*
+ * 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
+ *
* 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
+ * Triggers:
+ * candy:view.roster.after-context-menu using {roomJid, user, elements}
*/
show: function(elem, roomJid, user) {
elem = $(elem);
@@ -795,24 +850,68 @@ Candy.View.Pane = (function(self, $) {
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');
+ menu
+ .css({'left': posLeft.px, 'top': posTop.px})
+ .removeClass('left-top left-bottom right-top right-bottom')
+ .addClass(posLeft.backgroundPositionAlignment + '-' + posTop.backgroundPositionAlignment)
+ .fadeIn('fast');
- Candy.View.Event.Roster.afterContextMenu({'roomJid' : roomJid, 'user' : user, 'element': menu });
+ var evtData = {'roomJid' : roomJid, 'user' : user, 'element': menu};
+
+ // deprecated
+ Candy.View.Event.Roster.afterContextMenu(evtData);
+
+ /** Event: candy:view.roster.after-context-menu
+ * After context menu display
+ *
+ * Parameters:
+ * (String) roomJid - room where the context menu has been triggered
+ * (Candy.Core.ChatUser) user - User
+ * (jQuery.Element) element - Menu element
+ */
+ $(Candy).triggerHandler('candy:view.roster.after-context-menu', evtData);
return true;
}
},
/** Function: getMenuLinks
- * Extends <initialMenuLinks> with <Candy.View.Event.Roster.onContextMenu> links and returns those.
+ * Extends <initialMenuLinks> with menu links gathered from candy:view.roster.contextmenu
+ *
+ * Parameters:
+ * (String) roomJid - Room in which the menu will be displayed
+ * (Candy.Core.ChatUser) user - User
+ * (jQuery.Element) elem - Parent element of the context menu
+ *
+ * Triggers:
+ * candy:view.roster.context-menu using {roomJid, user, elem}
*
* 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;
+ var menulinks, extramenulinks, id;
+
+ var evtData = {'roomJid' : roomJid, 'user' : user, 'elem': elem};
+ // deprecated
+ extramenulinks = Candy.View.Event.Roster.onContextMenu(evtData);
+
+ evtData.menulinks = $.extend(this.initialMenuLinks(elem), extramenulinks);
+
+ /** Event: candy:view.roster.context-menu
+ * Modify existing menu links (add links)
+ *
+ * In order to modify the links you need to change the object passed with an additional
+ * key "menulinks" containing the menulink object.
+ *
+ * Parameters:
+ * (String) roomJid - Room on which the menu should be displayed
+ * (Candy.Core.ChatUser) user - User
+ * (jQuery.Element) elem - Parent element of the context menu
+ */
+ $(Candy).triggerHandler('candy:view.roster.context-menu', evtData);
+
+ menulinks = evtData.menulinks;
for(id in menulinks) {
if(menulinks.hasOwnProperty(id) && menulinks[id].requiredPermission !== undefined && !menulinks[id].requiredPermission(user, self.Room.getUser(roomJid), elem)) {
@@ -960,8 +1059,11 @@ Candy.View.Pane = (function(self, $) {
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');
+ menu
+ .css({'left': posLeft.px, 'top': posTop.px})
+ .removeClass('left-top left-bottom right-top right-bottom')
+ .addClass(posLeft.backgroundPositionAlignment + '-' + posTop.backgroundPositionAlignment)
+ .fadeIn('fast');
return true;
}
@@ -985,8 +1087,8 @@ Candy.View.Pane = (function(self, $) {
* - <Candy.View.Pane.Chat.addTab>
* - <getPane>
*
- * Calls:
- * - <Candy.View.Event.Room.onAdd>
+ * Triggers:
+ * candy:view.room.after-add using {roomJid, type, element}
*
* Returns:
* (String) - the room id of the element created.
@@ -999,7 +1101,7 @@ Candy.View.Pane = (function(self, $) {
}
var roomId = Candy.Util.jidToId(roomJid);
- self.Chat.rooms[roomJid] = { id: roomId, usercount: 0, name: roomName, type: roomType, messageCount: 0, scrollPosition: -1 };
+ 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,
@@ -1019,7 +1121,20 @@ Candy.View.Pane = (function(self, $) {
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)});
+ var evtData = {'roomJid': roomJid, 'type': roomType, 'element': self.Room.getPane(roomJid)};
+
+ // deprecated
+ Candy.View.Event.Room.onAdd(evtData);
+
+ /** Event: candy:view.room.after-add
+ * After initialising a room
+ *
+ * Parameters:
+ * (String) roomJid - Room JID
+ * (String) type - Room Type
+ * (jQuery.Element) element - Room element
+ */
+ $(Candy).triggerHandler('candy:view.room.after-add', evtData);
return roomId;
},
@@ -1029,6 +1144,10 @@ Candy.View.Pane = (function(self, $) {
*
* Parameters:
* (String) roomJid - room jid to show
+ *
+ * Triggers:
+ * candy:view.room.after-show using {roomJid, element}
+ * candy:view.room.after-hide using {roomJid, element}
*/
show: function(roomJid) {
var roomId = self.Chat.rooms[roomJid].id;
@@ -1037,17 +1156,41 @@ Candy.View.Pane = (function(self, $) {
if(elem.attr('id') === ('chat-room-' + roomId)) {
elem.show();
Candy.View.getCurrent().roomJid = roomJid;
- self.Chat.updateToolbar(roomJid);
self.Chat.setActiveTab(roomJid);
+ self.Chat.Toolbar.update(roomJid);
self.Chat.clearUnreadMessages(roomJid);
self.Room.setFocusToForm(roomJid);
self.Room.scrollToBottom(roomJid);
- Candy.View.Event.Room.onShow({'roomJid': roomJid, 'element' : elem});
+ var evtData = {'roomJid': roomJid, 'element' : elem};
+
+ // deprecated
+ Candy.View.Event.Room.onShow(evtData);
+
+ /** Event: candy:view.room.after-show
+ * After showing a room
+ *
+ * Parameters:
+ * (String) roomJid - Room JID
+ * (jQuery.Element) element - Room element
+ */
+ $(Candy).triggerHandler('candy:view.room.after-show', evtData);
+
} else {
elem.hide();
- Candy.View.Event.Room.onHide({'roomJid': roomJid, 'element' : elem});
+ var evtData = {'roomJid': roomJid, 'element' : elem};
+ // deprecated
+ Candy.View.Event.Room.onHide(evtData);
+
+ /** Event: candy:view.room.after-hide
+ * After hiding a room
+ *
+ * Parameters:
+ * (String) roomJid - Room JID
+ * (jQuery.Element) element - Room element
+ */
+ $(Candy).triggerHandler('candy:view.room.after-hide', evtData);
}
});
},
@@ -1055,6 +1198,9 @@ Candy.View.Pane = (function(self, $) {
/** Function: setSubject
* Called when someone changes the subject in the channel
*
+ * Triggers:
+ * candy:view.room.after-subject-change using {roomJid, element, subject}
+ *
* Parameters:
* (String) roomJid - Room Jid
* (String) subject - The new subject
@@ -1069,13 +1215,30 @@ Candy.View.Pane = (function(self, $) {
self.Room.appendToMessagePane(roomJid, html);
self.Room.scrollToBottom(roomJid);
- Candy.View.Event.Room.onSubjectChange({'roomJid': roomJid, 'element' : self.Room.getPane(roomJid), 'subject' : subject});
+ var evtData = {'roomJid': roomJid, 'element' : self.Room.getPane(roomJid), 'subject' : subject};
+
+ // deprecated
+ Candy.View.Event.Room.onSubjectChange(evtData);
+
+ /** Event: candy:view.room.after-subject-change
+ * After changing the subject of a room
+ *
+ * Parameters:
+ * (String) roomJid - Room JID
+ * (jQuery.Element) element - Room element
+ * (String) subject - New subject
+ */
+ $(Candy).triggerHandler('candy:view.room.after-subject-change', evtData);
},
/** 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)
+ * 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)
+ *
+ * Triggers:
+ * candy:view.room.after-close using {roomJid}
*
* Parameters:
* (String) roomJid - Room to close
@@ -1101,7 +1264,18 @@ Candy.View.Pane = (function(self, $) {
}
delete self.Chat.rooms[roomJid];
- Candy.View.Event.Room.onClose({'roomJid' : roomJid});
+ var evtData = {'roomJid' : roomJid};
+
+ // deprecated
+ Candy.View.Event.Room.onClose(evtData);
+
+ /** Event: candy:view.room.after-close
+ * After closing a room
+ *
+ * Parameters:
+ * (String) roomJid - Room JID
+ */
+ $(Candy).triggerHandler('candy:view.room.after-close', evtData);
},
/** Function: appendToMessagePane
@@ -1132,7 +1306,7 @@ Candy.View.Pane = (function(self, $) {
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.Room.getPane(roomJid, '.message-pane').children().slice(0, options.remove).remove();
self.Chat.rooms[roomJid].messageCount -= options.remove;
}
}
@@ -1332,8 +1506,8 @@ Candy.View.Pane = (function(self, $) {
* (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>
+ * Triggers:
+ * candy:view.private-room.after-open using {roomJid, type, element}
*/
open: function(roomJid, roomName, switchToRoom, isNoConferenceRoomJid) {
var user = isNoConferenceRoomJid ? Candy.Core.getUser() : self.Room.getUser(Strophe.getBareJidFromJid(roomJid));
@@ -1347,16 +1521,32 @@ Candy.View.Pane = (function(self, $) {
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)});
+ var evtData = {'roomJid': roomJid, type: 'chat', 'element': self.Room.getPane(roomJid)};
+
+ // deprecated
+ Candy.View.Event.Room.onAdd(evtData);
+
+ /** Event: candy:view.private-room.after-open
+ * After opening a new private room
+ *
+ * Parameters:
+ * (String) roomJid - Room JID
+ * (String) type - 'chat'
+ * (jQuery.Element) element - User element
+ */
+ $(Candy).triggerHandler('candy:view.private-room.after-open', evtData);
},
/** Function: setStatus
@@ -1399,11 +1589,34 @@ Candy.View.Pane = (function(self, $) {
* (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
+ *
+ * Triggers:
+ * candy:view.roster.before-update using {roomJid, user, action, element}
+ * candy:view.roster.after-update using {roomJid, user, action, element}
*/
update: function(roomJid, user, action, currentUser) {
var roomId = self.Chat.rooms[roomJid].id,
userId = Candy.Util.jidToId(user.getJid()),
- usercountDiff = -1;
+ usercountDiff = -1,
+ userElem = $('#user-' + roomId + '-' + userId);
+
+ var evtData = {'roomJid': roomJid, type: null, 'user': user};
+
+ /** Event: candy:view.roster.before-update
+ * Before updating the roster of a room
+ *
+ * Parameters:
+ * (String) roomJid - Room JID
+ * (Candy.Core.ChatUser) user - User
+ * (String) action - [join, leave, kick, ban]
+ * (jQuery.Element) element - User element
+ */
+ $(Candy).triggerHandler('candy:view.roster.before-update', {
+ 'roomJid' : roomJid,
+ 'user' : user,
+ 'action': action,
+ 'element': userElem
+ });
// a user joined the room
if(action === 'join') {
@@ -1419,8 +1632,7 @@ Candy.View.Pane = (function(self, $) {
me: currentUser !== undefined && user.getNick() === currentUser.getNick(),
tooltipRole: $.i18n._('tooltipRole'),
tooltipIgnored: $.i18n._('tooltipIgnored')
- }),
- userElem = $('#user-' + roomId + '-' + userId);
+ });
if(userElem.length < 1) {
var userInserted = false,
@@ -1459,6 +1671,10 @@ Candy.View.Pane = (function(self, $) {
usercountDiff = 0;
userElem.replaceWith(html);
$('#user-' + roomId + '-' + userId).css({opacity: 1}).show();
+ // it's me, update the toolbar
+ if(currentUser !== undefined && user.getNick() === currentUser.getNick() && self.Room.getUser(roomJid)) {
+ self.Chat.Toolbar.update(roomJid);
+ }
}
// Presence of client
@@ -1505,7 +1721,26 @@ Candy.View.Pane = (function(self, $) {
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)});
+ var evtData = {
+ 'roomJid' : roomJid,
+ 'user' : user,
+ 'action': action,
+ 'element': $('#user-' + roomId + '-' + userId)
+ };
+
+ // deprecated
+ Candy.View.Event.Roster.onUpdate(evtData);
+
+ /** Event: candy:view.roster.after-update
+ * After updating a room's roster
+ *
+ * Parameters:
+ * (String) roomJid - Room JID
+ * (Candy.Core.ChatUser) user - User
+ * (String) action - [join, leave, kick, ban]
+ * (jQuery.Element) element - User element
+ */
+ $(Candy).triggerHandler('candy:view.roster.after-update', evtData);
},
/** Function: userClick
@@ -1523,7 +1758,7 @@ Candy.View.Pane = (function(self, $) {
* (String) elementId - Specific element to do the animation on
*/
joinAnimation: function(elementId) {
- $('#' + elementId).stop(true).slideDown('normal', function() { $(this).animate({ opacity: 1 }); });
+ $('#' + elementId).stop(true).slideDown('normal', function() {$(this).animate({opacity: 1});});
},
/** Function: leaveAnimation
@@ -1533,9 +1768,9 @@ Candy.View.Pane = (function(self, $) {
* (String) elementId - Specific element to do the animation on
*/
leaveAnimation: function(elementId) {
- $('#' + elementId).stop(true).attr('id', '#' + elementId + '-leaving').animate({ opacity: 0 }, {
+ $('#' + elementId).stop(true).attr('id', '#' + elementId + '-leaving').animate({opacity: 0}, {
complete: function() {
- $(this).slideUp('normal', function() { $(this).remove(); });
+ $(this).slideUp('normal', function() {$(this).remove();});
}
});
}
@@ -1551,13 +1786,29 @@ Candy.View.Pane = (function(self, $) {
*
* Parameters:
* (Event) event - Triggered event
+ *
+ * Triggers:
+ * candy:view.message.before-send using {message}
*/
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);
+ // deprecated
message = Candy.View.Event.Message.beforeSend(message);
+ var evtData = {message: message};
+
+ /** Event: candy:view.message.before-send
+ * Before sending a message
+ *
+ * Parameters:
+ * (String) message - Message text
+ */
+ $(Candy).triggerHandler('candy:view.message.before-send', evtData);
+
+ message = evtData.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) {
@@ -1576,27 +1827,67 @@ Candy.View.Pane = (function(self, $) {
* (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.
+ *
+ * Triggers:
+ * candy:view.message.before-show using {roomJid, name, message}
+ * candy.view.message.before-render using {template, templateData}
+ * candy:view.message.after-show using {roomJid, name, message, element}
*/
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});
+
+ var evtData = {'roomJid': roomJid, 'name': name, 'message': message};
+ // deprecated
+ evtData.message = Candy.View.Event.Message.beforeShow(evtData);
+
+ /** Event: candy:view.message.before-show
+ * Before showing a new message
+ *
+ * Parameters:
+ * (String) roomJid - Room JID
+ * (String) name - Name of the sending user
+ * (String) message - Message text
+ */
+ $(Candy).triggerHandler('candy:view.message.before-show', evtData);
+
+ message = evtData.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())
- });
+ var renderEvtData = {
+ template: Candy.View.Template.Message.item,
+ templateData: {
+ name: name,
+ displayName: Candy.Util.crop(name, Candy.View.getOptions().crop.message.nickname),
+ message: message,
+ time: Candy.Util.localizedTime(timestamp || new Date().toGMTString())
+ }
+ };
+
+ /** Event: candy:view.message.before-render
+ * Before rendering the message element
+ *
+ * Parameters:
+ * (String) template - Template to use
+ * (Object) templateData - Template data consists of:
+ * - (String) name - Name of the sending user
+ * - (String) displayName - Cropped name of the sending user
+ * - (String) message - Message text
+ * - (String) time - Localized time
+ */
+ $(Candy).triggerHandler('candy:view.message.before-render', renderEvtData);
+
+ var html = Mustache.to_html(renderEvtData.template, renderEvtData.templateData);
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) {
+ elem.find('a.label').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)) {
+ var room = Candy.Core.getRoom(roomJid);
+ if(room && name !== self.Room.getUser(Candy.View.getCurrent().roomJid).getNick() && room.getRoster().get(roomJid + '/' + name)) {
Candy.View.Pane.PrivateRoom.open(roomJid + '/' + name, name, true);
}
});
@@ -1612,7 +1903,21 @@ Candy.View.Pane = (function(self, $) {
self.Room.scrollToBottom(roomJid);
}
- Candy.View.Event.Message.onShow({'roomJid': roomJid, 'element': elem, 'nick': name, 'message': message});
+ var evtData = {'roomJid': roomJid, 'element': elem, 'name': name, 'message': message};
+
+ // deprecated
+ Candy.View.Event.Message.onShow(evtData);
+
+ /** Event: candy:view.message.after-show
+ * Triggered after showing a message
+ *
+ * Parameters:
+ * (String) roomJid - Room JID
+ * (jQuery.Element) element - User element
+ * (String) name - Name of the sending user
+ * (String) message - Message text
+ */
+ $(Candy).triggerHandler('candy:view.message.after-show', evtData);
}
};
diff --git a/src/view/template.js b/src/view/template.js
index 696cb52..d2fbdb0 100644
--- a/src/view/template.js
+++ b/src/view/template.js
@@ -7,6 +7,7 @@
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
+ * (c) 2012 Patrick Stadler & Michael Weibel. All rights reserved.
*/
/** Class: Candy.View.Template
@@ -26,8 +27,8 @@ Candy.View.Template = (function(self){
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>',
+ adminMessage: '<li><small>{{time}}</small><div class="adminmessage"><span class="label">{{sender}}</span><span class="spacer">▸</span>{{subject}} {{message}}</div></li>',
+ infoMessage: '<li><small>{{time}}</small><div class="infomessage"><span class="spacer">•</span>{{subject}} {{message}}</div></li>',
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"'
@@ -35,28 +36,28 @@ Candy.View.Template = (function(self){
+ ' value="always" /><param name="FlashVars" value="listener=audioplayerListener&amp;mp3={{resourcesPath}}notify.mp3" />'
+ '</object>',
Context: {
- menu: '<div id="context-menu"><ul></ul></div>',
+ menu: '<div id="context-menu"><i class="arrow arrow-top"></i><ul></ul><i class="arrow arrow-bottom"></i></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>'
+ tooltip: '<div id="tooltip"><i class="arrow arrow-top"></i><div></div><i class="arrow arrow-bottom"></i></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>'
+ subject: '<li><small>{{time}}</small><div class="subject"><span class="label">{{roomName}}</span><span class="spacer">▸</span>{{_roomSubject}} {{subject}}</div></li>',
+ form: '<div class="message-form-wrapper"><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></div>'
};
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>'
+ 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}}">&#x25BE;</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>'
+ pane: '<div class="message-pane-wrapper"><ul class="message-pane"></ul></div>',
+ item: '<li><small>{{time}}</small><div><a class="label" href="#" class="name">{{displayName}}</a><span class="spacer">▸</span>{{{message}}}</div></li>'
};
self.Login = {
@@ -66,7 +67,7 @@ Candy.View.Template = (function(self){
+ '{{#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">'
diff --git a/src/view/translation.js b/src/view/translation.js
index 760bffd..6566ba3 100644
--- a/src/view/translation.js
+++ b/src/view/translation.js
@@ -7,6 +7,7 @@
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
+ * (c) 2012 Patrick Stadler & Michael Weibel. All rights reserved.
*/
/** Class: Candy.View.Translation
@@ -78,7 +79,6 @@ Candy.View.Translation = {
'antiSpamMessage' : 'Please do not spam. You have been blocked for a short-time.'
},
-
'de' : {
'status': 'Status: %s',
'statusConnecting': 'Verbinden...',
@@ -144,32 +144,31 @@ Candy.View.Translation = {
'antiSpamMessage' : 'Bitte nicht spammen. Du wurdest für eine kurze Zeit blockiert.'
},
-
'fr' : {
- 'status': 'Status: %s',
- 'statusConnecting': 'Connecter...',
+ 'status': 'Status : %s',
+ 'statusConnecting': 'Connexion…',
'statusConnected' : 'Connecté.',
- 'statusDisconnecting': 'Déconnecter....',
+ 'statusDisconnecting': 'Déconnexion…',
'statusDisconnected' : 'Déconnecté.',
- 'statusAuthfail': 'Authentification a échoué',
+ 'statusAuthfail': 'L\'authentification a échoué',
- 'roomSubject' : 'Sujet:',
+ 'roomSubject' : 'Sujet :',
'messageSubmit': 'Envoyer',
- 'labelUsername': 'Nom d\'utilisateur:',
- 'labelPassword': 'Mot de passe:',
- 'loginSubmit' : 'Inscription',
+ 'labelUsername': 'Nom d\'utilisateur :',
+ 'labelPassword': 'Mot de passe :',
+ 'loginSubmit' : 'Connexion',
'loginInvalid' : 'JID invalide',
- 'reason' : 'Justification:',
- 'subject' : 'Titre:',
- 'reasonWas' : 'Justification: %s.',
+ 'reason' : 'Motif :',
+ 'subject' : 'Titre :',
+ 'reasonWas' : 'Motif : %s.',
'kickActionLabel' : 'Kick',
- 'youHaveBeenKickedBy' : 'Tu as été expulsé de le salon %1$s (%2$s)',
- 'youHaveBeenKicked' : 'Tu as été expulsé de le salon %s',
+ 'youHaveBeenKickedBy' : 'Vous avez été expulsé du salon %1$s (%2$s)',
+ 'youHaveBeenKicked' : 'Vous avez été expulsé du 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',
+ 'youHaveBeenBannedBy' : 'Vous avez été banni du salon %1$s (%2$s)',
+ 'youHaveBeenBanned' : 'Vous avez été banni du salon %s',
'privateActionLabel' : 'Chat privé',
'ignoreActionLabel' : 'Ignorer',
@@ -184,33 +183,32 @@ Candy.View.Translation = {
'userHasBeenKickedFromRoom': '%s a été expulsé du salon.',
'userHasBeenBannedFromRoom': '%s a été banni du salon.',
- 'presenceUnknownWarningSubject': 'Note:',
+ 'presenceUnknownWarningSubject': 'Note :',
'presenceUnknownWarning' : 'Cet utilisateur n\'est malheureusement plus connecté, le message ne sera pas envoyé.',
- 'dateFormat': 'dd.mm.yyyy',
+ 'dateFormat': 'dd/mm/yyyy',
'timeFormat': 'HH:MM:ss',
'tooltipRole' : 'Modérateur',
- 'tooltipIgnored' : 'Tu ignores cette personne',
+ 'tooltipIgnored' : 'Vous ignorez cette personne',
'tooltipEmoticons' : 'Smileys',
- 'tooltipSound' : 'Jouer un son lorsque tu reçois de nouveaux messages privés',
- 'tooltipAutoscroll' : 'Auto-defilement',
+ 'tooltipSound' : 'Jouer un son lors de la réception de nouveaux messages privés',
+ 'tooltipAutoscroll' : 'Défilement automatique',
'tooltipStatusmessage' : 'Messages d\'état',
- 'tooltipAdministration' : 'Administrer le salon',
+ 'tooltipAdministration' : 'Administration du 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.',
+ 'passwordEnteredInvalid' : 'Le mot de passe pour le salon "%s" est invalide.',
- 'nicknameConflict': 'Le nom d\'utilisateur est déjà utilisé. Choisi un autre.',
+ 'nicknameConflict': 'Le nom d\'utilisateur est déjà utilisé. Veuillez en choisir 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.',
+ 'errorMembersOnly': 'Vous ne pouvez pas entrer dans le salon "%s" : droits insuffisants.',
+ 'errorMaxOccupantsReached': 'Vous ne pouvez pas entrer dans 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..'
+ 'antiSpamMessage' : 'Merci de ne pas envoyer de spam. Vous avez été bloqué pendant une courte période..'
},
-
'nl' : {
'status': 'Status: %s',
'statusConnecting': 'Verbinding maken...',
@@ -276,7 +274,6 @@ Candy.View.Translation = {
'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...',
@@ -342,7 +339,6 @@ Candy.View.Translation = {
'antiSpamMessage' : 'Por favor, no hagas spam. Has sido bloqueado temporalmente.'
},
-
'cn': {
'status': '状态: %s',
'statusConnecting': '连接中...',
@@ -406,7 +402,6 @@ Candy.View.Translation = {
'antiSpamMessage': '因为您在短时间内发送过多的消息 服务器要阻止您一小段时间。'
},
-
'ja' : {
'status' : 'ステータス: %s',
'statusConnecting' : '接続中…',
@@ -471,5 +466,330 @@ Candy.View.Translation = {
'errorMaxOccupantsReached' : '"%s"の部屋に入ることができません: 参加者の数はすでに上限に達しました。',
'antiSpamMessage' : 'スパムなどの行為はやめてください。あなたは一時的にブロックされました。'
+ },
+ 'sv' : {
+ 'status': 'Status: %s',
+ 'statusConnecting': 'Ansluter...',
+ 'statusConnected' : 'Ansluten',
+ 'statusDisconnecting': 'Kopplar från...',
+ 'statusDisconnected' : 'Frånkopplad',
+ 'statusAuthfail': 'Autentisering misslyckades',
+
+ 'roomSubject' : 'Ämne:',
+ 'messageSubmit': 'Skicka',
+
+ 'labelUsername': 'Användarnamn:',
+ 'labelPassword': 'Lösenord:',
+ 'loginSubmit' : 'Logga in',
+ 'loginInvalid' : 'Ogiltigt JID',
+
+ 'reason' : 'Anledning:',
+ 'subject' : 'Ämne:',
+ 'reasonWas' : 'Anledningen var: %s.',
+ 'kickActionLabel' : 'Sparka ut',
+ 'youHaveBeenKickedBy' : 'Du har blivit utsparkad från %2$s av %1$s',
+ 'youHaveBeenKicked' : 'Du har blivit utsparkad från %s',
+ 'banActionLabel' : 'Bannlys',
+ 'youHaveBeenBannedBy' : 'Du har blivit bannlyst från %1$s av %2$s',
+ 'youHaveBeenBanned' : 'Du har blivit bannlyst från %s',
+
+ 'privateActionLabel' : 'Privat chatt',
+ 'ignoreActionLabel' : 'Blockera',
+ 'unignoreActionLabel' : 'Avblockera',
+
+ 'setSubjectActionLabel': 'Ändra ämne',
+
+ 'administratorMessageSubject' : 'Administratör',
+
+ 'userJoinedRoom' : '%s kom in i rummet.',
+ 'userLeftRoom' : '%s har lämnat rummet.',
+ 'userHasBeenKickedFromRoom': '%s har blivit utsparkad ur rummet.',
+ 'userHasBeenBannedFromRoom': '%s har blivit bannlyst från rummet.',
+
+ 'presenceUnknownWarningSubject': 'Notera:',
+ 'presenceUnknownWarning' : 'Denna användare kan vara offline. Vi kan inte följa dennes närvaro.',
+
+ 'dateFormat': 'yyyy-mm-dd',
+ 'timeFormat': 'HH:MM:ss',
+
+ 'tooltipRole' : 'Moderator',
+ 'tooltipIgnored' : 'Du blockerar denna användare',
+ 'tooltipEmoticons' : 'Smilies',
+ 'tooltipSound' : 'Spela upp ett ljud vid nytt privat meddelande',
+ 'tooltipAutoscroll' : 'Autoskrolla',
+ 'tooltipStatusmessage' : 'Visa statusmeddelanden',
+ 'tooltipAdministration' : 'Rumadministrering',
+ 'tooltipUsercount' : 'Antal användare i rummet',
+
+ 'enterRoomPassword' : 'Rummet "%s" är lösenordsskyddat.',
+ 'enterRoomPasswordSubmit' : 'Anslut till rum',
+ 'passwordEnteredInvalid' : 'Ogiltigt lösenord för rummet "%s".',
+
+ 'nicknameConflict': 'Upptaget användarnamn. Var god välj ett annat.',
+
+ 'errorMembersOnly': 'Du kan inte ansluta till rummet "%s": Otillräckliga rättigheter.',
+ 'errorMaxOccupantsReached': 'Du kan inte ansluta till rummet "%s": Rummet är fullt.',
+
+ 'antiSpamMessage' : 'Var god avstå från att spamma. Du har blivit blockerad för en kort stund.'
+ },
+ 'it' : {
+ 'status': 'Stato: %s',
+ 'statusConnecting': 'Connessione...',
+ 'statusConnected' : 'Connessione',
+ 'statusDisconnecting': 'Disconnessione...',
+ 'statusDisconnected' : 'Disconnesso',
+ 'statusAuthfail': 'Autenticazione fallita',
+
+ 'roomSubject' : 'Oggetto:',
+ 'messageSubmit': 'Invia',
+
+ 'labelUsername': 'Nome utente:',
+ 'labelPassword': 'Password:',
+ 'loginSubmit' : 'Login',
+ 'loginInvalid' : 'JID non valido',
+
+ 'reason' : 'Ragione:',
+ 'subject' : 'Oggetto:',
+ 'reasonWas' : 'Ragione precedente: %s.',
+ 'kickActionLabel' : 'Espelli',
+ 'youHaveBeenKickedBy' : 'Sei stato espulso da %2$s da %1$s',
+ 'youHaveBeenKicked' : 'Sei stato espulso da %s',
+ 'banActionLabel' : 'Escluso',
+ 'youHaveBeenBannedBy' : 'Sei stato escluso da %1$s da %2$s',
+ 'youHaveBeenBanned' : 'Sei stato escluso da %s',
+
+ 'privateActionLabel' : 'Stanza privata',
+ 'ignoreActionLabel' : 'Ignora',
+ 'unignoreActionLabel' : 'Non ignorare',
+
+ 'setSubjectActionLabel': 'Cambia oggetto',
+
+ 'administratorMessageSubject' : 'Amministratore',
+
+ 'userJoinedRoom' : '%s si è unito alla stanza.',
+ 'userLeftRoom' : '%s ha lasciato la stanza.',
+ 'userHasBeenKickedFromRoom': '%s è stato espulso dalla stanza.',
+ 'userHasBeenBannedFromRoom': '%s è stato escluso dalla stanza.',
+
+ 'presenceUnknownWarningSubject': 'Nota:',
+ 'presenceUnknownWarning' : 'Questo utente potrebbe essere offline. Non possiamo tracciare la sua presenza.',
+
+ 'dateFormat': 'dd/mm/yyyy',
+ 'timeFormat': 'HH:MM:ss',
+
+ 'tooltipRole' : 'Moderatore',
+ 'tooltipIgnored' : 'Stai ignorando questo utente',
+ 'tooltipEmoticons' : 'Emoticons',
+ 'tooltipSound' : 'Riproduci un suono quando arrivano messaggi privati',
+ 'tooltipAutoscroll' : 'Autoscroll',
+ 'tooltipStatusmessage' : 'Mostra messaggi di stato',
+ 'tooltipAdministration' : 'Amministrazione stanza',
+ 'tooltipUsercount' : 'Partecipanti alla stanza',
+
+ 'enterRoomPassword' : 'La stanza "%s" è protetta da password.',
+ 'enterRoomPasswordSubmit' : 'Unisciti alla stanza',
+ 'passwordEnteredInvalid' : 'Password non valida per la stanza "%s".',
+
+ 'nicknameConflict': 'Nome utente già in uso. Scegline un altro.',
+
+ 'errorMembersOnly': 'Non puoi unirti alla stanza "%s": Permessi insufficienti.',
+ 'errorMaxOccupantsReached': 'Non puoi unirti alla stanza "%s": Troppi partecipanti.',
+
+ 'antiSpamMessage' : 'Per favore non scrivere messaggi pubblicitari. Sei stato bloccato per un po\' di tempo.'
+ },
+ 'pt': {
+ 'status': 'Status: %s',
+ 'statusConnecting': 'Conectando...',
+ 'statusConnected' : 'Conectado',
+ 'statusDisconnecting': 'Desligando...',
+ 'statusDisconnected' : 'Desligado',
+ 'statusAuthfail': 'Falha na autenticação',
+
+ 'roomSubject' : 'Assunto:',
+ 'messageSubmit': 'Enviar',
+
+ 'labelUsername': 'Usuário:',
+ 'labelPassword': 'Senha:',
+ 'loginSubmit' : 'Entrar',
+ 'loginInvalid' : 'JID inválido',
+
+ 'reason' : 'Motivo:',
+ 'subject' : 'Assunto:',
+ 'reasonWas' : 'O motivo foi: %s.',
+ 'kickActionLabel' : 'Excluir',
+ 'youHaveBeenKickedBy' : 'Você foi excluido de %1$s por %2$s',
+ 'youHaveBeenKicked' : 'Você foi excluido de %s',
+ 'banActionLabel' : 'Bloquear',
+ 'youHaveBeenBannedBy' : 'Você foi excluido permanentemente de %1$s por %2$s',
+ 'youHaveBeenBanned' : 'Você foi excluido permanentemente de %s',
+
+ 'privateActionLabel' : 'Bate-papo privado',
+ 'ignoreActionLabel' : 'Ignorar',
+ 'unignoreActionLabel' : 'Não ignorar',
+
+ 'setSubjectActionLabel': 'Trocar Assunto',
+
+ 'administratorMessageSubject' : 'Administrador',
+
+ 'userJoinedRoom' : '%s entrou na sala.',
+ 'userLeftRoom' : '%s saiu da sala.',
+ 'userHasBeenKickedFromRoom': '%s foi excluido da sala.',
+ 'userHasBeenBannedFromRoom': '%s foi excluido permanentemente da sala.',
+
+ 'presenceUnknownWarning' : 'Este usuário pode estar desconectado. Não é possível determinar o status.',
+ 'presenceUnknownWarning' : 'Este usuário poderá ser desligado..',
+
+ 'dateFormat': 'dd.mm.yyyy',
+ 'timeFormat': 'HH:MM:ss',
+
+ 'tooltipRole' : 'Moderador',
+ 'tooltipIgnored' : 'Você ignora este usuário',
+ 'tooltipEmoticons' : 'Emoticons',
+ 'tooltipSound' : 'Reproduzir o som para novas mensagens privados',
+ 'tooltipAutoscroll' : 'Deslocamento automático',
+ 'tooltipStatusmessage' : 'Mostrar mensagens de status',
+ 'tooltipAdministration' : 'Administração da sala',
+ 'tooltipUsercount' : 'Usuários na sala',
+
+ 'enterRoomPassword' : 'A sala "%s" é protegida por senha.',
+ 'enterRoomPasswordSubmit' : 'Junte-se à sala',
+ 'passwordEnteredInvalid' : 'Senha incorreta para a sala "%s".',
+
+ 'nicknameConflict': 'O nome de usuário já está em uso. Por favor, escolha outro.',
+
+ 'errorMembersOnly': 'Você não pode participar da sala "%s": privilégios insuficientes.',
+ 'errorMaxOccupantsReached': 'Você não pode participar da sala "%s": muitos participantes.',
+
+ 'antiSpamMessage' : 'Por favor, não envie spam. Você foi bloqueado temporariamente.'
+ },
+ 'ru' : {
+ 'status': 'Статус: %s',
+ 'statusConnecting': 'Подключение...',
+ 'statusConnected' : 'Подключено',
+ 'statusDisconnecting': 'Отключение...',
+ 'statusDisconnected' : 'Отключено',
+ 'statusAuthfail': 'Неверный логин',
+
+ 'roomSubject' : 'Топик:',
+ 'messageSubmit': 'Послать',
+
+ 'labelUsername': 'Имя:',
+ 'labelPassword': 'Пароль:',
+ 'loginSubmit' : 'Логин',
+ 'loginInvalid' : 'Неверный JID',
+
+ 'reason' : 'Причина:',
+ 'subject' : 'Топик:',
+ 'reasonWas' : 'Причина была: %s.',
+ 'kickActionLabel' : 'Выбросить',
+ 'youHaveBeenKickedBy' : 'Пользователь %1$s выбросил вас из чата %2$s',
+ 'youHaveBeenKicked' : 'Вас выбросили из чата %s',
+ 'banActionLabel' : 'Запретить доступ',
+ 'youHaveBeenBannedBy' : 'Пользователь %1$s запретил вам доступ в чат %2$s',
+ 'youHaveBeenBanned' : 'Вам запретили доступ в чат %s',
+
+ 'privateActionLabel' : 'Один-на-один чат',
+ 'ignoreActionLabel' : 'Игнорировать',
+ 'unignoreActionLabel' : 'Отменить игнорирование',
+
+ 'setSubjectActionLabel': 'Изменить топик',
+
+ 'administratorMessageSubject' : 'Администратор',
+
+ 'userJoinedRoom' : '%s вошёл в чат.',
+ 'userLeftRoom' : '%s вышел из чата.',
+ 'userHasBeenKickedFromRoom': '%s выброшен из чата.',
+ 'userHasBeenBannedFromRoom': '%s запрещён доступ в чат.',
+
+ 'presenceUnknownWarningSubject': 'Уведомление:',
+ 'presenceUnknownWarning' : 'Этот пользователь вероятнее всего оффлайн.',
+
+ 'dateFormat': 'mm.dd.yyyy',
+ 'timeFormat': 'HH:MM:ss',
+
+ 'tooltipRole' : 'Модератор',
+ 'tooltipIgnored' : 'Вы игнорируете этого пользователя.',
+ 'tooltipEmoticons' : 'Смайлики',
+ 'tooltipSound' : 'Озвучивать новое частное сообщение',
+ 'tooltipAutoscroll' : 'Авто-прокручивание',
+ 'tooltipStatusmessage' : 'Показывать статус сообщения',
+ 'tooltipAdministration' : 'Администрирование чат комнаты',
+ 'tooltipUsercount' : 'Участники чата',
+
+ 'enterRoomPassword' : 'Чат комната "%s" защищена паролем.',
+ 'enterRoomPasswordSubmit' : 'Войти в чат',
+ 'passwordEnteredInvalid' : 'Неверный пароль для комнаты "%s".',
+
+ 'nicknameConflict': 'Это имя уже используется. Пожалуйста выберите другое имя.',
+
+ 'errorMembersOnly': 'Вы не можете войти в чат "%s": Недостаточно прав доступа.',
+ 'errorMaxOccupantsReached': 'Вы не можете войти в чат "%s": Слишком много участников.',
+
+ 'antiSpamMessage' : 'Пожалуйста не рассылайте спам. Вас заблокировали на короткое время.'
+ },
+ 'ca': {
+ 'status': 'Estat: %s',
+ 'statusConnecting': 'Connectant...',
+ 'statusConnected' : 'Connectat',
+ 'statusDisconnecting': 'Desconnectant...',
+ 'statusDisconnected' : 'Desconnectat',
+ 'statusAuthfail': 'Ha fallat la autenticació',
+
+ 'roomSubject' : 'Assumpte:',
+ 'messageSubmit': 'Enviar',
+
+ 'labelUsername': 'Usuari:',
+ 'labelPassword': 'Clau:',
+ 'loginSubmit' : 'Entrar',
+ 'loginInvalid' : 'JID no vàlid',
+
+ 'reason' : 'Raó:',
+ 'subject' : 'Assumpte:',
+ 'reasonWas' : 'La raó ha estat: %s.',
+ 'kickActionLabel' : 'Expulsar',
+ 'youHaveBeenKickedBy' : 'Has estat expulsat de %1$s per %2$s',
+ 'youHaveBeenKicked' : 'Has estat expulsat de %s',
+ 'banActionLabel' : 'Prohibir',
+ 'youHaveBeenBannedBy' : 'Has estat expulsat permanentment de %1$s per %2$s',
+ 'youHaveBeenBanned' : 'Has estat expulsat permanentment de %s',
+
+ 'privateActionLabel' : 'Xat privat',
+ 'ignoreActionLabel' : 'Ignorar',
+ 'unignoreActionLabel' : 'No ignorar',
+
+ 'setSubjectActionLabel': 'Canviar assumpte',
+
+ 'administratorMessageSubject' : 'Administrador',
+
+ 'userJoinedRoom' : '%s ha entrat a la sala.',
+ 'userLeftRoom' : '%s ha deixat la sala.',
+ 'userHasBeenKickedFromRoom': '%s ha estat expulsat de la sala.',
+ 'userHasBeenBannedFromRoom': '%s ha estat expulsat permanentment de la sala.',
+
+ 'presenceUnknownWarningSubject': 'Atenció:',
+ 'presenceUnknownWarning' : 'Aquest usuari podria estar desconnectat ...',
+
+ 'dateFormat': 'dd.mm.yyyy',
+ 'timeFormat': 'HH:MM:ss',
+
+ 'tooltipRole' : 'Moderador',
+ 'tooltipIgnored' : 'Estàs ignorant aquest usuari',
+ 'tooltipEmoticons' : 'Emoticones',
+ 'tooltipSound' : 'Reproduir un so per a nous missatges',
+ 'tooltipAutoscroll' : 'Desplaçament automàtic',
+ 'tooltipStatusmessage' : 'Mostrar missatges d\'estat',
+ 'tooltipAdministration' : 'Administració de la sala',
+ 'tooltipUsercount' : 'Usuaris dins la sala',
+
+ 'enterRoomPassword' : 'La sala "%s" està protegida amb contrasenya.',
+ 'enterRoomPasswordSubmit' : 'Entrar a la sala',
+ 'passwordEnteredInvalid' : 'Contrasenya incorrecta per a la sala "%s".',
+
+ 'nicknameConflict': 'El nom d\'usuari ja s\'està utilitzant. Si us plau, escolleix-ne un altre.',
+
+ 'errorMembersOnly': 'No pots unir-te a la sala "%s": no tens prous privilegis.',
+ 'errorMaxOccupantsReached': 'No pots unir-te a la sala "%s": hi ha masses participants.',
+
+ 'antiSpamMessage' : 'Si us plau, no facis spam. Has estat bloquejat temporalment.'
}
-}; \ No newline at end of file
+};