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:
authorMichael Weibel <michael.weibel@gmail.com>2015-07-10 08:40:48 +0300
committerMichael Weibel <michael.weibel@gmail.com>2015-07-10 08:40:48 +0300
commitcd048fd58fc48e02c7192885b6b81804ef1f7850 (patch)
tree97a3420016396c07f38127ac7b9b47fcd09a873c
parenta9dcafe1a1995b61755986b56777fb2f44c832b2 (diff)
parent7e439479ccce1495ab51ed9c87d191ede195e725 (diff)
Merge branch 'dev'v2.0.0
Update version to 2.0.0
-rw-r--r--.editorconfig11
-rw-r--r--.gitattributes3
-rw-r--r--.gitignore10
-rw-r--r--.gitmodules12
-rw-r--r--.jshintrc6
-rw-r--r--.travis.yml22
-rwxr-xr-x[-rw-r--r--]CONTRIBUTING.md30
-rw-r--r--Gruntfile.js103
-rw-r--r--README.md7
-rw-r--r--Vagrantfile20
-rw-r--r--bower.json11
-rw-r--r--candy.bundle.js5694
-rw-r--r--candy.bundle.map1
-rw-r--r--candy.min.js4
-rw-r--r--candy.min.map1
-rw-r--r--devbox/index.html50
-rw-r--r--devbox/nginx-default.conf14
-rw-r--r--devbox/prosody.cfg.lua184
-rw-r--r--devbox/provisioning.sh58
-rw-r--r--devbox/selenium.init.sh52
-rw-r--r--example/index.html2
m---------libs/jquery-i18n0
-rw-r--r--libs/libs.bundle.js6348
-rw-r--r--libs/libs.bundle.map1
-rw-r--r--libs/libs.min.js3
m---------libs/mustache.js0
m---------libs/strophejs0
m---------libs/strophejs-plugins0
-rw-r--r--package.json17
-rw-r--r--res/audioplayer.swfbin2680 -> 0 bytes
-rw-r--r--res/default.css21
-rw-r--r--res/notify.m4abin0 -> 4495 bytes
-rw-r--r--res/notify.mp3bin1095 -> 1149 bytes
-rw-r--r--res/notify.oggbin0 -> 4040 bytes
-rwxr-xr-xsetup.sh54
-rw-r--r--src/candy.js2
-rw-r--r--src/core.js139
-rw-r--r--src/core/action.js39
-rw-r--r--src/core/chatRoom.js126
-rw-r--r--src/core/chatRoster.js78
-rw-r--r--src/core/chatUser.js466
-rw-r--r--src/core/contact.js152
-rw-r--r--src/core/event.js697
-rw-r--r--src/util.js45
-rw-r--r--src/view.js7
-rw-r--r--src/view/observer.js26
-rw-r--r--src/view/pane.js2094
-rw-r--r--src/view/pane/chat.js1064
-rw-r--r--src/view/pane/message.js253
-rw-r--r--src/view/pane/privateRoom.js181
-rw-r--r--src/view/pane/room.js484
-rw-r--r--src/view/pane/roster.js295
-rw-r--r--src/view/pane/window.js117
-rw-r--r--src/view/template.js30
-rw-r--r--src/view/translation.js228
-rw-r--r--tests/candy/functional/all.js5
-rw-r--r--tests/candy/functional/basics.js24
-rw-r--r--tests/candy/unit/all.js17
-rw-r--r--tests/candy/unit/candy.js22
-rw-r--r--tests/candy/unit/core.js28
-rw-r--r--tests/candy/unit/core/action.js259
-rw-r--r--tests/candy/unit/core/chatRoom.js50
-rw-r--r--tests/candy/unit/core/chatRoster.js49
-rw-r--r--tests/candy/unit/core/chatUser.js209
-rw-r--r--tests/candy/unit/core/contact.js201
-rw-r--r--tests/candy/unit/core/event.js2131
-rw-r--r--tests/candy/unit/util.js198
-rw-r--r--tests/helper.js90
-rw-r--r--tests/intern.js71
-rw-r--r--tests/intern.local.js11
-rw-r--r--tests/intern.vagrant.js9
-rw-r--r--vendor_libs/dateformat/dateFormat.js (renamed from libs/dateformat/dateFormat.js)0
72 files changed, 7751 insertions, 14885 deletions
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..f02c9f2
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,11 @@
+# EditorConfig is awesome: http://EditorConfig.org
+
+root = true
+
+[*]
+end_of_line = lf
+insert_final_newline = true
+indent_style = tab
+indent_size = 4
+charset = utf-8
+trim_trailing_whitespace = true
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..6915f9e
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,3 @@
+res/notify.m4a binary
+res/notify.ogg binary
+res/notify.mp3 binary
diff --git a/.gitignore b/.gitignore
index d2809c0..3a9bb32 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,4 +5,12 @@ example/.htaccess
.idea
.ndproj/Data
.ndproj/Menu.txt
-node_modules \ No newline at end of file
+node_modules
+bower_components
+candy.bundle.*
+candy.min.*
+libs.bundle.*
+libs.min.*
+.vagrant
+*.log
+lcov.info
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index fe85dab..0000000
--- a/.gitmodules
+++ /dev/null
@@ -1,12 +0,0 @@
-[submodule "libs/jquery-i18n"]
- path = libs/jquery-i18n
- url = git://github.com/recurser/jquery-i18n.git
-[submodule "libs/strophejs"]
- path = libs/strophejs
- url = git://github.com/strophe/strophejs.git
-[submodule "libs/strophejs-plugins"]
- path = libs/strophejs-plugins
- url = git://github.com/strophe/strophejs-plugins.git
-[submodule "libs/mustache.js"]
- path = libs/mustache.js
- url = git://github.com/janl/mustache.js.git
diff --git a/.jshintrc b/.jshintrc
index d2f32ad..6a2ea00 100644
--- a/.jshintrc
+++ b/.jshintrc
@@ -8,5 +8,7 @@
"undef": true,
"unused": true,
"trailing": true,
- "maxdepth": 4
-} \ No newline at end of file
+ "maxdepth": 5,
+ "laxcomma": true,
+ "browser": true
+}
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..e8b54cf
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,22 @@
+language: node_js
+node_js:
+- '0.10'
+env:
+ global:
+ - SAUCE_USERNAME: candy-chat-ci
+ - SAUCE_ACCESS_KEY: 6c3e7f58-b230-4b91-8bcb-290cf28c1457
+ - CANDY_VAGRANT: false
+before_script:
+- sudo apt-get update
+- sudo apt-get install -y nginx
+- sudo mkdir -p /usr/share/nginx/html
+- sudo cp ${TRAVIS_BUILD_DIR}/devbox/nginx-default.conf /etc/nginx/sites-available/default
+- sudo /etc/init.d/nginx restart
+- sudo ln -fs ${TRAVIS_BUILD_DIR} /usr/share/nginx/html/candy
+- sudo ln -fs ${TRAVIS_BUILD_DIR}/devbox/index.html /usr/share/nginx/html/index.html
+- sudo apt-get install -y naturaldocs
+- npm install -g grunt-cli bower
+- npm install
+- bower install
+script:
+- grunt ci
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 870ce22..0679f06 100644..100755
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -7,6 +7,8 @@
## Learn & listen
+[![Gitter chat](https://badges.gitter.im/candy-chat.png)](https://gitter.im/candy-chat)
+
* [Mailing list](http://groups.google.com/group/candy-chat)
* yes, non-gmail users can signup as well
* [FAQ](https://github.com/candy-chat/candy/wiki/Frequently-Asked-Questions)
@@ -21,8 +23,7 @@ A few hopefully helpful hints to contributing to Candy
#### Using vagrant
1. [Fork](https://help.github.com/articles/fork-a-repo) Candy
2. [Install Vagrant](http://vagrantup.com/)
-3. Follow instructions [for Candy Vagrant](https://github.com/candy-chat/vagrant)
-4. Change the remote in the `candy` and `candy-plugins` repos: `git remote set-url origin git://github.com/YOURNAME/candy` (or candy-plugins)
+3. Run `vagrant up`.
5. Create a branch based on the `dev` branch (`git checkout -B my-awesome-feature`)
6. Run `grunt watch` to automatically run jshint (syntax checker) and the build of `candy.bundle.js` and `candy.min.js` while developing.
7. Make your changes, fix eventual *jshint* errors & push them back to your fork
@@ -34,13 +35,22 @@ Please note that you should have a working XMPP server to test your changes (the
1. [Fork](https://help.github.com/articles/fork-a-repo) Candy
2. Clone your fork
-2. Checkout out `dev` branch (`git checkout dev`) & Update git submodules `git submodule update --init`
-3. Install [Node.js](http://nodejs.org/)
-4. Install [Grunt](http://gruntjs.com/) (`npm install -g grunt-cli`)
-5. Install npm dependencies (`npm install` in candy root directory)
-6. Create a branch based on the `dev` branch (`git checkout -B my-awesome-feature`)
-7. Run `grunt watch` to automatically run jshint (syntax checker) and the build of `candy.bundle.js` and `candy.min.js` while developing.
-8. Make your changes, fix eventual *jshint* errors & push them back to your fork
-9. Create a [pull request](https://help.github.com/articles/using-pull-requests)
+3. Checkout out `dev` branch (`git checkout dev`)
+4. Install [Node.js](http://nodejs.org/)
+5. Install [Grunt](http://gruntjs.com/) (`npm install -g grunt-cli`)
+6. Install [Bower](http://bower.io/) (`npm install -g bower`)
+7. Install npm dependencies (`npm install` in candy root directory)
+8. Install bower dependencies (`bower install` in candy root directory)
+9. Create a branch based on the `dev` branch (`git checkout -B my-awesome-feature`)
+10. Run `grunt watch` to automatically run jshint (syntax checker) and the build of `candy.bundle.js` and `candy.min.js` while developing.
+11. Make your changes, fix eventual *jshint* errors & push them back to your fork
+12. Create a [pull request](https://help.github.com/articles/using-pull-requests)
In case you have any questions, don't hesitate to ask on the [Mailing list](http://groups.google.com/group/candy-chat).
+
+### Running tests
+
+* Tests are run using [Intern](http://theintern.io).
+* `grunt` and `grunt watch` will each run unit tests in Chrome on Linux (for fast feedback).
+* `grunt test` will run both unit and integration tests in a variety of environments. Tests are run using Selenium Standalone and Phantom.JS while developing, and on Sauce Labs in CI or using `grunt test`.
+* If you don't want to use the Vagrant box to run Selenium/PhantomJS, set `CANDY_VAGRANT='false'` to run tests.
diff --git a/Gruntfile.js b/Gruntfile.js
index 1934f5f..70057ca 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -1,13 +1,14 @@
'use strict';
+var localInternConfig = process.env.CANDY_VAGRANT === 'false' ? 'tests/intern.local' : 'tests/intern.vagrant';
+
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
-
jshint: {
- all: ['Gruntfile.js', './src/**/*.js'],
+ all: ['Gruntfile.js', './src/**/*.js', './tests/**/*.js'],
options: {
jshintrc: "./.jshintrc",
reporter: require('jshint-stylish')
@@ -30,8 +31,11 @@ module.exports = function(grunt) {
'src/candy.js', 'src/core.js', 'src/view.js',
'src/util.js', 'src/core/action.js',
'src/core/chatRoom.js', 'src/core/chatRoster.js',
- 'src/core/chatUser.js', 'src/core/event.js',
- 'src/view/observer.js', 'src/view/pane.js',
+ 'src/core/chatUser.js', 'src/core/contact.js',
+ 'src/core/event.js', 'src/view/observer.js',
+ 'src/view/pane/chat.js', 'src/view/pane/message.js',
+ 'src/view/pane/privateRoom.js', 'src/view/pane/room.js',
+ 'src/view/pane/roster.js', 'src/view/pane/window.js',
'src/view/template.js', 'src/view/translation.js'
]
},
@@ -53,14 +57,15 @@ module.exports = function(grunt) {
},
libs: {
files: {
- 'libs/libs.bundle.js': [
- 'libs/strophejs/strophe.js',
- 'libs/strophejs-plugins/muc/strophe.muc.js',
- 'libs/strophejs-plugins/disco/strophe.disco.js',
- 'libs/strophejs-plugins/caps/strophe.caps.jsonly.js',
- 'libs/mustache.js/mustache.js',
- 'libs/jquery-i18n/jquery.i18n.js',
- 'libs/dateformat/dateFormat.js'
+ 'libs.bundle.js': [
+ 'bower_components/strophe/strophe.js',
+ 'bower_components/strophejs-plugins/muc/strophe.muc.js',
+ 'bower_components/strophejs-plugins/roster/strophe.roster.js',
+ 'bower_components/strophejs-plugins/disco/strophe.disco.js',
+ 'bower_components/strophejs-plugins/caps/strophe.caps.jsonly.js',
+ 'bower_components/mustache/mustache.js',
+ 'bower_components/jquery-i18n/jquery.i18n.js',
+ 'vendor_libs/dateformat/dateFormat.js'
]
},
options: {
@@ -73,23 +78,38 @@ module.exports = function(grunt) {
},
'libs-min': {
files: {
- 'libs/libs.min.js': ['libs/libs.bundle.js']
+ 'libs.min.js': ['libs.bundle.js']
}
}
},
watch: {
+ clear: {
+ files: ['src/*.js', 'src/**/*.js', 'tests/**/*.js'],
+ tasks: ['clear']
+ },
+ grunt: {
+ files: ['Gruntfile.js']
+ },
bundle: {
- files: ['src/*.js', 'src/**/*.js'],
- tasks: ['jshint', 'uglify:bundle', 'uglify:min', 'notify:bundle']
+ files: ['src/**/*.js'],
+ tasks: ['todo:src', 'jshint', 'uglify:bundle', 'uglify:min', 'notify:bundle', 'intern:unit']
},
libs: {
- files: ['libs/*/**/*.js'],
+ files: ['bower_components/*/**/*.js', 'vendor_libs/*/**/*.js'],
tasks: ['uglify:libs', 'uglify:libs-min', 'notify:libs']
+ },
+ tests: {
+ files: ['tests/candy/unit/**/*.js'],
+ tasks: ['todo:tests', 'jshint', 'intern:unit']
+ },
+ functional_tests: {
+ files: ['tests/candy/functional/**/*.js'],
+ tasks: ['todo:tests', 'jshint', 'intern:functional']
}
},
natural_docs: {
all: {
- bin: process.env.NATURALDOCS_DIR + '/NaturalDocs',
+ bin: process.env.NATURALDOCS_DIR ? process.env.NATURALDOCS_DIR + '/NaturalDocs' : 'naturaldocs',
flags: ['-r'],
inputs: ['./src'],
output: './docs',
@@ -98,7 +118,7 @@ module.exports = function(grunt) {
},
clean: {
bundle: ['./candy.bundle.js', './candy.bundle.map', './candy.min.js'],
- libs: ['./libs/libs.bundle.js', './libs/libs.bundle.map', './libs/libs.min.js'],
+ libs: ['./libs.bundle.js', './libs.bundle.map', './libs.min.js'],
docs: ['./docs']
},
mkdir: {
@@ -129,6 +149,41 @@ module.exports = function(grunt) {
message: 'JsHint & bundling done'
}
}
+ },
+ intern: {
+ all: {
+ options: {
+ runType: 'runner',
+ config: 'tests/intern'
+ }
+ },
+ unit: {
+ options: {
+ runType: 'runner',
+ config: localInternConfig,
+ functionalSuites: []
+ }
+ },
+ functional: {
+ options: {
+ runType: 'runner',
+ config: localInternConfig,
+ suites: []
+ }
+ }
+ },
+ coveralls: {
+ options: {
+ force: true // prevent from failing CI build if coveralls is down etc.
+ },
+ all: {
+ src: 'lcov.info',
+ }
+ },
+ todo: {
+ options: {},
+ src: ['src/**/*.js'],
+ tests: ['tests/**/*.js']
}
});
@@ -140,10 +195,16 @@ module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-mkdir');
grunt.loadNpmTasks('grunt-notify');
grunt.loadNpmTasks('grunt-sync-pkg');
+ grunt.loadNpmTasks('intern');
+ grunt.loadNpmTasks('grunt-clear');
+ grunt.loadNpmTasks('grunt-coveralls');
+ grunt.loadNpmTasks('grunt-todo');
+ grunt.registerTask('test', ['intern:all']);
+ grunt.registerTask('ci', ['todo', 'jshint', 'build', 'intern:all', 'coveralls:all', 'docs']);
+ grunt.registerTask('build', ['uglify:libs', 'uglify:libs-min', 'uglify:bundle', 'uglify:min']);
grunt.registerTask('default', [
- 'jshint', 'uglify:libs', 'uglify:libs-min',
- 'uglify:bundle', 'uglify:min', 'notify:default'
+ 'jshint', 'build', 'notify:default', 'intern:unit'
]);
grunt.registerTask('docs', ['mkdir:docs', 'natural_docs', 'notify:docs']);
-}; \ No newline at end of file
+};
diff --git a/README.md b/README.md
index ee31a9e..de47635 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,9 @@
Candy — a JavaScript-based multi-user chat client
==================================================
+[![Build Status](https://travis-ci.org/candy-chat/candy.png?branch=dev)](https://travis-ci.org/candy-chat/candy)
+[![Coverage Status](https://coveralls.io/repos/candy-chat/candy/badge.png?branch=dev)](https://coveralls.io/r/candy-chat/candy)
+
Visit the official project page: http://candy-chat.github.io/candy
Features
@@ -11,7 +14,7 @@ Features
- 100% well-documented JavaScript source code
- Built for Jabber (XMPP), using famous technologies
- Used and approved in a productive environment with up to 400 concurrent users
-- Works with all major web browsers including IE7
+- Works with all major web browsers including IE9
Plugins
-------
@@ -20,6 +23,6 @@ If you wish to add new functionality (to your candy installation) or contribute
Support & Community
-------------------
Take a look at our [FAQ](https://github.com/candy-chat/candy/wiki/Frequently-Asked-Questions). If it doesn't solve your questions, you're welcome to join our [Mailinglist on Google Groups](http://groups.google.com/group/candy-chat).
-You don't need to have a Gmail account for it.
+You don't need to have a Gmail account for it.
[![githalytics.com alpha](https://cruel-carlota.pagodabox.com/a41a8075608abeaf99db685d7ef29cf6 "githalytics.com")](http://githalytics.com/candy-chat/candy)
diff --git a/Vagrantfile b/Vagrantfile
new file mode 100644
index 0000000..8f277b1
--- /dev/null
+++ b/Vagrantfile
@@ -0,0 +1,20 @@
+# -*- mode: ruby -*-
+# vi: set ft=ruby :
+
+# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
+VAGRANTFILE_API_VERSION = "2"
+
+Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
+ config.vm.box = "ubuntu/trusty64"
+ config.vm.network :forwarded_port, guest: 80, host: 8080
+ config.vm.network :forwarded_port, guest: 5280, host: 5280
+ config.vm.network :forwarded_port, guest: 4444, host: 4444
+ config.vm.network :private_network, ip: '192.168.88.4'
+
+ config.vm.provision :shell, :path => "devbox/provisioning.sh"
+
+ config.vm.provider "virtualbox" do |v|
+ v.name = "candy"
+ v.customize ["modifyvm", :id, "--memory", 768]
+ end
+end
diff --git a/bower.json b/bower.json
index 5471414..b4f50d2 100644
--- a/bower.json
+++ b/bower.json
@@ -29,5 +29,12 @@
"bower_components",
"test",
"tests"
- ]
-} \ No newline at end of file
+ ],
+ "dependencies": {
+ "jquery": "~1.10.2",
+ "strophe": "1.1.3",
+ "strophejs-plugins": "benlangfeld/strophejs-plugins#30fb089457addc37e01d69c3536dee868a90a9ad",
+ "mustache": "0.3.0",
+ "jquery-i18n": "1.1.1"
+ }
+}
diff --git a/candy.bundle.js b/candy.bundle.js
deleted file mode 100644
index 713a8b5..0000000
--- a/candy.bundle.js
+++ /dev/null
@@ -1,5694 +0,0 @@
-/** File: candy.js
- * Candy - Chats are not dead yet.
- *
- * Authors:
- * - Patrick Stadler <patrick.stadler@gmail.com>
- * - Michael Weibel <michael.weibel@gmail.com>
- *
- * Copyright:
- * (c) 2011 Amiado Group AG. All rights reserved.
- * (c) 2012-2014 Patrick Stadler & Michael Weibel. All rights reserved.
- */
-"use strict";
-
-/* global jQuery */
-/** Class: Candy
- * Candy base class for initalizing the view and the core
- *
- * Parameters:
- * (Candy) self - itself
- * (jQuery) $ - jQuery
- */
-var Candy = function(self, $) {
- /** Object: about
- * About candy
- *
- * Contains:
- * (String) name - Candy
- * (Float) version - Candy version
- */
- self.about = {
- name: "Candy",
- version: "1.7.1"
- };
- /** Function: init
- * Init view & core
- *
- * Parameters:
- * (String) service - URL to the BOSH interface
- * (Object) options - Options for candy
- *
- * Options:
- * (Boolean) debug - Debug (Default: false)
- * (Array|Boolean) autojoin - Autojoin these channels. When boolean true, do not autojoin, wait if the server sends something.
- */
- self.init = function(service, options) {
- if (!options.viewClass) {
- options.viewClass = self.View;
- }
- options.viewClass.init($("#candy"), options.view);
- self.Core.init(service, options.core);
- };
- return self;
-}(Candy || {}, jQuery);
-
-/** File: core.js
- * Candy - Chats are not dead yet.
- *
- * Authors:
- * - Patrick Stadler <patrick.stadler@gmail.com>
- * - Michael Weibel <michael.weibel@gmail.com>
- *
- * Copyright:
- * (c) 2011 Amiado Group AG. All rights reserved.
- * (c) 2012-2014 Patrick Stadler & Michael Weibel. All rights reserved.
- */
-"use strict";
-
-/* global Candy, window, Strophe, jQuery */
-/** Class: Candy.Core
- * Candy Chat Core
- *
- * Parameters:
- * (Candy.Core) self - itself
- * (Strophe) Strophe - Strophe JS
- * (jQuery) $ - jQuery
- */
-Candy.Core = function(self, Strophe, $) {
- /** PrivateVariable: _connection
- * Strophe connection
- */
- var _connection = null, /** PrivateVariable: _service
- * URL of BOSH service
- */
- _service = null, /** PrivateVariable: _user
- * Current user (me)
- */
- _user = null, /** PrivateVariable: _rooms
- * Opened rooms, containing instances of Candy.Core.ChatRooms
- */
- _rooms = {}, /** PrivateVariable: _anonymousConnection
- * Set in <Candy.Core.connect> when jidOrHost doesn't contain a @-char.
- */
- _anonymousConnection = false, /** PrivateVariable: _status
- * Current Strophe connection state
- */
- _status, /** PrivateVariable: _options
- * Options:
- * (Boolean) debug - Debug (Default: false)
- * (Array|Boolean) autojoin - Autojoin these channels. When boolean true, do not autojoin, wait if the server sends something.
- */
- _options = {
- /** Boolean: autojoin
- * If set to `true` try to get the bookmarks and autojoin the rooms (supported by ejabberd, Openfire).
- * You may want to define an array of rooms to autojoin: `['room1@conference.host.tld', 'room2...]` (ejabberd, Openfire, ...)
- */
- autojoin: undefined,
- debug: false,
- disableWindowUnload: false,
- /** Integer: presencePriority
- * Default priority for presence messages in order to receive messages across different resources
- */
- presencePriority: 1,
- /** String: resource
- * JID resource to use when connecting to the server.
- * Specify `''` (an empty string) to request a random resource.
- */
- resource: Candy.about.name
- }, /** PrivateFunction: _addNamespace
- * Adds a namespace.
- *
- * Parameters:
- * (String) name - namespace name (will become a constant living in Strophe.NS.*)
- * (String) value - XML Namespace
- */
- _addNamespace = function(name, value) {
- Strophe.addNamespace(name, value);
- }, /** PrivateFunction: _addNamespaces
- * Adds namespaces needed by Candy.
- */
- _addNamespaces = function() {
- _addNamespace("PRIVATE", "jabber:iq:private");
- _addNamespace("BOOKMARKS", "storage:bookmarks");
- _addNamespace("PRIVACY", "jabber:iq:privacy");
- _addNamespace("DELAY", "jabber:x:delay");
- _addNamespace("PUBSUB", "http://jabber.org/protocol/pubsub");
- }, _getEscapedJidFromJid = function(jid) {
- var node = Strophe.getNodeFromJid(jid), domain = Strophe.getDomainFromJid(jid);
- return node ? Strophe.escapeNode(node) + "@" + domain : domain;
- };
- /** Function: init
- * Initialize Core.
- *
- * Parameters:
- * (String) service - URL of BOSH/Websocket service
- * (Object) options - Options for candy
- */
- self.init = function(service, options) {
- _service = service;
- // Apply options
- $.extend(true, _options, options);
- // Enable debug logging
- if (_options.debug) {
- if (typeof window.console !== undefined && typeof window.console.log !== undefined) {
- // Strophe has a polyfill for bind which doesn't work in IE8.
- if (Function.prototype.bind && Candy.Util.getIeVersion() > 8) {
- self.log = Function.prototype.bind.call(console.log, console);
- } else {
- self.log = function() {
- Function.prototype.apply.call(console.log, console, arguments);
- };
- }
- }
- self.log("[Init] Debugging enabled");
- }
- _addNamespaces();
- // Connect to BOSH/Websocket service
- _connection = new Strophe.Connection(_service);
- _connection.rawInput = self.rawInput.bind(self);
- _connection.rawOutput = self.rawOutput.bind(self);
- // set caps node
- _connection.caps.node = "https://candy-chat.github.io/candy/";
- // Window unload handler... works on all browsers but Opera. There is NO workaround.
- // Opera clients getting disconnected 1-2 minutes delayed.
- if (!_options.disableWindowUnload) {
- window.onbeforeunload = self.onWindowUnload;
- }
- };
- /** 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(_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.
- *
- * There are four different procedures to login:
- * connect('JID', 'password') - Connect a registered user
- * connect('domain') - Connect anonymously to the domain. The user should receive a random JID.
- * connect('domain', null, 'nick') - Connect anonymously to the domain. The user should receive a random JID but with a nick set.
- * connect('JID') - Show login form and prompt for password. JID input is hidden.
- * connect() - Show login form and prompt for JID and password.
- *
- * See:
- * <Candy.Core.attach()> for attaching an already established session.
- *
- * Parameters:
- * (String) jidOrHost - JID or Host
- * (String) password - Password of the user
- * (String) nick - Nick of the user. Set one if you want to anonymously connect but preset a nick. If jidOrHost is a domain
- * and this param is not set, Candy will prompt for a nick.
- */
- self.connect = function(jidOrHost, password, nick) {
- // Reset before every connection attempt to make sure reconnections work after authfail, alltabsclosed, ...
- _connection.reset();
- self.registerEventHandlers();
- /** Event: candy:core.before-connect
- * Triggered before a connection attempt is made.
- *
- * Plugins should register their stanza handlers using this event
- * to ensure that they are set.
- *
- * See also <#84 at https://github.com/candy-chat/candy/issues/84>.
- *
- * Parameters:
- * (Strophe.Connection) conncetion - Strophe connection
- */
- $(Candy).triggerHandler("candy:core.before-connect", {
- connection: _connection
- });
- _anonymousConnection = !_anonymousConnection ? jidOrHost && jidOrHost.indexOf("@") < 0 : true;
- if (jidOrHost && password) {
- // authentication
- _connection.connect(_getEscapedJidFromJid(jidOrHost) + "/" + _options.resource, password, Candy.Core.Event.Strophe.Connect);
- 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) + "/" + _options.resource, null, Candy.Core.Event.Strophe.Connect);
- _user = new self.ChatUser(null, nick);
- } else if (jidOrHost) {
- Candy.Core.Event.Login(jidOrHost);
- } else {
- // display login modal
- Candy.Core.Event.Login();
- }
- };
- /** Function: attach
- * Attach an already binded & connected session to the server
- *
- * _See_ Strophe.Connection.attach
- *
- * Parameters:
- * (String) jid - Jabber ID
- * (Integer) sid - Session ID
- * (Integer) rid - rid
- */
- self.attach = function(jid, sid, rid) {
- _user = new self.ChatUser(jid, Strophe.getNodeFromJid(jid));
- self.registerEventHandlers();
- _connection.attach(jid, sid, rid, Candy.Core.Event.Strophe.Connect);
- };
- /** Function: disconnect
- * Leave all rooms and disconnect
- */
- self.disconnect = function() {
- if (_connection.connected) {
- $.each(self.getRooms(), function() {
- Candy.Core.Action.Jabber.Room.Leave(this.getJid());
- });
- _connection.disconnect();
- }
- };
- /** Function: addHandler
- * Wrapper for Strophe.Connection.addHandler() to add a stanza handler for the connection.
- *
- * Parameters:
- * (Function) handler - The user callback.
- * (String) ns - The namespace to match.
- * (String) name - The stanza name to match.
- * (String) type - The stanza type attribute to match.
- * (String) id - The stanza id attribute to match.
- * (String) from - The stanza from attribute to match.
- * (String) options - The handler options
- *
- * Returns:
- * A reference to the handler that can be used to remove it.
- */
- self.addHandler = function(handler, ns, name, type, id, from, options) {
- return _connection.addHandler(handler, ns, name, type, id, from, options);
- };
- /** Function: getUser
- * Gets current user
- *
- * Returns:
- * Instance of Candy.Core.ChatUser
- */
- self.getUser = function() {
- return _user;
- };
- /** Function: setUser
- * Set current user. Needed when anonymous login is used, as jid gets retrieved later.
- *
- * Parameters:
- * (Candy.Core.ChatUser) user - User instance
- */
- self.setUser = function(user) {
- _user = user;
- };
- /** Function: getConnection
- * Gets Strophe connection
- *
- * Returns:
- * Instance of Strophe.Connection
- */
- self.getConnection = function() {
- return _connection;
- };
- /** Function: removeRoom
- * Removes a room from the rooms list
- *
- * Parameters:
- * (String) roomJid - roomJid
- */
- self.removeRoom = function(roomJid) {
- delete _rooms[roomJid];
- };
- /** Function: getRooms
- * Gets all joined rooms
- *
- * Returns:
- * Object containing instances of Candy.Core.ChatRoom
- */
- self.getRooms = function() {
- 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.
- *
- * Returns:
- * (Boolean)
- */
- self.isAnonymousConnection = function() {
- return _anonymousConnection;
- };
- /** Function: getOptions
- * Gets options
- *
- * Returns:
- * Object
- */
- self.getOptions = function() {
- return _options;
- };
- /** Function: getRoom
- * Gets a specific room
- *
- * Parameters:
- * (String) roomJid - JID of the room
- *
- * Returns:
- * If the room is joined, instance of Candy.Core.ChatRoom, otherwise null.
- */
- self.getRoom = function(roomJid) {
- if (_rooms[roomJid]) {
- return _rooms[roomJid];
- }
- return null;
- };
- /** Function: onWindowUnload
- * window.onbeforeunload event which disconnects the client from the Jabber server.
- */
- self.onWindowUnload = function() {
- // Enable synchronous requests because Safari doesn't send asynchronous requests within unbeforeunload events.
- // Only works properly when following patch is applied to strophejs: https://github.com/metajack/strophejs/issues/16/#issuecomment-600266
- _connection.options.sync = true;
- self.disconnect();
- _connection.flush();
- };
- /** Function: rawInput
- * (Overridden from Strophe.Connection.rawInput)
- *
- * Logs all raw input if debug is set to true.
- */
- self.rawInput = function(data) {
- this.log("RECV: " + data);
- };
- /** Function rawOutput
- * (Overridden from Strophe.Connection.rawOutput)
- *
- * Logs all raw output if debug is set to true.
- */
- self.rawOutput = function(data) {
- this.log("SENT: " + data);
- };
- /** Function: log
- * Overridden to do something useful if debug is set to true.
- *
- * See: Candy.Core#init
- */
- self.log = function() {};
- return self;
-}(Candy.Core || {}, Strophe, jQuery);
-
-/** File: view.js
- * Candy - Chats are not dead yet.
- *
- * Authors:
- * - Patrick Stadler <patrick.stadler@gmail.com>
- * - Michael Weibel <michael.weibel@gmail.com>
- *
- * Copyright:
- * (c) 2011 Amiado Group AG. All rights reserved.
- * (c) 2012-2014 Patrick Stadler & Michael Weibel. All rights reserved.
- */
-"use strict";
-
-/* global jQuery, Candy, window, Mustache, document */
-/** Class: Candy.View
- * The Candy View Class
- *
- * Parameters:
- * (Candy.View) self - itself
- * (jQuery) $ - jQuery
- */
-Candy.View = function(self, $) {
- /** PrivateObject: _current
- * Object containing current container & roomJid which the client sees.
- */
- var _current = {
- container: null,
- roomJid: null
- }, /** PrivateObject: _options
- *
- * Options:
- * (String) language - language to use
- * (String) assets - path to assets (res) directory (with trailing slash)
- * (Object) messages - limit: clean up message pane when n is reached / remove: remove n messages after limit has been reached
- * (Object) crop - crop if longer than defined: message.nickname=15, message.body=1000, roster.nickname=15
- * (Bool) enableXHTML - [default: false] enables XHTML messages sending & displaying
- */
- _options = {
- language: "en",
- assets: "res/",
- messages: {
- limit: 2e3,
- remove: 500
- },
- crop: {
- message: {
- nickname: 15,
- body: 1e3
- },
- roster: {
- nickname: 15
- }
- },
- enableXHTML: false
- }, /** PrivateFunction: _setupTranslation
- * Set dictionary using jQuery.i18n plugin.
- *
- * See: view/translation.js
- * See: libs/jquery-i18n/jquery.i18n.js
- *
- * Parameters:
- * (String) language - Language identifier
- */
- _setupTranslation = function(language) {
- $.i18n.load(self.Translation[language]);
- }, /** PrivateFunction: _registerObservers
- * Register observers. Candy core will now notify the View on changes.
- */
- _registerObservers = function() {
- $(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.autojoin-missing", self.Observer.AutojoinMissing);
- $(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
- * Register window focus / blur / resize handlers.
- *
- * jQuery.focus()/.blur() <= 1.5.1 do not work for IE < 9. Fortunately onfocusin/onfocusout will work for them.
- */
- _registerWindowHandlers = function() {
- if (Candy.Util.getIeVersion() < 9) {
- $(document).focusin(Candy.View.Pane.Window.onFocus).focusout(Candy.View.Pane.Window.onBlur);
- } else {
- $(window).focus(Candy.View.Pane.Window.onFocus).blur(Candy.View.Pane.Window.onBlur);
- }
- $(window).resize(Candy.View.Pane.Chat.fitTabs);
- }, /** PrivateFunction: _initToolbar
- * Initialize toolbar.
- */
- _initToolbar = function() {
- self.Pane.Chat.Toolbar.init();
- }, /** PrivateFunction: _delegateTooltips
- * Delegate mouseenter on tooltipified element to <Candy.View.Pane.Chat.Tooltip.show>.
- */
- _delegateTooltips = function() {
- $("body").delegate("li[data-tooltip]", "mouseenter", Candy.View.Pane.Chat.Tooltip.show);
- };
- /** Function: init
- * Initialize chat view (setup DOM, register handlers & observers)
- *
- * Parameters:
- * (jQuery.element) container - Container element of the whole chat view
- * (Object) options - Options: see _options field (value passed here gets extended by the default value in _options field)
- */
- self.init = function(container, options) {
- // #216
- // Rename `resources` to `assets` but prevent installations from failing
- // after upgrade
- if (options.resources) {
- options.assets = options.resources;
- }
- delete options.resources;
- $.extend(true, _options, options);
- _setupTranslation(_options.language);
- // Set path to emoticons
- Candy.Util.Parser.setEmoticonPath(this.getOptions().assets + "img/emoticons/");
- // Start DOMination...
- _current.container = container;
- _current.container.html(Mustache.to_html(Candy.View.Template.Chat.pane, {
- tooltipEmoticons: $.i18n._("tooltipEmoticons"),
- tooltipSound: $.i18n._("tooltipSound"),
- tooltipAutoscroll: $.i18n._("tooltipAutoscroll"),
- tooltipStatusmessage: $.i18n._("tooltipStatusmessage"),
- tooltipAdministration: $.i18n._("tooltipAdministration"),
- tooltipUsercount: $.i18n._("tooltipUsercount"),
- assetsPath: this.getOptions().assets
- }, {
- tabs: Candy.View.Template.Chat.tabs,
- rooms: Candy.View.Template.Chat.rooms,
- modal: Candy.View.Template.Chat.modal,
- toolbar: Candy.View.Template.Chat.toolbar,
- soundcontrol: Candy.View.Template.Chat.soundcontrol
- }));
- // ... and let the elements dance.
- _registerWindowHandlers();
- _initToolbar();
- _registerObservers();
- _delegateTooltips();
- };
- /** Function: getCurrent
- * Get current container & roomJid in an object.
- *
- * Returns:
- * Object containing container & roomJid
- */
- self.getCurrent = function() {
- return _current;
- };
- /** Function: getOptions
- * Gets options
- *
- * Returns:
- * Object
- */
- self.getOptions = function() {
- return _options;
- };
- return self;
-}(Candy.View || {}, jQuery);
-
-/** File: util.js
- * Candy - Chats are not dead yet.
- *
- * Authors:
- * - Patrick Stadler <patrick.stadler@gmail.com>
- * - Michael Weibel <michael.weibel@gmail.com>
- *
- * Copyright:
- * (c) 2011 Amiado Group AG. All rights reserved.
- * (c) 2012-2014 Patrick Stadler & Michael Weibel. All rights reserved.
- */
-"use strict";
-
-/* global Candy, MD5, Strophe, document, escape, jQuery */
-/** Class: Candy.Util
- * Candy utils
- *
- * Parameters:
- * (Candy.Util) self - itself
- * (jQuery) $ - jQuery
- */
-Candy.Util = function(self, $) {
- /** Function: jidToId
- * Translates a jid to a MD5-Id
- *
- * Parameters:
- * (String) jid - Jid
- *
- * Returns:
- * MD5-ified jid
- */
- self.jidToId = function(jid) {
- return MD5.hexdigest(jid);
- };
- /** Function: escapeJid
- * Escapes a jid (node & resource get escaped)
- *
- * See:
- * XEP-0106
- *
- * Parameters:
- * (String) jid - Jid
- *
- * Returns:
- * (String) - escaped jid
- */
- self.escapeJid = function(jid) {
- var node = Strophe.escapeNode(Strophe.getNodeFromJid(jid)), domain = Strophe.getDomainFromJid(jid), resource = Strophe.getResourceFromJid(jid);
- jid = node + "@" + domain;
- if (resource) {
- jid += "/" + resource;
- }
- return jid;
- };
- /** Function: unescapeJid
- * Unescapes a jid (node & resource get unescaped)
- *
- * See:
- * XEP-0106
- *
- * Parameters:
- * (String) jid - Jid
- *
- * Returns:
- * (String) - unescaped Jid
- */
- self.unescapeJid = function(jid) {
- var node = Strophe.unescapeNode(Strophe.getNodeFromJid(jid)), domain = Strophe.getDomainFromJid(jid), resource = Strophe.getResourceFromJid(jid);
- jid = node + "@" + domain;
- if (resource) {
- jid += "/" + resource;
- }
- return jid;
- };
- /** Function: crop
- * Crop a string with the specified length
- *
- * Parameters:
- * (String) str - String to crop
- * (Integer) len - Max length
- */
- self.crop = function(str, len) {
- if (str.length > len) {
- str = str.substr(0, len - 3) + "...";
- }
- return str;
- };
- /** Function: parseAndCropXhtml
- * Parses the XHTML and applies various Candy related filters to it.
- *
- * - Ensures it contains only valid XHTML
- * - Crops text to a max length
- * - Parses the text in order to display html
- *
- * Parameters:
- * (String) str - String containing XHTML
- * (Integer) len - Max text length
- */
- self.parseAndCropXhtml = function(str, len) {
- return $("<div/>").append(self.createHtml($(str).get(0), len)).html();
- };
- /** Function: setCookie
- * Sets a new cookie
- *
- * Parameters:
- * (String) name - cookie name
- * (String) value - Value
- * (Integer) lifetime_days - Lifetime in days
- */
- self.setCookie = function(name, value, lifetime_days) {
- var exp = new Date();
- exp.setDate(new Date().getDate() + lifetime_days);
- document.cookie = name + "=" + value + ";expires=" + exp.toUTCString() + ";path=/";
- };
- /** Function: cookieExists
- * Tests if a cookie with the given name exists
- *
- * Parameters:
- * (String) name - Cookie name
- *
- * Returns:
- * (Boolean) - true/false
- */
- self.cookieExists = function(name) {
- return document.cookie.indexOf(name) > -1;
- };
- /** Function: getCookie
- * Returns the cookie value if there's one with this name, otherwise returns undefined
- *
- * Parameters:
- * (String) name - Cookie name
- *
- * Returns:
- * Cookie value or undefined
- */
- self.getCookie = function(name) {
- if (document.cookie) {
- var regex = new RegExp(escape(name) + "=([^;]*)", "gm"), matches = regex.exec(document.cookie);
- if (matches) {
- return matches[1];
- }
- }
- };
- /** Function: deleteCookie
- * Deletes a cookie with the given name
- *
- * Parameters:
- * (String) name - cookie name
- */
- self.deleteCookie = function(name) {
- document.cookie = name + "=;expires=Thu, 01-Jan-70 00:00:01 GMT;path=/";
- };
- /** Function: getPosLeftAccordingToWindowBounds
- * Fetches the window width and element width
- * and checks if specified position + element width is bigger
- * than the window width.
- *
- * If this evaluates to true, the position gets substracted by the element width.
- *
- * Parameters:
- * (jQuery.Element) elem - Element to position
- * (Integer) pos - Position left
- *
- * Returns:
- * Object containing `px` (calculated position in pixel) and `alignment` (alignment of the element in relation to pos, either 'left' or 'right')
- */
- self.getPosLeftAccordingToWindowBounds = function(elem, pos) {
- var windowWidth = $(document).width(), elemWidth = elem.outerWidth(), marginDiff = elemWidth - elem.outerWidth(true), backgroundPositionAlignment = "left";
- if (pos + elemWidth >= windowWidth) {
- pos -= elemWidth - marginDiff;
- backgroundPositionAlignment = "right";
- }
- return {
- px: pos,
- backgroundPositionAlignment: backgroundPositionAlignment
- };
- };
- /** Function: getPosTopAccordingToWindowBounds
- * Fetches the window height and element height
- * and checks if specified position + element height is bigger
- * than the window height.
- *
- * If this evaluates to true, the position gets substracted by the element height.
- *
- * Parameters:
- * (jQuery.Element) elem - Element to position
- * (Integer) pos - Position top
- *
- * Returns:
- * Object containing `px` (calculated position in pixel) and `alignment` (alignment of the element in relation to pos, either 'top' or 'bottom')
- */
- self.getPosTopAccordingToWindowBounds = function(elem, pos) {
- var windowHeight = $(document).height(), elemHeight = elem.outerHeight(), marginDiff = elemHeight - elem.outerHeight(true), backgroundPositionAlignment = "top";
- if (pos + elemHeight >= windowHeight) {
- pos -= elemHeight - marginDiff;
- backgroundPositionAlignment = "bottom";
- }
- return {
- px: pos,
- backgroundPositionAlignment: backgroundPositionAlignment
- };
- };
- /** Function: localizedTime
- * Localizes ISO-8610 Date with the time/dateformat specified in the translation.
- *
- * See: libs/dateformat/dateFormat.js
- * See: src/view/translation.js
- * See: jquery-i18n/jquery.i18n.js
- *
- * Parameters:
- * (String) dateTime - ISO-8610 Datetime
- *
- * Returns:
- * If current date is equal to the date supplied, format with timeFormat, otherwise with dateFormat
- */
- self.localizedTime = function(dateTime) {
- if (dateTime === undefined) {
- return undefined;
- }
- var date = self.iso8601toDate(dateTime);
- if (date.toDateString() === new Date().toDateString()) {
- return date.format($.i18n._("timeFormat"));
- } else {
- return date.format($.i18n._("dateFormat"));
- }
- };
- /** Function: iso8610toDate
- * Parses a ISO-8610 Date to a Date-Object.
- *
- * Uses a fallback if the client's browser doesn't support it.
- *
- * Quote:
- * ECMAScript revision 5 adds native support for ISO-8601 dates in the Date.parse method,
- * but many browsers currently on the market (Safari 4, Chrome 4, IE 6-8) do not support it.
- *
- * Credits:
- * <Colin Snover at http://zetafleet.com/blog/javascript-dateparse-for-iso-8601>
- *
- * Parameters:
- * (String) date - ISO-8610 Date
- *
- * Returns:
- * Date-Object
- */
- self.iso8601toDate = function(date) {
- var timestamp = Date.parse(date);
- 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);
- };
- /** Function: isEmptyObject
- * IE7 doesn't work with jQuery.isEmptyObject (<=1.5.1), workaround.
- *
- * Parameters:
- * (Object) obj - the object to test for
- *
- * Returns:
- * Boolean true or false.
- */
- self.isEmptyObject = function(obj) {
- var prop;
- for (prop in obj) {
- if (obj.hasOwnProperty(prop)) {
- return false;
- }
- }
- return true;
- };
- /** Function: forceRedraw
- * Fix IE7 not redrawing under some circumstances.
- *
- * Parameters:
- * (jQuery.element) elem - jQuery element to redraw
- */
- self.forceRedraw = function(elem) {
- elem.css({
- display: "none"
- });
- setTimeout(function() {
- this.css({
- display: "block"
- });
- }.bind(elem), 1);
- };
- /** PrivateVariable: ie
- * Checks for IE version
- *
- * From: http://stackoverflow.com/a/5574871/315242
- */
- var ie = function() {
- var undef, v = 3, div = document.createElement("div"), all = div.getElementsByTagName("i");
- while (// adds innerhtml and continues as long as all[0] is truthy
- div.innerHTML = "<!--[if gt IE " + ++v + "]><i></i><![endif]-->", all[0]) {}
- return v > 4 ? v : undef;
- }();
- /** Function: getIeVersion
- * Returns local variable `ie` which you can use to detect which IE version
- * is available.
- *
- * Use e.g. like this: if(Candy.Util.getIeVersion() < 9) alert('kaboom');
- */
- self.getIeVersion = function() {
- return ie;
- };
- /** Class: Candy.Util.Parser
- * Parser for emoticons, links and also supports escaping.
- */
- self.Parser = {
- /** PrivateVariable: _emoticonPath
- * Path to emoticons.
- *
- * Use setEmoticonPath() to change it
- */
- _emoticonPath: "",
- /** Function: setEmoticonPath
- * Set emoticons location.
- *
- * Parameters:
- * (String) path - location of emoticons with trailing slash
- */
- setEmoticonPath: function(path) {
- this._emoticonPath = path;
- },
- /** Array: emoticons
- * Array containing emoticons to be replaced by their images.
- *
- * Can be overridden/extended.
- */
- emoticons: [ {
- plain: ":)",
- regex: /((\s):-?\)|:-?\)(\s|$))/gm,
- image: "Smiling.png"
- }, {
- plain: ";)",
- regex: /((\s);-?\)|;-?\)(\s|$))/gm,
- image: "Winking.png"
- }, {
- plain: ":D",
- regex: /((\s):-?D|:-?D(\s|$))/gm,
- image: "Grinning.png"
- }, {
- plain: ";D",
- regex: /((\s);-?D|;-?D(\s|$))/gm,
- image: "Grinning_Winking.png"
- }, {
- plain: ":(",
- regex: /((\s):-?\(|:-?\((\s|$))/gm,
- image: "Unhappy.png"
- }, {
- plain: "^^",
- regex: /((\s)\^\^|\^\^(\s|$))/gm,
- image: "Happy_3.png"
- }, {
- plain: ":P",
- regex: /((\s):-?P|:-?P(\s|$))/gim,
- image: "Tongue_Out.png"
- }, {
- plain: ";P",
- regex: /((\s);-?P|;-?P(\s|$))/gim,
- image: "Tongue_Out_Winking.png"
- }, {
- plain: ":S",
- regex: /((\s):-?S|:-?S(\s|$))/gim,
- image: "Confused.png"
- }, {
- plain: ":/",
- regex: /((\s):-?\/|:-?\/(\s|$))/gm,
- image: "Uncertain.png"
- }, {
- plain: "8)",
- regex: /((\s)8-?\)|8-?\)(\s|$))/gm,
- image: "Sunglasses.png"
- }, {
- plain: "$)",
- regex: /((\s)\$-?\)|\$-?\)(\s|$))/gm,
- image: "Greedy.png"
- }, {
- plain: "oO",
- regex: /((\s)oO|oO(\s|$))/gm,
- image: "Huh.png"
- }, {
- plain: ":x",
- regex: /((\s):x|:x(\s|$))/gm,
- image: "Lips_Sealed.png"
- }, {
- plain: ":666:",
- regex: /((\s):666:|:666:(\s|$))/gm,
- image: "Devil.png"
- }, {
- plain: "<3",
- regex: /((\s)&lt;3|&lt;3(\s|$))/gm,
- image: "Heart.png"
- } ],
- /** Function: emotify
- * Replaces text-emoticons with their image equivalent.
- *
- * Parameters:
- * (String) text - Text to emotify
- *
- * Returns:
- * Emotified text
- */
- emotify: function(text) {
- var i;
- for (i = this.emoticons.length - 1; i >= 0; i--) {
- text = text.replace(this.emoticons[i].regex, '$2<img class="emoticon" alt="$1" src="' + this._emoticonPath + this.emoticons[i].image + '" />$3');
- }
- return text;
- },
- /** Function: linkify
- * Replaces URLs with a HTML-link.
- *
- * Parameters:
- * (String) text - Text to linkify
- *
- * Returns:
- * Linkified text
- */
- linkify: function(text) {
- text = text.replace(/(^|[^\/])(www\.[^\.]+\.[\S]+(\b|$))/gi, "$1http://$2");
- return text.replace(/(\b(https?|ftp|file):\/\/[\-A-Z0-9+&@#\/%?=~_|!:,.;]*[\-A-Z0-9+&@#\/%=~_|])/gi, '<a href="$1" target="_blank">$1</a>');
- },
- /** Function: escape
- * Escapes a text using a jQuery function (like htmlspecialchars in PHP)
- *
- * Parameters:
- * (String) text - Text to escape
- *
- * Returns:
- * Escaped text
- */
- escape: function(text) {
- return $("<div/>").text(text).html();
- },
- /** Function: 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.
- *
- * Parameters:
- * (String) text - Text to parse
- *
- * Returns:
- * (String) Parsed text
- */
- all: function(text) {
- if (text) {
- text = this.escape(text);
- text = this.linkify(text);
- text = this.emotify(text);
- text = this.nl2br(text);
- }
- return text;
- }
- };
- /** 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.
- *
- * It's a function copied & adapted from [Strophe.js core.js](https://github.com/strophe/strophejs/blob/master/src/core.js).
- *
- * Parameters:
- * (HTMLElement) elem - A DOM element.
- * (Integer) maxLength - Max length of text
- * (Integer) currentLength - Current accumulated text length
- *
- * Returns:
- * A new, copied DOM element tree.
- */
- self.createHtml = function(elem, maxLength, currentLength) {
- /* jshint -W073 */
- currentLength = currentLength || 0;
- var i, el, j, tag, attribute, value, css, cssAttrs, attr, cssName, cssValue;
- if (elem.nodeType === Strophe.ElementType.NORMAL) {
- tag = elem.nodeName.toLowerCase();
- if (Strophe.XHTML.validTag(tag)) {
- try {
- el = $("<" + 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;
- }
- }
- // 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.attr(attribute, value);
- }
- } else {
- el.attr(attribute, value);
- }
- }
- for (i = 0; i < elem.childNodes.length; i++) {
- el.append(self.createHtml(elem.childNodes[i], maxLength, currentLength));
- }
- } catch (e) {
- // invalid elements
- Candy.Core.log("[Util:createHtml] Error while parsing XHTML:");
- Candy.Core.log(e);
- el = Strophe.xmlTextNode("");
- }
- } else {
- el = Strophe.xmlGenerator().createDocumentFragment();
- for (i = 0; i < elem.childNodes.length; i++) {
- el.appendChild(self.createHtml(elem.childNodes[i], maxLength, currentLength));
- }
- }
- } else if (elem.nodeType === Strophe.ElementType.FRAGMENT) {
- el = Strophe.xmlGenerator().createDocumentFragment();
- for (i = 0; i < elem.childNodes.length; i++) {
- el.appendChild(self.createHtml(elem.childNodes[i], maxLength, currentLength));
- }
- } else if (elem.nodeType === Strophe.ElementType.TEXT) {
- var text = elem.nodeValue;
- currentLength += text.length;
- if (maxLength && currentLength > maxLength) {
- text = text.substring(0, maxLength);
- }
- text = Candy.Util.Parser.all(text);
- el = $.parseHTML(text);
- }
- return el;
- };
- return self;
-}(Candy.Util || {}, jQuery);
-
-/** File: action.js
- * Candy - Chats are not dead yet.
- *
- * Authors:
- * - Patrick Stadler <patrick.stadler@gmail.com>
- * - Michael Weibel <michael.weibel@gmail.com>
- *
- * Copyright:
- * (c) 2011 Amiado Group AG. All rights reserved.
- * (c) 2012-2014 Patrick Stadler & Michael Weibel. All rights reserved.
- */
-"use strict";
-
-/* global Candy, $iq, navigator, Candy, $pres, Strophe, jQuery, $msg */
-/** Class: Candy.Core.Action
- * Chat Actions (basicly a abstraction of Jabber commands)
- *
- * Parameters:
- * (Candy.Core.Action) self - itself
- * (Strophe) Strophe - Strophe
- * (jQuery) $ - jQuery
- */
-Candy.Core.Action = function(self, Strophe, $) {
- /** Class: Candy.Core.Action.Jabber
- * Jabber actions
- */
- self.Jabber = {
- /** Function: Version
- * Replies to a version request
- *
- * Parameters:
- * (jQuery.element) msg - jQuery element
- */
- Version: function(msg) {
- Candy.Core.getConnection().sendIQ($iq({
- type: "result",
- to: Candy.Util.escapeJid(msg.attr("from")),
- from: Candy.Util.escapeJid(msg.attr("to")),
- id: msg.attr("id")
- }).c("query", {
- name: Candy.about.name,
- version: Candy.about.version,
- os: navigator.userAgent
- }));
- },
- /** Function: SetNickname
- * Sets the supplied nickname for all rooms (if parameter "room" is not specified) or
- * sets it only for the specified rooms
- *
- * Parameters:
- * (String) nickname - New nickname
- * (Array) rooms - Rooms
- */
- SetNickname: function(nickname, rooms) {
- rooms = rooms instanceof Array ? rooms : Candy.Core.getRooms();
- var roomNick, presence, conn = Candy.Core.getConnection();
- $.each(rooms, function(roomJid) {
- roomNick = Candy.Util.escapeJid(roomJid + "/" + nickname);
- presence = $pres({
- to: roomNick,
- from: conn.jid,
- id: "pres:" + conn.getUniqueId()
- });
- Candy.Core.getConnection().send(presence);
- });
- },
- /** Function: Roster
- * Sends a request for a roster
- */
- Roster: function() {
- Candy.Core.getConnection().sendIQ($iq({
- type: "get",
- xmlns: Strophe.NS.CLIENT
- }).c("query", {
- xmlns: Strophe.NS.ROSTER
- }).tree());
- },
- /** Function: Presence
- * Sends a request for presence
- *
- * Parameters:
- * (Object) attr - Optional attributes
- * (Strophe.Builder) el - Optional element to include in presence stanza
- */
- Presence: function(attr, el) {
- var conn = Candy.Core.getConnection();
- attr = attr || {};
- if (!attr.id) {
- attr.id = "pres:" + conn.getUniqueId();
- }
- var pres = $pres(attr).c("priority").t(Candy.Core.getOptions().presencePriority.toString()).up().c("c", conn.caps.generateCapsAttrs()).up();
- if (el) {
- pres.node.appendChild(el.node);
- }
- conn.send(pres.tree());
- },
- /** Function: Services
- * Sends a request for disco items
- */
- Services: function() {
- Candy.Core.getConnection().sendIQ($iq({
- type: "get",
- xmlns: Strophe.NS.CLIENT
- }).c("query", {
- xmlns: Strophe.NS.DISCO_ITEMS
- }).tree());
- },
- /** Function: Autojoin
- * When Candy.Core.getOptions().autojoin is true, request autojoin bookmarks (OpenFire)
- *
- * Otherwise, if Candy.Core.getOptions().autojoin is an array, join each channel specified.
- * Channel can be in jid:password format to pass room password if needed.
-
- * Triggers:
- * candy:core.autojoin-missing in case no autojoin info has been found
- */
- Autojoin: function() {
- // Request bookmarks
- if (Candy.Core.getOptions().autojoin === true) {
- Candy.Core.getConnection().sendIQ($iq({
- type: "get",
- xmlns: Strophe.NS.CLIENT
- }).c("query", {
- xmlns: Strophe.NS.PRIVATE
- }).c("storage", {
- xmlns: Strophe.NS.BOOKMARKS
- }).tree());
- var pubsubBookmarkRequest = Candy.Core.getConnection().getUniqueId("pubsub");
- Candy.Core.addHandler(Candy.Core.Event.Jabber.Bookmarks, Strophe.NS.PUBSUB, "iq", "result", pubsubBookmarkRequest);
- Candy.Core.getConnection().sendIQ($iq({
- type: "get",
- id: pubsubBookmarkRequest
- }).c("pubsub", {
- xmlns: Strophe.NS.PUBSUB
- }).c("items", {
- node: Strophe.NS.BOOKMARKS
- }).tree());
- } else if ($.isArray(Candy.Core.getOptions().autojoin)) {
- $.each(Candy.Core.getOptions().autojoin, function() {
- self.Jabber.Room.Join.apply(null, this.valueOf().split(":", 2));
- });
- } else {
- /** Event: candy:core.autojoin-missing
- * Triggered when no autojoin information has been found
- */
- $(Candy).triggerHandler("candy:core.autojoin-missing");
- }
- },
- /** Function: ResetIgnoreList
- * Create new ignore privacy list (and reset the previous one, if it exists).
- */
- ResetIgnoreList: function() {
- Candy.Core.getConnection().sendIQ($iq({
- type: "set",
- from: Candy.Core.getUser().getEscapedJid()
- }).c("query", {
- xmlns: Strophe.NS.PRIVACY
- }).c("list", {
- name: "ignore"
- }).c("item", {
- action: "allow",
- order: "0"
- }).tree());
- },
- /** Function: RemoveIgnoreList
- * Remove an existing ignore list.
- */
- RemoveIgnoreList: function() {
- Candy.Core.getConnection().sendIQ($iq({
- type: "set",
- from: Candy.Core.getUser().getEscapedJid()
- }).c("query", {
- xmlns: Strophe.NS.PRIVACY
- }).c("list", {
- name: "ignore"
- }).tree());
- },
- /** Function: GetIgnoreList
- * Get existing ignore privacy list when connecting.
- */
- GetIgnoreList: function() {
- var iq = $iq({
- type: "get",
- from: Candy.Core.getUser().getEscapedJid()
- }).c("query", {
- xmlns: Strophe.NS.PRIVACY
- }).c("list", {
- name: "ignore"
- }).tree();
- var iqId = Candy.Core.getConnection().sendIQ(iq);
- // add handler (<#200 at https://github.com/candy-chat/candy/issues/200>)
- Candy.Core.addHandler(Candy.Core.Event.Jabber.PrivacyList, null, "iq", null, iqId);
- },
- /** Function: SetIgnoreListActive
- * Set ignore privacy list active
- */
- SetIgnoreListActive: function() {
- Candy.Core.getConnection().sendIQ($iq({
- type: "set",
- from: Candy.Core.getUser().getEscapedJid()
- }).c("query", {
- xmlns: Strophe.NS.PRIVACY
- }).c("active", {
- name: "ignore"
- }).tree());
- },
- /** Function: GetJidIfAnonymous
- * On anonymous login, initially we don't know the jid and as a result, Candy.Core._user doesn't have a jid.
- * Check if user doesn't have a jid and get it if necessary from the connection.
- */
- GetJidIfAnonymous: function() {
- if (!Candy.Core.getUser().getJid()) {
- Candy.Core.log("[Jabber] Anonymous login");
- Candy.Core.getUser().data.jid = Candy.Core.getConnection().jid;
- }
- },
- /** Class: Candy.Core.Action.Jabber.Room
- * Room-specific commands
- */
- Room: {
- /** Function: Join
- * Requests disco of specified room and joins afterwards.
- *
- * TODO:
- * maybe we should wait for disco and later join the room?
- * but what if we send disco but don't want/can join the room
- *
- * Parameters:
- * (String) roomJid - Room to join
- * (String) password - [optional] Password for the room
- */
- Join: function(roomJid, password) {
- self.Jabber.Room.Disco(roomJid);
- roomJid = Candy.Util.escapeJid(roomJid);
- var conn = Candy.Core.getConnection(), roomNick = roomJid + "/" + Candy.Core.getUser().getNick(), pres = $pres({
- to: roomNick,
- id: "pres:" + conn.getUniqueId()
- }).c("x", {
- xmlns: Strophe.NS.MUC
- });
- if (password) {
- pres.c("password").t(password);
- }
- pres.up().c("c", conn.caps.generateCapsAttrs());
- conn.send(pres.tree());
- },
- /** Function: Leave
- * Leaves a room.
- *
- * Parameters:
- * (String) roomJid - Room to leave
- */
- Leave: function(roomJid) {
- var user = Candy.Core.getRoom(roomJid).getUser();
- roomJid = Candy.Util.escapeJid(roomJid);
- if (user) {
- Candy.Core.getConnection().muc.leave(roomJid, user.getNick(), function() {});
- }
- },
- /** Function: Disco
- * Requests <disco info of a room at http://xmpp.org/extensions/xep-0045.html#disco-roominfo>.
- *
- * Parameters:
- * (String) roomJid - Room to get info for
- */
- Disco: function(roomJid) {
- Candy.Core.getConnection().sendIQ($iq({
- type: "get",
- from: Candy.Core.getUser().getEscapedJid(),
- to: Candy.Util.escapeJid(roomJid)
- }).c("query", {
- xmlns: Strophe.NS.DISCO_INFO
- }).tree());
- },
- /** Function: Message
- * Send message
- *
- * Parameters:
- * (String) roomJid - Room to which send the message into
- * (String) msg - Message
- * (String) type - "groupchat" or "chat" ("chat" is for private messages)
- * (String) xhtmlMsg - XHTML formatted message [optional]
- *
- * Returns:
- * (Boolean) - true if message is not empty after trimming, false otherwise.
- */
- Message: function(roomJid, msg, type, xhtmlMsg) {
- // Trim message
- msg = $.trim(msg);
- if (msg === "") {
- return false;
- }
- var nick = null;
- if (type === "chat") {
- nick = Strophe.getResourceFromJid(roomJid);
- roomJid = Strophe.getBareJidFromJid(roomJid);
- }
- // muc takes care of the escaping now.
- Candy.Core.getConnection().muc.message(roomJid, nick, msg, xhtmlMsg, type);
- return true;
- },
- /** Function: Invite
- * Sends an invite stanza to multiple JIDs
- *
- * Parameters:
- * (String) roomJid - Room to which send the message into
- * (Array) invitees - Array of JIDs to be invited to the room
- * (String) reason - Message to include with the invitation [optional]
- * (String) password - Password for the MUC, if required [optional]
- */
- Invite: function(roomJid, invitees, reason, password) {
- reason = $.trim(reason);
- var message = $msg({
- to: roomJid
- });
- var x = message.c("x", {
- xmlns: Strophe.NS.MUC_USER
- });
- $.each(invitees, function(i, invitee) {
- invitee = Strophe.getBareJidFromJid(invitee);
- x.c("invite", {
- to: invitee
- });
- if (typeof reason !== "undefined" && reason !== "") {
- x.c("reason", reason);
- }
- });
- if (typeof password !== "undefined" && password !== "") {
- x.c("password", password);
- }
- Candy.Core.getConnection().send(message);
- },
- /** Function: IgnoreUnignore
- * Checks if the user is already ignoring the target user, if yes: unignore him, if no: ignore him.
- *
- * Uses the ignore privacy list set on connecting.
- *
- * Parameters:
- * (String) userJid - Target user jid
- */
- IgnoreUnignore: function(userJid) {
- Candy.Core.getUser().addToOrRemoveFromPrivacyList("ignore", userJid);
- Candy.Core.Action.Jabber.Room.UpdatePrivacyList();
- },
- /** Function: UpdatePrivacyList
- * Updates privacy list according to the privacylist in the currentUser
- */
- UpdatePrivacyList: function() {
- var currentUser = Candy.Core.getUser(), iq = $iq({
- type: "set",
- from: currentUser.getEscapedJid()
- }).c("query", {
- xmlns: "jabber:iq:privacy"
- }).c("list", {
- name: "ignore"
- }), privacyList = currentUser.getPrivacyList("ignore");
- if (privacyList.length > 0) {
- $.each(privacyList, function(index, jid) {
- iq.c("item", {
- type: "jid",
- value: Candy.Util.escapeJid(jid),
- action: "deny",
- order: index
- }).c("message").up().up();
- });
- } else {
- iq.c("item", {
- action: "allow",
- order: "0"
- });
- }
- Candy.Core.getConnection().sendIQ(iq.tree());
- },
- /** Class: Candy.Core.Action.Jabber.Room.Admin
- * Room administration commands
- */
- Admin: {
- /** Function: UserAction
- * Kick or ban a user
- *
- * Parameters:
- * (String) roomJid - Room in which the kick/ban should be done
- * (String) userJid - Victim
- * (String) type - "kick" or "ban"
- * (String) msg - Reason
- *
- * Returns:
- * (Boolean) - true if sent successfully, false if type is not one of "kick" or "ban".
- */
- UserAction: function(roomJid, userJid, type, reason) {
- roomJid = Candy.Util.escapeJid(roomJid);
- userJid = Candy.Util.escapeJid(userJid);
- var itemObj = {
- nick: Strophe.getResourceFromJid(userJid)
- };
- switch (type) {
- case "kick":
- itemObj.role = "none";
- break;
-
- case "ban":
- itemObj.affiliation = "outcast";
- break;
-
- default:
- return false;
- }
- Candy.Core.getConnection().sendIQ($iq({
- type: "set",
- from: Candy.Core.getUser().getEscapedJid(),
- to: roomJid
- }).c("query", {
- xmlns: Strophe.NS.MUC_ADMIN
- }).c("item", itemObj).c("reason").t(reason).tree());
- return true;
- },
- /** Function: SetSubject
- * Sets subject (topic) of a room.
- *
- * Parameters:
- * (String) roomJid - Room
- * (String) subject - Subject to set
- */
- SetSubject: function(roomJid, subject) {
- Candy.Core.getConnection().muc.setTopic(Candy.Util.escapeJid(roomJid), subject);
- }
- }
- }
- };
- return self;
-}(Candy.Core.Action || {}, Strophe, jQuery);
-
-/** File: chatRoom.js
- * Candy - Chats are not dead yet.
- *
- * Authors:
- * - Patrick Stadler <patrick.stadler@gmail.com>
- * - Michael Weibel <michael.weibel@gmail.com>
- *
- * Copyright:
- * (c) 2011 Amiado Group AG. All rights reserved.
- * (c) 2012-2014 Patrick Stadler & Michael Weibel. All rights reserved.
- */
-"use strict";
-
-/* global Candy, Strophe */
-/** Class: Candy.Core.ChatRoom
- * Candy Chat Room
- *
- * Parameters:
- * (String) roomJid - Room jid
- */
-Candy.Core.ChatRoom = function(roomJid) {
- /** Object: room
- * Object containing roomJid and name.
- */
- this.room = {
- jid: roomJid,
- name: Strophe.getNodeFromJid(roomJid)
- };
- /** Variable: user
- * Current local user of this room.
- */
- this.user = null;
- /** Variable: Roster
- * Candy.Core.ChatRoster instance
- */
- this.roster = new Candy.Core.ChatRoster();
- /** Function: setUser
- * Set user of this room.
- *
- * Parameters:
- * (Candy.Core.ChatUser) user - Chat user
- */
- this.setUser = function(user) {
- this.user = user;
- };
- /** Function: getUser
- * Get current local user
- *
- * Returns:
- * (Object) - Candy.Core.ChatUser instance or null
- */
- this.getUser = function() {
- return this.user;
- };
- /** Function: getJid
- * Get room jid
- *
- * Returns:
- * (String) - Room jid
- */
- this.getJid = function() {
- return this.room.jid;
- };
- /** Function: setName
- * Set room name
- *
- * Parameters:
- * (String) name - Room name
- */
- this.setName = function(name) {
- this.room.name = name;
- };
- /** Function: getName
- * Get room name
- *
- * Returns:
- * (String) - Room name
- */
- this.getName = function() {
- return this.room.name;
- };
- /** Function: setRoster
- * Set roster of room
- *
- * Parameters:
- * (Candy.Core.ChatRoster) roster - Chat roster
- */
- this.setRoster = function(roster) {
- this.roster = roster;
- };
- /** Function: getRoster
- * Get roster
- *
- * Returns
- * (Candy.Core.ChatRoster) - instance
- */
- this.getRoster = function() {
- return this.roster;
- };
-};
-
-/** File: chatRoster.js
- * Candy - Chats are not dead yet.
- *
- * Authors:
- * - Patrick Stadler <patrick.stadler@gmail.com>
- * - Michael Weibel <michael.weibel@gmail.com>
- *
- * Copyright:
- * (c) 2011 Amiado Group AG. All rights reserved.
- * (c) 2012-2014 Patrick Stadler & Michael Weibel. All rights reserved.
- */
-"use strict";
-
-/* global Candy */
-/** Class: Candy.Core.ChatRoster
- * Chat Roster
- */
-Candy.Core.ChatRoster = function() {
- /** Object: items
- * Roster items
- */
- this.items = {};
- /** Function: add
- * Add user to roster
- *
- * Parameters:
- * (Candy.Core.ChatUser) user - User to add
- */
- this.add = function(user) {
- this.items[user.getJid()] = user;
- };
- /** Function: remove
- * Remove user from roster
- *
- * Parameters:
- * (String) jid - User jid
- */
- this.remove = function(jid) {
- delete this.items[jid];
- };
- /** Function: get
- * Get user from roster
- *
- * Parameters:
- * (String) jid - User jid
- *
- * Returns:
- * (Candy.Core.ChatUser) - User
- */
- this.get = function(jid) {
- return this.items[jid];
- };
- /** Function: getAll
- * Get all items
- *
- * Returns:
- * (Object) - all roster items
- */
- this.getAll = function() {
- return this.items;
- };
-};
-
-/** File: chatUser.js
- * Candy - Chats are not dead yet.
- *
- * Authors:
- * - Patrick Stadler <patrick.stadler@gmail.com>
- * - Michael Weibel <michael.weibel@gmail.com>
- *
- * Copyright:
- * (c) 2011 Amiado Group AG. All rights reserved.
- * (c) 2012-2014 Patrick Stadler & Michael Weibel. All rights reserved.
- */
-"use strict";
-
-/* global Candy, Strophe */
-/** Class: Candy.Core.ChatUser
- * Chat User
- */
-Candy.Core.ChatUser = function(jid, nick, affiliation, role) {
- /** Constant: ROLE_MODERATOR
- * Moderator role
- */
- this.ROLE_MODERATOR = "moderator";
- /** Constant: AFFILIATION_OWNER
- * Affiliation owner
- */
- this.AFFILIATION_OWNER = "owner";
- /** Object: data
- * User data containing:
- * - jid
- * - nick
- * - affiliation
- * - role
- * - privacyLists
- * - customData to be used by e.g. plugins
- */
- this.data = {
- jid: jid,
- nick: Strophe.unescapeNode(nick),
- affiliation: affiliation,
- role: role,
- privacyLists: {},
- customData: {},
- previousNick: undefined
- };
- /** Function: getJid
- * Gets an unescaped user jid
- *
- * See:
- * <Candy.Util.unescapeJid>
- *
- * Returns:
- * (String) - jid
- */
- this.getJid = function() {
- if (this.data.jid) {
- return Candy.Util.unescapeJid(this.data.jid);
- }
- return;
- };
- /** Function: getEscapedJid
- * Escapes the user's jid (node & resource get escaped)
- *
- * See:
- * <Candy.Util.escapeJid>
- *
- * Returns:
- * (String) - escaped jid
- */
- this.getEscapedJid = function() {
- return Candy.Util.escapeJid(this.data.jid);
- };
- /** Function: setJid
- * Sets a user's jid
- *
- * Parameters:
- * (String) jid - New Jid
- */
- this.setJid = function(jid) {
- this.data.jid = jid;
- };
- /** Function: getNick
- * Gets user nick
- *
- * Returns:
- * (String) - nick
- */
- this.getNick = function() {
- return Strophe.unescapeNode(this.data.nick);
- };
- /** Function: setNick
- * Sets a user's nick
- *
- * Parameters:
- * (String) nick - New nick
- */
- this.setNick = function(nick) {
- this.data.nick = nick;
- };
- /** Function: getRole
- * Gets user role
- *
- * Returns:
- * (String) - role
- */
- this.getRole = function() {
- return this.data.role;
- };
- /** Function: setRole
- * Sets user role
- *
- * Parameters:
- * (String) role - Role
- */
- this.setRole = function(role) {
- this.data.role = role;
- };
- /** Function: setAffiliation
- * Sets user affiliation
- *
- * Parameters:
- * (String) affiliation - new affiliation
- */
- this.setAffiliation = function(affiliation) {
- this.data.affiliation = affiliation;
- };
- /** Function: getAffiliation
- * Gets user affiliation
- *
- * Returns:
- * (String) - affiliation
- */
- this.getAffiliation = function() {
- return this.data.affiliation;
- };
- /** Function: isModerator
- * Check if user is moderator. Depends on the room.
- *
- * Returns:
- * (Boolean) - true if user has role moderator or affiliation owner
- */
- this.isModerator = function() {
- return this.getRole() === this.ROLE_MODERATOR || this.getAffiliation() === this.AFFILIATION_OWNER;
- };
- /** Function: addToOrRemoveFromPrivacyList
- * Convenience function for adding/removing users from ignore list.
- *
- * Check if user is already in privacy list. If yes, remove it. If no, add it.
- *
- * Parameters:
- * (String) list - To which privacy list the user should be added / removed from. Candy supports curently only the "ignore" list.
- * (String) jid - User jid to add/remove
- *
- * Returns:
- * (Array) - Current privacy list.
- */
- this.addToOrRemoveFromPrivacyList = function(list, jid) {
- if (!this.data.privacyLists[list]) {
- this.data.privacyLists[list] = [];
- }
- var index = -1;
- if ((index = this.data.privacyLists[list].indexOf(jid)) !== -1) {
- this.data.privacyLists[list].splice(index, 1);
- } else {
- this.data.privacyLists[list].push(jid);
- }
- return this.data.privacyLists[list];
- };
- /** Function: getPrivacyList
- * Returns the privacy list of the listname of the param.
- *
- * Parameters:
- * (String) list - To which privacy list the user should be added / removed from. Candy supports curently only the "ignore" list.
- *
- * Returns:
- * (Array) - Privacy List
- */
- this.getPrivacyList = function(list) {
- if (!this.data.privacyLists[list]) {
- this.data.privacyLists[list] = [];
- }
- return this.data.privacyLists[list];
- };
- /** Function: setPrivacyLists
- * Sets privacy lists.
- *
- * Parameters:
- * (Object) lists - List object
- */
- this.setPrivacyLists = function(lists) {
- this.data.privacyLists = lists;
- };
- /** Function: isInPrivacyList
- * Tests if this user ignores the user provided by jid.
- *
- * Parameters:
- * (String) list - Privacy list
- * (String) jid - Jid to test for
- *
- * Returns:
- * (Boolean)
- */
- this.isInPrivacyList = function(list, jid) {
- if (!this.data.privacyLists[list]) {
- return false;
- }
- return this.data.privacyLists[list].indexOf(jid) !== -1;
- };
- /** Function: setCustomData
- * Stores custom data
- *
- * Parameter:
- * (Object) data - Object containing custom data
- */
- this.setCustomData = function(data) {
- this.data.customData = data;
- };
- /** Function: getCustomData
- * Retrieve custom data
- *
- * Returns:
- * (Object) - Object containing custom data
- */
- this.getCustomData = function() {
- return this.data.customData;
- };
- /** Function: setPreviousNick
- * If user has nickname changed, set previous nickname.
- *
- * Parameters:
- * (String) previousNick - the previous nickname
- */
- this.setPreviousNick = function(previousNick) {
- this.data.previousNick = previousNick;
- };
- /** Function: hasNicknameChanged
- * Gets the previous nickname if available.
- *
- * Returns:
- * (String) - previous nickname
- */
- this.getPreviousNick = function() {
- return this.data.previousNick;
- };
-};
-
-/** File: event.js
- * Candy - Chats are not dead yet.
- *
- * Authors:
- * - Patrick Stadler <patrick.stadler@gmail.com>
- * - Michael Weibel <michael.weibel@gmail.com>
- *
- * Copyright:
- * (c) 2011 Amiado Group AG. All rights reserved.
- * (c) 2012-2014 Patrick Stadler & Michael Weibel. All rights reserved.
- */
-"use strict";
-
-/* global Candy, Strophe, jQuery */
-/** Class: Candy.Core.Event
- * Chat Events
- *
- * Parameters:
- * (Candy.Core.Event) self - itself
- * (Strophe) Strophe - Strophe
- * (jQuery) $ - jQuery
- */
-Candy.Core.Event = function(self, Strophe, $) {
- /** Function: Login
- * Notify view that the login window should be displayed
- *
- * Parameters:
- * (String) presetJid - Preset user JID
- *
- * Triggers:
- * candy:core.login using {presetJid}
- */
- 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
- * Strophe-related events
- */
- self.Strophe = {
- /** Function: Connect
- * Acts on strophe status events and notifies view.
- *
- * 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");
- Candy.Core.Action.Jabber.GetJidIfAnonymous();
-
- /* falls through */
- case Strophe.Status.ATTACHED:
- Candy.Core.log("[Connection] Attached");
- Candy.Core.Action.Jabber.Presence();
- Candy.Core.Action.Jabber.Autojoin();
- Candy.Core.Action.Jabber.GetIgnoreList();
- break;
-
- case Strophe.Status.DISCONNECTED:
- Candy.Core.log("[Connection] Disconnected");
- break;
-
- case Strophe.Status.AUTHFAIL:
- Candy.Core.log("[Connection] Authentication failed");
- break;
-
- case Strophe.Status.CONNECTING:
- Candy.Core.log("[Connection] Connecting");
- break;
-
- case Strophe.Status.DISCONNECTING:
- Candy.Core.log("[Connection] Disconnecting");
- break;
-
- case Strophe.Status.AUTHENTICATING:
- Candy.Core.log("[Connection] Authenticating");
- break;
-
- case Strophe.Status.ERROR:
- case Strophe.Status.CONNFAIL:
- Candy.Core.log("[Connection] Failed (" + status + ")");
- break;
-
- default:
- Candy.Core.log("[Connection] What?!");
- break;
- }
- /** Event: candy:core.chat.connection
- * Connection status updates
- *
- * Parameters:
- * (Strophe.Status) status - Strophe status
- */
- $(Candy).triggerHandler("candy:core.chat.connection", {
- status: status
- });
- }
- };
- /** Class: Candy.Core.Event.Jabber
- * Jabber related events
- */
- self.Jabber = {
- /** Function: Version
- * Responds to a version request
- *
- * Parameters:
- * (String) msg - Raw XML Message
- *
- * Returns:
- * (Boolean) - true
- */
- Version: function(msg) {
- Candy.Core.log("[Jabber] Version");
- Candy.Core.Action.Jabber.Version($(msg));
- return true;
- },
- /** Function: Presence
- * Acts on a presence event
- *
- * Parameters:
- * (String) msg - Raw XML Message
- *
- * Triggers:
- * candy:core.presence using {from, stanza}
- *
- * Returns:
- * (Boolean) - true
- */
- Presence: function(msg) {
- Candy.Core.log("[Jabber] Presence");
- msg = $(msg);
- if (msg.children('x[xmlns^="' + Strophe.NS.MUC + '"]').length > 0) {
- if (msg.attr("type") === "error") {
- self.Jabber.Room.PresenceError(msg);
- } else {
- self.Jabber.Room.Presence(msg);
- }
- } 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;
- },
- /** Function: Bookmarks
- * Acts on a bookmarks event. When a bookmark has the attribute autojoin set, joins this room.
- *
- * Parameters:
- * (String) msg - Raw XML Message
- *
- * Returns:
- * (Boolean) - true
- */
- Bookmarks: function(msg) {
- Candy.Core.log("[Jabber] Bookmarks");
- // Autojoin bookmarks
- $("conference", msg).each(function() {
- var item = $(this);
- if (item.attr("autojoin")) {
- Candy.Core.Action.Jabber.Room.Join(item.attr("jid"));
- }
- });
- return true;
- },
- /** Function: PrivacyList
- * Acts on a privacy list event and sets up the current privacy list of this user.
- *
- * If no privacy list has been added yet, create the privacy list and listen again to this event.
- *
- * Parameters:
- * (String) msg - Raw XML Message
- *
- * Returns:
- * (Boolean) - false to disable the handler after first call.
- */
- PrivacyList: function(msg) {
- Candy.Core.log("[Jabber] PrivacyList");
- var currentUser = Candy.Core.getUser();
- msg = $(msg);
- if (msg.attr("type") === "result") {
- $('list[name="ignore"] item', msg).each(function() {
- var item = $(this);
- if (item.attr("action") === "deny") {
- currentUser.addToOrRemoveFromPrivacyList("ignore", item.attr("value"));
- }
- });
- Candy.Core.Action.Jabber.SetIgnoreListActive();
- return false;
- }
- return self.Jabber.PrivacyListError(msg);
- },
- /** Function: PrivacyListError
- * Acts when a privacy list error has been received.
- *
- * Currently only handles the case, when a privacy list doesn't exist yet and creates one.
- *
- * Parameters:
- * (String) msg - Raw XML Message
- *
- * Returns:
- * (Boolean) - false to disable the handler after first call.
- */
- PrivacyListError: function(msg) {
- Candy.Core.log("[Jabber] PrivacyListError");
- // check if msg says that privacyList doesn't exist
- if ($('error[code="404"][type="cancel"] item-not-found', msg)) {
- Candy.Core.Action.Jabber.ResetIgnoreList();
- Candy.Core.Action.Jabber.SetIgnoreListActive();
- }
- return false;
- },
- /** Function: Message
- * Acts on room, admin and server messages and notifies the view if required.
- *
- * Parameters:
- * (String) msg - Raw XML Message
- *
- * Triggers:
- * candy:core.chat.message.admin using {type, message}
- * candy:core.chat.message.server {type, subject, message}
- *
- * Returns:
- * (Boolean) - true
- */
- Message: function(msg) {
- Candy.Core.log("[Jabber] Message");
- msg = $(msg);
- var fromJid = msg.attr("from"), type = msg.attr("type") || "undefined", toJid = msg.attr("to");
- // Inspect the message type.
- if (type === "normal" || type === "undefined") {
- var mediatedInvite = msg.find("invite"), directInvite = msg.find('x[xmlns="jabber:x:conference"]');
- if (mediatedInvite.length > 0) {
- var passwordNode = msg.find("password"), password = null, continueNode = mediatedInvite.find("continue"), continuedThread = null;
- if (passwordNode) {
- password = passwordNode.text();
- }
- if (continueNode) {
- continuedThread = continueNode.attr("thread");
- }
- /** Event: candy:core:chat:invite
- * Incoming chat invite for a MUC.
- *
- * Parameters:
- * (String) roomJid - The room the invite is to
- * (String) from - User JID that invite is from text
- * (String) reason - Reason for invite [default: '']
- * (String) password - Password for the room [default: null]
- * (String) continuedThread - The thread ID if this is a continuation of a 1-on-1 chat [default: null]
- */
- $(Candy).triggerHandler("candy:core:chat:invite", {
- roomJid: fromJid,
- from: mediatedInvite.attr("from") || "undefined",
- reason: mediatedInvite.find("reason").html() || "",
- password: password,
- continuedThread: continuedThread
- });
- }
- if (directInvite.length > 0) {
- /** Event: candy:core:chat:invite
- * Incoming chat invite for a MUC.
- *
- * Parameters:
- * (String) roomJid - The room the invite is to
- * (String) from - User JID that invite is from text
- * (String) reason - Reason for invite [default: '']
- * (String) password - Password for the room [default: null]
- * (String) continuedThread - The thread ID if this is a continuation of a 1-on-1 chat [default: null]
- */
- $(Candy).triggerHandler("candy:core:chat:invite", {
- roomJid: directInvite.attr("jid"),
- from: fromJid,
- reason: directInvite.attr("reason") || "",
- password: directInvite.attr("password"),
- continuedThread: directInvite.attr("thread")
- });
- }
- /** Event: candy:core:chat:message:normal
- * Messages with the type attribute of normal or those
- * that do not have the optional type attribute.
- *
- * Parameters:
- * (String) type - Type of the message [default: message]
- * (Object) message - Message object.
- */
- // Detect message with type normal or with no type.
- $(Candy).triggerHandler("candy:core:chat:message:normal", {
- type: type || "normal",
- message: msg
- });
- return true;
- } else if (type !== "groupchat" && type !== "chat" && type !== "error" && type !== "headline") {
- /** Event: candy:core:chat:message:other
- * Messages with a type other than the ones listed in RFC3921
- * section 2.1.1. This allows plugins to catch custom message
- * types.
- *
- * Parameters:
- * (String) type - Type of the message [default: message]
- * (Object) message - Message object.
- */
- // Detect message with type normal or with no type.
- $(Candy).triggerHandler("candy:core:chat:message:other", {
- type: type,
- message: msg
- });
- return true;
- }
- // Room message
- if (fromJid !== Strophe.getDomainFromJid(fromJid) && (type === "groupchat" || type === "chat" || type === "error")) {
- self.Jabber.Room.Message(msg);
- } else if (!toJid && fromJid === Strophe.getDomainFromJid(fromJid)) {
- /** 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()
- });
- } else if (toJid && fromJid === Strophe.getDomainFromJid(fromJid)) {
- /** 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;
- },
- /** Class: Candy.Core.Event.Jabber.Room
- * Room specific events
- */
- Room: {
- /** Function: Leave
- * Leaves a room and cleans up related data and notifies view.
- *
- * Parameters:
- * (String) msg - Raw XML Message
- *
- * Triggers:
- * candy:core.presence.leave using {roomJid, roomName, type, reason, actor, user}
- *
- * Returns:
- * (Boolean) - true
- */
- Leave: function(msg) {
- Candy.Core.log("[Jabber:Room] Leave");
- msg = $(msg);
- var from = Candy.Util.unescapeJid(msg.attr("from")), roomJid = Strophe.getBareJidFromJid(from);
- // if room is not joined yet, ignore.
- if (!Candy.Core.getRoom(roomJid)) {
- return true;
- }
- var roomName = Candy.Core.getRoom(roomJid).getName(), item = msg.find("item"), type = "leave", reason, actor;
- delete Candy.Core.getRooms()[roomJid];
- // if user gets kicked, role is none and there's a status code 307
- if (item.attr("role") === "none") {
- var code = msg.find("status").attr("code");
- if (code === "307") {
- type = "kick";
- } else if (code === "301") {
- type = "ban";
- }
- reason = item.find("reason").text();
- actor = item.find("actor").attr("jid");
- }
- var user = new Candy.Core.ChatUser(from, Strophe.getResourceFromJid(from), item.attr("affiliation"), item.attr("role"));
- /** 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;
- },
- /** Function: Disco
- * Sets informations to rooms according to the disco info received.
- *
- * Parameters:
- * (String) msg - Raw XML Message
- *
- * Returns:
- * (Boolean) - true
- */
- Disco: function(msg) {
- Candy.Core.log("[Jabber:Room] Disco");
- msg = $(msg);
- // Temp fix for #219
- // Don't go further if it's no conference disco reply
- // FIXME: Do this in a more beautiful way
- if (!msg.find('identity[category="conference"]').length) {
- return true;
- }
- var roomJid = Strophe.getBareJidFromJid(Candy.Util.unescapeJid(msg.attr("from")));
- // Client joined a room
- if (!Candy.Core.getRooms()[roomJid]) {
- Candy.Core.getRooms()[roomJid] = new Candy.Core.ChatRoom(roomJid);
- }
- // Room existed but room name was unknown
- var identity = msg.find("identity");
- if (identity.length) {
- var roomName = identity.attr("name"), room = Candy.Core.getRoom(roomJid);
- if (room.getName() === null) {
- room.setName(Strophe.unescapeNode(roomName));
- }
- }
- return true;
- },
- /** Function: Presence
- * Acts on various presence messages (room leaving, room joining, error presence) and notifies view.
- *
- * Parameters:
- * (Object) msg - jQuery object of XML message
- *
- * Triggers:
- * candy:core.presence.room using {roomJid, roomName, user, action, currentUser}
- *
- * Returns:
- * (Boolean) - true
- */
- Presence: function(msg) {
- Candy.Core.log("[Jabber:Room] Presence");
- var from = Candy.Util.unescapeJid(msg.attr("from")), roomJid = Strophe.getBareJidFromJid(from), presenceType = msg.attr("type"), status = msg.find("status"), nickAssign = false, nickChange = false;
- if (status.length) {
- // check if status code indicates a nick assignment or nick change
- for (var i = 0, l = status.length; i < l; i++) {
- var $status = $(status[i]), code = $status.attr("code");
- if (code === "303") {
- nickChange = true;
- } else if (code === "210") {
- nickAssign = true;
- }
- }
- }
- // Current User joined a room
- var room = Candy.Core.getRoom(roomJid);
- if (!room) {
- Candy.Core.getRooms()[roomJid] = new Candy.Core.ChatRoom(roomJid);
- room = Candy.Core.getRoom(roomJid);
- }
- // Current User left a room
- var currentUser = room.getUser() ? room.getUser() : Candy.Core.getUser();
- if (Strophe.getResourceFromJid(from) === currentUser.getNick() && presenceType === "unavailable" && nickChange === false) {
- self.Jabber.Room.Leave(msg);
- return true;
- }
- var roster = room.getRoster(), action, user, nick, item = msg.find("item");
- // User joined a room
- if (presenceType !== "unavailable") {
- if (roster.get(from)) {
- // role/affiliation change
- user = roster.get(from);
- var role = item.attr("role"), affiliation = item.attr("affiliation");
- user.setRole(role);
- user.setAffiliation(affiliation);
- // FIXME: currently role/affilation changes are handled with this action
- action = "join";
- } else {
- nick = Strophe.getResourceFromJid(from);
- user = new Candy.Core.ChatUser(from, nick, item.attr("affiliation"), item.attr("role"));
- // Room existed but client (myself) is not yet registered
- if (room.getUser() === null && (Candy.Core.getUser().getNick() === nick || nickAssign)) {
- room.setUser(user);
- currentUser = user;
- }
- roster.add(user);
- action = "join";
- }
- } else {
- user = roster.get(from);
- roster.remove(from);
- if (nickChange) {
- // user changed nick
- nick = item.attr("nick");
- action = "nickchange";
- user.setPreviousNick(user.getNick());
- user.setNick(nick);
- user.setJid(Strophe.getBareJidFromJid(from) + "/" + nick);
- roster.add(user);
- } else {
- action = "leave";
- if (item.attr("role") === "none") {
- if (msg.find("status").attr("code") === "307") {
- action = "kick";
- } else if (msg.find("status").attr("code") === "301") {
- action = "ban";
- }
- }
- }
- }
- /** 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: currentUser
- });
- 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
- */
- PresenceError: function(msg) {
- Candy.Core.log("[Jabber:Room] Presence Error");
- var from = Candy.Util.unescapeJid(msg.attr("from")), roomJid = Strophe.getBareJidFromJid(from), room = Candy.Core.getRooms()[roomJid], roomName = room.getName();
- // Presence error: Remove room from array to prevent error when disconnecting
- Candy.Core.removeRoom(roomJid);
- room = undefined;
- /** 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
- });
- return true;
- },
- /** Function: Message
- * Acts on various message events (subject changed, private chat message, multi-user chat message)
- * and notifies view.
- *
- * Parameters:
- * (String) msg - jQuery object of XML message
- *
- * Triggers:
- * candy:core.message using {roomJid, message, timestamp}
- *
- * Returns:
- * (Boolean) - true
- */
- Message: function(msg) {
- Candy.Core.log("[Jabber:Room] Message");
- // Room subject
- var roomJid, message, name;
- if (msg.children("subject").length > 0 && msg.children("subject").text().length > 0 && msg.attr("type") === "groupchat") {
- roomJid = Candy.Util.unescapeJid(Strophe.getBareJidFromJid(msg.attr("from")));
- message = {
- name: Strophe.getNodeFromJid(roomJid),
- body: msg.children("subject").text(),
- type: "subject"
- };
- } else if (msg.attr("type") === "error") {
- var error = msg.children("error");
- if (error.children("text").length > 0) {
- roomJid = msg.attr("from");
- message = {
- type: "info",
- body: error.children("text").text()
- };
- }
- } else if (msg.children("body").length > 0) {
- // Private chat message
- 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.
- isNoConferenceRoomJid = !Candy.Core.getRoom(bareRoomJid);
- name = isNoConferenceRoomJid ? Strophe.getNodeFromJid(roomJid) : Strophe.getResourceFromJid(roomJid);
- message = {
- name: name,
- body: msg.children("body").text(),
- type: msg.attr("type"),
- isNoConferenceRoomJid: isNoConferenceRoomJid
- };
- } else {
- roomJid = Candy.Util.unescapeJid(Strophe.getBareJidFromJid(msg.attr("from")));
- var resource = Strophe.getResourceFromJid(msg.attr("from"));
- // Message from a user
- if (resource) {
- resource = Strophe.unescapeNode(resource);
- message = {
- name: resource,
- body: msg.children("body").text(),
- type: msg.attr("type")
- };
- } 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"
- };
- }
- }
- var xhtmlChild = msg.children('html[xmlns="' + Strophe.NS.XHTML_IM + '"]');
- if (Candy.View.getOptions().enableXHTML === true && xhtmlChild.length > 0) {
- var xhtmlMessage = xhtmlChild.children('body[xmlns="' + Strophe.NS.XHTML + '"]').first().html();
- message.xhtmlMessage = xhtmlMessage;
- }
- } else if (msg.children("composing").length > 0 || msg.children("inactive").length > 0 || msg.children("paused").length > 0) {
- roomJid = Candy.Util.unescapeJid(msg.attr("from"));
- name = Strophe.getResourceFromJid(roomJid);
- var chatstate;
- if (msg.children("composing").length > 0) {
- chatstate = "composing";
- } else if (msg.children("paused").length > 0) {
- chatstate = "paused";
- } else if (msg.children("inactive").length > 0) {
- chatstate = "inactive";
- } else if (msg.children("gone").length > 0) {
- chatstate = "gone";
- }
- /** Event: candy:core.message.chatstate
- * Triggers on any recieved chatstate notification.
- *
- * The resulting message object contains the name of the person, the roomJid, and the indicated chatstate.
- *
- * The following lists explain those parameters:
- *
- * Message Object Parameters:
- * (String) name - User name
- * (String) roomJid - Room jid
- * (String) chatstate - Chatstate being indicated. ("paused", "inactive", "composing", "gone")
- *
- * TODO:
- * Perhaps handle blank "active" as specified by XEP-0085?
- */
- $(Candy).triggerHandler("candy:core.message.chatstate", {
- name: name,
- roomJid: roomJid,
- chatstate: chatstate
- });
- return true;
- } else {
- return true;
- }
- // besides the delayed delivery (XEP-0203), there exists also XEP-0091 which is the legacy delayed delivery.
- // the x[xmlns=jabber:x:delay] is the format in XEP-0091.
- var delay = msg.children("delay") ? msg.children("delay") : msg.children('x[xmlns="' + Strophe.NS.DELAY + '"]'), timestamp = delay !== undefined ? delay.attr("stamp") : null;
- /** 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);
-
-/** File: observer.js
- * Candy - Chats are not dead yet.
- *
- * Authors:
- * - Patrick Stadler <patrick.stadler@gmail.com>
- * - Michael Weibel <michael.weibel@gmail.com>
- *
- * Copyright:
- * (c) 2011 Amiado Group AG. All rights reserved.
- * (c) 2012-2014 Patrick Stadler & Michael Weibel
- */
-"use strict";
-
-/* global Candy, Strophe, Mustache, jQuery */
-/** Class: Candy.View.Observer
- * Observes Candy core events
- *
- * Parameters:
- * (Candy.View.Observer) self - itself
- * (jQuery) $ - jQuery
- */
-Candy.View.Observer = function(self, $) {
- /** PrivateVariable: _showConnectedMessageModal
- * Ugly way to determine if the 'connected' modal should be shown.
- * Is set to false in case no autojoin param is set.
- */
- var _showConnectedMessageModal = true;
- /** Class: Candy.View.Observer.Chat
- * Chat events
- */
- self.Chat = {
- /** Function: Connection
- * The update method gets called whenever an event to which "Chat" is subscribed.
- *
- * Currently listens for connection status updates
- *
- * Parameters:
- * (jQuery.Event) event - jQuery Event object
- * (Object) args - {status (Strophe.Status.*)}
- */
- Connection: function(event, args) {
- var eventName = "candy:view.connection.status-" + args.status;
- /** Event: candy:view.connection.status-<STROPHE-STATUS>
- * Using this event, you can alter the default Candy (View) behaviour when reacting
- * to connection updates.
- *
- * STROPHE-STATUS has to be replaced by one of <Strophe.Status at https://github.com/strophe/strophejs/blob/master/src/core.js#L276>:
- * - ERROR: 0,
- * - CONNECTING: 1,
- * - CONNFAIL: 2,
- * - AUTHENTICATING: 3,
- * - AUTHFAIL: 4,
- * - CONNECTED: 5,
- * - DISCONNECTED: 6,
- * - DISCONNECTING: 7,
- * - ATTACHED: 8
- *
- *
- * If your event handler returns `false`, no View changes will take place.
- * You can, of course, also return `true` and do custom things but still
- * let Candy (View) do it's job.
- *
- * This event has been implemented due to <issue #202 at https://github.com/candy-chat/candy/issues/202>
- * and here's an example use-case for it:
- *
- * (start code)
- * // react to DISCONNECTED event
- * $(Candy).on('candy:view.connection.status-6', function() {
- * // on next browser event loop
- * setTimeout(function() {
- * // reload page to automatically reattach on disconnect
- * window.location.reload();
- * }, 0);
- * // stop view changes right here.
- * return false;
- * });
- * (end code)
- */
- if ($(Candy).triggerHandler(eventName) === false) {
- return false;
- }
- 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:
- if (_showConnectedMessageModal === true) {
- // only show 'connected' if the autojoin error is not shown
- // which is determined by having a visible modal in this stage.
- Candy.View.Pane.Chat.Modal.show($.i18n._("statusConnected"));
- Candy.View.Pane.Chat.Modal.hide();
- }
- break;
-
- case Strophe.Status.DISCONNECTING:
- Candy.View.Pane.Chat.Modal.show($.i18n._("statusDisconnecting"), false, true);
- break;
-
- case Strophe.Status.DISCONNECTED:
- var presetJid = Candy.Core.isAnonymousConnection() ? Strophe.getDomainFromJid(Candy.Core.getUser().getJid()) : null;
- Candy.View.Pane.Chat.Modal.showLoginForm($.i18n._("statusDisconnected"), presetJid);
- break;
-
- case Strophe.Status.AUTHFAIL:
- Candy.View.Pane.Chat.Modal.showLoginForm($.i18n._("statusAuthfail"));
- break;
-
- default:
- Candy.View.Pane.Chat.Modal.show($.i18n._("status", args.status));
- break;
- }
- },
- /** 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.
- Candy.View.Pane.Chat.onInfoMessage(Candy.View.getCurrent().roomJid, args.subject || "", args.message);
- }
- }
- };
- /** Class: Candy.View.Observer.Presence
- * Presence update events
- */
- self.Presence = {
- /** Function: update
- * Every presence update gets dispatched from this method.
- *
- * Parameters:
- * (jQuery.Event) event - jQuery.Event object
- * (Object) args - Arguments differ on each type
- *
- * Uses:
- * - <notifyPrivateChats>
- */
- update: function(event, args) {
- // Client left
- if (args.type === "leave") {
- var user = Candy.View.Pane.Room.getUser(args.roomJid);
- Candy.View.Pane.Room.close(args.roomJid);
- self.Presence.notifyPrivateChats(user, args.type);
- } else if (args.type === "kick" || args.type === "ban") {
- var actorName = args.actor ? Strophe.getNodeFromJid(args.actor) : null, actionLabel, translationParams = [ args.roomName ];
- if (actorName) {
- translationParams.push(actorName);
- }
- switch (args.type) {
- case "kick":
- actionLabel = $.i18n._(actorName ? "youHaveBeenKickedBy" : "youHaveBeenKicked", translationParams);
- break;
-
- case "ban":
- actionLabel = $.i18n._(actorName ? "youHaveBeenBannedBy" : "youHaveBeenBanned", translationParams);
- break;
- }
- Candy.View.Pane.Chat.Modal.show(Mustache.to_html(Candy.View.Template.Chat.Context.adminMessageReason, {
- reason: args.reason,
- _action: actionLabel,
- _reason: $.i18n._("reasonWas", [ args.reason ])
- }));
- setTimeout(function() {
- Candy.View.Pane.Chat.Modal.hide(function() {
- Candy.View.Pane.Room.close(args.roomJid);
- self.Presence.notifyPrivateChats(args.user, args.type);
- });
- }, 5e3);
- var evtData = {
- type: args.type,
- reason: args.reason,
- roomJid: args.roomJid,
- user: args.user
- };
- /** 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 ]);
- } else if (args.roomJid) {
- args.roomJid = Candy.Util.unescapeJid(args.roomJid);
- // Initialize room if not yet existing
- if (!Candy.View.Pane.Chat.rooms[args.roomJid]) {
- if (Candy.View.Pane.Room.init(args.roomJid, args.roomName) === false) {
- return false;
- }
- Candy.View.Pane.Room.show(args.roomJid);
- }
- Candy.View.Pane.Roster.update(args.roomJid, args.user, args.action, args.currentUser);
- // Notify private user chats if existing, but not in case the action is nickchange
- // -- this is because the nickchange presence already contains the new
- // user jid
- if (Candy.View.Pane.Chat.rooms[args.user.getJid()] && args.action !== "nickchange") {
- Candy.View.Pane.Roster.update(args.user.getJid(), args.user, args.action, args.currentUser);
- Candy.View.Pane.PrivateRoom.setStatus(args.user.getJid(), args.action);
- }
- }
- },
- /** Function: notifyPrivateChats
- * Notify private user chats if existing
- *
- * Parameters:
- * (Candy.Core.ChatUser) user - User which has done the event
- * (String) type - Event type (leave, join, kick/ban)
- */
- notifyPrivateChats: function(user, type) {
- Candy.Core.log("[View:Observer] notify Private Chats");
- var roomJid;
- for (roomJid in Candy.View.Pane.Chat.rooms) {
- if (Candy.View.Pane.Chat.rooms.hasOwnProperty(roomJid) && Candy.View.Pane.Room.getUser(roomJid) && user.getJid() === Candy.View.Pane.Room.getUser(roomJid).getJid()) {
- Candy.View.Pane.Roster.update(roomJid, user, type, user);
- Candy.View.Pane.PrivateRoom.setStatus(roomJid, type);
- }
- }
- }
- };
- /** Function: Candy.View.Observer.PresenceError
- * Presence errors get handled in this method
- *
- * Parameters:
- * (jQuery.Event) event - jQuery.Event object
- * (Object) args - {msg, type, roomJid, roomName}
- */
- 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;
- }
- };
- /** Function: Candy.View.Observer.Message
- * Messages received get dispatched from this method.
- *
- * Parameters:
- * (jQuery.Event) event - jQuery Event object
- * (Object) args - {message, roomJid}
- */
- 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.message.xhtmlMessage, args.timestamp);
- }
- };
- /** Function: Candy.View.Observer.Login
- * The login event gets dispatched to this method
- *
- * Parameters:
- * (jQuery.Event) event - jQuery Event object
- * (Object) args - {presetJid}
- */
- self.Login = function(event, args) {
- Candy.View.Pane.Chat.Modal.showLoginForm(null, args.presetJid);
- };
- /** Class: Candy.View.Observer.AutojoinMissing
- * Displays an error about missing autojoin information
- */
- self.AutojoinMissing = function() {
- _showConnectedMessageModal = false;
- Candy.View.Pane.Chat.Modal.showError("errorAutojoinMissing");
- };
- return self;
-}(Candy.View.Observer || {}, jQuery);
-
-/** File: pane.js
- * Candy - Chats are not dead yet.
- *
- * Authors:
- * - Patrick Stadler <patrick.stadler@gmail.com>
- * - Michael Weibel <michael.weibel@gmail.com>
- *
- * Copyright:
- * (c) 2011 Amiado Group AG. All rights reserved.
- * (c) 2012-2014 Patrick Stadler & Michael Weibel. All rights reserved.
- */
-"use strict";
-
-/* global Candy, document, Mustache, Strophe, Audio, jQuery */
-/** Class: Candy.View.Pane
- * Candy view pane handles everything regarding DOM updates etc.
- *
- * Parameters:
- * (Candy.View.Pane) self - itself
- * (jQuery) $ - jQuery
- */
-Candy.View.Pane = function(self, $) {
- /** Class: Candy.View.Pane.Window
- * Window related view updates
- */
- self.Window = {
- /** PrivateVariable: _hasFocus
- * Window has focus
- */
- _hasFocus: true,
- /** PrivateVariable: _plainTitle
- * Document title
- */
- _plainTitle: document.title,
- /** PrivateVariable: _unreadMessagesCount
- * Unread messages count
- */
- _unreadMessagesCount: 0,
- /** Variable: autoscroll
- * Boolean whether autoscroll is enabled
- */
- autoscroll: true,
- /** Function: hasFocus
- * Checks if window has focus
- *
- * Returns:
- * (Boolean)
- */
- hasFocus: function() {
- return self.Window._hasFocus;
- },
- /** Function: increaseUnreadMessages
- * Increases unread message count in window title by one.
- */
- increaseUnreadMessages: function() {
- self.Window.renderUnreadMessages(++self.Window._unreadMessagesCount);
- },
- /** Function: reduceUnreadMessages
- * Reduce unread message count in window title by `num`.
- *
- * Parameters:
- * (Integer) num - Unread message count will be reduced by this value
- */
- reduceUnreadMessages: function(num) {
- self.Window._unreadMessagesCount -= num;
- if (self.Window._unreadMessagesCount <= 0) {
- self.Window.clearUnreadMessages();
- } else {
- self.Window.renderUnreadMessages(self.Window._unreadMessagesCount);
- }
- },
- /** Function: clearUnreadMessages
- * Clear unread message count in window title.
- */
- clearUnreadMessages: function() {
- self.Window._unreadMessagesCount = 0;
- document.title = self.Window._plainTitle;
- },
- /** Function: renderUnreadMessages
- * Update window title to show message count.
- *
- * Parameters:
- * (Integer) count - Number of unread messages to show in window title
- */
- renderUnreadMessages: function(count) {
- document.title = Candy.View.Template.Window.unreadmessages.replace("{{count}}", count).replace("{{title}}", self.Window._plainTitle);
- },
- /** Function: onFocus
- * Window focus event handler.
- */
- onFocus: function() {
- self.Window._hasFocus = true;
- if (Candy.View.getCurrent().roomJid) {
- self.Room.setFocusToForm(Candy.View.getCurrent().roomJid);
- self.Chat.clearUnreadMessages(Candy.View.getCurrent().roomJid);
- }
- },
- /** Function: onBlur
- * Window blur event handler.
- */
- onBlur: function() {
- self.Window._hasFocus = false;
- }
- };
- /** Class: Candy.View.Pane.Chat
- * Chat-View related view updates
- */
- self.Chat = {
- /** Variable: rooms
- * Contains opened room elements
- */
- rooms: [],
- /** Function: addTab
- * Add a tab to the chat pane.
- *
- * Parameters:
- * (String) roomJid - JID of room
- * (String) roomName - Tab label
- * (String) roomType - Type of room: `groupchat` or `chat`
- */
- addTab: function(roomJid, roomName, roomType) {
- var roomId = Candy.Util.jidToId(roomJid), html = Mustache.to_html(Candy.View.Template.Chat.tab, {
- roomJid: roomJid,
- roomId: roomId,
- name: roomName || Strophe.getNodeFromJid(roomJid),
- privateUserChat: function() {
- return roomType === "chat";
- },
- roomType: roomType
- }), tab = $(html).appendTo("#chat-tabs");
- tab.click(self.Chat.tabClick);
- // TODO: maybe we find a better way to get the close element.
- $("a.close", tab).click(self.Chat.tabClose);
- self.Chat.fitTabs();
- },
- /** Function: getTab
- * Get tab by JID.
- *
- * Parameters:
- * (String) roomJid - JID of room
- *
- * Returns:
- * (jQuery object) - Tab element
- */
- getTab: function(roomJid) {
- return $("#chat-tabs").children('li[data-roomjid="' + roomJid + '"]');
- },
- /** Function: removeTab
- * Remove tab element.
- *
- * Parameters:
- * (String) roomJid - JID of room
- */
- removeTab: function(roomJid) {
- self.Chat.getTab(roomJid).remove();
- self.Chat.fitTabs();
- },
- /** Function: setActiveTab
- * Set the active tab.
- *
- * Add CSS classname `active` to the choosen tab and remove `active` from all other.
- *
- * Parameters:
- * (String) roomJid - JID of room
- */
- setActiveTab: function(roomJid) {
- $("#chat-tabs").children().each(function() {
- var tab = $(this);
- if (tab.attr("data-roomjid") === roomJid) {
- tab.addClass("active");
- } else {
- tab.removeClass("active");
- }
- });
- },
- /** Function: increaseUnreadMessages
- * Increase unread message count in a tab by one.
- *
- * Parameters:
- * (String) roomJid - JID of room
- *
- * Uses:
- * - <Window.increaseUnreadMessages>
- */
- increaseUnreadMessages: function(roomJid) {
- var unreadElem = this.getTab(roomJid).find(".unread");
- unreadElem.show().text(unreadElem.text() !== "" ? parseInt(unreadElem.text(), 10) + 1 : 1);
- // only increase window unread messages in private chats
- if (self.Chat.rooms[roomJid].type === "chat") {
- self.Window.increaseUnreadMessages();
- }
- },
- /** Function: clearUnreadMessages
- * Clear unread message count in a tab.
- *
- * Parameters:
- * (String) roomJid - JID of room
- *
- * Uses:
- * - <Window.reduceUnreadMessages>
- */
- clearUnreadMessages: function(roomJid) {
- var unreadElem = self.Chat.getTab(roomJid).find(".unread");
- self.Window.reduceUnreadMessages(unreadElem.text());
- unreadElem.hide().text("");
- },
- /** Function: tabClick
- * Tab click event: show the room associated with the tab and stops the event from doing the default.
- */
- tabClick: function(e) {
- // remember scroll position of current room
- var currentRoomJid = Candy.View.getCurrent().roomJid;
- self.Chat.rooms[currentRoomJid].scrollPosition = self.Room.getPane(currentRoomJid, ".message-pane-wrapper").scrollTop();
- self.Room.show($(this).attr("data-roomjid"));
- e.preventDefault();
- },
- /** Function: tabClose
- * Tab close (click) event: Leave the room (groupchat) or simply close the tab (chat).
- *
- * Parameters:
- * (DOMEvent) e - Event triggered
- *
- * Returns:
- * (Boolean) - false, this will stop the event from bubbling
- */
- tabClose: function() {
- var roomJid = $(this).parent().attr("data-roomjid");
- // close private user tab
- if (self.Chat.rooms[roomJid].type === "chat") {
- self.Room.close(roomJid);
- } else {
- Candy.Core.Action.Jabber.Room.Leave(roomJid);
- }
- return false;
- },
- /** Function: allTabsClosed
- * All tabs closed event: Disconnect from service. Hide sound control.
- *
- * TODO: Handle window close
- *
- * Returns:
- * (Boolean) - false, this will stop the event from bubbling
- */
- allTabsClosed: function() {
- Candy.Core.disconnect();
- self.Chat.Toolbar.hide();
- return;
- },
- /** Function: fitTabs
- * Fit tab size according to window size
- */
- fitTabs: function() {
- var availableWidth = $("#chat-tabs").innerWidth(), tabsWidth = 0, tabs = $("#chat-tabs").children();
- tabs.each(function() {
- tabsWidth += $(this).css({
- width: "auto",
- overflow: "visible"
- }).outerWidth(true);
- });
- if (tabsWidth > availableWidth) {
- // tabs.[outer]Width() measures the first element in `tabs`. It's no very readable but nearly two times faster than using :first
- var tabDiffToRealWidth = tabs.outerWidth(true) - tabs.width(), tabWidth = Math.floor(availableWidth / tabs.length) - tabDiffToRealWidth;
- tabs.css({
- width: tabWidth,
- overflow: "hidden"
- });
- }
- },
- /** Function: 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?
- var html = Mustache.to_html(Candy.View.Template.Chat.adminMessage, {
- subject: subject,
- message: message,
- sender: $.i18n._("administratorMessageSubject"),
- time: Candy.Util.localizedTime(new Date().toGMTString())
- });
- $("#chat-rooms").children().each(function() {
- self.Room.appendToMessagePane($(this).attr("data-roomjid"), html);
- });
- self.Room.scrollToBottom(Candy.View.getCurrent().roomJid);
- /** Event: candy:view.chat.admin-message
- * After admin message display
- *
- * Parameters:
- * (String) presetJid - Preset user JID
- */
- $(Candy).triggerHandler("candy:view.chat.admin-message", {
- subject: subject,
- message: message
- });
- }
- },
- /** Function: infoMessage
- * Display info message. This is a wrapper for <onInfoMessage> to be able to disable certain info messages.
- *
- * Parameters:
- * (String) roomJid - Room JID
- * (String) subject - Subject
- * (String) message - Message
- */
- infoMessage: function(roomJid, subject, message) {
- self.Chat.onInfoMessage(roomJid, subject, message);
- },
- /** Function: onInfoMessage
- * Display info message. Used by <infoMessage> and several other functions which do not wish that their info message
- * can be disabled (such as kick/ban message or leave/join message in private chats).
- *
- * Parameters:
- * (String) roomJid - Room JID
- * (String) subject - Subject
- * (String) message - Message
- */
- onInfoMessage: function(roomJid, subject, message) {
- if (Candy.View.getCurrent().roomJid) {
- // Simply dismiss info message if no room joined so far. TODO: maybe we should show those messages on a dedicated pane?
- var html = Mustache.to_html(Candy.View.Template.Chat.infoMessage, {
- subject: subject,
- message: $.i18n._(message),
- time: Candy.Util.localizedTime(new Date().toGMTString())
- });
- self.Room.appendToMessagePane(roomJid, html);
- if (Candy.View.getCurrent().roomJid === roomJid) {
- self.Room.scrollToBottom(Candy.View.getCurrent().roomJid);
- }
- }
- },
- /** Class: Candy.View.Pane.Toolbar
- * Chat toolbar for things like emoticons toolbar, room management etc.
- */
- Toolbar: {
- _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.
- */
- show: function() {
- $("#chat-toolbar").show();
- },
- /** Function: hide
- * Hide toolbar.
- */
- hide: function() {
- $("#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).
- */
- playSound: function() {
- self.Chat.Toolbar.onPlaySound();
- },
- /** Function: onPlaySound
- * 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() {
- try {
- if (self.Chat.Toolbar._supportsNativeAudio) {
- new Audio(Candy.View.getOptions().assets + "notify.mp3").play();
- } else {
- var chatSoundPlayer = document.getElementById("chat-sound-player");
- chatSoundPlayer.SetVariable("method:stop", "");
- chatSoundPlayer.SetVariable("method:play", "");
- }
- } catch (e) {}
- },
- /** Function: onSoundControlClick
- * Sound control click event handler.
- *
- * Toggle sound (overwrite `playSound()`) and handle cookies.
- */
- onSoundControlClick: function() {
- var control = $("#chat-sound-control");
- if (control.hasClass("checked")) {
- self.Chat.Toolbar.playSound = function() {};
- Candy.Util.setCookie("candy-nosound", "1", 365);
- } else {
- self.Chat.Toolbar.playSound = function() {
- self.Chat.Toolbar.onPlaySound();
- };
- Candy.Util.deleteCookie("candy-nosound");
- }
- control.toggleClass("checked");
- },
- /** Function: onAutoscrollControlClick
- * Autoscroll control event handler.
- *
- * Toggle autoscroll
- */
- onAutoscrollControlClick: function() {
- var control = $("#chat-autoscroll-control");
- if (control.hasClass("checked")) {
- self.Room.scrollToBottom = function(roomJid) {
- self.Room.onScrollToStoredPosition(roomJid);
- };
- self.Window.autoscroll = false;
- } else {
- self.Room.scrollToBottom = function(roomJid) {
- self.Room.onScrollToBottom(roomJid);
- };
- self.Room.scrollToBottom(Candy.View.getCurrent().roomJid);
- self.Window.autoscroll = true;
- }
- control.toggleClass("checked");
- },
- /** Function: onStatusMessageControlClick
- * Status message control event handler.
- *
- * Toggle status message
- */
- onStatusMessageControlClick: function() {
- var control = $("#chat-statusmessage-control");
- if (control.hasClass("checked")) {
- self.Chat.infoMessage = function() {};
- Candy.Util.setCookie("candy-nostatusmessages", "1", 365);
- } else {
- self.Chat.infoMessage = function(roomJid, subject, message) {
- self.Chat.onInfoMessage(roomJid, subject, message);
- };
- Candy.Util.deleteCookie("candy-nostatusmessages");
- }
- control.toggleClass("checked");
- },
- /** Function: updateUserCount
- * Update usercount element with count.
- *
- * Parameters:
- * (Integer) count - Current usercount
- */
- updateUsercount: function(count) {
- $("#chat-usercount").text(count);
- }
- },
- /** Class: Candy.View.Pane.Modal
- * Modal window
- */
- Modal: {
- /** Function: show
- * Display modal window
- *
- * Parameters:
- * (String) html - HTML code to put into the modal window
- * (Boolean) showCloseControl - set to true if a close button should be displayed [default false]
- * (Boolean) showSpinner - set to true if a loading spinner should be shown [default false]
- */
- show: function(html, showCloseControl, showSpinner) {
- if (showCloseControl) {
- self.Chat.Modal.showCloseControl();
- } else {
- self.Chat.Modal.hideCloseControl();
- }
- if (showSpinner) {
- self.Chat.Modal.showSpinner();
- } else {
- self.Chat.Modal.hideSpinner();
- }
- $("#chat-modal").stop(false, true);
- $("#chat-modal-body").html(html);
- $("#chat-modal").fadeIn("fast");
- $("#chat-modal-overlay").show();
- },
- /** Function: hide
- * Hide modal window
- *
- * Parameters:
- * (Function) callback - Calls the specified function after modal window has been hidden.
- */
- hide: function(callback) {
- $("#chat-modal").fadeOut("fast", function() {
- $("#chat-modal-body").text("");
- $("#chat-modal-overlay").hide();
- });
- // restore initial esc handling
- $(document).keydown(function(e) {
- if (e.which === 27) {
- e.preventDefault();
- }
- });
- if (callback) {
- callback();
- }
- },
- /** Function: showSpinner
- * Show loading spinner
- */
- showSpinner: function() {
- $("#chat-modal-spinner").show();
- },
- /** Function: hideSpinner
- * Hide loading spinner
- */
- hideSpinner: function() {
- $("#chat-modal-spinner").hide();
- },
- /** Function: showCloseControl
- * Show a close button
- */
- showCloseControl: function() {
- $("#admin-message-cancel").show().click(function(e) {
- self.Chat.Modal.hide();
- // some strange behaviour on IE7 (and maybe other browsers) triggers onWindowUnload when clicking on the close button.
- // prevent this.
- e.preventDefault();
- });
- // enable esc to close modal
- $(document).keydown(function(e) {
- if (e.which === 27) {
- self.Chat.Modal.hide();
- e.preventDefault();
- }
- });
- },
- /** Function: hideCloseControl
- * Hide the close button
- */
- hideCloseControl: function() {
- $("#admin-message-cancel").hide().click(function() {});
- },
- /** Function: showLoginForm
- * Show the login form modal
- *
- * Parameters:
- * (String) message - optional message to display above the form
- * (String) presetJid - optional user jid. if set, the user will only be prompted for password.
- */
- showLoginForm: function(message, presetJid) {
- self.Chat.Modal.show((message ? message : "") + Mustache.to_html(Candy.View.Template.Login.form, {
- _labelNickname: $.i18n._("labelNickname"),
- _labelUsername: $.i18n._("labelUsername"),
- _labelPassword: $.i18n._("labelPassword"),
- _loginSubmit: $.i18n._("loginSubmit"),
- displayPassword: !Candy.Core.isAnonymousConnection(),
- displayUsername: !presetJid,
- displayNickname: Candy.Core.isAnonymousConnection(),
- presetJid: presetJid ? presetJid : false
- }));
- $("#login-form").children(":input:first").focus();
- // register submit handler
- $("#login-form").submit(function() {
- var username = $("#username").val(), password = $("#password").val();
- if (!Candy.Core.isAnonymousConnection()) {
- // guess the input and create a jid out of it
- var jid = Candy.Core.getUser() && username.indexOf("@") < 0 ? username + "@" + Strophe.getDomainFromJid(Candy.Core.getUser().getJid()) : username;
- if (jid.indexOf("@") < 0 && !Candy.Core.getUser()) {
- Candy.View.Pane.Chat.Modal.showLoginForm($.i18n._("loginInvalid"));
- } else {
- //Candy.View.Pane.Chat.Modal.hide();
- Candy.Core.connect(jid, password);
- }
- } else {
- // anonymous login
- Candy.Core.connect(presetJid, null, username);
- }
- return false;
- });
- },
- /** Function: showEnterPasswordForm
- * Shows a form for entering room password
- *
- * Parameters:
- * (String) roomJid - Room jid to join
- * (String) roomName - Room name
- * (String) message - [optional] Message to show as the label
- */
- showEnterPasswordForm: function(roomJid, roomName, message) {
- self.Chat.Modal.show(Mustache.to_html(Candy.View.Template.PresenceError.enterPasswordForm, {
- roomName: roomName,
- _labelPassword: $.i18n._("labelPassword"),
- _label: message ? message : $.i18n._("enterRoomPassword", [ roomName ]),
- _joinSubmit: $.i18n._("enterRoomPasswordSubmit")
- }), true);
- $("#password").focus();
- // register submit handler
- $("#enter-password-form").submit(function() {
- var password = $("#password").val();
- self.Chat.Modal.hide(function() {
- Candy.Core.Action.Jabber.Room.Join(roomJid, password);
- });
- return false;
- });
- },
- /** Function: showNicknameConflictForm
- * Shows a form indicating that the nickname is already taken and
- * for chosing a new nickname
- *
- * Parameters:
- * (String) roomJid - Room jid to join
- */
- showNicknameConflictForm: function(roomJid) {
- self.Chat.Modal.show(Mustache.to_html(Candy.View.Template.PresenceError.nicknameConflictForm, {
- _labelNickname: $.i18n._("labelNickname"),
- _label: $.i18n._("nicknameConflict"),
- _loginSubmit: $.i18n._("loginSubmit")
- }));
- $("#nickname").focus();
- // register submit handler
- $("#nickname-conflict-form").submit(function() {
- var nickname = $("#nickname").val();
- self.Chat.Modal.hide(function() {
- Candy.Core.getUser().data.nick = nickname;
- Candy.Core.Action.Jabber.Room.Join(roomJid);
- });
- return false;
- });
- },
- /** Function: showError
- * Show modal containing error message
- *
- * Parameters:
- * (String) message - key of translation to display
- * (Array) replacements - array containing replacements for translation (%s)
- */
- showError: function(message, replacements) {
- self.Chat.Modal.show(Mustache.to_html(Candy.View.Template.PresenceError.displayError, {
- _error: $.i18n._(message, replacements)
- }), true);
- }
- },
- /** Class: Candy.View.Pane.Tooltip
- * Class to display tooltips over specific elements
- */
- Tooltip: {
- /** Function: show
- * Show a tooltip on event.currentTarget with content specified or content within the target's attribute data-tooltip.
- *
- * On mouseleave on the target, hide the tooltip.
- *
- * Parameters:
- * (Event) event - Triggered event
- * (String) content - Content to display [optional]
- */
- show: function(event, content) {
- var tooltip = $("#tooltip"), target = $(event.currentTarget);
- if (!content) {
- content = target.attr("data-tooltip");
- }
- if (tooltip.length === 0) {
- var html = Mustache.to_html(Candy.View.Template.Chat.tooltip);
- $("#chat-pane").append(html);
- tooltip = $("#tooltip");
- }
- $("#context-menu").hide();
- tooltip.stop(false, true);
- tooltip.children("div").html(content);
- var pos = target.offset(), posLeft = Candy.Util.getPosLeftAccordingToWindowBounds(tooltip, pos.left), posTop = Candy.Util.getPosTopAccordingToWindowBounds(tooltip, pos.top);
- tooltip.css({
- left: posLeft.px,
- top: posTop.px
- }).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
- });
- });
- });
- }
- },
- /** Class: Candy.View.Pane.Context
- * Context menu for actions and settings
- */
- Context: {
- /** Function: init
- * Initialize context menu and setup mouseleave handler.
- */
- init: function() {
- if ($("#context-menu").length === 0) {
- var html = Mustache.to_html(Candy.View.Template.Chat.Context.menu);
- $("#chat-pane").append(html);
- $("#context-menu").mouseleave(function() {
- $(this).fadeOut("fast");
- });
- }
- },
- /** Function: show
- * Show context menu (positions it according to the window height/width)
- *
- * 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
- *
- * Triggers:
- * candy:view.roster.after-context-menu using {roomJid, user, elements}
- */
- show: function(elem, roomJid, user) {
- elem = $(elem);
- var roomId = self.Chat.rooms[roomJid].id, menu = $("#context-menu"), links = $("ul li", menu);
- $("#tooltip").hide();
- // add specific context-user class if a user is available (when context menu should be opened next to a user)
- if (!user) {
- user = Candy.Core.getUser();
- }
- links.remove();
- var menulinks = this.getMenuLinks(roomJid, user, elem), id, clickHandler = function(roomJid, user) {
- return function(event) {
- event.data.callback(event, roomJid, user);
- $("#context-menu").hide();
- };
- };
- for (id in menulinks) {
- if (menulinks.hasOwnProperty(id)) {
- var link = menulinks[id], html = Mustache.to_html(Candy.View.Template.Chat.Context.menulinks, {
- roomId: roomId,
- "class": link["class"],
- id: id,
- label: link.label
- });
- $("ul", menu).append(html);
- $("#context-menu-" + id).bind("click", link, clickHandler(roomJid, user));
- }
- }
- // if `id` is set the menu is not empty
- if (id) {
- var pos = elem.offset(), posLeft = Candy.Util.getPosLeftAccordingToWindowBounds(menu, pos.left), posTop = Candy.Util.getPosTopAccordingToWindowBounds(menu, pos.top);
- menu.css({
- left: posLeft.px,
- top: posTop.px
- }).removeClass("left-top left-bottom right-top right-bottom").addClass(posLeft.backgroundPositionAlignment + "-" + posTop.backgroundPositionAlignment).fadeIn("fast");
- /** 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", {
- roomJid: roomJid,
- user: user,
- element: menu
- });
- return true;
- }
- },
- /** Function: getMenuLinks
- * 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, id;
- var evtData = {
- roomJid: roomJid,
- user: user,
- elem: elem,
- menulinks: this.initialMenuLinks(elem)
- };
- /** 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)) {
- delete menulinks[id];
- }
- }
- return menulinks;
- },
- /** Function: initialMenuLinks
- * Returns initial menulinks. The following are initial:
- *
- * - Private Chat
- * - Ignore
- * - Unignore
- * - Kick
- * - Ban
- * - Change Subject
- *
- * Returns:
- * (Object) - object containing those menulinks
- */
- initialMenuLinks: function() {
- return {
- "private": {
- requiredPermission: function(user, me) {
- return me.getNick() !== user.getNick() && Candy.Core.getRoom(Candy.View.getCurrent().roomJid) && !Candy.Core.getUser().isInPrivacyList("ignore", user.getJid());
- },
- "class": "private",
- label: $.i18n._("privateActionLabel"),
- callback: function(e, roomJid, user) {
- $("#user-" + Candy.Util.jidToId(roomJid) + "-" + Candy.Util.jidToId(user.getJid())).click();
- }
- },
- ignore: {
- requiredPermission: function(user, me) {
- return me.getNick() !== user.getNick() && !Candy.Core.getUser().isInPrivacyList("ignore", user.getJid());
- },
- "class": "ignore",
- label: $.i18n._("ignoreActionLabel"),
- callback: function(e, roomJid, user) {
- Candy.View.Pane.Room.ignoreUser(roomJid, user.getJid());
- }
- },
- unignore: {
- requiredPermission: function(user, me) {
- return me.getNick() !== user.getNick() && Candy.Core.getUser().isInPrivacyList("ignore", user.getJid());
- },
- "class": "unignore",
- label: $.i18n._("unignoreActionLabel"),
- callback: function(e, roomJid, user) {
- Candy.View.Pane.Room.unignoreUser(roomJid, user.getJid());
- }
- },
- kick: {
- requiredPermission: function(user, me) {
- return me.getNick() !== user.getNick() && me.isModerator() && !user.isModerator();
- },
- "class": "kick",
- label: $.i18n._("kickActionLabel"),
- callback: function(e, roomJid, user) {
- self.Chat.Modal.show(Mustache.to_html(Candy.View.Template.Chat.Context.contextModalForm, {
- _label: $.i18n._("reason"),
- _submit: $.i18n._("kickActionLabel")
- }), true);
- $("#context-modal-field").focus();
- $("#context-modal-form").submit(function() {
- Candy.Core.Action.Jabber.Room.Admin.UserAction(roomJid, user.getJid(), "kick", $("#context-modal-field").val());
- self.Chat.Modal.hide();
- return false;
- });
- }
- },
- ban: {
- requiredPermission: function(user, me) {
- return me.getNick() !== user.getNick() && me.isModerator() && !user.isModerator();
- },
- "class": "ban",
- label: $.i18n._("banActionLabel"),
- callback: function(e, roomJid, user) {
- self.Chat.Modal.show(Mustache.to_html(Candy.View.Template.Chat.Context.contextModalForm, {
- _label: $.i18n._("reason"),
- _submit: $.i18n._("banActionLabel")
- }), true);
- $("#context-modal-field").focus();
- $("#context-modal-form").submit(function() {
- Candy.Core.Action.Jabber.Room.Admin.UserAction(roomJid, user.getJid(), "ban", $("#context-modal-field").val());
- self.Chat.Modal.hide();
- return false;
- });
- }
- },
- subject: {
- requiredPermission: function(user, me) {
- return me.getNick() === user.getNick() && me.isModerator();
- },
- "class": "subject",
- label: $.i18n._("setSubjectActionLabel"),
- callback: function(e, roomJid) {
- self.Chat.Modal.show(Mustache.to_html(Candy.View.Template.Chat.Context.contextModalForm, {
- _label: $.i18n._("subject"),
- _submit: $.i18n._("setSubjectActionLabel")
- }), true);
- $("#context-modal-field").focus();
- $("#context-modal-form").submit(function(e) {
- Candy.Core.Action.Jabber.Room.Admin.SetSubject(roomJid, $("#context-modal-field").val());
- self.Chat.Modal.hide();
- e.preventDefault();
- });
- }
- }
- };
- },
- /** Function: showEmoticonsMenu
- * Shows the special emoticons menu
- *
- * Parameters:
- * (Element) elem - Element on which it should be positioned to.
- *
- * Returns:
- * (Boolean) - true
- */
- showEmoticonsMenu: function(elem) {
- elem = $(elem);
- var pos = elem.offset(), menu = $("#context-menu"), content = $("ul", menu), emoticons = "", i;
- $("#tooltip").hide();
- for (i = Candy.Util.Parser.emoticons.length - 1; i >= 0; i--) {
- emoticons = '<img src="' + Candy.Util.Parser._emoticonPath + Candy.Util.Parser.emoticons[i].image + '" alt="' + Candy.Util.Parser.emoticons[i].plain + '" />' + emoticons;
- }
- content.html('<li class="emoticons">' + emoticons + "</li>");
- content.find("img").click(function() {
- var input = Candy.View.Pane.Room.getPane(Candy.View.getCurrent().roomJid, ".message-form").children(".field"), value = input.val(), emoticon = $(this).attr("alt") + " ";
- input.val(value ? value + " " + emoticon : emoticon).focus();
- });
- var posLeft = Candy.Util.getPosLeftAccordingToWindowBounds(menu, pos.left), posTop = Candy.Util.getPosTopAccordingToWindowBounds(menu, pos.top);
- menu.css({
- left: posLeft.px,
- top: posTop.px
- }).removeClass("left-top left-bottom right-top right-bottom").addClass(posLeft.backgroundPositionAlignment + "-" + posTop.backgroundPositionAlignment).fadeIn("fast");
- return true;
- }
- }
- };
- /** Class: Candy.View.Pane.Room
- * Everything which belongs to room view things belongs here.
- */
- self.Room = {
- /** Function: init
- * Initialize a new room and inserts the room html into the DOM
- *
- * Parameters:
- * (String) roomJid - Room JID
- * (String) roomName - Room name
- * (String) roomType - Type: either "groupchat" or "chat" (private chat)
- *
- * Uses:
- * - <Candy.Util.jidToId>
- * - <Candy.View.Pane.Chat.addTab>
- * - <getPane>
- *
- * Triggers:
- * candy:view.room.after-add using {roomJid, type, element}
- *
- * Returns:
- * (String) - the room id of the element created.
- */
- init: function(roomJid, roomName, roomType) {
- roomType = roomType || "groupchat";
- roomJid = Candy.Util.unescapeJid(roomJid);
- var evtData = {
- roomJid: roomJid,
- type: roomType
- };
- /** Event: candy:view.room.before-add
- * Before initialising a room
- *
- * Parameters:
- * (String) roomJid - Room JID
- * (String) type - Room Type
- *
- * Returns:
- * Boolean - if you don't want to initialise the room, return false.
- */
- if ($(Candy).triggerHandler("candy:view.room.before-add", evtData) === false) {
- return false;
- }
- // First room, show sound control
- if (Candy.Util.isEmptyObject(self.Chat.rooms)) {
- self.Chat.Toolbar.show();
- }
- var roomId = Candy.Util.jidToId(roomJid);
- self.Chat.rooms[roomJid] = {
- id: roomId,
- usercount: 0,
- name: roomName,
- type: roomType,
- messageCount: 0,
- scrollPosition: -1
- };
- $("#chat-rooms").append(Mustache.to_html(Candy.View.Template.Room.pane, {
- roomId: roomId,
- roomJid: roomJid,
- roomType: roomType,
- form: {
- _messageSubmit: $.i18n._("messageSubmit")
- },
- roster: {
- _userOnline: $.i18n._("userOnline")
- }
- }, {
- roster: Candy.View.Template.Roster.pane,
- messages: Candy.View.Template.Message.pane,
- form: Candy.View.Template.Room.form
- }));
- self.Chat.addTab(roomJid, roomName, roomType);
- self.Room.getPane(roomJid, ".message-form").submit(self.Message.submit);
- evtData.element = self.Room.getPane(roomJid);
- /** 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;
- },
- /** Function: show
- * Show a specific room and hides the other rooms (if there are any)
- *
- * 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, evtData;
- $(".room-pane").each(function() {
- var elem = $(this);
- evtData = {
- roomJid: elem.attr("data-roomjid"),
- element: elem
- };
- if (elem.attr("id") === "chat-room-" + roomId) {
- elem.show();
- Candy.View.getCurrent().roomJid = roomJid;
- self.Chat.setActiveTab(roomJid);
- self.Chat.Toolbar.update(roomJid);
- self.Chat.clearUnreadMessages(roomJid);
- self.Room.setFocusToForm(roomJid);
- self.Room.scrollToBottom(roomJid);
- /** 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();
- /** 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);
- }
- });
- },
- /** 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
- */
- setSubject: function(roomJid, subject) {
- subject = Candy.Util.Parser.linkify(Candy.Util.Parser.escape(subject));
- var html = Mustache.to_html(Candy.View.Template.Room.subject, {
- subject: subject,
- roomName: self.Chat.rooms[roomJid].name,
- _roomSubject: $.i18n._("roomSubject"),
- time: Candy.Util.localizedTime(new Date().toGMTString())
- });
- self.Room.appendToMessagePane(roomJid, html);
- self.Room.scrollToBottom(roomJid);
- /** 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", {
- roomJid: roomJid,
- element: self.Room.getPane(roomJid),
- subject: subject
- });
- },
- /** Function: close
- * Close a room and remove everything in the DOM belonging to this room.
- *
- * NOTICE: There's a rendering bug in Opera when all rooms have been closed.
- * (Take a look in the source for a more detailed description)
- *
- * Triggers:
- * candy:view.room.after-close using {roomJid}
- *
- * Parameters:
- * (String) roomJid - Room to close
- */
- close: function(roomJid) {
- self.Chat.removeTab(roomJid);
- self.Window.clearUnreadMessages();
- /* TODO:
- There's a rendering bug in Opera which doesn't redraw (remove) the message form.
- Only a cosmetical issue (when all tabs are closed) but it's annoying...
- This happens when form has no focus too. Maybe it's because of CSS positioning.
- */
- self.Room.getPane(roomJid).remove();
- var openRooms = $("#chat-rooms").children();
- if (Candy.View.getCurrent().roomJid === roomJid) {
- Candy.View.getCurrent().roomJid = null;
- if (openRooms.length === 0) {
- self.Chat.allTabsClosed();
- } else {
- self.Room.show(openRooms.last().attr("data-roomjid"));
- }
- }
- delete self.Chat.rooms[roomJid];
- /** Event: candy:view.room.after-close
- * After closing a room
- *
- * Parameters:
- * (String) roomJid - Room JID
- */
- $(Candy).triggerHandler("candy:view.room.after-close", {
- roomJid: roomJid
- });
- },
- /** Function: appendToMessagePane
- * Append a new message to the message pane.
- *
- * Parameters:
- * (String) roomJid - Room JID
- * (String) html - rendered message html
- */
- appendToMessagePane: function(roomJid, html) {
- self.Room.getPane(roomJid, ".message-pane").append(html);
- self.Chat.rooms[roomJid].messageCount++;
- self.Room.sliceMessagePane(roomJid);
- },
- /** Function: sliceMessagePane
- * Slices the message pane after the max amount of messages specified in the Candy View options (limit setting).
- *
- * This is done to hopefully prevent browsers from getting slow after a certain amount of messages in the DOM.
- *
- * The slice is only done when autoscroll is on, because otherwise someone might lose exactly the message he want to look for.
- *
- * Parameters:
- * (String) roomJid - Room JID
- */
- sliceMessagePane: function(roomJid) {
- // Only clean if autoscroll is enabled
- if (self.Window.autoscroll) {
- var options = Candy.View.getOptions().messages;
- if (self.Chat.rooms[roomJid].messageCount > options.limit) {
- self.Room.getPane(roomJid, ".message-pane").children().slice(0, options.remove).remove();
- self.Chat.rooms[roomJid].messageCount -= options.remove;
- }
- }
- },
- /** Function: scrollToBottom
- * Scroll to bottom wrapper for <onScrollToBottom> to be able to disable it by overwriting the function.
- *
- * Parameters:
- * (String) roomJid - Room JID
- *
- * Uses:
- * - <onScrollToBottom>
- */
- scrollToBottom: function(roomJid) {
- self.Room.onScrollToBottom(roomJid);
- },
- /** Function: onScrollToBottom
- * Scrolls to the latest message received/sent.
- *
- * Parameters:
- * (String) roomJid - Room JID
- */
- onScrollToBottom: function(roomJid) {
- var messagePane = self.Room.getPane(roomJid, ".message-pane-wrapper");
- messagePane.scrollTop(messagePane.prop("scrollHeight"));
- },
- /** Function: onScrollToStoredPosition
- * When autoscroll is off, the position where the scrollbar is has to be stored for each room, because it otherwise
- * goes to the top in the message window.
- *
- * Parameters:
- * (String) roomJid - Room JID
- */
- onScrollToStoredPosition: function(roomJid) {
- // This should only apply when entering a room...
- // ... therefore we set scrollPosition to -1 after execution.
- if (self.Chat.rooms[roomJid].scrollPosition > -1) {
- var messagePane = self.Room.getPane(roomJid, ".message-pane-wrapper");
- messagePane.scrollTop(self.Chat.rooms[roomJid].scrollPosition);
- self.Chat.rooms[roomJid].scrollPosition = -1;
- }
- },
- /** Function: setFocusToForm
- * Set focus to the message input field within the message form.
- *
- * Parameters:
- * (String) roomJid - Room JID
- */
- setFocusToForm: function(roomJid) {
- var pane = self.Room.getPane(roomJid, ".message-form");
- if (pane) {
- // IE8 will fail maybe, because the field isn't there yet.
- try {
- pane.children(".field")[0].focus();
- } catch (e) {}
- }
- },
- /** Function: setUser
- * Sets or updates the current user in the specified room (called by <Candy.View.Pane.Roster.update>) and set specific informations
- * (roles and affiliations) on the room tab (chat-pane).
- *
- * Parameters:
- * (String) roomJid - Room in which the user is set to.
- * (Candy.Core.ChatUser) user - The user
- */
- setUser: function(roomJid, user) {
- self.Chat.rooms[roomJid].user = user;
- var roomPane = self.Room.getPane(roomJid), chatPane = $("#chat-pane");
- roomPane.attr("data-userjid", user.getJid());
- // Set classes based on user role / affiliation
- if (user.isModerator()) {
- if (user.getRole() === user.ROLE_MODERATOR) {
- chatPane.addClass("role-moderator");
- }
- if (user.getAffiliation() === user.AFFILIATION_OWNER) {
- chatPane.addClass("affiliation-owner");
- }
- } else {
- chatPane.removeClass("role-moderator affiliation-owner");
- }
- self.Chat.Context.init();
- },
- /** Function: getUser
- * Get the current user in the room specified with the jid
- *
- * Parameters:
- * (String) roomJid - Room of which the user should be returned from
- *
- * Returns:
- * (Candy.Core.ChatUser) - user
- */
- getUser: function(roomJid) {
- return self.Chat.rooms[roomJid].user;
- },
- /** Function: ignoreUser
- * Ignore specified user and add the ignore icon to the roster item of the user
- *
- * Parameters:
- * (String) roomJid - Room in which the user should be ignored
- * (String) userJid - User which should be ignored
- */
- ignoreUser: function(roomJid, userJid) {
- Candy.Core.Action.Jabber.Room.IgnoreUnignore(userJid);
- Candy.View.Pane.Room.addIgnoreIcon(roomJid, userJid);
- },
- /** Function: unignoreUser
- * Unignore an ignored user and remove the ignore icon of the roster item.
- *
- * Parameters:
- * (String) roomJid - Room in which the user should be unignored
- * (String) userJid - User which should be unignored
- */
- unignoreUser: function(roomJid, userJid) {
- Candy.Core.Action.Jabber.Room.IgnoreUnignore(userJid);
- Candy.View.Pane.Room.removeIgnoreIcon(roomJid, userJid);
- },
- /** Function: addIgnoreIcon
- * Add the ignore icon to the roster item of the specified user
- *
- * Parameters:
- * (String) roomJid - Room in which the roster item should be updated
- * (String) userJid - User of which the roster item should be updated
- */
- addIgnoreIcon: function(roomJid, userJid) {
- if (Candy.View.Pane.Chat.rooms[userJid]) {
- $("#user-" + Candy.View.Pane.Chat.rooms[userJid].id + "-" + Candy.Util.jidToId(userJid)).addClass("status-ignored");
- }
- if (Candy.View.Pane.Chat.rooms[Strophe.getBareJidFromJid(roomJid)]) {
- $("#user-" + Candy.View.Pane.Chat.rooms[Strophe.getBareJidFromJid(roomJid)].id + "-" + Candy.Util.jidToId(userJid)).addClass("status-ignored");
- }
- },
- /** Function: removeIgnoreIcon
- * Remove the ignore icon to the roster item of the specified user
- *
- * Parameters:
- * (String) roomJid - Room in which the roster item should be updated
- * (String) userJid - User of which the roster item should be updated
- */
- removeIgnoreIcon: function(roomJid, userJid) {
- if (Candy.View.Pane.Chat.rooms[userJid]) {
- $("#user-" + Candy.View.Pane.Chat.rooms[userJid].id + "-" + Candy.Util.jidToId(userJid)).removeClass("status-ignored");
- }
- if (Candy.View.Pane.Chat.rooms[Strophe.getBareJidFromJid(roomJid)]) {
- $("#user-" + Candy.View.Pane.Chat.rooms[Strophe.getBareJidFromJid(roomJid)].id + "-" + Candy.Util.jidToId(userJid)).removeClass("status-ignored");
- }
- },
- /** Function: getPane
- * Get the chat room pane or a subPane of it (if subPane is specified)
- *
- * Parameters:
- * (String) roomJid - Room in which the pane lies
- * (String) subPane - Sub pane of the chat room pane if needed [optional]
- */
- getPane: function(roomJid, subPane) {
- if (self.Chat.rooms[roomJid]) {
- if (subPane) {
- if (self.Chat.rooms[roomJid]["pane-" + subPane]) {
- return self.Chat.rooms[roomJid]["pane-" + subPane];
- } else {
- self.Chat.rooms[roomJid]["pane-" + subPane] = $("#chat-room-" + self.Chat.rooms[roomJid].id).find(subPane);
- return self.Chat.rooms[roomJid]["pane-" + subPane];
- }
- } else {
- return $("#chat-room-" + self.Chat.rooms[roomJid].id);
- }
- }
- },
- /** Function: changeDataUserJidIfUserIsMe
- * Changes the room's data-userjid attribute if the specified user is the current user.
- *
- * Parameters:
- * (String) roomId - Id of the room
- * (Candy.Core.ChatUser) user - User
- */
- changeDataUserJidIfUserIsMe: function(roomId, user) {
- if (user.getNick() === Candy.Core.getUser().getNick()) {
- var roomElement = $("#chat-room-" + roomId);
- roomElement.attr("data-userjid", Strophe.getBareJidFromJid(roomElement.attr("data-userjid")) + "/" + user.getNick());
- }
- }
- };
- /** Class: Candy.View.Pane.PrivateRoom
- * Private room handling
- */
- self.PrivateRoom = {
- /** Function: open
- * Opens a new private room
- *
- * Parameters:
- * (String) roomJid - Room jid to open
- * (String) roomName - Room name
- * (Boolean) switchToRoom - If true, displayed room switches automatically to this room
- * (e.g. when user clicks itself on another user to open a private chat)
- * (Boolean) isNoConferenceRoomJid - true if a 3rd-party client sends a direct message to this user (not via the room)
- * then the username is the node and not the resource. This param addresses this case.
- *
- * 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)), evtData = {
- roomJid: roomJid,
- roomName: roomName,
- type: "chat"
- };
- /** Event: candy:view.private-room.before-open
- * Before opening a new private room
- *
- * Parameters:
- * (String) roomJid - Room JID
- * (String) roomName - Room name
- * (String) type - 'chat'
- *
- * Returns:
- * Boolean - if you don't want to open the private room, return false
- */
- if ($(Candy).triggerHandler("candy:view.private-room.before-open", evtData) === false) {
- return false;
- }
- // if target user is in privacy list, don't open the private chat.
- if (Candy.Core.getUser().isInPrivacyList("ignore", roomJid)) {
- return false;
- }
- if (!self.Chat.rooms[roomJid]) {
- if (self.Room.init(roomJid, roomName, "chat") === false) {
- return false;
- }
- }
- 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"));
- }
- evtData.element = self.Room.getPane(roomJid);
- /** 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
- * Set offline or online status for private rooms (when one of the participants leaves the room)
- *
- * Parameters:
- * (String) roomJid - Private room jid
- * (String) status - "leave"/"join"
- */
- setStatus: function(roomJid, status) {
- var messageForm = self.Room.getPane(roomJid, ".message-form");
- if (status === "join") {
- self.Chat.getTab(roomJid).addClass("online").removeClass("offline");
- messageForm.children(".field").removeAttr("disabled");
- messageForm.children(".submit").removeAttr("disabled");
- self.Chat.getTab(roomJid);
- } else if (status === "leave") {
- self.Chat.getTab(roomJid).addClass("offline").removeClass("online");
- messageForm.children(".field").attr("disabled", true);
- messageForm.children(".submit").attr("disabled", true);
- }
- },
- /** Function: changeNick
- * Changes the nick for every private room opened with this roomJid.
- *
- * Parameters:
- * (String) roomJid - Public room jid
- * (Candy.Core.ChatUser) user - User which changes his nick
- */
- changeNick: function changeNick(roomJid, user) {
- Candy.Core.log("[View:Pane:PrivateRoom] changeNick");
- var previousPrivateRoomJid = roomJid + "/" + user.getPreviousNick(), newPrivateRoomJid = roomJid + "/" + user.getNick(), previousPrivateRoomId = Candy.Util.jidToId(previousPrivateRoomJid), newPrivateRoomId = Candy.Util.jidToId(newPrivateRoomJid), room = self.Chat.rooms[previousPrivateRoomJid], roomElement, roomTabElement;
- // it could happen that the new private room is already existing -> close it first.
- // if this is not done, errors appear as two rooms would have the same id
- if (self.Chat.rooms[newPrivateRoomJid]) {
- self.Room.close(newPrivateRoomJid);
- }
- if (room) {
- /* someone I talk with, changed nick */
- room.name = user.getNick();
- room.id = newPrivateRoomId;
- self.Chat.rooms[newPrivateRoomJid] = room;
- delete self.Chat.rooms[previousPrivateRoomJid];
- roomElement = $("#chat-room-" + previousPrivateRoomId);
- if (roomElement) {
- roomElement.attr("data-roomjid", newPrivateRoomJid);
- roomElement.attr("id", "chat-room-" + newPrivateRoomId);
- roomTabElement = $('#chat-tabs li[data-roomjid="' + previousPrivateRoomJid + '"]');
- roomTabElement.attr("data-roomjid", newPrivateRoomJid);
- /* TODO: The '@' is defined in the template. Somehow we should
- * extract both things into our CSS or do something else to prevent that.
- */
- roomTabElement.children("a.label").text("@" + user.getNick());
- if (Candy.View.getCurrent().roomJid === previousPrivateRoomJid) {
- Candy.View.getCurrent().roomJid = newPrivateRoomJid;
- }
- }
- } else {
- /* I changed the nick */
- roomElement = $('.room-pane.roomtype-chat[data-userjid="' + previousPrivateRoomJid + '"]');
- if (roomElement.length) {
- previousPrivateRoomId = Candy.Util.jidToId(roomElement.attr("data-roomjid"));
- roomElement.attr("data-userjid", newPrivateRoomJid);
- }
- }
- if (roomElement && roomElement.length) {
- self.Roster.changeNick(previousPrivateRoomId, user);
- }
- }
- };
- /** Class Candy.View.Pane.Roster
- * Handles everyhing regarding roster updates.
- */
- self.Roster = {
- /** Function: update
- * Called by <Candy.View.Observer.Presence.update> to update the roster if needed.
- * Adds/removes users from the roster list or updates informations on their items (roles, affiliations etc.)
- *
- * TODO: Refactoring, this method has too much LOC.
- *
- * Parameters:
- * (String) roomJid - Room JID in which the update happens
- * (Candy.Core.ChatUser) user - User on which the update happens
- * (String) action - one of "join", "leave", "kick" and "ban"
- * (Candy.Core.ChatUser) currentUser - Current user
- *
- * 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) {
- Candy.Core.log("[View:Pane:Roster] " + action);
- var roomId = self.Chat.rooms[roomJid].id, userId = Candy.Util.jidToId(user.getJid()), usercountDiff = -1, userElem = $("#user-" + roomId + "-" + userId), evtData = {
- roomJid: roomJid,
- user: user,
- action: action,
- element: userElem
- };
- /** 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", evtData);
- // a user joined the room
- if (action === "join") {
- usercountDiff = 1;
- var html = Mustache.to_html(Candy.View.Template.Roster.user, {
- roomId: roomId,
- userId: userId,
- userJid: user.getJid(),
- nick: user.getNick(),
- displayNick: Candy.Util.crop(user.getNick(), Candy.View.getOptions().crop.roster.nickname),
- role: user.getRole(),
- affiliation: user.getAffiliation(),
- me: currentUser !== undefined && user.getNick() === currentUser.getNick(),
- tooltipRole: $.i18n._("tooltipRole"),
- tooltipIgnored: $.i18n._("tooltipIgnored")
- });
- if (userElem.length < 1) {
- var userInserted = false, rosterPane = self.Room.getPane(roomJid, ".roster-pane");
- // there are already users in the roster
- if (rosterPane.children().length > 0) {
- // insert alphabetically
- var userSortCompare = user.getNick().toUpperCase();
- rosterPane.children().each(function() {
- var elem = $(this);
- if (elem.attr("data-nick").toUpperCase() > userSortCompare) {
- elem.before(html);
- userInserted = true;
- return false;
- }
- return true;
- });
- }
- // first user in roster
- if (!userInserted) {
- rosterPane.append(html);
- }
- self.Roster.showJoinAnimation(user, userId, roomId, roomJid, currentUser);
- } else {
- 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
- if (currentUser !== undefined && currentUser.getNick() === user.getNick()) {
- self.Room.setUser(roomJid, user);
- } else {
- $("#user-" + roomId + "-" + userId).click(self.Roster.userClick);
- }
- $("#user-" + roomId + "-" + userId + " .context").click(function(e) {
- self.Chat.Context.show(e.currentTarget, roomJid, user);
- e.stopPropagation();
- });
- // check if current user is ignoring the user who has joined.
- if (currentUser !== undefined && currentUser.isInPrivacyList("ignore", user.getJid())) {
- Candy.View.Pane.Room.addIgnoreIcon(roomJid, user.getJid());
- }
- } else if (action === "leave") {
- self.Roster.leaveAnimation("user-" + roomId + "-" + userId);
- // always show leave message in private room, even if status messages have been disabled
- if (self.Chat.rooms[roomJid].type === "chat") {
- self.Chat.onInfoMessage(roomJid, $.i18n._("userLeftRoom", [ user.getNick() ]));
- } else {
- self.Chat.infoMessage(roomJid, $.i18n._("userLeftRoom", [ user.getNick() ]));
- }
- } else if (action === "nickchange") {
- usercountDiff = 0;
- self.Roster.changeNick(roomId, user);
- self.Room.changeDataUserJidIfUserIsMe(roomId, user);
- self.PrivateRoom.changeNick(roomJid, user);
- var infoMessage = $.i18n._("userChangedNick", [ user.getPreviousNick(), user.getNick() ]);
- self.Chat.onInfoMessage(roomJid, infoMessage);
- } else if (action === "kick") {
- self.Roster.leaveAnimation("user-" + roomId + "-" + userId);
- self.Chat.onInfoMessage(roomJid, $.i18n._("userHasBeenKickedFromRoom", [ user.getNick() ]));
- } else if (action === "ban") {
- self.Roster.leaveAnimation("user-" + roomId + "-" + userId);
- self.Chat.onInfoMessage(roomJid, $.i18n._("userHasBeenBannedFromRoom", [ user.getNick() ]));
- }
- // Update user count
- Candy.View.Pane.Chat.rooms[roomJid].usercount += usercountDiff;
- if (roomJid === Candy.View.getCurrent().roomJid) {
- Candy.View.Pane.Chat.Toolbar.updateUsercount(Candy.View.Pane.Chat.rooms[roomJid].usercount);
- }
- // in case there's been a join, the element is now there (previously not)
- evtData.element = $("#user-" + roomId + "-" + userId);
- /** 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
- * Click handler for opening a private room
- */
- userClick: function() {
- var elem = $(this);
- self.PrivateRoom.open(elem.attr("data-jid"), elem.attr("data-nick"), true);
- },
- /** Function: showJoinAnimation
- * Shows join animation if needed
- *
- * FIXME: Refactor. Part of this will be done by the big room improvements
- */
- showJoinAnimation: function(user, userId, roomId, roomJid, currentUser) {
- // don't show if the user has recently changed the nickname.
- var rosterUserId = "user-" + roomId + "-" + userId, $rosterUserElem = $("#" + rosterUserId);
- if (!user.getPreviousNick() || !$rosterUserElem || $rosterUserElem.is(":visible") === false) {
- self.Roster.joinAnimation(rosterUserId);
- // only show other users joining & don't show if there's no message in the room.
- if (currentUser !== undefined && user.getNick() !== currentUser.getNick() && self.Room.getUser(roomJid)) {
- // always show join message in private room, even if status messages have been disabled
- if (self.Chat.rooms[roomJid].type === "chat") {
- self.Chat.onInfoMessage(roomJid, $.i18n._("userJoinedRoom", [ user.getNick() ]));
- } else {
- self.Chat.infoMessage(roomJid, $.i18n._("userJoinedRoom", [ user.getNick() ]));
- }
- }
- }
- },
- /** Function: joinAnimation
- * Animates specified elementId on join
- *
- * Parameters:
- * (String) elementId - Specific element to do the animation on
- */
- joinAnimation: function(elementId) {
- $("#" + elementId).stop(true).slideDown("normal", function() {
- $(this).animate({
- opacity: 1
- });
- });
- },
- /** Function: leaveAnimation
- * Leave animation for specified element id and removes the DOM element on completion.
- *
- * Parameters:
- * (String) elementId - Specific element to do the animation on
- */
- leaveAnimation: function(elementId) {
- $("#" + elementId).stop(true).attr("id", "#" + elementId + "-leaving").animate({
- opacity: 0
- }, {
- complete: function() {
- $(this).slideUp("normal", function() {
- $(this).remove();
- });
- }
- });
- },
- /** Function: changeNick
- * Change nick of an existing user in the roster
- *
- * UserId has to be recalculated from the user because at the time of this call,
- * the user is already set with the new jid & nick.
- *
- * Parameters:
- * (String) roomId - Id of the room
- * (Candy.Core.ChatUser) user - User object
- */
- changeNick: function(roomId, user) {
- Candy.Core.log("[View:Pane:Roster] changeNick");
- var previousUserJid = Strophe.getBareJidFromJid(user.getJid()) + "/" + user.getPreviousNick(), elementId = "user-" + roomId + "-" + Candy.Util.jidToId(previousUserJid), el = $("#" + elementId);
- el.attr("data-nick", user.getNick());
- el.attr("data-jid", user.getJid());
- el.children("div.label").text(user.getNick());
- el.attr("id", "user-" + roomId + "-" + Candy.Util.jidToId(user.getJid()));
- }
- };
- /** Class: Candy.View.Pane.Message
- * Message submit/show handling
- */
- self.Message = {
- /** Function: submit
- * on submit handler for message field sends the message to the server and if it's a private chat, shows the message
- * immediately because the server doesn't send back those message.
- *
- * Parameters:
- * (Event) event - Triggered event
- *
- * Triggers:
- * candy:view.message.before-send using {message}
- *
- * FIXME: as everywhere, `roomJid` might be slightly incorrect in this case
- * - maybe rename this as part of a refactoring.
- */
- submit: function(event) {
- var roomJid = Candy.View.getCurrent().roomJid, roomType = Candy.View.Pane.Chat.rooms[roomJid].type, message = $(this).children(".field").val().substring(0, Candy.View.getOptions().crop.message.body), xhtmlMessage, evtData = {
- roomJid: roomJid,
- message: message,
- xhtmlMessage: xhtmlMessage
- };
- /** Event: candy:view.message.before-send
- * Before sending a message
- *
- * Parameters:
- * (String) roomJid - room to which the message should be sent
- * (String) message - Message text
- * (String) xhtmlMessage - XHTML formatted message [default: undefined]
- *
- * Returns:
- * Boolean|undefined - if you like to stop sending the message, return false.
- */
- if ($(Candy).triggerHandler("candy:view.message.before-send", evtData) === false) {
- event.preventDefault();
- return;
- }
- message = evtData.message;
- xhtmlMessage = evtData.xhtmlMessage;
- Candy.Core.Action.Jabber.Room.Message(roomJid, message, roomType, xhtmlMessage);
- // Private user chat. Jabber won't notify the user who has sent the message. Just show it as the user hits the button...
- if (roomType === "chat" && message) {
- self.Message.show(roomJid, self.Room.getUser(roomJid).getNick(), message);
- }
- // Clear input and set focus to it
- $(this).children(".field").val("").focus();
- event.preventDefault();
- },
- /** Function: show
- * Show a message in the message pane
- *
- * Parameters:
- * (String) roomJid - room in which the message has been sent to
- * (String) name - Name of the user which sent the message
- * (String) message - Message
- * (String) xhtmlMessage - XHTML formatted message [if options enableXHTML is true]
- * (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, xhtmlMessage, timestamp) {
- message = Candy.Util.Parser.all(message.substring(0, Candy.View.getOptions().crop.message.body));
- if (xhtmlMessage) {
- xhtmlMessage = Candy.Util.parseAndCropXhtml(xhtmlMessage, Candy.View.getOptions().crop.message.body);
- }
- var evtData = {
- roomJid: roomJid,
- name: name,
- message: message,
- xhtmlMessage: xhtmlMessage
- };
- /** 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
- *
- * Returns:
- * Boolean - if you don't want to show the message, return false
- */
- if ($(Candy).triggerHandler("candy:view.message.before-show", evtData) === false) {
- return;
- }
- message = evtData.message;
- xhtmlMessage = evtData.xhtmlMessage;
- if (xhtmlMessage !== undefined && xhtmlMessage.length > 0) {
- message = xhtmlMessage;
- }
- if (!message) {
- return;
- }
- 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.label").click(function(event) {
- event.preventDefault();
- // Check if user is online and not myself
- var room = Candy.Core.getRoom(roomJid);
- if (room && name !== self.Room.getUser(Candy.View.getCurrent().roomJid).getNick() && room.getRoster().get(roomJid + "/" + name)) {
- if (Candy.View.Pane.PrivateRoom.open(roomJid + "/" + name, name, true) === false) {
- return false;
- }
- }
- });
- // Notify the user about a new private message
- if (Candy.View.getCurrent().roomJid !== roomJid || !self.Window.hasFocus()) {
- self.Chat.increaseUnreadMessages(roomJid);
- if (Candy.View.Pane.Chat.rooms[roomJid].type === "chat" && !self.Window.hasFocus()) {
- self.Chat.Toolbar.playSound();
- }
- }
- if (Candy.View.getCurrent().roomJid === roomJid) {
- self.Room.scrollToBottom(roomJid);
- }
- evtData.element = elem;
- /** 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);
- }
- };
- return self;
-}(Candy.View.Pane || {}, jQuery);
-
-/** File: template.js
- * Candy - Chats are not dead yet.
- *
- * Authors:
- * - Patrick Stadler <patrick.stadler@gmail.com>
- * - Michael Weibel <michael.weibel@gmail.com>
- *
- * Copyright:
- * (c) 2011 Amiado Group AG. All rights reserved.
- * (c) 2012-2014 Patrick Stadler & Michael Weibel. All rights reserved.
- */
-"use strict";
-
-/* global Candy */
-/** Class: Candy.View.Template
- * Contains mustache.js templates
- */
-Candy.View.Template = function(self) {
- self.Window = {
- /**
- * Unread messages - used to extend the window title
- */
- unreadmessages: "({{count}}) {{title}}"
- };
- self.Chat = {
- pane: '<div id="chat-pane">{{> tabs}}{{> toolbar}}{{> rooms}}</div>{{> modal}}',
- rooms: '<div id="chat-rooms" class="rooms"></div>',
- tabs: '<ul id="chat-tabs"></ul>',
- tab: '<li class="roomtype-{{roomType}}" data-roomjid="{{roomJid}}" data-roomtype="{{roomType}}">' + '<a href="#" class="label">{{#privateUserChat}}@{{/privateUserChat}}{{name}}</a>' + '<a href="#" class="transition"></a><a href="#" class="close">×</a>' + '<small class="unread"></small></li>',
- modal: '<div id="chat-modal"><a id="admin-message-cancel" class="close" href="#">×</a>' + '<span id="chat-modal-body"></span>' + '<img src="{{assetsPath}}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="{{assetsPath}}audioplayer.swf"' + ' width="0" height="0"><param name="movie" value="{{assetsPath}}audioplayer.swf" /><param name="AllowScriptAccess"' + ' value="always" /><param name="FlashVars" value="listener=audioplayerListener&amp;mp3={{assetsPath}}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>'
- };
- self.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" aria-label="Message Form Text Field" 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}}">&#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"><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 = {
- form: '<form method="post" id="login-form" class="login-form">' + '{{#displayNickname}}<label for="username">{{_labelNickname}}</label><input type="text" id="username" name="username"/>{{/displayNickname}}' + '{{#displayUsername}}<label for="username">{{_labelUsername}}</label>' + '<input type="text" id="username" name="username"/>{{/displayUsername}}' + '{{#presetJid}}<input type="hidden" id="username" name="username" value="{{presetJid}}"/>{{/presetJid}}' + '{{#displayPassword}}<label for="password">{{_labelPassword}}</label>' + '<input type="password" id="password" name="password" />{{/displayPassword}}' + '<input type="submit" class="button" value="{{_loginSubmit}}" /></form>'
- };
- self.PresenceError = {
- enterPasswordForm: "<strong>{{_label}}</strong>" + '<form method="post" id="enter-password-form" class="enter-password-form">' + '<label for="password">{{_labelPassword}}</label><input type="password" id="password" name="password" />' + '<input type="submit" class="button" value="{{_joinSubmit}}" /></form>',
- nicknameConflictForm: "<strong>{{_label}}</strong>" + '<form method="post" id="nickname-conflict-form" class="nickname-conflict-form">' + '<label for="nickname">{{_labelNickname}}</label><input type="text" id="nickname" name="nickname" />' + '<input type="submit" class="button" value="{{_loginSubmit}}" /></form>',
- displayError: "<strong>{{_error}}</strong>"
- };
- return self;
-}(Candy.View.Template || {});
-
-/** File: translation.js
- * Candy - Chats are not dead yet.
- *
- * Authors:
- * - Patrick Stadler <patrick.stadler@gmail.com>
- * - Michael Weibel <michael.weibel@gmail.com>
- *
- * Copyright:
- * (c) 2011 Amiado Group AG. All rights reserved.
- * (c) 2012-2014 Patrick Stadler & Michael Weibel. All rights reserved.
- */
-"use strict";
-
-/* global Candy */
-/** Class: Candy.View.Translation
- * Contains translations
- */
-Candy.View.Translation = {
- en: {
- status: "Status: %s",
- statusConnecting: "Connecting...",
- statusConnected: "Connected",
- statusDisconnecting: "Disconnecting...",
- statusDisconnected: "Disconnected",
- statusAuthfail: "Authentication failed",
- roomSubject: "Subject:",
- messageSubmit: "Send",
- labelUsername: "Username:",
- labelNickname: "Nickname:",
- 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.",
- userChangedNick: "%1$s has changed his nickname to %2$s.",
- 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.',
- errorAutojoinMissing: "No autojoin parameter set in configuration. Please set one to continue.",
- 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:",
- labelNickname: "Spitzname:",
- 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.",
- userChangedNick: "%1$s hat den Nicknamen zu %2$s geändert.",
- 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.',
- errorAutojoinMissing: 'Keine "autojoin" Konfiguration gefunden. Bitte setze eine konfiguration um fortzufahren.',
- 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.",
- 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."
- },
- pt_br: {
- status: "Estado: %s",
- statusConnecting: "Conectando...",
- statusConnected: "Conectado",
- statusDisconnecting: "Desconectando...",
- statusDisconnected: "Desconectado",
- statusAuthfail: "Autenticação falhou",
- roomSubject: "Assunto:",
- messageSubmit: "Enviar",
- labelUsername: "Usuário:",
- labelPassword: "Senha:",
- loginSubmit: "Entrar",
- loginInvalid: "JID inválido",
- reason: "Motivo:",
- subject: "Assunto:",
- reasonWas: "Motivo foi: %s.",
- kickActionLabel: "Derrubar",
- youHaveBeenKickedBy: "Você foi derrubado de %2$s por %1$s",
- youHaveBeenKicked: "Você foi derrubado de %s",
- banActionLabel: "Banir",
- youHaveBeenBannedBy: "Você foi banido de %1$s por %2$s",
- youHaveBeenBanned: "Você foi banido de %s",
- privateActionLabel: "Conversa privada",
- ignoreActionLabel: "Ignorar",
- unignoreActionLabel: "Não ignorar",
- setSubjectActionLabel: "Mudar Assunto",
- administratorMessageSubject: "Administrador",
- userJoinedRoom: "%s entrou na sala.",
- userLeftRoom: "%s saiu da sala.",
- userHasBeenKickedFromRoom: "%s foi derrubado da sala.",
- userHasBeenBannedFromRoom: "%s foi banido da sala.",
- presenceUnknownWarningSubject: "Aviso:",
- presenceUnknownWarning: "Este usuário pode estar desconectado.. Não conseguimos rastrear sua presença..",
- dateFormat: "dd.mm.yyyy",
- timeFormat: "HH:MM:ss",
- tooltipRole: "Moderador",
- tooltipIgnored: "Você ignora este usuário",
- tooltipEmoticons: "Emoticons",
- tooltipSound: "Tocar som para novas mensagens privadas",
- tooltipAutoscroll: "Auto-rolagem",
- tooltipStatusmessage: "Exibir mensagens de estados",
- tooltipAdministration: "Administração de Sala",
- tooltipUsercount: "Participantes da Sala",
- enterRoomPassword: 'Sala "%s" é protegida por senha.',
- enterRoomPasswordSubmit: "Entrar na sala",
- passwordEnteredInvalid: 'Senha inváida para sala "%s".',
- nicknameConflict: "Nome de usuário já em uso. Por favor escolha outro.",
- errorMembersOnly: 'Você não pode entrar na sala "%s": privilégios insuficientes.',
- errorMaxOccupantsReached: 'Você não pode entrar na sala "%s": máximo de participantes atingido.',
- antiSpamMessage: "Por favor, não faça 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."
- }
-};
-//# sourceMappingURL=candy.bundle.map \ No newline at end of file
diff --git a/candy.bundle.map b/candy.bundle.map
deleted file mode 100644
index 94dc4bc..0000000
--- a/candy.bundle.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"file":"candy.bundle.js","sources":["src/candy.js","src/core.js","src/view.js","src/util.js","src/core/action.js","src/core/chatRoom.js","src/core/chatRoster.js","src/core/chatUser.js","src/core/event.js","src/view/observer.js","src/view/pane.js","src/view/template.js","src/view/translation.js"],"names":["Candy","self","$","about","name","version","init","service","options","viewClass","View","view","Core","core","jQuery","Strophe","_connection","_service","_user","_rooms","_anonymousConnection","_status","_options","autojoin","undefined","debug","disableWindowUnload","presencePriority","resource","_addNamespace","value","addNamespace","_addNamespaces","_getEscapedJidFromJid","jid","node","getNodeFromJid","domain","getDomainFromJid","escapeNode","extend","window","console","log","Function","prototype","bind","Util","getIeVersion","call","apply","arguments","Connection","rawInput","rawOutput","caps","onbeforeunload","onWindowUnload","registerEventHandlers","addHandler","Event","Jabber","Version","NS","VERSION","Presence","Message","Bookmarks","PRIVATE","Room","Disco","DISCO_INFO","disco","_onDiscoInfo","_onDiscoItems","DISCO_ITEMS","_delegateCapabilities","CAPS","connect","jidOrHost","password","nick","reset","triggerHandler","connection","indexOf","Connect","ChatUser","Login","attach","sid","rid","disconnect","connected","each","getRooms","Action","Leave","this","getJid","handler","ns","type","id","from","getUser","setUser","user","getConnection","removeRoom","roomJid","getStropheStatus","setStropheStatus","status","isAnonymousConnection","getOptions","getRoom","sync","flush","data","_current","container","language","assets","messages","limit","remove","crop","message","nickname","body","roster","enableXHTML","_setupTranslation","i18n","load","Translation","_registerObservers","on","Observer","Chat","AutojoinMissing","update","PresenceError","_registerWindowHandlers","document","focusin","Pane","Window","onFocus","focusout","onBlur","focus","blur","resize","fitTabs","_initToolbar","Toolbar","_delegateTooltips","delegate","Tooltip","show","resources","Parser","setEmoticonPath","html","Mustache","to_html","Template","pane","tooltipEmoticons","_","tooltipSound","tooltipAutoscroll","tooltipStatusmessage","tooltipAdministration","tooltipUsercount","assetsPath","tabs","rooms","modal","toolbar","soundcontrol","getCurrent","jidToId","MD5","hexdigest","escapeJid","getResourceFromJid","unescapeJid","unescapeNode","str","len","length","substr","parseAndCropXhtml","append","createHtml","get","setCookie","lifetime_days","exp","Date","setDate","getDate","cookie","toUTCString","cookieExists","getCookie","regex","RegExp","escape","matches","exec","deleteCookie","getPosLeftAccordingToWindowBounds","elem","pos","windowWidth","width","elemWidth","outerWidth","marginDiff","backgroundPositionAlignment","px","getPosTopAccordingToWindowBounds","windowHeight","height","elemHeight","outerHeight","localizedTime","dateTime","date","iso8601toDate","toDateString","format","timestamp","parse","isNaN","struct","minutesOffset","getTimezoneOffset","replace","isEmptyObject","obj","prop","hasOwnProperty","forceRedraw","css","display","setTimeout","ie","undef","v","div","createElement","all","getElementsByTagName","innerHTML","_emoticonPath","path","emoticons","plain","image","emotify","text","i","linkify","nl2br","maxLength","currentLength","el","j","tag","attribute","cssAttrs","attr","cssName","cssValue","nodeType","ElementType","NORMAL","nodeName","toLowerCase","XHTML","validTag","attributes","getAttribute","cssText","split","validCSS","push","join","childNodes","e","xmlTextNode","xmlGenerator","createDocumentFragment","appendChild","FRAGMENT","TEXT","nodeValue","substring","parseHTML","msg","sendIQ","$iq","to","c","os","navigator","userAgent","SetNickname","Array","roomNick","presence","conn","$pres","getUniqueId","send","Roster","xmlns","CLIENT","ROSTER","tree","pres","t","toString","up","generateCapsAttrs","Services","Autojoin","BOOKMARKS","pubsubBookmarkRequest","PUBSUB","isArray","Join","valueOf","ResetIgnoreList","getEscapedJid","PRIVACY","action","order","RemoveIgnoreList","GetIgnoreList","iq","iqId","PrivacyList","SetIgnoreListActive","GetJidIfAnonymous","getNick","MUC","muc","leave","xhtmlMsg","trim","getBareJidFromJid","Invite","invitees","reason","$msg","x","MUC_USER","invitee","IgnoreUnignore","userJid","addToOrRemoveFromPrivacyList","UpdatePrivacyList","currentUser","privacyList","getPrivacyList","index","Admin","UserAction","itemObj","role","affiliation","MUC_ADMIN","SetSubject","subject","setTopic","ChatRoom","room","ChatRoster","setName","getName","setRoster","getRoster","items","add","getAll","ROLE_MODERATOR","AFFILIATION_OWNER","privacyLists","customData","previousNick","setJid","setNick","getRole","setRole","setAffiliation","getAffiliation","isModerator","list","splice","setPrivacyLists","lists","isInPrivacyList","setCustomData","getCustomData","setPreviousNick","getPreviousNick","presetJid","Status","CONNECTED","ATTACHED","DISCONNECTED","AUTHFAIL","CONNECTING","DISCONNECTING","AUTHENTICATING","ERROR","CONNFAIL","children","stanza","item","PrivacyListError","fromJid","toJid","mediatedInvite","find","directInvite","passwordNode","continueNode","continuedThread","roomName","actor","code","identity","presenceType","nickAssign","nickChange","l","$status","tagName","error","bareRoomJid","isNoConferenceRoomJid","xhtmlChild","XHTML_IM","xhtmlMessage","first","chatstate","delay","DELAY","_showConnectedMessageModal","event","args","eventName","Modal","hide","showLoginForm","adminMessage","onInfoMessage","close","notifyPrivateChats","actorName","actionLabel","translationParams","Context","adminMessageReason","_action","_reason","evtData","PrivateRoom","setStatus","showEnterPasswordForm","showNicknameConflictForm","showError","setSubject","infoMessage","open","_hasFocus","_plainTitle","title","_unreadMessagesCount","autoscroll","hasFocus","increaseUnreadMessages","renderUnreadMessages","reduceUnreadMessages","num","clearUnreadMessages","count","unreadmessages","setFocusToForm","addTab","roomType","roomId","tab","privateUserChat","appendTo","click","tabClick","tabClose","getTab","removeTab","setActiveTab","addClass","removeClass","unreadElem","parseInt","currentRoomJid","scrollPosition","getPane","scrollTop","preventDefault","parent","allTabsClosed","availableWidth","innerWidth","tabsWidth","overflow","tabDiffToRealWidth","tabWidth","Math","floor","sender","time","toGMTString","appendToMessagePane","scrollToBottom","_supportsNativeAudio","showEmoticonsMenu","currentTarget","stopPropagation","onAutoscrollControlClick","a","canPlayType","onSoundControlClick","onStatusMessageControlClick","context","me","updateUsercount","usercount","playSound","onPlaySound","Audio","play","chatSoundPlayer","getElementById","SetVariable","control","hasClass","toggleClass","onScrollToStoredPosition","onScrollToBottom","showCloseControl","showSpinner","hideCloseControl","hideSpinner","stop","fadeIn","callback","fadeOut","keydown","which","form","_labelNickname","_labelUsername","_labelPassword","_loginSubmit","displayPassword","displayUsername","displayNickname","submit","username","val","enterPasswordForm","_label","_joinSubmit","nicknameConflictForm","replacements","displayError","_error","content","tooltip","target","offset","posLeft","left","posTop","top","mouseleave","menu","links","menulinks","getMenuLinks","clickHandler","link","class","label","element","initialMenuLinks","requiredPermission","private","ignore","ignoreUser","unignore","unignoreUser","kick","contextModalForm","_submit","ban","input","emoticon","messageCount","_messageSubmit","_userOnline","_roomSubject","openRooms","last","sliceMessagePane","slice","messagePane","roomPane","chatPane","addIgnoreIcon","removeIgnoreIcon","subPane","changeDataUserJidIfUserIsMe","roomElement","switchToRoom","messageForm","removeAttr","changeNick","previousPrivateRoomJid","newPrivateRoomJid","previousPrivateRoomId","newPrivateRoomId","roomTabElement","userId","usercountDiff","userElem","displayNick","tooltipRole","tooltipIgnored","userInserted","rosterPane","userSortCompare","toUpperCase","before","showJoinAnimation","replaceWith","opacity","userClick","leaveAnimation","rosterUserId","$rosterUserElem","is","joinAnimation","elementId","slideDown","animate","complete","slideUp","previousUserJid","renderEvtData","template","templateData","displayName","en","statusConnecting","statusConnected","statusDisconnecting","statusDisconnected","statusAuthfail","roomSubject","messageSubmit","labelUsername","labelNickname","labelPassword","loginSubmit","loginInvalid","reasonWas","kickActionLabel","youHaveBeenKickedBy","youHaveBeenKicked","banActionLabel","youHaveBeenBannedBy","youHaveBeenBanned","privateActionLabel","ignoreActionLabel","unignoreActionLabel","setSubjectActionLabel","administratorMessageSubject","userJoinedRoom","userLeftRoom","userHasBeenKickedFromRoom","userHasBeenBannedFromRoom","userChangedNick","presenceUnknownWarningSubject","presenceUnknownWarning","dateFormat","timeFormat","enterRoomPassword","enterRoomPasswordSubmit","passwordEnteredInvalid","nicknameConflict","errorMembersOnly","errorMaxOccupantsReached","errorAutojoinMissing","antiSpamMessage","de","fr","nl","es","cn","ja","sv","it","pt","pt_br","ru","ca"],"mappings":";;;;;;;;;;;AAWA;;;;;;;;;;AAWA,IAAIA,QAAS,SAASC,MAAMC;;;;;;;;IAQ3BD,KAAKE;QACJC,MAAM;QACNC,SAAS;;;;;;;;;;;;;IAcVJ,KAAKK,OAAO,SAASC,SAASC;QAC7B,KAAKA,QAAQC,WAAW;YACvBD,QAAQC,YAAYR,KAAKS;;QAE1BF,QAAQC,UAAUH,KAAKJ,EAAE,WAAWM,QAAQG;QAC5CV,KAAKW,KAAKN,KAAKC,SAASC,QAAQK;;IAGjC,OAAOZ;EACND,aAAac;;;;;;;;;;;;;AC5Cf;;;;;;;;;;;AAYAd,MAAMY,OAAQ,SAASX,MAAMc,SAASb;;;;IAIrC,IAAIc,cAAc;;;IAIjBC,WAAW;;;IAIXC,QAAQ;;;IAIRC;;;IAIAC,uBAAuB;;;IAIvBC;;;;;IAMAC;;;;;QAKCC,UAAUC;QACVC,OAAO;QACPC,qBAAqB;;;;QAIrBC,kBAAkB;;;;;QAKlBC,UAAU5B,MAAMG,MAAMC;;;;;;;;IAUvByB,gBAAgB,SAASzB,MAAM0B;QAC9Bf,QAAQgB,aAAa3B,MAAM0B;;;;IAM5BE,iBAAiB;QAChBH,cAAc,WAAW;QACzBA,cAAc,aAAa;QAC3BA,cAAc,WAAW;QACzBA,cAAc,SAAS;QACvBA,cAAc,UAAU;OAGzBI,wBAAwB,SAASC;QAChC,IAAIC,OAAOpB,QAAQqB,eAAeF,MACjCG,SAAStB,QAAQuB,iBAAiBJ;QACnC,OAAOC,OAAOpB,QAAQwB,WAAWJ,QAAQ,MAAME,SAASA;;;;;;;;;IAU1DpC,KAAKK,OAAO,SAASC,SAASC;QAC7BS,WAAWV;;QAEXL,EAAEsC,OAAO,MAAMlB,UAAUd;;QAGzB,IAAGc,SAASG,OAAO;YAClB,WAAUgB,OAAOC,YAAYlB,oBAAoBiB,OAAOC,QAAQC,QAAQnB,WAAW;;gBAElF,IAAGoB,SAASC,UAAUC,QAAQ9C,MAAM+C,KAAKC,iBAAiB,GAAG;oBAC5D/C,KAAK0C,MAAMC,SAASC,UAAUC,KAAKG,KAAKP,QAAQC,KAAKD;uBAC/C;oBACNzC,KAAK0C,MAAM;wBACVC,SAASC,UAAUK,MAAMD,KAAKP,QAAQC,KAAKD,SAASS;;;;YAIvDlD,KAAK0C,IAAI;;QAGVX;;QAGAhB,cAAc,IAAID,QAAQqC,WAAWnC;QACrCD,YAAYqC,WAAWpD,KAAKoD,SAASP,KAAK7C;QAC1Ce,YAAYsC,YAAYrD,KAAKqD,UAAUR,KAAK7C;;QAG5Ce,YAAYuC,KAAKpB,OAAO;;;QAIxB,KAAKb,SAASI,qBAAqB;YAClCe,OAAOe,iBAAiBvD,KAAKwD;;;;;;;;IAS/BxD,KAAKyD,wBAAwB;QAC5BzD,KAAK0D,WAAW1D,KAAK2D,MAAMC,OAAOC,SAAS/C,QAAQgD,GAAGC,SAAS;QAC/D/D,KAAK0D,WAAW1D,KAAK2D,MAAMC,OAAOI,UAAU,MAAM;QAClDhE,KAAK0D,WAAW1D,KAAK2D,MAAMC,OAAOK,SAAS,MAAM;QACjDjE,KAAK0D,WAAW1D,KAAK2D,MAAMC,OAAOM,WAAWpD,QAAQgD,GAAGK,SAAS;QACjEnE,KAAK0D,WAAW1D,KAAK2D,MAAMC,OAAOQ,KAAKC,OAAOvD,QAAQgD,GAAGQ,YAAY,MAAM;QAE3EtE,KAAK0D,WAAW3C,YAAYwD,MAAMC,aAAa3B,KAAK9B,YAAYwD,QAAQzD,QAAQgD,GAAGQ,YAAY,MAAM;QACrGtE,KAAK0D,WAAW3C,YAAYwD,MAAME,cAAc5B,KAAK9B,YAAYwD,QAAQzD,QAAQgD,GAAGY,aAAa,MAAM;QACvG1E,KAAK0D,WAAW3C,YAAYuC,KAAKqB,sBAAsB9B,KAAK9B,YAAYuC,OAAOxC,QAAQgD,GAAGc;;;;;;;;;;;;;;;;;;;;;IAsB3F5E,KAAK6E,UAAU,SAASC,WAAWC,UAAUC;;QAE5CjE,YAAYkE;QACZjF,KAAKyD;;;;;;;;;;;;QAYLxD,EAAEF,OAAOmF,eAAe;YACvBC,YAAYpE;;QAGbI,wBAAwBA,uBAAuB2D,aAAaA,UAAUM,QAAQ,OAAO,IAAI;QAEzF,IAAGN,aAAaC,UAAU;;YAEzBhE,YAAY8D,QAAQ7C,sBAAsB8C,aAAa,MAAMzD,SAASM,UAAUoD,UAAUhF,MAAMY,KAAKgD,MAAM7C,QAAQuE;YACnH,IAAIL,MAAM;gBACT/D,QAAQ,IAAIjB,KAAKsF,SAASR,WAAWE;mBAC/B;gBACN/D,QAAQ,IAAIjB,KAAKsF,SAASR,WAAWhE,QAAQqB,eAAe2C;;eAEvD,IAAGA,aAAaE,MAAM;;YAE5BjE,YAAY8D,QAAQ7C,sBAAsB8C,aAAa,MAAMzD,SAASM,UAAU,MAAM5B,MAAMY,KAAKgD,MAAM7C,QAAQuE;YAC/GpE,QAAQ,IAAIjB,KAAKsF,SAAS,MAAMN;eAC1B,IAAGF,WAAW;YACpB/E,MAAMY,KAAKgD,MAAM4B,MAAMT;eACjB;;YAEN/E,MAAMY,KAAKgD,MAAM4B;;;;;;;;;;;;;IAcnBvF,KAAKwF,SAAS,SAASvD,KAAKwD,KAAKC;QAChCzE,QAAQ,IAAIjB,KAAKsF,SAASrD,KAAKnB,QAAQqB,eAAeF;QACtDjC,KAAKyD;QACL1C,YAAYyE,OAAOvD,KAAKwD,KAAKC,KAAK3F,MAAMY,KAAKgD,MAAM7C,QAAQuE;;;;;IAM5DrF,KAAK2F,aAAa;QACjB,IAAG5E,YAAY6E,WAAW;YACzB3F,EAAE4F,KAAK7F,KAAK8F,YAAY;gBACvB/F,MAAMY,KAAKoF,OAAOnC,OAAOQ,KAAK4B,MAAMC,KAAKC;;YAE1CnF,YAAY4E;;;;;;;;;;;;;;;;;;IAmBd3F,KAAK0D,aAAa,SAASyC,SAASC,IAAIjG,MAAMkG,MAAMC,IAAIC,MAAMhG;QAC7D,OAAOQ,YAAY2C,WAAWyC,SAASC,IAAIjG,MAAMkG,MAAMC,IAAIC,MAAMhG;;;;;;;;IASlEP,KAAKwG,UAAU;QACd,OAAOvF;;;;;;;;IASRjB,KAAKyG,UAAU,SAASC;QACvBzF,QAAQyF;;;;;;;;IAST1G,KAAK2G,gBAAgB;QACpB,OAAO5F;;;;;;;;IASRf,KAAK4G,aAAa,SAASC;eACnB3F,OAAO2F;;;;;;;;IASf7G,KAAK8F,WAAW;QACf,OAAO5E;;;;;;;;IASRlB,KAAK8G,mBAAmB;QACvB,OAAO1F;;;;;;;;;;;IAYRpB,KAAK+G,mBAAmB,SAASC;QAChC5F,UAAU4F;;;;;;;;IASXhH,KAAKiH,wBAAwB;QAC5B,OAAO9F;;;;;;;;IASRnB,KAAKkH,aAAa;QACjB,OAAO7F;;;;;;;;;;;IAYRrB,KAAKmH,UAAU,SAASN;QACvB,IAAI3F,OAAO2F,UAAU;YACpB,OAAO3F,OAAO2F;;QAEf,OAAO;;;;;IAMR7G,KAAKwD,iBAAiB;;;QAGrBzC,YAAYR,QAAQ6G,OAAO;QAC3BpH,KAAK2F;QACL5E,YAAYsG;;;;;;;IAQbrH,KAAKoD,WAAW,SAASkE;QACxBrB,KAAKvD,IAAI,WAAW4E;;;;;;;IAQrBtH,KAAKqD,YAAY,SAASiE;QACzBrB,KAAKvD,IAAI,WAAW4E;;;;;;;IAQrBtH,KAAK0C,MAAM;IAEX,OAAO1C;EACND,MAAMY,YAAYG,SAASD;;;;;;;;;;;;;ACnZ7B;;;;;;;;;;AAWAd,MAAMU,OAAQ,SAAST,MAAMC;;;;IAI5B,IAAIsH;QAAaC,WAAW;QAAMX,SAAS;;;;;;;;;;IAU1CxF;QACCoG,UAAU;QACVC,QAAQ;QACRC;YAAYC,OAAO;YAAMC,QAAQ;;QACjCC;YACCC;gBAAWC,UAAU;gBAAIC,MAAM;;YAC/BC;gBAAUF,UAAU;;;QAErBG,aAAa;;;;;;;;;;IAYdC,oBAAoB,SAASX;QAC5BxH,EAAEoI,KAAKC,KAAKtI,KAAKuI,YAAYd;;;;IAM9Be,qBAAqB;QACpBvI,EAAEF,OAAO0I,GAAG,8BAA8BzI,KAAK0I,SAASC,KAAKxF;QAC7DlD,EAAEF,OAAO0I,GAAG,2BAA2BzI,KAAK0I,SAASC,KAAK1E;QAC1DhE,EAAEF,OAAO0I,GAAG,oBAAoBzI,KAAK0I,SAASnD;QAC9CtF,EAAEF,OAAO0I,GAAG,+BAA+BzI,KAAK0I,SAASE;QACzD3I,EAAEF,OAAO0I,GAAG,uBAAuBzI,KAAK0I,SAAS1E,SAAS6E;QAC1D5I,EAAEF,OAAO0I,GAAG,6BAA6BzI,KAAK0I,SAAS1E,SAAS6E;QAChE5I,EAAEF,OAAO0I,GAAG,4BAA4BzI,KAAK0I,SAAS1E,SAAS6E;QAC/D5I,EAAEF,OAAO0I,GAAG,6BAA6BzI,KAAK0I,SAASI;QACvD7I,EAAEF,OAAO0I,GAAG,sBAAsBzI,KAAK0I,SAASzE;;;;;;IAQjD8E,0BAA0B;QACzB,IAAGhJ,MAAM+C,KAAKC,iBAAiB,GAAG;YACjC9C,EAAE+I,UAAUC,QAAQlJ,MAAMU,KAAKyI,KAAKC,OAAOC,SAASC,SAAStJ,MAAMU,KAAKyI,KAAKC,OAAOG;eAC9E;YACNrJ,EAAEuC,QAAQ+G,MAAMxJ,MAAMU,KAAKyI,KAAKC,OAAOC,SAASI,KAAKzJ,MAAMU,KAAKyI,KAAKC,OAAOG;;QAE7ErJ,EAAEuC,QAAQiH,OAAO1J,MAAMU,KAAKyI,KAAKP,KAAKe;;;;IAMvCC,eAAe;QACd3J,KAAKkJ,KAAKP,KAAKiB,QAAQvJ;;;;IAMxBwJ,oBAAoB;QACnB5J,EAAE,QAAQ6J,SAAS,oBAAoB,cAAc/J,MAAMU,KAAKyI,KAAKP,KAAKoB,QAAQC;;;;;;;;;IAUpFhK,KAAKK,OAAO,SAASmH,WAAWjH;;;;QAI/B,IAAGA,QAAQ0J,WAAW;YACrB1J,QAAQmH,SAASnH,QAAQ0J;;eAEnB1J,QAAQ0J;QAEfhK,EAAEsC,OAAO,MAAMlB,UAAUd;QACzB6H,kBAAkB/G,SAASoG;;QAG3B1H,MAAM+C,KAAKoH,OAAOC,gBAAgBlE,KAAKiB,aAAaQ,SAAS;;QAG7DH,SAASC,YAAYA;QACrBD,SAASC,UAAU4C,KAAKC,SAASC,QAAQvK,MAAMU,KAAK8J,SAAS5B,KAAK6B;YACjEC,kBAAmBxK,EAAEoI,KAAKqC,EAAE;YAC5BC,cAAe1K,EAAEoI,KAAKqC,EAAE;YACxBE,mBAAoB3K,EAAEoI,KAAKqC,EAAE;YAC7BG,sBAAuB5K,EAAEoI,KAAKqC,EAAE;YAChCI,uBAAwB7K,EAAEoI,KAAKqC,EAAE;YACjCK,kBAAmB9K,EAAEoI,KAAKqC,EAAE;YAC5BM,YAAa/E,KAAKiB,aAAaQ;;YAE/BuD,MAAMlL,MAAMU,KAAK8J,SAAS5B,KAAKsC;YAC/BC,OAAOnL,MAAMU,KAAK8J,SAAS5B,KAAKuC;YAChCC,OAAOpL,MAAMU,KAAK8J,SAAS5B,KAAKwC;YAChCC,SAASrL,MAAMU,KAAK8J,SAAS5B,KAAKyC;YAClCC,cAActL,MAAMU,KAAK8J,SAAS5B,KAAK0C;;;QAIxCtC;QACAY;QACAnB;QACAqB;;;;;;;;IASD7J,KAAKsL,aAAa;QACjB,OAAO/D;;;;;;;;IASRvH,KAAKkH,aAAa;QACjB,OAAO7F;;IAGR,OAAOrB;EACND,MAAMU,YAAYI;;;;;;;;;;;;;AChKpB;;;;;;;;;;AAWAd,MAAM+C,OAAQ,SAAS9C,MAAMC;;;;;;;;;;IAU5BD,KAAKuL,UAAU,SAAStJ;QACvB,OAAOuJ,IAAIC,UAAUxJ;;;;;;;;;;;;;;IAetBjC,KAAK0L,YAAY,SAASzJ;QACzB,IAAIC,OAAOpB,QAAQwB,WAAWxB,QAAQqB,eAAeF,OACpDG,SAAStB,QAAQuB,iBAAiBJ,MAClCN,WAAWb,QAAQ6K,mBAAmB1J;QAEvCA,MAAMC,OAAO,MAAME;QACnB,IAAIT,UAAU;YACbM,OAAO,MAAMN;;QAGd,OAAOM;;;;;;;;;;;;;;IAeRjC,KAAK4L,cAAc,SAAS3J;QAC3B,IAAIC,OAAOpB,QAAQ+K,aAAa/K,QAAQqB,eAAeF,OACtDG,SAAStB,QAAQuB,iBAAiBJ,MAClCN,WAAWb,QAAQ6K,mBAAmB1J;QAEvCA,MAAMC,OAAO,MAAME;QACnB,IAAGT,UAAU;YACZM,OAAO,MAAMN;;QAGd,OAAOM;;;;;;;;;IAURjC,KAAK8H,OAAO,SAASgE,KAAKC;QACzB,IAAID,IAAIE,SAASD,KAAK;YACrBD,MAAMA,IAAIG,OAAO,GAAGF,MAAM,KAAK;;QAEhC,OAAOD;;;;;;;;;;;;;IAcR9L,KAAKkM,oBAAoB,SAASJ,KAAKC;QACtC,OAAO9L,EAAE,UAAUkM,OAAOnM,KAAKoM,WAAWnM,EAAE6L,KAAKO,IAAI,IAAIN,MAAM3B;;;;;;;;;;IAWhEpK,KAAKsM,YAAY,SAASnM,MAAM0B,OAAO0K;QACtC,IAAIC,MAAM,IAAIC;QACdD,IAAIE,QAAQ,IAAID,OAAOE,YAAYJ;QACnCvD,SAAS4D,SAASzM,OAAO,MAAM0B,QAAQ,cAAc2K,IAAIK,gBAAgB;;;;;;;;;;;IAY1E7M,KAAK8M,eAAe,SAAS3M;QAC5B,OAAO6I,SAAS4D,OAAOxH,QAAQjF,SAAS;;;;;;;;;;;IAYzCH,KAAK+M,YAAY,SAAS5M;QACzB,IAAG6I,SAAS4D,QAAQ;YACnB,IAAII,QAAQ,IAAIC,OAAOC,OAAO/M,QAAQ,YAAY,OACjDgN,UAAUH,MAAMI,KAAKpE,SAAS4D;YAC/B,IAAGO,SAAS;gBACX,OAAOA,QAAQ;;;;;;;;;;IAWlBnN,KAAKqN,eAAe,SAASlN;QAC5B6I,SAAS4D,SAASzM,OAAO;;;;;;;;;;;;;;;;IAiB1BH,KAAKsN,oCAAoC,SAASC,MAAMC;QACvD,IAAIC,cAAcxN,EAAE+I,UAAU0E,SAC7BC,YAAcJ,KAAKK,cACnBC,aAAaF,YAAYJ,KAAKK,WAAW,OACzCE,8BAA8B;QAE/B,IAAIN,MAAMG,aAAaF,aAAa;YACnCD,OAAOG,YAAYE;YACnBC,8BAA8B;;QAG/B;YAASC,IAAIP;YAAKM,6BAA6BA;;;;;;;;;;;;;;;;;IAiBhD9N,KAAKgO,mCAAmC,SAAST,MAAMC;QACtD,IAAIS,eAAehO,EAAE+I,UAAUkF,UAC9BC,aAAeZ,KAAKa,eACpBP,aAAaM,aAAaZ,KAAKa,YAAY,OAC3CN,8BAA8B;QAE/B,IAAIN,MAAMW,cAAcF,cAAc;YACrCT,OAAOW,aAAaN;YACpBC,8BAA8B;;QAG/B;YAASC,IAAIP;YAAKM,6BAA6BA;;;;;;;;;;;;;;;;IAgBhD9N,KAAKqO,gBAAgB,SAASC;QAC7B,IAAIA,aAAa/M,WAAW;YAC3B,OAAOA;;QAGR,IAAIgN,OAAOvO,KAAKwO,cAAcF;QAC9B,IAAGC,KAAKE,mBAAmB,IAAIhC,OAAOgC,gBAAgB;YACrD,OAAOF,KAAKG,OAAOzO,EAAEoI,KAAKqC,EAAE;eACtB;YACN,OAAO6D,KAAKG,OAAOzO,EAAEoI,KAAKqC,EAAE;;;;;;;;;;;;;;;;;;;;;IAsB9B1K,KAAKwO,gBAAgB,SAASD;QAC7B,IAAII,YAAYlC,KAAKmC,MAAML;QAC3B,IAAGM,MAAMF,YAAY;YACpB,IAAIG,SAAS,8HAA8H1B,KAAKmB;YAChJ,IAAGO,QAAQ;gBACV,IAAIC,gBAAgB;gBACpB,IAAGD,OAAO,OAAO,KAAK;oBACrBC,iBAAiBD,OAAO,MAAM,MAAOA,OAAO;oBAC5C,IAAGA,OAAO,OAAO,KAAK;wBACrBC,iBAAiBA;;;gBAGnBA,iBAAiB,IAAItC,OAAOuC;gBAC5B,OAAO,IAAIvC,MAAMqC,OAAO,KAAKA,OAAO,KAAK,IAAIA,OAAO,KAAKA,OAAO,KAAKA,OAAO,KAAKC,gBAAgBD,OAAO,IAAIA,OAAO,MAAMA,OAAO,GAAG7C,OAAO,GAAG,KAAK;mBAC5I;;gBAEN0C,YAAYlC,KAAKmC,MAAML,KAAKU,QAAQ,0BAA0B,cAAc;;;QAG9E,OAAO,IAAIxC,KAAKkC;;;;;;;;;;;IAYjB3O,KAAKkP,gBAAgB,SAASC;QAC7B,IAAIC;QACJ,KAAIA,QAAQD,KAAK;YAChB,IAAIA,IAAIE,eAAeD,OAAO;gBAC7B,OAAO;;;QAGT,OAAO;;;;;;;;IASRpP,KAAKsP,cAAc,SAAS/B;QAC3BA,KAAKgC;YAAKC,SAAQ;;QAClBC,WAAW;YACVxJ,KAAKsJ;gBAAKC,SAAQ;;UACjB3M,KAAK0K,OAAO;;;;;;;IAQf,IAAImC,KAAM;QACT,IAAIC,OACHC,IAAI,GACJC,MAAM7G,SAAS8G,cAAc,QAC7BC,MAAMF,IAAIG,qBAAqB;QAChC;QAECH,IAAII,YAAY,qBAAsBL,IAAK,yBAC3CG,IAAI,IACH;QACF,OAAOH,IAAI,IAAIA,IAAID;;;;;;;;IASpB3P,KAAK+C,eAAe;QACnB,OAAO2M;;;;;IAMR1P,KAAKkK;;;;;;QAMJgG,eAAe;;;;;;;QAQf/F,iBAAiB,SAASgG;YACzBlK,KAAKiK,gBAAgBC;;;;;;;QAQtBC;YAEEC,OAAO;YACPrD,OAAO;YACPsD,OAAO;;YAGPD,OAAO;YACPrD,OAAO;YACPsD,OAAO;;YAGPD,OAAO;YACPrD,OAAO;YACPsD,OAAO;;YAGPD,OAAO;YACPrD,OAAO;YACPsD,OAAO;;YAGPD,OAAO;YACPrD,OAAO;YACPsD,OAAO;;YAGPD,OAAO;YACPrD,OAAO;YACPsD,OAAO;;YAGPD,OAAO;YACPrD,OAAO;YACPsD,OAAO;;YAGPD,OAAO;YACPrD,OAAO;YACPsD,OAAO;;YAGPD,OAAO;YACPrD,OAAO;YACPsD,OAAO;;YAGPD,OAAO;YACPrD,OAAO;YACPsD,OAAO;;YAGPD,OAAO;YACPrD,OAAO;YACPsD,OAAO;;YAGPD,OAAO;YACPrD,OAAO;YACPsD,OAAO;;YAGPD,OAAO;YACPrD,OAAO;YACPsD,OAAO;;YAGPD,OAAO;YACPrD,OAAO;YACPsD,OAAO;;YAGPD,OAAO;YACPrD,OAAO;YACPsD,OAAO;;YAGPD,OAAO;YACPrD,OAAO;YACPsD,OAAO;;;;;;;;;;;QAaTC,SAAS,SAASC;YACjB,IAAIC;YACJ,KAAIA,IAAIxK,KAAKmK,UAAUpE,SAAO,GAAGyE,KAAK,GAAGA,KAAK;gBAC7CD,OAAOA,KAAKvB,QAAQhJ,KAAKmK,UAAUK,GAAGzD,OAAO,2CAA2C/G,KAAKiK,gBAAgBjK,KAAKmK,UAAUK,GAAGH,QAAQ;;YAExI,OAAOE;;;;;;;;;;;QAYRE,SAAS,SAASF;YACjBA,OAAOA,KAAKvB,QAAQ,yCAAyC;YAC7D,OAAOuB,KAAKvB,QAAQ,iFAAiF;;;;;;;;;;;QAYtG/B,QAAQ,SAASsD;YAChB,OAAOvQ,EAAE,UAAUuQ,KAAKA,MAAMpG;;;;;;;;;;;QAY/BuG,OAAO,SAASH;YACf,OAAOA,KAAKvB,QAAQ,eAAe;;;;;;;;;;;QAYpCc,KAAK,SAASS;YACb,IAAGA,MAAM;gBACRA,OAAOvK,KAAKiH,OAAOsD;gBACnBA,OAAOvK,KAAKyK,QAAQF;gBACpBA,OAAOvK,KAAKsK,QAAQC;gBACpBA,OAAOvK,KAAK0K,MAAMH;;YAEnB,OAAOA;;;;;;;;;;;;;;;;;;;IAoBTxQ,KAAKoM,aAAa,SAASmB,MAAMqD,WAAWC;;QAE3CA,gBAAgBA,iBAAiB;QACjC,IAAIJ,GAAGK,IAAIC,GAAGC,KAAKC,WAAWpP,OAAO0N,KAAK2B,UAAUC,MAAMC,SAASC;QACnE,IAAI9D,KAAK+D,aAAaxQ,QAAQyQ,YAAYC,QAAQ;YACjDR,MAAMzD,KAAKkE,SAASC;YACpB,IAAG5Q,QAAQ6Q,MAAMC,SAASZ,MAAM;gBAC/B;oBACCF,KAAK7Q,EAAE,MAAM+Q,MAAM;oBACnB,KAAIP,IAAI,GAAGA,IAAI3P,QAAQ6Q,MAAME,WAAWb,KAAKhF,QAAQyE,KAAK;wBACzDQ,YAAYnQ,QAAQ6Q,MAAME,WAAWb,KAAKP;wBAC1C5O,QAAQ0L,KAAKuE,aAAab;wBAC1B,WAAUpP,UAAU,eAAeA,UAAU,QAAQA,UAAU,MAAMA,UAAU,SAASA,UAAU,GAAG;4BACpG;;wBAED,IAAGoP,cAAc,kBAAkBpP,UAAU,UAAU;4BACtD,WAAUA,MAAMkQ,YAAY,aAAa;gCACxClQ,QAAQA,MAAMkQ;;;;wBAIhB,IAAGd,cAAc,SAAS;4BACzB1B;4BACA2B,WAAWrP,MAAMmQ,MAAM;4BACvB,KAAIjB,IAAI,GAAGA,IAAIG,SAASlF,QAAQ+E,KAAK;gCACpCI,OAAOD,SAASH,GAAGiB,MAAM;gCACzBZ,UAAUD,KAAK,GAAGlC,QAAQ,QAAQ,IAAIA,QAAQ,QAAQ,IAAIyC;gCAC1D,IAAG5Q,QAAQ6Q,MAAMM,SAASb,UAAU;oCACnCC,WAAWF,KAAK,GAAGlC,QAAQ,QAAQ,IAAIA,QAAQ,QAAQ;oCACvDM,IAAI2C,KAAKd,UAAU,OAAOC;;;4BAG5B,IAAG9B,IAAIvD,SAAS,GAAG;gCAClBnK,QAAQ0N,IAAI4C,KAAK;gCACjBrB,GAAGK,KAAKF,WAAWpP;;+BAEd;4BACNiP,GAAGK,KAAKF,WAAWpP;;;oBAIrB,KAAK4O,IAAI,GAAGA,IAAIlD,KAAK6E,WAAWpG,QAAQyE,KAAK;wBAC5CK,GAAG3E,OAAOnM,KAAKoM,WAAWmB,KAAK6E,WAAW3B,IAAIG,WAAWC;;kBAEzD,OAAMwB;;oBACPtS,MAAMY,KAAK+B,IAAI;oBACf3C,MAAMY,KAAK+B,IAAI2P;oBACfvB,KAAKhQ,QAAQwR,YAAY;;mBAEpB;gBACNxB,KAAKhQ,QAAQyR,eAAeC;gBAC5B,KAAK/B,IAAI,GAAGA,IAAIlD,KAAK6E,WAAWpG,QAAQyE,KAAK;oBAC5CK,GAAG2B,YAAYzS,KAAKoM,WAAWmB,KAAK6E,WAAW3B,IAAIG,WAAWC;;;eAG1D,IAAItD,KAAK+D,aAAaxQ,QAAQyQ,YAAYmB,UAAU;YAC1D5B,KAAKhQ,QAAQyR,eAAeC;YAC5B,KAAK/B,IAAI,GAAGA,IAAIlD,KAAK6E,WAAWpG,QAAQyE,KAAK;gBAC5CK,GAAG2B,YAAYzS,KAAKoM,WAAWmB,KAAK6E,WAAW3B,IAAIG,WAAWC;;eAEzD,IAAItD,KAAK+D,aAAaxQ,QAAQyQ,YAAYoB,MAAM;YACtD,IAAInC,OAAOjD,KAAKqF;YAChB/B,iBAAiBL,KAAKxE;YACtB,IAAG4E,aAAaC,gBAAgBD,WAAW;gBAC1CJ,OAAOA,KAAKqC,UAAU,GAAGjC;;YAE1BJ,OAAOzQ,MAAM+C,KAAKoH,OAAO6F,IAAIS;YAC7BM,KAAK7Q,EAAE6S,UAAUtC;;QAGlB,OAAOM;;IAIR,OAAO9Q;EACND,MAAM+C,YAAYjC;;;;;;;;;;;;;AC3mBpB;;;;;;;;;;;AAYAd,MAAMY,KAAKoF,SAAU,SAAS/F,MAAMc,SAASb;;;;IAI5CD,KAAK4D;;;;;;;QAOJC,SAAS,SAASkP;YACjBhT,MAAMY,KAAKgG,gBAAgBqM,OAAOC;gBACjC5M,MAAM;gBACN6M,IAAInT,MAAM+C,KAAK4I,UAAUqH,IAAI5B,KAAK;gBAClC5K,MAAMxG,MAAM+C,KAAK4I,UAAUqH,IAAI5B,KAAK;gBACpC7K,IAAIyM,IAAI5B,KAAK;eACXgC,EAAE;gBACJhT,MAAMJ,MAAMG,MAAMC;gBAClBC,SAASL,MAAMG,MAAME;gBACrBgT,IAAIC,UAAUC;;;;;;;;;;;QAYhBC,aAAa,SAASvL,UAAUkD;YAC/BA,QAAQA,iBAAiBsI,QAAQtI,QAAQnL,MAAMY,KAAKmF;YACpD,IAAI2N,UAAUC,UACbC,OAAO5T,MAAMY,KAAKgG;YACnB1G,EAAE4F,KAAKqF,OAAO,SAASrE;gBACtB4M,WAAW1T,MAAM+C,KAAK4I,UAAU7E,UAAU,MAAMmB;gBAChD0L,WAAWE;oBACVV,IAAIO;oBACJlN,MAAMoN,KAAK1R;oBACXqE,IAAI,UAAUqN,KAAKE;;gBAEpB9T,MAAMY,KAAKgG,gBAAgBmN,KAAKJ;;;;;;QAOlCK,QAAQ;YACPhU,MAAMY,KAAKgG,gBAAgBqM,OAAOC;gBACjC5M,MAAM;gBACN2N,OAAOlT,QAAQgD,GAAGmQ;eAChBd,EAAE;gBAAUa,OAAOlT,QAAQgD,GAAGoQ;eAASC;;;;;;;;;QAU3CnQ,UAAU,SAASmN,MAAML;YACxB,IAAI6C,OAAO5T,MAAMY,KAAKgG;YACtBwK,OAAOA;YACP,KAAIA,KAAK7K,IAAI;gBACZ6K,KAAK7K,KAAK,UAAUqN,KAAKE;;YAE1B,IAAIO,OAAOR,MAAMzC,MAAMgC,EAAE,YAAYkB,EAAEtU,MAAMY,KAAKuG,aAAaxF,iBAAiB4S,YAC9EC,KAAKpB,EAAE,KAAKQ,KAAKrQ,KAAKkR,qBACtBD;YACF,IAAGzD,IAAI;gBACNsD,KAAKlS,KAAKuQ,YAAY3B,GAAG5O;;YAE1ByR,KAAKG,KAAKM,KAAKD;;;;;QAMhBM,UAAU;YACT1U,MAAMY,KAAKgG,gBAAgBqM,OAAOC;gBACjC5M,MAAM;gBACN2N,OAAOlT,QAAQgD,GAAGmQ;eAChBd,EAAE;gBAAUa,OAAOlT,QAAQgD,GAAGY;eAAcyP;;;;;;;;;;;QAYhDO,UAAU;;YAET,IAAG3U,MAAMY,KAAKuG,aAAa5F,aAAa,MAAM;gBAC7CvB,MAAMY,KAAKgG,gBAAgBqM,OAAOC;oBACjC5M,MAAM;oBACN2N,OAAOlT,QAAQgD,GAAGmQ;mBAElBd,EAAE;oBAAUa,OAAOlT,QAAQgD,GAAGK;mBAC9BgP,EAAE;oBAAYa,OAAOlT,QAAQgD,GAAG6Q;mBAChCR;gBAED,IAAIS,wBAAwB7U,MAAMY,KAAKgG,gBAAgBkN,YAAY;gBACnE9T,MAAMY,KAAK+C,WAAW3D,MAAMY,KAAKgD,MAAMC,OAAOM,WAAWpD,QAAQgD,GAAG+Q,QAAQ,MAAM,UAAUD;gBAE5F7U,MAAMY,KAAKgG,gBAAgBqM,OAAOC;oBACjC5M,MAAM;oBACNC,IAAIsO;mBAEJzB,EAAE;oBAAYa,OAAOlT,QAAQgD,GAAG+Q;mBAChC1B,EAAE;oBAAWjR,MAAMpB,QAAQgD,GAAG6Q;mBAC9BR;mBAEK,IAAGlU,EAAE6U,QAAQ/U,MAAMY,KAAKuG,aAAa5F,WAAW;gBACtDrB,EAAE4F,KAAK9F,MAAMY,KAAKuG,aAAa5F,UAAU;oBACxCtB,KAAK4D,OAAOQ,KAAK2Q,KAAK9R,MAAM,MAAMgD,KAAK+O,UAAUhD,MAAM,KAAI;;mBAEtD;;;;gBAIN/R,EAAEF,OAAOmF,eAAe;;;;;;QAO1B+P,iBAAiB;YAChBlV,MAAMY,KAAKgG,gBAAgBqM,OAAOC;gBAChC5M,MAAM;gBACNE,MAAMxG,MAAMY,KAAK6F,UAAU0O;eAE3B/B,EAAE;gBAAUa,OAAOlT,QAAQgD,GAAGqR;eAC9BhC,EAAE;gBAAShT,MAAM;eACjBgT,EAAE;gBAASiC,QAAU;gBAASC,OAAS;eACvClB;;;;;QAMHmB,kBAAkB;YACjBvV,MAAMY,KAAKgG,gBAAgBqM,OAAOC;gBAChC5M,MAAM;gBACNE,MAAMxG,MAAMY,KAAK6F,UAAU0O;eAE3B/B,EAAE;gBAAUa,OAAOlT,QAAQgD,GAAGqR;eAC9BhC,EAAE;gBAAShT,MAAM;eAAWgU;;;;;QAM/BoB,eAAe;YACd,IAAIC,KAAKvC;gBACP5M,MAAM;gBACNE,MAAMxG,MAAMY,KAAK6F,UAAU0O;eAE3B/B,EAAE;gBAAUa,OAAOlT,QAAQgD,GAAGqR;eAC9BhC,EAAE;gBAAShT,MAAM;eAAWgU;YAC9B,IAAIsB,OAAO1V,MAAMY,KAAKgG,gBAAgBqM,OAAOwC;;YAE7CzV,MAAMY,KAAK+C,WAAW3D,MAAMY,KAAKgD,MAAMC,OAAO8R,aAAa,MAAM,MAAM,MAAMD;;;;;QAM9EE,qBAAqB;YACpB5V,MAAMY,KAAKgG,gBAAgBqM,OAAOC;gBAChC5M,MAAM;gBACNE,MAAMxG,MAAMY,KAAK6F,UAAU0O;eAC3B/B,EAAE;gBAAUa,OAAOlT,QAAQgD,GAAGqR;eAC9BhC,EAAE;gBAAWhT,MAAK;eAAWgU;;;;;;QAOhCyB,mBAAmB;YAClB,KAAK7V,MAAMY,KAAK6F,UAAUN,UAAU;gBACnCnG,MAAMY,KAAK+B,IAAI;gBACf3C,MAAMY,KAAK6F,UAAUc,KAAKrF,MAAMlC,MAAMY,KAAKgG,gBAAgB1E;;;;;;QAO7DmC;;;;;;;;;;;;YAYC2Q,MAAM,SAASlO,SAAS9B;gBACvB/E,KAAK4D,OAAOQ,KAAKC,MAAMwC;gBACvBA,UAAU9G,MAAM+C,KAAK4I,UAAU7E;gBAC/B,IAAI8M,OAAO5T,MAAMY,KAAKgG,iBACrB8M,WAAW5M,UAAU,MAAM9G,MAAMY,KAAK6F,UAAUqP,WAChDzB,OAAOR;oBAAQV,IAAIO;oBAAUnN,IAAI,UAAUqN,KAAKE;mBAC9CV,EAAE;oBAAMa,OAAOlT,QAAQgD,GAAGgS;;gBAC7B,IAAI/Q,UAAU;oBACbqP,KAAKjB,EAAE,YAAYkB,EAAEtP;;gBAEtBqP,KAAKG,KAAKpB,EAAE,KAAKQ,KAAKrQ,KAAKkR;gBAC3Bb,KAAKG,KAAKM,KAAKD;;;;;;;;YAShBnO,OAAO,SAASa;gBACf,IAAIH,OAAO3G,MAAMY,KAAKwG,QAAQN,SAASL;gBACvCK,UAAU9G,MAAM+C,KAAK4I,UAAU7E;gBAC/B,IAAIH,MAAM;oBACT3G,MAAMY,KAAKgG,gBAAgBoP,IAAIC,MAAMnP,SAASH,KAAKmP,WAAW;;;;;;;;;YAUhExR,OAAO,SAASwC;gBACf9G,MAAMY,KAAKgG,gBAAgBqM,OAAOC;oBACjC5M,MAAM;oBACNE,MAAMxG,MAAMY,KAAK6F,UAAU0O;oBAC3BhC,IAAInT,MAAM+C,KAAK4I,UAAU7E;mBACvBsM,EAAE;oBAAUa,OAAOlT,QAAQgD,GAAGQ;mBAAa6P;;;;;;;;;;;;;;YAe/ClQ,SAAS,SAAS4C,SAASkM,KAAK1M,MAAM4P;;gBAErClD,MAAM9S,EAAEiW,KAAKnD;gBACb,IAAGA,QAAQ,IAAI;oBACd,OAAO;;gBAER,IAAI/N,OAAO;gBACX,IAAGqB,SAAS,QAAQ;oBACnBrB,OAAOlE,QAAQ6K,mBAAmB9E;oBAClCA,UAAU/F,QAAQqV,kBAAkBtP;;;gBAGrC9G,MAAMY,KAAKgG,gBAAgBoP,IAAIhO,QAAQlB,SAAS7B,MAAM+N,KAAKkD,UAAU5P;gBACrE,OAAO;;;;;;;;;;;YAYR+P,QAAQ,SAASvP,SAASwP,UAAUC,QAAQvR;gBAC3CuR,SAASrW,EAAEiW,KAAKI;gBAChB,IAAIvO,UAAUwO;oBAAMrD,IAAIrM;;gBACxB,IAAI2P,IAAIzO,QAAQoL,EAAE;oBAAMa,OAAOlT,QAAQgD,GAAG2S;;gBAC1CxW,EAAE4F,KAAKwQ,UAAU,SAAS5F,GAAGiG;oBAC5BA,UAAU5V,QAAQqV,kBAAkBO;oBACpCF,EAAErD,EAAE;wBAAWD,IAAIwD;;oBACnB,WAAWJ,WAAW,eAAeA,WAAW,IAAI;wBACnDE,EAAErD,EAAE,UAAUmD;;;gBAIhB,WAAWvR,aAAa,eAAeA,aAAa,IAAI;oBACvDyR,EAAErD,EAAE,YAAYpO;;gBAGjBhF,MAAMY,KAAKgG,gBAAgBmN,KAAK/L;;;;;;;;;;YAWjC4O,gBAAgB,SAASC;gBACxB7W,MAAMY,KAAK6F,UAAUqQ,6BAA6B,UAAUD;gBAC5D7W,MAAMY,KAAKoF,OAAOnC,OAAOQ,KAAK0S;;;;;YAM/BA,mBAAmB;gBAClB,IAAIC,cAAchX,MAAMY,KAAK6F,WAC5BgP,KAAKvC;oBAAK5M,MAAM;oBAAOE,MAAMwQ,YAAY7B;mBACvC/B,EAAE;oBAAUa,OAAO;mBAClBb,EAAE;oBAAShT,MAAM;oBACpB6W,cAAcD,YAAYE,eAAe;gBAC1C,IAAID,YAAYhL,SAAS,GAAG;oBAC3B/L,EAAE4F,KAAKmR,aAAa,SAASE,OAAOjV;wBACnCuT,GAAGrC,EAAE;4BAAS9M,MAAK;4BAAOxE,OAAO9B,MAAM+C,KAAK4I,UAAUzJ;4BAAMmT,QAAQ;4BAAQC,OAAQ6B;2BAClF/D,EAAE,WAAWoB,KAAKA;;uBAEf;oBACNiB,GAAGrC,EAAE;wBAASiC,QAAQ;wBAASC,OAAQ;;;gBAExCtV,MAAMY,KAAKgG,gBAAgBqM,OAAOwC,GAAGrB;;;;;YAMtCgD;;;;;;;;;;;;;gBAaCC,YAAY,SAASvQ,SAAS+P,SAASvQ,MAAMiQ;oBAC5CzP,UAAU9G,MAAM+C,KAAK4I,UAAU7E;oBAC/B+P,UAAU7W,MAAM+C,KAAK4I,UAAUkL;oBAC/B,IAAIS;wBAAWrS,MAAMlE,QAAQ6K,mBAAmBiL;;oBAChD,QAAOvQ;sBACN,KAAK;wBACJgR,QAAQC,OAAO;wBACf;;sBACD,KAAK;wBACJD,QAAQE,cAAc;wBACtB;;sBACD;wBACC,OAAO;;oBAETxX,MAAMY,KAAKgG,gBAAgBqM,OAAOC;wBACjC5M,MAAM;wBACNE,MAAMxG,MAAMY,KAAK6F,UAAU0O;wBAC3BhC,IAAIrM;uBACFsM,EAAE;wBAAUa,OAAOlT,QAAQgD,GAAG0T;uBAC/BrE,EAAE,QAAQkE,SAASlE,EAAE,UAAUkB,EAAEiC,QAAQnC;oBAC3C,OAAO;;;;;;;;;gBAURsD,YAAY,SAAS5Q,SAAS6Q;oBAC7B3X,MAAMY,KAAKgG,gBAAgBoP,IAAI4B,SAAS5X,MAAM+C,KAAK4I,UAAU7E,UAAU6Q;;;;;IAM3E,OAAO1X;EACND,MAAMY,KAAKoF,cAAcjF,SAASD;;;;;;;;;;;;;ACvZpC;;;;;;;;;AAUAd,MAAMY,KAAKiX,WAAW,SAAS/Q;;;;IAI9BZ,KAAK4R;QACJ5V,KAAK4E;QACL1G,MAAMW,QAAQqB,eAAe0E;;;;;IAM9BZ,KAAKS,OAAO;;;;IAKZT,KAAKiC,SAAS,IAAInI,MAAMY,KAAKmX;;;;;;;IAQ7B7R,KAAKQ,UAAU,SAASC;QACvBT,KAAKS,OAAOA;;;;;;;;IASbT,KAAKO,UAAU;QACd,OAAOP,KAAKS;;;;;;;;IASbT,KAAKC,SAAS;QACb,OAAOD,KAAK4R,KAAK5V;;;;;;;;IASlBgE,KAAK8R,UAAU,SAAS5X;QACvB8F,KAAK4R,KAAK1X,OAAOA;;;;;;;;IASlB8F,KAAK+R,UAAU;QACd,OAAO/R,KAAK4R,KAAK1X;;;;;;;;IASlB8F,KAAKgS,YAAY,SAAS/P;QACzBjC,KAAKiC,SAASA;;;;;;;;IASfjC,KAAKiS,YAAY;QAChB,OAAOjS,KAAKiC;;;;;;;;;;;;;;;AChGd;;;;;;AAOAnI,MAAMY,KAAKmX,aAAa;;;;IAIvB7R,KAAKkS;;;;;;;IAQLlS,KAAKmS,MAAM,SAAS1R;QACnBT,KAAKkS,MAAMzR,KAAKR,YAAYQ;;;;;;;;IAS7BT,KAAK4B,SAAS,SAAS5F;eACfgE,KAAKkS,MAAMlW;;;;;;;;;;;IAYnBgE,KAAKoG,MAAM,SAASpK;QACnB,OAAOgE,KAAKkS,MAAMlW;;;;;;;;IASnBgE,KAAKoS,SAAS;QACb,OAAOpS,KAAKkS;;;;;;;;;;;;;;;ACrDd;;;;;;AAOApY,MAAMY,KAAK2E,WAAW,SAASrD,KAAK+C,MAAMuS,aAAaD;;;;IAItDrR,KAAKqS,iBAAoB;;;;IAKzBrS,KAAKsS,oBAAoB;;;;;;;;;;IAWzBtS,KAAKqB;QACJrF,KAAKA;QACL+C,MAAMlE,QAAQ+K,aAAa7G;QAC3BuS,aAAaA;QACbD,MAAMA;QACNkB;QACAC;QACAC,cAAcnX;;;;;;;;;;;IAYf0E,KAAKC,SAAS;QACb,IAAGD,KAAKqB,KAAKrF,KAAK;YACjB,OAAOlC,MAAM+C,KAAK8I,YAAY3F,KAAKqB,KAAKrF;;QAEzC;;;;;;;;;;;IAYDgE,KAAKiP,gBAAgB;QACpB,OAAOnV,MAAM+C,KAAK4I,UAAUzF,KAAKqB,KAAKrF;;;;;;;;IASvCgE,KAAK0S,SAAS,SAAS1W;QACtBgE,KAAKqB,KAAKrF,MAAMA;;;;;;;;IASjBgE,KAAK4P,UAAU;QACd,OAAO/U,QAAQ+K,aAAa5F,KAAKqB,KAAKtC;;;;;;;;IASvCiB,KAAK2S,UAAU,SAAS5T;QACvBiB,KAAKqB,KAAKtC,OAAOA;;;;;;;;IASlBiB,KAAK4S,UAAU;QACd,OAAO5S,KAAKqB,KAAKgQ;;;;;;;;IASlBrR,KAAK6S,UAAU,SAASxB;QACvBrR,KAAKqB,KAAKgQ,OAAOA;;;;;;;;IASlBrR,KAAK8S,iBAAiB,SAASxB;QAC9BtR,KAAKqB,KAAKiQ,cAAcA;;;;;;;;IASzBtR,KAAK+S,iBAAiB;QACrB,OAAO/S,KAAKqB,KAAKiQ;;;;;;;;IASlBtR,KAAKgT,cAAc;QAClB,OAAOhT,KAAK4S,cAAc5S,KAAKqS,kBAAkBrS,KAAK+S,qBAAqB/S,KAAKsS;;;;;;;;;;;;;;IAejFtS,KAAK4Q,+BAA+B,SAASqC,MAAMjX;QAClD,KAAKgE,KAAKqB,KAAKkR,aAAaU,OAAO;YAClCjT,KAAKqB,KAAKkR,aAAaU;;QAExB,IAAIhC,SAAS;QACb,KAAKA,QAAQjR,KAAKqB,KAAKkR,aAAaU,MAAM9T,QAAQnD,WAAW,GAAG;YAC/DgE,KAAKqB,KAAKkR,aAAaU,MAAMC,OAAOjC,OAAO;eACrC;YACNjR,KAAKqB,KAAKkR,aAAaU,MAAMhH,KAAKjQ;;QAEnC,OAAOgE,KAAKqB,KAAKkR,aAAaU;;;;;;;;;;;IAY/BjT,KAAKgR,iBAAiB,SAASiC;QAC9B,KAAKjT,KAAKqB,KAAKkR,aAAaU,OAAO;YAClCjT,KAAKqB,KAAKkR,aAAaU;;QAExB,OAAOjT,KAAKqB,KAAKkR,aAAaU;;;;;;;;IAS/BjT,KAAKmT,kBAAkB,SAASC;QAC/BpT,KAAKqB,KAAKkR,eAAea;;;;;;;;;;;;IAa1BpT,KAAKqT,kBAAkB,SAASJ,MAAMjX;QACrC,KAAKgE,KAAKqB,KAAKkR,aAAaU,OAAO;YAClC,OAAO;;QAER,OAAOjT,KAAKqB,KAAKkR,aAAaU,MAAM9T,QAAQnD,UAAU;;;;;;;;IASvDgE,KAAKsT,gBAAgB,SAASjS;QAC7BrB,KAAKqB,KAAKmR,aAAanR;;;;;;;;IASxBrB,KAAKuT,gBAAgB;QACpB,OAAOvT,KAAKqB,KAAKmR;;;;;;;;IASlBxS,KAAKwT,kBAAkB,SAASf;QAC/BzS,KAAKqB,KAAKoR,eAAeA;;;;;;;;IAS1BzS,KAAKyT,kBAAkB;QACtB,OAAOzT,KAAKqB,KAAKoR;;;;;;;;;;;;;;;AC3PnB;;;;;;;;;;;AAYA3Y,MAAMY,KAAKgD,QAAS,SAAS3D,MAAMc,SAASb;;;;;;;;;;IAU3CD,KAAKuF,QAAQ,SAASoU;;;;;;;QAOrB1Z,EAAEF,OAAOmF,eAAe;YAAsByU,WAAWA;;;;;;IAM1D3Z,KAAKc;;;;;;;;;;QAUJuE,SAAS,SAAS2B;YACjBjH,MAAMY,KAAKoG,iBAAiBC;YAC5B,QAAOA;cACN,KAAKlG,QAAQ8Y,OAAOC;gBACnB9Z,MAAMY,KAAK+B,IAAI;gBACf3C,MAAMY,KAAKoF,OAAOnC,OAAOgS;;;gBAE1B,KAAK9U,QAAQ8Y,OAAOE;gBACnB/Z,MAAMY,KAAK+B,IAAI;gBACf3C,MAAMY,KAAKoF,OAAOnC,OAAOI;gBACzBjE,MAAMY,KAAKoF,OAAOnC,OAAO8Q;gBACzB3U,MAAMY,KAAKoF,OAAOnC,OAAO2R;gBACzB;;cAED,KAAKzU,QAAQ8Y,OAAOG;gBACnBha,MAAMY,KAAK+B,IAAI;gBACf;;cAED,KAAK5B,QAAQ8Y,OAAOI;gBACnBja,MAAMY,KAAK+B,IAAI;gBACf;;cAED,KAAK5B,QAAQ8Y,OAAOK;gBACnBla,MAAMY,KAAK+B,IAAI;gBACf;;cAED,KAAK5B,QAAQ8Y,OAAOM;gBACnBna,MAAMY,KAAK+B,IAAI;gBACf;;cAED,KAAK5B,QAAQ8Y,OAAOO;gBACnBpa,MAAMY,KAAK+B,IAAI;gBACf;;cAED,KAAK5B,QAAQ8Y,OAAOQ;cACpB,KAAKtZ,QAAQ8Y,OAAOS;gBACnBta,MAAMY,KAAK+B,IAAI,0BAA0BsE,SAAS;gBAClD;;cAED;gBACCjH,MAAMY,KAAK+B,IAAI;gBACf;;;;;;;;YAQFzC,EAAEF,OAAOmF,eAAe;gBAAgC8B,QAAQA;;;;;;;IAOlEhH,KAAK4D;;;;;;;;;;QAUJC,SAAS,SAASkP;YACjBhT,MAAMY,KAAK+B,IAAI;YACf3C,MAAMY,KAAKoF,OAAOnC,OAAOC,QAAQ5D,EAAE8S;YACnC,OAAO;;;;;;;;;;;;;;QAeR/O,UAAU,SAAS+O;YAClBhT,MAAMY,KAAK+B,IAAI;YACfqQ,MAAM9S,EAAE8S;YACR,IAAGA,IAAIuH,SAAS,eAAexZ,QAAQgD,GAAGgS,MAAM,MAAM9J,SAAS,GAAG;gBACjE,IAAI+G,IAAI5B,KAAK,YAAY,SAAS;oBACjCnR,KAAK4D,OAAOQ,KAAK0E,cAAciK;uBACzB;oBACN/S,KAAK4D,OAAOQ,KAAKJ,SAAS+O;;mBAErB;;;;;;;;gBAQN9S,EAAEF,OAAOmF,eAAe;oBAAwBqB,MAAQwM,IAAI5B,KAAK;oBAASoJ,QAAUxH;;;YAErF,OAAO;;;;;;;;;;;QAYR7O,WAAW,SAAS6O;YACnBhT,MAAMY,KAAK+B,IAAI;;YAEfzC,EAAE,cAAc8S,KAAKlN,KAAK;gBACzB,IAAI2U,OAAOva,EAAEgG;gBACb,IAAGuU,KAAKrJ,KAAK,aAAa;oBACzBpR,MAAMY,KAAKoF,OAAOnC,OAAOQ,KAAK2Q,KAAKyF,KAAKrJ,KAAK;;;YAG/C,OAAO;;;;;;;;;;;;;QAcRuE,aAAa,SAAS3C;YACrBhT,MAAMY,KAAK+B,IAAI;YACf,IAAIqU,cAAchX,MAAMY,KAAK6F;YAC7BuM,MAAM9S,EAAE8S;YACR,IAAGA,IAAI5B,KAAK,YAAY,UAAU;gBACjClR,EAAE,4BAA4B8S,KAAKlN,KAAK;oBACvC,IAAI2U,OAAOva,EAAEgG;oBACb,IAAIuU,KAAKrJ,KAAK,cAAc,QAAQ;wBACnC4F,YAAYF,6BAA6B,UAAU2D,KAAKrJ,KAAK;;;gBAG/DpR,MAAMY,KAAKoF,OAAOnC,OAAO+R;gBACzB,OAAO;;YAER,OAAO3V,KAAK4D,OAAO6W,iBAAiB1H;;;;;;;;;;;;;QAcrC0H,kBAAkB,SAAS1H;YAC1BhT,MAAMY,KAAK+B,IAAI;;YAEf,IAAIzC,EAAE,mDAAmD8S,MAAM;gBAC9DhT,MAAMY,KAAKoF,OAAOnC,OAAOqR;gBACzBlV,MAAMY,KAAKoF,OAAOnC,OAAO+R;;YAE1B,OAAO;;;;;;;;;;;;;;;QAgBR1R,SAAS,SAAS8O;YACjBhT,MAAMY,KAAK+B,IAAI;YACfqQ,MAAM9S,EAAE8S;YAER,IAAI2H,UAAU3H,IAAI5B,KAAK,SACtB9K,OAAO0M,IAAI5B,KAAK,WAAW,aAC3BwJ,QAAQ5H,IAAI5B,KAAK;;YAGlB,IAAI9K,SAAS,YAAYA,SAAS,aAAa;gBAC9C,IAAIuU,iBAAiB7H,IAAI8H,KAAK,WAC7BC,eAAe/H,IAAI8H,KAAK;gBAEzB,IAAGD,eAAe5O,SAAS,GAAG;oBAC7B,IAAI+O,eAAehI,IAAI8H,KAAK,aAC3B9V,WAAW,MACXiW,eAAeJ,eAAeC,KAAK,aACnCI,kBAAkB;oBAEnB,IAAGF,cAAc;wBAChBhW,WAAWgW,aAAavK;;oBAGzB,IAAGwK,cAAc;wBAChBC,kBAAkBD,aAAa7J,KAAK;;;;;;;;;;;;oBAarClR,EAAEF,OAAOmF,eAAe;wBACvB2B,SAAS6T;wBACTnU,MAAMqU,eAAezJ,KAAK,WAAW;wBACrCmF,QAAQsE,eAAeC,KAAK,UAAUzQ,UAAU;wBAChDrF,UAAUA;wBACVkW,iBAAiBA;;;gBAInB,IAAGH,aAAa9O,SAAS,GAAG;;;;;;;;;;;oBAW3B/L,EAAEF,OAAOmF,eAAe;wBACvB2B,SAASiU,aAAa3J,KAAK;wBAC3B5K,MAAMmU;wBACNpE,QAAQwE,aAAa3J,KAAK,aAAa;wBACvCpM,UAAU+V,aAAa3J,KAAK;wBAC5B8J,iBAAiBH,aAAa3J,KAAK;;;;;;;;;;;;gBAarClR,EAAEF,OAAOmF,eAAe;oBACvBmB,MAAOA,QAAQ;oBACf0B,SAASgL;;gBAGV,OAAO;mBACD,IAAI1M,SAAS,eAAeA,SAAS,UAAUA,SAAS,WAAWA,SAAS,YAAY;;;;;;;;;;;gBAW9FpG,EAAEF,OAAOmF,eAAe;oBACvBmB,MAAMA;oBACN0B,SAASgL;;gBAEV,OAAO;;;YAIR,IAAG2H,YAAY5Z,QAAQuB,iBAAiBqY,aAAarU,SAAS,eAAeA,SAAS,UAAUA,SAAS,UAAU;gBAClHrG,KAAK4D,OAAOQ,KAAKH,QAAQ8O;mBAEnB,KAAI4H,SAASD,YAAY5Z,QAAQuB,iBAAiBqY,UAAU;;;;;;;;gBAQlEza,EAAEF,OAAOmF,eAAe;oBAAmCmB,MAAOA,QAAQ;oBAAY0B,SAASgL,IAAIuH,SAAS,QAAQ9J;;mBAE9G,IAAGmK,SAASD,YAAY5Z,QAAQuB,iBAAiBqY,UAAU;;;;;;;;;gBASjEza,EAAEF,OAAOmF,eAAe;oBACvBmB,MAAOA,QAAQ;oBACfqR,SAAS3E,IAAIuH,SAAS,WAAW9J;oBACjCzI,SAASgL,IAAIuH,SAAS,QAAQ9J;;;YAGhC,OAAO;;;;;QAMRpM;;;;;;;;;;;;;YAaC4B,OAAO,SAAS+M;gBACfhT,MAAMY,KAAK+B,IAAI;gBACfqQ,MAAM9S,EAAE8S;gBACR,IAAIxM,OAAOxG,MAAM+C,KAAK8I,YAAYmH,IAAI5B,KAAK,UAC1CtK,UAAU/F,QAAQqV,kBAAkB5P;;gBAGrC,KAAKxG,MAAMY,KAAKwG,QAAQN,UAAU;oBACjC,OAAO;;gBAGR,IAAIqU,WAAWnb,MAAMY,KAAKwG,QAAQN,SAASmR,WAC1CwC,OAAOzH,IAAI8H,KAAK,SAChBxU,OAAO,SACPiQ,QACA6E;uBAEMpb,MAAMY,KAAKmF,WAAWe;;gBAE7B,IAAG2T,KAAKrJ,KAAK,YAAY,QAAQ;oBAChC,IAAIiK,OAAOrI,IAAI8H,KAAK,UAAU1J,KAAK;oBACnC,IAAGiK,SAAS,OAAO;wBAClB/U,OAAO;2BACD,IAAG+U,SAAS,OAAO;wBACzB/U,OAAO;;oBAERiQ,SAASkE,KAAKK,KAAK,UAAUrK;oBAC7B2K,QAASX,KAAKK,KAAK,SAAS1J,KAAK;;gBAGlC,IAAIzK,OAAO,IAAI3G,MAAMY,KAAK2E,SAASiB,MAAMzF,QAAQ6K,mBAAmBpF,OAAOiU,KAAKrJ,KAAK,gBAAgBqJ,KAAKrJ,KAAK;;;;;;;;;;;;;;gBAe/GlR,EAAEF,OAAOmF,eAAe;oBACvB2B,SAAWA;oBACXqU,UAAYA;oBACZ7U,MAAQA;oBACRiQ,QAAUA;oBACV6E,OAASA;oBACTzU,MAAQA;;gBAET,OAAO;;;;;;;;;;;YAYRrC,OAAO,SAAS0O;gBACfhT,MAAMY,KAAK+B,IAAI;gBACfqQ,MAAM9S,EAAE8S;;;;gBAIR,KAAIA,IAAI8H,KAAK,mCAAmC7O,QAAQ;oBACvD,OAAO;;gBAER,IAAInF,UAAU/F,QAAQqV,kBAAkBpW,MAAM+C,KAAK8I,YAAYmH,IAAI5B,KAAK;;gBAGxE,KAAIpR,MAAMY,KAAKmF,WAAWe,UAAU;oBACnC9G,MAAMY,KAAKmF,WAAWe,WAAW,IAAI9G,MAAMY,KAAKiX,SAAS/Q;;;gBAG1D,IAAIwU,WAAWtI,IAAI8H,KAAK;gBACxB,IAAGQ,SAASrP,QAAQ;oBACnB,IAAIkP,WAAWG,SAASlK,KAAK,SAC5B0G,OAAO9X,MAAMY,KAAKwG,QAAQN;oBAC3B,IAAGgR,KAAKG,cAAc,MAAM;wBAC3BH,KAAKE,QAAQjX,QAAQ+K,aAAaqP;;;gBAMpC,OAAO;;;;;;;;;;;;;;YAeRlX,UAAU,SAAS+O;gBAClBhT,MAAMY,KAAK+B,IAAI;gBACf,IAAI6D,OAAOxG,MAAM+C,KAAK8I,YAAYmH,IAAI5B,KAAK,UAC1CtK,UAAU/F,QAAQqV,kBAAkB5P,OACpC+U,eAAevI,IAAI5B,KAAK,SACxBnK,SAAS+L,IAAI8H,KAAK,WAClBU,aAAa,OACbC,aAAa;gBAEd,IAAGxU,OAAOgF,QAAQ;;oBAEjB,KAAI,IAAIyE,IAAI,GAAGgL,IAAIzU,OAAOgF,QAAQyE,IAAIgL,GAAGhL,KAAK;wBAC7C,IAAIiL,UAAUzb,EAAE+G,OAAOyJ,KACtB2K,OAAOM,QAAQvK,KAAK;wBACrB,IAAGiK,SAAS,OAAO;4BAClBI,aAAa;+BACP,IAAGJ,SAAS,OAAO;4BACzBG,aAAa;;;;;gBAMhB,IAAI1D,OAAO9X,MAAMY,KAAKwG,QAAQN;gBAC9B,KAAIgR,MAAM;oBACT9X,MAAMY,KAAKmF,WAAWe,WAAW,IAAI9G,MAAMY,KAAKiX,SAAS/Q;oBACzDgR,OAAO9X,MAAMY,KAAKwG,QAAQN;;;gBAI3B,IAAIkQ,cAAcc,KAAKrR,YAAYqR,KAAKrR,YAAYzG,MAAMY,KAAK6F;gBAC/D,IAAG1F,QAAQ6K,mBAAmBpF,UAAUwQ,YAAYlB,aAAayF,iBAAiB,iBAAiBE,eAAe,OAAO;oBACxHxb,KAAK4D,OAAOQ,KAAK4B,MAAM+M;oBACvB,OAAO;;gBAGR,IAAI7K,SAAS2P,KAAKK,aACjB9C,QAAQ1O,MACR1B,MACAwV,OAAOzH,IAAI8H,KAAK;;gBAEjB,IAAGS,iBAAiB,eAAe;oBAClC,IAAIpT,OAAOmE,IAAI9F,OAAO;;wBAErBG,OAAOwB,OAAOmE,IAAI9F;wBAElB,IAAI+Q,OAAOkD,KAAKrJ,KAAK,SACpBoG,cAAciD,KAAKrJ,KAAK;wBAEzBzK,KAAKoS,QAAQxB;wBACb5Q,KAAKqS,eAAexB;;wBAGpBnC,SAAS;2BACH;wBACNpQ,OAAOlE,QAAQ6K,mBAAmBpF;wBAClCG,OAAO,IAAI3G,MAAMY,KAAK2E,SAASiB,MAAMvB,MAAMwV,KAAKrJ,KAAK,gBAAgBqJ,KAAKrJ,KAAK;;wBAE/E,IAAG0G,KAAKrR,cAAc,SAASzG,MAAMY,KAAK6F,UAAUqP,cAAc7Q,QAAQuW,aAAa;4BACtF1D,KAAKpR,QAAQC;4BACbqQ,cAAcrQ;;wBAEfwB,OAAOkQ,IAAI1R;wBACX0O,SAAS;;uBAGJ;oBACN1O,OAAOwB,OAAOmE,IAAI9F;oBAClB2B,OAAOL,OAAOtB;oBACd,IAAGiV,YAAY;;wBAEdxW,OAAOwV,KAAKrJ,KAAK;wBACjBiE,SAAS;wBACT1O,KAAK+S,gBAAgB/S,KAAKmP;wBAC1BnP,KAAKkS,QAAQ5T;wBACb0B,KAAKiS,OAAO7X,QAAQqV,kBAAkB5P,QAAQ,MAAMvB;wBACpDkD,OAAOkQ,IAAI1R;2BACL;wBACN0O,SAAS;wBACT,IAAGoF,KAAKrJ,KAAK,YAAY,QAAQ;4BAChC,IAAG4B,IAAI8H,KAAK,UAAU1J,KAAK,YAAY,OAAO;gCAC7CiE,SAAS;mCACH,IAAGrC,IAAI8H,KAAK,UAAU1J,KAAK,YAAY,OAAO;gCACpDiE,SAAS;;;;;;;;;;;;;;;gBAebnV,EAAEF,OAAOmF,eAAe;oBACvB2B,SAAWA;oBACXqU,UAAYrD,KAAKG;oBACjBtR,MAAQA;oBACR0O,QAAUA;oBACV2B,aAAeA;;gBAEhB,OAAO;;;;;;;;;;;;;;YAeRjO,eAAe,SAASiK;gBACvBhT,MAAMY,KAAK+B,IAAI;gBACf,IAAI6D,OAAOxG,MAAM+C,KAAK8I,YAAYmH,IAAI5B,KAAK,UAC1CtK,UAAU/F,QAAQqV,kBAAkB5P,OACpCsR,OAAO9X,MAAMY,KAAKmF,WAAWe,UAC7BqU,WAAWrD,KAAKG;;gBAGjBjY,MAAMY,KAAKiG,WAAWC;gBACtBgR,OAAOtW;;;;;;;;;;gBAWPtB,EAAEF,OAAOmF,eAAe;oBACvB6N,KAAQA;oBACR1M,MAAQ0M,IAAIuH,SAAS,SAASA,WAAW,GAAGqB,QAAQjK;oBACpD7K,SAAWA;oBACXqU,UAAYA;;gBAEb,OAAO;;;;;;;;;;;;;;;YAgBRjX,SAAS,SAAS8O;gBACjBhT,MAAMY,KAAK+B,IAAI;;gBAEf,IAAImE,SAASkB,SAAS5H;gBACtB,IAAG4S,IAAIuH,SAAS,WAAWtO,SAAS,KAAK+G,IAAIuH,SAAS,WAAW9J,OAAOxE,SAAS,KAAK+G,IAAI5B,KAAK,YAAY,aAAa;oBACvHtK,UAAU9G,MAAM+C,KAAK8I,YAAY9K,QAAQqV,kBAAkBpD,IAAI5B,KAAK;oBACpEpJ;wBAAY5H,MAAMW,QAAQqB,eAAe0E;wBAAUoB,MAAM8K,IAAIuH,SAAS,WAAW9J;wBAAQnK,MAAM;;uBAEzF,IAAG0M,IAAI5B,KAAK,YAAY,SAAS;oBACvC,IAAIyK,QAAQ7I,IAAIuH,SAAS;oBACzB,IAAGsB,MAAMtB,SAAS,QAAQtO,SAAS,GAAG;wBACrCnF,UAAUkM,IAAI5B,KAAK;wBACnBpJ;4BAAY1B,MAAM;4BAAQ4B,MAAM2T,MAAMtB,SAAS,QAAQ9J;;;uBAGlD,IAAGuC,IAAIuH,SAAS,QAAQtO,SAAS,GAAG;;oBAE1C,IAAG+G,IAAI5B,KAAK,YAAY,UAAU4B,IAAI5B,KAAK,YAAY,UAAU;wBAChEtK,UAAU9G,MAAM+C,KAAK8I,YAAYmH,IAAI5B,KAAK;wBAC1C,IAAI0K,cAAc/a,QAAQqV,kBAAkBtP;wBAE3CiV,yBAAyB/b,MAAMY,KAAKwG,QAAQ0U;wBAE7C1b,OAAO2b,wBAAwBhb,QAAQqB,eAAe0E,WAAW/F,QAAQ6K,mBAAmB9E;wBAC5FkB;4BAAY5H,MAAMA;4BAAM8H,MAAM8K,IAAIuH,SAAS,QAAQ9J;4BAAQnK,MAAM0M,IAAI5B,KAAK;4BAAS2K,uBAAuBA;;2BAEpG;wBACNjV,UAAU9G,MAAM+C,KAAK8I,YAAY9K,QAAQqV,kBAAkBpD,IAAI5B,KAAK;wBACpE,IAAIxP,WAAWb,QAAQ6K,mBAAmBoH,IAAI5B,KAAK;;wBAEnD,IAAGxP,UAAU;4BACZA,WAAWb,QAAQ+K,aAAalK;4BAChCoG;gCAAY5H,MAAMwB;gCAAUsG,MAAM8K,IAAIuH,SAAS,QAAQ9J;gCAAQnK,MAAM0M,IAAI5B,KAAK;;+BAExE;;4BAEN,KAAIpR,MAAMU,KAAKyI,KAAKP,KAAKuC,MAAM6H,IAAI5B,KAAK,UAAU;gCACjD,OAAO;;4BAERpJ;gCAAY5H,MAAM;gCAAI8H,MAAM8K,IAAIuH,SAAS,QAAQ9J;gCAAQnK,MAAM;;;;oBAIjE,IAAI0V,aAAahJ,IAAIuH,SAAS,iBAAiBxZ,QAAQgD,GAAGkY,WAAW;oBACrE,IAAGjc,MAAMU,KAAKyG,aAAaiB,gBAAgB,QAAQ4T,WAAW/P,SAAS,GAAG;wBACzE,IAAIiQ,eAAeF,WAAWzB,SAAS,iBAAiBxZ,QAAQgD,GAAG6N,QAAQ,MAAMuK,QAAQ9R;wBACzFrC,QAAQkU,eAAeA;;uBAGlB,IAAGlJ,IAAIuH,SAAS,aAAatO,SAAS,KAAK+G,IAAIuH,SAAS,YAAYtO,SAAS,KAAK+G,IAAIuH,SAAS,UAAUtO,SAAS,GAAG;oBAC3HnF,UAAU9G,MAAM+C,KAAK8I,YAAYmH,IAAI5B,KAAK;oBAC1ChR,OAAOW,QAAQ6K,mBAAmB9E;oBAClC,IAAIsV;oBACJ,IAAGpJ,IAAIuH,SAAS,aAAatO,SAAS,GAAG;wBACxCmQ,YAAY;2BACN,IAAGpJ,IAAIuH,SAAS,UAAUtO,SAAS,GAAG;wBAC5CmQ,YAAY;2BACN,IAAGpJ,IAAIuH,SAAS,YAAYtO,SAAS,GAAG;wBAC9CmQ,YAAY;2BACN,IAAGpJ,IAAIuH,SAAS,QAAQtO,SAAS,GAAG;wBAC1CmQ,YAAY;;;;;;;;;;;;;;;;;oBAiBblc,EAAEF,OAAOmF,eAAe;wBACvB/E,MAAMA;wBACN0G,SAASA;wBACTsV,WAAWA;;oBAEZ,OAAO;uBAED;oBACN,OAAO;;;;gBAKR,IAAIC,QAAQrJ,IAAIuH,SAAS,WAAWvH,IAAIuH,SAAS,WAAWvH,IAAIuH,SAAS,cAAcxZ,QAAQgD,GAAGuY,QAAO,OACxG1N,YAAYyN,UAAU7a,YAAY6a,MAAMjL,KAAK,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gBAiCzDlR,EAAEF,OAAOmF,eAAe;oBACvB2B,SAASA;oBACTkB,SAASA;oBACT4G,WAAWA;;gBAEZ,OAAO;;;;IAKV,OAAO3O;EACND,MAAMY,KAAKgD,aAAa7C,SAASD;;;;;;;;;;;;;ACjxBnC;;;;;;;;;;AAWAd,MAAMU,KAAKiI,WAAY,SAAS1I,MAAMC;;;;;IAKrC,IAAIqc,6BAA6B;;;;IAKjCtc,KAAK2I;;;;;;;;;;QAUJxF,YAAY,SAASoZ,OAAOC;YAC3B,IAAIC,YAAY,kCAAkCD,KAAKxV;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;YAqCvD,IAAG/G,EAAEF,OAAOmF,eAAeuX,eAAe,OAAO;gBAChD,OAAO;;YAGR,QAAOD,KAAKxV;cACX,KAAKlG,QAAQ8Y,OAAOK;cACpB,KAAKnZ,QAAQ8Y,OAAOO;gBACnBpa,MAAMU,KAAKyI,KAAKP,KAAK+T,MAAM1S,KAAK/J,EAAEoI,KAAKqC,EAAE,qBAAqB,OAAO;gBACrE;;cACD,KAAK5J,QAAQ8Y,OAAOE;cACpB,KAAKhZ,QAAQ8Y,OAAOC;gBACnB,IAAGyC,+BAA+B,MAAM;;;oBAGvCvc,MAAMU,KAAKyI,KAAKP,KAAK+T,MAAM1S,KAAK/J,EAAEoI,KAAKqC,EAAE;oBACzC3K,MAAMU,KAAKyI,KAAKP,KAAK+T,MAAMC;;gBAE5B;;cAED,KAAK7b,QAAQ8Y,OAAOM;gBACnBna,MAAMU,KAAKyI,KAAKP,KAAK+T,MAAM1S,KAAK/J,EAAEoI,KAAKqC,EAAE,wBAAwB,OAAO;gBACxE;;cAED,KAAK5J,QAAQ8Y,OAAOG;gBACnB,IAAIJ,YAAY5Z,MAAMY,KAAKsG,0BAA0BnG,QAAQuB,iBAAiBtC,MAAMY,KAAK6F,UAAUN,YAAY;gBAC/GnG,MAAMU,KAAKyI,KAAKP,KAAK+T,MAAME,cAAc3c,EAAEoI,KAAKqC,EAAE,uBAAuBiP;gBACzE;;cAED,KAAK7Y,QAAQ8Y,OAAOI;gBACnBja,MAAMU,KAAKyI,KAAKP,KAAK+T,MAAME,cAAc3c,EAAEoI,KAAKqC,EAAE;gBAClD;;cAED;gBACC3K,MAAMU,KAAKyI,KAAKP,KAAK+T,MAAM1S,KAAK/J,EAAEoI,KAAKqC,EAAE,UAAU8R,KAAKxV;gBACxD;;;;;;;;;;QAWH/C,SAAS,SAASsY,OAAOC;YACxB,IAAGA,KAAKnW,SAAS,WAAW;gBAC3BtG,MAAMU,KAAKyI,KAAKP,KAAKkU,aAAcL,KAAK9E,WAAW,IAAK8E,KAAKzU;mBACvD,IAAGyU,KAAKnW,SAAS,UAAUmW,KAAKnW,SAAS,aAAa;;gBAE5DtG,MAAMU,KAAKyI,KAAKP,KAAKmU,cAAc/c,MAAMU,KAAK6K,aAAazE,SAAU2V,KAAK9E,WAAW,IAAK8E,KAAKzU;;;;;;;IAQlG/H,KAAKgE;;;;;;;;;;;QAWJ6E,QAAQ,SAAS0T,OAAOC;;YAEvB,IAAGA,KAAKnW,SAAS,SAAS;gBACzB,IAAIK,OAAO3G,MAAMU,KAAKyI,KAAK9E,KAAKoC,QAAQgW,KAAK3V;gBAC7C9G,MAAMU,KAAKyI,KAAK9E,KAAK2Y,MAAMP,KAAK3V;gBAChC7G,KAAKgE,SAASgZ,mBAAmBtW,MAAM8V,KAAKnW;mBAEtC,IAAImW,KAAKnW,SAAS,UAAUmW,KAAKnW,SAAS,OAAO;gBACvD,IAAI4W,YAAYT,KAAKrB,QAAQra,QAAQqB,eAAeqa,KAAKrB,SAAS,MACjE+B,aACAC,sBAAqBX,KAAKtB;gBAE3B,IAAI+B,WAAW;oBACdE,kBAAkBjL,KAAK+K;;gBAGxB,QAAOT,KAAKnW;kBACX,KAAK;oBACJ6W,cAAcjd,EAAEoI,KAAKqC,EAAGuS,YAAY,wBAAwB,qBAAsBE;oBAClF;;kBACD,KAAK;oBACJD,cAAcjd,EAAEoI,KAAKqC,EAAGuS,YAAY,wBAAwB,qBAAsBE;oBAClF;;gBAEFpd,MAAMU,KAAKyI,KAAKP,KAAK+T,MAAM1S,KAAKK,SAASC,QAAQvK,MAAMU,KAAK8J,SAAS5B,KAAKyU,QAAQC;oBACjF/G,QAAQkG,KAAKlG;oBACbgH,SAASJ;oBACTK,SAAStd,EAAEoI,KAAKqC,EAAE,eAAc8R,KAAKlG;;gBAEtC7G,WAAW;oBACV1P,MAAMU,KAAKyI,KAAKP,KAAK+T,MAAMC,KAAK;wBAC/B5c,MAAMU,KAAKyI,KAAK9E,KAAK2Y,MAAMP,KAAK3V;wBAChC7G,KAAKgE,SAASgZ,mBAAmBR,KAAK9V,MAAM8V,KAAKnW;;mBAEhD;gBAEH,IAAImX;oBAAYnX,MAAMmW,KAAKnW;oBAAMiQ,QAAQkG,KAAKlG;oBAAQzP,SAAS2V,KAAK3V;oBAASH,MAAM8V,KAAK9V;;;;;;;;;;;gBAWxFzG,EAAEF,OAAOmF,eAAe,yBAAwBsY;mBAG1C,IAAGhB,KAAK3V,SAAS;gBACvB2V,KAAK3V,UAAU9G,MAAM+C,KAAK8I,YAAY4Q,KAAK3V;;gBAE3C,KAAI9G,MAAMU,KAAKyI,KAAKP,KAAKuC,MAAMsR,KAAK3V,UAAU;oBAC7C,IAAG9G,MAAMU,KAAKyI,KAAK9E,KAAK/D,KAAKmc,KAAK3V,SAAS2V,KAAKtB,cAAc,OAAO;wBACpE,OAAO;;oBAGRnb,MAAMU,KAAKyI,KAAK9E,KAAK4F,KAAKwS,KAAK3V;;gBAEhC9G,MAAMU,KAAKyI,KAAK6K,OAAOlL,OAAO2T,KAAK3V,SAAS2V,KAAK9V,MAAM8V,KAAKpH,QAAQoH,KAAKzF;;;;gBAIzE,IAAGhX,MAAMU,KAAKyI,KAAKP,KAAKuC,MAAMsR,KAAK9V,KAAKR,aAAasW,KAAKpH,WAAW,cAAc;oBAClFrV,MAAMU,KAAKyI,KAAK6K,OAAOlL,OAAO2T,KAAK9V,KAAKR,UAAUsW,KAAK9V,MAAM8V,KAAKpH,QAAQoH,KAAKzF;oBAC/EhX,MAAMU,KAAKyI,KAAKuU,YAAYC,UAAUlB,KAAK9V,KAAKR,UAAUsW,KAAKpH;;;;;;;;;;;QAYlE4H,oBAAoB,SAAStW,MAAML;YAClCtG,MAAMY,KAAK+B,IAAI;YACf,IAAImE;YACJ,KAAIA,WAAW9G,MAAMU,KAAKyI,KAAKP,KAAKuC,OAAO;gBAC1C,IAAGnL,MAAMU,KAAKyI,KAAKP,KAAKuC,MAAMmE,eAAexI,YAAY9G,MAAMU,KAAKyI,KAAK9E,KAAKoC,QAAQK,YAAYH,KAAKR,aAAanG,MAAMU,KAAKyI,KAAK9E,KAAKoC,QAAQK,SAASX,UAAU;oBACnKnG,MAAMU,KAAKyI,KAAK6K,OAAOlL,OAAOhC,SAASH,MAAML,MAAMK;oBACnD3G,MAAMU,KAAKyI,KAAKuU,YAAYC,UAAU7W,SAASR;;;;;;;;;;;;IAanDrG,KAAK8I,gBAAgB,SAASqG,KAAKqN;QAClC,QAAOA,KAAKnW;UACX,KAAK;YACJ,IAAI0B;YACJ,IAAIyU,KAAKzJ,IAAIuH,SAAS,KAAKA,SAAS,YAAYtO,SAAS,GAAG;gBAC3DjE,UAAU9H,EAAEoI,KAAKqC,EAAE,4BAA2B8R,KAAKtB;;YAEpDnb,MAAMU,KAAKyI,KAAKP,KAAK+T,MAAMiB,sBAAsBnB,KAAK3V,SAAS2V,KAAKtB,UAAUnT;YAC9E;;UACD,KAAK;YACJhI,MAAMU,KAAKyI,KAAKP,KAAK+T,MAAMkB,yBAAyBpB,KAAK3V;YACzD;;UACD,KAAK;YACJ9G,MAAMU,KAAKyI,KAAKP,KAAK+T,MAAMmB,UAAU,sBAAqBrB,KAAKtB;YAC/D;;UACD,KAAK;YACJnb,MAAMU,KAAKyI,KAAKP,KAAK+T,MAAMmB,UAAU,8BAA6BrB,KAAKtB;YACvE;;;;;;;;;;IAWHlb,KAAKiE,UAAU,SAASsY,OAAOC;QAC9B,IAAGA,KAAKzU,QAAQ1B,SAAS,WAAW;YACnC,KAAKtG,MAAMU,KAAKyI,KAAKP,KAAKuC,MAAMsR,KAAK3V,UAAU;gBAC9C9G,MAAMU,KAAKyI,KAAK9E,KAAK/D,KAAKmc,KAAK3V,SAAS2V,KAAKzU,QAAQ5H;gBACrDJ,MAAMU,KAAKyI,KAAK9E,KAAK4F,KAAKwS,KAAK3V;;YAEhC9G,MAAMU,KAAKyI,KAAK9E,KAAK0Z,WAAWtB,KAAK3V,SAAS2V,KAAKzU,QAAQE;eACrD,IAAGuU,KAAKzU,QAAQ1B,SAAS,QAAQ;YACvCtG,MAAMU,KAAKyI,KAAKP,KAAKoV,YAAYvB,KAAK3V,SAAS2V,KAAKzU,QAAQE;eACtD;;YAEN,IAAGuU,KAAKzU,QAAQ1B,SAAS,WAAWtG,MAAMU,KAAKyI,KAAKP,KAAKuC,MAAMsR,KAAK3V,UAAU;gBAC7E9G,MAAMU,KAAKyI,KAAKuU,YAAYO,KAAKxB,KAAK3V,SAAS2V,KAAKzU,QAAQ5H,MAAM,OAAOqc,KAAKzU,QAAQ+T;;YAEvF/b,MAAMU,KAAKyI,KAAKjF,QAAQ+F,KAAKwS,KAAK3V,SAAS2V,KAAKzU,QAAQ5H,MAAMqc,KAAKzU,QAAQE,MAAMuU,KAAKzU,QAAQkU,cAAcO,KAAK7N;;;;;;;;;;IAWnH3O,KAAKuF,QAAQ,SAASgX,OAAOC;QAC5Bzc,MAAMU,KAAKyI,KAAKP,KAAK+T,MAAME,cAAc,MAAMJ,KAAK7C;;;;;IAMrD3Z,KAAK4I,kBAAkB;QACtB0T,6BAA6B;QAC7Bvc,MAAMU,KAAKyI,KAAKP,KAAK+T,MAAMmB,UAAU;;IAGtC,OAAO7d;EACND,MAAMU,KAAKiI,gBAAgB7H;;;;;;;;;;;;;AC7S7B;;;;;;;;;;AAWAd,MAAMU,KAAKyI,OAAQ,SAASlJ,MAAMC;;;;IAKjCD,KAAKmJ;;;;QAIJ8U,WAAW;;;;QAIXC,aAAalV,SAASmV;;;;QAItBC,sBAAsB;;;;QAKtBC,YAAY;;;;;;;QAQZC,UAAU;YACT,OAAOte,KAAKmJ,OAAO8U;;;;;QAMpBM,wBAAwB;YACvBve,KAAKmJ,OAAOqV,uBAAuBxe,KAAKmJ,OAAOiV;;;;;;;;QAShDK,sBAAsB,SAASC;YAC9B1e,KAAKmJ,OAAOiV,wBAAwBM;YACpC,IAAG1e,KAAKmJ,OAAOiV,wBAAwB,GAAG;gBACzCpe,KAAKmJ,OAAOwV;mBACN;gBACN3e,KAAKmJ,OAAOqV,qBAAqBxe,KAAKmJ,OAAOiV;;;;;;QAO/CO,qBAAqB;YACpB3e,KAAKmJ,OAAOiV,uBAAuB;YACnCpV,SAASmV,QAAQne,KAAKmJ,OAAO+U;;;;;;;;QAS9BM,sBAAsB,SAASI;YAC9B5V,SAASmV,QAAQpe,MAAMU,KAAK8J,SAASpB,OAAO0V,eAAe5P,QAAQ,aAAa2P,OAAO3P,QAAQ,aAAajP,KAAKmJ,OAAO+U;;;;;QAMzH9U,SAAS;YACRpJ,KAAKmJ,OAAO8U,YAAY;YACxB,IAAIle,MAAMU,KAAK6K,aAAazE,SAAS;gBACpC7G,KAAKoE,KAAK0a,eAAe/e,MAAMU,KAAK6K,aAAazE;gBACjD7G,KAAK2I,KAAKgW,oBAAoB5e,MAAMU,KAAK6K,aAAazE;;;;;;QAOxDyC,QAAQ;YACPtJ,KAAKmJ,OAAO8U,YAAY;;;;;;IAO1Bje,KAAK2I;;;;QAIJuC;;;;;;;;;QAUA6T,QAAQ,SAASlY,SAASqU,UAAU8D;YACnC,IAAIC,SAASlf,MAAM+C,KAAKyI,QAAQ1E,UAC/BuD,OAAOC,SAASC,QAAQvK,MAAMU,KAAK8J,SAAS5B,KAAKuW;gBAChDrY,SAASA;gBACToY,QAAQA;gBACR9e,MAAM+a,YAAYpa,QAAQqB,eAAe0E;gBACzCsY,iBAAiB;oBAAY,OAAOH,aAAa;;gBACjDA,UAAUA;gBAEXE,MAAMjf,EAAEmK,MAAMgV,SAAS;YAExBF,IAAIG,MAAMrf,KAAK2I,KAAK2W;;YAEpBrf,EAAE,WAAWif,KAAKG,MAAMrf,KAAK2I,KAAK4W;YAElCvf,KAAK2I,KAAKe;;;;;;;;;;;QAYX8V,QAAQ,SAAS3Y;YAChB,OAAO5G,EAAE,cAAcqa,SAAS,sBAAsBzT,UAAU;;;;;;;;QASjE4Y,WAAW,SAAS5Y;YACnB7G,KAAK2I,KAAK6W,OAAO3Y,SAASgB;YAC1B7H,KAAK2I,KAAKe;;;;;;;;;;QAWXgW,cAAc,SAAS7Y;YACtB5G,EAAE,cAAcqa,WAAWzU,KAAK;gBAC/B,IAAIqZ,MAAMjf,EAAEgG;gBACZ,IAAGiZ,IAAI/N,KAAK,oBAAoBtK,SAAS;oBACxCqY,IAAIS,SAAS;uBACP;oBACNT,IAAIU,YAAY;;;;;;;;;;;;;QAcnBrB,wBAAwB,SAAS1X;YAChC,IAAIgZ,aAAa5Z,KAAKuZ,OAAO3Y,SAASgU,KAAK;YAC3CgF,WAAW7V,OAAOwG,KAAKqP,WAAWrP,WAAW,KAAKsP,SAASD,WAAWrP,QAAQ,MAAM,IAAI;;YAExF,IAAIxQ,KAAK2I,KAAKuC,MAAMrE,SAASR,SAAS,QAAQ;gBAC7CrG,KAAKmJ,OAAOoV;;;;;;;;;;;;QAadI,qBAAqB,SAAS9X;YAC7B,IAAIgZ,aAAa7f,KAAK2I,KAAK6W,OAAO3Y,SAASgU,KAAK;YAChD7a,KAAKmJ,OAAOsV,qBAAqBoB,WAAWrP;YAC5CqP,WAAWlD,OAAOnM,KAAK;;;;;QAMxB8O,UAAU,SAASjN;;YAElB,IAAI0N,iBAAiBhgB,MAAMU,KAAK6K,aAAazE;YAC7C7G,KAAK2I,KAAKuC,MAAM6U,gBAAgBC,iBAAiBhgB,KAAKoE,KAAK6b,QAAQF,gBAAgB,yBAAyBG;YAE5GlgB,KAAKoE,KAAK4F,KAAK/J,EAAEgG,MAAMkL,KAAK;YAC5BkB,EAAE8N;;;;;;;;;;;QAYHZ,UAAU;YACT,IAAI1Y,UAAU5G,EAAEgG,MAAMma,SAASjP,KAAK;;YAEpC,IAAGnR,KAAK2I,KAAKuC,MAAMrE,SAASR,SAAS,QAAQ;gBAC5CrG,KAAKoE,KAAK2Y,MAAMlW;mBAEV;gBACN9G,MAAMY,KAAKoF,OAAOnC,OAAOQ,KAAK4B,MAAMa;;YAErC,OAAO;;;;;;;;;;QAWRwZ,eAAe;YACdtgB,MAAMY,KAAKgF;YACX3F,KAAK2I,KAAKiB,QAAQ+S;YAClB;;;;;QAMDjT,SAAS;YACR,IAAI4W,iBAAiBrgB,EAAE,cAAcsgB,cACpCC,YAAY,GACZvV,OAAOhL,EAAE,cAAcqa;YACxBrP,KAAKpF,KAAK;gBACT2a,aAAavgB,EAAEgG,MAAMsJ;oBAAK7B,OAAO;oBAAQ+S,UAAU;mBAAY7S,WAAW;;YAE3E,IAAG4S,YAAYF,gBAAgB;;gBAE9B,IAAII,qBAAqBzV,KAAK2C,WAAW,QAAQ3C,KAAKyC,SACrDiT,WAAWC,KAAKC,MAAM,iBAAmB5V,KAAKe,UAAU0U;gBACzDzV,KAAKsE;oBAAK7B,OAAOiT;oBAAUF,UAAU;;;;;;;;;;;;;;QAcvC5D,cAAc,SAASnF,SAAS3P;YAC/B,IAAGhI,MAAMU,KAAK6K,aAAazE,SAAS;;gBACnC,IAAIuD,OAAOC,SAASC,QAAQvK,MAAMU,KAAK8J,SAAS5B,KAAKkU;oBACpDnF,SAASA;oBACT3P,SAASA;oBACT+Y,QAAQ7gB,EAAEoI,KAAKqC,EAAE;oBACjBqW,MAAMhhB,MAAM+C,KAAKuL,cAAc,IAAI5B,OAAOuU;;gBAE3C/gB,EAAE,eAAeqa,WAAWzU,KAAK;oBAChC7F,KAAKoE,KAAK6c,oBAAoBhhB,EAAEgG,MAAMkL,KAAK,iBAAiB/G;;gBAE7DpK,KAAKoE,KAAK8c,eAAenhB,MAAMU,KAAK6K,aAAazE;;;;;;;gBAQjD5G,EAAEF,OAAOmF,eAAe;oBACvBwS,SAAYA;oBACZ3P,SAAYA;;;;;;;;;;;;QAafgW,aAAa,SAASlX,SAAS6Q,SAAS3P;YACvC/H,KAAK2I,KAAKmU,cAAcjW,SAAS6Q,SAAS3P;;;;;;;;;;;QAY3C+U,eAAe,SAASjW,SAAS6Q,SAAS3P;YACzC,IAAGhI,MAAMU,KAAK6K,aAAazE,SAAS;;gBACnC,IAAIuD,OAAOC,SAASC,QAAQvK,MAAMU,KAAK8J,SAAS5B,KAAKoV;oBACpDrG,SAASA;oBACT3P,SAAS9H,EAAEoI,KAAKqC,EAAE3C;oBAClBgZ,MAAMhhB,MAAM+C,KAAKuL,cAAc,IAAI5B,OAAOuU;;gBAE3ChhB,KAAKoE,KAAK6c,oBAAoBpa,SAASuD;gBACvC,IAAIrK,MAAMU,KAAK6K,aAAazE,YAAYA,SAAS;oBAChD7G,KAAKoE,KAAK8c,eAAenhB,MAAMU,KAAK6K,aAAazE;;;;;;;QAQpD+C;YACCuX,sBAAsB;;;;YAKtB9gB,MAAM;gBACLJ,EAAE,mBAAmBof,MAAM,SAAShN;oBACpCrS,KAAK2I,KAAKyU,QAAQgE,kBAAkB/O,EAAEgP;oBACrChP,EAAEiP;;gBAEHrhB,EAAE,4BAA4Bof,MAAMrf,KAAK2I,KAAKiB,QAAQ2X;gBAEtD,IAAIC,IAAIxY,SAAS8G,cAAc;gBAC/B9P,KAAK2I,KAAKiB,QAAQuX,0BAA0BK,EAAEC,eAAeD,EAAEC,YAAY,eAAexS,QAAQ,MAAM;gBACxGhP,EAAE,uBAAuBof,MAAMrf,KAAK2I,KAAKiB,QAAQ8X;gBACjD,IAAG3hB,MAAM+C,KAAKgK,aAAa,kBAAkB;oBAC5C7M,EAAE,uBAAuBof;;gBAE1Bpf,EAAE,+BAA+Bof,MAAMrf,KAAK2I,KAAKiB,QAAQ+X;gBACzD,IAAG5hB,MAAM+C,KAAKgK,aAAa,2BAA2B;oBACrD7M,EAAE,+BAA+Bof;;;;;;YAOnCrV,MAAM;gBACL/J,EAAE,iBAAiB+J;;;;;YAMpB2S,MAAM;gBACL1c,EAAE,iBAAiB0c;;;;;YAMpB9T,QAAQ,SAAShC;gBAChB,IAAI+a,UAAU3hB,EAAE,iBAAiB4a,KAAK,aACrCgH,KAAK7hB,KAAKoE,KAAKoC,QAAQK;gBACxB,KAAIgb,OAAOA,GAAG5I,eAAe;oBAC5B2I,QAAQjF;uBACF;oBACNiF,QAAQ5X,OAAOqV,MAAM,SAAShN;wBAC7BrS,KAAK2I,KAAKyU,QAAQpT,KAAKqI,EAAEgP,eAAexa;wBACxCwL,EAAEiP;;;gBAGJthB,KAAK2I,KAAKiB,QAAQkY,gBAAgB9hB,KAAK2I,KAAKuC,MAAMrE,SAASkb;;;;;YAM5DC,WAAW;gBACVhiB,KAAK2I,KAAKiB,QAAQqY;;;;;;;;YASnBA,aAAa;gBACZ;oBACC,IAAGjiB,KAAK2I,KAAKiB,QAAQuX,sBAAsB;wBAC1C,IAAIe,MAAMniB,MAAMU,KAAKyG,aAAaQ,SAAS,cAAcya;2BACnD;wBACN,IAAIC,kBAAkBpZ,SAASqZ,eAAe;wBAC9CD,gBAAgBE,YAAY,eAAe;wBAC3CF,gBAAgBE,YAAY,eAAe;;kBAE3C,OAAOjQ;;;;;;;YAQVqP,qBAAqB;gBACpB,IAAIa,UAAUtiB,EAAE;gBAChB,IAAGsiB,QAAQC,SAAS,YAAY;oBAC/BxiB,KAAK2I,KAAKiB,QAAQoY,YAAY;oBAC9BjiB,MAAM+C,KAAKwJ,UAAU,iBAAiB,KAAK;uBACrC;oBACNtM,KAAK2I,KAAKiB,QAAQoY,YAAY;wBAC7BhiB,KAAK2I,KAAKiB,QAAQqY;;oBAEnBliB,MAAM+C,KAAKuK,aAAa;;gBAEzBkV,QAAQE,YAAY;;;;;;;YAQrBlB,0BAA0B;gBACzB,IAAIgB,UAAUtiB,EAAE;gBAChB,IAAGsiB,QAAQC,SAAS,YAAY;oBAC/BxiB,KAAKoE,KAAK8c,iBAAiB,SAASra;wBACnC7G,KAAKoE,KAAKse,yBAAyB7b;;oBAEpC7G,KAAKmJ,OAAOkV,aAAa;uBACnB;oBACNre,KAAKoE,KAAK8c,iBAAiB,SAASra;wBACnC7G,KAAKoE,KAAKue,iBAAiB9b;;oBAE5B7G,KAAKoE,KAAK8c,eAAenhB,MAAMU,KAAK6K,aAAazE;oBACjD7G,KAAKmJ,OAAOkV,aAAa;;gBAE1BkE,QAAQE,YAAY;;;;;;;YAQrBd,6BAA6B;gBAC5B,IAAIY,UAAUtiB,EAAE;gBAChB,IAAGsiB,QAAQC,SAAS,YAAY;oBAC/BxiB,KAAK2I,KAAKoV,cAAc;oBACxBhe,MAAM+C,KAAKwJ,UAAU,0BAA0B,KAAK;uBAC9C;oBACNtM,KAAK2I,KAAKoV,cAAc,SAASlX,SAAS6Q,SAAS3P;wBAClD/H,KAAK2I,KAAKmU,cAAcjW,SAAS6Q,SAAS3P;;oBAE3ChI,MAAM+C,KAAKuK,aAAa;;gBAEzBkV,QAAQE,YAAY;;;;;;;;YASrBX,iBAAiB,SAASlD;gBACzB3e,EAAE,mBAAmBuQ,KAAKoO;;;;;;QAO5BlC;;;;;;;;;YASC1S,MAAM,SAASI,MAAMwY,kBAAkBC;gBACtC,IAAGD,kBAAkB;oBACpB5iB,KAAK2I,KAAK+T,MAAMkG;uBACV;oBACN5iB,KAAK2I,KAAK+T,MAAMoG;;gBAEjB,IAAGD,aAAa;oBACf7iB,KAAK2I,KAAK+T,MAAMmG;uBACV;oBACN7iB,KAAK2I,KAAK+T,MAAMqG;;gBAEjB9iB,EAAE,eAAe+iB,KAAK,OAAO;gBAC7B/iB,EAAE,oBAAoBmK,KAAKA;gBAC3BnK,EAAE,eAAegjB,OAAO;gBACxBhjB,EAAE,uBAAuB+J;;;;;;;;YAS1B2S,MAAM,SAASuG;gBACdjjB,EAAE,eAAekjB,QAAQ,QAAQ;oBAChCljB,EAAE,oBAAoBuQ,KAAK;oBAC3BvQ,EAAE,uBAAuB0c;;;gBAG1B1c,EAAE+I,UAAUoa,QAAQ,SAAS/Q;oBAC5B,IAAGA,EAAEgR,UAAU,IAAI;wBAClBhR,EAAE8N;;;gBAGJ,IAAI+C,UAAU;oBACbA;;;;;;YAOFL,aAAa;gBACZ5iB,EAAE,uBAAuB+J;;;;;YAM1B+Y,aAAa;gBACZ9iB,EAAE,uBAAuB0c;;;;;YAM1BiG,kBAAkB;gBACjB3iB,EAAE,yBAAyB+J,OAAOqV,MAAM,SAAShN;oBAChDrS,KAAK2I,KAAK+T,MAAMC;;;oBAGhBtK,EAAE8N;;;gBAIHlgB,EAAE+I,UAAUoa,QAAQ,SAAS/Q;oBAC5B,IAAGA,EAAEgR,UAAU,IAAI;wBAClBrjB,KAAK2I,KAAK+T,MAAMC;wBAChBtK,EAAE8N;;;;;;;YAQL2C,kBAAkB;gBACjB7iB,EAAE,yBAAyB0c,OAAO0C,MAAM;;;;;;;;;YAUzCzC,eAAe,SAAS7U,SAAS4R;gBAChC3Z,KAAK2I,KAAK+T,MAAM1S,MAAMjC,UAAUA,UAAU,MAAMsC,SAASC,QAAQvK,MAAMU,KAAK8J,SAAShF,MAAM+d;oBAC1FC,gBAAgBtjB,EAAEoI,KAAKqC,EAAE;oBACzB8Y,gBAAgBvjB,EAAEoI,KAAKqC,EAAE;oBACzB+Y,gBAAgBxjB,EAAEoI,KAAKqC,EAAE;oBACzBgZ,cAAczjB,EAAEoI,KAAKqC,EAAE;oBACvBiZ,kBAAkB5jB,MAAMY,KAAKsG;oBAC7B2c,kBAAkBjK;oBAClBkK,iBAAiB9jB,MAAMY,KAAKsG;oBAC5B0S,WAAWA,YAAYA,YAAY;;gBAEpC1Z,EAAE,eAAeqa,SAAS,gBAAgB/Q;;gBAG1CtJ,EAAE,eAAe6jB,OAAO;oBACvB,IAAIC,WAAW9jB,EAAE,aAAa+jB,OAC7Bjf,WAAW9E,EAAE,aAAa+jB;oBAE3B,KAAKjkB,MAAMY,KAAKsG,yBAAyB;;wBAExC,IAAIhF,MAAMlC,MAAMY,KAAK6F,aAAaud,SAAS3e,QAAQ,OAAO,IACzD2e,WAAW,MAAMjjB,QAAQuB,iBAAiBtC,MAAMY,KAAK6F,UAAUN,YAAY6d;wBAE5E,IAAG9hB,IAAImD,QAAQ,OAAO,MAAMrF,MAAMY,KAAK6F,WAAW;4BACjDzG,MAAMU,KAAKyI,KAAKP,KAAK+T,MAAME,cAAc3c,EAAEoI,KAAKqC,EAAE;+BAC5C;;4BAEN3K,MAAMY,KAAKkE,QAAQ5C,KAAK8C;;2BAEnB;;wBACNhF,MAAMY,KAAKkE,QAAQ8U,WAAW,MAAMoK;;oBAErC,OAAO;;;;;;;;;;;YAYTpG,uBAAuB,SAAS9W,SAASqU,UAAUnT;gBAClD/H,KAAK2I,KAAK+T,MAAM1S,KAAKK,SAASC,QAAQvK,MAAMU,KAAK8J,SAASzB,cAAcmb;oBACvE/I,UAAUA;oBACVuI,gBAAgBxjB,EAAEoI,KAAKqC,EAAE;oBACzBwZ,QAASnc,UAAUA,UAAU9H,EAAEoI,KAAKqC,EAAE,uBAAsBwQ;oBAC5DiJ,aAAalkB,EAAEoI,KAAKqC,EAAE;oBACnB;gBACJzK,EAAE,aAAasJ;;gBAGftJ,EAAE,wBAAwB6jB,OAAO;oBAChC,IAAI/e,WAAW9E,EAAE,aAAa+jB;oBAE9BhkB,KAAK2I,KAAK+T,MAAMC,KAAK;wBACpB5c,MAAMY,KAAKoF,OAAOnC,OAAOQ,KAAK2Q,KAAKlO,SAAS9B;;oBAE7C,OAAO;;;;;;;;;;YAWT6Y,0BAA0B,SAAS/W;gBAClC7G,KAAK2I,KAAK+T,MAAM1S,KAAKK,SAASC,QAAQvK,MAAMU,KAAK8J,SAASzB,cAAcsb;oBACvEb,gBAAgBtjB,EAAEoI,KAAKqC,EAAE;oBACzBwZ,QAAQjkB,EAAEoI,KAAKqC,EAAE;oBACjBgZ,cAAczjB,EAAEoI,KAAKqC,EAAE;;gBAExBzK,EAAE,aAAasJ;;gBAGftJ,EAAE,2BAA2B6jB,OAAO;oBACnC,IAAI9b,WAAW/H,EAAE,aAAa+jB;oBAE9BhkB,KAAK2I,KAAK+T,MAAMC,KAAK;wBACpB5c,MAAMY,KAAK6F,UAAUc,KAAKtC,OAAOgD;wBACjCjI,MAAMY,KAAKoF,OAAOnC,OAAOQ,KAAK2Q,KAAKlO;;oBAEpC,OAAO;;;;;;;;;;YAWTgX,WAAW,SAAS9V,SAASsc;gBAC5BrkB,KAAK2I,KAAK+T,MAAM1S,KAAKK,SAASC,QAAQvK,MAAMU,KAAK8J,SAASzB,cAAcwb;oBACvEC,QAAQtkB,EAAEoI,KAAKqC,EAAE3C,SAASsc;oBACvB;;;;;;QAONta;;;;;;;;;;YAUCC,MAAM,SAASuS,OAAOiI;gBACrB,IAAIC,UAAUxkB,EAAE,aACfykB,SAASzkB,EAAEsc,MAAM8E;gBAElB,KAAImD,SAAS;oBACZA,UAAUE,OAAOvT,KAAK;;gBAGvB,IAAGsT,QAAQzY,WAAW,GAAG;oBACxB,IAAI5B,OAAOC,SAASC,QAAQvK,MAAMU,KAAK8J,SAAS5B,KAAK8b;oBACrDxkB,EAAE,cAAckM,OAAO/B;oBACvBqa,UAAUxkB,EAAE;;gBAGbA,EAAE,iBAAiB0c;gBAEnB8H,QAAQzB,KAAK,OAAO;gBACpByB,QAAQnK,SAAS,OAAOlQ,KAAKoa;gBAE7B,IAAIhX,MAAMkX,OAAOC,UACfC,UAAU7kB,MAAM+C,KAAKwK,kCAAkCmX,SAASjX,IAAIqX,OACpEC,SAAU/kB,MAAM+C,KAAKkL,iCAAiCyW,SAASjX,IAAIuX;gBAErEN,QACElV;oBAAKsV,MAAQD,QAAQ7W;oBAAIgX,KAAOD,OAAO/W;mBACvC6R,YAAY,+CACZD,SAASiF,QAAQ9W,8BAA8B,MAAMgX,OAAOhX,6BAC5DmV,OAAO;gBAETyB,OAAOM,WAAW,SAASzI;oBAC1BA,MAAM+E;oBACNrhB,EAAE,YAAY+iB,KAAK,OAAO,MAAMG,QAAQ,QAAQ;wBAAYljB,EAAEgG,MAAMsJ;4BAAKwV,KAAO;4BAAGF,MAAQ;;;;;;;;;QAQ9FzH;;;;YAIC/c,MAAM;gBACL,IAAIJ,EAAE,iBAAiB+L,WAAW,GAAG;oBACpC,IAAI5B,OAAOC,SAASC,QAAQvK,MAAMU,KAAK8J,SAAS5B,KAAKyU,QAAQ6H;oBAC7DhlB,EAAE,cAAckM,OAAO/B;oBACvBnK,EAAE,iBAAiB+kB,WAAW;wBAC7B/kB,EAAEgG,MAAMkd,QAAQ;;;;;;;;;;;;;;;;;;;;YAqBnBnZ,MAAM,SAASuD,MAAM1G,SAASH;gBAC7B6G,OAAOtN,EAAEsN;gBACT,IAAI0R,SAASjf,KAAK2I,KAAKuC,MAAMrE,SAASP,IACrC2e,OAAOhlB,EAAE,kBACTilB,QAAQjlB,EAAE,SAASglB;gBAEpBhlB,EAAE,YAAY0c;;gBAGd,KAAIjW,MAAM;oBACTA,OAAO3G,MAAMY,KAAK6F;;gBAGnB0e,MAAMrd;gBAEN,IAAIsd,YAAYlf,KAAKmf,aAAave,SAASH,MAAM6G,OAChDjH,IACA+e,eAAe,SAASxe,SAASH;oBAChC,OAAO,SAAS6V;wBACfA,MAAMjV,KAAK4b,SAAS3G,OAAO1V,SAASH;wBACpCzG,EAAE,iBAAiB0c;;;gBAItB,KAAIrW,MAAM6e,WAAW;oBACpB,IAAGA,UAAU9V,eAAe/I,KAAK;wBAChC,IAAIgf,OAAOH,UAAU7e,KACpB8D,OAAOC,SAASC,QAAQvK,MAAMU,KAAK8J,SAAS5B,KAAKyU,QAAQ+H;4BACxDlG,QAAaA;4BACbsG,SAAaD,KAAK;4BAClBhf,IAAaA;4BACbkf,OAAaF,KAAKE;;wBAEpBvlB,EAAE,MAAMglB,MAAM9Y,OAAO/B;wBACrBnK,EAAE,mBAAmBqG,IAAIzD,KAAK,SAASyiB,MAAMD,aAAaxe,SAASH;;;;gBAIrE,IAAGJ,IAAI;oBACN,IAAIkH,MAAMD,KAAKoX,UACdC,UAAU7kB,MAAM+C,KAAKwK,kCAAkC2X,MAAMzX,IAAIqX,OACjEC,SAAU/kB,MAAM+C,KAAKkL,iCAAiCiX,MAAMzX,IAAIuX;oBAEjEE,KACE1V;wBAAKsV,MAAQD,QAAQ7W;wBAAIgX,KAAOD,OAAO/W;uBACvC6R,YAAY,+CACZD,SAASiF,QAAQ9W,8BAA8B,MAAMgX,OAAOhX,6BAC5DmV,OAAO;;;;;;;;;oBAUThjB,EAAEF,OAAOmF,eAAe;wBACvB2B,SAAYA;wBACZH,MAASA;wBACT+e,SAAWR;;oBAGZ,OAAO;;;;;;;;;;;;;;;;;YAkBTG,cAAc,SAASve,SAASH,MAAM6G;gBACrC,IAAI4X,WAAW7e;gBAEf,IAAIkX;oBACH3W,SAAYA;oBACZH,MAASA;oBACT6G,MAAQA;oBACR4X,WAAalf,KAAKyf,iBAAiBnY;;;;;;;;;;;;;gBAcpCtN,EAAEF,OAAOmF,eAAe,kCAAkCsY;gBAE1D2H,YAAY3H,QAAQ2H;gBAEpB,KAAI7e,MAAM6e,WAAW;oBACpB,IAAGA,UAAU9V,eAAe/I,OAAO6e,UAAU7e,IAAIqf,uBAAuBpkB,cAAc4jB,UAAU7e,IAAIqf,mBAAmBjf,MAAM1G,KAAKoE,KAAKoC,QAAQK,UAAU0G,OAAO;+BACxJ4X,UAAU7e;;;gBAGnB,OAAO6e;;;;;;;;;;;;;;;YAgBRO,kBAAkB;gBACjB;oBACCE;wBACCD,oBAAoB,SAASjf,MAAMmb;4BAClC,OAAOA,GAAGhM,cAAcnP,KAAKmP,aAAa9V,MAAMY,KAAKwG,QAAQpH,MAAMU,KAAK6K,aAAazE,aAAa9G,MAAMY,KAAK6F,UAAU8S,gBAAgB,UAAU5S,KAAKR;;wBAEvJqf,SAAU;wBACVC,OAAUvlB,EAAEoI,KAAKqC,EAAE;wBACnBwY,UAAa,SAAS7Q,GAAGxL,SAASH;4BACjCzG,EAAE,WAAWF,MAAM+C,KAAKyI,QAAQ1E,WAAW,MAAM9G,MAAM+C,KAAKyI,QAAQ7E,KAAKR,WAAWmZ;;;oBAGtFwG;wBACCF,oBAAoB,SAASjf,MAAMmb;4BAClC,OAAOA,GAAGhM,cAAcnP,KAAKmP,cAAc9V,MAAMY,KAAK6F,UAAU8S,gBAAgB,UAAU5S,KAAKR;;wBAEhGqf,SAAU;wBACVC,OAAUvlB,EAAEoI,KAAKqC,EAAE;wBACnBwY,UAAa,SAAS7Q,GAAGxL,SAASH;4BACjC3G,MAAMU,KAAKyI,KAAK9E,KAAK0hB,WAAWjf,SAASH,KAAKR;;;oBAGhD6f;wBACCJ,oBAAoB,SAASjf,MAAMmb;4BAClC,OAAOA,GAAGhM,cAAcnP,KAAKmP,aAAa9V,MAAMY,KAAK6F,UAAU8S,gBAAgB,UAAU5S,KAAKR;;wBAE/Fqf,SAAU;wBACVC,OAAUvlB,EAAEoI,KAAKqC,EAAE;wBACnBwY,UAAa,SAAS7Q,GAAGxL,SAASH;4BACjC3G,MAAMU,KAAKyI,KAAK9E,KAAK4hB,aAAanf,SAASH,KAAKR;;;oBAGlD+f;wBACCN,oBAAoB,SAASjf,MAAMmb;4BAClC,OAAOA,GAAGhM,cAAcnP,KAAKmP,aAAagM,GAAG5I,kBAAkBvS,KAAKuS;;wBAErEsM,SAAU;wBACVC,OAAUvlB,EAAEoI,KAAKqC,EAAE;wBACnBwY,UAAa,SAAS7Q,GAAGxL,SAASH;4BACjC1G,KAAK2I,KAAK+T,MAAM1S,KAAKK,SAASC,QAAQvK,MAAMU,KAAK8J,SAAS5B,KAAKyU,QAAQ8I;gCACtEhC,QAAQjkB,EAAEoI,KAAKqC,EAAE;gCACjByb,SAASlmB,EAAEoI,KAAKqC,EAAE;gCACf;4BACJzK,EAAE,wBAAwBsJ;4BAC1BtJ,EAAE,uBAAuB6jB,OAAO;gCAC/B/jB,MAAMY,KAAKoF,OAAOnC,OAAOQ,KAAK+S,MAAMC,WAAWvQ,SAASH,KAAKR,UAAU,QAAQjG,EAAE,wBAAwB+jB;gCACzGhkB,KAAK2I,KAAK+T,MAAMC;gCAChB,OAAO;;;;oBAIVyJ;wBACCT,oBAAoB,SAASjf,MAAMmb;4BAClC,OAAOA,GAAGhM,cAAcnP,KAAKmP,aAAagM,GAAG5I,kBAAkBvS,KAAKuS;;wBAErEsM,SAAU;wBACVC,OAAUvlB,EAAEoI,KAAKqC,EAAE;wBACnBwY,UAAa,SAAS7Q,GAAGxL,SAASH;4BACjC1G,KAAK2I,KAAK+T,MAAM1S,KAAKK,SAASC,QAAQvK,MAAMU,KAAK8J,SAAS5B,KAAKyU,QAAQ8I;gCACtEhC,QAAQjkB,EAAEoI,KAAKqC,EAAE;gCACjByb,SAASlmB,EAAEoI,KAAKqC,EAAE;gCACf;4BACJzK,EAAE,wBAAwBsJ;4BAC1BtJ,EAAE,uBAAuB6jB,OAAO;gCAC/B/jB,MAAMY,KAAKoF,OAAOnC,OAAOQ,KAAK+S,MAAMC,WAAWvQ,SAASH,KAAKR,UAAU,OAAOjG,EAAE,wBAAwB+jB;gCACxGhkB,KAAK2I,KAAK+T,MAAMC;gCAChB,OAAO;;;;oBAIVjF;wBACCiO,oBAAoB,SAASjf,MAAMmb;4BAClC,OAAOA,GAAGhM,cAAcnP,KAAKmP,aAAagM,GAAG5I;;wBAE9CsM,SAAS;wBACTC,OAAUvlB,EAAEoI,KAAKqC,EAAE;wBACnBwY,UAAY,SAAS7Q,GAAGxL;4BACvB7G,KAAK2I,KAAK+T,MAAM1S,KAAKK,SAASC,QAAQvK,MAAMU,KAAK8J,SAAS5B,KAAKyU,QAAQ8I;gCACtEhC,QAAQjkB,EAAEoI,KAAKqC,EAAE;gCACjByb,SAASlmB,EAAEoI,KAAKqC,EAAE;gCACf;4BACJzK,EAAE,wBAAwBsJ;4BAC1BtJ,EAAE,uBAAuB6jB,OAAO,SAASzR;gCACxCtS,MAAMY,KAAKoF,OAAOnC,OAAOQ,KAAK+S,MAAMM,WAAW5Q,SAAS5G,EAAE,wBAAwB+jB;gCAClFhkB,KAAK2I,KAAK+T,MAAMC;gCAChBtK,EAAE8N;;;;;;;;;;;;;;;YAgBPiB,mBAAmB,SAAS7T;gBAC3BA,OAAOtN,EAAEsN;gBACT,IAAIC,MAAMD,KAAKoX,UACdM,OAAOhlB,EAAE,kBACTukB,UAAUvkB,EAAE,MAAMglB,OAClB7U,YAAY,IACZK;gBAEDxQ,EAAE,YAAY0c;gBAEd,KAAIlM,IAAI1Q,MAAM+C,KAAKoH,OAAOkG,UAAUpE,SAAO,GAAGyE,KAAK,GAAGA,KAAK;oBAC1DL,YAAY,eAAerQ,MAAM+C,KAAKoH,OAAOgG,gBAAgBnQ,MAAM+C,KAAKoH,OAAOkG,UAAUK,GAAGH,QAAQ,YAAYvQ,MAAM+C,KAAKoH,OAAOkG,UAAUK,GAAGJ,QAAQ,SAASD;;gBAEjKoU,QAAQpa,KAAK,2BAA2BgG,YAAY;gBACpDoU,QAAQ3J,KAAK,OAAOwE,MAAM;oBACzB,IAAIgH,QAAQtmB,MAAMU,KAAKyI,KAAK9E,KAAK6b,QAAQlgB,MAAMU,KAAK6K,aAAazE,SAAS,iBAAiByT,SAAS,WACnGzY,QAAQwkB,MAAMrC,OACdsC,WAAWrmB,EAAEgG,MAAMkL,KAAK,SAAS;oBAClCkV,MAAMrC,IAAIniB,QAAQA,QAAQ,MAAMykB,WAAWA,UAAU/c;;gBAGtD,IAAIqb,UAAU7kB,MAAM+C,KAAKwK,kCAAkC2X,MAAMzX,IAAIqX,OACpEC,SAAU/kB,MAAM+C,KAAKkL,iCAAiCiX,MAAMzX,IAAIuX;gBAEjEE,KACE1V;oBAAKsV,MAAQD,QAAQ7W;oBAAIgX,KAAOD,OAAO/W;mBACvC6R,YAAY,+CACZD,SAASiF,QAAQ9W,8BAA8B,MAAMgX,OAAOhX,6BAC5DmV,OAAO;gBAET,OAAO;;;;;;;IAQVjjB,KAAKoE;;;;;;;;;;;;;;;;;;;;QAoBJ/D,MAAM,SAASwG,SAASqU,UAAU8D;YACjCA,WAAWA,YAAY;YACvBnY,UAAU9G,MAAM+C,KAAK8I,YAAY/E;YAEjC,IAAI2W;gBACH3W,SAASA;gBACTR,MAAM2Y;;;;;;;;;;;;YAYP,IAAG/e,EAAEF,OAAOmF,eAAe,8BAA8BsY,aAAa,OAAO;gBAC5E,OAAO;;;YAIR,IAAGzd,MAAM+C,KAAKoM,cAAclP,KAAK2I,KAAKuC,QAAQ;gBAC7ClL,KAAK2I,KAAKiB,QAAQI;;YAGnB,IAAIiV,SAASlf,MAAM+C,KAAKyI,QAAQ1E;YAChC7G,KAAK2I,KAAKuC,MAAMrE;gBAAYP,IAAI2Y;gBAAQ8C,WAAW;gBAAG5hB,MAAM+a;gBAAU7U,MAAM2Y;gBAAUuH,cAAc;gBAAGvG,iBAAiB;;YAExH/f,EAAE,eAAekM,OAAO9B,SAASC,QAAQvK,MAAMU,KAAK8J,SAASnG,KAAKoG;gBACjEyU,QAAQA;gBACRpY,SAASA;gBACTmY,UAAUA;gBACVsE;oBACCkD,gBAAgBvmB,EAAEoI,KAAKqC,EAAE;;gBAE1BxC;oBACCue,aAAaxmB,EAAEoI,KAAKqC,EAAE;;;gBAGvBxC,QAAQnI,MAAMU,KAAK8J,SAASwJ,OAAOvJ;gBACnC7C,UAAU5H,MAAMU,KAAK8J,SAAStG,QAAQuG;gBACtC8Y,MAAMvjB,MAAMU,KAAK8J,SAASnG,KAAKkf;;YAEhCtjB,KAAK2I,KAAKoW,OAAOlY,SAASqU,UAAU8D;YACpChf,KAAKoE,KAAK6b,QAAQpZ,SAAS,iBAAiBid,OAAO9jB,KAAKiE,QAAQ6f;YAEhEtG,QAAQiI,UAAUzlB,KAAKoE,KAAK6b,QAAQpZ;;;;;;;;;YAUpC5G,EAAEF,OAAOmF,eAAe,6BAA6BsY;YAErD,OAAOyB;;;;;;;;;;;;QAaRjV,MAAM,SAASnD;YACd,IAAIoY,SAASjf,KAAK2I,KAAKuC,MAAMrE,SAASP,IACrCkX;YAEDvd,EAAE,cAAc4F,KAAK;gBACpB,IAAI0H,OAAOtN,EAAEgG;gBACbuX;oBACC3W,SAAW0G,KAAK4D,KAAK;oBACrBsU,SAAYlY;;gBAGb,IAAGA,KAAK4D,KAAK,UAAW,eAAe8N,QAAS;oBAC/C1R,KAAKvD;oBACLjK,MAAMU,KAAK6K,aAAazE,UAAUA;oBAClC7G,KAAK2I,KAAK+W,aAAa7Y;oBACvB7G,KAAK2I,KAAKiB,QAAQf,OAAOhC;oBACzB7G,KAAK2I,KAAKgW,oBAAoB9X;oBAC9B7G,KAAKoE,KAAK0a,eAAejY;oBACzB7G,KAAKoE,KAAK8c,eAAera;;;;;;;;oBASzB5G,EAAEF,OAAOmF,eAAe,8BAA8BsY;uBAEhD;oBACNjQ,KAAKoP;;;;;;;;oBASL1c,EAAEF,OAAOmF,eAAe,8BAA8BsY;;;;;;;;;;;;;;QAezDM,YAAY,SAASjX,SAAS6Q;YAC7BA,UAAU3X,MAAM+C,KAAKoH,OAAOwG,QAAQ3Q,MAAM+C,KAAKoH,OAAOgD,OAAOwK;YAC7D,IAAItN,OAAOC,SAASC,QAAQvK,MAAMU,KAAK8J,SAASnG,KAAKsT;gBACpDA,SAASA;gBACTwD,UAAUlb,KAAK2I,KAAKuC,MAAMrE,SAAS1G;gBACnCumB,cAAczmB,EAAEoI,KAAKqC,EAAE;gBACvBqW,MAAMhhB,MAAM+C,KAAKuL,cAAc,IAAI5B,OAAOuU;;YAE3ChhB,KAAKoE,KAAK6c,oBAAoBpa,SAASuD;YACvCpK,KAAKoE,KAAK8c,eAAera;;;;;;;;;YAUzB5G,EAAEF,OAAOmF,eAAe;gBACvB2B,SAAWA;gBACX4e,SAAYzlB,KAAKoE,KAAK6b,QAAQpZ;gBAC9B6Q,SAAYA;;;;;;;;;;;;;;;QAgBdqF,OAAO,SAASlW;YACf7G,KAAK2I,KAAK8W,UAAU5Y;YACpB7G,KAAKmJ,OAAOwV;;;;;;YAOZ3e,KAAKoE,KAAK6b,QAAQpZ,SAASgB;YAC3B,IAAI8e,YAAY1mB,EAAE,eAAeqa;YACjC,IAAGva,MAAMU,KAAK6K,aAAazE,YAAYA,SAAS;gBAC/C9G,MAAMU,KAAK6K,aAAazE,UAAU;gBAClC,IAAG8f,UAAU3a,WAAW,GAAG;oBAC1BhM,KAAK2I,KAAK0X;uBACJ;oBACNrgB,KAAKoE,KAAK4F,KAAK2c,UAAUC,OAAOzV,KAAK;;;mBAGhCnR,KAAK2I,KAAKuC,MAAMrE;;;;;;;YAQvB5G,EAAEF,OAAOmF,eAAe;gBACvB2B,SAAYA;;;;;;;;;;QAWdoa,qBAAqB,SAASpa,SAASuD;YACtCpK,KAAKoE,KAAK6b,QAAQpZ,SAAS,iBAAiBsF,OAAO/B;YACnDpK,KAAK2I,KAAKuC,MAAMrE,SAAS0f;YACzBvmB,KAAKoE,KAAKyiB,iBAAiBhgB;;;;;;;;;;;;QAa5BggB,kBAAkB,SAAShgB;;YAE1B,IAAG7G,KAAKmJ,OAAOkV,YAAY;gBAC1B,IAAI9d,UAAUR,MAAMU,KAAKyG,aAAaS;gBACtC,IAAG3H,KAAK2I,KAAKuC,MAAMrE,SAAS0f,eAAehmB,QAAQqH,OAAO;oBACzD5H,KAAKoE,KAAK6b,QAAQpZ,SAAS,iBAAiByT,WAAWwM,MAAM,GAAGvmB,QAAQsH,QAAQA;oBAChF7H,KAAK2I,KAAKuC,MAAMrE,SAAS0f,gBAAgBhmB,QAAQsH;;;;;;;;;;;;;QAcpDqZ,gBAAgB,SAASra;YACxB7G,KAAKoE,KAAKue,iBAAiB9b;;;;;;;;QAS5B8b,kBAAkB,SAAS9b;YAC1B,IAAIkgB,cAAc/mB,KAAKoE,KAAK6b,QAAQpZ,SAAS;YAC7CkgB,YAAY7G,UAAU6G,YAAY3X,KAAK;;;;;;;;;QAUxCsT,0BAA0B,SAAS7b;;;YAGlC,IAAG7G,KAAK2I,KAAKuC,MAAMrE,SAASmZ,kBAAkB,GAAG;gBAChD,IAAI+G,cAAc/mB,KAAKoE,KAAK6b,QAAQpZ,SAAS;gBAC7CkgB,YAAY7G,UAAUlgB,KAAK2I,KAAKuC,MAAMrE,SAASmZ;gBAC/ChgB,KAAK2I,KAAKuC,MAAMrE,SAASmZ,kBAAkB;;;;;;;;;QAU7ClB,gBAAgB,SAASjY;YACxB,IAAI2D,OAAOxK,KAAKoE,KAAK6b,QAAQpZ,SAAS;YACtC,IAAI2D,MAAM;;gBAET;oBACCA,KAAK8P,SAAS,UAAU,GAAG/Q;kBAC1B,OAAM8I;;;;;;;;;;;QAcV5L,SAAS,SAASI,SAASH;YAC1B1G,KAAK2I,KAAKuC,MAAMrE,SAASH,OAAOA;YAChC,IAAIsgB,WAAWhnB,KAAKoE,KAAK6b,QAAQpZ,UAChCogB,WAAWhnB,EAAE;YAEd+mB,SAAS7V,KAAK,gBAAgBzK,KAAKR;;YAEnC,IAAGQ,KAAKuS,eAAe;gBACtB,IAAIvS,KAAKmS,cAAcnS,KAAK4R,gBAAgB;oBAC3C2O,SAAStH,SAAS;;gBAEnB,IAAIjZ,KAAKsS,qBAAqBtS,KAAK6R,mBAAmB;oBACrD0O,SAAStH,SAAS;;mBAEb;gBACNsH,SAASrH,YAAY;;YAEtB5f,KAAK2I,KAAKyU,QAAQ/c;;;;;;;;;;;QAYnBmG,SAAS,SAASK;YACjB,OAAO7G,KAAK2I,KAAKuC,MAAMrE,SAASH;;;;;;;;;QAUjCof,YAAY,SAASjf,SAAS+P;YAC7B7W,MAAMY,KAAKoF,OAAOnC,OAAOQ,KAAKuS,eAAeC;YAC7C7W,MAAMU,KAAKyI,KAAK9E,KAAK8iB,cAAcrgB,SAAS+P;;;;;;;;;QAU7CoP,cAAc,SAASnf,SAAS+P;YAC/B7W,MAAMY,KAAKoF,OAAOnC,OAAOQ,KAAKuS,eAAeC;YAC7C7W,MAAMU,KAAKyI,KAAK9E,KAAK+iB,iBAAiBtgB,SAAS+P;;;;;;;;;QAUhDsQ,eAAe,SAASrgB,SAAS+P;YAChC,IAAI7W,MAAMU,KAAKyI,KAAKP,KAAKuC,MAAM0L,UAAU;gBACxC3W,EAAE,WAAWF,MAAMU,KAAKyI,KAAKP,KAAKuC,MAAM0L,SAAStQ,KAAK,MAAMvG,MAAM+C,KAAKyI,QAAQqL,UAAU+I,SAAS;;YAEnG,IAAI5f,MAAMU,KAAKyI,KAAKP,KAAKuC,MAAMpK,QAAQqV,kBAAkBtP,WAAW;gBACnE5G,EAAE,WAAWF,MAAMU,KAAKyI,KAAKP,KAAKuC,MAAMpK,QAAQqV,kBAAkBtP,UAAUP,KAAK,MAAMvG,MAAM+C,KAAKyI,QAAQqL,UAAU+I,SAAS;;;;;;;;;;QAW/HwH,kBAAkB,SAAStgB,SAAS+P;YACnC,IAAI7W,MAAMU,KAAKyI,KAAKP,KAAKuC,MAAM0L,UAAU;gBACxC3W,EAAE,WAAWF,MAAMU,KAAKyI,KAAKP,KAAKuC,MAAM0L,SAAStQ,KAAK,MAAMvG,MAAM+C,KAAKyI,QAAQqL,UAAUgJ,YAAY;;YAEtG,IAAI7f,MAAMU,KAAKyI,KAAKP,KAAKuC,MAAMpK,QAAQqV,kBAAkBtP,WAAW;gBACnE5G,EAAE,WAAWF,MAAMU,KAAKyI,KAAKP,KAAKuC,MAAMpK,QAAQqV,kBAAkBtP,UAAUP,KAAK,MAAMvG,MAAM+C,KAAKyI,QAAQqL,UAAUgJ,YAAY;;;;;;;;;;QAWlIK,SAAS,SAASpZ,SAASugB;YAC1B,IAAIpnB,KAAK2I,KAAKuC,MAAMrE,UAAU;gBAC7B,IAAGugB,SAAS;oBACX,IAAGpnB,KAAK2I,KAAKuC,MAAMrE,SAAS,UAAUugB,UAAU;wBAC/C,OAAOpnB,KAAK2I,KAAKuC,MAAMrE,SAAS,UAAUugB;2BACpC;wBACNpnB,KAAK2I,KAAKuC,MAAMrE,SAAS,UAAUugB,WAAWnnB,EAAE,gBAAgBD,KAAK2I,KAAKuC,MAAMrE,SAASP,IAAIuU,KAAKuM;wBAClG,OAAOpnB,KAAK2I,KAAKuC,MAAMrE,SAAS,UAAUugB;;uBAErC;oBACN,OAAOnnB,EAAE,gBAAgBD,KAAK2I,KAAKuC,MAAMrE,SAASP;;;;;;;;;;;QAYrD+gB,6BAA6B,SAASpI,QAAQvY;YAC7C,IAAIA,KAAKmP,cAAc9V,MAAMY,KAAK6F,UAAUqP,WAAW;gBACtD,IAAIyR,cAAcrnB,EAAE,gBAAgBgf;gBACpCqI,YAAYnW,KAAK,gBAAgBrQ,QAAQqV,kBAAkBmR,YAAYnW,KAAK,mBAAmB,MAAMzK,KAAKmP;;;;;;;IAQ7G7V,KAAKyd;;;;;;;;;;;;;;;QAeJO,MAAM,SAASnX,SAASqU,UAAUqM,cAAczL;YAC/C,IAAIpV,OAAOoV,wBAAwB/b,MAAMY,KAAK6F,YAAYxG,KAAKoE,KAAKoC,QAAQ1F,QAAQqV,kBAAkBtP,WACrG2W;gBACC3W,SAAWA;gBACXqU,UAAYA;gBACZ7U,MAAQ;;;;;;;;;;;;;YAcV,IAAGpG,EAAEF,OAAOmF,eAAe,uCAAuCsY,aAAa,OAAO;gBACrF,OAAO;;;YAIR,IAAIzd,MAAMY,KAAK6F,UAAU8S,gBAAgB,UAAUzS,UAAU;gBAC5D,OAAO;;YAER,KAAI7G,KAAK2I,KAAKuC,MAAMrE,UAAU;gBAC7B,IAAG7G,KAAKoE,KAAK/D,KAAKwG,SAASqU,UAAU,YAAY,OAAO;oBACvD,OAAO;;;YAGT,IAAGqM,cAAc;gBAChBvnB,KAAKoE,KAAK4F,KAAKnD;;YAGhB7G,KAAK+T,OAAOlL,OAAOhC,SAAS,IAAI9G,MAAMY,KAAK2E,SAASuB,SAASqU,WAAW,QAAQxU;YAChF1G,KAAK+T,OAAOlL,OAAOhC,SAASH,MAAM,QAAQA;YAC1C1G,KAAKyd,YAAYC,UAAU7W,SAAS;;YAKpC,IAAGiV,uBAAuB;gBACzB9b,KAAK2I,KAAKoV,YAAYlX,SAAS5G,EAAEoI,KAAKqC,EAAE,kCAAkCzK,EAAEoI,KAAKqC,EAAE;;YAGpF8S,QAAQiI,UAAUzlB,KAAKoE,KAAK6b,QAAQpZ;;;;;;;;;YASpC5G,EAAEF,OAAOmF,eAAe,sCAAsCsY;;;;;;;;;QAU/DE,WAAW,SAAS7W,SAASG;YAC5B,IAAIwgB,cAAcxnB,KAAKoE,KAAK6b,QAAQpZ,SAAS;YAC7C,IAAGG,WAAW,QAAQ;gBACrBhH,KAAK2I,KAAK6W,OAAO3Y,SAAS8Y,SAAS,UAAUC,YAAY;gBAEzD4H,YAAYlN,SAAS,UAAUmN,WAAW;gBAC1CD,YAAYlN,SAAS,WAAWmN,WAAW;gBAE3CznB,KAAK2I,KAAK6W,OAAO3Y;mBACX,IAAGG,WAAW,SAAS;gBAC7BhH,KAAK2I,KAAK6W,OAAO3Y,SAAS8Y,SAAS,WAAWC,YAAY;gBAE1D4H,YAAYlN,SAAS,UAAUnJ,KAAK,YAAY;gBAChDqW,YAAYlN,SAAS,WAAWnJ,KAAK,YAAY;;;;;;;;;;QAWnDuW,YAAY,SAASA,WAAW7gB,SAASH;YACxC3G,MAAMY,KAAK+B,IAAI;YAEf,IAAIilB,yBAAyB9gB,UAAU,MAAMH,KAAKgT,mBACjDkO,oBAAoB/gB,UAAU,MAAMH,KAAKmP,WACzCgS,wBAAwB9nB,MAAM+C,KAAKyI,QAAQoc,yBAC3CG,mBAAmB/nB,MAAM+C,KAAKyI,QAAQqc,oBACtC/P,OAAO7X,KAAK2I,KAAKuC,MAAMyc,yBACvBL,aACAS;;;YAID,IAAI/nB,KAAK2I,KAAKuC,MAAM0c,oBAAoB;gBACvC5nB,KAAKoE,KAAK2Y,MAAM6K;;YAGjB,IAAI/P,MAAM;;gBACTA,KAAK1X,OAAOuG,KAAKmP;gBACjBgC,KAAKvR,KAAOwhB;gBAEZ9nB,KAAK2I,KAAKuC,MAAM0c,qBAAqB/P;uBAC9B7X,KAAK2I,KAAKuC,MAAMyc;gBAEvBL,cAAcrnB,EAAE,gBAAgB4nB;gBAChC,IAAIP,aAAa;oBAChBA,YAAYnW,KAAK,gBAAgByW;oBACjCN,YAAYnW,KAAK,MAAM,eAAe2W;oBAEtCC,iBAAiB9nB,EAAE,iCAAiC0nB,yBAAyB;oBAC7EI,eAAe5W,KAAK,gBAAgByW;;;;oBAKpCG,eAAezN,SAAS,WAAW9J,KAAK,MAAM9J,KAAKmP;oBAEnD,IAAI9V,MAAMU,KAAK6K,aAAazE,YAAY8gB,wBAAwB;wBAC/D5nB,MAAMU,KAAK6K,aAAazE,UAAU+gB;;;mBAG9B;;gBACNN,cAAcrnB,EAAE,4CAA4C0nB,yBAAyB;gBACrF,IAAIL,YAAYtb,QAAQ;oBACvB6b,wBAAwB9nB,MAAM+C,KAAKyI,QAAQ+b,YAAYnW,KAAK;oBAC5DmW,YAAYnW,KAAK,gBAAgByW;;;YAGnC,IAAIN,eAAeA,YAAYtb,QAAQ;gBACtChM,KAAK+T,OAAO2T,WAAWG,uBAAuBnhB;;;;;;;IAQjD1G,KAAK+T;;;;;;;;;;;;;;;;;QAiBJlL,QAAQ,SAAShC,SAASH,MAAM0O,QAAQ2B;YACvChX,MAAMY,KAAK+B,IAAI,wBAAwB0S;YACvC,IAAI6J,SAASjf,KAAK2I,KAAKuC,MAAMrE,SAASP,IACrC0hB,SAASjoB,MAAM+C,KAAKyI,QAAQ7E,KAAKR,WACjC+hB,iBAAiB,GACjBC,WAAWjoB,EAAE,WAAWgf,SAAS,MAAM+I,SACvCxK;gBACC3W,SAAYA;gBACZH,MAASA;gBACT0O,QAAUA;gBACVqQ,SAAWyC;;;;;;;;;;;YAYbjoB,EAAEF,OAAOmF,eAAe,mCAAmCsY;;YAG3D,IAAGpI,WAAW,QAAQ;gBACrB6S,gBAAgB;gBAChB,IAAI7d,OAAOC,SAASC,QAAQvK,MAAMU,KAAK8J,SAASwJ,OAAOrN;oBACrDuY,QAAQA;oBACR+I,QAASA;oBACTpR,SAASlQ,KAAKR;oBACdlB,MAAM0B,KAAKmP;oBACXsS,aAAapoB,MAAM+C,KAAKgF,KAAKpB,KAAKmP,WAAW9V,MAAMU,KAAKyG,aAAaY,KAAKI,OAAOF;oBACjFsP,MAAM5Q,KAAKmS;oBACXtB,aAAa7Q,KAAKsS;oBAClB6I,IAAI9K,gBAAgBxV,aAAamF,KAAKmP,cAAckB,YAAYlB;oBAChEuS,aAAanoB,EAAEoI,KAAKqC,EAAE;oBACtB2d,gBAAgBpoB,EAAEoI,KAAKqC,EAAE;;gBAG3B,IAAGwd,SAASlc,SAAS,GAAG;oBACvB,IAAIsc,eAAe,OAClBC,aAAavoB,KAAKoE,KAAK6b,QAAQpZ,SAAS;;oBAGzC,IAAG0hB,WAAWjO,WAAWtO,SAAS,GAAG;;wBAEpC,IAAIwc,kBAAkB9hB,KAAKmP,UAAU4S;wBACrCF,WAAWjO,WAAWzU,KAAK;4BAC1B,IAAI0H,OAAOtN,EAAEgG;4BACb,IAAGsH,KAAK4D,KAAK,aAAasX,gBAAgBD,iBAAiB;gCAC1Djb,KAAKmb,OAAOte;gCACZke,eAAe;gCACf,OAAO;;4BAER,OAAO;;;;oBAIT,KAAIA,cAAc;wBACjBC,WAAWpc,OAAO/B;;oBAGnBpK,KAAK+T,OAAO4U,kBAAkBjiB,MAAMshB,QAAQ/I,QAAQpY,SAASkQ;uBAEvD;oBACNkR,gBAAgB;oBAChBC,SAASU,YAAYxe;oBACrBnK,EAAE,WAAWgf,SAAS,MAAM+I,QAAQzY;wBAAKsZ,SAAS;uBAAI7e;;oBAEtD,IAAG+M,gBAAgBxV,aAAamF,KAAKmP,cAAckB,YAAYlB,aAAa7V,KAAKoE,KAAKoC,QAAQK,UAAU;wBACvG7G,KAAK2I,KAAKiB,QAAQf,OAAOhC;;;;gBAK3B,IAAIkQ,gBAAgBxV,aAAawV,YAAYlB,cAAcnP,KAAKmP,WAAW;oBAC1E7V,KAAKoE,KAAKqC,QAAQI,SAASH;uBAErB;oBACNzG,EAAE,WAAWgf,SAAS,MAAM+I,QAAQ3I,MAAMrf,KAAK+T,OAAO+U;;gBAGvD7oB,EAAE,WAAWgf,SAAS,MAAM+I,SAAS,aAAa3I,MAAM,SAAShN;oBAChErS,KAAK2I,KAAKyU,QAAQpT,KAAKqI,EAAEgP,eAAexa,SAASH;oBACjD2L,EAAEiP;;;gBAIH,IAAIvK,gBAAgBxV,aAAawV,YAAYuC,gBAAgB,UAAU5S,KAAKR,WAAW;oBACtFnG,MAAMU,KAAKyI,KAAK9E,KAAK8iB,cAAcrgB,SAASH,KAAKR;;mBAG5C,IAAGkP,WAAW,SAAS;gBAC7BpV,KAAK+T,OAAOgV,eAAe,UAAU9J,SAAS,MAAM+I;;gBAEpD,IAAIhoB,KAAK2I,KAAKuC,MAAMrE,SAASR,SAAS,QAAQ;oBAC7CrG,KAAK2I,KAAKmU,cAAcjW,SAAS5G,EAAEoI,KAAKqC,EAAE,kBAAiBhE,KAAKmP;uBAC1D;oBACN7V,KAAK2I,KAAKoV,YAAYlX,SAAS5G,EAAEoI,KAAKqC,EAAE,kBAAiBhE,KAAKmP;;mBAGzD,IAAGT,WAAW,cAAc;gBAClC6S,gBAAgB;gBAChBjoB,KAAK+T,OAAO2T,WAAWzI,QAAQvY;gBAC/B1G,KAAKoE,KAAKijB,4BAA4BpI,QAAQvY;gBAC9C1G,KAAKyd,YAAYiK,WAAW7gB,SAASH;gBACrC,IAAIqX,cAAc9d,EAAEoI,KAAKqC,EAAE,qBAAoBhE,KAAKgT,mBAAmBhT,KAAKmP;gBAC5E7V,KAAK2I,KAAKmU,cAAcjW,SAASkX;mBAE3B,IAAG3I,WAAW,QAAQ;gBAC5BpV,KAAK+T,OAAOgV,eAAe,UAAU9J,SAAS,MAAM+I;gBACpDhoB,KAAK2I,KAAKmU,cAAcjW,SAAS5G,EAAEoI,KAAKqC,EAAE,+BAA8BhE,KAAKmP;mBAEvE,IAAGT,WAAW,OAAO;gBAC3BpV,KAAK+T,OAAOgV,eAAe,UAAU9J,SAAS,MAAM+I;gBACpDhoB,KAAK2I,KAAKmU,cAAcjW,SAAS5G,EAAEoI,KAAKqC,EAAE,+BAA8BhE,KAAKmP;;;YAI9E9V,MAAMU,KAAKyI,KAAKP,KAAKuC,MAAMrE,SAASkb,aAAakG;YAEjD,IAAGphB,YAAY9G,MAAMU,KAAK6K,aAAazE,SAAS;gBAC/C9G,MAAMU,KAAKyI,KAAKP,KAAKiB,QAAQkY,gBAAgB/hB,MAAMU,KAAKyI,KAAKP,KAAKuC,MAAMrE,SAASkb;;;YAKlFvE,QAAQiI,UAAUxlB,EAAE,WAAWgf,SAAS,MAAM+I;;;;;;;;;;YAU9C/nB,EAAEF,OAAOmF,eAAe,kCAAkCsY;;;;;QAM3DsL,WAAW;YACV,IAAIvb,OAAOtN,EAAEgG;YACbjG,KAAKyd,YAAYO,KAAKzQ,KAAK4D,KAAK,aAAa5D,KAAK4D,KAAK,cAAc;;;;;;;QAQtEwX,mBAAmB,SAASjiB,MAAMshB,QAAQ/I,QAAQpY,SAASkQ;;YAE1D,IAAIiS,eAAe,UAAU/J,SAAS,MAAM+I,QAC3CiB,kBAAkBhpB,EAAE,MAAM+oB;YAC3B,KAAKtiB,KAAKgT,sBAAsBuP,mBAAmBA,gBAAgBC,GAAG,gBAAgB,OAAO;gBAC5FlpB,KAAK+T,OAAOoV,cAAcH;;gBAE1B,IAAGjS,gBAAgBxV,aAAamF,KAAKmP,cAAckB,YAAYlB,aAAa7V,KAAKoE,KAAKoC,QAAQK,UAAU;;oBAEvG,IAAI7G,KAAK2I,KAAKuC,MAAMrE,SAASR,SAAS,QAAQ;wBAC7CrG,KAAK2I,KAAKmU,cAAcjW,SAAS5G,EAAEoI,KAAKqC,EAAE,oBAAmBhE,KAAKmP;2BAC5D;wBACN7V,KAAK2I,KAAKoV,YAAYlX,SAAS5G,EAAEoI,KAAKqC,EAAE,oBAAmBhE,KAAKmP;;;;;;;;;;;QAYpEsT,eAAe,SAASC;YACvBnpB,EAAE,MAAMmpB,WAAWpG,KAAK,MAAMqG,UAAU,UAAU;gBACjDppB,EAAEgG,MAAMqjB;oBAAST,SAAS;;;;;;;;;;QAU5BE,gBAAgB,SAASK;YACxBnpB,EAAE,MAAMmpB,WAAWpG,KAAK,MAAM7R,KAAK,MAAM,MAAMiY,YAAY,YAAYE;gBAAST,SAAS;;gBACxFU,UAAU;oBACTtpB,EAAEgG,MAAMujB,QAAQ,UAAU;wBACzBvpB,EAAEgG,MAAM4B;;;;;;;;;;;;;;;QAgBZ6f,YAAY,SAASzI,QAAQvY;YAC5B3G,MAAMY,KAAK+B,IAAI;YACf,IAAI+mB,kBAAkB3oB,QAAQqV,kBAAkBzP,KAAKR,YAAY,MAAMQ,KAAKgT,mBAC3E0P,YAAY,UAAUnK,SAAS,MAAMlf,MAAM+C,KAAKyI,QAAQke,kBACxD3Y,KAAK7Q,EAAE,MAAMmpB;YAEdtY,GAAGK,KAAK,aAAazK,KAAKmP;YAC1B/E,GAAGK,KAAK,YAAYzK,KAAKR;YACzB4K,GAAGwJ,SAAS,aAAa9J,KAAK9J,KAAKmP;YACnC/E,GAAGK,KAAK,MAAM,UAAU8N,SAAS,MAAMlf,MAAM+C,KAAKyI,QAAQ7E,KAAKR;;;;;;IAOjElG,KAAKiE;;;;;;;;;;;;;;QAcJ6f,QAAQ,SAASvH;YAChB,IAAI1V,UAAU9G,MAAMU,KAAK6K,aAAazE,SACrCmY,WAAWjf,MAAMU,KAAKyI,KAAKP,KAAKuC,MAAMrE,SAASR,MAC/C0B,UAAU9H,EAAEgG,MAAMqU,SAAS,UAAU0J,MAAMnR,UAAU,GAAG9S,MAAMU,KAAKyG,aAAaY,KAAKC,QAAQE,OAC7FgU,cACAuB;gBACC3W,SAASA;gBACTkB,SAASA;gBACTkU,cAAcA;;;;;;;;;;;;;YAchB,IAAGhc,EAAEF,OAAOmF,eAAe,kCAAkCsY,aAAa,OAAO;gBAChFjB,MAAM4D;gBACN;;YAGDpY,UAAUyV,QAAQzV;YAClBkU,eAAeuB,QAAQvB;YAEvBlc,MAAMY,KAAKoF,OAAOnC,OAAOQ,KAAKH,QAAQ4C,SAASkB,SAASiX,UAAU/C;;YAElE,IAAG+C,aAAa,UAAUjX,SAAS;gBAClC/H,KAAKiE,QAAQ+F,KAAKnD,SAAS7G,KAAKoE,KAAKoC,QAAQK,SAASgP,WAAW9N;;;YAGlE9H,EAAEgG,MAAMqU,SAAS,UAAU0J,IAAI,IAAIza;YACnCgT,MAAM4D;;;;;;;;;;;;;;;;;QAkBPnW,MAAM,SAASnD,SAAS1G,MAAM4H,SAASkU,cAActN;YACpD5G,UAAUhI,MAAM+C,KAAKoH,OAAO6F,IAAIhI,QAAQ8K,UAAU,GAAG9S,MAAMU,KAAKyG,aAAaY,KAAKC,QAAQE;YAC1F,IAAGgU,cAAc;gBAChBA,eAAelc,MAAM+C,KAAKoJ,kBAAkB+P,cAAclc,MAAMU,KAAKyG,aAAaY,KAAKC,QAAQE;;YAGhG,IAAIuV;gBACH3W,SAAWA;gBACX1G,MAAQA;gBACR4H,SAAWA;gBACXkU,cAAgBA;;;;;;;;;;;;;YAcjB,IAAGhc,EAAEF,OAAOmF,eAAe,kCAAkCsY,aAAa,OAAO;gBAChF;;YAGDzV,UAAUyV,QAAQzV;YAClBkU,eAAeuB,QAAQvB;YACvB,IAAGA,iBAAiB1a,aAAa0a,aAAajQ,SAAS,GAAG;gBACzDjE,UAAUkU;;YAGX,KAAIlU,SAAS;gBACZ;;YAGD,IAAI2hB;gBACHC,UAAU5pB,MAAMU,KAAK8J,SAAStG,QAAQuW;gBACtCoP;oBACCzpB,MAAMA;oBACN0pB,aAAa9pB,MAAM+C,KAAKgF,KAAK3H,MAAMJ,MAAMU,KAAKyG,aAAaY,KAAKC,QAAQC;oBACxED,SAASA;oBACTgZ,MAAMhhB,MAAM+C,KAAKuL,cAAcM,aAAa,IAAIlC,OAAOuU;;;;;;;;;;;;;;YAezD/gB,EAAEF,OAAOmF,eAAe,oCAAoCwkB;YAE5D,IAAItf,OAAOC,SAASC,QAAQof,cAAcC,UAAUD,cAAcE;YAClE5pB,KAAKoE,KAAK6c,oBAAoBpa,SAASuD;YACvC,IAAImD,OAAOvN,KAAKoE,KAAK6b,QAAQpZ,SAAS,iBAAiByT,WAAWsM;;YAElErZ,KAAKsN,KAAK,WAAWwE,MAAM,SAAS9C;gBACnCA,MAAM4D;;gBAEN,IAAItI,OAAO9X,MAAMY,KAAKwG,QAAQN;gBAC9B,IAAGgR,QAAQ1X,SAASH,KAAKoE,KAAKoC,QAAQzG,MAAMU,KAAK6K,aAAazE,SAASgP,aAAagC,KAAKK,YAAY7L,IAAIxF,UAAU,MAAM1G,OAAO;oBAC/H,IAAGJ,MAAMU,KAAKyI,KAAKuU,YAAYO,KAAKnX,UAAU,MAAM1G,MAAMA,MAAM,UAAU,OAAO;wBAChF,OAAO;;;;;YAMV,IAAGJ,MAAMU,KAAK6K,aAAazE,YAAYA,YAAY7G,KAAKmJ,OAAOmV,YAAY;gBAC1Ete,KAAK2I,KAAK4V,uBAAuB1X;gBACjC,IAAG9G,MAAMU,KAAKyI,KAAKP,KAAKuC,MAAMrE,SAASR,SAAS,WAAWrG,KAAKmJ,OAAOmV,YAAY;oBAClFte,KAAK2I,KAAKiB,QAAQoY;;;YAGpB,IAAGjiB,MAAMU,KAAK6K,aAAazE,YAAYA,SAAS;gBAC/C7G,KAAKoE,KAAK8c,eAAera;;YAG1B2W,QAAQiI,UAAUlY;;;;;;;;;;YAWlBtN,EAAEF,OAAOmF,eAAe,iCAAiCsY;;;IAI3D,OAAOxd;EACND,MAAMU,KAAKyI,YAAYrI;;;;;;;;;;;;;ACliEzB;;;;;;AAOAd,MAAMU,KAAK8J,WAAY,SAASvK;IAC/BA,KAAKmJ;;;;QAIJ0V,gBAAgB;;IAGjB7e,KAAK2I;QACJ6B,MAAM;QACNU,OAAO;QACPD,MAAM;QACNiU,KAAK,+FACH,oFACA,uEACA;QACF/T,OAAO,mFACL,uCACA,8EACA;QACF0R,cAAc,0DACZ,0CACA;QACFkB,aAAa,yDACX;QACF3S,SAAS,2BACP,sEACA,wGACA,gGACA,iGACA,4EACA,+DACA;QACFC,cAAc,2EACV,kDACA,sHACA,sHACA,uHACA;QACJ+R;YACC6H,MAAM,2DACL;YACDE,WAAW;YACXe,kBAAkB,8CACd,wDACA,4EACA;YACJ7I,oBAAoB,8DAChB;;QAELoH,SAAS,sDACN;;IAGJzkB,KAAKoE;QACJoG,MAAM,oIACL;QACDkN,SAAS,qDACP,4CACA;QACF4L,MAAM,uCACJ,8CACA,gIACA;;IAGHtjB,KAAK+T;QACJvJ,MAAM;QACN9D,MAAM,iFACJ,4DACA,mFACA,iDACA,yEACA,oGACA;;IAGH1G,KAAKiE;QACJuG,MAAM;QACNgQ,MAAM,qCACJ,+DACA;;IAGHxa,KAAKuF;QACJ+d,MAAM,4DACL,+IACA,yEACA,2EACA,2GACA,yEACA,gFACA;;IAGFtjB,KAAK8I;QACJmb,mBAAmB,gCAClB,8EACA,4GACA;QACDG,sBAAsB,gCACrB,oFACA,wGACA;QACDE,cAAc;;IAGf,OAAOtkB;EACND,MAAMU,KAAK8J;;;;;;;;;;;;;ACnHb;;;;;;AAOAxK,MAAMU,KAAK8H;IACVuhB;QACC9iB,QAAU;QACV+iB,kBAAoB;QACpBC,iBAAoB;QACpBC,qBAAuB;QACvBC,oBAAuB;QACvBC,gBAAkB;QAElBC,aAAiB;QACjBC,eAAiB;QAEjBC,eAAiB;QACjBC,eAAiB;QACjBC,eAAiB;QACjBC,aAAiB;QACjBC,cAAkB;QAElBpU,QAAc;QACdoB,SAAe;QACfiT,WAAiB;QACjBC,iBAAqB;QACrBC,qBAA0B;QAC1BC,mBAA0B;QAC1BC,gBAAoB;QACpBC,qBAA0B;QAC1BC,mBAA0B;QAE1BC,oBAAuB;QACvBC,mBAAuB;QACvBC,qBAAwB;QAExBC,uBAAyB;QAEzBC,6BAAgC;QAEhCC,gBAA6B;QAC7BC,cAA6B;QAC7BC,2BAA6B;QAC7BC,2BAA6B;QAC7BC,iBAAmB;QAEnBC,+BAAiC;QACjCC,wBAAiC;QAEjCC,YAAc;QACdC,YAAc;QAEd3D,aAAkB;QAClBC,gBAAoB;QACpB5d,kBAAsB;QACtBE,cAAmB;QACnBC,mBAAuB;QACvBC,sBAAyB;QACzBC,uBAA0B;QAC1BC,kBAAsB;QAEtBihB,mBAAsB;QACtBC,yBAA4B;QAC5BC,wBAA2B;QAE3BC,kBAAoB;QAEpBC,kBAAoB;QACpBC,0BAA4B;QAC5BC,sBAAwB;QAExBC,iBAAoB;;IAErBC;QACCxlB,QAAU;QACV+iB,kBAAoB;QACpBC,iBAAoB;QACpBC,qBAAuB;QACvBC,oBAAuB;QACvBC,gBAAkB;QAElBC,aAAiB;QACjBC,eAAiB;QAEjBC,eAAiB;QACjBC,eAAiB;QACjBC,eAAiB;QACjBC,aAAiB;QACjBC,cAAkB;QAElBpU,QAAc;QACdoB,SAAe;QACfiT,WAAiB;QACjBC,iBAAqB;QACrBC,qBAA0B;QAC1BC,mBAA0B;QAC1BC,gBAAoB;QACpBC,qBAA0B;QAC1BC,mBAA0B;QAE1BC,oBAAuB;QACvBC,mBAAuB;QACvBC,qBAAwB;QAExBC,uBAAyB;QAEzBC,6BAAgC;QAEhCC,gBAA6B;QAC7BC,cAA6B;QAC7BC,2BAA6B;QAC7BC,2BAA6B;QAC7BC,iBAAmB;QAEnBC,+BAAiC;QACjCC,wBAAiC;QAEjCC,YAAc;QACdC,YAAc;QAEd3D,aAAkB;QAClBC,gBAAoB;QACpB5d,kBAAsB;QACtBE,cAAmB;QACnBC,mBAAuB;QACvBC,sBAAyB;QACzBC,uBAA0B;QAC1BC,kBAAsB;QAEtBihB,mBAAsB;QACtBC,yBAA4B;QAC5BC,wBAA2B;QAE3BC,kBAAoB;QAEpBC,kBAAoB;QACpBC,0BAA4B;QAC5BC,sBAAwB;QAExBC,iBAAoB;;IAErBE;QACCzlB,QAAU;QACV+iB,kBAAoB;QACpBC,iBAAoB;QACpBC,qBAAuB;QACvBC,oBAAuB;QACvBC,gBAAkB;QAElBC,aAAiB;QACjBC,eAAiB;QAEjBC,eAAiB;QACjBE,eAAiB;QACjBC,aAAiB;QACjBC,cAAkB;QAElBpU,QAAc;QACdoB,SAAe;QACfiT,WAAiB;QACjBC,iBAAqB;QACrBC,qBAA0B;QAC1BC,mBAA0B;QAC1BC,gBAAoB;QACpBC,qBAA0B;QAC1BC,mBAA0B;QAE1BC,oBAAuB;QACvBC,mBAAuB;QACvBC,qBAAwB;QAExBC,uBAAyB;QAEzBC,6BAAgC;QAEhCC,gBAA6B;QAC7BC,cAA6B;QAC7BC,2BAA6B;QAC7BC,2BAA6B;QAE7BE,+BAAiC;QACjCC,wBAAiC;QAEjCC,YAAc;QACdC,YAAc;QAEd3D,aAAkB;QAClBC,gBAAoB;QACpB5d,kBAAsB;QACtBE,cAAmB;QACnBC,mBAAuB;QACvBC,sBAAyB;QACzBC,uBAA0B;QAC1BC,kBAAsB;QAEtBihB,mBAAsB;QACtBC,yBAA4B;QAC5BC,wBAA2B;QAE3BC,kBAAoB;QAEpBC,kBAAoB;QACpBC,0BAA4B;QAE5BE,iBAAoB;;IAErBG;QACC1lB,QAAU;QACV+iB,kBAAoB;QACpBC,iBAAoB;QACpBC,qBAAuB;QACvBC,oBAAuB;QACvBC,gBAAkB;QAElBC,aAAiB;QACjBC,eAAiB;QAEjBC,eAAiB;QACjBE,eAAiB;QACjBC,aAAiB;QACjBC,cAAkB;QAElBpU,QAAc;QACdoB,SAAe;QACfiT,WAAiB;QACjBC,iBAAqB;QACrBC,qBAA0B;QAC1BC,mBAA0B;QAC1BC,gBAAoB;QACpBC,qBAA0B;QAC1BC,mBAA0B;QAE1BC,oBAAuB;QACvBC,mBAAuB;QACvBC,qBAAwB;QAExBC,uBAAyB;QAEzBC,6BAAgC;QAEhCC,gBAA6B;QAC7BC,cAA6B;QAC7BC,2BAA6B;QAC7BC,2BAA6B;QAE7BE,+BAAiC;QACjCC,wBAAiC;QAEjCC,YAAc;QACdC,YAAc;QAEd3D,aAAkB;QAClBC,gBAAoB;QACpB5d,kBAAsB;QACtBE,cAAmB;QACnBC,mBAAuB;QACvBC,sBAAyB;QACzBC,uBAA0B;QAC1BC,kBAAsB;QAEtBihB,mBAAsB;QACtBC,yBAA4B;QAC5BC,wBAA2B;QAE3BC,kBAAoB;QAEpBC,kBAAoB;QACpBC,0BAA4B;QAE5BE,iBAAoB;;IAErBI;QACC3lB,QAAU;QACV+iB,kBAAoB;QACpBC,iBAAoB;QACpBC,qBAAuB;QACvBC,oBAAuB;QACvBC,gBAAkB;QAElBC,aAAiB;QACjBC,eAAiB;QAEjBC,eAAiB;QACjBE,eAAiB;QACjBC,aAAiB;QACjBC,cAAkB;QAElBpU,QAAc;QACdoB,SAAe;QACfiT,WAAiB;QACjBC,iBAAqB;QACrBC,qBAA0B;QAC1BC,mBAA0B;QAC1BC,gBAAoB;QACpBC,qBAA0B;QAC1BC,mBAA0B;QAE1BC,oBAAuB;QACvBC,mBAAuB;QACvBC,qBAAwB;QAExBC,uBAAyB;QAEzBC,6BAAgC;QAEhCC,gBAA6B;QAC7BC,cAA6B;QAC7BC,2BAA6B;QAC7BC,2BAA6B;QAE7BE,+BAAiC;QACjCC,wBAAiC;QAEjCC,YAAc;QACdC,YAAc;QAEd3D,aAAkB;QAClBC,gBAAoB;QACpB5d,kBAAsB;QACtBE,cAAmB;QACnBC,mBAAuB;QACvBC,sBAAyB;QACzBC,uBAA0B;QAC1BC,kBAAsB;QAEtBihB,mBAAsB;QACtBC,yBAA4B;QAC5BC,wBAA2B;QAE3BC,kBAAoB;QAEpBC,kBAAoB;QACpBC,0BAA4B;QAE5BE,iBAAoB;;IAErBK;QACC5lB,QAAU;QACV+iB,kBAAoB;QACpBC,iBAAmB;QACnBC,qBAAuB;QACvBC,oBAAsB;QACtBC,gBAAkB;QAElBC,aAAe;QACfC,eAAiB;QAEjBC,eAAiB;QACjBE,eAAiB;QACjBC,aAAe;QACfC,cAAgB;QAEhBpU,QAAU;QACVoB,SAAW;QACXiT,WAAa;QACbC,iBAAmB;QACnBC,qBAAuB;QACvBE,gBAAkB;QAClBC,qBAAuB;QAEvBE,oBAAsB;QACtBC,mBAAqB;QACrBC,qBAAuB;QAEvBC,uBAAyB;QAEzBC,6BAA+B;QAE/BC,gBAAkB;QAClBC,cAAgB;QAChBC,2BAA6B;QAC7BC,2BAA6B;QAE7BE,+BAAiC;QACjCC,wBAA0B;QAE1BC,YAAc;QACdC,YAAc;QAEd3D,aAAe;QACfC,gBAAkB;QAClB5d,kBAAoB;QACpBE,cAAgB;QAChBC,mBAAqB;QACrBC,sBAAwB;QACxBC,uBAAyB;QACzBC,kBAAoB;QAEpBihB,mBAAqB;QACrBC,yBAA2B;QAC3BC,wBAA0B;QAE1BC,kBAAoB;QAEpBC,kBAAoB;QACpBC,0BAA4B;QAE5BE,iBAAmB;;IAEpBM;QACC7lB,QAAkB;QAClB+iB,kBAAsB;QACtBC,iBAAsB;QACtBC,qBAA0B;QAC1BC,oBAA0B;QAC1BC,gBAAsB;QAEtBC,aAAsB;QACtBC,eAAsB;QAEtBC,eAAsB;QACtBE,eAAsB;QACtBC,aAAsB;QACtBC,cAAsB;QAEtBpU,QAAkB;QAClBoB,SAAkB;QAClBiT,WAAkB;QAClBC,iBAAsB;QACtBC,qBAA0B;QAC1BC,mBAA0B;QAC1BC,gBAAsB;QACtBC,qBAA0B;QAC1BC,mBAA0B;QAE1BC,oBAA0B;QAC1BC,mBAAsB;QACtBC,qBAA0B;QAE1BC,uBAA8B;QAE9BC,6BAAkC;QAElCC,gBAA0B;QAC1BC,cAA0B;QAC1BC,2BAA8B;QAC9BC,2BAA8B;QAE9BE,+BAAkC;QAClCC,wBAA8B;QAE9BC,YAAsB;QACtBC,YAAsB;QAEtB3D,aAAsB;QACtBC,gBAAsB;QACtB5d,kBAAsB;QACtBE,cAAsB;QACtBC,mBAAsB;QACtBC,sBAA0B;QAC1BC,uBAA0B;QAC1BC,kBAAsB;QAEtBihB,mBAA0B;QAC1BC,yBAA8B;QAC9BC,wBAA8B;QAE9BC,kBAAsB;QAEtBC,kBAA0B;QAC1BC,0BAA8B;QAE9BE,iBAAsB;;IAEvBO;QACC9lB,QAAU;QACV+iB,kBAAoB;QACpBC,iBAAoB;QACpBC,qBAAuB;QACvBC,oBAAuB;QACvBC,gBAAkB;QAElBC,aAAiB;QACjBC,eAAiB;QAEjBC,eAAiB;QACjBE,eAAiB;QACjBC,aAAiB;QACjBC,cAAkB;QAElBpU,QAA0B;QAC1BoB,SAA0B;QAC1BiT,WAA0B;QAC1BC,iBAA0B;QAC1BC,qBAA0B;QAC1BC,mBAA0B;QAC1BC,gBAA0B;QAC1BC,qBAA0B;QAC1BC,mBAA0B;QAE1BC,oBAAuB;QACvBC,mBAAuB;QACvBC,qBAAwB;QAExBC,uBAAyB;QAEzBC,6BAAgC;QAEhCC,gBAA6B;QAC7BC,cAA6B;QAC7BC,2BAA6B;QAC7BC,2BAA6B;QAE7BE,+BAAiC;QACjCC,wBAAiC;QAEjCC,YAAc;QACdC,YAAc;QAEd3D,aAA0B;QAC1BC,gBAA0B;QAC1B5d,kBAA0B;QAC1BE,cAA0B;QAC1BC,mBAA0B;QAC1BC,sBAA0B;QAC1BC,uBAA0B;QAC1BC,kBAA0B;QAE1BihB,mBAAsB;QACtBC,yBAA4B;QAC5BC,wBAA2B;QAE3BC,kBAAoB;QAEpBC,kBAAoB;QACpBC,0BAA4B;QAE5BE,iBAAoB;;IAErBQ;QACC/lB,QAAU;QACV+iB,kBAAoB;QACpBC,iBAAoB;QACpBC,qBAAuB;QACvBC,oBAAuB;QACvBC,gBAAkB;QAElBC,aAAiB;QACjBC,eAAiB;QAEjBC,eAAiB;QACjBE,eAAiB;QACjBC,aAAiB;QACjBC,cAAkB;QAElBpU,QAA0B;QAC1BoB,SAA0B;QAC1BiT,WAA0B;QAC1BC,iBAA0B;QAC1BC,qBAA0B;QAC1BC,mBAA0B;QAC1BC,gBAA0B;QAC1BC,qBAA0B;QAC1BC,mBAA0B;QAE1BC,oBAAuB;QACvBC,mBAAuB;QACvBC,qBAAwB;QAExBC,uBAAyB;QAEzBC,6BAAgC;QAEhCC,gBAA6B;QAC7BC,cAA6B;QAC7BC,2BAA6B;QAC7BC,2BAA6B;QAE7BE,+BAAiC;QACjCC,wBAAiC;QAEjCC,YAAc;QACdC,YAAc;QAEd3D,aAA0B;QAC1BC,gBAA0B;QAC1B5d,kBAA0B;QAC1BE,cAA0B;QAC1BC,mBAA0B;QAC1BC,sBAA0B;QAC1BC,uBAA0B;QAC1BC,kBAA0B;QAE1BihB,mBAAsB;QACtBC,yBAA4B;QAC5BC,wBAA2B;QAE3BC,kBAAoB;QAEpBC,kBAAoB;QACpBC,0BAA4B;QAE5BE,iBAAoB;;IAErBS;QACChmB,QAAU;QACV+iB,kBAAoB;QACpBC,iBAAoB;QACpBC,qBAAuB;QACvBC,oBAAuB;QACvBC,gBAAkB;QAElBC,aAAiB;QACjBC,eAAiB;QAEjBC,eAAiB;QACjBE,eAAiB;QACjBC,aAAiB;QACjBC,cAAkB;QAElBpU,QAAc;QACdoB,SAAe;QACfiT,WAAiB;QACjBC,iBAAqB;QACrBC,qBAA0B;QAC1BC,mBAA0B;QAC1BC,gBAAoB;QACpBC,qBAA0B;QAC1BC,mBAA0B;QAE1BC,oBAAuB;QACvBC,mBAAuB;QACvBC,qBAAwB;QAExBC,uBAAyB;QAEzBC,6BAAgC;QAEhCC,gBAA6B;QAC7BC,cAA6B;QAC7BC,2BAA6B;QAC7BC,2BAA6B;QAE7BG,wBAAiC;QAEjCC,YAAc;QACdC,YAAc;QAEd3D,aAAkB;QAClBC,gBAAoB;QACpB5d,kBAAsB;QACtBE,cAAmB;QACnBC,mBAAuB;QACvBC,sBAAyB;QACzBC,uBAA0B;QAC1BC,kBAAsB;QAEtBihB,mBAAsB;QACtBC,yBAA4B;QAC5BC,wBAA2B;QAE3BC,kBAAoB;QAEpBC,kBAAoB;QACpBC,0BAA4B;QAE5BE,iBAAoB;;IAErBU;QACCjmB,QAAU;QACV+iB,kBAAoB;QACpBC,iBAAoB;QACpBC,qBAAuB;QACvBC,oBAAuB;QACvBC,gBAAkB;QAElBC,aAAgB;QAChBC,eAAiB;QAEjBC,eAAiB;QACjBE,eAAiB;QACjBC,aAAgB;QAChBC,cAAiB;QAEjBpU,QAA0C;QAC1CoB,SAA2C;QAC3CiT,WAA6C;QAC7CC,iBAAmC;QACnCC,qBAAwB;QACxBC,mBAAsB;QACtBC,gBAAkC;QAClCC,qBAAwB;QACxBC,mBAAsB;QAEtBC,oBAAuB;QACvBC,mBAAsB;QACtBC,qBAAwB;QAExBC,uBAAyB;QAEzBC,6BAAgC;QAEhCC,gBAAmB;QACnBC,cAAiB;QACjBC,2BAA6B;QAC7BC,2BAA6B;QAE7BE,+BAAiC;QACjCC,wBAA2B;QAE3BC,YAAc;QACdC,YAAc;QAEd3D,aAAuC;QACvCC,gBAAkC;QAClC5d,kBAAoC;QACpCE,cAAwC;QACxCC,mBAAqC;QACrCC,sBAAgC;QAChCC,uBAAiC;QACjCC,kBAAoC;QAEpCihB,mBAAsB;QACtBC,yBAA4B;QAC5BC,wBAA2B;QAE3BC,kBAAoB;QAEpBC,kBAAoB;QACpBC,0BAA4B;QAE5BE,iBAAoB;;IAErBW;QACClmB,QAAU;QACV+iB,kBAAoB;QACpBC,iBAAoB;QACpBC,qBAAuB;QACvBC,oBAAuB;QACvBC,gBAAkB;QAElBC,aAAiB;QACjBC,eAAiB;QAEjBC,eAAiB;QACjBE,eAAiB;QACjBC,aAAiB;QACjBC,cAAkB;QAElBpU,QAAc;QACdoB,SAAe;QACfiT,WAAiB;QACjBC,iBAAqB;QACrBC,qBAA0B;QAC1BC,mBAA0B;QAC1BC,gBAAoB;QACpBC,qBAA0B;QAC1BC,mBAA0B;QAE1BC,oBAAuB;QACvBC,mBAAuB;QACvBC,qBAAwB;QAExBC,uBAAyB;QAEzBC,6BAAgC;QAEhCC,gBAA6B;QAC7BC,cAA6B;QAC7BC,2BAA6B;QAC7BC,2BAA6B;QAE7BE,+BAAiC;QACjCC,wBAAiC;QAEjCC,YAAc;QACdC,YAAc;QAEd3D,aAAkB;QAClBC,gBAAoB;QACpB5d,kBAAsB;QACtBE,cAAmB;QACnBC,mBAAuB;QACvBC,sBAAyB;QACzBC,uBAA0B;QAC1BC,kBAAsB;QAEtBihB,mBAAsB;QACtBC,yBAA4B;QAC5BC,wBAA2B;QAE3BC,kBAAoB;QAEpBC,kBAAoB;QACpBC,0BAA4B;QAE5BE,iBAAoB;;IAErBY;QACCnmB,QAAU;QACV+iB,kBAAoB;QACpBC,iBAAoB;QACpBC,qBAAuB;QACvBC,oBAAuB;QACvBC,gBAAkB;QAElBC,aAAiB;QACjBC,eAAiB;QAEjBC,eAAiB;QACjBE,eAAiB;QACjBC,aAAiB;QACjBC,cAAkB;QAElBpU,QAA0B;QAC1BoB,SAA0B;QAC1BiT,WAA0B;QAC1BC,iBAA0B;QAC1BC,qBAA0B;QAC1BC,mBAA0B;QAC1BC,gBAA0B;QAC1BC,qBAA0B;QAC1BC,mBAA0B;QAE1BC,oBAAuB;QACvBC,mBAAuB;QACvBC,qBAAwB;QAExBC,uBAAyB;QAEzBC,6BAAgC;QAEhCC,gBAA6B;QAC7BC,cAA6B;QAC7BC,2BAA6B;QAC7BC,2BAA6B;QAE7BE,+BAAiC;QACjCC,wBAAiC;QAEjCC,YAAc;QACdC,YAAc;QAEd3D,aAA0B;QAC1BC,gBAA0B;QAC1B5d,kBAA0B;QAC1BE,cAA0B;QAC1BC,mBAA0B;QAC1BC,sBAA0B;QAC1BC,uBAA0B;QAC1BC,kBAA0B;QAE1BihB,mBAAsB;QACtBC,yBAA4B;QAC5BC,wBAA2B;QAE3BC,kBAAoB;QAEpBC,kBAAoB;QACpBC,0BAA4B;QAE5BE,iBAAoB"} \ No newline at end of file
diff --git a/candy.min.js b/candy.min.js
deleted file mode 100644
index 474fc85..0000000
--- a/candy.min.js
+++ /dev/null
@@ -1,4 +0,0 @@
-"use strict";var Candy=function(a,b){return a.about={name:"Candy",version:"1.7.1"},a.init=function(c,d){d.viewClass||(d.viewClass=a.View),d.viewClass.init(b("#candy"),d.view),a.Core.init(c,d.core)},a}(Candy||{},jQuery);Candy.Core=function(a,b,c){var d,e=null,f=null,g=null,h={},i=!1,j={autojoin:void 0,debug:!1,disableWindowUnload:!1,presencePriority:1,resource:Candy.about.name},k=function(a,c){b.addNamespace(a,c)},l=function(){k("PRIVATE","jabber:iq:private"),k("BOOKMARKS","storage:bookmarks"),k("PRIVACY","jabber:iq:privacy"),k("DELAY","jabber:x:delay"),k("PUBSUB","http://jabber.org/protocol/pubsub")},m=function(a){var c=b.getNodeFromJid(a),d=b.getDomainFromJid(a);return c?b.escapeNode(c)+"@"+d:d};return a.init=function(d,g){f=d,c.extend(!0,j,g),j.debug&&(void 0!==typeof window.console&&void 0!==typeof window.console.log&&(a.log=Function.prototype.bind&&Candy.Util.getIeVersion()>8?Function.prototype.bind.call(console.log,console):function(){Function.prototype.apply.call(console.log,console,arguments)}),a.log("[Init] Debugging enabled")),l(),e=new b.Connection(f),e.rawInput=a.rawInput.bind(a),e.rawOutput=a.rawOutput.bind(a),e.caps.node="https://candy-chat.github.io/candy/",j.disableWindowUnload||(window.onbeforeunload=a.onWindowUnload)},a.registerEventHandlers=function(){a.addHandler(a.Event.Jabber.Version,b.NS.VERSION,"iq"),a.addHandler(a.Event.Jabber.Presence,null,"presence"),a.addHandler(a.Event.Jabber.Message,null,"message"),a.addHandler(a.Event.Jabber.Bookmarks,b.NS.PRIVATE,"iq"),a.addHandler(a.Event.Jabber.Room.Disco,b.NS.DISCO_INFO,"iq","result"),a.addHandler(e.disco._onDiscoInfo.bind(e.disco),b.NS.DISCO_INFO,"iq","get"),a.addHandler(e.disco._onDiscoItems.bind(e.disco),b.NS.DISCO_ITEMS,"iq","get"),a.addHandler(e.caps._delegateCapabilities.bind(e.caps),b.NS.CAPS)},a.connect=function(d,f,h){e.reset(),a.registerEventHandlers(),c(Candy).triggerHandler("candy:core.before-connect",{connection:e}),i=i?!0:d&&d.indexOf("@")<0,d&&f?(e.connect(m(d)+"/"+j.resource,f,Candy.Core.Event.Strophe.Connect),g=h?new a.ChatUser(d,h):new a.ChatUser(d,b.getNodeFromJid(d))):d&&h?(e.connect(m(d)+"/"+j.resource,null,Candy.Core.Event.Strophe.Connect),g=new a.ChatUser(null,h)):d?Candy.Core.Event.Login(d):Candy.Core.Event.Login()},a.attach=function(c,d,f){g=new a.ChatUser(c,b.getNodeFromJid(c)),a.registerEventHandlers(),e.attach(c,d,f,Candy.Core.Event.Strophe.Connect)},a.disconnect=function(){e.connected&&(c.each(a.getRooms(),function(){Candy.Core.Action.Jabber.Room.Leave(this.getJid())}),e.disconnect())},a.addHandler=function(a,b,c,d,f,g,h){return e.addHandler(a,b,c,d,f,g,h)},a.getUser=function(){return g},a.setUser=function(a){g=a},a.getConnection=function(){return e},a.removeRoom=function(a){delete h[a]},a.getRooms=function(){return h},a.getStropheStatus=function(){return d},a.setStropheStatus=function(a){d=a},a.isAnonymousConnection=function(){return i},a.getOptions=function(){return j},a.getRoom=function(a){return h[a]?h[a]:null},a.onWindowUnload=function(){e.options.sync=!0,a.disconnect(),e.flush()},a.rawInput=function(a){this.log("RECV: "+a)},a.rawOutput=function(a){this.log("SENT: "+a)},a.log=function(){},a}(Candy.Core||{},Strophe,jQuery),Candy.View=function(a,b){var c={container:null,roomJid:null},d={language:"en",assets:"res/",messages:{limit:2e3,remove:500},crop:{message:{nickname:15,body:1e3},roster:{nickname:15}},enableXHTML:!1},e=function(c){b.i18n.load(a.Translation[c])},f=function(){b(Candy).on("candy:core.chat.connection",a.Observer.Chat.Connection),b(Candy).on("candy:core.chat.message",a.Observer.Chat.Message),b(Candy).on("candy:core.login",a.Observer.Login),b(Candy).on("candy:core.autojoin-missing",a.Observer.AutojoinMissing),b(Candy).on("candy:core.presence",a.Observer.Presence.update),b(Candy).on("candy:core.presence.leave",a.Observer.Presence.update),b(Candy).on("candy:core.presence.room",a.Observer.Presence.update),b(Candy).on("candy:core.presence.error",a.Observer.PresenceError),b(Candy).on("candy:core.message",a.Observer.Message)},g=function(){Candy.Util.getIeVersion()<9?b(document).focusin(Candy.View.Pane.Window.onFocus).focusout(Candy.View.Pane.Window.onBlur):b(window).focus(Candy.View.Pane.Window.onFocus).blur(Candy.View.Pane.Window.onBlur),b(window).resize(Candy.View.Pane.Chat.fitTabs)},h=function(){a.Pane.Chat.Toolbar.init()},i=function(){b("body").delegate("li[data-tooltip]","mouseenter",Candy.View.Pane.Chat.Tooltip.show)};return a.init=function(a,j){j.resources&&(j.assets=j.resources),delete j.resources,b.extend(!0,d,j),e(d.language),Candy.Util.Parser.setEmoticonPath(this.getOptions().assets+"img/emoticons/"),c.container=a,c.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"),assetsPath:this.getOptions().assets},{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})),g(),h(),f(),i()},a.getCurrent=function(){return c},a.getOptions=function(){return d},a}(Candy.View||{},jQuery),Candy.Util=function(a,b){a.jidToId=function(a){return MD5.hexdigest(a)},a.escapeJid=function(a){var b=Strophe.escapeNode(Strophe.getNodeFromJid(a)),c=Strophe.getDomainFromJid(a),d=Strophe.getResourceFromJid(a);return a=b+"@"+c,d&&(a+="/"+d),a},a.unescapeJid=function(a){var b=Strophe.unescapeNode(Strophe.getNodeFromJid(a)),c=Strophe.getDomainFromJid(a),d=Strophe.getResourceFromJid(a);return a=b+"@"+c,d&&(a+="/"+d),a},a.crop=function(a,b){return a.length>b&&(a=a.substr(0,b-3)+"..."),a},a.parseAndCropXhtml=function(c,d){return b("<div/>").append(a.createHtml(b(c).get(0),d)).html()},a.setCookie=function(a,b,c){var d=new Date;d.setDate((new Date).getDate()+c),document.cookie=a+"="+b+";expires="+d.toUTCString()+";path=/"},a.cookieExists=function(a){return document.cookie.indexOf(a)>-1},a.getCookie=function(a){if(document.cookie){var b=new RegExp(escape(a)+"=([^;]*)","gm"),c=b.exec(document.cookie);if(c)return c[1]}},a.deleteCookie=function(a){document.cookie=a+"=;expires=Thu, 01-Jan-70 00:00:01 GMT;path=/"},a.getPosLeftAccordingToWindowBounds=function(a,c){var d=b(document).width(),e=a.outerWidth(),f=e-a.outerWidth(!0),g="left";return c+e>=d&&(c-=e-f,g="right"),{px:c,backgroundPositionAlignment:g}},a.getPosTopAccordingToWindowBounds=function(a,c){var d=b(document).height(),e=a.outerHeight(),f=e-a.outerHeight(!0),g="top";return c+e>=d&&(c-=e-f,g="bottom"),{px:c,backgroundPositionAlignment:g}},a.localizedTime=function(c){if(void 0===c)return void 0;var d=a.iso8601toDate(c);return d.format(d.toDateString()===(new Date).toDateString()?b.i18n._("timeFormat"):b.i18n._("dateFormat"))},a.iso8601toDate=function(a){var b=Date.parse(a);if(isNaN(b)){var c=/^(\d{4}|[+\-]\d{6})-(\d{2})-(\d{2})(?:[T ](\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3,}))?)?(?:(Z)|([+\-])(\d{2})(?::?(\d{2}))?))?/.exec(a);if(c){var d=0;return"Z"!==c[8]&&(d=60*+c[10]+ +c[11],"+"===c[9]&&(d=-d)),d-=(new Date).getTimezoneOffset(),new Date(+c[1],+c[2]-1,+c[3],+c[4],+c[5]+d,+c[6],c[7]?+c[7].substr(0,3):0)}b=Date.parse(a.replace(/^(\d{4})(\d{2})(\d{2})/,"$1-$2-$3")+"Z")}return new Date(b)},a.isEmptyObject=function(a){var b;for(b in a)if(a.hasOwnProperty(b))return!1;return!0},a.forceRedraw=function(a){a.css({display:"none"}),setTimeout(function(){this.css({display:"block"})}.bind(a),1)};var c=function(){for(var a,b=3,c=document.createElement("div"),d=c.getElementsByTagName("i");c.innerHTML="<!--[if gt IE "+ ++b+"]><i></i><![endif]-->",d[0];);return b>4?b:a}();return a.getIeVersion=function(){return c},a.Parser={_emoticonPath:"",setEmoticonPath:function(a){this._emoticonPath=a},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|$))/gim,image:"Tongue_Out.png"},{plain:";P",regex:/((\s);-?P|;-?P(\s|$))/gim,image:"Tongue_Out_Winking.png"},{plain:":S",regex:/((\s):-?S|:-?S(\s|$))/gim,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(a){var b;for(b=this.emoticons.length-1;b>=0;b--)a=a.replace(this.emoticons[b].regex,'$2<img class="emoticon" alt="$1" src="'+this._emoticonPath+this.emoticons[b].image+'" />$3');return a},linkify:function(a){return a=a.replace(/(^|[^\/])(www\.[^\.]+\.[\S]+(\b|$))/gi,"$1http://$2"),a.replace(/(\b(https?|ftp|file):\/\/[\-A-Z0-9+&@#\/%?=~_|!:,.;]*[\-A-Z0-9+&@#\/%=~_|])/gi,'<a href="$1" target="_blank">$1</a>')},escape:function(a){return b("<div/>").text(a).html()},nl2br:function(a){return a.replace(/\r\n|\r|\n/g,"<br />")},all:function(a){return a&&(a=this.escape(a),a=this.linkify(a),a=this.emotify(a),a=this.nl2br(a)),a}},a.createHtml=function(c,d,e){e=e||0;var f,g,h,i,j,k,l,m,n,o,p;if(c.nodeType===Strophe.ElementType.NORMAL)if(i=c.nodeName.toLowerCase(),Strophe.XHTML.validTag(i))try{for(g=b("<"+i+"/>"),f=0;f<Strophe.XHTML.attributes[i].length;f++)if(j=Strophe.XHTML.attributes[i][f],k=c.getAttribute(j),"undefined"!=typeof k&&null!==k&&""!==k&&k!==!1&&0!==k)if("style"===j&&"object"==typeof k&&"undefined"!=typeof k.cssText&&(k=k.cssText),"style"===j){for(l=[],m=k.split(";"),h=0;h<m.length;h++)n=m[h].split(":"),o=n[0].replace(/^\s*/,"").replace(/\s*$/,"").toLowerCase(),Strophe.XHTML.validCSS(o)&&(p=n[1].replace(/^\s*/,"").replace(/\s*$/,""),l.push(o+": "+p));l.length>0&&(k=l.join("; "),g.attr(j,k))}else g.attr(j,k);for(f=0;f<c.childNodes.length;f++)g.append(a.createHtml(c.childNodes[f],d,e))}catch(q){Candy.Core.log("[Util:createHtml] Error while parsing XHTML:"),Candy.Core.log(q),g=Strophe.xmlTextNode("")}else for(g=Strophe.xmlGenerator().createDocumentFragment(),f=0;f<c.childNodes.length;f++)g.appendChild(a.createHtml(c.childNodes[f],d,e));else if(c.nodeType===Strophe.ElementType.FRAGMENT)for(g=Strophe.xmlGenerator().createDocumentFragment(),f=0;f<c.childNodes.length;f++)g.appendChild(a.createHtml(c.childNodes[f],d,e));else if(c.nodeType===Strophe.ElementType.TEXT){var r=c.nodeValue;e+=r.length,d&&e>d&&(r=r.substring(0,d)),r=Candy.Util.Parser.all(r),g=b.parseHTML(r)}return g},a}(Candy.Util||{},jQuery),Candy.Core.Action=function(a,b,c){return a.Jabber={Version:function(a){Candy.Core.getConnection().sendIQ($iq({type:"result",to:Candy.Util.escapeJid(a.attr("from")),from:Candy.Util.escapeJid(a.attr("to")),id:a.attr("id")}).c("query",{name:Candy.about.name,version:Candy.about.version,os:navigator.userAgent}))},SetNickname:function(a,b){b=b instanceof Array?b:Candy.Core.getRooms();var d,e,f=Candy.Core.getConnection();c.each(b,function(b){d=Candy.Util.escapeJid(b+"/"+a),e=$pres({to:d,from:f.jid,id:"pres:"+f.getUniqueId()}),Candy.Core.getConnection().send(e)})},Roster:function(){Candy.Core.getConnection().sendIQ($iq({type:"get",xmlns:b.NS.CLIENT}).c("query",{xmlns:b.NS.ROSTER}).tree())},Presence:function(a,b){var c=Candy.Core.getConnection();a=a||{},a.id||(a.id="pres:"+c.getUniqueId());var d=$pres(a).c("priority").t(Candy.Core.getOptions().presencePriority.toString()).up().c("c",c.caps.generateCapsAttrs()).up();b&&d.node.appendChild(b.node),c.send(d.tree())},Services:function(){Candy.Core.getConnection().sendIQ($iq({type:"get",xmlns:b.NS.CLIENT}).c("query",{xmlns:b.NS.DISCO_ITEMS}).tree())},Autojoin:function(){if(Candy.Core.getOptions().autojoin===!0){Candy.Core.getConnection().sendIQ($iq({type:"get",xmlns:b.NS.CLIENT}).c("query",{xmlns:b.NS.PRIVATE}).c("storage",{xmlns:b.NS.BOOKMARKS}).tree());var d=Candy.Core.getConnection().getUniqueId("pubsub");Candy.Core.addHandler(Candy.Core.Event.Jabber.Bookmarks,b.NS.PUBSUB,"iq","result",d),Candy.Core.getConnection().sendIQ($iq({type:"get",id:d}).c("pubsub",{xmlns:b.NS.PUBSUB}).c("items",{node:b.NS.BOOKMARKS}).tree())}else c.isArray(Candy.Core.getOptions().autojoin)?c.each(Candy.Core.getOptions().autojoin,function(){a.Jabber.Room.Join.apply(null,this.valueOf().split(":",2))}):c(Candy).triggerHandler("candy:core.autojoin-missing")},ResetIgnoreList:function(){Candy.Core.getConnection().sendIQ($iq({type:"set",from:Candy.Core.getUser().getEscapedJid()}).c("query",{xmlns:b.NS.PRIVACY}).c("list",{name:"ignore"}).c("item",{action:"allow",order:"0"}).tree())},RemoveIgnoreList:function(){Candy.Core.getConnection().sendIQ($iq({type:"set",from:Candy.Core.getUser().getEscapedJid()}).c("query",{xmlns:b.NS.PRIVACY}).c("list",{name:"ignore"}).tree())},GetIgnoreList:function(){var a=$iq({type:"get",from:Candy.Core.getUser().getEscapedJid()}).c("query",{xmlns:b.NS.PRIVACY}).c("list",{name:"ignore"}).tree(),c=Candy.Core.getConnection().sendIQ(a);Candy.Core.addHandler(Candy.Core.Event.Jabber.PrivacyList,null,"iq",null,c)},SetIgnoreListActive:function(){Candy.Core.getConnection().sendIQ($iq({type:"set",from:Candy.Core.getUser().getEscapedJid()}).c("query",{xmlns:b.NS.PRIVACY}).c("active",{name:"ignore"}).tree())},GetJidIfAnonymous:function(){Candy.Core.getUser().getJid()||(Candy.Core.log("[Jabber] Anonymous login"),Candy.Core.getUser().data.jid=Candy.Core.getConnection().jid)},Room:{Join:function(c,d){a.Jabber.Room.Disco(c),c=Candy.Util.escapeJid(c);var e=Candy.Core.getConnection(),f=c+"/"+Candy.Core.getUser().getNick(),g=$pres({to:f,id:"pres:"+e.getUniqueId()}).c("x",{xmlns:b.NS.MUC});d&&g.c("password").t(d),g.up().c("c",e.caps.generateCapsAttrs()),e.send(g.tree())},Leave:function(a){var b=Candy.Core.getRoom(a).getUser();a=Candy.Util.escapeJid(a),b&&Candy.Core.getConnection().muc.leave(a,b.getNick(),function(){})},Disco:function(a){Candy.Core.getConnection().sendIQ($iq({type:"get",from:Candy.Core.getUser().getEscapedJid(),to:Candy.Util.escapeJid(a)}).c("query",{xmlns:b.NS.DISCO_INFO}).tree())},Message:function(a,d,e,f){if(d=c.trim(d),""===d)return!1;var g=null;return"chat"===e&&(g=b.getResourceFromJid(a),a=b.getBareJidFromJid(a)),Candy.Core.getConnection().muc.message(a,g,d,f,e),!0},Invite:function(a,d,e,f){e=c.trim(e);var g=$msg({to:a}),h=g.c("x",{xmlns:b.NS.MUC_USER});c.each(d,function(a,c){c=b.getBareJidFromJid(c),h.c("invite",{to:c}),"undefined"!=typeof e&&""!==e&&h.c("reason",e)}),"undefined"!=typeof f&&""!==f&&h.c("password",f),Candy.Core.getConnection().send(g)},IgnoreUnignore:function(a){Candy.Core.getUser().addToOrRemoveFromPrivacyList("ignore",a),Candy.Core.Action.Jabber.Room.UpdatePrivacyList()},UpdatePrivacyList:function(){var a=Candy.Core.getUser(),b=$iq({type:"set",from:a.getEscapedJid()}).c("query",{xmlns:"jabber:iq:privacy"}).c("list",{name:"ignore"}),d=a.getPrivacyList("ignore");d.length>0?c.each(d,function(a,c){b.c("item",{type:"jid",value:Candy.Util.escapeJid(c),action:"deny",order:a}).c("message").up().up()}):b.c("item",{action:"allow",order:"0"}),Candy.Core.getConnection().sendIQ(b.tree())},Admin:{UserAction:function(a,c,d,e){a=Candy.Util.escapeJid(a),c=Candy.Util.escapeJid(c);var f={nick:b.getResourceFromJid(c)};switch(d){case"kick":f.role="none";break;case"ban":f.affiliation="outcast";break;default:return!1}return Candy.Core.getConnection().sendIQ($iq({type:"set",from:Candy.Core.getUser().getEscapedJid(),to:a}).c("query",{xmlns:b.NS.MUC_ADMIN}).c("item",f).c("reason").t(e).tree()),!0},SetSubject:function(a,b){Candy.Core.getConnection().muc.setTopic(Candy.Util.escapeJid(a),b)}}}},a}(Candy.Core.Action||{},Strophe,jQuery),Candy.Core.ChatRoom=function(a){this.room={jid:a,name:Strophe.getNodeFromJid(a)},this.user=null,this.roster=new Candy.Core.ChatRoster,this.setUser=function(a){this.user=a},this.getUser=function(){return this.user},this.getJid=function(){return this.room.jid},this.setName=function(a){this.room.name=a},this.getName=function(){return this.room.name},this.setRoster=function(a){this.roster=a},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(a,b,c,d){this.ROLE_MODERATOR="moderator",this.AFFILIATION_OWNER="owner",this.data={jid:a,nick:Strophe.unescapeNode(b),affiliation:c,role:d,privacyLists:{},customData:{},previousNick:void 0},this.getJid=function(){return this.data.jid?Candy.Util.unescapeJid(this.data.jid):void 0},this.getEscapedJid=function(){return Candy.Util.escapeJid(this.data.jid)},this.setJid=function(a){this.data.jid=a},this.getNick=function(){return Strophe.unescapeNode(this.data.nick)},this.setNick=function(a){this.data.nick=a},this.getRole=function(){return this.data.role},this.setRole=function(a){this.data.role=a},this.setAffiliation=function(a){this.data.affiliation=a},this.getAffiliation=function(){return this.data.affiliation},this.isModerator=function(){return this.getRole()===this.ROLE_MODERATOR||this.getAffiliation()===this.AFFILIATION_OWNER},this.addToOrRemoveFromPrivacyList=function(a,b){this.data.privacyLists[a]||(this.data.privacyLists[a]=[]);var c=-1;return-1!==(c=this.data.privacyLists[a].indexOf(b))?this.data.privacyLists[a].splice(c,1):this.data.privacyLists[a].push(b),this.data.privacyLists[a]},this.getPrivacyList=function(a){return this.data.privacyLists[a]||(this.data.privacyLists[a]=[]),this.data.privacyLists[a]},this.setPrivacyLists=function(a){this.data.privacyLists=a},this.isInPrivacyList=function(a,b){return this.data.privacyLists[a]?-1!==this.data.privacyLists[a].indexOf(b):!1},this.setCustomData=function(a){this.data.customData=a},this.getCustomData=function(){return this.data.customData},this.setPreviousNick=function(a){this.data.previousNick=a},this.getPreviousNick=function(){return this.data.previousNick}},Candy.Core.Event=function(a,b,c){return a.Login=function(a){c(Candy).triggerHandler("candy:core.login",{presetJid:a})},a.Strophe={Connect:function(a){switch(Candy.Core.setStropheStatus(a),a){case b.Status.CONNECTED:Candy.Core.log("[Connection] Connected"),Candy.Core.Action.Jabber.GetJidIfAnonymous();case b.Status.ATTACHED:Candy.Core.log("[Connection] Attached"),Candy.Core.Action.Jabber.Presence(),Candy.Core.Action.Jabber.Autojoin(),Candy.Core.Action.Jabber.GetIgnoreList();break;case b.Status.DISCONNECTED:Candy.Core.log("[Connection] Disconnected");break;case b.Status.AUTHFAIL:Candy.Core.log("[Connection] Authentication failed");break;case b.Status.CONNECTING:Candy.Core.log("[Connection] Connecting");break;case b.Status.DISCONNECTING:Candy.Core.log("[Connection] Disconnecting");break;case b.Status.AUTHENTICATING:Candy.Core.log("[Connection] Authenticating");break;case b.Status.ERROR:case b.Status.CONNFAIL:Candy.Core.log("[Connection] Failed ("+a+")");break;default:Candy.Core.log("[Connection] What?!")}c(Candy).triggerHandler("candy:core.chat.connection",{status:a})}},a.Jabber={Version:function(a){return Candy.Core.log("[Jabber] Version"),Candy.Core.Action.Jabber.Version(c(a)),!0},Presence:function(d){return Candy.Core.log("[Jabber] Presence"),d=c(d),d.children('x[xmlns^="'+b.NS.MUC+'"]').length>0?"error"===d.attr("type")?a.Jabber.Room.PresenceError(d):a.Jabber.Room.Presence(d):c(Candy).triggerHandler("candy:core.presence",{from:d.attr("from"),stanza:d}),!0},Bookmarks:function(a){return Candy.Core.log("[Jabber] Bookmarks"),c("conference",a).each(function(){var a=c(this);a.attr("autojoin")&&Candy.Core.Action.Jabber.Room.Join(a.attr("jid"))}),!0},PrivacyList:function(b){Candy.Core.log("[Jabber] PrivacyList");var d=Candy.Core.getUser();return b=c(b),"result"===b.attr("type")?(c('list[name="ignore"] item',b).each(function(){var a=c(this);"deny"===a.attr("action")&&d.addToOrRemoveFromPrivacyList("ignore",a.attr("value"))}),Candy.Core.Action.Jabber.SetIgnoreListActive(),!1):a.Jabber.PrivacyListError(b)},PrivacyListError:function(a){return Candy.Core.log("[Jabber] PrivacyListError"),c('error[code="404"][type="cancel"] item-not-found',a)&&(Candy.Core.Action.Jabber.ResetIgnoreList(),Candy.Core.Action.Jabber.SetIgnoreListActive()),!1},Message:function(d){Candy.Core.log("[Jabber] Message"),d=c(d);var e=d.attr("from"),f=d.attr("type")||"undefined",g=d.attr("to");if("normal"===f||"undefined"===f){var h=d.find("invite"),i=d.find('x[xmlns="jabber:x:conference"]');if(h.length>0){var j=d.find("password"),k=null,l=h.find("continue"),m=null;j&&(k=j.text()),l&&(m=l.attr("thread")),c(Candy).triggerHandler("candy:core:chat:invite",{roomJid:e,from:h.attr("from")||"undefined",reason:h.find("reason").html()||"",password:k,continuedThread:m})}return i.length>0&&c(Candy).triggerHandler("candy:core:chat:invite",{roomJid:i.attr("jid"),from:e,reason:i.attr("reason")||"",password:i.attr("password"),continuedThread:i.attr("thread")}),c(Candy).triggerHandler("candy:core:chat:message:normal",{type:f||"normal",message:d}),!0}return"groupchat"!==f&&"chat"!==f&&"error"!==f&&"headline"!==f?(c(Candy).triggerHandler("candy:core:chat:message:other",{type:f,message:d}),!0):(e===b.getDomainFromJid(e)||"groupchat"!==f&&"chat"!==f&&"error"!==f?g||e!==b.getDomainFromJid(e)?g&&e===b.getDomainFromJid(e)&&c(Candy).triggerHandler("candy:core.chat.message.server",{type:f||"message",subject:d.children("subject").text(),message:d.children("body").text()}):c(Candy).triggerHandler("candy:core.chat.message.admin",{type:f||"message",message:d.children("body").text()}):a.Jabber.Room.Message(d),!0)},Room:{Leave:function(a){Candy.Core.log("[Jabber:Room] Leave"),a=c(a);var d=Candy.Util.unescapeJid(a.attr("from")),e=b.getBareJidFromJid(d);if(!Candy.Core.getRoom(e))return!0;var f,g,h=Candy.Core.getRoom(e).getName(),i=a.find("item"),j="leave";if(delete Candy.Core.getRooms()[e],"none"===i.attr("role")){var k=a.find("status").attr("code");"307"===k?j="kick":"301"===k&&(j="ban"),f=i.find("reason").text(),g=i.find("actor").attr("jid")}var l=new Candy.Core.ChatUser(d,b.getResourceFromJid(d),i.attr("affiliation"),i.attr("role"));return c(Candy).triggerHandler("candy:core.presence.leave",{roomJid:e,roomName:h,type:j,reason:f,actor:g,user:l}),!0},Disco:function(a){if(Candy.Core.log("[Jabber:Room] Disco"),a=c(a),!a.find('identity[category="conference"]').length)return!0;var d=b.getBareJidFromJid(Candy.Util.unescapeJid(a.attr("from")));Candy.Core.getRooms()[d]||(Candy.Core.getRooms()[d]=new Candy.Core.ChatRoom(d));var e=a.find("identity");if(e.length){var f=e.attr("name"),g=Candy.Core.getRoom(d);null===g.getName()&&g.setName(b.unescapeNode(f))}return!0},Presence:function(d){Candy.Core.log("[Jabber:Room] Presence");var e=Candy.Util.unescapeJid(d.attr("from")),f=b.getBareJidFromJid(e),g=d.attr("type"),h=d.find("status"),i=!1,j=!1;if(h.length)for(var k=0,l=h.length;l>k;k++){var m=c(h[k]),n=m.attr("code");"303"===n?j=!0:"210"===n&&(i=!0)}var o=Candy.Core.getRoom(f);o||(Candy.Core.getRooms()[f]=new Candy.Core.ChatRoom(f),o=Candy.Core.getRoom(f));var p=o.getUser()?o.getUser():Candy.Core.getUser();if(b.getResourceFromJid(e)===p.getNick()&&"unavailable"===g&&j===!1)return a.Jabber.Room.Leave(d),!0;var q,r,s,t=o.getRoster(),u=d.find("item");if("unavailable"!==g)if(t.get(e)){r=t.get(e);var v=u.attr("role"),w=u.attr("affiliation");r.setRole(v),r.setAffiliation(w),q="join"}else s=b.getResourceFromJid(e),r=new Candy.Core.ChatUser(e,s,u.attr("affiliation"),u.attr("role")),null!==o.getUser()||Candy.Core.getUser().getNick()!==s&&!i||(o.setUser(r),p=r),t.add(r),q="join";else r=t.get(e),t.remove(e),j?(s=u.attr("nick"),q="nickchange",r.setPreviousNick(r.getNick()),r.setNick(s),r.setJid(b.getBareJidFromJid(e)+"/"+s),t.add(r)):(q="leave","none"===u.attr("role")&&("307"===d.find("status").attr("code")?q="kick":"301"===d.find("status").attr("code")&&(q="ban")));return c(Candy).triggerHandler("candy:core.presence.room",{roomJid:f,roomName:o.getName(),user:r,action:q,currentUser:p}),!0},PresenceError:function(a){Candy.Core.log("[Jabber:Room] Presence Error");var d=Candy.Util.unescapeJid(a.attr("from")),e=b.getBareJidFromJid(d),f=Candy.Core.getRooms()[e],g=f.getName();return Candy.Core.removeRoom(e),f=void 0,c(Candy).triggerHandler("candy:core.presence.error",{msg:a,type:a.children("error").children()[0].tagName.toLowerCase(),roomJid:e,roomName:g}),!0},Message:function(a){Candy.Core.log("[Jabber:Room] Message");var d,e,f;if(a.children("subject").length>0&&a.children("subject").text().length>0&&"groupchat"===a.attr("type"))d=Candy.Util.unescapeJid(b.getBareJidFromJid(a.attr("from"))),e={name:b.getNodeFromJid(d),body:a.children("subject").text(),type:"subject"};else if("error"===a.attr("type")){var g=a.children("error");g.children("text").length>0&&(d=a.attr("from"),e={type:"info",body:g.children("text").text()})}else{if(!(a.children("body").length>0)){if(a.children("composing").length>0||a.children("inactive").length>0||a.children("paused").length>0){d=Candy.Util.unescapeJid(a.attr("from")),f=b.getResourceFromJid(d);var h;return a.children("composing").length>0?h="composing":a.children("paused").length>0?h="paused":a.children("inactive").length>0?h="inactive":a.children("gone").length>0&&(h="gone"),c(Candy).triggerHandler("candy:core.message.chatstate",{name:f,roomJid:d,chatstate:h}),!0}return!0}if("chat"===a.attr("type")||"normal"===a.attr("type")){d=Candy.Util.unescapeJid(a.attr("from"));var i=b.getBareJidFromJid(d),j=!Candy.Core.getRoom(i);f=j?b.getNodeFromJid(d):b.getResourceFromJid(d),e={name:f,body:a.children("body").text(),type:a.attr("type"),isNoConferenceRoomJid:j}}else{d=Candy.Util.unescapeJid(b.getBareJidFromJid(a.attr("from")));var k=b.getResourceFromJid(a.attr("from"));if(k)k=b.unescapeNode(k),e={name:k,body:a.children("body").text(),type:a.attr("type")};else{if(!Candy.View.Pane.Chat.rooms[a.attr("from")])return!0;e={name:"",body:a.children("body").text(),type:"info"}}}var l=a.children('html[xmlns="'+b.NS.XHTML_IM+'"]');if(Candy.View.getOptions().enableXHTML===!0&&l.length>0){var m=l.children('body[xmlns="'+b.NS.XHTML+'"]').first().html();e.xhtmlMessage=m}}var n=a.children(a.children("delay")?"delay":'x[xmlns="'+b.NS.DELAY+'"]'),o=void 0!==n?n.attr("stamp"):null;return c(Candy).triggerHandler("candy:core.message",{roomJid:d,message:e,timestamp:o}),!0}}},a}(Candy.Core.Event||{},Strophe,jQuery),Candy.View.Observer=function(a,b){var c=!0;return a.Chat={Connection:function(a,d){var e="candy:view.connection.status-"+d.status;if(b(Candy).triggerHandler(e)===!1)return!1;switch(d.status){case Strophe.Status.CONNECTING:case Strophe.Status.AUTHENTICATING:Candy.View.Pane.Chat.Modal.show(b.i18n._("statusConnecting"),!1,!0);break;case Strophe.Status.ATTACHED:case Strophe.Status.CONNECTED:c===!0&&(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"),!1,!0);break;case Strophe.Status.DISCONNECTED:var f=Candy.Core.isAnonymousConnection()?Strophe.getDomainFromJid(Candy.Core.getUser().getJid()):null;Candy.View.Pane.Chat.Modal.showLoginForm(b.i18n._("statusDisconnected"),f);break;case Strophe.Status.AUTHFAIL:Candy.View.Pane.Chat.Modal.showLoginForm(b.i18n._("statusAuthfail"));break;default:Candy.View.Pane.Chat.Modal.show(b.i18n._("status",d.status))}},Message:function(a,b){"message"===b.type?Candy.View.Pane.Chat.adminMessage(b.subject||"",b.message):("chat"===b.type||"groupchat"===b.type)&&Candy.View.Pane.Chat.onInfoMessage(Candy.View.getCurrent().roomJid,b.subject||"",b.message)}},a.Presence={update:function(c,d){if("leave"===d.type){var e=Candy.View.Pane.Room.getUser(d.roomJid);Candy.View.Pane.Room.close(d.roomJid),a.Presence.notifyPrivateChats(e,d.type)}else if("kick"===d.type||"ban"===d.type){var f,g=d.actor?Strophe.getNodeFromJid(d.actor):null,h=[d.roomName];switch(g&&h.push(g),d.type){case"kick":f=b.i18n._(g?"youHaveBeenKickedBy":"youHaveBeenKicked",h);break;case"ban":f=b.i18n._(g?"youHaveBeenBannedBy":"youHaveBeenBanned",h)}Candy.View.Pane.Chat.Modal.show(Mustache.to_html(Candy.View.Template.Chat.Context.adminMessageReason,{reason:d.reason,_action:f,_reason:b.i18n._("reasonWas",[d.reason])})),setTimeout(function(){Candy.View.Pane.Chat.Modal.hide(function(){Candy.View.Pane.Room.close(d.roomJid),a.Presence.notifyPrivateChats(d.user,d.type)})},5e3);var i={type:d.type,reason:d.reason,roomJid:d.roomJid,user:d.user};b(Candy).triggerHandler("candy:view.presence",[i])}else if(d.roomJid){if(d.roomJid=Candy.Util.unescapeJid(d.roomJid),!Candy.View.Pane.Chat.rooms[d.roomJid]){if(Candy.View.Pane.Room.init(d.roomJid,d.roomName)===!1)return!1;Candy.View.Pane.Room.show(d.roomJid)}Candy.View.Pane.Roster.update(d.roomJid,d.user,d.action,d.currentUser),Candy.View.Pane.Chat.rooms[d.user.getJid()]&&"nickchange"!==d.action&&(Candy.View.Pane.Roster.update(d.user.getJid(),d.user,d.action,d.currentUser),Candy.View.Pane.PrivateRoom.setStatus(d.user.getJid(),d.action))}},notifyPrivateChats:function(a,b){Candy.Core.log("[View:Observer] notify Private Chats");var c;for(c in Candy.View.Pane.Chat.rooms)Candy.View.Pane.Chat.rooms.hasOwnProperty(c)&&Candy.View.Pane.Room.getUser(c)&&a.getJid()===Candy.View.Pane.Room.getUser(c).getJid()&&(Candy.View.Pane.Roster.update(c,a,b,a),Candy.View.Pane.PrivateRoom.setStatus(c,b))}},a.PresenceError=function(a,c){switch(c.type){case"not-authorized":var d;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])}},a.Message=function(a,b){"subject"===b.message.type?(Candy.View.Pane.Chat.rooms[b.roomJid]||(Candy.View.Pane.Room.init(b.roomJid,b.message.name),Candy.View.Pane.Room.show(b.roomJid)),Candy.View.Pane.Room.setSubject(b.roomJid,b.message.body)):"info"===b.message.type?Candy.View.Pane.Chat.infoMessage(b.roomJid,b.message.body):("chat"!==b.message.type||Candy.View.Pane.Chat.rooms[b.roomJid]||Candy.View.Pane.PrivateRoom.open(b.roomJid,b.message.name,!1,b.message.isNoConferenceRoomJid),Candy.View.Pane.Message.show(b.roomJid,b.message.name,b.message.body,b.message.xhtmlMessage,b.timestamp))},a.Login=function(a,b){Candy.View.Pane.Chat.Modal.showLoginForm(null,b.presetJid)},a.AutojoinMissing=function(){c=!1,Candy.View.Pane.Chat.Modal.showError("errorAutojoinMissing")},a}(Candy.View.Observer||{},jQuery),Candy.View.Pane=function(a,b){return a.Window={_hasFocus:!0,_plainTitle:document.title,_unreadMessagesCount:0,autoscroll:!0,hasFocus:function(){return a.Window._hasFocus},increaseUnreadMessages:function(){a.Window.renderUnreadMessages(++a.Window._unreadMessagesCount)},reduceUnreadMessages:function(b){a.Window._unreadMessagesCount-=b,a.Window._unreadMessagesCount<=0?a.Window.clearUnreadMessages():a.Window.renderUnreadMessages(a.Window._unreadMessagesCount)},clearUnreadMessages:function(){a.Window._unreadMessagesCount=0,document.title=a.Window._plainTitle},renderUnreadMessages:function(b){document.title=Candy.View.Template.Window.unreadmessages.replace("{{count}}",b).replace("{{title}}",a.Window._plainTitle)},onFocus:function(){a.Window._hasFocus=!0,Candy.View.getCurrent().roomJid&&(a.Room.setFocusToForm(Candy.View.getCurrent().roomJid),a.Chat.clearUnreadMessages(Candy.View.getCurrent().roomJid))
-},onBlur:function(){a.Window._hasFocus=!1}},a.Chat={rooms:[],addTab:function(c,d,e){var f=Candy.Util.jidToId(c),g=Mustache.to_html(Candy.View.Template.Chat.tab,{roomJid:c,roomId:f,name:d||Strophe.getNodeFromJid(c),privateUserChat:function(){return"chat"===e},roomType:e}),h=b(g).appendTo("#chat-tabs");h.click(a.Chat.tabClick),b("a.close",h).click(a.Chat.tabClose),a.Chat.fitTabs()},getTab:function(a){return b("#chat-tabs").children('li[data-roomjid="'+a+'"]')},removeTab:function(b){a.Chat.getTab(b).remove(),a.Chat.fitTabs()},setActiveTab:function(a){b("#chat-tabs").children().each(function(){var c=b(this);c.attr("data-roomjid")===a?c.addClass("active"):c.removeClass("active")})},increaseUnreadMessages:function(b){var c=this.getTab(b).find(".unread");c.show().text(""!==c.text()?parseInt(c.text(),10)+1:1),"chat"===a.Chat.rooms[b].type&&a.Window.increaseUnreadMessages()},clearUnreadMessages:function(b){var c=a.Chat.getTab(b).find(".unread");a.Window.reduceUnreadMessages(c.text()),c.hide().text("")},tabClick:function(c){var d=Candy.View.getCurrent().roomJid;a.Chat.rooms[d].scrollPosition=a.Room.getPane(d,".message-pane-wrapper").scrollTop(),a.Room.show(b(this).attr("data-roomjid")),c.preventDefault()},tabClose:function(){var c=b(this).parent().attr("data-roomjid");return"chat"===a.Chat.rooms[c].type?a.Room.close(c):Candy.Core.Action.Jabber.Room.Leave(c),!1},allTabsClosed:function(){Candy.Core.disconnect(),a.Chat.Toolbar.hide()},fitTabs:function(){var a=b("#chat-tabs").innerWidth(),c=0,d=b("#chat-tabs").children();if(d.each(function(){c+=b(this).css({width:"auto",overflow:"visible"}).outerWidth(!0)}),c>a){var e=d.outerWidth(!0)-d.width(),f=Math.floor(a/d.length)-e;d.css({width:f,overflow:"hidden"})}},adminMessage:function(c,d){if(Candy.View.getCurrent().roomJid){var e=Mustache.to_html(Candy.View.Template.Chat.adminMessage,{subject:c,message:d,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"),e)}),a.Room.scrollToBottom(Candy.View.getCurrent().roomJid),b(Candy).triggerHandler("candy:view.chat.admin-message",{subject:c,message:d})}},infoMessage:function(b,c,d){a.Chat.onInfoMessage(b,c,d)},onInfoMessage:function(c,d,e){if(Candy.View.getCurrent().roomJid){var f=Mustache.to_html(Candy.View.Template.Chat.infoMessage,{subject:d,message:b.i18n._(e),time:Candy.Util.localizedTime((new Date).toGMTString())});a.Room.appendToMessagePane(c,f),Candy.View.getCurrent().roomJid===c&&a.Room.scrollToBottom(Candy.View.getCurrent().roomJid)}},Toolbar:{_supportsNativeAudio:!1,init:function(){b("#emoticons-icon").click(function(b){a.Chat.Context.showEmoticonsMenu(b.currentTarget),b.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),Candy.Util.cookieExists("candy-nosound")&&b("#chat-sound-control").click(),b("#chat-statusmessage-control").click(a.Chat.Toolbar.onStatusMessageControlClick),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);e&&e.isModerator()?d.show().click(function(b){a.Chat.Context.show(b.currentTarget,c),b.stopPropagation()}):d.hide(),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().assets+"notify.mp3").play();else{var b=document.getElementById("chat-sound-player");b.SetVariable("method:stop",""),b.SetVariable("method:play","")}}catch(c){}},onSoundControlClick:function(){var c=b("#chat-sound-control");c.hasClass("checked")?(a.Chat.Toolbar.playSound=function(){},Candy.Util.setCookie("candy-nosound","1",365)):(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");c.hasClass("checked")?(a.Room.scrollToBottom=function(b){a.Room.onScrollToStoredPosition(b)},a.Window.autoscroll=!1):(a.Room.scrollToBottom=function(b){a.Room.onScrollToBottom(b)},a.Room.scrollToBottom(Candy.View.getCurrent().roomJid),a.Window.autoscroll=!0),c.toggleClass("checked")},onStatusMessageControlClick:function(){var c=b("#chat-statusmessage-control");c.hasClass("checked")?(a.Chat.infoMessage=function(){},Candy.Util.setCookie("candy-nostatusmessages","1",365)):(a.Chat.infoMessage=function(b,c,d){a.Chat.onInfoMessage(b,c,d)},Candy.Util.deleteCookie("candy-nostatusmessages")),c.toggleClass("checked")},updateUsercount:function(a){b("#chat-usercount").text(a)}},Modal:{show:function(c,d,e){d?a.Chat.Modal.showCloseControl():a.Chat.Modal.hideCloseControl(),e?a.Chat.Modal.showSpinner():a.Chat.Modal.hideSpinner(),b("#chat-modal").stop(!1,!0),b("#chat-modal-body").html(c),b("#chat-modal").fadeIn("fast"),b("#chat-modal-overlay").show()},hide:function(a){b("#chat-modal").fadeOut("fast",function(){b("#chat-modal-body").text(""),b("#chat-modal-overlay").hide()}),b(document).keydown(function(a){27===a.which&&a.preventDefault()}),a&&a()},showSpinner:function(){b("#chat-modal-spinner").show()},hideSpinner:function(){b("#chat-modal-spinner").hide()},showCloseControl:function(){b("#admin-message-cancel").show().click(function(b){a.Chat.Modal.hide(),b.preventDefault()}),b(document).keydown(function(b){27===b.which&&(a.Chat.Modal.hide(),b.preventDefault())})},hideCloseControl:function(){b("#admin-message-cancel").hide().click(function(){})},showLoginForm:function(c,d){a.Chat.Modal.show((c?c:"")+Mustache.to_html(Candy.View.Template.Login.form,{_labelNickname:b.i18n._("labelNickname"),_labelUsername:b.i18n._("labelUsername"),_labelPassword:b.i18n._("labelPassword"),_loginSubmit:b.i18n._("loginSubmit"),displayPassword:!Candy.Core.isAnonymousConnection(),displayUsername:!d,displayNickname:Candy.Core.isAnonymousConnection(),presetJid:d?d:!1})),b("#login-form").children(":input:first").focus(),b("#login-form").submit(function(){var a=b("#username").val(),c=b("#password").val();if(Candy.Core.isAnonymousConnection())Candy.Core.connect(d,null,a);else{var e=Candy.Core.getUser()&&a.indexOf("@")<0?a+"@"+Strophe.getDomainFromJid(Candy.Core.getUser().getJid()):a;e.indexOf("@")<0&&!Candy.Core.getUser()?Candy.View.Pane.Chat.Modal.showLoginForm(b.i18n._("loginInvalid")):Candy.Core.connect(e,c)}return!1})},showEnterPasswordForm:function(c,d,e){a.Chat.Modal.show(Mustache.to_html(Candy.View.Template.PresenceError.enterPasswordForm,{roomName:d,_labelPassword:b.i18n._("labelPassword"),_label:e?e:b.i18n._("enterRoomPassword",[d]),_joinSubmit:b.i18n._("enterRoomPasswordSubmit")}),!0),b("#password").focus(),b("#enter-password-form").submit(function(){var d=b("#password").val();return a.Chat.Modal.hide(function(){Candy.Core.Action.Jabber.Room.Join(c,d)}),!1})},showNicknameConflictForm:function(c){a.Chat.Modal.show(Mustache.to_html(Candy.View.Template.PresenceError.nicknameConflictForm,{_labelNickname:b.i18n._("labelNickname"),_label:b.i18n._("nicknameConflict"),_loginSubmit:b.i18n._("loginSubmit")})),b("#nickname").focus(),b("#nickname-conflict-form").submit(function(){var d=b("#nickname").val();return a.Chat.Modal.hide(function(){Candy.Core.getUser().data.nick=d,Candy.Core.Action.Jabber.Room.Join(c)}),!1})},showError:function(c,d){a.Chat.Modal.show(Mustache.to_html(Candy.View.Template.PresenceError.displayError,{_error:b.i18n._(c,d)}),!0)}},Tooltip:{show:function(a,c){var d=b("#tooltip"),e=b(a.currentTarget);if(c||(c=e.attr("data-tooltip")),0===d.length){var f=Mustache.to_html(Candy.View.Template.Chat.tooltip);b("#chat-pane").append(f),d=b("#tooltip")}b("#context-menu").hide(),d.stop(!1,!0),d.children("div").html(c);var g=e.offset(),h=Candy.Util.getPosLeftAccordingToWindowBounds(d,g.left),i=Candy.Util.getPosTopAccordingToWindowBounds(d,g.top);d.css({left:h.px,top:i.px}).removeClass("left-top left-bottom right-top right-bottom").addClass(h.backgroundPositionAlignment+"-"+i.backgroundPositionAlignment).fadeIn("fast"),e.mouseleave(function(a){a.stopPropagation(),b("#tooltip").stop(!1,!0).fadeOut("fast",function(){b(this).css({top:0,left:0})})})}},Context:{init:function(){if(0===b("#context-menu").length){var a=Mustache.to_html(Candy.View.Template.Chat.Context.menu);b("#chat-pane").append(a),b("#context-menu").mouseleave(function(){b(this).fadeOut("fast")})}},show:function(c,d,e){c=b(c);var f=a.Chat.rooms[d].id,g=b("#context-menu"),h=b("ul li",g);b("#tooltip").hide(),e||(e=Candy.Core.getUser()),h.remove();var i,j=this.getMenuLinks(d,e,c),k=function(a,c){return function(d){d.data.callback(d,a,c),b("#context-menu").hide()}};for(i in j)if(j.hasOwnProperty(i)){var l=j[i],m=Mustache.to_html(Candy.View.Template.Chat.Context.menulinks,{roomId:f,"class":l["class"],id:i,label:l.label});b("ul",g).append(m),b("#context-menu-"+i).bind("click",l,k(d,e))}if(i){var n=c.offset(),o=Candy.Util.getPosLeftAccordingToWindowBounds(g,n.left),p=Candy.Util.getPosTopAccordingToWindowBounds(g,n.top);return g.css({left:o.px,top:p.px}).removeClass("left-top left-bottom right-top right-bottom").addClass(o.backgroundPositionAlignment+"-"+p.backgroundPositionAlignment).fadeIn("fast"),b(Candy).triggerHandler("candy:view.roster.after-context-menu",{roomJid:d,user:e,element:g}),!0}},getMenuLinks:function(c,d,e){var f,g,h={roomJid:c,user:d,elem:e,menulinks:this.initialMenuLinks(e)};b(Candy).triggerHandler("candy:view.roster.context-menu",h),f=h.menulinks;for(g in f)f.hasOwnProperty(g)&&void 0!==f[g].requiredPermission&&!f[g].requiredPermission(d,a.Room.getUser(c),e)&&delete f[g];return f},initialMenuLinks:function(){return{"private":{requiredPermission:function(a,b){return b.getNick()!==a.getNick()&&Candy.Core.getRoom(Candy.View.getCurrent().roomJid)&&!Candy.Core.getUser().isInPrivacyList("ignore",a.getJid())},"class":"private",label:b.i18n._("privateActionLabel"),callback:function(a,c,d){b("#user-"+Candy.Util.jidToId(c)+"-"+Candy.Util.jidToId(d.getJid())).click()}},ignore:{requiredPermission:function(a,b){return b.getNick()!==a.getNick()&&!Candy.Core.getUser().isInPrivacyList("ignore",a.getJid())},"class":"ignore",label:b.i18n._("ignoreActionLabel"),callback:function(a,b,c){Candy.View.Pane.Room.ignoreUser(b,c.getJid())}},unignore:{requiredPermission:function(a,b){return b.getNick()!==a.getNick()&&Candy.Core.getUser().isInPrivacyList("ignore",a.getJid())},"class":"unignore",label:b.i18n._("unignoreActionLabel"),callback:function(a,b,c){Candy.View.Pane.Room.unignoreUser(b,c.getJid())}},kick:{requiredPermission:function(a,b){return b.getNick()!==a.getNick()&&b.isModerator()&&!a.isModerator()},"class":"kick",label:b.i18n._("kickActionLabel"),callback:function(c,d,e){a.Chat.Modal.show(Mustache.to_html(Candy.View.Template.Chat.Context.contextModalForm,{_label:b.i18n._("reason"),_submit:b.i18n._("kickActionLabel")}),!0),b("#context-modal-field").focus(),b("#context-modal-form").submit(function(){return Candy.Core.Action.Jabber.Room.Admin.UserAction(d,e.getJid(),"kick",b("#context-modal-field").val()),a.Chat.Modal.hide(),!1})}},ban:{requiredPermission:function(a,b){return b.getNick()!==a.getNick()&&b.isModerator()&&!a.isModerator()},"class":"ban",label:b.i18n._("banActionLabel"),callback:function(c,d,e){a.Chat.Modal.show(Mustache.to_html(Candy.View.Template.Chat.Context.contextModalForm,{_label:b.i18n._("reason"),_submit:b.i18n._("banActionLabel")}),!0),b("#context-modal-field").focus(),b("#context-modal-form").submit(function(){return Candy.Core.Action.Jabber.Room.Admin.UserAction(d,e.getJid(),"ban",b("#context-modal-field").val()),a.Chat.Modal.hide(),!1})}},subject:{requiredPermission:function(a,b){return b.getNick()===a.getNick()&&b.isModerator()},"class":"subject",label:b.i18n._("setSubjectActionLabel"),callback:function(c,d){a.Chat.Modal.show(Mustache.to_html(Candy.View.Template.Chat.Context.contextModalForm,{_label:b.i18n._("subject"),_submit:b.i18n._("setSubjectActionLabel")}),!0),b("#context-modal-field").focus(),b("#context-modal-form").submit(function(c){Candy.Core.Action.Jabber.Room.Admin.SetSubject(d,b("#context-modal-field").val()),a.Chat.Modal.hide(),c.preventDefault()})}}}},showEmoticonsMenu:function(a){a=b(a);var c,d=a.offset(),e=b("#context-menu"),f=b("ul",e),g="";for(b("#tooltip").hide(),c=Candy.Util.Parser.emoticons.length-1;c>=0;c--)g='<img src="'+Candy.Util.Parser._emoticonPath+Candy.Util.Parser.emoticons[c].image+'" alt="'+Candy.Util.Parser.emoticons[c].plain+'" />'+g;f.html('<li class="emoticons">'+g+"</li>"),f.find("img").click(function(){var a=Candy.View.Pane.Room.getPane(Candy.View.getCurrent().roomJid,".message-form").children(".field"),c=a.val(),d=b(this).attr("alt")+" ";a.val(c?c+" "+d:d).focus()});var h=Candy.Util.getPosLeftAccordingToWindowBounds(e,d.left),i=Candy.Util.getPosTopAccordingToWindowBounds(e,d.top);return e.css({left:h.px,top:i.px}).removeClass("left-top left-bottom right-top right-bottom").addClass(h.backgroundPositionAlignment+"-"+i.backgroundPositionAlignment).fadeIn("fast"),!0}}},a.Room={init:function(c,d,e){e=e||"groupchat",c=Candy.Util.unescapeJid(c);var f={roomJid:c,type:e};if(b(Candy).triggerHandler("candy:view.room.before-add",f)===!1)return!1;Candy.Util.isEmptyObject(a.Chat.rooms)&&a.Chat.Toolbar.show();var g=Candy.Util.jidToId(c);return a.Chat.rooms[c]={id:g,usercount:0,name:d,type:e,messageCount:0,scrollPosition:-1},b("#chat-rooms").append(Mustache.to_html(Candy.View.Template.Room.pane,{roomId:g,roomJid:c,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(c,d,e),a.Room.getPane(c,".message-form").submit(a.Message.submit),f.element=a.Room.getPane(c),b(Candy).triggerHandler("candy:view.room.after-add",f),g},show:function(c){var d,e=a.Chat.rooms[c].id;b(".room-pane").each(function(){var f=b(this);d={roomJid:f.attr("data-roomjid"),element:f},f.attr("id")==="chat-room-"+e?(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),b(Candy).triggerHandler("candy:view.room.after-show",d)):(f.hide(),b(Candy).triggerHandler("candy:view.room.after-hide",d))})},setSubject:function(c,d){d=Candy.Util.Parser.linkify(Candy.Util.Parser.escape(d));var e=Mustache.to_html(Candy.View.Template.Room.subject,{subject:d,roomName:a.Chat.rooms[c].name,_roomSubject:b.i18n._("roomSubject"),time:Candy.Util.localizedTime((new Date).toGMTString())});a.Room.appendToMessagePane(c,e),a.Room.scrollToBottom(c),b(Candy).triggerHandler("candy:view.room.after-subject-change",{roomJid:c,element:a.Room.getPane(c),subject:d})},close:function(c){a.Chat.removeTab(c),a.Window.clearUnreadMessages(),a.Room.getPane(c).remove();var d=b("#chat-rooms").children();Candy.View.getCurrent().roomJid===c&&(Candy.View.getCurrent().roomJid=null,0===d.length?a.Chat.allTabsClosed():a.Room.show(d.last().attr("data-roomjid"))),delete a.Chat.rooms[c],b(Candy).triggerHandler("candy:view.room.after-close",{roomJid:c})},appendToMessagePane:function(b,c){a.Room.getPane(b,".message-pane").append(c),a.Chat.rooms[b].messageCount++,a.Room.sliceMessagePane(b)},sliceMessagePane:function(b){if(a.Window.autoscroll){var c=Candy.View.getOptions().messages;a.Chat.rooms[b].messageCount>c.limit&&(a.Room.getPane(b,".message-pane").children().slice(0,c.remove).remove(),a.Chat.rooms[b].messageCount-=c.remove)}},scrollToBottom:function(b){a.Room.onScrollToBottom(b)},onScrollToBottom:function(b){var c=a.Room.getPane(b,".message-pane-wrapper");c.scrollTop(c.prop("scrollHeight"))},onScrollToStoredPosition:function(b){if(a.Chat.rooms[b].scrollPosition>-1){var c=a.Room.getPane(b,".message-pane-wrapper");c.scrollTop(a.Chat.rooms[b].scrollPosition),a.Chat.rooms[b].scrollPosition=-1}},setFocusToForm:function(b){var c=a.Room.getPane(b,".message-form");if(c)try{c.children(".field")[0].focus()}catch(d){}},setUser:function(c,d){a.Chat.rooms[c].user=d;var e=a.Room.getPane(c),f=b("#chat-pane");e.attr("data-userjid",d.getJid()),d.isModerator()?(d.getRole()===d.ROLE_MODERATOR&&f.addClass("role-moderator"),d.getAffiliation()===d.AFFILIATION_OWNER&&f.addClass("affiliation-owner")):f.removeClass("role-moderator affiliation-owner"),a.Chat.Context.init()},getUser:function(b){return a.Chat.rooms[b].user},ignoreUser:function(a,b){Candy.Core.Action.Jabber.Room.IgnoreUnignore(b),Candy.View.Pane.Room.addIgnoreIcon(a,b)},unignoreUser:function(a,b){Candy.Core.Action.Jabber.Room.IgnoreUnignore(b),Candy.View.Pane.Room.removeIgnoreIcon(a,b)},addIgnoreIcon:function(a,c){Candy.View.Pane.Chat.rooms[c]&&b("#user-"+Candy.View.Pane.Chat.rooms[c].id+"-"+Candy.Util.jidToId(c)).addClass("status-ignored"),Candy.View.Pane.Chat.rooms[Strophe.getBareJidFromJid(a)]&&b("#user-"+Candy.View.Pane.Chat.rooms[Strophe.getBareJidFromJid(a)].id+"-"+Candy.Util.jidToId(c)).addClass("status-ignored")},removeIgnoreIcon:function(a,c){Candy.View.Pane.Chat.rooms[c]&&b("#user-"+Candy.View.Pane.Chat.rooms[c].id+"-"+Candy.Util.jidToId(c)).removeClass("status-ignored"),Candy.View.Pane.Chat.rooms[Strophe.getBareJidFromJid(a)]&&b("#user-"+Candy.View.Pane.Chat.rooms[Strophe.getBareJidFromJid(a)].id+"-"+Candy.Util.jidToId(c)).removeClass("status-ignored")},getPane:function(c,d){return a.Chat.rooms[c]?d?a.Chat.rooms[c]["pane-"+d]?a.Chat.rooms[c]["pane-"+d]:(a.Chat.rooms[c]["pane-"+d]=b("#chat-room-"+a.Chat.rooms[c].id).find(d),a.Chat.rooms[c]["pane-"+d]):b("#chat-room-"+a.Chat.rooms[c].id):void 0},changeDataUserJidIfUserIsMe:function(a,c){if(c.getNick()===Candy.Core.getUser().getNick()){var d=b("#chat-room-"+a);d.attr("data-userjid",Strophe.getBareJidFromJid(d.attr("data-userjid"))+"/"+c.getNick())}}},a.PrivateRoom={open:function(c,d,e,f){var g=f?Candy.Core.getUser():a.Room.getUser(Strophe.getBareJidFromJid(c)),h={roomJid:c,roomName:d,type:"chat"};return b(Candy).triggerHandler("candy:view.private-room.before-open",h)===!1?!1:Candy.Core.getUser().isInPrivacyList("ignore",c)?!1:a.Chat.rooms[c]||a.Room.init(c,d,"chat")!==!1?(e&&a.Room.show(c),a.Roster.update(c,new Candy.Core.ChatUser(c,d),"join",g),a.Roster.update(c,g,"join",g),a.PrivateRoom.setStatus(c,"join"),f&&a.Chat.infoMessage(c,b.i18n._("presenceUnknownWarningSubject"),b.i18n._("presenceUnknownWarning")),h.element=a.Room.getPane(c),void b(Candy).triggerHandler("candy:view.private-room.after-open",h)):!1},setStatus:function(b,c){var d=a.Room.getPane(b,".message-form");"join"===c?(a.Chat.getTab(b).addClass("online").removeClass("offline"),d.children(".field").removeAttr("disabled"),d.children(".submit").removeAttr("disabled"),a.Chat.getTab(b)):"leave"===c&&(a.Chat.getTab(b).addClass("offline").removeClass("online"),d.children(".field").attr("disabled",!0),d.children(".submit").attr("disabled",!0))},changeNick:function(c,d){Candy.Core.log("[View:Pane:PrivateRoom] changeNick");var e,f,g=c+"/"+d.getPreviousNick(),h=c+"/"+d.getNick(),i=Candy.Util.jidToId(g),j=Candy.Util.jidToId(h),k=a.Chat.rooms[g];a.Chat.rooms[h]&&a.Room.close(h),k?(k.name=d.getNick(),k.id=j,a.Chat.rooms[h]=k,delete a.Chat.rooms[g],e=b("#chat-room-"+i),e&&(e.attr("data-roomjid",h),e.attr("id","chat-room-"+j),f=b('#chat-tabs li[data-roomjid="'+g+'"]'),f.attr("data-roomjid",h),f.children("a.label").text("@"+d.getNick()),Candy.View.getCurrent().roomJid===g&&(Candy.View.getCurrent().roomJid=h))):(e=b('.room-pane.roomtype-chat[data-userjid="'+g+'"]'),e.length&&(i=Candy.Util.jidToId(e.attr("data-roomjid")),e.attr("data-userjid",h))),e&&e.length&&a.Roster.changeNick(i,d)}},a.Roster={update:function(c,d,e,f){Candy.Core.log("[View:Pane:Roster] "+e);var g=a.Chat.rooms[c].id,h=Candy.Util.jidToId(d.getJid()),i=-1,j=b("#user-"+g+"-"+h),k={roomJid:c,user:d,action:e,element:j};if(b(Candy).triggerHandler("candy:view.roster.before-update",k),"join"===e){i=1;var l=Mustache.to_html(Candy.View.Template.Roster.user,{roomId:g,userId:h,userJid:d.getJid(),nick:d.getNick(),displayNick:Candy.Util.crop(d.getNick(),Candy.View.getOptions().crop.roster.nickname),role:d.getRole(),affiliation:d.getAffiliation(),me:void 0!==f&&d.getNick()===f.getNick(),tooltipRole:b.i18n._("tooltipRole"),tooltipIgnored:b.i18n._("tooltipIgnored")});if(j.length<1){var m=!1,n=a.Room.getPane(c,".roster-pane");if(n.children().length>0){var o=d.getNick().toUpperCase();n.children().each(function(){var a=b(this);return a.attr("data-nick").toUpperCase()>o?(a.before(l),m=!0,!1):!0})}m||n.append(l),a.Roster.showJoinAnimation(d,h,g,c,f)}else i=0,j.replaceWith(l),b("#user-"+g+"-"+h).css({opacity:1}).show(),void 0!==f&&d.getNick()===f.getNick()&&a.Room.getUser(c)&&a.Chat.Toolbar.update(c);void 0!==f&&f.getNick()===d.getNick()?a.Room.setUser(c,d):b("#user-"+g+"-"+h).click(a.Roster.userClick),b("#user-"+g+"-"+h+" .context").click(function(b){a.Chat.Context.show(b.currentTarget,c,d),b.stopPropagation()}),void 0!==f&&f.isInPrivacyList("ignore",d.getJid())&&Candy.View.Pane.Room.addIgnoreIcon(c,d.getJid())}else if("leave"===e)a.Roster.leaveAnimation("user-"+g+"-"+h),"chat"===a.Chat.rooms[c].type?a.Chat.onInfoMessage(c,b.i18n._("userLeftRoom",[d.getNick()])):a.Chat.infoMessage(c,b.i18n._("userLeftRoom",[d.getNick()]));else if("nickchange"===e){i=0,a.Roster.changeNick(g,d),a.Room.changeDataUserJidIfUserIsMe(g,d),a.PrivateRoom.changeNick(c,d);var p=b.i18n._("userChangedNick",[d.getPreviousNick(),d.getNick()]);a.Chat.onInfoMessage(c,p)}else"kick"===e?(a.Roster.leaveAnimation("user-"+g+"-"+h),a.Chat.onInfoMessage(c,b.i18n._("userHasBeenKickedFromRoom",[d.getNick()]))):"ban"===e&&(a.Roster.leaveAnimation("user-"+g+"-"+h),a.Chat.onInfoMessage(c,b.i18n._("userHasBeenBannedFromRoom",[d.getNick()])));Candy.View.Pane.Chat.rooms[c].usercount+=i,c===Candy.View.getCurrent().roomJid&&Candy.View.Pane.Chat.Toolbar.updateUsercount(Candy.View.Pane.Chat.rooms[c].usercount),k.element=b("#user-"+g+"-"+h),b(Candy).triggerHandler("candy:view.roster.after-update",k)},userClick:function(){var c=b(this);a.PrivateRoom.open(c.attr("data-jid"),c.attr("data-nick"),!0)},showJoinAnimation:function(c,d,e,f,g){var h="user-"+e+"-"+d,i=b("#"+h);c.getPreviousNick()&&i&&i.is(":visible")!==!1||(a.Roster.joinAnimation(h),void 0!==g&&c.getNick()!==g.getNick()&&a.Room.getUser(f)&&("chat"===a.Chat.rooms[f].type?a.Chat.onInfoMessage(f,b.i18n._("userJoinedRoom",[c.getNick()])):a.Chat.infoMessage(f,b.i18n._("userJoinedRoom",[c.getNick()]))))},joinAnimation:function(a){b("#"+a).stop(!0).slideDown("normal",function(){b(this).animate({opacity:1})})},leaveAnimation:function(a){b("#"+a).stop(!0).attr("id","#"+a+"-leaving").animate({opacity:0},{complete:function(){b(this).slideUp("normal",function(){b(this).remove()})}})},changeNick:function(a,c){Candy.Core.log("[View:Pane:Roster] changeNick");var d=Strophe.getBareJidFromJid(c.getJid())+"/"+c.getPreviousNick(),e="user-"+a+"-"+Candy.Util.jidToId(d),f=b("#"+e);f.attr("data-nick",c.getNick()),f.attr("data-jid",c.getJid()),f.children("div.label").text(c.getNick()),f.attr("id","user-"+a+"-"+Candy.Util.jidToId(c.getJid()))}},a.Message={submit:function(c){var d,e=Candy.View.getCurrent().roomJid,f=Candy.View.Pane.Chat.rooms[e].type,g=b(this).children(".field").val().substring(0,Candy.View.getOptions().crop.message.body),h={roomJid:e,message:g,xhtmlMessage:d};return b(Candy).triggerHandler("candy:view.message.before-send",h)===!1?void c.preventDefault():(g=h.message,d=h.xhtmlMessage,Candy.Core.Action.Jabber.Room.Message(e,g,f,d),"chat"===f&&g&&a.Message.show(e,a.Room.getUser(e).getNick(),g),b(this).children(".field").val("").focus(),void c.preventDefault())},show:function(c,d,e,f,g){e=Candy.Util.Parser.all(e.substring(0,Candy.View.getOptions().crop.message.body)),f&&(f=Candy.Util.parseAndCropXhtml(f,Candy.View.getOptions().crop.message.body));var h={roomJid:c,name:d,message:e,xhtmlMessage:f};if(b(Candy).triggerHandler("candy:view.message.before-show",h)!==!1&&(e=h.message,f=h.xhtmlMessage,void 0!==f&&f.length>0&&(e=f),e)){var i={template:Candy.View.Template.Message.item,templateData:{name:d,displayName:Candy.Util.crop(d,Candy.View.getOptions().crop.message.nickname),message:e,time:Candy.Util.localizedTime(g||(new Date).toGMTString())}};b(Candy).triggerHandler("candy:view.message.before-render",i);var j=Mustache.to_html(i.template,i.templateData);a.Room.appendToMessagePane(c,j);var k=a.Room.getPane(c,".message-pane").children().last();k.find("a.label").click(function(b){b.preventDefault();var e=Candy.Core.getRoom(c);return e&&d!==a.Room.getUser(Candy.View.getCurrent().roomJid).getNick()&&e.getRoster().get(c+"/"+d)&&Candy.View.Pane.PrivateRoom.open(c+"/"+d,d,!0)===!1?!1:void 0}),Candy.View.getCurrent().roomJid===c&&a.Window.hasFocus()||(a.Chat.increaseUnreadMessages(c),"chat"!==Candy.View.Pane.Chat.rooms[c].type||a.Window.hasFocus()||a.Chat.Toolbar.playSound()),Candy.View.getCurrent().roomJid===c&&a.Room.scrollToBottom(c),h.element=k,b(Candy).triggerHandler("candy:view.message.after-show",h)}}},a}(Candy.View.Pane||{},jQuery),Candy.View.Template=function(a){return 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">×</a><small class="unread"></small></li>',modal:'<div id="chat-modal"><a id="admin-message-cancel" class="close" href="#">×</a><span id="chat-modal-body"></span><img src="{{assetsPath}}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="{{assetsPath}}audioplayer.swf" width="0" height="0"><param name="movie" value="{{assetsPath}}audioplayer.swf" /><param name="AllowScriptAccess" value="always" /><param name="FlashVars" value="listener=audioplayerListener&amp;mp3={{assetsPath}}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" aria-label="Message Form Text Field" 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">{{#displayNickname}}<label for="username">{{_labelNickname}}</label><input type="text" id="username" name="username"/>{{/displayNickname}}{{#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>"},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:",labelNickname:"Nickname:",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.",userChangedNick:"%1$s has changed his nickname to %2$s.",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.',errorAutojoinMissing:"No autojoin parameter set in configuration. Please set one to continue.",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:",labelNickname:"Spitzname:",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.",userChangedNick:"%1$s hat den Nicknamen zu %2$s geändert.",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.',errorAutojoinMissing:'Keine "autojoin" Konfiguration gefunden. Bitte setze eine konfiguration um fortzufahren.',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.",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."},pt_br:{status:"Estado: %s",statusConnecting:"Conectando...",statusConnected:"Conectado",statusDisconnecting:"Desconectando...",statusDisconnected:"Desconectado",statusAuthfail:"Autenticação falhou",roomSubject:"Assunto:",messageSubmit:"Enviar",labelUsername:"Usuário:",labelPassword:"Senha:",loginSubmit:"Entrar",loginInvalid:"JID inválido",reason:"Motivo:",subject:"Assunto:",reasonWas:"Motivo foi: %s.",kickActionLabel:"Derrubar",youHaveBeenKickedBy:"Você foi derrubado de %2$s por %1$s",youHaveBeenKicked:"Você foi derrubado de %s",banActionLabel:"Banir",youHaveBeenBannedBy:"Você foi banido de %1$s por %2$s",youHaveBeenBanned:"Você foi banido de %s",privateActionLabel:"Conversa privada",ignoreActionLabel:"Ignorar",unignoreActionLabel:"Não ignorar",setSubjectActionLabel:"Mudar Assunto",administratorMessageSubject:"Administrador",userJoinedRoom:"%s entrou na sala.",userLeftRoom:"%s saiu da sala.",userHasBeenKickedFromRoom:"%s foi derrubado da sala.",userHasBeenBannedFromRoom:"%s foi banido da sala.",presenceUnknownWarningSubject:"Aviso:",presenceUnknownWarning:"Este usuário pode estar desconectado.. Não conseguimos rastrear sua presença..",dateFormat:"dd.mm.yyyy",timeFormat:"HH:MM:ss",tooltipRole:"Moderador",tooltipIgnored:"Você ignora este usuário",tooltipEmoticons:"Emoticons",tooltipSound:"Tocar som para novas mensagens privadas",tooltipAutoscroll:"Auto-rolagem",tooltipStatusmessage:"Exibir mensagens de estados",tooltipAdministration:"Administração de Sala",tooltipUsercount:"Participantes da Sala",enterRoomPassword:'Sala "%s" é protegida por senha.',enterRoomPasswordSubmit:"Entrar na sala",passwordEnteredInvalid:'Senha inváida para sala "%s".',nicknameConflict:"Nome de usuário já em uso. Por favor escolha outro.",errorMembersOnly:'Você não pode entrar na sala "%s": privilégios insuficientes.',errorMaxOccupantsReached:'Você não pode entrar na sala "%s": máximo de participantes atingido.',antiSpamMessage:"Por favor, não faça 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."}};
-
-//# sourceMappingURL=candy.min.map \ No newline at end of file
diff --git a/candy.min.map b/candy.min.map
deleted file mode 100644
index 46000e8..0000000
--- a/candy.min.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"file":"candy.min.js","sources":["candy.bundle.js"],"names":["Candy","self","$","about","name","version","init","service","options","viewClass","View","view","Core","core","jQuery","Strophe","_status","_connection","_service","_user","_rooms","_anonymousConnection","_options","autojoin","undefined","debug","disableWindowUnload","presencePriority","resource","_addNamespace","value","addNamespace","_addNamespaces","_getEscapedJidFromJid","jid","node","getNodeFromJid","domain","getDomainFromJid","escapeNode","extend","window","console","log","Function","prototype","bind","Util","getIeVersion","call","apply","arguments","Connection","rawInput","rawOutput","caps","onbeforeunload","onWindowUnload","registerEventHandlers","addHandler","Event","Jabber","Version","NS","VERSION","Presence","Message","Bookmarks","PRIVATE","Room","Disco","DISCO_INFO","disco","_onDiscoInfo","_onDiscoItems","DISCO_ITEMS","_delegateCapabilities","CAPS","connect","jidOrHost","password","nick","reset","triggerHandler","connection","indexOf","Connect","ChatUser","Login","attach","sid","rid","disconnect","connected","each","getRooms","Action","Leave","this","getJid","handler","ns","type","id","from","getUser","setUser","user","getConnection","removeRoom","roomJid","getStropheStatus","setStropheStatus","status","isAnonymousConnection","getOptions","getRoom","sync","flush","data","_current","container","language","assets","messages","limit","remove","crop","message","nickname","body","roster","enableXHTML","_setupTranslation","i18n","load","Translation","_registerObservers","on","Observer","Chat","AutojoinMissing","update","PresenceError","_registerWindowHandlers","document","focusin","Pane","Window","onFocus","focusout","onBlur","focus","blur","resize","fitTabs","_initToolbar","Toolbar","_delegateTooltips","delegate","Tooltip","show","resources","Parser","setEmoticonPath","html","Mustache","to_html","Template","pane","tooltipEmoticons","_","tooltipSound","tooltipAutoscroll","tooltipStatusmessage","tooltipAdministration","tooltipUsercount","assetsPath","tabs","rooms","modal","toolbar","soundcontrol","getCurrent","jidToId","MD5","hexdigest","escapeJid","getResourceFromJid","unescapeJid","unescapeNode","str","len","length","substr","parseAndCropXhtml","append","createHtml","get","setCookie","lifetime_days","exp","Date","setDate","getDate","cookie","toUTCString","cookieExists","getCookie","regex","RegExp","escape","matches","exec","deleteCookie","getPosLeftAccordingToWindowBounds","elem","pos","windowWidth","width","elemWidth","outerWidth","marginDiff","backgroundPositionAlignment","px","getPosTopAccordingToWindowBounds","windowHeight","height","elemHeight","outerHeight","localizedTime","dateTime","date","iso8601toDate","format","toDateString","timestamp","parse","isNaN","struct","minutesOffset","getTimezoneOffset","replace","isEmptyObject","obj","prop","hasOwnProperty","forceRedraw","css","display","setTimeout","ie","undef","v","div","createElement","all","getElementsByTagName","innerHTML","_emoticonPath","path","emoticons","plain","image","emotify","text","i","linkify","nl2br","maxLength","currentLength","el","j","tag","attribute","cssAttrs","attr","cssName","cssValue","nodeType","ElementType","NORMAL","nodeName","toLowerCase","XHTML","validTag","attributes","getAttribute","cssText","split","validCSS","push","join","childNodes","e","xmlTextNode","xmlGenerator","createDocumentFragment","appendChild","FRAGMENT","TEXT","nodeValue","substring","parseHTML","msg","sendIQ","$iq","to","c","os","navigator","userAgent","SetNickname","Array","roomNick","presence","conn","$pres","getUniqueId","send","Roster","xmlns","CLIENT","ROSTER","tree","pres","t","toString","up","generateCapsAttrs","Services","Autojoin","BOOKMARKS","pubsubBookmarkRequest","PUBSUB","isArray","Join","valueOf","ResetIgnoreList","getEscapedJid","PRIVACY","action","order","RemoveIgnoreList","GetIgnoreList","iq","iqId","PrivacyList","SetIgnoreListActive","GetJidIfAnonymous","getNick","MUC","muc","leave","xhtmlMsg","trim","getBareJidFromJid","Invite","invitees","reason","$msg","x","MUC_USER","invitee","IgnoreUnignore","userJid","addToOrRemoveFromPrivacyList","UpdatePrivacyList","currentUser","privacyList","getPrivacyList","index","Admin","UserAction","itemObj","role","affiliation","MUC_ADMIN","SetSubject","subject","setTopic","ChatRoom","room","ChatRoster","setName","getName","setRoster","getRoster","items","add","getAll","ROLE_MODERATOR","AFFILIATION_OWNER","privacyLists","customData","previousNick","setJid","setNick","getRole","setRole","setAffiliation","getAffiliation","isModerator","list","splice","setPrivacyLists","lists","isInPrivacyList","setCustomData","getCustomData","setPreviousNick","getPreviousNick","presetJid","Status","CONNECTED","ATTACHED","DISCONNECTED","AUTHFAIL","CONNECTING","DISCONNECTING","AUTHENTICATING","ERROR","CONNFAIL","children","stanza","item","PrivacyListError","fromJid","toJid","mediatedInvite","find","directInvite","passwordNode","continueNode","continuedThread","actor","roomName","code","identity","presenceType","nickAssign","nickChange","l","$status","tagName","error","chatstate","bareRoomJid","isNoConferenceRoomJid","xhtmlChild","XHTML_IM","xhtmlMessage","first","delay","DELAY","_showConnectedMessageModal","event","args","eventName","Modal","hide","showLoginForm","adminMessage","onInfoMessage","close","notifyPrivateChats","actionLabel","actorName","translationParams","Context","adminMessageReason","_action","_reason","evtData","PrivateRoom","setStatus","showEnterPasswordForm","showNicknameConflictForm","showError","setSubject","infoMessage","open","_hasFocus","_plainTitle","title","_unreadMessagesCount","autoscroll","hasFocus","increaseUnreadMessages","renderUnreadMessages","reduceUnreadMessages","num","clearUnreadMessages","count","unreadmessages","setFocusToForm","addTab","roomType","roomId","tab","privateUserChat","appendTo","click","tabClick","tabClose","getTab","removeTab","setActiveTab","addClass","removeClass","unreadElem","parseInt","currentRoomJid","scrollPosition","getPane","scrollTop","preventDefault","parent","allTabsClosed","availableWidth","innerWidth","tabsWidth","overflow","tabDiffToRealWidth","tabWidth","Math","floor","sender","time","toGMTString","appendToMessagePane","scrollToBottom","_supportsNativeAudio","showEmoticonsMenu","currentTarget","stopPropagation","onAutoscrollControlClick","a","canPlayType","onSoundControlClick","onStatusMessageControlClick","context","me","updateUsercount","usercount","playSound","onPlaySound","Audio","play","chatSoundPlayer","getElementById","SetVariable","control","hasClass","toggleClass","onScrollToStoredPosition","onScrollToBottom","showCloseControl","showSpinner","hideCloseControl","hideSpinner","stop","fadeIn","callback","fadeOut","keydown","which","form","_labelNickname","_labelUsername","_labelPassword","_loginSubmit","displayPassword","displayUsername","displayNickname","submit","username","val","enterPasswordForm","_label","_joinSubmit","nicknameConflictForm","replacements","displayError","_error","content","tooltip","target","offset","posLeft","left","posTop","top","mouseleave","menu","links","menulinks","getMenuLinks","clickHandler","link","class","label","element","initialMenuLinks","requiredPermission","private","ignore","ignoreUser","unignore","unignoreUser","kick","contextModalForm","_submit","ban","input","emoticon","messageCount","_messageSubmit","_userOnline","_roomSubject","openRooms","last","sliceMessagePane","slice","messagePane","roomPane","chatPane","addIgnoreIcon","removeIgnoreIcon","subPane","changeDataUserJidIfUserIsMe","roomElement","switchToRoom","messageForm","removeAttr","changeNick","roomTabElement","previousPrivateRoomJid","newPrivateRoomJid","previousPrivateRoomId","newPrivateRoomId","userId","usercountDiff","userElem","displayNick","tooltipRole","tooltipIgnored","userInserted","rosterPane","userSortCompare","toUpperCase","before","showJoinAnimation","replaceWith","opacity","userClick","leaveAnimation","rosterUserId","$rosterUserElem","is","joinAnimation","elementId","slideDown","animate","complete","slideUp","previousUserJid","renderEvtData","template","templateData","displayName","en","statusConnecting","statusConnected","statusDisconnecting","statusDisconnected","statusAuthfail","roomSubject","messageSubmit","labelUsername","labelNickname","labelPassword","loginSubmit","loginInvalid","reasonWas","kickActionLabel","youHaveBeenKickedBy","youHaveBeenKicked","banActionLabel","youHaveBeenBannedBy","youHaveBeenBanned","privateActionLabel","ignoreActionLabel","unignoreActionLabel","setSubjectActionLabel","administratorMessageSubject","userJoinedRoom","userLeftRoom","userHasBeenKickedFromRoom","userHasBeenBannedFromRoom","userChangedNick","presenceUnknownWarningSubject","presenceUnknownWarning","dateFormat","timeFormat","enterRoomPassword","enterRoomPasswordSubmit","passwordEnteredInvalid","nicknameConflict","errorMembersOnly","errorMaxOccupantsReached","errorAutojoinMissing","antiSpamMessage","de","fr","nl","es","cn","ja","sv","it","pt","pt_br","ru","ca"],"mappings":"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"} \ No newline at end of file
diff --git a/devbox/index.html b/devbox/index.html
new file mode 100644
index 0000000..0881df5
--- /dev/null
+++ b/devbox/index.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+ <title>Candy - Chats are not dead yet</title>
+ <link rel="shortcut icon" href="candy/res/img/favicon.png" type="image/gif" />
+ <link rel="stylesheet" type="text/css" href="candy/res/default.css" />
+
+ <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.js"></script>
+ <script type="text/javascript" src="candy/libs.bundle.js"></script>
+ <script type="text/javascript" src="candy/candy.bundle.js"></script>
+ <script type="text/javascript">
+ $(document).ready(function() {
+ //Candy.init('/http-bind/', { // uncomment & comment next line if you'd like to use BOSH
+ Candy.init('ws://localhost:5280/xmpp-websocket/', {
+ core: {
+ // only set this to true if developing / debugging errors
+ debug: true,
+ // autojoin is a *required* parameter if you don't have a plugin (e.g. roomPanel) for it
+ // true
+ // -> fetch info from server (NOTE: does only work with openfire server)
+ // ['test@conference.example.com']
+ // -> array of rooms to join after connecting
+ autojoin: ['test@conference.localhost']
+ },
+ view: { assets: 'candy/res/' }
+ });
+
+ Candy.Core.connect('localhost');
+
+ /**
+ * Thanks for trying Candy!
+ *
+ * If you need more information, please see here:
+ * - Setup instructions & config params: http://candy-chat.github.io/candy/#setup
+ * - FAQ & more: https://github.com/candy-chat/candy/wiki
+ *
+ * Mailinglist for questions:
+ * - http://groups.google.com/group/candy-chat
+ *
+ * Github issues for bugs:
+ * - https://github.com/candy-chat/candy/issues
+ */
+ });
+ </script>
+</head>
+<body>
+ <div id="candy"></div>
+</body>
+</html>
diff --git a/devbox/nginx-default.conf b/devbox/nginx-default.conf
new file mode 100644
index 0000000..6bad803
--- /dev/null
+++ b/devbox/nginx-default.conf
@@ -0,0 +1,14 @@
+server {
+ root /usr/share/nginx/html;
+ index index.html;
+
+ charset utf-8;
+
+ server_name localhost;
+
+ location /http-bind/ {
+ proxy_pass http://localhost:5280/http-bind/;
+ proxy_buffering off;
+ tcp_nodelay on;
+ }
+}
diff --git a/devbox/prosody.cfg.lua b/devbox/prosody.cfg.lua
new file mode 100644
index 0000000..5572bcb
--- /dev/null
+++ b/devbox/prosody.cfg.lua
@@ -0,0 +1,184 @@
+-- Prosody Example Configuration File
+--
+-- Information on configuring Prosody can be found on our
+-- website at http://prosody.im/doc/configure
+--
+-- Tip: You can check that the syntax of this file is correct
+-- when you have finished by running: luac -p prosody.cfg.lua
+-- If there are any errors, it will let you know what and where
+-- they are, otherwise it will keep quiet.
+--
+-- The only thing left to do is rename this file to remove the .dist ending, and fill in the
+-- blanks. Good luck, and happy Jabbering!
+
+
+---------- Server-wide settings ----------
+-- Settings in this section apply to the whole server and are the default settings
+-- for any virtual hosts
+
+-- This is a (by default, empty) list of accounts that are admins
+-- for the server. Note that you must create the accounts separately
+-- (see http://prosody.im/doc/creating_accounts for info)
+-- Example: admins = { "user1@example.com", "user2@example.net" }
+admins = { }
+
+-- Enable use of libevent for better performance under high load
+-- For more information see: http://prosody.im/doc/libevent
+use_libevent = true;
+
+-- This is the list of modules Prosody will load on startup.
+-- It looks for mod_modulename.lua in the plugins folder, so make sure that exists too.
+-- Documentation on modules can be found at: http://prosody.im/doc/modules
+modules_enabled = {
+
+ -- Generally required
+ "roster"; -- Allow users to have a roster. Recommended ;)
+ "saslauth"; -- Authentication for clients and servers. Recommended if you want to log in.
+ "tls"; -- Add support for secure TLS on c2s/s2s connections
+ "dialback"; -- s2s dialback support
+ "disco"; -- Service discovery
+
+ -- Not essential, but recommended
+ "private"; -- Private XML storage (for room bookmarks, etc.)
+ --"vcard"; -- Allow users to set vCards
+ "privacy"; -- Support privacy lists
+ --"compression"; -- Stream compression
+
+ -- Nice to have
+ --"legacyauth"; -- Legacy authentication. Only used by some old clients and bots.
+ "version"; -- Replies to server version requests
+ "uptime"; -- Report how long server has been running
+ "time"; -- Let others know the time here on this server
+ "ping"; -- Replies to XMPP pings with pongs
+ --"pep"; -- Enables users to publish their mood, activity, playing music and more
+ "register"; -- Allow users to register on this server using a client and change passwords
+ "adhoc"; -- Support for "ad-hoc commands" that can be executed with an XMPP client
+
+ -- Admin interfaces
+ "admin_adhoc"; -- Allows administration via an XMPP client that supports ad-hoc commands
+ --"admin_telnet"; -- Opens telnet console interface on localhost port 5582
+
+ -- Other specific functionality
+ "bosh"; -- Enable BOSH clients, aka "Jabber over HTTP"
+ "websocket"; -- Websocket support
+ --"httpserver"; -- Serve static files from a directory over HTTP
+ --"groups"; -- Shared roster support
+ --"announce"; -- Send announcement to all online users
+ --"welcome"; -- Welcome users who register accounts
+ --"watchregistrations"; -- Alert admins of registrations
+ --"motd"; -- Send a message to users when they log in
+ "carbons";
+ -- Debian: do not remove this module, or you lose syslog
+ -- support
+ "posix"; -- POSIX functionality, sends server to background, enables syslog, etc.
+};
+
+-- These modules are auto-loaded, should you
+-- (for some mad reason) want to disable
+-- them then uncomment them below
+modules_disabled = {
+ -- "presence"; -- Route user/contact status information
+ -- "message"; -- Route messages
+ -- "iq"; -- Route info queries
+ -- "offline"; -- Store offline messages
+};
+
+-- Disable account creation by default, for security
+-- For more information see http://prosody.im/doc/creating_accounts
+allow_registration = false;
+
+-- Debian:
+-- send the server to background.
+--
+daemonize = true;
+
+-- Debian:
+-- Please, don't change this option since /var/run/prosody/
+-- is one of the few directories Prosody is allowed to write to
+--
+pidfile = "/var/run/prosody/prosody.pid";
+
+-- These are the SSL/TLS-related settings. If you don't want
+-- to use SSL/TLS, you may comment or remove this
+ssl = {
+ key = "/etc/prosody/certs/localhost.key";
+ certificate = "/etc/prosody/certs/localhost.crt";
+}
+
+-- Only allow encrypted streams? Encryption is already used when
+-- available. These options will cause Prosody to deny connections that
+-- are not encrypted. Note that some servers do not support s2s
+-- encryption or have it disabled, including gmail.com and Google Apps
+-- domains.
+
+--c2s_require_encryption = false
+--s2s_require_encryption = false
+
+-- Select the authentication backend to use. The 'internal' providers
+-- use Prosody's configured data storage to store the authentication data.
+-- To allow Prosody to offer secure authentication mechanisms to clients, the
+-- default provider stores passwords in plaintext. If you do not trust your
+-- server please see http://prosody.im/doc/modules/mod_auth_internal_hashed
+-- for information about using the hashed backend.
+
+authentication = "anonymous"
+
+-- Select the storage backend to use. By default Prosody uses flat files
+-- in its configured data directory, but it also supports more backends
+-- through modules. An "sql" backend is included by default, but requires
+-- additional dependencies. See http://prosody.im/doc/storage for more info.
+
+--storage = "sql" -- Default is "internal"
+
+-- For the "sql" backend, you can uncomment *one* of the below to configure:
+--sql = { driver = "SQLite3", database = "prosody.sqlite" } -- Default. 'database' is the filename.
+--sql = { driver = "MySQL", database = "prosody", username = "prosody", password = "secret", host = "localhost" }
+--sql = { driver = "PostgreSQL", database = "prosody", username = "prosody", password = "secret", host = "localhost" }
+
+-- Logging configuration
+-- For advanced logging see http://prosody.im/doc/logging
+--
+-- Debian:
+-- Logs info and higher to /var/log
+-- Logs errors to syslog also
+log = {
+ -- Log files (change 'info' to 'debug' for debug logs):
+ info = "/var/log/prosody/prosody.log";
+ error = "/var/log/prosody/prosody.err";
+ -- Syslog:
+ { levels = { "error" }; to = "syslog"; };
+}
+
+----------- Virtual hosts -----------
+-- You need to add a VirtualHost entry for each domain you wish Prosody to serve.
+-- Settings under each VirtualHost entry apply *only* to that host.
+
+-- Setup localhost VirtualHost
+VirtualHost "localhost"
+
+------ Components ------
+-- You can specify components to add hosts that provide special services,
+-- like multi-user conferences, and transports.
+-- For more information on components, see http://prosody.im/doc/components
+
+---Set up a MUC (multi-user chat) room server on conference.example.com:
+Component "conference.localhost" "muc"
+
+-- Set up a SOCKS5 bytestream proxy for server-proxied file transfers:
+--Component "proxy.example.com" "proxy65"
+
+---Set up an external component (default component port is 5347)
+--
+-- External components allow adding various services, such as gateways/
+-- transports to other networks like ICQ, MSN and Yahoo. For more info
+-- see: http://prosody.im/doc/components#adding_an_external_component
+--
+--Component "gateway.example.com"
+-- component_secret = "password"
+
+------ Additional config files ------
+-- For organizational purposes you may prefer to add VirtualHost and
+-- Component definitions in their own config files. This line includes
+-- all config files in /etc/prosody/conf.d/
+
+Include "conf.d/*.cfg.lua"
diff --git a/devbox/provisioning.sh b/devbox/provisioning.sh
new file mode 100644
index 0000000..309050b
--- /dev/null
+++ b/devbox/provisioning.sh
@@ -0,0 +1,58 @@
+#!/usr/bin/env bash
+#
+# Vagrant provisioning script
+#
+# Copyright 2014 Michael Weibel <michael.weibel@gmail.com>
+# License: MIT
+#
+
+#
+# Install Prosody XMPP server
+#
+echo "deb http://packages.prosody.im/debian precise main" > /etc/apt/sources.list.d/prosody.list
+wget https://prosody.im/files/prosody-debian-packages.key -O- | sudo apt-key add -
+apt-get update
+
+apt-get install -y liblua5.1-bitop prosody lua-event
+
+# Place config
+cp /vagrant/devbox/prosody.cfg.lua /etc/prosody/prosody.cfg.lua
+
+/etc/init.d/prosody restart
+
+#
+# Install nginx for static file serving
+#
+apt-get install -y nginx
+cp /vagrant/devbox/nginx-default.conf /etc/nginx/sites-available/default
+/etc/init.d/nginx restart
+
+ln -fs /vagrant /usr/share/nginx/html/candy
+ln -fs /vagrant/devbox/index.html /usr/share/nginx/html/index.html
+
+#
+# Candy development dependencies
+#
+sudo add-apt-repository ppa:chris-lea/node.js
+sudo apt-get update
+sudo apt-get install -y nodejs git
+npm install -g grunt-cli
+npm install -g bower
+
+cd /vagrant
+su -u vagrant npm install
+su -u vagrant bower install
+
+#
+# Selenium & PhantomJS for testing
+#
+apt-get install -y openjdk-7-jre
+mkdir /usr/lib/selenium/
+wget --no-verbose --output-document=/usr/lib/selenium/selenium-server-standalone-2.42.2.jar -- http://selenium-release.storage.googleapis.com/2.42/selenium-server-standalone-2.42.2.jar
+mkdir -p /var/log/selenium/
+chmod a+w /var/log/selenium/
+cp /vagrant/devbox/selenium.init.sh /etc/init.d/selenium
+chmod 755 /etc/init.d/selenium
+/etc/init.d/selenium start
+update-rc.d selenium defaults
+apt-get install -y phantomjs
diff --git a/devbox/selenium.init.sh b/devbox/selenium.init.sh
new file mode 100644
index 0000000..e9841ca
--- /dev/null
+++ b/devbox/selenium.init.sh
@@ -0,0 +1,52 @@
+#!/bin/bash
+
+case "${1:-''}" in
+ 'start')
+ if test -f /tmp/selenium.pid
+ then
+ echo "Selenium is already running."
+ else
+ java -jar /usr/lib/selenium/selenium-server-standalone-2.42.2.jar -port 4444 > /var/log/selenium/selenium-output.log 2> /var/log/selenium/selenium-error.log & echo $! > /tmp/selenium.pid
+ echo "Starting Selenium..."
+
+ error=$?
+ if test $error -gt 0
+ then
+ echo "${bon}Error $error! Couldn't start Selenium!${boff}"
+ fi
+ fi
+ ;;
+ 'stop')
+ if test -f /tmp/selenium.pid
+ then
+ echo "Stopping Selenium..."
+ PID=`cat /tmp/selenium.pid`
+ kill -3 $PID
+ if kill -9 $PID ;
+ then
+ sleep 2
+ test -f /tmp/selenium.pid && rm -f /tmp/selenium.pid
+ else
+ echo "Selenium could not be stopped..."
+ fi
+ else
+ echo "Selenium is not running."
+ fi
+ ;;
+ 'restart')
+ if test -f /tmp/selenium.pid
+ then
+ kill -HUP `cat /tmp/selenium.pid`
+ test -f /tmp/selenium.pid && rm -f /tmp/selenium.pid
+ sleep 1
+ java -jar /usr/lib/selenium/selenium-server-standalone-2.42.2.jar -port 4444 > /var/log/selenium/selenium-output.log 2> /var/log/selenium/selenium-error.log & echo $! > /tmp/selenium.pid
+ echo "Reload Selenium..."
+ else
+ echo "Selenium isn't running..."
+ fi
+ ;;
+ *) # no parameter specified
+ echo "Usage: $SELF start|stop|restart|reload|force-reload|status"
+ exit 1
+ ;;
+esac
diff --git a/example/index.html b/example/index.html
index a001237..6087b49 100644
--- a/example/index.html
+++ b/example/index.html
@@ -7,7 +7,7 @@
<link rel="stylesheet" type="text/css" href="../res/default.css" />
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
- <script type="text/javascript" src="../libs/libs.min.js"></script>
+ <script type="text/javascript" src="../libs.min.js"></script>
<script type="text/javascript" src="../candy.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
diff --git a/libs/jquery-i18n b/libs/jquery-i18n
deleted file mode 160000
-Subproject e4dd2cf2d1c4897b876807c1f4a5469a2c342e2
diff --git a/libs/libs.bundle.js b/libs/libs.bundle.js
deleted file mode 100644
index b0d3abc..0000000
--- a/libs/libs.bundle.js
+++ /dev/null
@@ -1,6348 +0,0 @@
-// This code was written by Tyler Akins and has been placed in the
-// public domain. It would be nice if you left this header intact.
-// Base64 code from Tyler Akins -- http://rumkin.com
-var Base64 = function() {
- var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
- var obj = {
- /**
- * Encodes a string in base64
- * @param {String} input The string to encode in base64.
- */
- encode: function(input) {
- var output = "";
- var chr1, chr2, chr3;
- var enc1, enc2, enc3, enc4;
- var i = 0;
- do {
- chr1 = input.charCodeAt(i++);
- chr2 = input.charCodeAt(i++);
- chr3 = input.charCodeAt(i++);
- enc1 = chr1 >> 2;
- enc2 = (chr1 & 3) << 4 | chr2 >> 4;
- enc3 = (chr2 & 15) << 2 | chr3 >> 6;
- enc4 = chr3 & 63;
- if (isNaN(chr2)) {
- enc3 = enc4 = 64;
- } else if (isNaN(chr3)) {
- enc4 = 64;
- }
- output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) + keyStr.charAt(enc3) + keyStr.charAt(enc4);
- } while (i < input.length);
- return output;
- },
- /**
- * Decodes a base64 string.
- * @param {String} input The string to decode.
- */
- decode: function(input) {
- var output = "";
- var chr1, chr2, chr3;
- var enc1, enc2, enc3, enc4;
- var i = 0;
- // remove all characters that are not A-Z, a-z, 0-9, +, /, or =
- input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
- do {
- enc1 = keyStr.indexOf(input.charAt(i++));
- enc2 = keyStr.indexOf(input.charAt(i++));
- enc3 = keyStr.indexOf(input.charAt(i++));
- enc4 = keyStr.indexOf(input.charAt(i++));
- chr1 = enc1 << 2 | enc2 >> 4;
- chr2 = (enc2 & 15) << 4 | enc3 >> 2;
- chr3 = (enc3 & 3) << 6 | enc4;
- output = output + String.fromCharCode(chr1);
- if (enc3 != 64) {
- output = output + String.fromCharCode(chr2);
- }
- if (enc4 != 64) {
- output = output + String.fromCharCode(chr3);
- }
- } while (i < input.length);
- return output;
- }
- };
- 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.
- */
-/* Some functions and variables have been stripped for use with Strophe */
-/*
- * These are the functions you'll usually want to call
- * They take string arguments and return either hex or base-64 encoded strings
- */
-function b64_sha1(s) {
- return binb2b64(core_sha1(str2binb(s), s.length * 8));
-}
-
-function str_sha1(s) {
- return binb2str(core_sha1(str2binb(s), s.length * 8));
-}
-
-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));
-}
-
-/*
- * 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] |= 128 << 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 * 8);
- }
- var ipad = new Array(16), opad = new Array(16);
- for (var i = 0; i < 16; i++) {
- ipad[i] = bkey[i] ^ 909522486;
- opad[i] = bkey[i] ^ 1549556828;
- }
- var hash = core_sha1(ipad.concat(str2binb(data)), 512 + data.length * 8);
- 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 & 65535) + (y & 65535);
- var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
- return msw << 16 | lsw & 65535;
-}
-
-/*
- * 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 = 255;
- for (var i = 0; i < str.length * 8; i += 8) {
- bin[i >> 5] |= (str.charCodeAt(i / 8) & mask) << 24 - i % 32;
- }
- return bin;
-}
-
-/*
- * Convert an array of big-endian words to a string
- */
-function binb2str(bin) {
- var str = "";
- var mask = 255;
- for (var i = 0; i < bin.length * 32; i += 8) {
- str += String.fromCharCode(bin[i >> 5] >>> 24 - i % 32 & mask);
- }
- 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) & 255) << 16 | (binarray[i + 1 >> 2] >> 8 * (3 - (i + 1) % 4) & 255) << 8 | binarray[i + 2 >> 2] >> 8 * (3 - (i + 2) % 4) & 255;
- for (j = 0; j < 4; j++) {
- if (i * 8 + j * 6 > binarray.length * 32) {
- str += "=";
- } else {
- str += tab.charAt(triplet >> 6 * (3 - j) & 63);
- }
- }
- }
- 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.
- * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
- * Distributed under the BSD License
- * See http://pajhome.org.uk/crypt/md5 for more info.
- */
-/*
- * Everything that isn't used by Strophe has been stripped here!
- */
-var MD5 = function() {
- /*
- * Add integers, wrapping at 2^32. This uses 16-bit operations internally
- * to work around bugs in some JS interpreters.
- */
- var safe_add = function(x, y) {
- var lsw = (x & 65535) + (y & 65535);
- var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
- return msw << 16 | lsw & 65535;
- };
- /*
- * Bitwise rotate a 32-bit number to the left.
- */
- var bit_rol = function(num, cnt) {
- return num << cnt | num >>> 32 - cnt;
- };
- /*
- * Convert a string to an array of little-endian words
- */
- var str2binl = function(str) {
- var bin = [];
- for (var i = 0; i < str.length * 8; i += 8) {
- bin[i >> 5] |= (str.charCodeAt(i / 8) & 255) << i % 32;
- }
- return bin;
- };
- /*
- * Convert an array of little-endian words to a string
- */
- var binl2str = function(bin) {
- var str = "";
- for (var i = 0; i < bin.length * 32; i += 8) {
- str += String.fromCharCode(bin[i >> 5] >>> i % 32 & 255);
- }
- return str;
- };
- /*
- * Convert an array of little-endian words to a hex string.
- */
- var binl2hex = function(binarray) {
- var hex_tab = "0123456789abcdef";
- var str = "";
- for (var i = 0; i < binarray.length * 4; i++) {
- str += hex_tab.charAt(binarray[i >> 2] >> i % 4 * 8 + 4 & 15) + hex_tab.charAt(binarray[i >> 2] >> i % 4 * 8 & 15);
- }
- return str;
- };
- /*
- * These functions implement the four basic operations the algorithm uses.
- */
- var md5_cmn = function(q, a, b, x, s, t) {
- return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s), b);
- };
- var md5_ff = function(a, b, c, d, x, s, t) {
- return md5_cmn(b & c | ~b & d, a, b, x, s, t);
- };
- var md5_gg = function(a, b, c, d, x, s, t) {
- return md5_cmn(b & d | c & ~d, a, b, x, s, t);
- };
- var md5_hh = function(a, b, c, d, x, s, t) {
- return md5_cmn(b ^ c ^ d, a, b, x, s, t);
- };
- var md5_ii = function(a, b, c, d, x, s, t) {
- return md5_cmn(c ^ (b | ~d), a, b, x, s, t);
- };
- /*
- * Calculate the MD5 of an array of little-endian words, and a bit length
- */
- var core_md5 = function(x, len) {
- /* append padding */
- x[len >> 5] |= 128 << len % 32;
- x[(len + 64 >>> 9 << 4) + 14] = len;
- var a = 1732584193;
- var b = -271733879;
- var c = -1732584194;
- var d = 271733878;
- var olda, oldb, oldc, oldd;
- for (var i = 0; i < x.length; i += 16) {
- olda = a;
- oldb = b;
- oldc = c;
- oldd = d;
- a = md5_ff(a, b, c, d, x[i + 0], 7, -680876936);
- d = md5_ff(d, a, b, c, x[i + 1], 12, -389564586);
- c = md5_ff(c, d, a, b, x[i + 2], 17, 606105819);
- b = md5_ff(b, c, d, a, x[i + 3], 22, -1044525330);
- a = md5_ff(a, b, c, d, x[i + 4], 7, -176418897);
- d = md5_ff(d, a, b, c, x[i + 5], 12, 1200080426);
- c = md5_ff(c, d, a, b, x[i + 6], 17, -1473231341);
- b = md5_ff(b, c, d, a, x[i + 7], 22, -45705983);
- a = md5_ff(a, b, c, d, x[i + 8], 7, 1770035416);
- d = md5_ff(d, a, b, c, x[i + 9], 12, -1958414417);
- c = md5_ff(c, d, a, b, x[i + 10], 17, -42063);
- b = md5_ff(b, c, d, a, x[i + 11], 22, -1990404162);
- a = md5_ff(a, b, c, d, x[i + 12], 7, 1804603682);
- d = md5_ff(d, a, b, c, x[i + 13], 12, -40341101);
- c = md5_ff(c, d, a, b, x[i + 14], 17, -1502002290);
- b = md5_ff(b, c, d, a, x[i + 15], 22, 1236535329);
- a = md5_gg(a, b, c, d, x[i + 1], 5, -165796510);
- d = md5_gg(d, a, b, c, x[i + 6], 9, -1069501632);
- c = md5_gg(c, d, a, b, x[i + 11], 14, 643717713);
- b = md5_gg(b, c, d, a, x[i + 0], 20, -373897302);
- a = md5_gg(a, b, c, d, x[i + 5], 5, -701558691);
- d = md5_gg(d, a, b, c, x[i + 10], 9, 38016083);
- c = md5_gg(c, d, a, b, x[i + 15], 14, -660478335);
- b = md5_gg(b, c, d, a, x[i + 4], 20, -405537848);
- a = md5_gg(a, b, c, d, x[i + 9], 5, 568446438);
- d = md5_gg(d, a, b, c, x[i + 14], 9, -1019803690);
- c = md5_gg(c, d, a, b, x[i + 3], 14, -187363961);
- b = md5_gg(b, c, d, a, x[i + 8], 20, 1163531501);
- a = md5_gg(a, b, c, d, x[i + 13], 5, -1444681467);
- d = md5_gg(d, a, b, c, x[i + 2], 9, -51403784);
- c = md5_gg(c, d, a, b, x[i + 7], 14, 1735328473);
- b = md5_gg(b, c, d, a, x[i + 12], 20, -1926607734);
- a = md5_hh(a, b, c, d, x[i + 5], 4, -378558);
- d = md5_hh(d, a, b, c, x[i + 8], 11, -2022574463);
- c = md5_hh(c, d, a, b, x[i + 11], 16, 1839030562);
- b = md5_hh(b, c, d, a, x[i + 14], 23, -35309556);
- a = md5_hh(a, b, c, d, x[i + 1], 4, -1530992060);
- d = md5_hh(d, a, b, c, x[i + 4], 11, 1272893353);
- c = md5_hh(c, d, a, b, x[i + 7], 16, -155497632);
- b = md5_hh(b, c, d, a, x[i + 10], 23, -1094730640);
- a = md5_hh(a, b, c, d, x[i + 13], 4, 681279174);
- d = md5_hh(d, a, b, c, x[i + 0], 11, -358537222);
- c = md5_hh(c, d, a, b, x[i + 3], 16, -722521979);
- b = md5_hh(b, c, d, a, x[i + 6], 23, 76029189);
- a = md5_hh(a, b, c, d, x[i + 9], 4, -640364487);
- d = md5_hh(d, a, b, c, x[i + 12], 11, -421815835);
- c = md5_hh(c, d, a, b, x[i + 15], 16, 530742520);
- b = md5_hh(b, c, d, a, x[i + 2], 23, -995338651);
- a = md5_ii(a, b, c, d, x[i + 0], 6, -198630844);
- d = md5_ii(d, a, b, c, x[i + 7], 10, 1126891415);
- c = md5_ii(c, d, a, b, x[i + 14], 15, -1416354905);
- b = md5_ii(b, c, d, a, x[i + 5], 21, -57434055);
- a = md5_ii(a, b, c, d, x[i + 12], 6, 1700485571);
- d = md5_ii(d, a, b, c, x[i + 3], 10, -1894986606);
- c = md5_ii(c, d, a, b, x[i + 10], 15, -1051523);
- b = md5_ii(b, c, d, a, x[i + 1], 21, -2054922799);
- a = md5_ii(a, b, c, d, x[i + 8], 6, 1873313359);
- d = md5_ii(d, a, b, c, x[i + 15], 10, -30611744);
- c = md5_ii(c, d, a, b, x[i + 6], 15, -1560198380);
- b = md5_ii(b, c, d, a, x[i + 13], 21, 1309151649);
- a = md5_ii(a, b, c, d, x[i + 4], 6, -145523070);
- d = md5_ii(d, a, b, c, x[i + 11], 10, -1120210379);
- c = md5_ii(c, d, a, b, x[i + 2], 15, 718787259);
- b = md5_ii(b, c, d, a, x[i + 9], 21, -343485551);
- a = safe_add(a, olda);
- b = safe_add(b, oldb);
- c = safe_add(c, oldc);
- d = safe_add(d, oldd);
- }
- return [ a, b, c, d ];
- };
- var obj = {
- /*
- * These are the functions you'll usually want to call.
- * They take string arguments and return either hex or base-64 encoded
- * strings.
- */
- hexdigest: function(s) {
- return binl2hex(core_md5(str2binl(s), s.length * 8));
- },
- hash: function(s) {
- return binl2str(core_md5(str2binl(s), s.length * 8));
- }
- };
- return obj;
-}();
-
-/*
- This program is distributed under the terms of the MIT license.
- Please see the LICENSE file for details.
-
- Copyright 2006-2008, OGG, LLC
-*/
-/* jshint undef: true, unused: true:, noarg: true, latedef: true */
-/*global document, window, setTimeout, clearTimeout, console,
- ActiveXObject, Base64, MD5, DOMParser */
-// from sha1.js
-/*global core_hmac_sha1, binb2str, str_hmac_sha1, str_sha1, b64_hmac_sha1*/
-/** File: strophe.js
- * A JavaScript library for XMPP BOSH/XMPP over Websocket.
- *
- * This is the JavaScript version of the Strophe library. Since JavaScript
- * had no facilities for persistent TCP connections, this library uses
- * Bidirectional-streams Over Synchronous HTTP (BOSH) to emulate
- * a persistent, stateful, two-way connection to an XMPP server. More
- * information on BOSH can be found in XEP 124.
- *
- * This version of Strophe also works with WebSockets.
- * For more information on XMPP-over WebSocket see this RFC draft:
- * http://tools.ietf.org/html/draft-ietf-xmpp-websocket-00
- */
-/** PrivateFunction: Function.prototype.bind
- * Bind a function to an instance.
- *
- * This Function object extension method creates a bound method similar
- * to those in Python. This means that the 'this' object will point
- * to the instance you want. See
- * <a href='https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind'>MDC's bind() documentation</a> and
- * <a href='http://benjamin.smedbergs.us/blog/2007-01-03/bound-functions-and-function-imports-in-javascript/'>Bound Functions and Function Imports in JavaScript</a>
- * for a complete explanation.
- *
- * This extension already exists in some browsers (namely, Firefox 3), but
- * we provide it to support those that don't.
- *
- * Parameters:
- * (Object) obj - The object that will become 'this' in the bound function.
- * (Object) argN - An option argument that will be prepended to the
- * arguments given for the function call
- *
- * Returns:
- * The bound function.
- */
-if (!Function.prototype.bind) {
- Function.prototype.bind = function(obj) {
- var func = this;
- 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, _slice.call(arguments, 0)));
- };
- };
-}
-
-/** PrivateFunction: Array.prototype.indexOf
- * Return the index of an object in an array.
- *
- * This function is not supplied by some JavaScript implementations, so
- * we provide it if it is missing. This code is from:
- * http://developer.mozilla.org/En/Core_JavaScript_1.5_Reference:Objects:Array:indexOf
- *
- * Parameters:
- * (Object) elt - The object to look for.
- * (Integer) from - The index from which to start looking. (optional).
- *
- * Returns:
- * The index of elt in the array or -1 if not found.
- */
-if (!Array.prototype.indexOf) {
- Array.prototype.indexOf = function(elt) {
- var len = this.length;
- var from = Number(arguments[1]) || 0;
- from = from < 0 ? Math.ceil(from) : Math.floor(from);
- if (from < 0) {
- from += len;
- }
- for (;from < len; from++) {
- if (from in this && this[from] === elt) {
- return from;
- }
- }
- return -1;
- };
-}
-
-/* All of the Strophe globals are defined in this special function below so
- * that references to the globals become closures. This will ensure that
- * on page reload, these references will still be available to callbacks
- * that are still executing.
- */
-(function(callback) {
- var Strophe;
- /** Function: $build
- * Create a Strophe.Builder.
- * This is an alias for 'new Strophe.Builder(name, attrs)'.
- *
- * Parameters:
- * (String) name - The root element name.
- * (Object) attrs - The attributes for the root element in object notation.
- *
- * Returns:
- * A new Strophe.Builder object.
- */
- function $build(name, attrs) {
- return new Strophe.Builder(name, attrs);
- }
- /** Function: $msg
- * Create a Strophe.Builder with a <message/> element as the root.
- *
- * Parmaeters:
- * (Object) attrs - The <message/> element attributes in object notation.
- *
- * Returns:
- * A new Strophe.Builder object.
- */
- function $msg(attrs) {
- return new Strophe.Builder("message", attrs);
- }
- /** Function: $iq
- * Create a Strophe.Builder with an <iq/> element as the root.
- *
- * Parameters:
- * (Object) attrs - The <iq/> element attributes in object notation.
- *
- * Returns:
- * A new Strophe.Builder object.
- */
- function $iq(attrs) {
- return new Strophe.Builder("iq", attrs);
- }
- /** Function: $pres
- * Create a Strophe.Builder with a <presence/> element as the root.
- *
- * Parameters:
- * (Object) attrs - The <presence/> element attributes in object notation.
- *
- * Returns:
- * A new Strophe.Builder object.
- */
- function $pres(attrs) {
- return new Strophe.Builder("presence", attrs);
- }
- /** Class: Strophe
- * An object container for all Strophe library functions.
- *
- * This class is just a container for all the objects and constants
- * used in the library. It is not meant to be instantiated, but to
- * provide a namespace for library objects, constants, and functions.
- */
- Strophe = {
- /** Constant: VERSION
- * The version of the Strophe library. Unreleased builds will have
- * a version of head-HASH where HASH is a partial revision.
- */
- VERSION: "02c798f",
- /** Constants: XMPP Namespace Constants
- * Common namespace constants from the XMPP RFCs and XEPs.
- *
- * NS.HTTPBIND - HTTP BIND namespace from XEP 124.
- * NS.BOSH - BOSH namespace from XEP 206.
- * NS.CLIENT - Main XMPP client namespace.
- * NS.AUTH - Legacy authentication namespace.
- * NS.ROSTER - Roster operations namespace.
- * NS.PROFILE - Profile namespace.
- * NS.DISCO_INFO - Service discovery info namespace from XEP 30.
- * NS.DISCO_ITEMS - Service discovery items namespace from XEP 30.
- * NS.MUC - Multi-User Chat namespace from XEP 45.
- * NS.SASL - XMPP SASL namespace from RFC 3920.
- * 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",
- 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"
- },
- /** 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;
- }
- },
- /** Constants: Connection Status Constants
- * Connection status constants for use by the connection handler
- * callback.
- *
- * Status.ERROR - An error has occurred
- * Status.CONNECTING - The connection is currently being made
- * Status.CONNFAIL - The connection attempt failed
- * Status.AUTHENTICATING - The connection is authenticating
- * Status.AUTHFAIL - The authentication attempt failed
- * Status.CONNECTED - The connection has succeeded
- * Status.DISCONNECTED - The connection has been terminated
- * Status.DISCONNECTING - The connection is currently being terminated
- * Status.ATTACHED - The connection has been attached
- */
- Status: {
- ERROR: 0,
- CONNECTING: 1,
- CONNFAIL: 2,
- AUTHENTICATING: 3,
- AUTHFAIL: 4,
- CONNECTED: 5,
- DISCONNECTED: 6,
- DISCONNECTING: 7,
- ATTACHED: 8
- },
- /** Constants: Log Level Constants
- * Logging level indicators.
- *
- * LogLevel.DEBUG - Debug output
- * LogLevel.INFO - Informational output
- * LogLevel.WARN - Warnings
- * LogLevel.ERROR - Errors
- * LogLevel.FATAL - Fatal errors
- */
- LogLevel: {
- DEBUG: 0,
- INFO: 1,
- WARN: 2,
- ERROR: 3,
- FATAL: 4
- },
- /** PrivateConstants: DOM Element Type Constants
- * DOM element types.
- *
- * ElementType.NORMAL - Normal element.
- * ElementType.TEXT - Text data element.
- * ElementType.FRAGMENT - XHTML fragment element.
- */
- ElementType: {
- NORMAL: 1,
- TEXT: 3,
- CDATA: 4,
- FRAGMENT: 11
- },
- /** PrivateConstants: Timeout Values
- * Timeout values for error states. These values are in seconds.
- * These should not be changed unless you know exactly what you are
- * doing.
- *
- * TIMEOUT - Timeout multiplier. A waiting request will be considered
- * failed after Math.floor(TIMEOUT * wait) seconds have elapsed.
- * This defaults to 1.1, and with default wait, 66 seconds.
- * SECONDARY_TIMEOUT - Secondary timeout multiplier. In cases where
- * Strophe can detect early failure, it will consider the request
- * failed if it doesn't return after
- * Math.floor(SECONDARY_TIMEOUT * wait) seconds have elapsed.
- * This defaults to 0.1, and with default wait, 6 seconds.
- */
- TIMEOUT: 1.1,
- SECONDARY_TIMEOUT: .1,
- /** 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.
- * For example:
- * Strophe.addNamespace('PUBSUB', "http://jabber.org/protocol/pubsub");
- *
- * Parameters:
- * (String) name - The name under which the namespace will be
- * referenced under Strophe.NS
- * (String) value - The actual namespace.
- */
- addNamespace: function(name, value) {
- Strophe.NS[name] = value;
- },
- /** Function: forEachChild
- * Map a function over some or all child elements of a given element.
- *
- * This is a small convenience function for mapping a function over
- * some or all of the children of an element. If elemName is null, all
- * children will be passed to the function, otherwise only children
- * whose tag names match elemName will be passed.
- *
- * Parameters:
- * (XMLElement) elem - The element to operate on.
- * (String) elemName - The child element tag name filter.
- * (Function) func - The function to apply to each child. This
- * function should take a single argument, a DOM element.
- */
- forEachChild: function(elem, elemName, func) {
- var i, childNode;
- for (i = 0; i < elem.childNodes.length; i++) {
- childNode = elem.childNodes[i];
- if (childNode.nodeType == Strophe.ElementType.NORMAL && (!elemName || this.isTagEqual(childNode, elemName))) {
- func(childNode);
- }
- }
- },
- /** Function: isTagEqual
- * Compare an element's tag name with a string.
- *
- * This function is case insensitive.
- *
- * Parameters:
- * (XMLElement) el - A DOM element.
- * (String) name - The element name.
- *
- * Returns:
- * true if the element's tag name matches _el_, and false
- * otherwise.
- */
- isTagEqual: function(el, name) {
- return el.tagName.toLowerCase() == name.toLowerCase();
- },
- /** PrivateVariable: _xmlGenerator
- * _Private_ variable that caches a DOM document to
- * generate elements.
- */
- _xmlGenerator: null,
- /** PrivateFunction: _makeGenerator
- * _Private_ function that creates a dummy XML DOM document to serve as
- * an element and text node generator.
- */
- _makeGenerator: function() {
- var doc;
- // 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 {
- doc = document.implementation.createDocument("jabber:client", "strophe", null);
- }
- return doc;
- },
- /** Function: xmlGenerator
- * Get the DOM document to generate elements.
- *
- * Returns:
- * The currently used DOM document.
- */
- xmlGenerator: function() {
- if (!Strophe._xmlGenerator) {
- Strophe._xmlGenerator = Strophe._makeGenerator();
- }
- return Strophe._xmlGenerator;
- },
- /** PrivateFunction: _getIEXmlDom
- * Gets IE xml doc object
- *
- * Returns:
- * A Microsoft XML DOM Object
- * See Also:
- * http://msdn.microsoft.com/en-us/library/ms757837%28VS.85%29.aspx
- */
- _getIEXmlDom: function() {
- var doc = null;
- var docStrings = [ "Msxml2.DOMDocument.6.0", "Msxml2.DOMDocument.5.0", "Msxml2.DOMDocument.4.0", "MSXML2.DOMDocument.3.0", "MSXML2.DOMDocument", "MSXML.DOMDocument", "Microsoft.XMLDOM" ];
- for (var d = 0; d < docStrings.length; d++) {
- if (doc === null) {
- try {
- doc = new ActiveXObject(docStrings[d]);
- } catch (e) {
- doc = null;
- }
- } else {
- break;
- }
- }
- return doc;
- },
- /** Function: xmlElement
- * Create an XML DOM element.
- *
- * This function creates an XML DOM element correctly across all
- * implementations. Note that these are not HTML DOM elements, which
- * aren't appropriate for XMPP stanzas.
- *
- * Parameters:
- * (String) name - The name for the element.
- * (Array|Object) attrs - An optional array or object containing
- * key/value pairs to use as element attributes. The object should
- * be in the format {'key': 'value'} or {key: 'value'}. The array
- * should have the format [['key1', 'value1'], ['key2', 'value2']].
- * (String) text - The text child data for the element.
- *
- * Returns:
- * A new XML DOM element.
- */
- xmlElement: function(name) {
- if (!name) {
- return null;
- }
- var node = Strophe.xmlGenerator().createElement(name);
- // FIXME: this should throw errors if args are the wrong type or
- // there are more than two optional args
- var a, i, k;
- for (a = 1; a < arguments.length; a++) {
- if (!arguments[a]) {
- continue;
- }
- if (typeof arguments[a] == "string" || typeof arguments[a] == "number") {
- node.appendChild(Strophe.xmlTextNode(arguments[a]));
- } else if (typeof arguments[a] == "object" && typeof arguments[a].sort == "function") {
- for (i = 0; i < arguments[a].length; i++) {
- if (typeof arguments[a][i] == "object" && typeof arguments[a][i].sort == "function") {
- node.setAttribute(arguments[a][i][0], arguments[a][i][1]);
- }
- }
- } else if (typeof arguments[a] == "object") {
- for (k in arguments[a]) {
- if (arguments[a].hasOwnProperty(k)) {
- node.setAttribute(k, arguments[a][k]);
- }
- }
- }
- }
- return node;
- },
- /* Function: xmlescape
- * Excapes invalid xml characters.
- *
- * Parameters:
- * (String) text - text to escape.
- *
- * Returns:
- * Escaped text.
- */
- xmlescape: function(text) {
- text = text.replace(/\&/g, "&amp;");
- text = text.replace(/</g, "&lt;");
- text = text.replace(/>/g, "&gt;");
- text = text.replace(/'/g, "&apos;");
- text = text.replace(/"/g, "&quot;");
- return text;
- },
- /** Function: xmlTextNode
- * Creates an XML DOM text node.
- *
- * Provides a cross implementation version of document.createTextNode.
- *
- * Parameters:
- * (String) text - The content of the text node.
- *
- * Returns:
- * A new XML DOM text node.
- */
- xmlTextNode: function(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) {
- var node;
- //ensure text is escaped
- if (window.DOMParser) {
- var 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.
- *
- * Parameters:
- * (XMLElement) elem - A DOM element.
- *
- * Returns:
- * A String with the concatenated text of all text element children.
- */
- getText: function(elem) {
- if (!elem) {
- return null;
- }
- var str = "";
- if (elem.childNodes.length === 0 && elem.nodeType == Strophe.ElementType.TEXT) {
- str += elem.nodeValue;
- }
- for (var i = 0; i < elem.childNodes.length; i++) {
- if (elem.childNodes[i].nodeType == Strophe.ElementType.TEXT) {
- str += elem.childNodes[i].nodeValue;
- }
- }
- return Strophe.xmlescape(str);
- },
- /** Function: copyElement
- * Copy an XML DOM element.
- *
- * This function copies a DOM element and all its descendants and returns
- * the new copy.
- *
- * Parameters:
- * (XMLElement) elem - A DOM element.
- *
- * Returns:
- * A new, copied DOM element tree.
- */
- copyElement: function(elem) {
- var i, el;
- if (elem.nodeType == Strophe.ElementType.NORMAL) {
- el = Strophe.xmlElement(elem.tagName);
- for (i = 0; i < elem.attributes.length; i++) {
- el.setAttribute(elem.attributes[i].nodeName.toLowerCase(), elem.attributes[i].value);
- }
- for (i = 0; i < elem.childNodes.length; i++) {
- el.appendChild(Strophe.copyElement(elem.childNodes[i]));
- }
- } else if (elem.nodeType == Strophe.ElementType.TEXT) {
- el = Strophe.xmlGenerator().createTextNode(elem.nodeValue);
- }
- 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;
- 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;
- }
- }
- // 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.
- *
- * Parameters:
- * (String) node - A node (or local part).
- *
- * Returns:
- * An escaped node (or local part).
- */
- escapeNode: function(node) {
- return node.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");
- },
- /** Function: unescapeNode
- * Unescape a node part (also called local part) of a JID.
- *
- * Parameters:
- * (String) node - A node (or local part).
- *
- * Returns:
- * An unescaped node (or local part).
- */
- unescapeNode: function(node) {
- return node.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, "\\");
- },
- /** Function: getNodeFromJid
- * Get the node portion of a JID String.
- *
- * Parameters:
- * (String) jid - A JID.
- *
- * Returns:
- * A String containing the node.
- */
- getNodeFromJid: function(jid) {
- if (jid.indexOf("@") < 0) {
- return null;
- }
- return jid.split("@")[0];
- },
- /** Function: getDomainFromJid
- * Get the domain portion of a JID String.
- *
- * Parameters:
- * (String) jid - A JID.
- *
- * Returns:
- * A String containing the domain.
- */
- getDomainFromJid: function(jid) {
- var bare = Strophe.getBareJidFromJid(jid);
- if (bare.indexOf("@") < 0) {
- return bare;
- } else {
- var parts = bare.split("@");
- parts.splice(0, 1);
- return parts.join("@");
- }
- },
- /** Function: getResourceFromJid
- * Get the resource portion of a JID String.
- *
- * Parameters:
- * (String) jid - A JID.
- *
- * Returns:
- * A String containing the resource.
- */
- getResourceFromJid: function(jid) {
- var s = jid.split("/");
- if (s.length < 2) {
- return null;
- }
- s.splice(0, 1);
- return s.join("/");
- },
- /** Function: getBareJidFromJid
- * Get the bare JID from a JID String.
- *
- * Parameters:
- * (String) jid - A JID.
- *
- * Returns:
- * A String containing the bare JID.
- */
- getBareJidFromJid: function(jid) {
- return jid ? jid.split("/")[0] : null;
- },
- /** Function: log
- * User overrideable logging function.
- *
- * This function is called whenever the Strophe library calls any
- * of the logging functions. The default implementation of this
- * function does nothing. If client code wishes to handle the logging
- * messages, it should override this with
- * > Strophe.log = function (level, msg) {
- * > (user code here)
- * > };
- *
- * Please note that data sent and received over the wire is logged
- * via Strophe.Connection.rawInput() and Strophe.Connection.rawOutput().
- *
- * The different levels and their meanings are
- *
- * DEBUG - Messages useful for debugging purposes.
- * INFO - Informational messages. This is mostly information like
- * 'disconnect was called' or 'SASL auth succeeded'.
- * WARN - Warnings about potential problems. This is mostly used
- * to report transient connection errors like request timeouts.
- * ERROR - Some error occurred.
- * FATAL - A non-recoverable fatal error occurred.
- *
- * Parameters:
- * (Integer) level - The log level of the log message. This will
- * be one of the values in Strophe.LogLevel.
- * (String) msg - The log message.
- */
- /* jshint ignore:start */
- log: function(level, msg) {
- return;
- },
- /* jshint ignore:end */
- /** Function: debug
- * Log a message at the Strophe.LogLevel.DEBUG level.
- *
- * Parameters:
- * (String) msg - The log message.
- */
- debug: function(msg) {
- this.log(this.LogLevel.DEBUG, msg);
- },
- /** Function: info
- * Log a message at the Strophe.LogLevel.INFO level.
- *
- * Parameters:
- * (String) msg - The log message.
- */
- info: function(msg) {
- this.log(this.LogLevel.INFO, msg);
- },
- /** Function: warn
- * Log a message at the Strophe.LogLevel.WARN level.
- *
- * Parameters:
- * (String) msg - The log message.
- */
- warn: function(msg) {
- this.log(this.LogLevel.WARN, msg);
- },
- /** Function: error
- * Log a message at the Strophe.LogLevel.ERROR level.
- *
- * Parameters:
- * (String) msg - The log message.
- */
- error: function(msg) {
- this.log(this.LogLevel.ERROR, msg);
- },
- /** Function: fatal
- * Log a message at the Strophe.LogLevel.FATAL level.
- *
- * Parameters:
- * (String) msg - The log message.
- */
- fatal: function(msg) {
- this.log(this.LogLevel.FATAL, msg);
- },
- /** Function: serialize
- * Render a DOM element and all descendants to a String.
- *
- * Parameters:
- * (XMLElement) elem - A DOM element.
- *
- * Returns:
- * The serialized element tree as a String.
- */
- serialize: function(elem) {
- var result;
- if (!elem) {
- return null;
- }
- if (typeof elem.tree === "function") {
- elem = elem.tree();
- }
- var nodeName = elem.nodeName;
- var i, child;
- if (elem.getAttribute("_realname")) {
- nodeName = elem.getAttribute("_realname");
- }
- result = "<" + nodeName;
- for (i = 0; i < elem.attributes.length; i++) {
- if (elem.attributes[i].nodeName != "_realname") {
- result += " " + elem.attributes[i].nodeName.toLowerCase() + "='" + elem.attributes[i].value.replace(/&/g, "&amp;").replace(/\'/g, "&apos;").replace(/>/g, "&gt;").replace(/</g, "&lt;") + "'";
- }
- }
- if (elem.childNodes.length > 0) {
- result += ">";
- for (i = 0; i < elem.childNodes.length; i++) {
- child = elem.childNodes[i];
- switch (child.nodeType) {
- case Strophe.ElementType.NORMAL:
- // normal element, so recurse
- result += Strophe.serialize(child);
- break;
-
- case Strophe.ElementType.TEXT:
- // text element to escape values
- result += Strophe.xmlescape(child.nodeValue);
- break;
-
- case Strophe.ElementType.CDATA:
- // cdata section so don't escape values
- result += "<![CDATA[" + child.nodeValue + "]]>";
- }
- }
- result += "</" + nodeName + ">";
- } else {
- result += "/>";
- }
- return result;
- },
- /** PrivateVariable: _requestId
- * _Private_ variable that keeps track of the request ids for
- * connections.
- */
- _requestId: 0,
- /** PrivateVariable: Strophe.connectionPlugins
- * _Private_ variable Used to store plugin names that need
- * initialization on Strophe.Connection construction.
- */
- _connectionPlugins: {},
- /** Function: addConnectionPlugin
- * Extends the Strophe.Connection object with the given plugin.
- *
- * Parameters:
- * (String) name - The name of the extension.
- * (Object) ptype - The plugin's prototype.
- */
- addConnectionPlugin: function(name, ptype) {
- Strophe._connectionPlugins[name] = ptype;
- }
- };
- /** Class: Strophe.Builder
- * XML DOM builder.
- *
- * This object provides an interface similar to JQuery but for building
- * DOM element easily and rapidly. All the functions except for toString()
- * and tree() return the object, so calls can be chained. Here's an
- * example using the $iq() builder helper.
- * > $iq({to: 'you', from: 'me', type: 'get', id: '1'})
- * > .c('query', {xmlns: 'strophe:example'})
- * > .c('example')
- * > .toString()
- * The above generates this XML fragment
- * > <iq to='you' from='me' type='get' id='1'>
- * > <query xmlns='strophe:example'>
- * > <example/>
- * > </query>
- * > </iq>
- * The corresponding DOM manipulations to get a similar fragment would be
- * a lot more tedious and probably involve several helper variables.
- *
- * Since adding children makes new operations operate on the child, up()
- * is provided to traverse up the tree. To add two children, do
- * > builder.c('child1', ...).up().c('child2', ...)
- * The next operation on the Builder will be relative to the second child.
- */
- /** Constructor: Strophe.Builder
- * Create a Strophe.Builder object.
- *
- * The attributes should be passed in object notation. For example
- * > var b = new Builder('message', {to: 'you', from: 'me'});
- * or
- * > var b = new Builder('messsage', {'xml:lang': 'en'});
- *
- * Parameters:
- * (String) name - The name of the root element.
- * (Object) attrs - The attributes for the root element in object notation.
- *
- * Returns:
- * A new Strophe.Builder.
- */
- Strophe.Builder = function(name, attrs) {
- // Set correct namespace for jabber:client elements
- if (name == "presence" || name == "message" || name == "iq") {
- if (attrs && !attrs.xmlns) {
- attrs.xmlns = Strophe.NS.CLIENT;
- } else if (!attrs) {
- attrs = {
- xmlns: Strophe.NS.CLIENT
- };
- }
- }
- // Holds the tree being built.
- this.nodeTree = Strophe.xmlElement(name, attrs);
- // Points to the current operation node.
- this.node = this.nodeTree;
- };
- Strophe.Builder.prototype = {
- /** Function: tree
- * Return the DOM tree.
- *
- * This function returns the current DOM tree as an element object. This
- * is suitable for passing to functions like Strophe.Connection.send().
- *
- * Returns:
- * The DOM tree as a element object.
- */
- tree: function() {
- return this.nodeTree;
- },
- /** Function: toString
- * Serialize the DOM tree to a String.
- *
- * This function returns a string serialization of the current DOM
- * tree. It is often used internally to pass data to a
- * Strophe.Request object.
- *
- * Returns:
- * The serialized DOM tree in a String.
- */
- toString: function() {
- return Strophe.serialize(this.nodeTree);
- },
- /** Function: up
- * Make the current parent element the new current element.
- *
- * This function is often used after c() to traverse back up the tree.
- * For example, to add two children to the same element
- * > builder.c('child1', {}).up().c('child2', {});
- *
- * Returns:
- * The Stophe.Builder object.
- */
- up: function() {
- this.node = this.node.parentNode;
- return this;
- },
- /** Function: attrs
- * Add or modify attributes of the current element.
- *
- * The attributes should be passed in object notation. This function
- * does not move the current element pointer.
- *
- * Parameters:
- * (Object) moreattrs - The attributes to add/modify in object notation.
- *
- * Returns:
- * The Strophe.Builder object.
- */
- attrs: function(moreattrs) {
- for (var k in moreattrs) {
- if (moreattrs.hasOwnProperty(k)) {
- this.node.setAttribute(k, moreattrs[k]);
- }
- }
- return this;
- },
- /** Function: c
- * Add a child to the current element and make it the new current
- * element.
- *
- * This function moves the current element pointer to the child,
- * unless text is provided. If you need to add another child, it
- * is necessary to use up() to go back to the parent in the tree.
- *
- * Parameters:
- * (String) name - The name of the child.
- * (Object) attrs - The attributes of the child in object notation.
- * (String) text - The text to add to the child.
- *
- * Returns:
- * The Strophe.Builder object.
- */
- c: function(name, attrs, text) {
- var child = Strophe.xmlElement(name, attrs, text);
- this.node.appendChild(child);
- if (!text) {
- this.node = child;
- }
- return this;
- },
- /** Function: cnode
- * Add a child to the current element and make it the new current
- * element.
- *
- * This function is the same as c() except that instead of using a
- * name and an attributes object to create the child it uses an
- * existing DOM element object.
- *
- * Parameters:
- * (XMLElement) elem - A DOM element.
- *
- * Returns:
- * The Strophe.Builder object.
- */
- cnode: function(elem) {
- var impNode;
- var xmlGen = Strophe.xmlGenerator();
- try {
- impNode = xmlGen.importNode !== undefined;
- } catch (e) {
- impNode = false;
- }
- var newElem = impNode ? xmlGen.importNode(elem, true) : Strophe.copyElement(elem);
- this.node.appendChild(newElem);
- this.node = newElem;
- return this;
- },
- /** Function: t
- * Add a child text element.
- *
- * This *does not* make the child the new current element since there
- * are no children of text elements.
- *
- * Parameters:
- * (String) text - The text data to append to the current element.
- *
- * Returns:
- * The Strophe.Builder object.
- */
- t: function(text) {
- 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.
- *
- * A Strophe.Handler encapsulates a user provided callback function to be
- * executed when matching stanzas are received by the connection.
- * Handlers can be either one-off or persistant depending on their
- * return value. Returning true will cause a Handler to remain active, and
- * returning false will remove the Handler.
- *
- * Users will not use Strophe.Handler objects directly, but instead they
- * will use Strophe.Connection.addHandler() and
- * Strophe.Connection.deleteHandler().
- */
- /** PrivateConstructor: Strophe.Handler
- * Create and initialize a new Strophe.Handler.
- *
- * Parameters:
- * (Function) handler - A function to be executed when the handler is run.
- * (String) ns - The namespace to match.
- * (String) name - The element name to match.
- * (String) type - The element type to match.
- * (String) id - The element id attribute to match.
- * (String) from - The element from attribute to match.
- * (Object) options - Handler options
- *
- * Returns:
- * A new Strophe.Handler object.
- */
- Strophe.Handler = function(handler, ns, name, type, id, from, options) {
- this.handler = handler;
- this.ns = ns;
- this.name = name;
- 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;
- }
- if (this.options.matchBare) {
- this.from = from ? Strophe.getBareJidFromJid(from) : null;
- } else {
- this.from = from;
- }
- // whether the handler is a user handler or a system handler
- this.user = true;
- };
- Strophe.Handler.prototype = {
- /** PrivateFunction: isMatch
- * Tests if a stanza matches the Strophe.Handler.
- *
- * Parameters:
- * (XMLElement) elem - The XML element to test.
- *
- * Returns:
- * true if the stanza matches and false otherwise.
- */
- isMatch: function(elem) {
- var nsMatch;
- var from = null;
- if (this.options.matchBare) {
- from = Strophe.getBareJidFromJid(elem.getAttribute("from"));
- } else {
- from = elem.getAttribute("from");
- }
- nsMatch = false;
- if (!this.ns) {
- nsMatch = true;
- } else {
- var that = this;
- Strophe.forEachChild(elem, null, function(elem) {
- if (elem.getAttribute("xmlns") == that.ns) {
- nsMatch = true;
- }
- });
- nsMatch = nsMatch || elem.getAttribute("xmlns") == this.ns;
- }
- if (nsMatch && (!this.name || Strophe.isTagEqual(elem, this.name)) && (!this.type || elem.getAttribute("type") == this.type) && (!this.id || elem.getAttribute("id") == this.id) && (!this.from || from == this.from)) {
- return true;
- }
- return false;
- },
- /** PrivateFunction: run
- * Run the callback on a matching stanza.
- *
- * Parameters:
- * (XMLElement) elem - The DOM element that triggered the
- * Strophe.Handler.
- *
- * Returns:
- * A boolean indicating if the handler should remain active.
- */
- run: function(elem) {
- var result = null;
- try {
- result = this.handler(elem);
- } catch (e) {
- if (e.sourceURL) {
- Strophe.fatal("error: " + this.handler + " " + e.sourceURL + ":" + e.line + " - " + e.name + ": " + e.message);
- } else if (e.fileName) {
- if (typeof console != "undefined") {
- console.trace();
- console.error(this.handler, " - error - ", e, e.message);
- }
- Strophe.fatal("error: " + this.handler + " " + e.fileName + ":" + e.lineNumber + " - " + e.name + ": " + e.message);
- } else {
- Strophe.fatal("error: " + e.message + "\n" + e.stack);
- }
- throw e;
- }
- return result;
- },
- /** PrivateFunction: toString
- * Get a String representation of the Strophe.Handler object.
- *
- * Returns:
- * A String.
- */
- toString: function() {
- return "{Handler: " + this.handler + "(" + this.name + "," + this.id + "," + this.ns + ")}";
- }
- };
- /** PrivateClass: Strophe.TimedHandler
- * _Private_ helper class for managing timed handlers.
- *
- * A Strophe.TimedHandler encapsulates a user provided callback that
- * should be called after a certain period of time or at regular
- * intervals. The return value of the callback determines whether the
- * Strophe.TimedHandler will continue to fire.
- *
- * Users will not use Strophe.TimedHandler objects directly, but instead
- * they will use Strophe.Connection.addTimedHandler() and
- * Strophe.Connection.deleteTimedHandler().
- */
- /** PrivateConstructor: Strophe.TimedHandler
- * Create and initialize a new Strophe.TimedHandler object.
- *
- * Parameters:
- * (Integer) period - The number of milliseconds to wait before the
- * handler is called.
- * (Function) handler - The callback to run when the handler fires. This
- * function should take no arguments.
- *
- * Returns:
- * A new Strophe.TimedHandler object.
- */
- Strophe.TimedHandler = function(period, handler) {
- this.period = period;
- this.handler = handler;
- this.lastCalled = new Date().getTime();
- this.user = true;
- };
- Strophe.TimedHandler.prototype = {
- /** PrivateFunction: run
- * Run the callback for the Strophe.TimedHandler.
- *
- * Returns:
- * true if the Strophe.TimedHandler should be called again, and false
- * otherwise.
- */
- run: function() {
- this.lastCalled = new Date().getTime();
- return this.handler();
- },
- /** PrivateFunction: reset
- * Reset the last called time for the Strophe.TimedHandler.
- */
- reset: function() {
- this.lastCalled = new Date().getTime();
- },
- /** PrivateFunction: toString
- * Get a string representation of the Strophe.TimedHandler object.
- *
- * Returns:
- * The string representation.
- */
- toString: function() {
- return "{TimedHandler: " + this.handler + "(" + this.period + ")}";
- }
- };
- /** Class: Strophe.Connection
- * XMPP Connection manager.
- *
- * 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, SASL SCRAM-SHA1
- * and legacy authentication.
- *
- * After creating a Strophe.Connection object, the user will typically
- * call connect() with a user supplied callback to handle connection level
- * events like authentication failure, disconnection, or connection
- * complete.
- *
- * The user will also have several event handlers defined by using
- * addHandler() and addTimedHandler(). These will allow the user code to
- * respond to interesting stanzas or do something periodically with the
- * connection. These handlers will be active once authentication is
- * finished.
- *
- * To send data to the connection, use send().
- */
- /** Constructor: Strophe.Connection
- * Create and initialize a Strophe.Connection object.
- *
- * The transport-protocol for this connection will be chosen automatically
- * based on the given service parameter. URLs starting with "ws://" or
- * "wss://" will use WebSockets, URLs starting with "http://", "https://"
- * or without a protocol will use BOSH.
- *
- * To make Strophe connect to the current host you can leave out the protocol
- * and host part and just pass the path, e.g.
- *
- * > var conn = new Strophe.Connection("/http-bind/");
- *
- * WebSocket options:
- *
- * If you want to connect to the current host with a WebSocket connection you
- * can tell Strophe to use WebSockets through a "protocol" attribute in the
- * optional options parameter. Valid values are "ws" for WebSocket and "wss"
- * for Secure WebSocket.
- * So to connect to "wss://CURRENT_HOSTNAME/xmpp-websocket" you would call
- *
- * > var conn = new Strophe.Connection("/xmpp-websocket/", {protocol: "wss"});
- *
- * Note that relative URLs _NOT_ starting with a "/" will also include the path
- * of the current site.
- *
- * Also because downgrading security is not permitted by browsers, when using
- * relative URLs both BOSH and WebSocket connections will use their secure
- * variants if the current connection to the site is also secure (https).
- *
- * BOSH options:
- *
- * by adding "sync" to the options, you can control if requests will
- * be made synchronously or not. The default behaviour is asynchronous.
- * If you want to make requests synchronous, make "sync" evaluate to true:
- * > var conn = new Strophe.Connection("/http-bind/", {sync: true});
- * You can also toggle this on an already established connection:
- * > conn.options.sync = true;
- *
- *
- * Parameters:
- * (String) service - The BOSH or WebSocket service URL.
- * (Object) options - A hash of configuration options
- *
- * Returns:
- * A new Strophe.Connection object.
- */
- Strophe.Connection = function(service, options) {
- // The service URL
- this.service = service;
- // Configuration options
- this.options = options || {};
- var proto = this.options.protocol || "";
- // Select protocal based on service or options
- if (service.indexOf("ws:") === 0 || service.indexOf("wss:") === 0 || proto.indexOf("ws") === 0) {
- this._proto = new Strophe.Websocket(this);
- } else {
- this._proto = new Strophe.Bosh(this);
- }
- /* The connected JID. */
- this.jid = "";
- /* the JIDs domain */
- this.domain = null;
- /* stream:features */
- this.features = null;
- // SASL
- this._sasl_data = {};
- this.do_session = false;
- this.do_bind = false;
- // handler lists
- 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._data = [];
- this._uniqueId = 0;
- this._sasl_success_handler = null;
- 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);
- // initialize plugins
- for (var k in Strophe._connectionPlugins) {
- if (Strophe._connectionPlugins.hasOwnProperty(k)) {
- var ptype = Strophe._connectionPlugins[k];
- // jslint complaints about the below line, but this is fine
- var F = function() {};
- // jshint ignore:line
- F.prototype = ptype;
- this[k] = new F();
- this[k].init(this);
- }
- }
- };
- Strophe.Connection.prototype = {
- /** Function: reset
- * Reset the connection.
- *
- * This function should be called after a connection is disconnected
- * before that connection is reused.
- */
- reset: function() {
- this._proto._reset();
- // SASL
- this.do_session = false;
- this.do_bind = false;
- // handler lists
- 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 = 0;
- },
- /** Function: pause
- * Pause the request manager.
- *
- * This will prevent Strophe from sending any more requests to the
- * server. This is very useful for temporarily pausing
- * BOSH-Connections while a lot of send() calls are happening quickly.
- * This causes Strophe to send the data in a single request, saving
- * many request trips.
- */
- pause: function() {
- this.paused = true;
- },
- /** Function: resume
- * Resume the request manager.
- *
- * This resumes after pause() has been called.
- */
- resume: function() {
- this.paused = false;
- },
- /** Function: getUniqueId
- * Generate a unique ID for use in <iq/> elements.
- *
- * All <iq/> stanzas are required to have unique id attributes. This
- * function makes creating these easy. Each connection instance has
- * a counter which starts from zero, and the value of this counter
- * plus a colon followed by the suffix becomes the unique id. If no
- * suffix is supplied, the counter is used as the unique id.
- *
- * Suffixes are used to make debugging easier when reading the stream
- * data, and their use is recommended. The counter resets to 0 for
- * every new connection for the same reason. For connections to the
- * same server that authenticate the same way, all the ids should be
- * the same, which makes it easy to see changes. This is useful for
- * automated testing as well.
- *
- * Parameters:
- * (String) suffix - A optional suffix to append to the id.
- *
- * Returns:
- * A unique string to be used for the id attribute.
- */
- getUniqueId: function(suffix) {
- if (typeof suffix == "string" || typeof suffix == "number") {
- return ++this._uniqueId + ":" + suffix;
- } else {
- return ++this._uniqueId + "";
- }
- },
- /** Function: connect
- * Starts the connection process.
- *
- * As the connection process proceeds, the user supplied callback will
- * be triggered multiple times with status updates. The callback
- * should take two arguments - the status code and the error condition.
- *
- * The status code will be one of the values in the Strophe.Status
- * constants. The error condition will be one of the conditions
- * defined in RFC 3920 or the condition 'strophe-parsererror'.
- *
- * The Parameters _wait_, _hold_ and _route_ are optional and only relevant
- * for BOSH connections. Please see XEP 124 for a more detailed explanation
- * of the optional parameters.
- *
- * Parameters:
- * (String) jid - The user's JID. This may be a bare JID,
- * or a full JID. If a node is not supplied, SASL ANONYMOUS
- * authentication will be attempted.
- * (String) pass - The user's password.
- * (Function) callback - The connect callback function.
- * (Integer) wait - The optional HTTPBIND wait value. This is the
- * time the server will wait before returning an empty result for
- * a request. The default setting of 60 seconds is recommended.
- * (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 - The optional route value.
- */
- connect: function(jid, pass, callback, wait, hold, route) {
- this.jid = jid;
- /** Variable: authzid
- * Authorization identity.
- */
- this.authzid = Strophe.getBareJidFromJid(this.jid);
- /** Variable: authcid
- * Authentication identity (User name).
- */
- this.authcid = Strophe.getNodeFromJid(this.jid);
- /** Variable: pass
- * Authentication identity (User password).
- */
- this.pass = pass;
- /** Variable: servtype
- * Digest MD5 compatibility.
- */
- this.servtype = "xmpp";
- this.connect_callback = callback;
- this.disconnecting = false;
- this.connected = false;
- this.authenticated = false;
- this.errors = 0;
- // parse jid for domain
- this.domain = Strophe.getDomainFromJid(this.jid);
- this._changeConnectStatus(Strophe.Status.CONNECTING, null);
- this._proto._connect(wait, hold, route);
- },
- /** Function: attach
- * Attach to an already created and authenticated BOSH session.
- *
- * This function is provided to allow Strophe to attach to BOSH
- * sessions which have been created externally, perhaps by a Web
- * application. This is often used to support auto-login type features
- * without putting user credentials into the page.
- *
- * Parameters:
- * (String) jid - The full JID that is bound by the session.
- * (String) sid - The SID of the BOSH session.
- * (String) rid - The current RID of the BOSH session. This RID
- * will be used by the next request.
- * (Function) callback The connect callback function.
- * (Integer) wait - The optional HTTPBIND wait value. This is the
- * time the server will wait before returning an empty result for
- * a request. The default setting of 60 seconds is recommended.
- * Other settings will require tweaks to the Strophe.TIMEOUT value.
- * (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).
- * (Integer) wind - The optional HTTBIND window value. This is the
- * allowed range of request ids that are valid. The default is 5.
- */
- attach: function(jid, sid, rid, callback, wait, hold, wind) {
- this._proto._attach(jid, sid, rid, callback, wait, hold, wind);
- },
- /** Function: xmlInput
- * User overrideable function that receives XML data coming into the
- * connection.
- *
- * The default function does nothing. User code can override this with
- * > Strophe.Connection.xmlInput = function (elem) {
- * > (user code)
- * > };
- *
- * Due to limitations of current Browsers' XML-Parsers the opening and closing
- * <stream> tag for WebSocket-Connoctions will be passed as selfclosing here.
- *
- * BOSH-Connections will have all stanzas wrapped in a <body> tag. See
- * <Strophe.Bosh.strip> if you want to strip this tag.
- *
- * Parameters:
- * (XMLElement) elem - The XML data received by the connection.
- */
- /* jshint unused:false */
- xmlInput: function(elem) {
- return;
- },
- /* jshint unused:true */
- /** Function: xmlOutput
- * User overrideable function that receives XML data sent to the
- * connection.
- *
- * The default function does nothing. User code can override this with
- * > Strophe.Connection.xmlOutput = function (elem) {
- * > (user code)
- * > };
- *
- * Due to limitations of current Browsers' XML-Parsers the opening and closing
- * <stream> tag for WebSocket-Connoctions will be passed as selfclosing here.
- *
- * BOSH-Connections will have all stanzas wrapped in a <body> tag. See
- * <Strophe.Bosh.strip> if you want to strip this tag.
- *
- * Parameters:
- * (XMLElement) elem - The XMLdata sent by the connection.
- */
- /* jshint unused:false */
- xmlOutput: function(elem) {
- return;
- },
- /* jshint unused:true */
- /** Function: rawInput
- * User overrideable function that receives raw data coming into the
- * connection.
- *
- * The default function does nothing. User code can override this with
- * > Strophe.Connection.rawInput = function (data) {
- * > (user code)
- * > };
- *
- * Parameters:
- * (String) data - The data received by the connection.
- */
- /* jshint unused:false */
- rawInput: function(data) {
- return;
- },
- /* jshint unused:true */
- /** Function: rawOutput
- * User overrideable function that receives raw data sent to the
- * connection.
- *
- * The default function does nothing. User code can override this with
- * > Strophe.Connection.rawOutput = function (data) {
- * > (user code)
- * > };
- *
- * Parameters:
- * (String) data - The data sent by the connection.
- */
- /* jshint unused:false */
- rawOutput: function(data) {
- return;
- },
- /* jshint unused:true */
- /** Function: send
- * Send a stanza.
- *
- * This function is called to push data onto the send queue to
- * go out over the wire. Whenever a request is sent to the BOSH
- * server, all pending data is sent and the queue is flushed.
- *
- * Parameters:
- * (XMLElement |
- * [XMLElement] |
- * Strophe.Builder) elem - The stanza to send.
- */
- send: function(elem) {
- if (elem === null) {
- return;
- }
- if (typeof elem.sort === "function") {
- for (var i = 0; i < elem.length; i++) {
- this._queueData(elem[i]);
- }
- } else if (typeof elem.tree === "function") {
- this._queueData(elem.tree());
- } else {
- this._queueData(elem);
- }
- this._proto._send();
- },
- /** Function: flush
- * Immediately send any pending outgoing data.
- *
- * Normally send() queues outgoing data until the next idle period
- * (100ms), which optimizes network use in the common cases when
- * several send()s are called in succession. flush() can be used to
- * immediately send all pending data.
- */
- flush: function() {
- // cancel the pending idle period and run the idle function
- // immediately
- clearTimeout(this._idleTimeout);
- this._onIdle();
- },
- /** Function: sendIQ
- * Helper function to send IQ stanzas.
- *
- * Parameters:
- * (XMLElement) elem - The stanza to send.
- * (Function) callback - The callback function for a successful request.
- * (Function) errback - The callback function for a failed or timed
- * out request. On timeout, the stanza will be null.
- * (Integer) timeout - The time specified in milliseconds for a
- * timeout to occur.
- *
- * Returns:
- * The id used to send the IQ.
- */
- sendIQ: function(elem, callback, errback, timeout) {
- var timeoutHandler = null;
- var that = this;
- if (typeof elem.tree === "function") {
- elem = elem.tree();
- }
- var id = elem.getAttribute("id");
- // inject id if not found
- if (!id) {
- id = this.getUniqueId("sendIQ");
- elem.setAttribute("id", id);
- }
- var handler = this.addHandler(function(stanza) {
- // remove timeout handler if there is one
- if (timeoutHandler) {
- that.deleteTimedHandler(timeoutHandler);
- }
- var iqtype = stanza.getAttribute("type");
- if (iqtype == "result") {
- if (callback) {
- callback(stanza);
- }
- } else if (iqtype == "error") {
- if (errback) {
- errback(stanza);
- }
- } else {
- throw {
- name: "StropheError",
- message: "Got bad IQ type of " + iqtype
- };
- }
- }, null, "iq", null, id);
- // if timeout specified, setup timeout handler.
- if (timeout) {
- timeoutHandler = this.addTimedHandler(timeout, function() {
- // get rid of normal handler
- that.deleteHandler(handler);
- // call errback on timeout with null stanza
- if (errback) {
- errback(null);
- }
- return false;
- });
- }
- this.send(elem);
- return id;
- },
- /** PrivateFunction: _queueData
- * Queue outgoing data for later sending. Also ensures that the data
- * is a DOMElement.
- */
- _queueData: function(element) {
- if (element === null || !element.tagName || !element.childNodes) {
- throw {
- name: "StropheError",
- message: "Cannot queue non-DOMElement."
- };
- }
- this._data.push(element);
- },
- /** PrivateFunction: _sendRestart
- * Send an xmpp:restart stanza.
- */
- _sendRestart: function() {
- this._data.push("restart");
- this._proto._sendRestart();
- this._idleTimeout = setTimeout(this._onIdle.bind(this), 100);
- },
- /** Function: addTimedHandler
- * Add a timed handler to the connection.
- *
- * This function adds a timed handler. The provided handler will
- * be called every period milliseconds until it returns false,
- * the connection is terminated, or the handler is removed. Handlers
- * that wish to continue being invoked should return true.
- *
- * Because of method binding it is necessary to save the result of
- * this function if you wish to remove a handler with
- * deleteTimedHandler().
- *
- * Note that user handlers are not active until authentication is
- * successful.
- *
- * Parameters:
- * (Integer) period - The period of the handler.
- * (Function) handler - The callback function.
- *
- * Returns:
- * A reference to the handler that can be used to remove it.
- */
- addTimedHandler: function(period, handler) {
- var thand = new Strophe.TimedHandler(period, handler);
- this.addTimeds.push(thand);
- return thand;
- },
- /** Function: deleteTimedHandler
- * Delete a timed handler for a connection.
- *
- * This function removes a timed handler from the connection. The
- * handRef parameter is *not* the function passed to addTimedHandler(),
- * but is the reference returned from addTimedHandler().
- *
- * Parameters:
- * (Strophe.TimedHandler) handRef - The handler reference.
- */
- deleteTimedHandler: function(handRef) {
- // this must be done in the Idle loop so that we don't change
- // the handlers during iteration
- this.removeTimeds.push(handRef);
- },
- /** Function: addHandler
- * Add a stanza handler for the connection.
- *
- * This function adds a stanza handler to the connection. The
- * handler callback will be called for any stanza that matches
- * the parameters. Note that if multiple parameters are supplied,
- * they must all match for the handler to be invoked.
- *
- * The handler will receive the stanza that triggered it as its argument.
- * The handler should return true if it is to be invoked again;
- * returning false will remove the handler after it returns.
- *
- * As a convenience, the ns parameters applies to the top level element
- * and also any of its immediate children. This is primarily to make
- * matching /iq/query elements easy.
- *
- * The options argument contains handler matching flags that affect how
- * matches are determined. Currently the only flag is matchBare (a
- * boolean). When matchBare is true, the from parameter and the from
- * attribute on the stanza will be matched as bare JIDs instead of
- * full JIDs. To use this, pass {matchBare: true} as the value of
- * options. The default value for matchBare is false.
- *
- * The return value should be saved if you wish to remove the handler
- * with deleteHandler().
- *
- * Parameters:
- * (Function) handler - The user callback.
- * (String) ns - The namespace to match.
- * (String) name - The stanza name to match.
- * (String) type - The stanza type attribute to match.
- * (String) id - The stanza id attribute to match.
- * (String) from - The stanza from attribute to match.
- * (String) options - The handler options
- *
- * Returns:
- * A reference to the handler that can be used to remove it.
- */
- addHandler: function(handler, ns, name, type, id, from, options) {
- var hand = new Strophe.Handler(handler, ns, name, type, id, from, options);
- this.addHandlers.push(hand);
- return hand;
- },
- /** Function: deleteHandler
- * Delete a stanza handler for a connection.
- *
- * This function removes a stanza handler from the connection. The
- * handRef parameter is *not* the function passed to addHandler(),
- * but is the reference returned from addHandler().
- *
- * Parameters:
- * (Strophe.Handler) handRef - The handler reference.
- */
- deleteHandler: function(handRef) {
- // this must be done in the Idle loop so that we don't change
- // the handlers during iteration
- this.removeHandlers.push(handRef);
- },
- /** Function: disconnect
- * Start the graceful disconnection process.
- *
- * This function starts the disconnection process. This process starts
- * by sending unavailable presence and sending BOSH body of type
- * terminate. A timeout handler makes sure that disconnection happens
- * even if the BOSH server does not respond.
- *
- * The user supplied connection callback will be notified of the
- * progress as this process happens.
- *
- * Parameters:
- * (String) reason - The reason the disconnect is occuring.
- */
- disconnect: function(reason) {
- this._changeConnectStatus(Strophe.Status.DISCONNECTING, reason);
- Strophe.info("Disconnect was called because: " + reason);
- if (this.connected) {
- var pres = false;
- this.disconnecting = true;
- if (this.authenticated) {
- pres = $pres({
- xmlns: Strophe.NS.CLIENT,
- type: "unavailable"
- });
- }
- // setup timeout handler
- this._disconnectTimeout = this._addSysTimedHandler(3e3, this._onDisconnectTimeout.bind(this));
- this._proto._disconnect(pres);
- }
- },
- /** PrivateFunction: _changeConnectStatus
- * _Private_ helper function that makes sure plugins and the user's
- * callback are notified of connection status changes.
- *
- * Parameters:
- * (Integer) status - the new connection status, one of the values
- * in Strophe.Status
- * (String) condition - the error condition or null
- */
- _changeConnectStatus: function(status, condition) {
- // notify all plugins listening for status changes
- for (var k in Strophe._connectionPlugins) {
- if (Strophe._connectionPlugins.hasOwnProperty(k)) {
- var plugin = this[k];
- if (plugin.statusChanged) {
- try {
- plugin.statusChanged(status, condition);
- } catch (err) {
- Strophe.error("" + k + " plugin caused an exception " + "changing status: " + err);
- }
- }
- }
- }
- // notify the user's callback
- if (this.connect_callback) {
- try {
- this.connect_callback(status, condition);
- } catch (e) {
- Strophe.error("User connection callback caused an " + "exception: " + e);
- }
- }
- },
- /** PrivateFunction: _doDisconnect
- * _Private_ function to disconnect.
- *
- * This is the last piece of the disconnection logic. This resets the
- * connection and alerts the user's connection callback.
- */
- _doDisconnect: function() {
- // Cancel Disconnect Timeout
- if (this._disconnectTimeout !== null) {
- this.deleteTimedHandler(this._disconnectTimeout);
- this._disconnectTimeout = null;
- }
- Strophe.info("_doDisconnect was called");
- this._proto._doDisconnect();
- this.authenticated = false;
- this.disconnecting = false;
- // delete handlers
- this.handlers = [];
- this.timedHandlers = [];
- this.removeTimeds = [];
- this.removeHandlers = [];
- this.addTimeds = [];
- this.addHandlers = [];
- // tell the parent we disconnected
- this._changeConnectStatus(Strophe.Status.DISCONNECTED, null);
- this.connected = false;
- },
- /** PrivateFunction: _dataRecv
- * _Private_ handler to processes incoming data from the the connection.
- *
- * Except for _connect_cb handling the initial connection request,
- * this function handles the incoming data for all requests. This
- * function also fires stanza handlers that match each incoming
- * stanza.
- *
- * Parameters:
- * (Strophe.Request) req - The request that has data ready.
- * (string) req - The stanza a raw string (optiona).
- */
- _dataRecv: function(req, raw) {
- Strophe.info("_dataRecv called");
- var elem = this._proto._reqToData(req);
- if (elem === null) {
- return;
- }
- if (this.xmlInput !== Strophe.Connection.prototype.xmlInput) {
- if (elem.nodeName === this._proto.strip && elem.childNodes.length) {
- this.xmlInput(elem.childNodes[0]);
- } else {
- this.xmlInput(elem);
- }
- }
- if (this.rawInput !== Strophe.Connection.prototype.rawInput) {
- if (raw) {
- this.rawInput(raw);
- } else {
- this.rawInput(Strophe.serialize(elem));
- }
- }
- // remove handlers scheduled for deletion
- var i, hand;
- while (this.removeHandlers.length > 0) {
- hand = this.removeHandlers.pop();
- i = this.handlers.indexOf(hand);
- if (i >= 0) {
- this.handlers.splice(i, 1);
- }
- }
- // add handlers scheduled for addition
- while (this.addHandlers.length > 0) {
- this.handlers.push(this.addHandlers.pop());
- }
- // handle graceful disconnect
- if (this.disconnecting && this._proto._emptyQueue()) {
- this._doDisconnect();
- return;
- }
- var typ = elem.getAttribute("type");
- var cond, conflict;
- if (typ !== null && typ == "terminate") {
- // Don't process stanzas that come in after disconnect
- if (this.disconnecting) {
- return;
- }
- // an error occurred
- cond = elem.getAttribute("condition");
- conflict = elem.getElementsByTagName("conflict");
- if (cond !== null) {
- if (cond == "remote-stream-error" && conflict.length > 0) {
- cond = "conflict";
- }
- this._changeConnectStatus(Strophe.Status.CONNFAIL, cond);
- } else {
- this._changeConnectStatus(Strophe.Status.CONNFAIL, "unknown");
- }
- this.disconnect("unknown stream-error");
- return;
- }
- // send each incoming stanza through the handler chain
- var that = this;
- Strophe.forEachChild(elem, null, function(child) {
- var i, newList;
- // process handlers
- newList = that.handlers;
- that.handlers = [];
- for (i = 0; i < newList.length; i++) {
- var hand = newList[i];
- // encapsulate 'handler.run' not to lose the whole handler list if
- // one of the handlers throws an exception
- try {
- if (hand.isMatch(child) && (that.authenticated || !hand.user)) {
- if (hand.run(child)) {
- that.handlers.push(hand);
- }
- } else {
- that.handlers.push(hand);
- }
- } catch (e) {
- // if the handler throws an exception, we consider it as false
- Strophe.warn("Removing Strophe handlers due to uncaught exception: " + e.message);
- }
- }
- });
- },
- /** Attribute: mechanisms
- * SASL Mechanisms available for Conncection.
- */
- mechanisms: {},
- /** PrivateFunction: _connect_cb
- * _Private_ handler for initial connection request.
- *
- * This handler is used to process the initial connection request
- * response from the BOSH server. It is used to set up authentication
- * handlers and start the authentication process.
- *
- * SASL authentication will be attempted if available, otherwise
- * the code will fall back to legacy authentication.
- *
- * 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, _callback, raw) {
- Strophe.info("_connect_cb was called");
- this.connected = true;
- var bodyWrap = this._proto._reqToData(req);
- if (!bodyWrap) {
- return;
- }
- if (this.xmlInput !== Strophe.Connection.prototype.xmlInput) {
- if (bodyWrap.nodeName === this._proto.strip && bodyWrap.childNodes.length) {
- this.xmlInput(bodyWrap.childNodes[0]);
- } else {
- this.xmlInput(bodyWrap);
- }
- }
- if (this.rawInput !== Strophe.Connection.prototype.rawInput) {
- if (raw) {
- this.rawInput(raw);
- } else {
- this.rawInput(Strophe.serialize(bodyWrap));
- }
- }
- var conncheck = this._proto._connect_cb(bodyWrap);
- if (conncheck === Strophe.Status.CONNFAIL) {
- return;
- }
- 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;
- // 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 matched = [];
- var i, mech, found_authentication = false;
- if (!hasFeatures) {
- this._proto._no_auth_received(_callback);
- return;
- }
- if (mechanisms.length > 0) {
- for (i = 0; i < mechanisms.length; i++) {
- mech = Strophe.getText(mechanisms[i]);
- if (this.mechanisms[mech]) matched.push(this.mechanisms[mech]);
- }
- }
- this._authentication.legacy_auth = bodyWrap.getElementsByTagName("auth").length > 0;
- found_authentication = this._authentication.legacy_auth || matched.length > 0;
- if (!found_authentication) {
- this._proto._no_auth_received(_callback);
- return;
- }
- if (this.do_authentication !== false) this.authenticate(matched);
- },
- /** 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(matched) {
- var i;
- // Sorting matched mechanisms according to priority.
- for (i = 0; i < matched.length - 1; ++i) {
- var higher = i;
- for (var j = i + 1; j < matched.length; ++j) {
- if (matched[j].prototype.priority > matched[higher].prototype.priority) {
- higher = j;
- }
- }
- if (higher != i) {
- var swap = matched[i];
- matched[i] = matched[higher];
- matched[higher] = swap;
- }
- }
- // run each mechanism
- var mechanism_found = false;
- for (i = 0; i < matched.length; ++i) {
- if (!matched[i].test(this)) continue;
- 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._sasl_challenge_handler = this._addSysHandler(this._sasl_challenge_cb.bind(this), null, "challenge", null, null);
- this._sasl_mechanism = new matched[i]();
- this._sasl_mechanism.onStart(this);
- var request_auth_exchange = $build("auth", {
- xmlns: Strophe.NS.SASL,
- mechanism: this._sasl_mechanism.name
- });
- if (this._sasl_mechanism.isClientFirst) {
- var response = this._sasl_mechanism.onChallenge(this, null);
- request_auth_exchange.t(Base64.encode(response));
- }
- this.send(request_auth_exchange.tree());
- mechanism_found = true;
- break;
- }
- if (!mechanism_found) {
- // if none of the mechanism worked
- if (Strophe.getNodeFromJid(this.jid) === null) {
- // we don't have a node, which is required for non-anonymous
- // client connections
- this._changeConnectStatus(Strophe.Status.CONNFAIL, "x-strophe-bad-non-anon-jid");
- this.disconnect("x-strophe-bad-non-anon-jid");
- } else {
- // fall back to legacy authentication
- this._changeConnectStatus(Strophe.Status.AUTHENTICATING, null);
- this._addSysHandler(this._auth1_cb.bind(this), null, null, null, "_auth_1");
- this.send($iq({
- type: "get",
- to: this.domain,
- id: "_auth_1"
- }).c("query", {
- xmlns: Strophe.NS.AUTH
- }).c("username", {}).t(Strophe.getNodeFromJid(this.jid)).tree());
- }
- }
- },
- _sasl_challenge_cb: function(elem) {
- var challenge = Base64.decode(Strophe.getText(elem));
- var response = this._sasl_mechanism.onChallenge(this, challenge);
- var stanza = $build("response", {
- xmlns: Strophe.NS.SASL
- });
- if (response !== "") {
- stanza.t(Base64.encode(response));
- }
- this.send(stanza.tree());
- return true;
- },
- /** PrivateFunction: _auth1_cb
- * _Private_ handler for legacy authentication.
- *
- * This handler is called in response to the initial <iq type='get'/>
- * for legacy authentication. It builds an authentication <iq/> and
- * sends it, creating a handler (calling back to _auth2_cb()) to
- * handle the result
- *
- * Parameters:
- * (XMLElement) elem - The stanza that triggered the callback.
- *
- * Returns:
- * false to remove the handler.
- */
- /* jshint unused:false */
- _auth1_cb: function(elem) {
- // build plaintext auth iq
- var iq = $iq({
- type: "set",
- id: "_auth_2"
- }).c("query", {
- xmlns: Strophe.NS.AUTH
- }).c("username", {}).t(Strophe.getNodeFromJid(this.jid)).up().c("password").t(this.pass);
- if (!Strophe.getResourceFromJid(this.jid)) {
- // since the user has not supplied a resource, we pick
- // a default one here. unlike other auth methods, the server
- // cannot do this for us.
- this.jid = Strophe.getBareJidFromJid(this.jid) + "/strophe";
- }
- iq.up().c("resource", {}).t(Strophe.getResourceFromJid(this.jid));
- this._addSysHandler(this._auth2_cb.bind(this), null, null, null, "_auth_2");
- this.send(iq.tree());
- return false;
- },
- /* jshint unused:true */
- /** PrivateFunction: _sasl_success_cb
- * _Private_ handler for succesful SASL authentication.
- *
- * Parameters:
- * (XMLElement) elem - The matching stanza.
- *
- * Returns:
- * false to remove the handler.
- */
- _sasl_success_cb: function(elem) {
- if (this._sasl_data["server-signature"]) {
- var serverSignature;
- var success = Base64.decode(Strophe.getText(elem));
- var attribMatch = /([a-z]+)=([^,]+)(,|$)/;
- var 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.");
- if (this._sasl_mechanism) this._sasl_mechanism.onSuccess();
- // 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._addSysHandler(this._sasl_auth1_cb.bind(this), null, "stream:features", null, null);
- // we must send an xmpp:restart now
- this._sendRestart();
- return false;
- },
- /** PrivateFunction: _sasl_auth1_cb
- * _Private_ handler to start stream binding.
- *
- * Parameters:
- * (XMLElement) elem - The matching stanza.
- *
- * Returns:
- * false to remove the handler.
- */
- _sasl_auth1_cb: function(elem) {
- // save stream:features for future usage
- this.features = elem;
- var i, child;
- for (i = 0; i < elem.childNodes.length; i++) {
- child = elem.childNodes[i];
- if (child.nodeName == "bind") {
- this.do_bind = true;
- }
- if (child.nodeName == "session") {
- this.do_session = true;
- }
- }
- if (!this.do_bind) {
- this._changeConnectStatus(Strophe.Status.AUTHFAIL, null);
- return false;
- } else {
- this._addSysHandler(this._sasl_bind_cb.bind(this), null, null, null, "_bind_auth_2");
- var resource = Strophe.getResourceFromJid(this.jid);
- if (resource) {
- this.send($iq({
- type: "set",
- id: "_bind_auth_2"
- }).c("bind", {
- xmlns: Strophe.NS.BIND
- }).c("resource", {}).t(resource).tree());
- } else {
- this.send($iq({
- type: "set",
- id: "_bind_auth_2"
- }).c("bind", {
- xmlns: Strophe.NS.BIND
- }).tree());
- }
- }
- return false;
- },
- /** PrivateFunction: _sasl_bind_cb
- * _Private_ handler for binding result and session start.
- *
- * Parameters:
- * (XMLElement) elem - The matching stanza.
- *
- * Returns:
- * false to remove the handler.
- */
- _sasl_bind_cb: function(elem) {
- if (elem.getAttribute("type") == "error") {
- Strophe.info("SASL binding failed.");
- var conflict = elem.getElementsByTagName("conflict"), condition;
- if (conflict.length > 0) {
- condition = "conflict";
- }
- this._changeConnectStatus(Strophe.Status.AUTHFAIL, condition);
- return false;
- }
- // TODO - need to grab errors
- var bind = elem.getElementsByTagName("bind");
- var jidNode;
- if (bind.length > 0) {
- // Grab jid
- jidNode = bind[0].getElementsByTagName("jid");
- if (jidNode.length > 0) {
- this.jid = Strophe.getText(jidNode[0]);
- if (this.do_session) {
- this._addSysHandler(this._sasl_session_cb.bind(this), null, null, null, "_session_auth_2");
- this.send($iq({
- type: "set",
- id: "_session_auth_2"
- }).c("session", {
- xmlns: Strophe.NS.SESSION
- }).tree());
- } else {
- this.authenticated = true;
- this._changeConnectStatus(Strophe.Status.CONNECTED, null);
- }
- }
- } else {
- Strophe.info("SASL binding failed.");
- this._changeConnectStatus(Strophe.Status.AUTHFAIL, null);
- return false;
- }
- },
- /** PrivateFunction: _sasl_session_cb
- * _Private_ handler to finish successful SASL connection.
- *
- * This sets Connection.authenticated to true on success, which
- * starts the processing of user handlers.
- *
- * Parameters:
- * (XMLElement) elem - The matching stanza.
- *
- * Returns:
- * false to remove the handler.
- */
- _sasl_session_cb: function(elem) {
- if (elem.getAttribute("type") == "result") {
- this.authenticated = true;
- this._changeConnectStatus(Strophe.Status.CONNECTED, null);
- } else if (elem.getAttribute("type") == "error") {
- Strophe.info("Session creation failed.");
- this._changeConnectStatus(Strophe.Status.AUTHFAIL, null);
- return false;
- }
- return false;
- },
- /** PrivateFunction: _sasl_failure_cb
- * _Private_ handler for SASL authentication failure.
- *
- * Parameters:
- * (XMLElement) elem - The matching stanza.
- *
- * Returns:
- * false to remove the handler.
- */
- /* jshint unused:false */
- _sasl_failure_cb: function(elem) {
- // delete unneeded handlers
- 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;
- }
- if (this._sasl_mechanism) this._sasl_mechanism.onFailure();
- this._changeConnectStatus(Strophe.Status.AUTHFAIL, null);
- return false;
- },
- /* jshint unused:true */
- /** PrivateFunction: _auth2_cb
- * _Private_ handler to finish legacy authentication.
- *
- * This handler is called when the result from the jabber:iq:auth
- * <iq/> stanza is returned.
- *
- * Parameters:
- * (XMLElement) elem - The stanza that triggered the callback.
- *
- * Returns:
- * false to remove the handler.
- */
- _auth2_cb: function(elem) {
- if (elem.getAttribute("type") == "result") {
- this.authenticated = true;
- this._changeConnectStatus(Strophe.Status.CONNECTED, null);
- } else if (elem.getAttribute("type") == "error") {
- this._changeConnectStatus(Strophe.Status.AUTHFAIL, null);
- this.disconnect("authentication failed");
- }
- return false;
- },
- /** PrivateFunction: _addSysTimedHandler
- * _Private_ function to add a system level timed handler.
- *
- * This function is used to add a Strophe.TimedHandler for the
- * library code. System timed handlers are allowed to run before
- * authentication is complete.
- *
- * Parameters:
- * (Integer) period - The period of the handler.
- * (Function) handler - The callback function.
- */
- _addSysTimedHandler: function(period, handler) {
- var thand = new Strophe.TimedHandler(period, handler);
- thand.user = false;
- this.addTimeds.push(thand);
- return thand;
- },
- /** PrivateFunction: _addSysHandler
- * _Private_ function to add a system level stanza handler.
- *
- * This function is used to add a Strophe.Handler for the
- * library code. System stanza handlers are allowed to run before
- * authentication is complete.
- *
- * Parameters:
- * (Function) handler - The callback function.
- * (String) ns - The namespace to match.
- * (String) name - The stanza name to match.
- * (String) type - The stanza type attribute to match.
- * (String) id - The stanza id attribute to match.
- */
- _addSysHandler: function(handler, ns, name, type, id) {
- var hand = new Strophe.Handler(handler, ns, name, type, id);
- hand.user = false;
- this.addHandlers.push(hand);
- return hand;
- },
- /** PrivateFunction: _onDisconnectTimeout
- * _Private_ timeout handler for handling non-graceful disconnection.
- *
- * If the graceful disconnect process does not complete within the
- * time allotted, this handler finishes the disconnect anyway.
- *
- * Returns:
- * false to remove the handler.
- */
- _onDisconnectTimeout: function() {
- Strophe.info("_onDisconnectTimeout was called");
- this._proto._onDisconnectTimeout();
- // actually disconnect
- this._doDisconnect();
- return false;
- },
- /** PrivateFunction: _onIdle
- * _Private_ handler to process events during idle cycle.
- *
- * This handler is called every 100ms to fire timed handlers that
- * are ready and keep poll requests going.
- */
- _onIdle: function() {
- var i, thand, since, newList;
- // add timed handlers scheduled for addition
- // NOTE: we add before remove in the case a timed handler is
- // added and then deleted before the next _onIdle() call.
- while (this.addTimeds.length > 0) {
- this.timedHandlers.push(this.addTimeds.pop());
- }
- // remove timed handlers that have been scheduled for deletion
- while (this.removeTimeds.length > 0) {
- thand = this.removeTimeds.pop();
- i = this.timedHandlers.indexOf(thand);
- if (i >= 0) {
- this.timedHandlers.splice(i, 1);
- }
- }
- // call ready timed handlers
- var now = new Date().getTime();
- newList = [];
- for (i = 0; i < this.timedHandlers.length; i++) {
- thand = this.timedHandlers[i];
- if (this.authenticated || !thand.user) {
- since = thand.lastCalled + thand.period;
- if (since - now <= 0) {
- if (thand.run()) {
- newList.push(thand);
- }
- } else {
- newList.push(thand);
- }
- }
- }
- this.timedHandlers = newList;
- clearTimeout(this._idleTimeout);
- this._proto._onIdle();
- // reactivate the timer only if connected
- if (this.connected) {
- this._idleTimeout = setTimeout(this._onIdle.bind(this), 100);
- }
- }
- };
- if (callback) {
- callback(Strophe, $build, $msg, $iq, $pres);
- }
- /** Class: Strophe.SASLMechanism
- *
- * encapsulates SASL authentication mechanisms.
- *
- * User code may override the priority for each mechanism or disable it completely.
- * See <priority> for information about changing priority and <test> for informatian on
- * how to disable a mechanism.
- *
- * By default, all mechanisms are enabled and the priorities are
- *
- * SCRAM-SHA1 - 40
- * DIGEST-MD5 - 30
- * Plain - 20
- */
- /**
- * PrivateConstructor: Strophe.SASLMechanism
- * SASL auth mechanism abstraction.
- *
- * Parameters:
- * (String) name - SASL Mechanism name.
- * (Boolean) isClientFirst - If client should send response first without challenge.
- * (Number) priority - Priority.
- *
- * Returns:
- * A new Strophe.SASLMechanism object.
- */
- Strophe.SASLMechanism = function(name, isClientFirst, priority) {
- /** PrivateVariable: name
- * Mechanism name.
- */
- this.name = name;
- /** PrivateVariable: isClientFirst
- * If client sends response without initial server challenge.
- */
- this.isClientFirst = isClientFirst;
- /** Variable: priority
- * Determines which <SASLMechanism> is chosen for authentication (Higher is better).
- * Users may override this to prioritize mechanisms differently.
- *
- * In the default configuration the priorities are
- *
- * SCRAM-SHA1 - 40
- * DIGEST-MD5 - 30
- * Plain - 20
- *
- * Example: (This will cause Strophe to choose the mechanism that the server sent first)
- *
- * > Strophe.SASLMD5.priority = Strophe.SASLSHA1.priority;
- *
- * See <SASL mechanisms> for a list of available mechanisms.
- *
- */
- this.priority = priority;
- };
- Strophe.SASLMechanism.prototype = {
- /**
- * Function: test
- * Checks if mechanism able to run.
- * To disable a mechanism, make this return false;
- *
- * To disable plain authentication run
- * > Strophe.SASLPlain.test = function() {
- * > return false;
- * > }
- *
- * See <SASL mechanisms> for a list of available mechanisms.
- *
- * Parameters:
- * (Strophe.Connection) connection - Target Connection.
- *
- * Returns:
- * (Boolean) If mechanism was able to run.
- */
- /* jshint unused:false */
- test: function(connection) {
- return true;
- },
- /* jshint unused:true */
- /** PrivateFunction: onStart
- * Called before starting mechanism on some connection.
- *
- * Parameters:
- * (Strophe.Connection) connection - Target Connection.
- */
- onStart: function(connection) {
- this._connection = connection;
- },
- /** PrivateFunction: onChallenge
- * Called by protocol implementation on incoming challenge. If client is
- * first (isClientFirst == true) challenge will be null on the first call.
- *
- * Parameters:
- * (Strophe.Connection) connection - Target Connection.
- * (String) challenge - current challenge to handle.
- *
- * Returns:
- * (String) Mechanism response.
- */
- /* jshint unused:false */
- onChallenge: function(connection, challenge) {
- throw new Error("You should implement challenge handling!");
- },
- /* jshint unused:true */
- /** PrivateFunction: onFailure
- * Protocol informs mechanism implementation about SASL failure.
- */
- onFailure: function() {
- this._connection = null;
- },
- /** PrivateFunction: onSuccess
- * Protocol informs mechanism implementation about SASL success.
- */
- onSuccess: function() {
- this._connection = null;
- }
- };
- /** Constants: SASL mechanisms
- * Available authentication mechanisms
- *
- * Strophe.SASLAnonymous - SASL Anonymous authentication.
- * Strophe.SASLPlain - SASL Plain authentication.
- * Strophe.SASLMD5 - SASL Digest-MD5 authentication
- * Strophe.SASLSHA1 - SASL SCRAM-SHA1 authentication
- */
- // Building SASL callbacks
- /** PrivateConstructor: SASLAnonymous
- * SASL Anonymous authentication.
- */
- Strophe.SASLAnonymous = function() {};
- Strophe.SASLAnonymous.prototype = new Strophe.SASLMechanism("ANONYMOUS", false, 10);
- Strophe.SASLAnonymous.test = function(connection) {
- return connection.authcid === null;
- };
- Strophe.Connection.prototype.mechanisms[Strophe.SASLAnonymous.prototype.name] = Strophe.SASLAnonymous;
- /** PrivateConstructor: SASLPlain
- * SASL Plain authentication.
- */
- Strophe.SASLPlain = function() {};
- Strophe.SASLPlain.prototype = new Strophe.SASLMechanism("PLAIN", true, 20);
- Strophe.SASLPlain.test = function(connection) {
- return connection.authcid !== null;
- };
- Strophe.SASLPlain.prototype.onChallenge = function(connection) {
- var auth_str = connection.authzid;
- auth_str = auth_str + "\x00";
- auth_str = auth_str + connection.authcid;
- auth_str = auth_str + "\x00";
- auth_str = auth_str + connection.pass;
- return auth_str;
- };
- Strophe.Connection.prototype.mechanisms[Strophe.SASLPlain.prototype.name] = Strophe.SASLPlain;
- /** PrivateConstructor: SASLSHA1
- * SASL SCRAM SHA 1 authentication.
- */
- Strophe.SASLSHA1 = function() {};
- /* TEST:
- * This is a simple example of a SCRAM-SHA-1 authentication exchange
- * when the client doesn't support channel bindings (username 'user' and
- * password 'pencil' are used):
- *
- * C: n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL
- * S: r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,
- * i=4096
- * C: c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,
- * p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=
- * S: v=rmF9pqV8S7suAoZWja4dJRkFsKQ=
- *
- */
- Strophe.SASLSHA1.prototype = new Strophe.SASLMechanism("SCRAM-SHA-1", true, 40);
- Strophe.SASLSHA1.test = function(connection) {
- return connection.authcid !== null;
- };
- Strophe.SASLSHA1.prototype.onChallenge = function(connection, challenge, test_cnonce) {
- var cnonce = test_cnonce || MD5.hexdigest(Math.random() * 1234567890);
- var auth_str = "n=" + connection.authcid;
- auth_str += ",r=";
- auth_str += cnonce;
- connection._sasl_data.cnonce = cnonce;
- connection._sasl_data["client-first-message-bare"] = auth_str;
- auth_str = "n,," + auth_str;
- this.onChallenge = function(connection, challenge) {
- var nonce, salt, iter, Hi, U, U_old, i, k;
- var clientKey, serverKey, clientSignature;
- var responseText = "c=biws,";
- var authMessage = connection._sasl_data["client-first-message-bare"] + "," + challenge + ",";
- var cnonce = connection._sasl_data.cnonce;
- var attribMatch = /([a-z]+)=([^,]+)(,|$)/;
- while (challenge.match(attribMatch)) {
- var 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) {
- connection._sasl_data = {};
- return connection._sasl_failure_cb();
- }
- responseText += "r=" + nonce;
- authMessage += responseText;
- salt = Base64.decode(salt);
- salt += "\x00\x00\x00";
- Hi = U_old = core_hmac_sha1(connection.pass, salt);
- for (i = 1; i < iter; i++) {
- U = core_hmac_sha1(connection.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);
- connection._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));
- return responseText;
- }.bind(this);
- return auth_str;
- };
- Strophe.Connection.prototype.mechanisms[Strophe.SASLSHA1.prototype.name] = Strophe.SASLSHA1;
- /** PrivateConstructor: SASLMD5
- * SASL DIGEST MD5 authentication.
- */
- Strophe.SASLMD5 = function() {};
- Strophe.SASLMD5.prototype = new Strophe.SASLMechanism("DIGEST-MD5", false, 30);
- Strophe.SASLMD5.test = function(connection) {
- return connection.authcid !== null;
- };
- /** PrivateFunction: _quote
- * _Private_ utility function to backslash escape and quote strings.
- *
- * Parameters:
- * (String) str - The string to be quoted.
- *
- * Returns:
- * quoted string
- */
- Strophe.SASLMD5.prototype._quote = function(str) {
- return '"' + str.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"';
- };
- Strophe.SASLMD5.prototype.onChallenge = function(connection, challenge, test_cnonce) {
- var attribMatch = /([a-z]+)=("[^"]+"|[^,"]+)(?:,|$)/;
- var cnonce = test_cnonce || MD5.hexdigest("" + Math.random() * 1234567890);
- var realm = "";
- var host = null;
- var nonce = "";
- var qop = "";
- var matches;
- while (challenge.match(attribMatch)) {
- matches = challenge.match(attribMatch);
- challenge = challenge.replace(matches[0], "");
- matches[2] = matches[2].replace(/^"(.+)"$/, "$1");
- switch (matches[1]) {
- case "realm":
- realm = matches[2];
- break;
-
- case "nonce":
- nonce = matches[2];
- break;
-
- case "qop":
- qop = matches[2];
- break;
-
- case "host":
- host = matches[2];
- break;
- }
- }
- var digest_uri = connection.servtype + "/" + connection.domain;
- if (host !== null) {
- digest_uri = digest_uri + "/" + host;
- }
- var A1 = MD5.hash(connection.authcid + ":" + realm + ":" + this._connection.pass) + ":" + nonce + ":" + cnonce;
- var A2 = "AUTHENTICATE:" + digest_uri;
- var responseText = "";
- responseText += "charset=utf-8,";
- responseText += "username=" + this._quote(connection.authcid) + ",";
- responseText += "realm=" + this._quote(realm) + ",";
- responseText += "nonce=" + this._quote(nonce) + ",";
- responseText += "nc=00000001,";
- responseText += "cnonce=" + this._quote(cnonce) + ",";
- responseText += "digest-uri=" + this._quote(digest_uri) + ",";
- responseText += "response=" + MD5.hexdigest(MD5.hexdigest(A1) + ":" + nonce + ":00000001:" + cnonce + ":auth:" + MD5.hexdigest(A2)) + ",";
- responseText += "qop=auth";
- this.onChallenge = function() {
- return "";
- }.bind(this);
- return responseText;
- };
- Strophe.Connection.prototype.mechanisms[Strophe.SASLMD5.prototype.name] = Strophe.SASLMD5;
-})(function() {
- window.Strophe = arguments[0];
- window.$build = arguments[1];
- window.$msg = arguments[2];
- window.$iq = arguments[3];
- window.$pres = arguments[4];
-});
-
-/*
- This program is distributed under the terms of the MIT license.
- Please see the LICENSE file for details.
-
- Copyright 2006-2008, OGG, LLC
-*/
-/* jshint undef: true, unused: true:, noarg: true, latedef: true */
-/*global window, setTimeout, clearTimeout,
- XMLHttpRequest, ActiveXObject,
- Strophe, $build */
-/** PrivateClass: Strophe.Request
- * _Private_ helper class that provides a cross implementation abstraction
- * for a BOSH related XMLHttpRequest.
- *
- * The Strophe.Request class is used internally to encapsulate BOSH request
- * information. It is not meant to be used from user's code.
- */
-/** PrivateConstructor: Strophe.Request
- * Create and initialize a new Strophe.Request object.
- *
- * Parameters:
- * (XMLElement) elem - The XML data to be sent in the request.
- * (Function) func - The function that will be called when the
- * XMLHttpRequest readyState changes.
- * (Integer) rid - The BOSH rid attribute associated with this request.
- * (Integer) sends - The number of times this same request has been
- * sent.
- */
-Strophe.Request = function(elem, func, rid, sends) {
- this.id = ++Strophe._requestId;
- this.xmlData = elem;
- this.data = Strophe.serialize(elem);
- // save original function in case we need to make a new request
- // from this one.
- this.origFunc = func;
- this.func = func;
- this.rid = rid;
- this.date = NaN;
- this.sends = sends || 0;
- this.abort = false;
- this.dead = null;
- this.age = function() {
- if (!this.date) {
- return 0;
- }
- var now = new Date();
- return (now - this.date) / 1e3;
- };
- this.timeDead = function() {
- if (!this.dead) {
- return 0;
- }
- var now = new Date();
- return (now - this.dead) / 1e3;
- };
- this.xhr = this._newXHR();
-};
-
-Strophe.Request.prototype = {
- /** PrivateFunction: getResponse
- * Get a response from the underlying XMLHttpRequest.
- *
- * This function attempts to get a response from the request and checks
- * for errors.
- *
- * Throws:
- * "parsererror" - A parser error occured.
- *
- * Returns:
- * The DOM element tree of the response.
- */
- getResponse: function() {
- var node = null;
- if (this.xhr.responseXML && this.xhr.responseXML.documentElement) {
- node = this.xhr.responseXML.documentElement;
- if (node.tagName == "parsererror") {
- Strophe.error("invalid response received");
- Strophe.error("responseText: " + this.xhr.responseText);
- Strophe.error("responseXML: " + Strophe.serialize(this.xhr.responseXML));
- throw "parsererror";
- }
- } else if (this.xhr.responseText) {
- Strophe.error("invalid response received");
- Strophe.error("responseText: " + this.xhr.responseText);
- Strophe.error("responseXML: " + Strophe.serialize(this.xhr.responseXML));
- }
- return node;
- },
- /** PrivateFunction: _newXHR
- * _Private_ helper function to create XMLHttpRequests.
- *
- * This function creates XMLHttpRequests across all implementations.
- *
- * Returns:
- * A new XMLHttpRequest.
- */
- _newXHR: function() {
- var xhr = null;
- if (window.XMLHttpRequest) {
- xhr = new XMLHttpRequest();
- if (xhr.overrideMimeType) {
- xhr.overrideMimeType("text/xml");
- }
- } else if (window.ActiveXObject) {
- xhr = new ActiveXObject("Microsoft.XMLHTTP");
- }
- // use Function.bind() to prepend ourselves as an argument
- xhr.onreadystatechange = this.func.bind(null, this);
- return xhr;
- }
-};
-
-/** Class: Strophe.Bosh
- * _Private_ helper class that handles BOSH Connections
- *
- * The Strophe.Bosh class is used internally by Strophe.Connection
- * to encapsulate BOSH sessions. It is not meant to be used from user's code.
- */
-/** File: bosh.js
- * A JavaScript library to enable BOSH in Strophejs.
- *
- * this library uses Bidirectional-streams Over Synchronous HTTP (BOSH)
- * to emulate a persistent, stateful, two-way connection to an XMPP server.
- * More information on BOSH can be found in XEP 124.
- */
-/** PrivateConstructor: Strophe.Bosh
- * Create and initialize a Strophe.Bosh object.
- *
- * Parameters:
- * (Strophe.Connection) connection - The Strophe.Connection that will use BOSH.
- *
- * Returns:
- * A new Strophe.Bosh object.
- */
-Strophe.Bosh = function(connection) {
- this._conn = connection;
- /* request id for body tags */
- this.rid = Math.floor(Math.random() * 4294967295);
- /* The current session ID. */
- this.sid = null;
- // default BOSH values
- this.hold = 1;
- this.wait = 60;
- this.window = 5;
- this._requests = [];
-};
-
-Strophe.Bosh.prototype = {
- /** Variable: strip
- *
- * BOSH-Connections will have all stanzas wrapped in a <body> tag when
- * passed to <Strophe.Connection.xmlInput> or <Strophe.Connection.xmlOutput>.
- * To strip this tag, User code can set <Strophe.Bosh.strip> to "body":
- *
- * > Strophe.Bosh.prototype.strip = "body";
- *
- * This will enable stripping of the body tag in both
- * <Strophe.Connection.xmlInput> and <Strophe.Connection.xmlOutput>.
- */
- strip: null,
- /** PrivateFunction: _buildBody
- * _Private_ helper function to generate the <body/> wrapper for BOSH.
- *
- * Returns:
- * A Strophe.Builder with a <body/> element.
- */
- _buildBody: function() {
- var bodyWrap = $build("body", {
- rid: this.rid++,
- xmlns: Strophe.NS.HTTPBIND
- });
- if (this.sid !== null) {
- bodyWrap.attrs({
- sid: this.sid
- });
- }
- return bodyWrap;
- },
- /** PrivateFunction: _reset
- * Reset the connection.
- *
- * This function is called by the reset function of the Strophe Connection
- */
- _reset: function() {
- this.rid = Math.floor(Math.random() * 4294967295);
- this.sid = null;
- },
- /** PrivateFunction: _connect
- * _Private_ function that initializes the BOSH connection.
- *
- * Creates and sends the Request that initializes the BOSH connection.
- */
- _connect: function(wait, hold, route) {
- this.wait = wait || this.wait;
- this.hold = hold || this.hold;
- // build the body tag
- var body = this._buildBody().attrs({
- to: this._conn.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": Strophe.NS.BOSH
- });
- if (route) {
- body.attrs({
- route: route
- });
- }
- var _connect_cb = this._conn._connect_cb;
- this._requests.push(new Strophe.Request(body.tree(), this._onRequestStateChange.bind(this, _connect_cb.bind(this._conn)), body.tree().getAttribute("rid")));
- this._throttledRequestHandler();
- },
- /** PrivateFunction: _attach
- * Attach to an already created and authenticated BOSH session.
- *
- * This function is provided to allow Strophe to attach to BOSH
- * sessions which have been created externally, perhaps by a Web
- * application. This is often used to support auto-login type features
- * without putting user credentials into the page.
- *
- * Parameters:
- * (String) jid - The full JID that is bound by the session.
- * (String) sid - The SID of the BOSH session.
- * (String) rid - The current RID of the BOSH session. This RID
- * will be used by the next request.
- * (Function) callback The connect callback function.
- * (Integer) wait - The optional HTTPBIND wait value. This is the
- * time the server will wait before returning an empty result for
- * a request. The default setting of 60 seconds is recommended.
- * Other settings will require tweaks to the Strophe.TIMEOUT value.
- * (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).
- * (Integer) wind - The optional HTTBIND window value. This is the
- * allowed range of request ids that are valid. The default is 5.
- */
- _attach: function(jid, sid, rid, callback, wait, hold, wind) {
- this._conn.jid = jid;
- this.sid = sid;
- this.rid = rid;
- this._conn.connect_callback = callback;
- this._conn.domain = Strophe.getDomainFromJid(this._conn.jid);
- this._conn.authenticated = true;
- this._conn.connected = true;
- this.wait = wait || this.wait;
- this.hold = hold || this.hold;
- this.window = wind || this.window;
- this._conn._changeConnectStatus(Strophe.Status.ATTACHED, null);
- },
- /** PrivateFunction: _connect_cb
- * _Private_ handler for initial connection request.
- *
- * This handler is used to process the Bosh-part of the initial request.
- * Parameters:
- * (Strophe.Request) bodyWrap - The received stanza.
- */
- _connect_cb: function(bodyWrap) {
- var typ = bodyWrap.getAttribute("type");
- var cond, conflict;
- if (typ !== null && typ == "terminate") {
- // an error occurred
- Strophe.error("BOSH-Connection failed: " + cond);
- cond = bodyWrap.getAttribute("condition");
- conflict = bodyWrap.getElementsByTagName("conflict");
- if (cond !== null) {
- if (cond == "remote-stream-error" && conflict.length > 0) {
- cond = "conflict";
- }
- this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, cond);
- } else {
- this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, "unknown");
- }
- this._conn._doDisconnect();
- return Strophe.Status.CONNFAIL;
- }
- // check to make sure we don't overwrite these if _connect_cb is
- // called multiple times in the case of missing stream:features
- if (!this.sid) {
- this.sid = bodyWrap.getAttribute("sid");
- }
- var wind = bodyWrap.getAttribute("requests");
- if (wind) {
- this.window = parseInt(wind, 10);
- }
- var hold = bodyWrap.getAttribute("hold");
- if (hold) {
- this.hold = parseInt(hold, 10);
- }
- var wait = bodyWrap.getAttribute("wait");
- if (wait) {
- this.wait = parseInt(wait, 10);
- }
- },
- /** PrivateFunction: _disconnect
- * _Private_ part of Connection.disconnect for Bosh
- *
- * Parameters:
- * (Request) pres - This stanza will be sent before disconnecting.
- */
- _disconnect: function(pres) {
- this._sendTerminate(pres);
- },
- /** PrivateFunction: _doDisconnect
- * _Private_ function to disconnect.
- *
- * Resets the SID and RID.
- */
- _doDisconnect: function() {
- this.sid = null;
- this.rid = Math.floor(Math.random() * 4294967295);
- },
- /** PrivateFunction: _emptyQueue
- * _Private_ function to check if the Request queue is empty.
- *
- * Returns:
- * True, if there are no Requests queued, False otherwise.
- */
- _emptyQueue: function() {
- return this._requests.length === 0;
- },
- /** PrivateFunction: _hitError
- * _Private_ function to handle the error count.
- *
- * Requests are resent automatically until their error count reaches
- * 5. Each time an error is encountered, this function is called to
- * increment the count and disconnect if the count is too high.
- *
- * Parameters:
- * (Integer) reqStatus - The request status.
- */
- _hitError: function(reqStatus) {
- this.errors++;
- Strophe.warn("request errored, status: " + reqStatus + ", number of errors: " + this.errors);
- if (this.errors > 4) {
- this._onDisconnectTimeout();
- }
- },
- /** PrivateFunction: _no_auth_received
- *
- * Called on stream start/restart when no stream:features
- * has been received and sends a blank poll request.
- */
- _no_auth_received: function(_callback) {
- if (_callback) {
- _callback = _callback.bind(this._conn);
- } else {
- _callback = this._conn._connect_cb.bind(this._conn);
- }
- var body = this._buildBody();
- this._requests.push(new Strophe.Request(body.tree(), this._onRequestStateChange.bind(this, _callback.bind(this._conn)), body.tree().getAttribute("rid")));
- this._throttledRequestHandler();
- },
- /** PrivateFunction: _onDisconnectTimeout
- * _Private_ timeout handler for handling non-graceful disconnection.
- *
- * Cancels all remaining Requests and clears the queue.
- */
- _onDisconnectTimeout: function() {
- var req;
- while (this._requests.length > 0) {
- req = this._requests.pop();
- req.abort = true;
- req.xhr.abort();
- // jslint complains, but this is fine. setting to empty func
- // is necessary for IE6
- req.xhr.onreadystatechange = function() {};
- }
- },
- /** PrivateFunction: _onIdle
- * _Private_ handler called by Strophe.Connection._onIdle
- *
- * Sends all queued Requests or polls with empty Request if there are none.
- */
- _onIdle: function() {
- var data = this._conn._data;
- // if no requests are in progress, poll
- if (this._conn.authenticated && this._requests.length === 0 && data.length === 0 && !this._conn.disconnecting) {
- Strophe.info("no requests during idle cycle, sending " + "blank request");
- data.push(null);
- }
- if (this._requests.length < 2 && data.length > 0 && !this._conn.paused) {
- var body = this._buildBody();
- for (var i = 0; i < data.length; i++) {
- if (data[i] !== null) {
- if (data[i] === "restart") {
- body.attrs({
- to: this._conn.domain,
- "xml:lang": "en",
- "xmpp:restart": "true",
- "xmlns:xmpp": Strophe.NS.BOSH
- });
- } else {
- body.cnode(data[i]).up();
- }
- }
- }
- delete this._conn._data;
- this._conn._data = [];
- this._requests.push(new Strophe.Request(body.tree(), this._onRequestStateChange.bind(this, this._conn._dataRecv.bind(this._conn)), body.tree().getAttribute("rid")));
- this._processRequest(this._requests.length - 1);
- }
- if (this._requests.length > 0) {
- var time_elapsed = this._requests[0].age();
- if (this._requests[0].dead !== null) {
- if (this._requests[0].timeDead() > Math.floor(Strophe.SECONDARY_TIMEOUT * this.wait)) {
- this._throttledRequestHandler();
- }
- }
- if (time_elapsed > Math.floor(Strophe.TIMEOUT * this.wait)) {
- Strophe.warn("Request " + this._requests[0].id + " timed out, over " + Math.floor(Strophe.TIMEOUT * this.wait) + " seconds since last activity");
- this._throttledRequestHandler();
- }
- }
- },
- /** PrivateFunction: _onRequestStateChange
- * _Private_ handler for Strophe.Request state changes.
- *
- * This function is called when the XMLHttpRequest readyState changes.
- * It contains a lot of error handling logic for the many ways that
- * requests can fail, and calls the request callback when requests
- * succeed.
- *
- * Parameters:
- * (Function) func - The handler for the request.
- * (Strophe.Request) req - The request that is changing readyState.
- */
- _onRequestStateChange: function(func, req) {
- Strophe.debug("request id " + req.id + "." + req.sends + " state changed to " + req.xhr.readyState);
- if (req.abort) {
- req.abort = false;
- return;
- }
- // request complete
- var reqStatus;
- if (req.xhr.readyState == 4) {
- reqStatus = 0;
- try {
- reqStatus = req.xhr.status;
- } catch (e) {}
- if (typeof reqStatus == "undefined") {
- reqStatus = 0;
- }
- if (this.disconnecting) {
- if (reqStatus >= 400) {
- this._hitError(reqStatus);
- return;
- }
- }
- var reqIs0 = this._requests[0] == req;
- var reqIs1 = this._requests[1] == req;
- if (reqStatus > 0 && reqStatus < 500 || req.sends > 5) {
- // remove from internal queue
- this._removeRequest(req);
- Strophe.debug("request id " + req.id + " should now be removed");
- }
- // request succeeded
- if (reqStatus == 200) {
- // if request 1 finished, or request 0 finished and request
- // 1 is over Strophe.SECONDARY_TIMEOUT seconds old, we need to
- // restart the other - both will be in the first spot, as the
- // completed request has been removed from the queue already
- if (reqIs1 || reqIs0 && this._requests.length > 0 && this._requests[0].age() > Math.floor(Strophe.SECONDARY_TIMEOUT * this.wait)) {
- this._restartRequest(0);
- }
- // call handler
- Strophe.debug("request id " + req.id + "." + req.sends + " got 200");
- func(req);
- this.errors = 0;
- } else {
- Strophe.error("request id " + req.id + "." + req.sends + " error " + reqStatus + " happened");
- if (reqStatus === 0 || reqStatus >= 400 && reqStatus < 600 || reqStatus >= 12e3) {
- this._hitError(reqStatus);
- if (reqStatus >= 400 && reqStatus < 500) {
- this._conn._changeConnectStatus(Strophe.Status.DISCONNECTING, null);
- this._conn._doDisconnect();
- }
- }
- }
- if (!(reqStatus > 0 && reqStatus < 500 || req.sends > 5)) {
- this._throttledRequestHandler();
- }
- }
- },
- /** PrivateFunction: _processRequest
- * _Private_ function to process a request in the queue.
- *
- * This function takes requests off the queue and sends them and
- * restarts dead requests.
- *
- * Parameters:
- * (Integer) i - The index of the request in the queue.
- */
- _processRequest: function(i) {
- var self = this;
- var req = this._requests[i];
- var reqStatus = -1;
- try {
- if (req.xhr.readyState == 4) {
- reqStatus = req.xhr.status;
- }
- } catch (e) {
- Strophe.error("caught an error in _requests[" + i + "], reqStatus: " + reqStatus);
- }
- if (typeof reqStatus == "undefined") {
- reqStatus = -1;
- }
- // make sure we limit the number of retries
- if (req.sends > this.maxRetries) {
- this._onDisconnectTimeout();
- return;
- }
- var time_elapsed = req.age();
- var primaryTimeout = !isNaN(time_elapsed) && time_elapsed > Math.floor(Strophe.TIMEOUT * this.wait);
- var secondaryTimeout = req.dead !== null && req.timeDead() > Math.floor(Strophe.SECONDARY_TIMEOUT * this.wait);
- var requestCompletedWithServerError = req.xhr.readyState == 4 && (reqStatus < 1 || reqStatus >= 500);
- if (primaryTimeout || secondaryTimeout || requestCompletedWithServerError) {
- if (secondaryTimeout) {
- Strophe.error("Request " + this._requests[i].id + " timed out (secondary), restarting");
- }
- req.abort = true;
- req.xhr.abort();
- // setting to null fails on IE6, so set to empty function
- req.xhr.onreadystatechange = function() {};
- this._requests[i] = new Strophe.Request(req.xmlData, req.origFunc, req.rid, req.sends);
- req = this._requests[i];
- }
- if (req.xhr.readyState === 0) {
- Strophe.debug("request id " + req.id + "." + req.sends + " posting");
- try {
- req.xhr.open("POST", this._conn.service, this._conn.options.sync ? false : true);
- } catch (e2) {
- Strophe.error("XHR open failed.");
- if (!this._conn.connected) {
- this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, "bad-service");
- }
- this._conn.disconnect();
- return;
- }
- // Fires the XHR request -- may be invoked immediately
- // or on a gradually expanding retry window for reconnects
- var sendFunc = function() {
- req.date = new Date();
- if (self._conn.options.customHeaders) {
- var headers = self._conn.options.customHeaders;
- for (var header in headers) {
- if (headers.hasOwnProperty(header)) {
- req.xhr.setRequestHeader(header, headers[header]);
- }
- }
- }
- req.xhr.send(req.data);
- };
- // Implement progressive backoff for reconnects --
- // First retry (send == 1) should also be instantaneous
- if (req.sends > 1) {
- // Using a cube of the retry number creates a nicely
- // expanding retry window
- var backoff = Math.min(Math.floor(Strophe.TIMEOUT * this.wait), Math.pow(req.sends, 3)) * 1e3;
- setTimeout(sendFunc, backoff);
- } else {
- sendFunc();
- }
- req.sends++;
- if (this._conn.xmlOutput !== Strophe.Connection.prototype.xmlOutput) {
- if (req.xmlData.nodeName === this.strip && req.xmlData.childNodes.length) {
- this._conn.xmlOutput(req.xmlData.childNodes[0]);
- } else {
- this._conn.xmlOutput(req.xmlData);
- }
- }
- if (this._conn.rawOutput !== Strophe.Connection.prototype.rawOutput) {
- this._conn.rawOutput(req.data);
- }
- } else {
- Strophe.debug("_processRequest: " + (i === 0 ? "first" : "second") + " request has readyState of " + req.xhr.readyState);
- }
- },
- /** PrivateFunction: _removeRequest
- * _Private_ function to remove a request from the queue.
- *
- * Parameters:
- * (Strophe.Request) req - The request to remove.
- */
- _removeRequest: function(req) {
- Strophe.debug("removing request");
- var i;
- for (i = this._requests.length - 1; i >= 0; i--) {
- if (req == this._requests[i]) {
- this._requests.splice(i, 1);
- }
- }
- // IE6 fails on setting to null, so set to empty function
- req.xhr.onreadystatechange = function() {};
- this._throttledRequestHandler();
- },
- /** PrivateFunction: _restartRequest
- * _Private_ function to restart a request that is presumed dead.
- *
- * Parameters:
- * (Integer) i - The index of the request in the queue.
- */
- _restartRequest: function(i) {
- var req = this._requests[i];
- if (req.dead === null) {
- req.dead = new Date();
- }
- this._processRequest(i);
- },
- /** PrivateFunction: _reqToData
- * _Private_ function to get a stanza out of a request.
- *
- * Tries to extract a stanza out of a Request Object.
- * When this fails the current connection will be disconnected.
- *
- * Parameters:
- * (Object) req - The Request.
- *
- * Returns:
- * The stanza that was passed.
- */
- _reqToData: function(req) {
- try {
- return req.getResponse();
- } catch (e) {
- if (e != "parsererror") {
- throw e;
- }
- this._conn.disconnect("strophe-parsererror");
- }
- },
- /** PrivateFunction: _sendTerminate
- * _Private_ function to send initial disconnect sequence.
- *
- * This is the first step in a graceful disconnect. It sends
- * the BOSH server a terminate body and includes an unavailable
- * presence if authentication has completed.
- */
- _sendTerminate: function(pres) {
- Strophe.info("_sendTerminate was called");
- var body = this._buildBody().attrs({
- type: "terminate"
- });
- if (pres) {
- body.cnode(pres.tree());
- }
- var req = new Strophe.Request(body.tree(), this._onRequestStateChange.bind(this, this._conn._dataRecv.bind(this._conn)), body.tree().getAttribute("rid"));
- this._requests.push(req);
- this._throttledRequestHandler();
- },
- /** PrivateFunction: _send
- * _Private_ part of the Connection.send function for BOSH
- *
- * Just triggers the RequestHandler to send the messages that are in the queue
- */
- _send: function() {
- clearTimeout(this._conn._idleTimeout);
- this._throttledRequestHandler();
- this._conn._idleTimeout = setTimeout(this._conn._onIdle.bind(this._conn), 100);
- },
- /** PrivateFunction: _sendRestart
- *
- * Send an xmpp:restart stanza.
- */
- _sendRestart: function() {
- this._throttledRequestHandler();
- clearTimeout(this._conn._idleTimeout);
- },
- /** PrivateFunction: _throttledRequestHandler
- * _Private_ function to throttle requests to the connection window.
- *
- * This function makes sure we don't send requests so fast that the
- * request ids overflow the connection window in the case that one
- * request died.
- */
- _throttledRequestHandler: function() {
- if (!this._requests) {
- Strophe.debug("_throttledRequestHandler called with " + "undefined requests");
- } else {
- Strophe.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);
- }
- }
-};
-
-/*
- This program is distributed under the terms of the MIT license.
- Please see the LICENSE file for details.
-
- Copyright 2006-2008, OGG, LLC
-*/
-/* jshint undef: true, unused: true:, noarg: true, latedef: true */
-/*global document, window, clearTimeout, WebSocket,
- DOMParser, Strophe, $build */
-/** Class: Strophe.WebSocket
- * _Private_ helper class that handles WebSocket Connections
- *
- * The Strophe.WebSocket class is used internally by Strophe.Connection
- * to encapsulate WebSocket sessions. It is not meant to be used from user's code.
- */
-/** File: websocket.js
- * A JavaScript library to enable XMPP over Websocket in Strophejs.
- *
- * This file implements XMPP over WebSockets for Strophejs.
- * If a Connection is established with a Websocket url (ws://...)
- * Strophe will use WebSockets.
- * For more information on XMPP-over WebSocket see this RFC draft:
- * http://tools.ietf.org/html/draft-ietf-xmpp-websocket-00
- *
- * WebSocket support implemented by Andreas Guth (andreas.guth@rwth-aachen.de)
- */
-/** PrivateConstructor: Strophe.Websocket
- * Create and initialize a Strophe.WebSocket object.
- * Currently only sets the connection Object.
- *
- * Parameters:
- * (Strophe.Connection) connection - The Strophe.Connection that will use WebSockets.
- *
- * Returns:
- * A new Strophe.WebSocket object.
- */
-Strophe.Websocket = function(connection) {
- this._conn = connection;
- this.strip = "stream:stream";
- var service = connection.service;
- if (service.indexOf("ws:") !== 0 && service.indexOf("wss:") !== 0) {
- // If the service is not an absolute URL, assume it is a path and put the absolute
- // URL together from options, current URL and the path.
- var new_service = "";
- if (connection.options.protocol === "ws" && window.location.protocol !== "https:") {
- new_service += "ws";
- } else {
- new_service += "wss";
- }
- new_service += "://" + window.location.host;
- if (service.indexOf("/") !== 0) {
- new_service += window.location.pathname + service;
- } else {
- new_service += service;
- }
- connection.service = new_service;
- }
-};
-
-Strophe.Websocket.prototype = {
- /** PrivateFunction: _buildStream
- * _Private_ helper function to generate the <stream> start tag for WebSockets
- *
- * Returns:
- * A Strophe.Builder with a <stream> element.
- */
- _buildStream: function() {
- return $build("stream:stream", {
- to: this._conn.domain,
- xmlns: Strophe.NS.CLIENT,
- "xmlns:stream": Strophe.NS.STREAM,
- version: "1.0"
- });
- },
- /** PrivateFunction: _check_streamerror
- * _Private_ checks a message for stream:error
- *
- * Parameters:
- * (Strophe.Request) bodyWrap - The received stanza.
- * connectstatus - The ConnectStatus that will be set on error.
- * Returns:
- * true if there was a streamerror, false otherwise.
- */
- _check_streamerror: function(bodyWrap, connectstatus) {
- var errors = bodyWrap.getElementsByTagName("stream:error");
- if (errors.length === 0) {
- return false;
- }
- var error = errors[0];
- var condition = "";
- var text = "";
- var ns = "urn:ietf:params:xml:ns:xmpp-streams";
- for (var i = 0; i < error.childNodes.length; i++) {
- var e = error.childNodes[i];
- if (e.getAttribute("xmlns") !== ns) {
- break;
- }
- if (e.nodeName === "text") {
- text = e.textContent;
- } else {
- condition = e.nodeName;
- }
- }
- var errorString = "WebSocket stream error: ";
- if (condition) {
- errorString += condition;
- } else {
- errorString += "unknown";
- }
- if (text) {
- errorString += " - " + condition;
- }
- Strophe.error(errorString);
- // close the connection on stream_error
- this._conn._changeConnectStatus(connectstatus, condition);
- this._conn._doDisconnect();
- return true;
- },
- /** PrivateFunction: _reset
- * Reset the connection.
- *
- * This function is called by the reset function of the Strophe Connection.
- * Is not needed by WebSockets.
- */
- _reset: function() {
- return;
- },
- /** PrivateFunction: _connect
- * _Private_ function called by Strophe.Connection.connect
- *
- * Creates a WebSocket for a connection and assigns Callbacks to it.
- * Does nothing if there already is a WebSocket.
- */
- _connect: function() {
- // Ensure that there is no open WebSocket from a previous Connection.
- this._closeSocket();
- // Create the new WobSocket
- this.socket = new WebSocket(this._conn.service, "xmpp");
- this.socket.onopen = this._onOpen.bind(this);
- this.socket.onerror = this._onError.bind(this);
- this.socket.onclose = this._onClose.bind(this);
- this.socket.onmessage = this._connect_cb_wrapper.bind(this);
- },
- /** PrivateFunction: _connect_cb
- * _Private_ function called by Strophe.Connection._connect_cb
- *
- * checks for stream:error
- *
- * Parameters:
- * (Strophe.Request) bodyWrap - The received stanza.
- */
- _connect_cb: function(bodyWrap) {
- var error = this._check_streamerror(bodyWrap, Strophe.Status.CONNFAIL);
- if (error) {
- return Strophe.Status.CONNFAIL;
- }
- },
- /** PrivateFunction: _handleStreamStart
- * _Private_ function that checks the opening stream:stream tag for errors.
- *
- * Disconnects if there is an error and returns false, true otherwise.
- *
- * Parameters:
- * (Node) message - Stanza containing the stream:stream.
- */
- _handleStreamStart: function(message) {
- var error = false;
- // Check for errors in the stream:stream tag
- var ns = message.getAttribute("xmlns");
- if (typeof ns !== "string") {
- error = "Missing xmlns in stream:stream";
- } else if (ns !== Strophe.NS.CLIENT) {
- error = "Wrong xmlns in stream:stream: " + ns;
- }
- var ns_stream = message.namespaceURI;
- if (typeof ns_stream !== "string") {
- error = "Missing xmlns:stream in stream:stream";
- } else if (ns_stream !== Strophe.NS.STREAM) {
- error = "Wrong xmlns:stream in stream:stream: " + ns_stream;
- }
- var ver = message.getAttribute("version");
- if (typeof ver !== "string") {
- error = "Missing version in stream:stream";
- } else if (ver !== "1.0") {
- error = "Wrong version in stream:stream: " + ver;
- }
- if (error) {
- this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, error);
- this._conn._doDisconnect();
- return false;
- }
- return true;
- },
- /** PrivateFunction: _connect_cb_wrapper
- * _Private_ function that handles the first connection messages.
- *
- * On receiving an opening stream tag this callback replaces itself with the real
- * message handler. On receiving a stream error the connection is terminated.
- */
- _connect_cb_wrapper: function(message) {
- if (message.data.indexOf("<stream:stream ") === 0 || message.data.indexOf("<?xml") === 0) {
- // Strip the XML Declaration, if there is one
- var data = message.data.replace(/^(<\?.*?\?>\s*)*/, "");
- if (data === "") return;
- //Make the initial stream:stream selfclosing to parse it without a SAX parser.
- data = message.data.replace(/<stream:stream (.*[^\/])>/, "<stream:stream $1/>");
- var streamStart = new DOMParser().parseFromString(data, "text/xml").documentElement;
- this._conn.xmlInput(streamStart);
- this._conn.rawInput(message.data);
- //_handleStreamSteart will check for XML errors and disconnect on error
- if (this._handleStreamStart(streamStart)) {
- //_connect_cb will check for stream:error and disconnect on error
- this._connect_cb(streamStart);
- // ensure received stream:stream is NOT selfclosing and save it for following messages
- this.streamStart = message.data.replace(/^<stream:(.*)\/>$/, "<stream:$1>");
- }
- } else if (message.data === "</stream:stream>") {
- this._conn.rawInput(message.data);
- this._conn.xmlInput(document.createElement("stream:stream"));
- this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, "Received closing stream");
- this._conn._doDisconnect();
- return;
- } else {
- var string = this._streamWrap(message.data);
- var elem = new DOMParser().parseFromString(string, "text/xml").documentElement;
- this.socket.onmessage = this._onMessage.bind(this);
- this._conn._connect_cb(elem, null, message.data);
- }
- },
- /** PrivateFunction: _disconnect
- * _Private_ function called by Strophe.Connection.disconnect
- *
- * Disconnects and sends a last stanza if one is given
- *
- * Parameters:
- * (Request) pres - This stanza will be sent before disconnecting.
- */
- _disconnect: function(pres) {
- if (this.socket.readyState !== WebSocket.CLOSED) {
- if (pres) {
- this._conn.send(pres);
- }
- var close = "</stream:stream>";
- this._conn.xmlOutput(document.createElement("stream:stream"));
- this._conn.rawOutput(close);
- try {
- this.socket.send(close);
- } catch (e) {
- Strophe.info("Couldn't send closing stream tag.");
- }
- }
- this._conn._doDisconnect();
- },
- /** PrivateFunction: _doDisconnect
- * _Private_ function to disconnect.
- *
- * Just closes the Socket for WebSockets
- */
- _doDisconnect: function() {
- Strophe.info("WebSockets _doDisconnect was called");
- this._closeSocket();
- },
- /** PrivateFunction _streamWrap
- * _Private_ helper function to wrap a stanza in a <stream> tag.
- * This is used so Strophe can process stanzas from WebSockets like BOSH
- */
- _streamWrap: function(stanza) {
- return this.streamStart + stanza + "</stream:stream>";
- },
- /** PrivateFunction: _closeSocket
- * _Private_ function to close the WebSocket.
- *
- * Closes the socket if it is still open and deletes it
- */
- _closeSocket: function() {
- if (this.socket) {
- try {
- this.socket.close();
- } catch (e) {}
- }
- this.socket = null;
- },
- /** PrivateFunction: _emptyQueue
- * _Private_ function to check if the message queue is empty.
- *
- * Returns:
- * True, because WebSocket messages are send immediately after queueing.
- */
- _emptyQueue: function() {
- return true;
- },
- /** PrivateFunction: _onClose
- * _Private_ function to handle websockets closing.
- *
- * Nothing to do here for WebSockets
- */
- _onClose: function() {
- if (this._conn.connected && !this._conn.disconnecting) {
- Strophe.error("Websocket closed unexcectedly");
- this._conn._doDisconnect();
- } else {
- Strophe.info("Websocket closed");
- }
- },
- /** PrivateFunction: _no_auth_received
- *
- * Called on stream start/restart when no stream:features
- * has been received.
- */
- _no_auth_received: function(_callback) {
- Strophe.error("Server did not send any auth methods");
- this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, "Server did not send any auth methods");
- if (_callback) {
- _callback = _callback.bind(this._conn);
- _callback();
- }
- this._conn._doDisconnect();
- },
- /** PrivateFunction: _onDisconnectTimeout
- * _Private_ timeout handler for handling non-graceful disconnection.
- *
- * This does nothing for WebSockets
- */
- _onDisconnectTimeout: function() {},
- /** PrivateFunction: _onError
- * _Private_ function to handle websockets errors.
- *
- * Parameters:
- * (Object) error - The websocket error.
- */
- _onError: function(error) {
- Strophe.error("Websocket error " + error);
- this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, "The WebSocket connection could not be established was disconnected.");
- this._disconnect();
- },
- /** PrivateFunction: _onIdle
- * _Private_ function called by Strophe.Connection._onIdle
- *
- * sends all queued stanzas
- */
- _onIdle: function() {
- var data = this._conn._data;
- if (data.length > 0 && !this._conn.paused) {
- for (var i = 0; i < data.length; i++) {
- if (data[i] !== null) {
- var stanza, rawStanza;
- if (data[i] === "restart") {
- stanza = this._buildStream();
- rawStanza = this._removeClosingTag(stanza);
- stanza = stanza.tree();
- } else {
- stanza = data[i];
- rawStanza = Strophe.serialize(stanza);
- }
- this._conn.xmlOutput(stanza);
- this._conn.rawOutput(rawStanza);
- this.socket.send(rawStanza);
- }
- }
- this._conn._data = [];
- }
- },
- /** PrivateFunction: _onMessage
- * _Private_ function to handle websockets messages.
- *
- * This function parses each of the messages as if they are full documents. [TODO : We may actually want to use a SAX Push parser].
- *
- * Since all XMPP traffic starts with "<stream:stream version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' id='3697395463' from='SERVER'>"
- * The first stanza will always fail to be parsed...
- * Addtionnaly, the seconds stanza will always be a <stream:features> with the stream NS defined in the previous stanza... so we need to 'force' the inclusion of the NS in this stanza!
- *
- * Parameters:
- * (string) message - The websocket message.
- */
- _onMessage: function(message) {
- var elem, data;
- // check for closing stream
- if (message.data === "</stream:stream>") {
- var close = "</stream:stream>";
- this._conn.rawInput(close);
- this._conn.xmlInput(document.createElement("stream:stream"));
- if (!this._conn.disconnecting) {
- this._conn._doDisconnect();
- }
- return;
- } else if (message.data.search("<stream:stream ") === 0) {
- //Make the initial stream:stream selfclosing to parse it without a SAX parser.
- data = message.data.replace(/<stream:stream (.*[^\/])>/, "<stream:stream $1/>");
- elem = new DOMParser().parseFromString(data, "text/xml").documentElement;
- if (!this._handleStreamStart(elem)) {
- return;
- }
- } else {
- data = this._streamWrap(message.data);
- elem = new DOMParser().parseFromString(data, "text/xml").documentElement;
- }
- if (this._check_streamerror(elem, Strophe.Status.ERROR)) {
- return;
- }
- //handle unavailable presence stanza before disconnecting
- if (this._conn.disconnecting && elem.firstChild.nodeName === "presence" && elem.firstChild.getAttribute("type") === "unavailable") {
- this._conn.xmlInput(elem);
- this._conn.rawInput(Strophe.serialize(elem));
- // if we are already disconnecting we will ignore the unavailable stanza and
- // wait for the </stream:stream> tag before we close the connection
- return;
- }
- this._conn._dataRecv(elem, message.data);
- },
- /** PrivateFunction: _onOpen
- * _Private_ function to handle websockets connection setup.
- *
- * The opening stream tag is sent here.
- */
- _onOpen: function() {
- Strophe.info("Websocket open");
- var start = this._buildStream();
- this._conn.xmlOutput(start.tree());
- var startString = this._removeClosingTag(start);
- this._conn.rawOutput(startString);
- this.socket.send(startString);
- },
- /** PrivateFunction: _removeClosingTag
- * _Private_ function to Make the first <stream:stream> non-selfclosing
- *
- * Parameters:
- * (Object) elem - The <stream:stream> tag.
- *
- * Returns:
- * The stream:stream tag as String
- */
- _removeClosingTag: function(elem) {
- var string = Strophe.serialize(elem);
- string = string.replace(/<(stream:stream .*[^\/])\/>$/, "<$1>");
- return string;
- },
- /** PrivateFunction: _reqToData
- * _Private_ function to get a stanza out of a request.
- *
- * WebSockets don't use requests, so the passed argument is just returned.
- *
- * Parameters:
- * (Object) stanza - The stanza.
- *
- * Returns:
- * The stanza that was passed.
- */
- _reqToData: function(stanza) {
- return stanza;
- },
- /** PrivateFunction: _send
- * _Private_ part of the Connection.send function for WebSocket
- *
- * Just flushes the messages that are in the queue
- */
- _send: function() {
- this._conn.flush();
- },
- /** PrivateFunction: _sendRestart
- *
- * Send an xmpp:restart stanza.
- */
- _sendRestart: function() {
- clearTimeout(this._conn._idleTimeout);
- this._conn._onIdle.bind(this._conn)();
- }
-};
-
-// Generated by CoffeeScript 1.7.1
-/*
- *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>
- */
-(function() {
- var Occupant, RoomConfig, XmppRoom, __bind = function(fn, me) {
- return function() {
- return fn.apply(me, arguments);
- };
- };
- Strophe.addConnectionPlugin("muc", {
- _connection: null,
- rooms: {},
- roomNames: [],
- /*Function
- Initialize the MUC plugin. Sets the correct connection object and
- extends the namesace.
- */
- init: function(conn) {
- 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
- Join a multi-user chat room
- Parameters:
- (String) room - The multi-user chat room to join.
- (String) nick - The nickname to use in the chat room. Optional
- (Function) msg_handler_cb - The function call to handle messages from the
- specified chat room.
- (Function) pres_handler_cb - The function call back to handle presence
- in the chat room.
- (Function) roster_cb - The function call to handle roster info in the chat room
- (String) password - The optional password to use. (password protected
- rooms only)
- (Object) history_attrs - Optional attributes for retrieving history
- (XML DOM Element) extended_presence - Optional XML for extending presence
- */
- join: function(room, nick, msg_handler_cb, pres_handler_cb, roster_cb, password, history_attrs) {
- var msg, room_nick;
- room_nick = this.test_append_nick(room, nick);
- msg = $pres({
- from: this._connection.jid,
- to: room_nick
- }).c("x", {
- xmlns: Strophe.NS.MUC
- });
- if (history_attrs != null) {
- msg = msg.c("history", history_attrs).up;
- }
- if (password != null) {
- msg.cnode(Strophe.xmlElement("password", [], password));
- }
- if (typeof extended_presence !== "undefined" && extended_presence !== null) {
- msg.up.cnode(extended_presence);
- }
- if (this._muc_handler == null) {
- this._muc_handler = this._connection.addHandler(function(_this) {
- return function(stanza) {
- var from, handler, handlers, id, roomname, x, xmlns, xquery, _i, _len;
- from = stanza.getAttribute("from");
- if (!from) {
- return true;
- }
- 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;
- }
- }
- }
- }
- for (id in handlers) {
- handler = handlers[id];
- if (!handler(stanza, room)) {
- delete handlers[id];
- }
- }
- return true;
- };
- }(this));
- }
- if (!this.rooms.hasOwnProperty(room)) {
- this.rooms[room] = new XmppRoom(this, room, nick, password);
- this.roomNames.push(room);
- }
- 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
- 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, exit_msg) {
- var id, presence, presenceid, room_nick;
- id = this.roomNames.indexOf(room);
- delete this.rooms[room];
- if (id >= 0) {
- this.roomNames.splice(id, 1);
- if (this.roomNames.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 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, 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, handler_cb, error_cb) {
- var config, stanza;
- config = $iq({
- to: room,
- type: "get"
- }).c("query", {
- xmlns: Strophe.NS.MUC_OWNER
- });
- stanza = config.tree();
- return this._connection.sendIQ(stanza, handler_cb, error_cb);
- },
- /*Function
- Cancel the room configuration
- Parameters:
- (String) room - The multi-user chat room name.
- Returns:
- id - the unique id used to cancel the configuration.
- */
- cancelConfigure: function(room) {
- 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.
- (Array) config- Form Object or an array of form elements used to configure the room.
- Returns:
- id - the unique id used to save the configuration.
- */
- saveConfiguration: function(room, config, success_cb, error_cb) {
- var conf, iq, stanza, _i, _len;
- iq = $iq({
- to: room,
- type: "set"
- }).c("query", {
- xmlns: Strophe.NS.MUC_OWNER
- });
- if (typeof Form !== "undefined" && config instanceof Form) {
- config.type = "submit";
- iq.cnode(config.toXML());
- } else {
- iq.c("x", {
- xmlns: "jabber:x:data",
- type: "submit"
- });
- for (_i = 0, _len = config.length; _i < _len; _i++) {
- conf = config[_i];
- iq.cnode(conf).up();
- }
- }
- stanza = iq.tree();
- return this._connection.sendIQ(stanza, success_cb, error_cb);
- },
- /*Function
- Parameters:
- (String) room - The multi-user chat room name.
- Returns:
- id - the unique id used to create the chat room.
- */
- createInstantRoom: function(room, success_cb, error_cb) {
- 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(), success_cb, error_cb);
- },
- /*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 = $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) error_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:
- (String) room - The multi-user chat room name.
- (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 - Optional reason for the change.
- (Function) handler_cb - Optional callback for success
- (Function) error_cb - Optional callback for error
- Returns:
- iq - the id of the mode change request.
- */
- 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) error_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 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
- 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.
- (String) error_cb - Function to call on error.
- */
- listRooms: function(server, handle_cb, error_cb) {
- 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, error_cb);
- },
- test_append_nick: function(room, nick) {
- var domain, node;
- node = Strophe.escapeNode(Strophe.getNodeFromJid(room));
- domain = Strophe.getDomainFromJid(room);
- return node + "@" + domain + (nick != null ? "/" + nick : "");
- }
- });
- XmppRoom = function() {
- 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);
- this.roster = {};
- this._message_handlers = {};
- this._presence_handlers = {};
- this._roster_handlers = {};
- this._handler_ids = 0;
- if (client.muc) {
- this.client = client.muc;
- }
- this.name = Strophe.getBareJidFromJid(name);
- this.addHandler("presence", this._roomRosterHandler);
- }
- XmppRoom.prototype.join = function(msg_handler_cb, pres_handler_cb, roster_cb) {
- return this.client.join(this.name, this.nick, msg_handler_cb, pres_handler_cb, roster_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(config) {
- return this.client.saveConfiguration(this.name, config);
- };
- 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, _len1, _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7;
- data = {};
- a = pres.attributes;
- data.nick = Strophe.getResourceFromJid(a.from.textContent);
- data.type = ((_ref = a.type) != null ? _ref.textContent : void 0) || null;
- data.states = [];
- _ref1 = pres.childNodes;
- for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
- c = _ref1[_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 (((_ref2 = a.xmlns) != null ? _ref2.textContent : void 0) === Strophe.NS.MUC_USER) {
- _ref3 = c.childNodes;
- for (_j = 0, _len1 = _ref3.length; _j < _len1; _j++) {
- c2 = _ref3[_j];
- switch (c2.nodeName) {
- case "item":
- a = c2.attributes;
- data.affiliation = ((_ref4 = a.affiliation) != null ? _ref4.textContent : void 0) || null;
- data.role = ((_ref5 = a.role) != null ? _ref5.textContent : void 0) || null;
- data.jid = ((_ref6 = a.jid) != null ? _ref6.textContent : void 0) || null;
- data.newnick = ((_ref7 = a.nick) != null ? _ref7.textContent : void 0) || null;
- break;
-
- case "status":
- if (c2.attributes.code) {
- data.states.push(c2.attributes.code.textContent);
- }
- }
- }
- }
- }
- }
- 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, _len1, _len2, _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, _len1 = attrs.length; _j < _len1; _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, _len2 = _ref.length; _k < _len2; _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);
-
-/*
- 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;
- }
-});
-
-/*
- mustache.js — Logic-less templates in JavaScript
-
- See http://mustache.github.com/ for more info.
-*/
-var Mustache = function() {
- var Renderer = function() {};
- Renderer.prototype = {
- otag: "{{",
- ctag: "}}",
- pragmas: {},
- buffer: [],
- pragmas_implemented: {
- "IMPLICIT-ITERATOR": true
- },
- context: {},
- render: function(template, context, partials, in_recursion) {
- // reset buffer & set context
- if (!in_recursion) {
- this.context = context;
- this.buffer = [];
- }
- // fail fast
- if (!this.includes("", template)) {
- if (in_recursion) {
- return template;
- } else {
- this.send(template);
- return;
- }
- }
- template = this.render_pragmas(template);
- var html = this.render_section(template, context, partials);
- if (in_recursion) {
- return this.render_tags(html, context, partials, in_recursion);
- }
- this.render_tags(html, context, partials, in_recursion);
- },
- /*
- Sends parsed lines
- */
- send: function(line) {
- if (line !== "") {
- this.buffer.push(line);
- }
- },
- /*
- Looks for %PRAGMAS
- */
- render_pragmas: function(template) {
- // no pragmas
- if (!this.includes("%", template)) {
- return template;
- }
- var that = this;
- var regex = new RegExp(this.otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" + this.ctag, "g");
- return template.replace(regex, function(match, pragma, options) {
- if (!that.pragmas_implemented[pragma]) {
- throw {
- message: "This implementation of mustache doesn't understand the '" + pragma + "' pragma"
- };
- }
- that.pragmas[pragma] = {};
- if (options) {
- var opts = options.split("=");
- that.pragmas[pragma][opts[0]] = opts[1];
- }
- return "";
- });
- },
- /*
- Tries to find a partial in the curent scope and render it
- */
- render_partial: function(name, context, partials) {
- name = this.trim(name);
- if (!partials || partials[name] === undefined) {
- throw {
- message: "unknown_partial '" + name + "'"
- };
- }
- if (typeof context[name] != "object") {
- return this.render(partials[name], context, partials, true);
- }
- return this.render(partials[name], context[name], partials, true);
- },
- /*
- Renders inverted (^) and normal (#) sections
- */
- render_section: function(template, context, partials) {
- if (!this.includes("#", template) && !this.includes("^", template)) {
- return template;
- }
- var that = this;
- // CSW - Added "+?" so it finds the tighest bound, not the widest
- var regex = new RegExp(this.otag + "(\\^|\\#)\\s*(.+)\\s*" + this.ctag + "\n*([\\s\\S]+?)" + this.otag + "\\/\\s*\\2\\s*" + this.ctag + "\\s*", "mg");
- // for each {{#foo}}{{/foo}} section do...
- return template.replace(regex, function(match, type, name, content) {
- var value = that.find(name, context);
- if (type == "^") {
- // inverted section
- if (!value || that.is_array(value) && value.length === 0) {
- // false or empty list, render it
- return that.render(content, context, partials, true);
- } else {
- return "";
- }
- } else if (type == "#") {
- // normal section
- if (that.is_array(value)) {
- // Enumerable, Let's loop!
- return that.map(value, function(row) {
- return that.render(content, that.create_context(row), partials, true);
- }).join("");
- } else if (that.is_object(value)) {
- // Object, Use it as subcontext!
- return that.render(content, that.create_context(value), partials, true);
- } else if (typeof value === "function") {
- // higher order section
- return value.call(context, content, function(text) {
- return that.render(text, context, partials, true);
- });
- } else if (value) {
- // boolean section
- return that.render(content, context, partials, true);
- } else {
- return "";
- }
- }
- });
- },
- /*
- Replace {{foo}} and friends with values from our view
- */
- render_tags: function(template, context, partials, in_recursion) {
- // tit for tat
- var that = this;
- var new_regex = function() {
- return new RegExp(that.otag + "(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?" + that.ctag + "+", "g");
- };
- var regex = new_regex();
- var tag_replace_callback = function(match, operator, name) {
- switch (operator) {
- case "!":
- // ignore comments
- return "";
-
- case "=":
- // set new delimiters, rebuild the replace regexp
- that.set_delimiters(name);
- regex = new_regex();
- return "";
-
- case ">":
- // render partial
- return that.render_partial(name, context, partials);
-
- case "{":
- // the triple mustache is unescaped
- return that.find(name, context);
-
- default:
- // escape the value
- return that.escape(that.find(name, context));
- }
- };
- var lines = template.split("\n");
- for (var i = 0; i < lines.length; i++) {
- lines[i] = lines[i].replace(regex, tag_replace_callback, this);
- if (!in_recursion) {
- this.send(lines[i]);
- }
- }
- if (in_recursion) {
- return lines.join("\n");
- }
- },
- set_delimiters: function(delimiters) {
- var dels = delimiters.split(" ");
- this.otag = this.escape_regex(dels[0]);
- this.ctag = this.escape_regex(dels[1]);
- },
- escape_regex: function(text) {
- // thank you Simon Willison
- if (!arguments.callee.sRE) {
- var specials = [ "/", ".", "*", "+", "?", "|", "(", ")", "[", "]", "{", "}", "\\" ];
- arguments.callee.sRE = new RegExp("(\\" + specials.join("|\\") + ")", "g");
- }
- return text.replace(arguments.callee.sRE, "\\$1");
- },
- /*
- find `name` in current `context`. That is find me a value
- from the view object
- */
- find: function(name, context) {
- name = this.trim(name);
- // Checks whether a value is thruthy or false or 0
- function is_kinda_truthy(bool) {
- return bool === false || bool === 0 || bool;
- }
- var value;
- if (is_kinda_truthy(context[name])) {
- value = context[name];
- } else if (is_kinda_truthy(this.context[name])) {
- value = this.context[name];
- }
- if (typeof value === "function") {
- return value.apply(context);
- }
- if (value !== undefined) {
- return value;
- }
- // silently ignore unkown variables
- return "";
- },
- // Utility methods
- /* includes tag */
- includes: function(needle, haystack) {
- return haystack.indexOf(this.otag + needle) != -1;
- },
- /*
- Does away with nasty characters
- */
- escape: function(s) {
- s = String(s === null ? "" : s);
- return s.replace(/&(?!\w+;)|["'<>\\]/g, function(s) {
- switch (s) {
- case "&":
- return "&amp;";
-
- case "\\":
- return "\\\\";
-
- case '"':
- return "&quot;";
-
- case "'":
- return "&#39;";
-
- case "<":
- return "&lt;";
-
- case ">":
- return "&gt;";
-
- default:
- return s;
- }
- });
- },
- // by @langalex, support for arrays of strings
- create_context: function(_context) {
- if (this.is_object(_context)) {
- return _context;
- } else {
- var iterator = ".";
- if (this.pragmas["IMPLICIT-ITERATOR"]) {
- iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator;
- }
- var ctx = {};
- ctx[iterator] = _context;
- return ctx;
- }
- },
- is_object: function(a) {
- return a && typeof a == "object";
- },
- is_array: function(a) {
- return Object.prototype.toString.call(a) === "[object Array]";
- },
- /*
- Gets rid of leading and trailing whitespace
- */
- trim: function(s) {
- return s.replace(/^\s*|\s*$/g, "");
- },
- /*
- Why, why, why? Because IE. Cry, cry cry.
- */
- map: function(array, fn) {
- if (typeof array.map == "function") {
- return array.map(fn);
- } else {
- var r = [];
- var l = array.length;
- for (var i = 0; i < l; i++) {
- r.push(fn(array[i]));
- }
- return r;
- }
- }
- };
- return {
- name: "mustache.js",
- version: "0.3.1-dev",
- /*
- Turns a template and view into HTML
- */
- to_html: function(template, view, partials, send_fun) {
- var renderer = new Renderer();
- if (send_fun) {
- renderer.send = send_fun;
- }
- renderer.render(template, view, partials);
- if (!send_fun) {
- return renderer.buffer.join("\n");
- }
- }
- };
-}();
-
-/*!
- * jQuery i18n plugin
- * @requires jQuery v1.1 or later
- *
- * See https://github.com/recurser/jquery-i18n
- *
- * Licensed under the MIT license.
- *
- * Version: 1.1.1 (Sun, 05 Jan 2014 05:26:50 GMT)
- */
-(function($) {
- /**
- * i18n provides a mechanism for translating strings using a jscript dictionary.
- *
- */
- var __slice = Array.prototype.slice;
- /*
- * i18n property list
- */
- var i18n = {
- dict: null,
- /**
- * load()
- *
- * Load translations.
- *
- * @param property_list i18n_dict : The dictionary to use for translation.
- */
- load: function(i18n_dict) {
- if (this.dict !== null) {
- $.extend(this.dict, i18n_dict);
- } else {
- this.dict = i18n_dict;
- }
- },
- /**
- * _()
- *
- * Looks the given string up in the dictionary and returns the translation if
- * one exists. If a translation is not found, returns the original word.
- *
- * @param string str : The string to translate.
- * @param property_list params.. : params for using printf() on the string.
- *
- * @return string : Translated word.
- */
- _: function(str) {
- dict = this.dict;
- if (dict && dict.hasOwnProperty(str)) {
- str = dict[str];
- }
- args = __slice.call(arguments);
- args[0] = str;
- // Substitute any params.
- return this.printf.apply(this, args);
- },
- /*
- * printf()
- *
- * Substitutes %s with parameters given in list. %%s is used to escape %s.
- *
- * @param string str : String to perform printf on.
- * @param string args : Array of arguments for printf.
- *
- * @return string result : Substituted string
- */
- printf: function(str, args) {
- if (arguments.length < 2) return str;
- args = $.isArray(args) ? args : __slice.call(arguments, 1);
- return str.replace(/([^%]|^)%(?:(\d+)\$)?s/g, function(p0, p, position) {
- if (position) {
- return p + args[parseInt(position) - 1];
- }
- return p + args.shift();
- }).replace(/%%s/g, "%s");
- }
- };
- /*
- * _t()
- *
- * Allows you to translate a jQuery selector.
- *
- * eg $('h1')._t('some text')
- *
- * @param string str : The string to translate .
- * @param property_list params : Params for using printf() on the string.
- *
- * @return element : Chained and translated element(s).
- */
- $.fn._t = function(str, params) {
- return $(this).html(i18n._.apply(i18n, arguments));
- };
- $.i18n = i18n;
-})(jQuery);
-
-/*
- * Date Format 1.2.3
- * (c) 2007-2009 Steven Levithan <stevenlevithan.com>
- * MIT license
- *
- * Includes enhancements by Scott Trenda <scott.trenda.net>
- * and Kris Kowal <cixar.com/~kris.kowal/>
- *
- * Accepts a date, a mask, or a date and a mask.
- * Returns a formatted version of the given date.
- * The date defaults to the current date/time.
- * The mask defaults to dateFormat.masks.default.
- *
- * @link http://blog.stevenlevithan.com/archives/date-time-format
- */
-var dateFormat = function() {
- var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g, timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g, timezoneClip = /[^-+\dA-Z]/g, pad = function(val, len) {
- val = String(val);
- len = len || 2;
- while (val.length < len) val = "0" + val;
- return val;
- };
- // Regexes and supporting functions are cached through closure
- return function(date, mask, utc) {
- var dF = dateFormat;
- // You can't provide utc if you skip other args (use the "UTC:" mask prefix)
- if (arguments.length == 1 && Object.prototype.toString.call(date) == "[object String]" && !/\d/.test(date)) {
- mask = date;
- date = undefined;
- }
- // Passing date through Date applies Date.parse, if necessary
- date = date ? new Date(date) : new Date();
- if (isNaN(date)) throw SyntaxError("invalid date");
- mask = String(dF.masks[mask] || mask || dF.masks["default"]);
- // Allow setting the utc argument via the mask
- if (mask.slice(0, 4) == "UTC:") {
- mask = mask.slice(4);
- utc = true;
- }
- var _ = utc ? "getUTC" : "get", d = date[_ + "Date"](), D = date[_ + "Day"](), m = date[_ + "Month"](), y = date[_ + "FullYear"](), H = date[_ + "Hours"](), M = date[_ + "Minutes"](), s = date[_ + "Seconds"](), L = date[_ + "Milliseconds"](), o = utc ? 0 : date.getTimezoneOffset(), flags = {
- d: d,
- dd: pad(d),
- ddd: dF.i18n.dayNames[D],
- dddd: dF.i18n.dayNames[D + 7],
- m: m + 1,
- mm: pad(m + 1),
- mmm: dF.i18n.monthNames[m],
- mmmm: dF.i18n.monthNames[m + 12],
- yy: String(y).slice(2),
- yyyy: y,
- h: H % 12 || 12,
- hh: pad(H % 12 || 12),
- H: H,
- HH: pad(H),
- M: M,
- MM: pad(M),
- s: s,
- ss: pad(s),
- l: pad(L, 3),
- L: pad(L > 99 ? Math.round(L / 10) : L),
- t: H < 12 ? "a" : "p",
- tt: H < 12 ? "am" : "pm",
- T: H < 12 ? "A" : "P",
- TT: H < 12 ? "AM" : "PM",
- Z: utc ? "UTC" : (String(date).match(timezone) || [ "" ]).pop().replace(timezoneClip, ""),
- o: (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4),
- S: [ "th", "st", "nd", "rd" ][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10]
- };
- return mask.replace(token, function($0) {
- return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1);
- });
- };
-}();
-
-// Some common format strings
-dateFormat.masks = {
- "default": "ddd mmm dd yyyy HH:MM:ss",
- shortDate: "m/d/yy",
- mediumDate: "mmm d, yyyy",
- longDate: "mmmm d, yyyy",
- fullDate: "dddd, mmmm d, yyyy",
- shortTime: "h:MM TT",
- mediumTime: "h:MM:ss TT",
- longTime: "h:MM:ss TT Z",
- isoDate: "yyyy-mm-dd",
- isoTime: "HH:MM:ss",
- isoDateTime: "yyyy-mm-dd'T'HH:MM:ss",
- isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'"
-};
-
-// Internationalization strings
-dateFormat.i18n = {
- dayNames: [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ],
- monthNames: [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ]
-};
-
-// For convenience...
-Date.prototype.format = function(mask, utc) {
- return dateFormat(this, mask, utc);
-};
-//# sourceMappingURL=libs.bundle.map \ No newline at end of file
diff --git a/libs/libs.bundle.map b/libs/libs.bundle.map
deleted file mode 100644
index f125018..0000000
--- a/libs/libs.bundle.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"file":"libs.bundle.js","sources":["strophejs/strophe.js","strophejs-plugins/muc/strophe.muc.js","strophejs-plugins/disco/strophe.disco.js","strophejs-plugins/caps/strophe.caps.jsonly.js","mustache.js/mustache.js","jquery-i18n/jquery.i18n.js","dateformat/dateFormat.js"],"names":["Base64","keyStr","obj","encode","input","output","chr1","chr2","chr3","enc1","enc2","enc3","enc4","i","charCodeAt","isNaN","charAt","length","decode","replace","indexOf","String","fromCharCode","b64_sha1","s","binb2b64","core_sha1","str2binb","str_sha1","binb2str","b64_hmac_sha1","key","data","core_hmac_sha1","str_hmac_sha1","x","len","w","Array","a","b","c","d","e","j","t","olda","oldb","oldc","oldd","olde","rol","safe_add","sha1_ft","sha1_kt","bkey","ipad","opad","hash","concat","y","lsw","msw","num","cnt","str","bin","mask","binarray","tab","triplet","MD5","bit_rol","str2binl","binl2str","binl2hex","hex_tab","md5_cmn","q","md5_ff","md5_gg","md5_hh","md5_ii","core_md5","hexdigest","Function","prototype","bind","func","this","_slice","slice","_concat","_args","call","arguments","apply","elt","from","Number","Math","ceil","floor","callback","Strophe","$build","name","attrs","Builder","$msg","$iq","$pres","VERSION","NS","HTTPBIND","BOSH","CLIENT","AUTH","ROSTER","PROFILE","DISCO_INFO","DISCO_ITEMS","MUC","SASL","STREAM","BIND","SESSION","STANZAS","XHTML_IM","XHTML","tags","attributes","blockquote","br","cite","em","img","li","ol","p","span","strong","ul","body","css","validTag","tag","validAttribute","attribute","validCSS","style","Status","ERROR","CONNECTING","CONNFAIL","AUTHENTICATING","AUTHFAIL","CONNECTED","DISCONNECTED","DISCONNECTING","ATTACHED","LogLevel","DEBUG","INFO","WARN","FATAL","ElementType","NORMAL","TEXT","CDATA","FRAGMENT","TIMEOUT","SECONDARY_TIMEOUT","addNamespace","value","forEachChild","elem","elemName","childNode","childNodes","nodeType","isTagEqual","el","tagName","toLowerCase","_xmlGenerator","_makeGenerator","doc","document","implementation","createDocument","undefined","documentMode","_getIEXmlDom","appendChild","createElement","xmlGenerator","docStrings","ActiveXObject","xmlElement","node","k","xmlTextNode","setAttribute","hasOwnProperty","xmlescape","text","createTextNode","xmlHtmlNode","html","window","DOMParser","parser","parseFromString","async","loadXML","getText","nodeValue","copyElement","nodeName","createHtml","cssAttrs","attr","cssName","cssValue","getAttribute","cssText","split","push","join","createDocumentFragment","escapeNode","unescapeNode","getNodeFromJid","jid","getDomainFromJid","bare","getBareJidFromJid","parts","splice","getResourceFromJid","log","level","msg","debug","info","warn","error","fatal","serialize","result","tree","child","_requestId","_connectionPlugins","addConnectionPlugin","ptype","xmlns","nodeTree","toString","up","parentNode","moreattrs","cnode","impNode","xmlGen","importNode","newElem","h","fragment","innerHTML","xhtml","Handler","handler","ns","type","id","options","matchBare","user","isMatch","nsMatch","that","run","sourceURL","line","message","fileName","console","trace","lineNumber","stack","TimedHandler","period","lastCalled","Date","getTime","reset","Connection","service","proto","protocol","_proto","Websocket","Bosh","domain","features","_sasl_data","do_session","do_bind","timedHandlers","handlers","removeTimeds","removeHandlers","addTimeds","addHandlers","_authentication","_idleTimeout","_disconnectTimeout","do_authentication","authenticated","disconnecting","connected","errors","paused","_data","_uniqueId","_sasl_success_handler","_sasl_failure_handler","_sasl_challenge_handler","maxRetries","setTimeout","_onIdle","F","init","_reset","_requests","pause","resume","getUniqueId","suffix","connect","pass","wait","hold","route","authzid","authcid","servtype","connect_callback","_changeConnectStatus","_connect","attach","sid","rid","wind","_attach","xmlInput","xmlOutput","rawInput","rawOutput","send","_queueData","_send","flush","clearTimeout","sendIQ","errback","timeout","timeoutHandler","addHandler","stanza","deleteTimedHandler","iqtype","addTimedHandler","deleteHandler","element","_sendRestart","thand","handRef","hand","disconnect","reason","pres","_addSysTimedHandler","_onDisconnectTimeout","_disconnect","status","condition","plugin","statusChanged","err","_doDisconnect","_dataRecv","req","raw","_reqToData","strip","pop","_emptyQueue","typ","cond","conflict","getElementsByTagName","newList","mechanisms","_connect_cb","_callback","bodyWrap","conncheck","sasl_scram_sha1","sasl_plain","sasl_digest_md5","sasl_anonymous","legacy_auth","hasFeatures","matched","mech","found_authentication","_no_auth_received","authenticate","higher","priority","swap","mechanism_found","test","_addSysHandler","_sasl_success_cb","_sasl_failure_cb","_sasl_challenge_cb","_sasl_mechanism","onStart","request_auth_exchange","mechanism","isClientFirst","response","onChallenge","_auth1_cb","to","challenge","iq","_auth2_cb","serverSignature","success","attribMatch","matches","match","onSuccess","_sasl_auth1_cb","_sasl_bind_cb","resource","jidNode","_sasl_session_cb","onFailure","since","now","SASLMechanism","connection","_connection","Error","SASLAnonymous","SASLPlain","auth_str","SASLSHA1","test_cnonce","cnonce","random","nonce","salt","iter","Hi","U","U_old","clientKey","serverKey","clientSignature","responseText","authMessage","substr","SASLMD5","_quote","realm","host","qop","digest_uri","A1","A2","Request","sends","xmlData","origFunc","date","NaN","abort","dead","age","timeDead","xhr","_newXHR","getResponse","responseXML","documentElement","XMLHttpRequest","overrideMimeType","onreadystatechange","_conn","_buildBody","xml:lang","content","ver","xmpp:version","xmlns:xmpp","_onRequestStateChange","_throttledRequestHandler","parseInt","_sendTerminate","_hitError","reqStatus","xmpp:restart","_processRequest","time_elapsed","readyState","reqIs0","reqIs1","_removeRequest","_restartRequest","self","primaryTimeout","secondaryTimeout","requestCompletedWithServerError","open","sync","e2","sendFunc","customHeaders","headers","header","setRequestHeader","backoff","min","pow","abs","new_service","location","pathname","_buildStream","xmlns:stream","version","_check_streamerror","connectstatus","textContent","errorString","_closeSocket","socket","WebSocket","onopen","_onOpen","onerror","_onError","onclose","_onClose","onmessage","_connect_cb_wrapper","_handleStreamStart","ns_stream","namespaceURI","streamStart","string","_streamWrap","_onMessage","CLOSED","close","rawStanza","_removeClosingTag","search","firstChild","start","startString","Occupant","RoomConfig","XmppRoom","__bind","fn","me","rooms","roomNames","conn","_muc_handler","room","nick","msg_handler_cb","pres_handler_cb","roster_cb","password","history_attrs","room_nick","test_append_nick","extended_presence","_this","roomname","xquery","_i","_len","_message_handlers","_presence_handlers","leave","handler_cb","exit_msg","presence","presenceid","html_message","msgid","parent","removeChild","groupchat","invite","receiver","invitation","MUC_USER","directInvite","queryOccupants","success_cb","error_cb","configure","config","MUC_OWNER","cancelConfigure","saveConfiguration","conf","Form","toXML","createInstantRoom","roomiq","setTopic","topic","_modifyPrivilege","item","MUC_ADMIN","modifyRole","role","kick","voice","mute","op","deop","modifyAffiliation","affiliation","ban","member","revoke","owner","admin","changeNick","setStatus","show","listRooms","server","handle_cb","client","_roomRosterHandler","_addOccupant","roster","_roster_handlers","_handler_ids","muc","handler_type","removeHandler","occ","newnick","_ref","_parsePresence","update","c2","_j","_len1","_ref1","_ref2","_ref3","_ref4","_ref5","_ref6","_ref7","states","code","parse","field","identity","query","_k","_len2","identities","var","label","_identities","_features","_items","_onDiscoInfo","_onDiscoItems","addIdentity","category","lang","addFeature","var_name","removeFeature","addItem","call_back","items","_buildIQResult","query_attrs","iqresult","HASH","_ver","_knownCapabilities","_jidVerIndex","disco","CAPS","_delegateCapabilities","generateCapsAttrs","generateVer","sort","_sortIdentities","identitiesLen","featuresLen","curIdent","getCapabilitiesByJid","querySelector","_requestCapabilities","_handleDiscoInfoReply","childNodesLen","Mustache","Renderer","otag","ctag","pragmas","buffer","pragmas_implemented","IMPLICIT-ITERATOR","context","render","template","partials","in_recursion","includes","render_pragmas","render_section","render_tags","regex","RegExp","pragma","opts","render_partial","trim","find","is_array","map","row","create_context","is_object","new_regex","tag_replace_callback","operator","set_delimiters","escape","lines","delimiters","dels","escape_regex","callee","sRE","specials","is_kinda_truthy","bool","needle","haystack","_context","iterator","ctx","Object","array","r","l","to_html","view","send_fun","renderer","$","__slice","i18n","dict","load","i18n_dict","extend","_","args","printf","isArray","p0","position","shift","_t","params","jQuery","dateFormat","token","timezone","timezoneClip","pad","val","utc","dF","SyntaxError","masks","D","m","H","M","L","o","getTimezoneOffset","flags","dd","ddd","dayNames","dddd","mm","mmm","monthNames","mmmm","yy","yyyy","hh","HH","MM","ss","round","tt","T","TT","Z","S","$0","default","shortDate","mediumDate","longDate","fullDate","shortTime","mediumTime","longTime","isoDate","isoTime","isoDateTime","isoUtcDateTime","format"],"mappings":"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"} \ No newline at end of file
diff --git a/libs/libs.min.js b/libs/libs.min.js
deleted file mode 100644
index 6c12560..0000000
--- a/libs/libs.min.js
+++ /dev/null
@@ -1,3 +0,0 @@
-function b64_sha1(a){return binb2b64(core_sha1(str2binb(a),8*a.length))}function str_sha1(a){return binb2str(core_sha1(str2binb(a),8*a.length))}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 core_sha1(a,b){a[b>>5]|=128<<24-b%32,a[(b+64>>9<<4)+15]=b;var c,d,e,f,g,h,i,j,k=new Array(80),l=1732584193,m=-271733879,n=-1732584194,o=271733878,p=-1009589776;for(c=0;c<a.length;c+=16){for(f=l,g=m,h=n,i=o,j=p,d=0;80>d;d++)k[d]=16>d?a[c+d]:rol(k[d-3]^k[d-8]^k[d-14]^k[d-16],1),e=safe_add(safe_add(rol(l,5),sha1_ft(d,m,n,o)),safe_add(safe_add(p,k[d]),sha1_kt(d))),p=o,o=n,n=rol(m,30),m=l,l=e;l=safe_add(l,f),m=safe_add(m,g),n=safe_add(n,h),o=safe_add(o,i),p=safe_add(p,j)}return[l,m,n,o,p]}function sha1_ft(a,b,c,d){return 20>a?b&c|~b&d:40>a?b^c^d:60>a?b&c|b&d|c&d:b^c^d}function sha1_kt(a){return 20>a?1518500249:40>a?1859775393:60>a?-1894007588:-899497514}function core_hmac_sha1(a,b){var c=str2binb(a);c.length>16&&(c=core_sha1(c,8*a.length));for(var d=new Array(16),e=new Array(16),f=0;16>f;f++)d[f]=909522486^c[f],e[f]=1549556828^c[f];var g=core_sha1(d.concat(str2binb(b)),512+8*b.length);return core_sha1(e.concat(g),672)}function safe_add(a,b){var c=(65535&a)+(65535&b),d=(a>>16)+(b>>16)+(c>>16);return d<<16|65535&c}function rol(a,b){return a<<b|a>>>32-b}function str2binb(a){for(var b=[],c=255,d=0;d<8*a.length;d+=8)b[d>>5]|=(a.charCodeAt(d/8)&c)<<24-d%32;return b}function binb2str(a){for(var b="",c=255,d=0;d<32*a.length;d+=8)b+=String.fromCharCode(a[d>>5]>>>24-d%32&c);return b}function binb2b64(a){for(var b,c,d="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",e="",f=0;f<4*a.length;f+=3)for(b=(a[f>>2]>>8*(3-f%4)&255)<<16|(a[f+1>>2]>>8*(3-(f+1)%4)&255)<<8|a[f+2>>2]>>8*(3-(f+2)%4)&255,c=0;4>c;c++)e+=8*f+6*c>32*a.length?"=":d.charAt(b>>6*(3-c)&63);return e}var Base64=function(){var a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",b={encode:function(b){var c,d,e,f,g,h,i,j="",k=0;do c=b.charCodeAt(k++),d=b.charCodeAt(k++),e=b.charCodeAt(k++),f=c>>2,g=(3&c)<<4|d>>4,h=(15&d)<<2|e>>6,i=63&e,isNaN(d)?h=i=64:isNaN(e)&&(i=64),j=j+a.charAt(f)+a.charAt(g)+a.charAt(h)+a.charAt(i);while(k<b.length);return j},decode:function(b){var c,d,e,f,g,h,i,j="",k=0;b=b.replace(/[^A-Za-z0-9\+\/\=]/g,"");do f=a.indexOf(b.charAt(k++)),g=a.indexOf(b.charAt(k++)),h=a.indexOf(b.charAt(k++)),i=a.indexOf(b.charAt(k++)),c=f<<2|g>>4,d=(15&g)<<4|h>>2,e=(3&h)<<6|i,j+=String.fromCharCode(c),64!=h&&(j+=String.fromCharCode(d)),64!=i&&(j+=String.fromCharCode(e));while(k<b.length);return j}};return b}(),MD5=function(){var a=function(a,b){var c=(65535&a)+(65535&b),d=(a>>16)+(b>>16)+(c>>16);return d<<16|65535&c},b=function(a,b){return a<<b|a>>>32-b},c=function(a){for(var b=[],c=0;c<8*a.length;c+=8)b[c>>5]|=(255&a.charCodeAt(c/8))<<c%32;return b},d=function(a){for(var b="",c=0;c<32*a.length;c+=8)b+=String.fromCharCode(a[c>>5]>>>c%32&255);return b},e=function(a){for(var b="0123456789abcdef",c="",d=0;d<4*a.length;d++)c+=b.charAt(a[d>>2]>>d%4*8+4&15)+b.charAt(a[d>>2]>>d%4*8&15);return c},f=function(c,d,e,f,g,h){return a(b(a(a(d,c),a(f,h)),g),e)},g=function(a,b,c,d,e,g,h){return f(b&c|~b&d,a,b,e,g,h)},h=function(a,b,c,d,e,g,h){return f(b&d|c&~d,a,b,e,g,h)},i=function(a,b,c,d,e,g,h){return f(b^c^d,a,b,e,g,h)},j=function(a,b,c,d,e,g,h){return f(c^(b|~d),a,b,e,g,h)},k=function(b,c){b[c>>5]|=128<<c%32,b[(c+64>>>9<<4)+14]=c;for(var d,e,f,k,l=1732584193,m=-271733879,n=-1732584194,o=271733878,p=0;p<b.length;p+=16)d=l,e=m,f=n,k=o,l=g(l,m,n,o,b[p+0],7,-680876936),o=g(o,l,m,n,b[p+1],12,-389564586),n=g(n,o,l,m,b[p+2],17,606105819),m=g(m,n,o,l,b[p+3],22,-1044525330),l=g(l,m,n,o,b[p+4],7,-176418897),o=g(o,l,m,n,b[p+5],12,1200080426),n=g(n,o,l,m,b[p+6],17,-1473231341),m=g(m,n,o,l,b[p+7],22,-45705983),l=g(l,m,n,o,b[p+8],7,1770035416),o=g(o,l,m,n,b[p+9],12,-1958414417),n=g(n,o,l,m,b[p+10],17,-42063),m=g(m,n,o,l,b[p+11],22,-1990404162),l=g(l,m,n,o,b[p+12],7,1804603682),o=g(o,l,m,n,b[p+13],12,-40341101),n=g(n,o,l,m,b[p+14],17,-1502002290),m=g(m,n,o,l,b[p+15],22,1236535329),l=h(l,m,n,o,b[p+1],5,-165796510),o=h(o,l,m,n,b[p+6],9,-1069501632),n=h(n,o,l,m,b[p+11],14,643717713),m=h(m,n,o,l,b[p+0],20,-373897302),l=h(l,m,n,o,b[p+5],5,-701558691),o=h(o,l,m,n,b[p+10],9,38016083),n=h(n,o,l,m,b[p+15],14,-660478335),m=h(m,n,o,l,b[p+4],20,-405537848),l=h(l,m,n,o,b[p+9],5,568446438),o=h(o,l,m,n,b[p+14],9,-1019803690),n=h(n,o,l,m,b[p+3],14,-187363961),m=h(m,n,o,l,b[p+8],20,1163531501),l=h(l,m,n,o,b[p+13],5,-1444681467),o=h(o,l,m,n,b[p+2],9,-51403784),n=h(n,o,l,m,b[p+7],14,1735328473),m=h(m,n,o,l,b[p+12],20,-1926607734),l=i(l,m,n,o,b[p+5],4,-378558),o=i(o,l,m,n,b[p+8],11,-2022574463),n=i(n,o,l,m,b[p+11],16,1839030562),m=i(m,n,o,l,b[p+14],23,-35309556),l=i(l,m,n,o,b[p+1],4,-1530992060),o=i(o,l,m,n,b[p+4],11,1272893353),n=i(n,o,l,m,b[p+7],16,-155497632),m=i(m,n,o,l,b[p+10],23,-1094730640),l=i(l,m,n,o,b[p+13],4,681279174),o=i(o,l,m,n,b[p+0],11,-358537222),n=i(n,o,l,m,b[p+3],16,-722521979),m=i(m,n,o,l,b[p+6],23,76029189),l=i(l,m,n,o,b[p+9],4,-640364487),o=i(o,l,m,n,b[p+12],11,-421815835),n=i(n,o,l,m,b[p+15],16,530742520),m=i(m,n,o,l,b[p+2],23,-995338651),l=j(l,m,n,o,b[p+0],6,-198630844),o=j(o,l,m,n,b[p+7],10,1126891415),n=j(n,o,l,m,b[p+14],15,-1416354905),m=j(m,n,o,l,b[p+5],21,-57434055),l=j(l,m,n,o,b[p+12],6,1700485571),o=j(o,l,m,n,b[p+3],10,-1894986606),n=j(n,o,l,m,b[p+10],15,-1051523),m=j(m,n,o,l,b[p+1],21,-2054922799),l=j(l,m,n,o,b[p+8],6,1873313359),o=j(o,l,m,n,b[p+15],10,-30611744),n=j(n,o,l,m,b[p+6],15,-1560198380),m=j(m,n,o,l,b[p+13],21,1309151649),l=j(l,m,n,o,b[p+4],6,-145523070),o=j(o,l,m,n,b[p+11],10,-1120210379),n=j(n,o,l,m,b[p+2],15,718787259),m=j(m,n,o,l,b[p+9],21,-343485551),l=a(l,d),m=a(m,e),n=a(n,f),o=a(o,k);return[l,m,n,o]},l={hexdigest:function(a){return e(k(c(a),8*a.length))},hash:function(a){return d(k(c(a),8*a.length))}};return l}();Function.prototype.bind||(Function.prototype.bind=function(a){var b=this,c=Array.prototype.slice,d=Array.prototype.concat,e=c.call(arguments,1);return function(){return b.apply(a?a:this,d.call(e,c.call(arguments,0)))}}),Array.prototype.indexOf||(Array.prototype.indexOf=function(a){var b=this.length,c=Number(arguments[1])||0;for(c=0>c?Math.ceil(c):Math.floor(c),0>c&&(c+=b);b>c;c++)if(c in this&&this[c]===a)return c;return-1}),function(a){function b(a,b){return new f.Builder(a,b)}function c(a){return new f.Builder("message",a)}function d(a){return new f.Builder("iq",a)}function e(a){return new f.Builder("presence",a)}var f;f={VERSION:"02c798f",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(a){for(var b=0;b<f.XHTML.tags.length;b++)if(a==f.XHTML.tags[b])return!0;return!1},validAttribute:function(a,b){if("undefined"!=typeof f.XHTML.attributes[a]&&f.XHTML.attributes[a].length>0)for(var c=0;c<f.XHTML.attributes[a].length;c++)if(b==f.XHTML.attributes[a][c])return!0;return!1},validCSS:function(a){for(var b=0;b<f.XHTML.css.length;b++)if(a==f.XHTML.css[b])return!0;return!1}},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:.1,addNamespace:function(a,b){f.NS[a]=b},forEachChild:function(a,b,c){var d,e;for(d=0;d<a.childNodes.length;d++)e=a.childNodes[d],e.nodeType!=f.ElementType.NORMAL||b&&!this.isTagEqual(e,b)||c(e)},isTagEqual:function(a,b){return a.tagName.toLowerCase()==b.toLowerCase()},_xmlGenerator:null,_makeGenerator:function(){var a;return void 0===document.implementation.createDocument||document.implementation.createDocument&&document.documentMode&&document.documentMode<10?(a=this._getIEXmlDom(),a.appendChild(a.createElement("strophe"))):a=document.implementation.createDocument("jabber:client","strophe",null),a},xmlGenerator:function(){return f._xmlGenerator||(f._xmlGenerator=f._makeGenerator()),f._xmlGenerator},_getIEXmlDom:function(){for(var a=null,b=["Msxml2.DOMDocument.6.0","Msxml2.DOMDocument.5.0","Msxml2.DOMDocument.4.0","MSXML2.DOMDocument.3.0","MSXML2.DOMDocument","MSXML.DOMDocument","Microsoft.XMLDOM"],c=0;c<b.length&&null===a;c++)try{a=new ActiveXObject(b[c])}catch(d){a=null}return a},xmlElement:function(a){if(!a)return null;var b,c,d,e=f.xmlGenerator().createElement(a);for(b=1;b<arguments.length;b++)if(arguments[b])if("string"==typeof arguments[b]||"number"==typeof arguments[b])e.appendChild(f.xmlTextNode(arguments[b]));else if("object"==typeof arguments[b]&&"function"==typeof arguments[b].sort)for(c=0;c<arguments[b].length;c++)"object"==typeof arguments[b][c]&&"function"==typeof arguments[b][c].sort&&e.setAttribute(arguments[b][c][0],arguments[b][c][1]);else if("object"==typeof arguments[b])for(d in arguments[b])arguments[b].hasOwnProperty(d)&&e.setAttribute(d,arguments[b][d]);return e},xmlescape:function(a){return a=a.replace(/\&/g,"&amp;"),a=a.replace(/</g,"&lt;"),a=a.replace(/>/g,"&gt;"),a=a.replace(/'/g,"&apos;"),a=a.replace(/"/g,"&quot;")},xmlTextNode:function(a){return f.xmlGenerator().createTextNode(a)},xmlHtmlNode:function(a){var b;if(window.DOMParser){var c=new DOMParser;b=c.parseFromString(a,"text/xml")}else b=new ActiveXObject("Microsoft.XMLDOM"),b.async="false",b.loadXML(a);return b},getText:function(a){if(!a)return null;var b="";0===a.childNodes.length&&a.nodeType==f.ElementType.TEXT&&(b+=a.nodeValue);for(var c=0;c<a.childNodes.length;c++)a.childNodes[c].nodeType==f.ElementType.TEXT&&(b+=a.childNodes[c].nodeValue);return f.xmlescape(b)},copyElement:function(a){var b,c;if(a.nodeType==f.ElementType.NORMAL){for(c=f.xmlElement(a.tagName),b=0;b<a.attributes.length;b++)c.setAttribute(a.attributes[b].nodeName.toLowerCase(),a.attributes[b].value);for(b=0;b<a.childNodes.length;b++)c.appendChild(f.copyElement(a.childNodes[b]))}else a.nodeType==f.ElementType.TEXT&&(c=f.xmlGenerator().createTextNode(a.nodeValue));return c},createHtml:function(a){var b,c,d,e,g,h,i,j,k,l,m;if(a.nodeType==f.ElementType.NORMAL)if(e=a.nodeName.toLowerCase(),f.XHTML.validTag(e))try{for(c=f.xmlElement(e),b=0;b<f.XHTML.attributes[e].length;b++)if(g=f.XHTML.attributes[e][b],h=a.getAttribute(g),"undefined"!=typeof h&&null!==h&&""!==h&&h!==!1&&0!==h)if("style"==g&&"object"==typeof h&&"undefined"!=typeof h.cssText&&(h=h.cssText),"style"==g){for(i=[],j=h.split(";"),d=0;d<j.length;d++)k=j[d].split(":"),l=k[0].replace(/^\s*/,"").replace(/\s*$/,"").toLowerCase(),f.XHTML.validCSS(l)&&(m=k[1].replace(/^\s*/,"").replace(/\s*$/,""),i.push(l+": "+m));i.length>0&&(h=i.join("; "),c.setAttribute(g,h))}else c.setAttribute(g,h);for(b=0;b<a.childNodes.length;b++)c.appendChild(f.createHtml(a.childNodes[b]))}catch(n){c=f.xmlTextNode("")}else for(c=f.xmlGenerator().createDocumentFragment(),b=0;b<a.childNodes.length;b++)c.appendChild(f.createHtml(a.childNodes[b]));else if(a.nodeType==f.ElementType.FRAGMENT)for(c=f.xmlGenerator().createDocumentFragment(),b=0;b<a.childNodes.length;b++)c.appendChild(f.createHtml(a.childNodes[b]));else a.nodeType==f.ElementType.TEXT&&(c=f.xmlTextNode(a.nodeValue));return c},escapeNode:function(a){return a.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(a){return a.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(a){return a.indexOf("@")<0?null:a.split("@")[0]},getDomainFromJid:function(a){var b=f.getBareJidFromJid(a);if(b.indexOf("@")<0)return b;var c=b.split("@");return c.splice(0,1),c.join("@")},getResourceFromJid:function(a){var b=a.split("/");return b.length<2?null:(b.splice(0,1),b.join("/"))},getBareJidFromJid:function(a){return a?a.split("/")[0]:null},log:function(){},debug:function(a){this.log(this.LogLevel.DEBUG,a)},info:function(a){this.log(this.LogLevel.INFO,a)},warn:function(a){this.log(this.LogLevel.WARN,a)},error:function(a){this.log(this.LogLevel.ERROR,a)},fatal:function(a){this.log(this.LogLevel.FATAL,a)},serialize:function(a){var b;if(!a)return null;"function"==typeof a.tree&&(a=a.tree());var c,d,e=a.nodeName;for(a.getAttribute("_realname")&&(e=a.getAttribute("_realname")),b="<"+e,c=0;c<a.attributes.length;c++)"_realname"!=a.attributes[c].nodeName&&(b+=" "+a.attributes[c].nodeName.toLowerCase()+"='"+a.attributes[c].value.replace(/&/g,"&amp;").replace(/\'/g,"&apos;").replace(/>/g,"&gt;").replace(/</g,"&lt;")+"'");if(a.childNodes.length>0){for(b+=">",c=0;c<a.childNodes.length;c++)switch(d=a.childNodes[c],d.nodeType){case f.ElementType.NORMAL:b+=f.serialize(d);break;case f.ElementType.TEXT:b+=f.xmlescape(d.nodeValue);break;case f.ElementType.CDATA:b+="<![CDATA["+d.nodeValue+"]]>"}b+="</"+e+">"}else b+="/>";return b},_requestId:0,_connectionPlugins:{},addConnectionPlugin:function(a,b){f._connectionPlugins[a]=b}},f.Builder=function(a,b){("presence"==a||"message"==a||"iq"==a)&&(b&&!b.xmlns?b.xmlns=f.NS.CLIENT:b||(b={xmlns:f.NS.CLIENT})),this.nodeTree=f.xmlElement(a,b),this.node=this.nodeTree},f.Builder.prototype={tree:function(){return this.nodeTree},toString:function(){return f.serialize(this.nodeTree)},up:function(){return this.node=this.node.parentNode,this},attrs:function(a){for(var b in a)a.hasOwnProperty(b)&&this.node.setAttribute(b,a[b]);return this},c:function(a,b,c){var d=f.xmlElement(a,b,c);return this.node.appendChild(d),c||(this.node=d),this},cnode:function(a){var b,c=f.xmlGenerator();try{b=void 0!==c.importNode}catch(d){b=!1}var e=b?c.importNode(a,!0):f.copyElement(a);return this.node.appendChild(e),this.node=e,this},t:function(a){var b=f.xmlTextNode(a);return this.node.appendChild(b),this},h:function(a){var b=document.createElement("body");b.innerHTML=a;for(var c=f.createHtml(b);c.childNodes.length>0;)this.node.appendChild(c.childNodes[0]);return this}},f.Handler=function(a,b,c,d,e,g,h){this.handler=a,this.ns=b,this.name=c,this.type=d,this.id=e,this.options=h||{matchBare:!1},this.options.matchBare||(this.options.matchBare=!1),this.from=this.options.matchBare?g?f.getBareJidFromJid(g):null:g,this.user=!0},f.Handler.prototype={isMatch:function(a){var b,c=null;if(c=this.options.matchBare?f.getBareJidFromJid(a.getAttribute("from")):a.getAttribute("from"),b=!1,this.ns){var d=this;f.forEachChild(a,null,function(a){a.getAttribute("xmlns")==d.ns&&(b=!0)}),b=b||a.getAttribute("xmlns")==this.ns}else b=!0;return!b||this.name&&!f.isTagEqual(a,this.name)||this.type&&a.getAttribute("type")!=this.type||this.id&&a.getAttribute("id")!=this.id||this.from&&c!=this.from?!1:!0},run:function(a){var b=null;try{b=this.handler(a)}catch(c){throw c.sourceURL?f.fatal("error: "+this.handler+" "+c.sourceURL+":"+c.line+" - "+c.name+": "+c.message):c.fileName?("undefined"!=typeof console&&(console.trace(),console.error(this.handler," - error - ",c,c.message)),f.fatal("error: "+this.handler+" "+c.fileName+":"+c.lineNumber+" - "+c.name+": "+c.message)):f.fatal("error: "+c.message+"\n"+c.stack),c}return b},toString:function(){return"{Handler: "+this.handler+"("+this.name+","+this.id+","+this.ns+")}"}},f.TimedHandler=function(a,b){this.period=a,this.handler=b,this.lastCalled=(new Date).getTime(),this.user=!0},f.TimedHandler.prototype={run:function(){return this.lastCalled=(new Date).getTime(),this.handler()},reset:function(){this.lastCalled=(new Date).getTime()},toString:function(){return"{TimedHandler: "+this.handler+"("+this.period+")}"}},f.Connection=function(a,b){this.service=a,this.options=b||{};var c=this.options.protocol||"";this._proto=0===a.indexOf("ws:")||0===a.indexOf("wss:")||0===c.indexOf("ws")?new f.Websocket(this):new f.Bosh(this),this.jid="",this.domain=null,this.features=null,this._sasl_data={},this.do_session=!1,this.do_bind=!1,this.timedHandlers=[],this.handlers=[],this.removeTimeds=[],this.removeHandlers=[],this.addTimeds=[],this.addHandlers=[],this._authentication={},this._idleTimeout=null,this._disconnectTimeout=null,this.do_authentication=!0,this.authenticated=!1,this.disconnecting=!1,this.connected=!1,this.errors=0,this.paused=!1,this._data=[],this._uniqueId=0,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 d in f._connectionPlugins)if(f._connectionPlugins.hasOwnProperty(d)){var e=f._connectionPlugins[d],g=function(){};g.prototype=e,this[d]=new g,this[d].init(this)}},f.Connection.prototype={reset:function(){this._proto._reset(),this.do_session=!1,this.do_bind=!1,this.timedHandlers=[],this.handlers=[],this.removeTimeds=[],this.removeHandlers=[],this.addTimeds=[],this.addHandlers=[],this._authentication={},this.authenticated=!1,this.disconnecting=!1,this.connected=!1,this.errors=0,this._requests=[],this._uniqueId=0},pause:function(){this.paused=!0},resume:function(){this.paused=!1},getUniqueId:function(a){return"string"==typeof a||"number"==typeof a?++this._uniqueId+":"+a:++this._uniqueId+""},connect:function(a,b,c,d,e,g){this.jid=a,this.authzid=f.getBareJidFromJid(this.jid),this.authcid=f.getNodeFromJid(this.jid),this.pass=b,this.servtype="xmpp",this.connect_callback=c,this.disconnecting=!1,this.connected=!1,this.authenticated=!1,this.errors=0,this.domain=f.getDomainFromJid(this.jid),this._changeConnectStatus(f.Status.CONNECTING,null),this._proto._connect(d,e,g)},attach:function(a,b,c,d,e,f,g){this._proto._attach(a,b,c,d,e,f,g)},xmlInput:function(){},xmlOutput:function(){},rawInput:function(){},rawOutput:function(){},send:function(a){if(null!==a){if("function"==typeof a.sort)for(var b=0;b<a.length;b++)this._queueData(a[b]);else this._queueData("function"==typeof a.tree?a.tree():a);this._proto._send()}},flush:function(){clearTimeout(this._idleTimeout),this._onIdle()},sendIQ:function(a,b,c,d){var e=null,f=this;"function"==typeof a.tree&&(a=a.tree());var g=a.getAttribute("id");g||(g=this.getUniqueId("sendIQ"),a.setAttribute("id",g));var h=this.addHandler(function(a){e&&f.deleteTimedHandler(e);var d=a.getAttribute("type");if("result"==d)b&&b(a);else{if("error"!=d)throw{name:"StropheError",message:"Got bad IQ type of "+d};c&&c(a)}},null,"iq",null,g);return d&&(e=this.addTimedHandler(d,function(){return f.deleteHandler(h),c&&c(null),!1})),this.send(a),g},_queueData:function(a){if(null===a||!a.tagName||!a.childNodes)throw{name:"StropheError",message:"Cannot queue non-DOMElement."};this._data.push(a)},_sendRestart:function(){this._data.push("restart"),this._proto._sendRestart(),this._idleTimeout=setTimeout(this._onIdle.bind(this),100)},addTimedHandler:function(a,b){var c=new f.TimedHandler(a,b);return this.addTimeds.push(c),c},deleteTimedHandler:function(a){this.removeTimeds.push(a)},addHandler:function(a,b,c,d,e,g,h){var i=new f.Handler(a,b,c,d,e,g,h);return this.addHandlers.push(i),i},deleteHandler:function(a){this.removeHandlers.push(a)},disconnect:function(a){if(this._changeConnectStatus(f.Status.DISCONNECTING,a),f.info("Disconnect was called because: "+a),this.connected){var b=!1;this.disconnecting=!0,this.authenticated&&(b=e({xmlns:f.NS.CLIENT,type:"unavailable"})),this._disconnectTimeout=this._addSysTimedHandler(3e3,this._onDisconnectTimeout.bind(this)),this._proto._disconnect(b)}},_changeConnectStatus:function(a,b){for(var c in f._connectionPlugins)if(f._connectionPlugins.hasOwnProperty(c)){var d=this[c];if(d.statusChanged)try{d.statusChanged(a,b)}catch(e){f.error(""+c+" plugin caused an exception changing status: "+e)}}if(this.connect_callback)try{this.connect_callback(a,b)}catch(g){f.error("User connection callback caused an exception: "+g)}},_doDisconnect:function(){null!==this._disconnectTimeout&&(this.deleteTimedHandler(this._disconnectTimeout),this._disconnectTimeout=null),f.info("_doDisconnect was called"),this._proto._doDisconnect(),this.authenticated=!1,this.disconnecting=!1,this.handlers=[],this.timedHandlers=[],this.removeTimeds=[],this.removeHandlers=[],this.addTimeds=[],this.addHandlers=[],this._changeConnectStatus(f.Status.DISCONNECTED,null),this.connected=!1},_dataRecv:function(a,b){f.info("_dataRecv called");var c=this._proto._reqToData(a);if(null!==c){this.xmlInput!==f.Connection.prototype.xmlInput&&this.xmlInput(c.nodeName===this._proto.strip&&c.childNodes.length?c.childNodes[0]:c),this.rawInput!==f.Connection.prototype.rawInput&&this.rawInput(b?b:f.serialize(c));for(var d,e;this.removeHandlers.length>0;)e=this.removeHandlers.pop(),d=this.handlers.indexOf(e),d>=0&&this.handlers.splice(d,1);for(;this.addHandlers.length>0;)this.handlers.push(this.addHandlers.pop());if(this.disconnecting&&this._proto._emptyQueue())return void this._doDisconnect();var g,h,i=c.getAttribute("type");if(null!==i&&"terminate"==i){if(this.disconnecting)return;return g=c.getAttribute("condition"),h=c.getElementsByTagName("conflict"),null!==g?("remote-stream-error"==g&&h.length>0&&(g="conflict"),this._changeConnectStatus(f.Status.CONNFAIL,g)):this._changeConnectStatus(f.Status.CONNFAIL,"unknown"),void this.disconnect("unknown stream-error")}var j=this;f.forEachChild(c,null,function(a){var b,c;for(c=j.handlers,j.handlers=[],b=0;b<c.length;b++){var d=c[b];try{!d.isMatch(a)||!j.authenticated&&d.user?j.handlers.push(d):d.run(a)&&j.handlers.push(d)}catch(e){f.warn("Removing Strophe handlers due to uncaught exception: "+e.message)}}})}},mechanisms:{},_connect_cb:function(a,b,c){f.info("_connect_cb was called"),this.connected=!0;var d=this._proto._reqToData(a);if(d){this.xmlInput!==f.Connection.prototype.xmlInput&&this.xmlInput(d.nodeName===this._proto.strip&&d.childNodes.length?d.childNodes[0]:d),this.rawInput!==f.Connection.prototype.rawInput&&this.rawInput(c?c:f.serialize(d));var e=this._proto._connect_cb(d);if(e!==f.Status.CONNFAIL){this._authentication.sasl_scram_sha1=!1,this._authentication.sasl_plain=!1,this._authentication.sasl_digest_md5=!1,this._authentication.sasl_anonymous=!1,this._authentication.legacy_auth=!1;var g=d.getElementsByTagName("stream:features").length>0;g||(g=d.getElementsByTagName("features").length>0);var h,i,j=d.getElementsByTagName("mechanism"),k=[],l=!1;if(!g)return void this._proto._no_auth_received(b);if(j.length>0)for(h=0;h<j.length;h++)i=f.getText(j[h]),this.mechanisms[i]&&k.push(this.mechanisms[i]);return this._authentication.legacy_auth=d.getElementsByTagName("auth").length>0,(l=this._authentication.legacy_auth||k.length>0)?void(this.do_authentication!==!1&&this.authenticate(k)):void this._proto._no_auth_received(b)}}},authenticate:function(a){var c;for(c=0;c<a.length-1;++c){for(var e=c,g=c+1;g<a.length;++g)a[g].prototype.priority>a[e].prototype.priority&&(e=g);if(e!=c){var h=a[c];a[c]=a[e],a[e]=h}}var i=!1;for(c=0;c<a.length;++c)if(a[c].test(this)){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._sasl_challenge_handler=this._addSysHandler(this._sasl_challenge_cb.bind(this),null,"challenge",null,null),this._sasl_mechanism=new a[c],this._sasl_mechanism.onStart(this);var j=b("auth",{xmlns:f.NS.SASL,mechanism:this._sasl_mechanism.name});if(this._sasl_mechanism.isClientFirst){var k=this._sasl_mechanism.onChallenge(this,null);j.t(Base64.encode(k))}this.send(j.tree()),i=!0;break}i||(null===f.getNodeFromJid(this.jid)?(this._changeConnectStatus(f.Status.CONNFAIL,"x-strophe-bad-non-anon-jid"),this.disconnect("x-strophe-bad-non-anon-jid")):(this._changeConnectStatus(f.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:f.NS.AUTH}).c("username",{}).t(f.getNodeFromJid(this.jid)).tree())))},_sasl_challenge_cb:function(a){var c=Base64.decode(f.getText(a)),d=this._sasl_mechanism.onChallenge(this,c),e=b("response",{xmlns:f.NS.SASL});return""!==d&&e.t(Base64.encode(d)),this.send(e.tree()),!0},_auth1_cb:function(){var a=d({type:"set",id:"_auth_2"}).c("query",{xmlns:f.NS.AUTH}).c("username",{}).t(f.getNodeFromJid(this.jid)).up().c("password").t(this.pass);return f.getResourceFromJid(this.jid)||(this.jid=f.getBareJidFromJid(this.jid)+"/strophe"),a.up().c("resource",{}).t(f.getResourceFromJid(this.jid)),this._addSysHandler(this._auth2_cb.bind(this),null,null,null,"_auth_2"),this.send(a.tree()),!1},_sasl_success_cb:function(a){if(this._sasl_data["server-signature"]){var b,c=Base64.decode(f.getText(a)),d=/([a-z]+)=([^,]+)(,|$)/,e=c.match(d);if("v"==e[1]&&(b=e[2]),b!=this._sasl_data["server-signature"])return this.deleteHandler(this._sasl_failure_handler),this._sasl_failure_handler=null,this._sasl_challenge_handler&&(this.deleteHandler(this._sasl_challenge_handler),this._sasl_challenge_handler=null),this._sasl_data={},this._sasl_failure_cb(null)}return f.info("SASL authentication succeeded."),this._sasl_mechanism&&this._sasl_mechanism.onSuccess(),this.deleteHandler(this._sasl_failure_handler),this._sasl_failure_handler=null,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(),!1},_sasl_auth1_cb:function(a){this.features=a;var b,c;for(b=0;b<a.childNodes.length;b++)c=a.childNodes[b],"bind"==c.nodeName&&(this.do_bind=!0),"session"==c.nodeName&&(this.do_session=!0);if(!this.do_bind)return this._changeConnectStatus(f.Status.AUTHFAIL,null),!1;this._addSysHandler(this._sasl_bind_cb.bind(this),null,null,null,"_bind_auth_2");var e=f.getResourceFromJid(this.jid);return this.send(e?d({type:"set",id:"_bind_auth_2"}).c("bind",{xmlns:f.NS.BIND}).c("resource",{}).t(e).tree():d({type:"set",id:"_bind_auth_2"}).c("bind",{xmlns:f.NS.BIND}).tree()),!1},_sasl_bind_cb:function(a){if("error"==a.getAttribute("type")){f.info("SASL binding failed.");var b,c=a.getElementsByTagName("conflict");return c.length>0&&(b="conflict"),this._changeConnectStatus(f.Status.AUTHFAIL,b),!1}var e,g=a.getElementsByTagName("bind");return g.length>0?(e=g[0].getElementsByTagName("jid"),void(e.length>0&&(this.jid=f.getText(e[0]),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:f.NS.SESSION}).tree())):(this.authenticated=!0,this._changeConnectStatus(f.Status.CONNECTED,null))))):(f.info("SASL binding failed."),this._changeConnectStatus(f.Status.AUTHFAIL,null),!1)},_sasl_session_cb:function(a){if("result"==a.getAttribute("type"))this.authenticated=!0,this._changeConnectStatus(f.Status.CONNECTED,null);else if("error"==a.getAttribute("type"))return f.info("Session creation failed."),this._changeConnectStatus(f.Status.AUTHFAIL,null),!1;return!1},_sasl_failure_cb:function(){return this._sasl_success_handler&&(this.deleteHandler(this._sasl_success_handler),this._sasl_success_handler=null),this._sasl_challenge_handler&&(this.deleteHandler(this._sasl_challenge_handler),this._sasl_challenge_handler=null),this._sasl_mechanism&&this._sasl_mechanism.onFailure(),this._changeConnectStatus(f.Status.AUTHFAIL,null),!1},_auth2_cb:function(a){return"result"==a.getAttribute("type")?(this.authenticated=!0,this._changeConnectStatus(f.Status.CONNECTED,null)):"error"==a.getAttribute("type")&&(this._changeConnectStatus(f.Status.AUTHFAIL,null),this.disconnect("authentication failed")),!1},_addSysTimedHandler:function(a,b){var c=new f.TimedHandler(a,b);return c.user=!1,this.addTimeds.push(c),c},_addSysHandler:function(a,b,c,d,e){var g=new f.Handler(a,b,c,d,e);return g.user=!1,this.addHandlers.push(g),g},_onDisconnectTimeout:function(){return f.info("_onDisconnectTimeout was called"),this._proto._onDisconnectTimeout(),this._doDisconnect(),!1},_onIdle:function(){for(var a,b,c,d;this.addTimeds.length>0;)this.timedHandlers.push(this.addTimeds.pop());for(;this.removeTimeds.length>0;)b=this.removeTimeds.pop(),a=this.timedHandlers.indexOf(b),a>=0&&this.timedHandlers.splice(a,1);var e=(new Date).getTime();for(d=[],a=0;a<this.timedHandlers.length;a++)b=this.timedHandlers[a],(this.authenticated||!b.user)&&(c=b.lastCalled+b.period,0>=c-e?b.run()&&d.push(b):d.push(b));this.timedHandlers=d,clearTimeout(this._idleTimeout),this._proto._onIdle(),this.connected&&(this._idleTimeout=setTimeout(this._onIdle.bind(this),100))}},a&&a(f,b,c,d,e),f.SASLMechanism=function(a,b,c){this.name=a,this.isClientFirst=b,this.priority=c},f.SASLMechanism.prototype={test:function(){return!0},onStart:function(a){this._connection=a},onChallenge:function(){throw new Error("You should implement challenge handling!")},onFailure:function(){this._connection=null},onSuccess:function(){this._connection=null}},f.SASLAnonymous=function(){},f.SASLAnonymous.prototype=new f.SASLMechanism("ANONYMOUS",!1,10),f.SASLAnonymous.test=function(a){return null===a.authcid},f.Connection.prototype.mechanisms[f.SASLAnonymous.prototype.name]=f.SASLAnonymous,f.SASLPlain=function(){},f.SASLPlain.prototype=new f.SASLMechanism("PLAIN",!0,20),f.SASLPlain.test=function(a){return null!==a.authcid},f.SASLPlain.prototype.onChallenge=function(a){var b=a.authzid;return b+="\x00",b+=a.authcid,b+="\x00",b+=a.pass},f.Connection.prototype.mechanisms[f.SASLPlain.prototype.name]=f.SASLPlain,f.SASLSHA1=function(){},f.SASLSHA1.prototype=new f.SASLMechanism("SCRAM-SHA-1",!0,40),f.SASLSHA1.test=function(a){return null!==a.authcid},f.SASLSHA1.prototype.onChallenge=function(a,b,c){var d=c||MD5.hexdigest(1234567890*Math.random()),e="n="+a.authcid;return e+=",r=",e+=d,a._sasl_data.cnonce=d,a._sasl_data["client-first-message-bare"]=e,e="n,,"+e,this.onChallenge=function(a,b){for(var c,d,e,f,g,h,i,j,k,l,m,n="c=biws,",o=a._sasl_data["client-first-message-bare"]+","+b+",",p=a._sasl_data.cnonce,q=/([a-z]+)=([^,]+)(,|$)/;b.match(q);){var r=b.match(q);switch(b=b.replace(r[0],""),r[1]){case"r":c=r[2];break;case"s":d=r[2];break;case"i":e=r[2]}}if(c.substr(0,p.length)!==p)return a._sasl_data={},a._sasl_failure_cb();for(n+="r="+c,o+=n,d=Base64.decode(d),d+="\x00\x00\x00",f=h=core_hmac_sha1(a.pass,d),i=1;e>i;i++){for(g=core_hmac_sha1(a.pass,binb2str(h)),j=0;5>j;j++)f[j]^=g[j];h=g}for(f=binb2str(f),k=core_hmac_sha1(f,"Client Key"),l=str_hmac_sha1(f,"Server Key"),m=core_hmac_sha1(str_sha1(binb2str(k)),o),a._sasl_data["server-signature"]=b64_hmac_sha1(l,o),j=0;5>j;j++)k[j]^=m[j];return n+=",p="+Base64.encode(binb2str(k))}.bind(this),e},f.Connection.prototype.mechanisms[f.SASLSHA1.prototype.name]=f.SASLSHA1,f.SASLMD5=function(){},f.SASLMD5.prototype=new f.SASLMechanism("DIGEST-MD5",!1,30),f.SASLMD5.test=function(a){return null!==a.authcid},f.SASLMD5.prototype._quote=function(a){return'"'+a.replace(/\\/g,"\\\\").replace(/"/g,'\\"')+'"'},f.SASLMD5.prototype.onChallenge=function(a,b,c){for(var d,e=/([a-z]+)=("[^"]+"|[^,"]+)(?:,|$)/,f=c||MD5.hexdigest(""+1234567890*Math.random()),g="",h=null,i="",j="";b.match(e);)switch(d=b.match(e),b=b.replace(d[0],""),d[2]=d[2].replace(/^"(.+)"$/,"$1"),d[1]){case"realm":g=d[2];
-break;case"nonce":i=d[2];break;case"qop":j=d[2];break;case"host":h=d[2]}var k=a.servtype+"/"+a.domain;null!==h&&(k=k+"/"+h);var l=MD5.hash(a.authcid+":"+g+":"+this._connection.pass)+":"+i+":"+f,m="AUTHENTICATE:"+k,n="";return n+="charset=utf-8,",n+="username="+this._quote(a.authcid)+",",n+="realm="+this._quote(g)+",",n+="nonce="+this._quote(i)+",",n+="nc=00000001,",n+="cnonce="+this._quote(f)+",",n+="digest-uri="+this._quote(k)+",",n+="response="+MD5.hexdigest(MD5.hexdigest(l)+":"+i+":00000001:"+f+":auth:"+MD5.hexdigest(m))+",",n+="qop=auth",this.onChallenge=function(){return""}.bind(this),n},f.Connection.prototype.mechanisms[f.SASLMD5.prototype.name]=f.SASLMD5}(function(){window.Strophe=arguments[0],window.$build=arguments[1],window.$msg=arguments[2],window.$iq=arguments[3],window.$pres=arguments[4]}),Strophe.Request=function(a,b,c,d){this.id=++Strophe._requestId,this.xmlData=a,this.data=Strophe.serialize(a),this.origFunc=b,this.func=b,this.rid=c,this.date=0/0,this.sends=d||0,this.abort=!1,this.dead=null,this.age=function(){if(!this.date)return 0;var a=new Date;return(a-this.date)/1e3},this.timeDead=function(){if(!this.dead)return 0;var a=new Date;return(a-this.dead)/1e3},this.xhr=this._newXHR()},Strophe.Request.prototype={getResponse:function(){var a=null;if(this.xhr.responseXML&&this.xhr.responseXML.documentElement){if(a=this.xhr.responseXML.documentElement,"parsererror"==a.tagName)throw Strophe.error("invalid response received"),Strophe.error("responseText: "+this.xhr.responseText),Strophe.error("responseXML: "+Strophe.serialize(this.xhr.responseXML)),"parsererror"}else this.xhr.responseText&&(Strophe.error("invalid response received"),Strophe.error("responseText: "+this.xhr.responseText),Strophe.error("responseXML: "+Strophe.serialize(this.xhr.responseXML)));return a},_newXHR:function(){var a=null;return window.XMLHttpRequest?(a=new XMLHttpRequest,a.overrideMimeType&&a.overrideMimeType("text/xml")):window.ActiveXObject&&(a=new ActiveXObject("Microsoft.XMLHTTP")),a.onreadystatechange=this.func.bind(null,this),a}},Strophe.Bosh=function(a){this._conn=a,this.rid=Math.floor(4294967295*Math.random()),this.sid=null,this.hold=1,this.wait=60,this.window=5,this._requests=[]},Strophe.Bosh.prototype={strip:null,_buildBody:function(){var a=$build("body",{rid:this.rid++,xmlns:Strophe.NS.HTTPBIND});return null!==this.sid&&a.attrs({sid:this.sid}),a},_reset:function(){this.rid=Math.floor(4294967295*Math.random()),this.sid=null},_connect:function(a,b,c){this.wait=a||this.wait,this.hold=b||this.hold;var d=this._buildBody().attrs({to:this._conn.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":Strophe.NS.BOSH});c&&d.attrs({route:c});var e=this._conn._connect_cb;this._requests.push(new Strophe.Request(d.tree(),this._onRequestStateChange.bind(this,e.bind(this._conn)),d.tree().getAttribute("rid"))),this._throttledRequestHandler()},_attach:function(a,b,c,d,e,f,g){this._conn.jid=a,this.sid=b,this.rid=c,this._conn.connect_callback=d,this._conn.domain=Strophe.getDomainFromJid(this._conn.jid),this._conn.authenticated=!0,this._conn.connected=!0,this.wait=e||this.wait,this.hold=f||this.hold,this.window=g||this.window,this._conn._changeConnectStatus(Strophe.Status.ATTACHED,null)},_connect_cb:function(a){var b,c,d=a.getAttribute("type");if(null!==d&&"terminate"==d)return Strophe.error("BOSH-Connection failed: "+b),b=a.getAttribute("condition"),c=a.getElementsByTagName("conflict"),null!==b?("remote-stream-error"==b&&c.length>0&&(b="conflict"),this._conn._changeConnectStatus(Strophe.Status.CONNFAIL,b)):this._conn._changeConnectStatus(Strophe.Status.CONNFAIL,"unknown"),this._conn._doDisconnect(),Strophe.Status.CONNFAIL;this.sid||(this.sid=a.getAttribute("sid"));var e=a.getAttribute("requests");e&&(this.window=parseInt(e,10));var f=a.getAttribute("hold");f&&(this.hold=parseInt(f,10));var g=a.getAttribute("wait");g&&(this.wait=parseInt(g,10))},_disconnect:function(a){this._sendTerminate(a)},_doDisconnect:function(){this.sid=null,this.rid=Math.floor(4294967295*Math.random())},_emptyQueue:function(){return 0===this._requests.length},_hitError:function(a){this.errors++,Strophe.warn("request errored, status: "+a+", number of errors: "+this.errors),this.errors>4&&this._onDisconnectTimeout()},_no_auth_received:function(a){a=a?a.bind(this._conn):this._conn._connect_cb.bind(this._conn);var b=this._buildBody();this._requests.push(new Strophe.Request(b.tree(),this._onRequestStateChange.bind(this,a.bind(this._conn)),b.tree().getAttribute("rid"))),this._throttledRequestHandler()},_onDisconnectTimeout:function(){for(var a;this._requests.length>0;)a=this._requests.pop(),a.abort=!0,a.xhr.abort(),a.xhr.onreadystatechange=function(){}},_onIdle:function(){var a=this._conn._data;if(this._conn.authenticated&&0===this._requests.length&&0===a.length&&!this._conn.disconnecting&&(Strophe.info("no requests during idle cycle, sending blank request"),a.push(null)),this._requests.length<2&&a.length>0&&!this._conn.paused){for(var b=this._buildBody(),c=0;c<a.length;c++)null!==a[c]&&("restart"===a[c]?b.attrs({to:this._conn.domain,"xml:lang":"en","xmpp:restart":"true","xmlns:xmpp":Strophe.NS.BOSH}):b.cnode(a[c]).up());delete this._conn._data,this._conn._data=[],this._requests.push(new Strophe.Request(b.tree(),this._onRequestStateChange.bind(this,this._conn._dataRecv.bind(this._conn)),b.tree().getAttribute("rid"))),this._processRequest(this._requests.length-1)}if(this._requests.length>0){var d=this._requests[0].age();null!==this._requests[0].dead&&this._requests[0].timeDead()>Math.floor(Strophe.SECONDARY_TIMEOUT*this.wait)&&this._throttledRequestHandler(),d>Math.floor(Strophe.TIMEOUT*this.wait)&&(Strophe.warn("Request "+this._requests[0].id+" timed out, over "+Math.floor(Strophe.TIMEOUT*this.wait)+" seconds since last activity"),this._throttledRequestHandler())}},_onRequestStateChange:function(a,b){if(Strophe.debug("request id "+b.id+"."+b.sends+" state changed to "+b.xhr.readyState),b.abort)return void(b.abort=!1);var c;if(4==b.xhr.readyState){c=0;try{c=b.xhr.status}catch(d){}if("undefined"==typeof c&&(c=0),this.disconnecting&&c>=400)return void this._hitError(c);var e=this._requests[0]==b,f=this._requests[1]==b;(c>0&&500>c||b.sends>5)&&(this._removeRequest(b),Strophe.debug("request id "+b.id+" should now be removed")),200==c?((f||e&&this._requests.length>0&&this._requests[0].age()>Math.floor(Strophe.SECONDARY_TIMEOUT*this.wait))&&this._restartRequest(0),Strophe.debug("request id "+b.id+"."+b.sends+" got 200"),a(b),this.errors=0):(Strophe.error("request id "+b.id+"."+b.sends+" error "+c+" happened"),(0===c||c>=400&&600>c||c>=12e3)&&(this._hitError(c),c>=400&&500>c&&(this._conn._changeConnectStatus(Strophe.Status.DISCONNECTING,null),this._conn._doDisconnect()))),c>0&&500>c||b.sends>5||this._throttledRequestHandler()}},_processRequest:function(a){var b=this,c=this._requests[a],d=-1;try{4==c.xhr.readyState&&(d=c.xhr.status)}catch(e){Strophe.error("caught an error in _requests["+a+"], reqStatus: "+d)}if("undefined"==typeof d&&(d=-1),c.sends>this.maxRetries)return void this._onDisconnectTimeout();var f=c.age(),g=!isNaN(f)&&f>Math.floor(Strophe.TIMEOUT*this.wait),h=null!==c.dead&&c.timeDead()>Math.floor(Strophe.SECONDARY_TIMEOUT*this.wait),i=4==c.xhr.readyState&&(1>d||d>=500);if((g||h||i)&&(h&&Strophe.error("Request "+this._requests[a].id+" timed out (secondary), restarting"),c.abort=!0,c.xhr.abort(),c.xhr.onreadystatechange=function(){},this._requests[a]=new Strophe.Request(c.xmlData,c.origFunc,c.rid,c.sends),c=this._requests[a]),0===c.xhr.readyState){Strophe.debug("request id "+c.id+"."+c.sends+" posting");try{c.xhr.open("POST",this._conn.service,this._conn.options.sync?!1:!0)}catch(j){return Strophe.error("XHR open failed."),this._conn.connected||this._conn._changeConnectStatus(Strophe.Status.CONNFAIL,"bad-service"),void this._conn.disconnect()}var k=function(){if(c.date=new Date,b._conn.options.customHeaders){var a=b._conn.options.customHeaders;for(var d in a)a.hasOwnProperty(d)&&c.xhr.setRequestHeader(d,a[d])}c.xhr.send(c.data)};if(c.sends>1){var l=1e3*Math.min(Math.floor(Strophe.TIMEOUT*this.wait),Math.pow(c.sends,3));setTimeout(k,l)}else k();c.sends++,this._conn.xmlOutput!==Strophe.Connection.prototype.xmlOutput&&this._conn.xmlOutput(c.xmlData.nodeName===this.strip&&c.xmlData.childNodes.length?c.xmlData.childNodes[0]:c.xmlData),this._conn.rawOutput!==Strophe.Connection.prototype.rawOutput&&this._conn.rawOutput(c.data)}else Strophe.debug("_processRequest: "+(0===a?"first":"second")+" request has readyState of "+c.xhr.readyState)},_removeRequest:function(a){Strophe.debug("removing request");var b;for(b=this._requests.length-1;b>=0;b--)a==this._requests[b]&&this._requests.splice(b,1);a.xhr.onreadystatechange=function(){},this._throttledRequestHandler()},_restartRequest:function(a){var b=this._requests[a];null===b.dead&&(b.dead=new Date),this._processRequest(a)},_reqToData:function(a){try{return a.getResponse()}catch(b){if("parsererror"!=b)throw b;this._conn.disconnect("strophe-parsererror")}},_sendTerminate:function(a){Strophe.info("_sendTerminate was called");var b=this._buildBody().attrs({type:"terminate"});a&&b.cnode(a.tree());var c=new Strophe.Request(b.tree(),this._onRequestStateChange.bind(this,this._conn._dataRecv.bind(this._conn)),b.tree().getAttribute("rid"));this._requests.push(c),this._throttledRequestHandler()},_send:function(){clearTimeout(this._conn._idleTimeout),this._throttledRequestHandler(),this._conn._idleTimeout=setTimeout(this._conn._onIdle.bind(this._conn),100)},_sendRestart:function(){this._throttledRequestHandler(),clearTimeout(this._conn._idleTimeout)},_throttledRequestHandler:function(){Strophe.debug(this._requests?"_throttledRequestHandler called with "+this._requests.length+" requests":"_throttledRequestHandler called with undefined requests"),this._requests&&0!==this._requests.length&&(this._requests.length>0&&this._processRequest(0),this._requests.length>1&&Math.abs(this._requests[0].rid-this._requests[1].rid)<this.window&&this._processRequest(1))}},Strophe.Websocket=function(a){this._conn=a,this.strip="stream:stream";var b=a.service;if(0!==b.indexOf("ws:")&&0!==b.indexOf("wss:")){var c="";c+="ws"===a.options.protocol&&"https:"!==window.location.protocol?"ws":"wss",c+="://"+window.location.host,c+=0!==b.indexOf("/")?window.location.pathname+b:b,a.service=c}},Strophe.Websocket.prototype={_buildStream:function(){return $build("stream:stream",{to:this._conn.domain,xmlns:Strophe.NS.CLIENT,"xmlns:stream":Strophe.NS.STREAM,version:"1.0"})},_check_streamerror:function(a,b){var c=a.getElementsByTagName("stream:error");if(0===c.length)return!1;for(var d=c[0],e="",f="",g="urn:ietf:params:xml:ns:xmpp-streams",h=0;h<d.childNodes.length;h++){var i=d.childNodes[h];if(i.getAttribute("xmlns")!==g)break;"text"===i.nodeName?f=i.textContent:e=i.nodeName}var j="WebSocket stream error: ";return j+=e?e:"unknown",f&&(j+=" - "+e),Strophe.error(j),this._conn._changeConnectStatus(b,e),this._conn._doDisconnect(),!0},_reset:function(){},_connect:function(){this._closeSocket(),this.socket=new WebSocket(this._conn.service,"xmpp"),this.socket.onopen=this._onOpen.bind(this),this.socket.onerror=this._onError.bind(this),this.socket.onclose=this._onClose.bind(this),this.socket.onmessage=this._connect_cb_wrapper.bind(this)},_connect_cb:function(a){var b=this._check_streamerror(a,Strophe.Status.CONNFAIL);return b?Strophe.Status.CONNFAIL:void 0},_handleStreamStart:function(a){var b=!1,c=a.getAttribute("xmlns");"string"!=typeof c?b="Missing xmlns in stream:stream":c!==Strophe.NS.CLIENT&&(b="Wrong xmlns in stream:stream: "+c);var d=a.namespaceURI;"string"!=typeof d?b="Missing xmlns:stream in stream:stream":d!==Strophe.NS.STREAM&&(b="Wrong xmlns:stream in stream:stream: "+d);var e=a.getAttribute("version");return"string"!=typeof e?b="Missing version in stream:stream":"1.0"!==e&&(b="Wrong version in stream:stream: "+e),b?(this._conn._changeConnectStatus(Strophe.Status.CONNFAIL,b),this._conn._doDisconnect(),!1):!0},_connect_cb_wrapper:function(a){if(0===a.data.indexOf("<stream:stream ")||0===a.data.indexOf("<?xml")){var b=a.data.replace(/^(<\?.*?\?>\s*)*/,"");if(""===b)return;b=a.data.replace(/<stream:stream (.*[^\/])>/,"<stream:stream $1/>");var c=(new DOMParser).parseFromString(b,"text/xml").documentElement;this._conn.xmlInput(c),this._conn.rawInput(a.data),this._handleStreamStart(c)&&(this._connect_cb(c),this.streamStart=a.data.replace(/^<stream:(.*)\/>$/,"<stream:$1>"))}else{if("</stream:stream>"===a.data)return this._conn.rawInput(a.data),this._conn.xmlInput(document.createElement("stream:stream")),this._conn._changeConnectStatus(Strophe.Status.CONNFAIL,"Received closing stream"),void this._conn._doDisconnect();var d=this._streamWrap(a.data),e=(new DOMParser).parseFromString(d,"text/xml").documentElement;this.socket.onmessage=this._onMessage.bind(this),this._conn._connect_cb(e,null,a.data)}},_disconnect:function(a){if(this.socket.readyState!==WebSocket.CLOSED){a&&this._conn.send(a);var b="</stream:stream>";this._conn.xmlOutput(document.createElement("stream:stream")),this._conn.rawOutput(b);try{this.socket.send(b)}catch(c){Strophe.info("Couldn't send closing stream tag.")}}this._conn._doDisconnect()},_doDisconnect:function(){Strophe.info("WebSockets _doDisconnect was called"),this._closeSocket()},_streamWrap:function(a){return this.streamStart+a+"</stream:stream>"},_closeSocket:function(){if(this.socket)try{this.socket.close()}catch(a){}this.socket=null},_emptyQueue:function(){return!0},_onClose:function(){this._conn.connected&&!this._conn.disconnecting?(Strophe.error("Websocket closed unexcectedly"),this._conn._doDisconnect()):Strophe.info("Websocket closed")},_no_auth_received:function(a){Strophe.error("Server did not send any auth methods"),this._conn._changeConnectStatus(Strophe.Status.CONNFAIL,"Server did not send any auth methods"),a&&(a=a.bind(this._conn))(),this._conn._doDisconnect()},_onDisconnectTimeout:function(){},_onError:function(a){Strophe.error("Websocket error "+a),this._conn._changeConnectStatus(Strophe.Status.CONNFAIL,"The WebSocket connection could not be established was disconnected."),this._disconnect()},_onIdle:function(){var a=this._conn._data;if(a.length>0&&!this._conn.paused){for(var b=0;b<a.length;b++)if(null!==a[b]){var c,d;"restart"===a[b]?(c=this._buildStream(),d=this._removeClosingTag(c),c=c.tree()):(c=a[b],d=Strophe.serialize(c)),this._conn.xmlOutput(c),this._conn.rawOutput(d),this.socket.send(d)}this._conn._data=[]}},_onMessage:function(a){var b,c;if("</stream:stream>"===a.data){var d="</stream:stream>";return this._conn.rawInput(d),this._conn.xmlInput(document.createElement("stream:stream")),void(this._conn.disconnecting||this._conn._doDisconnect())}if(0===a.data.search("<stream:stream ")){if(c=a.data.replace(/<stream:stream (.*[^\/])>/,"<stream:stream $1/>"),b=(new DOMParser).parseFromString(c,"text/xml").documentElement,!this._handleStreamStart(b))return}else c=this._streamWrap(a.data),b=(new DOMParser).parseFromString(c,"text/xml").documentElement;if(!this._check_streamerror(b,Strophe.Status.ERROR))return this._conn.disconnecting&&"presence"===b.firstChild.nodeName&&"unavailable"===b.firstChild.getAttribute("type")?(this._conn.xmlInput(b),void this._conn.rawInput(Strophe.serialize(b))):void this._conn._dataRecv(b,a.data)},_onOpen:function(){Strophe.info("Websocket open");var a=this._buildStream();this._conn.xmlOutput(a.tree());var b=this._removeClosingTag(a);this._conn.rawOutput(b),this.socket.send(b)},_removeClosingTag:function(a){var b=Strophe.serialize(a);return b=b.replace(/<(stream:stream .*[^\/])\/>$/,"<$1>")},_reqToData:function(a){return a},_send:function(){this._conn.flush()},_sendRestart:function(){clearTimeout(this._conn._idleTimeout),this._conn._onIdle.bind(this._conn)()}},function(){var a,b,c,d=function(a,b){return function(){return a.apply(b,arguments)}};Strophe.addConnectionPlugin("muc",{_connection:null,rooms:{},roomNames:[],init:function(a){return this._connection=a,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"),Strophe.addNamespace("MUC_ROOMCONF",Strophe.NS.MUC+"#roomconfig")},join:function(a,b,d,e,f,g,h){var i,j;return j=this.test_append_nick(a,b),i=$pres({from:this._connection.jid,to:j}).c("x",{xmlns:Strophe.NS.MUC}),null!=h&&(i=i.c("history",h).up),null!=g&&i.cnode(Strophe.xmlElement("password",[],g)),"undefined"!=typeof extended_presence&&null!==extended_presence&&i.up.cnode(extended_presence),null==this._muc_handler&&(this._muc_handler=this._connection.addHandler(function(b){return function(c){var d,e,f,g,h,i,j,k,l,m;if(d=c.getAttribute("from"),!d)return!0;if(h=d.split("/")[0],!b.rooms[h])return!0;if(a=b.rooms[h],f={},"message"===c.nodeName)f=a._message_handlers;else if("presence"===c.nodeName&&(k=c.getElementsByTagName("x"),k.length>0))for(l=0,m=k.length;m>l;l++)if(i=k[l],j=i.getAttribute("xmlns"),j&&j.match(Strophe.NS.MUC)){f=a._presence_handlers;break}for(g in f)e=f[g],e(c,a)||delete f[g];return!0}}(this))),this.rooms.hasOwnProperty(a)||(this.rooms[a]=new c(this,a,b,g),this.roomNames.push(a)),e&&this.rooms[a].addHandler("presence",e),d&&this.rooms[a].addHandler("message",d),f&&this.rooms[a].addHandler("roster",f),this._connection.send(i)},leave:function(a,b,c,d){var e,f,g,h;return e=this.roomNames.indexOf(a),delete this.rooms[a],e>=0&&(this.roomNames.splice(e,1),0===this.roomNames.length&&(this._connection.deleteHandler(this._muc_handler),this._muc_handler=null)),h=this.test_append_nick(a,b),g=this._connection.getUniqueId(),f=$pres({type:"unavailable",id:g,from:this._connection.jid,to:h}),null!=d&&f.c("status",d),null!=c&&this._connection.addHandler(c,null,"presence",null,g),this._connection.send(f),g},message:function(a,b,c,d,e){var f,g,h,i;return i=this.test_append_nick(a,b),e=e||(null!=b?"chat":"groupchat"),g=this._connection.getUniqueId(),f=$msg({to:i,from:this._connection.jid,type:e,id:g}).c("body",{xmlns:Strophe.NS.CLIENT}).t(c),f.up(),null!=d&&(f.c("html",{xmlns:Strophe.NS.XHTML_IM}).c("body",{xmlns:Strophe.NS.XHTML}).h(d),0===f.node.childNodes.length?(h=f.node.parentNode,f.up().up(),f.node.removeChild(h)):f.up().up()),f.c("x",{xmlns:"jabber:x:event"}).c("composing"),this._connection.send(f),g},groupchat:function(a,b,c){return this.message(a,null,b,c)},invite:function(a,b,c){var d,e;return e=this._connection.getUniqueId(),d=$msg({from:this._connection.jid,to:a,id:e}).c("x",{xmlns:Strophe.NS.MUC_USER}).c("invite",{to:b}),null!=c&&d.c("reason",c),this._connection.send(d),e},directInvite:function(a,b,c,d){var e,f,g;return g=this._connection.getUniqueId(),e={xmlns:"jabber:x:conference",jid:a},null!=c&&(e.reason=c),null!=d&&(e.password=d),f=$msg({from:this._connection.jid,to:b,id:g}).c("x",e),this._connection.send(f),g},queryOccupants:function(a,b,c){var d,e;return d={xmlns:Strophe.NS.DISCO_ITEMS},e=$iq({from:this._connection.jid,to:a,type:"get"}).c("query",d),this._connection.sendIQ(e,b,c)},configure:function(a,b,c){var d,e;return d=$iq({to:a,type:"get"}).c("query",{xmlns:Strophe.NS.MUC_OWNER}),e=d.tree(),this._connection.sendIQ(e,b,c)},cancelConfigure:function(a){var b,c;return b=$iq({to:a,type:"set"}).c("query",{xmlns:Strophe.NS.MUC_OWNER}).c("x",{xmlns:"jabber:x:data",type:"cancel"}),c=b.tree(),this._connection.sendIQ(c)},saveConfiguration:function(a,b,c,d){var e,f,g,h,i;if(f=$iq({to:a,type:"set"}).c("query",{xmlns:Strophe.NS.MUC_OWNER}),"undefined"!=typeof Form&&b instanceof Form)b.type="submit",f.cnode(b.toXML());else for(f.c("x",{xmlns:"jabber:x:data",type:"submit"}),h=0,i=b.length;i>h;h++)e=b[h],f.cnode(e).up();return g=f.tree(),this._connection.sendIQ(g,c,d)},createInstantRoom:function(a,b,c){var d;return d=$iq({to:a,type:"set"}).c("query",{xmlns:Strophe.NS.MUC_OWNER}).c("x",{xmlns:"jabber:x:data",type:"submit"}),this._connection.sendIQ(d.tree(),b,c)},setTopic:function(a,b){var c;return c=$msg({to:a,from:this._connection.jid,type:"groupchat"}).c("subject",{xmlns:"jabber:client"}).t(b),this._connection.send(c.tree())},_modifyPrivilege:function(a,b,c,d,e){var f;return f=$iq({to:a,type:"set"}).c("query",{xmlns:Strophe.NS.MUC_ADMIN}).cnode(b.node),null!=c&&f.c("reason",c),this._connection.sendIQ(f.tree(),d,e)},modifyRole:function(a,b,c,d,e,f){var g;return g=$build("item",{nick:b,role:c}),this._modifyPrivilege(a,g,d,e,f)},kick:function(a,b,c,d,e){return this.modifyRole(a,b,"none",c,d,e)},voice:function(a,b,c,d,e){return this.modifyRole(a,b,"participant",c,d,e)},mute:function(a,b,c,d,e){return this.modifyRole(a,b,"visitor",c,d,e)},op:function(a,b,c,d,e){return this.modifyRole(a,b,"moderator",c,d,e)},deop:function(a,b,c,d,e){return this.modifyRole(a,b,"participant",c,d,e)},modifyAffiliation:function(a,b,c,d,e,f){var g;return g=$build("item",{jid:b,affiliation:c}),this._modifyPrivilege(a,g,d,e,f)},ban:function(a,b,c,d,e){return this.modifyAffiliation(a,b,"outcast",c,d,e)},member:function(a,b,c,d,e){return this.modifyAffiliation(a,b,"member",c,d,e)},revoke:function(a,b,c,d,e){return this.modifyAffiliation(a,b,"none",c,d,e)},owner:function(a,b,c,d,e){return this.modifyAffiliation(a,b,"owner",c,d,e)},admin:function(a,b,c,d,e){return this.modifyAffiliation(a,b,"admin",c,d,e)},changeNick:function(a,b){var c,d;return d=this.test_append_nick(a,b),c=$pres({from:this._connection.jid,to:d,id:this._connection.getUniqueId()}),this._connection.send(c.tree())},setStatus:function(a,b,c,d){var e,f;return f=this.test_append_nick(a,b),e=$pres({from:this._connection.jid,to:f}),null!=c&&e.c("show",c).up(),null!=d&&e.c("status",d),this._connection.send(e.tree())},listRooms:function(a,b,c){var d;return d=$iq({to:a,from:this._connection.jid,type:"get"}).c("query",{xmlns:Strophe.NS.DISCO_ITEMS}),this._connection.sendIQ(d,b,c)},test_append_nick:function(a,b){var c,d;return d=Strophe.escapeNode(Strophe.getNodeFromJid(a)),c=Strophe.getDomainFromJid(a),d+"@"+c+(null!=b?"/"+b:"")}}),c=function(){function b(a,b,c,e){this.client=a,this.name=b,this.nick=c,this.password=e,this._roomRosterHandler=d(this._roomRosterHandler,this),this._addOccupant=d(this._addOccupant,this),this.roster={},this._message_handlers={},this._presence_handlers={},this._roster_handlers={},this._handler_ids=0,a.muc&&(this.client=a.muc),this.name=Strophe.getBareJidFromJid(b),this.addHandler("presence",this._roomRosterHandler)}return b.prototype.join=function(a,b,c){return this.client.join(this.name,this.nick,a,b,c,this.password)},b.prototype.leave=function(a,b){return this.client.leave(this.name,this.nick,a,b),delete this.client.rooms[this.name]},b.prototype.message=function(a,b,c,d){return this.client.message(this.name,a,b,c,d)},b.prototype.groupchat=function(a,b){return this.client.groupchat(this.name,a,b)},b.prototype.invite=function(a,b){return this.client.invite(this.name,a,b)},b.prototype.directInvite=function(a,b){return this.client.directInvite(this.name,a,b,this.password)},b.prototype.configure=function(a){return this.client.configure(this.name,a)},b.prototype.cancelConfigure=function(){return this.client.cancelConfigure(this.name)},b.prototype.saveConfiguration=function(a){return this.client.saveConfiguration(this.name,a)},b.prototype.queryOccupants=function(a,b){return this.client.queryOccupants(this.name,a,b)},b.prototype.setTopic=function(a){return this.client.setTopic(this.name,a)},b.prototype.modifyRole=function(a,b,c,d,e){return this.client.modifyRole(this.name,a,b,c,d,e)},b.prototype.kick=function(a,b,c,d){return this.client.kick(this.name,a,b,c,d)},b.prototype.voice=function(a,b,c,d){return this.client.voice(this.name,a,b,c,d)},b.prototype.mute=function(a,b,c,d){return this.client.mute(this.name,a,b,c,d)},b.prototype.op=function(a,b,c,d){return this.client.op(this.name,a,b,c,d)},b.prototype.deop=function(a,b,c,d){return this.client.deop(this.name,a,b,c,d)},b.prototype.modifyAffiliation=function(a,b,c,d,e){return this.client.modifyAffiliation(this.name,a,b,c,d,e)},b.prototype.ban=function(a,b,c,d){return this.client.ban(this.name,a,b,c,d)},b.prototype.member=function(a,b,c,d){return this.client.member(this.name,a,b,c,d)},b.prototype.revoke=function(a,b,c,d){return this.client.revoke(this.name,a,b,c,d)},b.prototype.owner=function(a,b,c,d){return this.client.owner(this.name,a,b,c,d)},b.prototype.admin=function(a,b,c,d){return this.client.admin(this.name,a,b,c,d)},b.prototype.changeNick=function(a){return this.nick=a,this.client.changeNick(this.name,a)},b.prototype.setStatus=function(a,b){return this.client.setStatus(this.name,this.nick,a,b)},b.prototype.addHandler=function(a,b){var c;switch(c=this._handler_ids++,a){case"presence":this._presence_handlers[c]=b;break;case"message":this._message_handlers[c]=b;break;case"roster":this._roster_handlers[c]=b;break;default:return this._handler_ids--,null}return c},b.prototype.removeHandler=function(a){return delete this._presence_handlers[a],delete this._message_handlers[a],delete this._roster_handlers[a]},b.prototype._addOccupant=function(b){var c;return c=new a(b,this),this.roster[c.nick]=c,c},b.prototype._roomRosterHandler=function(a){var c,d,e,f,g,h;switch(c=b._parsePresence(a),g=c.nick,f=c.newnick||null,c.type){case"error":return;case"unavailable":f&&(c.nick=f,this.roster[g]&&this.roster[f]&&(this.roster[g].update(this.roster[f]),this.roster[f]=this.roster[g]),this.roster[g]&&!this.roster[f]&&(this.roster[f]=this.roster[g].update(c))),delete this.roster[g];break;default:this.roster[g]?this.roster[g].update(c):this._addOccupant(c)}h=this._roster_handlers;for(e in h)d=h[e],d(this.roster,this)||delete this._roster_handlers[e];return!0},b._parsePresence=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q;for(e={},b=a.attributes,e.nick=Strophe.getResourceFromJid(b.from.textContent),e.type=(null!=(j=b.type)?j.textContent:void 0)||null,e.states=[],k=a.childNodes,f=0,h=k.length;h>f;f++)switch(c=k[f],c.nodeName){case"status":e.status=c.textContent||null;break;case"show":e.show=c.textContent||null;break;case"x":if(b=c.attributes,(null!=(l=b.xmlns)?l.textContent:void 0)===Strophe.NS.MUC_USER)for(m=c.childNodes,g=0,i=m.length;i>g;g++)switch(d=m[g],d.nodeName){case"item":b=d.attributes,e.affiliation=(null!=(n=b.affiliation)?n.textContent:void 0)||null,e.role=(null!=(o=b.role)?o.textContent:void 0)||null,e.jid=(null!=(p=b.jid)?p.textContent:void 0)||null,e.newnick=(null!=(q=b.nick)?q.textContent:void 0)||null;break;case"status":d.attributes.code&&e.states.push(d.attributes.code.textContent)}}return e},b}(),b=function(){function a(a){this.parse=d(this.parse,this),null!=a&&this.parse(a)}return a.prototype.parse=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n;for(g=a.getElementsByTagName("query")[0].childNodes,this.identities=[],this.features=[],this.x=[],h=0,k=g.length;k>h;h++)switch(d=g[h],c=d.attributes,d.nodeName){case"identity":for(f={},i=0,l=c.length;l>i;i++)b=c[i],f[b.name]=b.textContent;this.identities.push(f);break;case"feature":this.features.push(c["var"].textContent);break;case"x":if(c=d.childNodes[0].attributes,"FORM_TYPE"===!c["var"].textContent||"hidden"===!c.type.textContent)break;for(n=d.childNodes,j=0,m=n.length;m>j;j++)e=n[j],e.attributes.type||(c=e.attributes,this.x.push({"var":c["var"].textContent,label:c.label.textContent||"",value:e.firstChild.textContent||""}))}return{identities:this.identities,features:this.features,x:this.x}},a}(),a=function(){function a(a,b){this.room=b,this.update=d(this.update,this),this.admin=d(this.admin,this),this.owner=d(this.owner,this),this.revoke=d(this.revoke,this),this.member=d(this.member,this),this.ban=d(this.ban,this),this.modifyAffiliation=d(this.modifyAffiliation,this),this.deop=d(this.deop,this),this.op=d(this.op,this),this.mute=d(this.mute,this),this.voice=d(this.voice,this),this.kick=d(this.kick,this),this.modifyRole=d(this.modifyRole,this),this.update(a)}return a.prototype.modifyRole=function(a,b,c,d){return this.room.modifyRole(this.nick,a,b,c,d)},a.prototype.kick=function(a,b,c){return this.room.kick(this.nick,a,b,c)},a.prototype.voice=function(a,b,c){return this.room.voice(this.nick,a,b,c)},a.prototype.mute=function(a,b,c){return this.room.mute(this.nick,a,b,c)},a.prototype.op=function(a,b,c){return this.room.op(this.nick,a,b,c)},a.prototype.deop=function(a,b,c){return this.room.deop(this.nick,a,b,c)},a.prototype.modifyAffiliation=function(a,b,c,d){return this.room.modifyAffiliation(this.jid,a,b,c,d)},a.prototype.ban=function(a,b,c){return this.room.ban(this.jid,a,b,c)},a.prototype.member=function(a,b,c){return this.room.member(this.jid,a,b,c)},a.prototype.revoke=function(a,b,c){return this.room.revoke(this.jid,a,b,c)},a.prototype.owner=function(a,b,c){return this.room.owner(this.jid,a,b,c)},a.prototype.admin=function(a,b,c){return this.room.admin(this.jid,a,b,c)},a.prototype.update=function(a){return this.nick=a.nick||null,this.affiliation=a.affiliation||null,this.role=a.role||null,this.jid=a.jid||null,this.status=a.status||null,this.show=a.show||null,this},a}()}.call(this),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(a,b,c,d){for(var e=0;e<this._identities.length;e++)if(this._identities[e].category==a&&this._identities[e].type==b&&this._identities[e].name==c&&this._identities[e].lang==d)return!1;return this._identities.push({category:a,type:b,name:c,lang:d}),!0},addFeature:function(a){for(var b=0;b<this._features.length;b++)if(this._features[b]==a)return!1;return this._features.push(a),!0},removeFeature:function(a){for(var b=0;b<this._features.length;b++)if(this._features[b]===a)return this._features.splice(b,1),!0;return!1},addItem:function(a,b,c,d){return c&&!d?!1:(this._items.push({jid:a,name:b,node:c,call_back:d}),!0)},info:function(a,b,c,d,e){var f={xmlns:Strophe.NS.DISCO_INFO};b&&(f.node=b);var g=$iq({from:this._connection.jid,to:a,type:"get"}).c("query",f);this._connection.sendIQ(g,c,d,e)},items:function(a,b,c,d,e){var f={xmlns:Strophe.NS.DISCO_ITEMS};b&&(f.node=b);var g=$iq({from:this._connection.jid,to:a,type:"get"}).c("query",f);this._connection.sendIQ(g,c,d,e)},_buildIQResult:function(a,b){var c=a.getAttribute("id"),d=a.getAttribute("from"),e=$iq({type:"result",id:c});return null!==d&&e.attrs({to:d}),e.c("query",b)},_onDiscoInfo:function(a){var b=a.getElementsByTagName("query")[0].getAttribute("node"),c={xmlns:Strophe.NS.DISCO_INFO};b&&(c.node=b);for(var d=this._buildIQResult(a,c),e=0;e<this._identities.length;e++){var c={category:this._identities[e].category,type:this._identities[e].type};this._identities[e].name&&(c.name=this._identities[e].name),this._identities[e].lang&&(c["xml:lang"]=this._identities[e].lang),d.c("identity",c).up()}for(var e=0;e<this._features.length;e++)d.c("feature",{"var":this._features[e]}).up();return this._connection.send(d.tree()),!0},_onDiscoItems:function(a){var b={xmlns:Strophe.NS.DISCO_ITEMS},c=a.getElementsByTagName("query")[0].getAttribute("node");if(c){b.node=c;for(var d=[],e=0;e<this._items.length;e++)if(this._items[e].node==c){d=this._items[e].call_back(a);break}}else var d=this._items;for(var f=this._buildIQResult(a,b),e=0;e<d.length;e++){var g={jid:d[e].jid};d[e].name&&(g.name=d[e].name),d[e].node&&(g.node=d[e].node),f.c("item",g).up()}return this._connection.send(f.tree()),!0}}),Strophe.addConnectionPlugin("caps",{HASH:"sha-1",node:"http://strophe.im/strophejs/",_ver:"",_connection:null,_knownCapabilities:{},_jidVerIndex:{},init:function(a){if(this._connection=a,Strophe.addNamespace("CAPS","http://jabber.org/protocol/caps"),!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;for(var a="",b=this._connection.disco._identities.sort(this._sortIdentities),c=b.length,d=this._connection.disco._features.sort(),e=d.length,f=0;c>f;f++){var g=b[f];a+=g.category+"/"+g.type+"/"+g.lang+"/"+g.name+"<"}for(var f=0;e>f;f++)a+=d[f]+"<";return this._ver=b64_sha1(a),this._ver},getCapabilitiesByJid:function(a){return this._jidVerIndex[a]?this._knownCapabilities[this._jidVerIndex[a]]:null},_delegateCapabilities:function(a){var b=a.getAttribute("from"),c=a.querySelector("c"),d=c.getAttribute("ver"),e=c.getAttribute("node");return this._knownCapabilities[d]?(this._jidVerIndex[b]=d,this._jidVerIndex[b]&&!this._jidVerIndex[b]===d||(this._jidVerIndex[b]=d),!0):this._requestCapabilities(b,e,d)},_requestCapabilities:function(a,b,c){if(a!==this._connection.jid){var d=this._connection.disco.info(a,b+"#"+c);this._connection.addHandler(this._handleDiscoInfoReply.bind(this),Strophe.NS.DISCO_INFO,"iq","result",d,a)}return!0},_handleDiscoInfoReply:function(a){var b=a.querySelector("query"),c=b.getAttribute("node").split("#"),d=c[1],e=a.getAttribute("from");if(this._knownCapabilities[d])this._jidVerIndex[e]&&!this._jidVerIndex[e]===d||(this._jidVerIndex[e]=d);else{var f=b.childNodes,g=f.length;this._knownCapabilities[d]=[];for(var h=0;g>h;h++){var c=f[h];this._knownCapabilities[d].push({name:c.nodeName,attributes:c.attributes})}this._jidVerIndex[e]=d}return!1},_sortIdentities:function(a,b){return a.category>b.category?1:a.category<b.category?-1:a.type>b.type?1:a.type<b.type?-1:a.lang>b.lang?1:a.lang<b.lang?-1:0}});var Mustache=function(){var a=function(){};return a.prototype={otag:"{{",ctag:"}}",pragmas:{},buffer:[],pragmas_implemented:{"IMPLICIT-ITERATOR":!0},context:{},render:function(a,b,c,d){if(d||(this.context=b,this.buffer=[]),!this.includes("",a))return d?a:void this.send(a);a=this.render_pragmas(a);var e=this.render_section(a,b,c);return d?this.render_tags(e,b,c,d):void this.render_tags(e,b,c,d)},send:function(a){""!==a&&this.buffer.push(a)},render_pragmas:function(a){if(!this.includes("%",a))return a;var b=this,c=new RegExp(this.otag+"%([\\w-]+) ?([\\w]+=[\\w]+)?"+this.ctag,"g");return a.replace(c,function(a,c,d){if(!b.pragmas_implemented[c])throw{message:"This implementation of mustache doesn't understand the '"+c+"' pragma"};if(b.pragmas[c]={},d){var e=d.split("=");b.pragmas[c][e[0]]=e[1]}return""})},render_partial:function(a,b,c){if(a=this.trim(a),!c||void 0===c[a])throw{message:"unknown_partial '"+a+"'"};return"object"!=typeof b[a]?this.render(c[a],b,c,!0):this.render(c[a],b[a],c,!0)},render_section:function(a,b,c){if(!this.includes("#",a)&&!this.includes("^",a))return a;var d=this,e=new RegExp(this.otag+"(\\^|\\#)\\s*(.+)\\s*"+this.ctag+"\n*([\\s\\S]+?)"+this.otag+"\\/\\s*\\2\\s*"+this.ctag+"\\s*","mg");return a.replace(e,function(a,e,f,g){var h=d.find(f,b);return"^"==e?!h||d.is_array(h)&&0===h.length?d.render(g,b,c,!0):"":"#"==e?d.is_array(h)?d.map(h,function(a){return d.render(g,d.create_context(a),c,!0)}).join(""):d.is_object(h)?d.render(g,d.create_context(h),c,!0):"function"==typeof h?h.call(b,g,function(a){return d.render(a,b,c,!0)}):h?d.render(g,b,c,!0):"":void 0})},render_tags:function(a,b,c,d){for(var e=this,f=function(){return new RegExp(e.otag+"(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?"+e.ctag+"+","g")},g=f(),h=function(a,d,h){switch(d){case"!":return"";case"=":return e.set_delimiters(h),g=f(),"";case">":return e.render_partial(h,b,c);case"{":return e.find(h,b);default:return e.escape(e.find(h,b))}},i=a.split("\n"),j=0;j<i.length;j++)i[j]=i[j].replace(g,h,this),d||this.send(i[j]);return d?i.join("\n"):void 0},set_delimiters:function(a){var b=a.split(" ");this.otag=this.escape_regex(b[0]),this.ctag=this.escape_regex(b[1])},escape_regex:function(a){if(!arguments.callee.sRE){var b=["/",".","*","+","?","|","(",")","[","]","{","}","\\"];arguments.callee.sRE=new RegExp("(\\"+b.join("|\\")+")","g")}return a.replace(arguments.callee.sRE,"\\$1")},find:function(a,b){function c(a){return a===!1||0===a||a}a=this.trim(a);var d;return c(b[a])?d=b[a]:c(this.context[a])&&(d=this.context[a]),"function"==typeof d?d.apply(b):void 0!==d?d:""},includes:function(a,b){return-1!=b.indexOf(this.otag+a)},escape:function(a){return a=String(null===a?"":a),a.replace(/&(?!\w+;)|["'<>\\]/g,function(a){switch(a){case"&":return"&amp;";case"\\":return"\\\\";case'"':return"&quot;";case"'":return"&#39;";case"<":return"&lt;";case">":return"&gt;";default:return a}})},create_context:function(a){if(this.is_object(a))return a;var b=".";this.pragmas["IMPLICIT-ITERATOR"]&&(b=this.pragmas["IMPLICIT-ITERATOR"].iterator);var c={};return c[b]=a,c},is_object:function(a){return a&&"object"==typeof a},is_array:function(a){return"[object Array]"===Object.prototype.toString.call(a)},trim:function(a){return a.replace(/^\s*|\s*$/g,"")},map:function(a,b){if("function"==typeof a.map)return a.map(b);for(var c=[],d=a.length,e=0;d>e;e++)c.push(b(a[e]));return c}},{name:"mustache.js",version:"0.3.1-dev",to_html:function(b,c,d,e){var f=new a;return e&&(f.send=e),f.render(b,c,d),e?void 0:f.buffer.join("\n")}}}();!function(a){var b=Array.prototype.slice,c={dict:null,load:function(b){null!==this.dict?a.extend(this.dict,b):this.dict=b},_:function(a){return dict=this.dict,dict&&dict.hasOwnProperty(a)&&(a=dict[a]),args=b.call(arguments),args[0]=a,this.printf.apply(this,args)},printf:function(c,d){return arguments.length<2?c:(d=a.isArray(d)?d:b.call(arguments,1),c.replace(/([^%]|^)%(?:(\d+)\$)?s/g,function(a,b,c){return c?b+d[parseInt(c)-1]:b+d.shift()}).replace(/%%s/g,"%s"))}};a.fn._t=function(){return a(this).html(c._.apply(c,arguments))},a.i18n=c}(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,c=/[^-+\dA-Z]/g,d=function(a,b){for(a=String(a),b=b||2;a.length<b;)a="0"+a;return a};return function(e,f,g){var h=dateFormat;if(1!=arguments.length||"[object String]"!=Object.prototype.toString.call(e)||/\d/.test(e)||(f=e,e=void 0),e=e?new Date(e):new Date,isNaN(e))throw SyntaxError("invalid date");f=String(h.masks[f]||f||h.masks["default"]),"UTC:"==f.slice(0,4)&&(f=f.slice(4),g=!0);var i=g?"getUTC":"get",j=e[i+"Date"](),k=e[i+"Day"](),l=e[i+"Month"](),m=e[i+"FullYear"](),n=e[i+"Hours"](),o=e[i+"Minutes"](),p=e[i+"Seconds"](),q=e[i+"Milliseconds"](),r=g?0:e.getTimezoneOffset(),s={d:j,dd:d(j),ddd:h.i18n.dayNames[k],dddd:h.i18n.dayNames[k+7],m:l+1,mm:d(l+1),mmm:h.i18n.monthNames[l],mmmm:h.i18n.monthNames[l+12],yy:String(m).slice(2),yyyy:m,h:n%12||12,hh:d(n%12||12),H:n,HH:d(n),M:o,MM:d(o),s:p,ss:d(p),l:d(q,3),L:d(q>99?Math.round(q/10):q),t:12>n?"a":"p",tt:12>n?"am":"pm",T:12>n?"A":"P",TT:12>n?"AM":"PM",Z:g?"UTC":(String(e).match(b)||[""]).pop().replace(c,""),o:(r>0?"-":"+")+d(100*Math.floor(Math.abs(r)/60)+Math.abs(r)%60,4),S:["th","st","nd","rd"][j%10>3?0:(j%100-j%10!=10)*j%10]};return f.replace(a,function(a){return a in s?s[a]:a.slice(1,a.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/mustache.js b/libs/mustache.js
deleted file mode 160000
-Subproject 32a26c4b7e5d3031abecfe0f06ac8fa4bbdd253
diff --git a/libs/strophejs b/libs/strophejs
deleted file mode 160000
-Subproject 02c798fed585e5e4be055d33a9d625d2b164c41
diff --git a/libs/strophejs-plugins b/libs/strophejs-plugins
deleted file mode 160000
-Subproject ca6124b235b0dc007a1bac740a5db3e0978c1a2
diff --git a/package.json b/package.json
index 3739558..f147d6a 100644
--- a/package.json
+++ b/package.json
@@ -40,14 +40,21 @@
"homepage": "http://candy-chat.github.io/candy/",
"devDependencies": {
"grunt": "^0.4.5",
- "grunt-contrib-clean": "~0.5.0",
+ "grunt-clear": "^0.2.1",
+ "grunt-contrib-clean": "^0.5.0",
"grunt-contrib-jshint": "^0.10.0",
"grunt-contrib-uglify": "^0.4.0",
"grunt-contrib-watch": "^0.6.1",
- "grunt-mkdir": "~0.1.1",
- "grunt-natural-docs": "~0.1.1",
+ "grunt-coveralls": "^0.3.0",
+ "grunt-mkdir": "^0.1.1",
+ "grunt-natural-docs": "^0.1.1",
"grunt-notify": "^0.3.0",
- "grunt-sync-pkg": "~0.1.2",
- "jshint-stylish": "^0.2.0"
+ "grunt-sync-pkg": "^0.1.2",
+ "intern": "^2.0.1",
+ "jshint-stylish": "^0.2.0",
+ "sinon": "^1.10.3",
+ "sinon-chai": "^2.5.0",
+ "lolex": "^1.2.0",
+ "grunt-todo": "~0.4.0"
}
}
diff --git a/res/audioplayer.swf b/res/audioplayer.swf
deleted file mode 100644
index 72390d4..0000000
--- a/res/audioplayer.swf
+++ /dev/null
Binary files differ
diff --git a/res/default.css b/res/default.css
index a780826..22f4ac9 100644
--- a/res/default.css
+++ b/res/default.css
@@ -167,11 +167,11 @@ ul {
}
#chat-statusmessage-control {
- background: url(img/action/statusmessage-off.png);
+ background-image: url(img/action/statusmessage-off.png);
}
#chat-statusmessage-control.checked {
- background: url(img/action/statusmessage-on.png);
+ background-image: url(img/action/statusmessage-on.png);
}
#chat-toolbar .usercount {
@@ -575,7 +575,7 @@ ul {
width: 15px;
}
-#chat-modal {
+#chat-modal.modal-common {
background: #eee;
width: 300px;
padding: 20px 5px;
@@ -641,6 +641,21 @@ ul {
color: #333;
}
+#chat-modal.login-with-domains {
+ width: 650px;
+ margin-left: -330px;
+}
+
+#chat-modal span.at-symbol {
+ float: left;
+ padding: 6px;
+ font-size: 14px;
+}
+
+#chat-modal select[name=domain] {
+ width: 320px;
+}
+
#chat-modal label {
text-align: right;
padding-right: 1em;
diff --git a/res/notify.m4a b/res/notify.m4a
new file mode 100644
index 0000000..b090b29
--- /dev/null
+++ b/res/notify.m4a
Binary files differ
diff --git a/res/notify.mp3 b/res/notify.mp3
index c00d997..c7f7495 100644
--- a/res/notify.mp3
+++ b/res/notify.mp3
Binary files differ
diff --git a/res/notify.ogg b/res/notify.ogg
new file mode 100644
index 0000000..c6663d3
--- /dev/null
+++ b/res/notify.ogg
Binary files differ
diff --git a/setup.sh b/setup.sh
new file mode 100755
index 0000000..be693f8
--- /dev/null
+++ b/setup.sh
@@ -0,0 +1,54 @@
+#!/usr/bin/env bash
+#
+# Easy installation for contributing to candy
+#
+# Copyright 2014 Michael Weibel <michael.weibel@gmail.com>
+# License: MIT
+#
+
+# Show errors in case of undefined variables
+set -o nounset
+
+echo
+echo "Welcome to the Candy Vagrant setup"
+echo
+echo "This script will setup a Vagrant box with development dependencies on it."
+echo "It will also build Candy and run tests to verify that everything is working."
+echo
+echo "In case of an error, use 'install.log' for log informations."
+echo
+
+touch install.log
+echo "" > install.log
+
+echo -n "* Booting Vagrant box (this might take a while)..."
+if vagrant up --no-provision >> install.log 2>&1
+ then echo "done"
+else
+ echo "failed!"
+ echo "Do you have 'vagrant' installed in your PATH?"
+ echo "Please check install.log"
+ echo
+ echo "Aborting"
+ exit 2
+fi
+
+echo -n "* Provisioning Vagrant box (this might take a few minutes)..."
+if vagrant provision >> install.log 2>&1
+ then echo "done"
+else
+ echo "failed!"
+ echo "Please check install.log"
+ echo
+ echo "Aborting"
+ exit 2
+fi
+
+echo -n "* Building Candy and running tests..."
+vagrant ssh -c "cd /vagrant && grunt && grunt test"
+
+echo
+echo "Candy is now running on http://localhost:8080"
+echo
+
+exit 0
diff --git a/src/candy.js b/src/candy.js
index aacbcf4..2ec7011 100644
--- a/src/candy.js
+++ b/src/candy.js
@@ -30,7 +30,7 @@ var Candy = (function(self, $) {
*/
self.about = {
name: 'Candy',
- version: '1.7.1'
+ version: '2.0.0'
};
/** Function: init
diff --git a/src/core.js b/src/core.js
index 54407f2..2296205 100644
--- a/src/core.js
+++ b/src/core.js
@@ -34,6 +34,10 @@ Candy.Core = (function(self, Strophe, $) {
* Current user (me)
*/
_user = null,
+ /** PrivateVariable: _roster
+ * Main roster of contacts
+ */
+ _roster = null,
/** PrivateVariable: _rooms
* Opened rooms, containing instances of Candy.Core.ChatRooms
*/
@@ -47,9 +51,7 @@ Candy.Core = (function(self, Strophe, $) {
*/
_status,
/** PrivateVariable: _options
- * Options:
- * (Boolean) debug - Debug (Default: false)
- * (Array|Boolean) autojoin - Autojoin these channels. When boolean true, do not autojoin, wait if the server sends something.
+ * Config options
*/
_options = {
/** Boolean: autojoin
@@ -57,7 +59,46 @@ Candy.Core = (function(self, Strophe, $) {
* You may want to define an array of rooms to autojoin: `['room1@conference.host.tld', 'room2...]` (ejabberd, Openfire, ...)
*/
autojoin: undefined,
+ /** Boolean: disconnectWithoutTabs
+ * If you set to `false`, when you close all of the tabs, the service does not disconnect.
+ * Set to `true`, when you close all of the tabs, the service will disconnect.
+ */
+ disconnectWithoutTabs: true,
+ /** String: conferenceDomain
+ * Holds the prefix for an XMPP chat server's conference subdomain.
+ * If not set, assumes no specific subdomain.
+ */
+ conferenceDomain: undefined,
+ /** Boolean: debug
+ * Enable debug
+ */
debug: false,
+ /** List: domains
+ * If non-null, causes login form to offer this
+ * pre-set list of domains to choose between when
+ * logging in. Any user-provided domain is discarded
+ * and the selected domain is appended.
+ * For each list item, only characters up to the first
+ * whitespace are used, so you can append extra
+ * information to each item if desired.
+ */
+ domains: null,
+ /** Boolean: hideDomainList
+ * If true, the domain list defined above is suppressed.
+ * Without a selector displayed, the default domain
+ * (usually the first one listed) will be used as
+ * described above. Probably only makes sense with a
+ * single domain defined.
+ */
+ hideDomainList: false,
+ /** Boolean: disableCoreNotifications
+ * If set to `true`, the built-in notifications (sounds and badges) are disabled.
+ * This is useful if you are using a plugin to handle notifications.
+ */
+ disableCoreNotifications: false,
+ /** Boolean: disableWindowUnload
+ * Disable window unload handler which usually disconnects from XMPP
+ */
disableWindowUnload: false,
/** Integer: presencePriority
* Default priority for presence messages in order to receive messages across different resources
@@ -67,7 +108,20 @@ Candy.Core = (function(self, Strophe, $) {
* JID resource to use when connecting to the server.
* Specify `''` (an empty string) to request a random resource.
*/
- resource: Candy.about.name
+ resource: Candy.about.name,
+ /** Boolean: useParticipantRealJid
+ * If set true, will direct one-on-one chats to participant's real JID rather than their MUC jid
+ */
+ useParticipantRealJid: false,
+ /**
+ * Roster version we claim to already have. Used when loading a cached roster.
+ * Defaults to null, indicating we don't have the roster.
+ */
+ initialRosterVersion: null,
+ /**
+ * Initial roster items. Loaded from a cache, used to bootstrap displaying a roster prior to fetching updates.
+ */
+ initialRosterItems: []
},
/** PrivateFunction: _addNamespace
@@ -88,8 +142,10 @@ Candy.Core = (function(self, Strophe, $) {
_addNamespace('PRIVATE', 'jabber:iq:private');
_addNamespace('BOOKMARKS', 'storage:bookmarks');
_addNamespace('PRIVACY', 'jabber:iq:privacy');
- _addNamespace('DELAY', 'jabber:x:delay');
+ _addNamespace('DELAY', 'urn:xmpp:delay');
+ _addNamespace('JABBER_DELAY', 'jabber:x:delay');
_addNamespace('PUBSUB', 'http://jabber.org/protocol/pubsub');
+ _addNamespace('CARBONS', 'urn:xmpp:carbons:2');
},
_getEscapedJidFromJid = function(jid) {
@@ -122,11 +178,39 @@ Candy.Core = (function(self, Strophe, $) {
};
}
}
+ Strophe.log = function (level, message) {
+ var level_name, console_level;
+ switch (level) {
+ case Strophe.LogLevel.DEBUG:
+ level_name = 'DEBUG';
+ console_level = 'log';
+ break;
+ case Strophe.LogLevel.INFO:
+ level_name = 'INFO';
+ console_level = 'info';
+ break;
+ case Strophe.LogLevel.WARN:
+ level_name = 'WARN';
+ console_level = 'info';
+ break;
+ case Strophe.LogLevel.ERROR:
+ level_name = 'ERROR';
+ console_level = 'error';
+ break;
+ case Strophe.LogLevel.FATAL:
+ level_name = 'FATAL';
+ console_level = 'error';
+ break;
+ }
+ console[console_level]('[Strophe][' + level_name + ']: ' + message);
+ };
self.log('[Init] Debugging enabled');
}
_addNamespaces();
+ _roster = new Candy.Core.ChatRoster();
+
// Connect to BOSH/Websocket service
_connection = new Strophe.Connection(_service);
_connection.rawInput = self.rawInput.bind(self);
@@ -200,6 +284,12 @@ Candy.Core = (function(self, Strophe, $) {
_anonymousConnection = !_anonymousConnection ? jidOrHost && jidOrHost.indexOf("@") < 0 : true;
if(jidOrHost && password) {
+ // Respect the resource, if provided
+ var resource = Strophe.getResourceFromJid(jidOrHost);
+ if (resource) {
+ _options.resource = resource;
+ }
+
// authentication
_connection.connect(_getEscapedJidFromJid(jidOrHost) + '/' + _options.resource, password, Candy.Core.Event.Strophe.Connect);
if (nick) {
@@ -229,8 +319,14 @@ Candy.Core = (function(self, Strophe, $) {
* (Integer) sid - Session ID
* (Integer) rid - rid
*/
- self.attach = function(jid, sid, rid) {
- _user = new self.ChatUser(jid, Strophe.getNodeFromJid(jid));
+ self.attach = function(jid, sid, rid, nick) {
+ if (nick) {
+ _user = new self.ChatUser(jid, nick);
+ } else {
+ _user = new self.ChatUser(jid, Strophe.getNodeFromJid(jid));
+ }
+ // Reset before every connection attempt to make sure reconnections work after authfail, alltabsclosed, ...
+ _connection.reset();
self.registerEventHandlers();
_connection.attach(jid, sid, rid, Candy.Core.Event.Strophe.Connect);
};
@@ -240,9 +336,6 @@ Candy.Core = (function(self, Strophe, $) {
*/
self.disconnect = function() {
if(_connection.connected) {
- $.each(self.getRooms(), function() {
- Candy.Core.Action.Jabber.Room.Leave(this.getJid());
- });
_connection.disconnect();
}
};
@@ -266,6 +359,16 @@ Candy.Core = (function(self, Strophe, $) {
return _connection.addHandler(handler, ns, name, type, id, from, options);
};
+ /** Function: getRoster
+ * Gets main roster
+ *
+ * Returns:
+ * Instance of Candy.Core.ChatRoster
+ */
+ self.getRoster = function() {
+ return _roster;
+ };
+
/** Function: getUser
* Gets current user
*
@@ -411,5 +514,21 @@ Candy.Core = (function(self, Strophe, $) {
*/
self.log = function() {};
+ /** Function: warn
+ * Print a message to the browser's "info" log
+ * Enabled regardless of debug mode
+ */
+ self.warn = function() {
+ Function.prototype.apply.call(console.warn, console, arguments);
+ };
+
+ /** Function: error
+ * Print a message to the browser's "error" log
+ * Enabled regardless of debug mode
+ */
+ self.error = function() {
+ Function.prototype.apply.call(console.error, console, arguments);
+ };
+
return self;
}(Candy.Core || {}, Strophe, jQuery));
diff --git a/src/core/action.js b/src/core/action.js
index 2680934..b72a946 100644
--- a/src/core/action.js
+++ b/src/core/action.js
@@ -39,10 +39,11 @@ Candy.Core.Action = (function(self, Strophe, $) {
from: Candy.Util.escapeJid(msg.attr('to')),
id: msg.attr('id')
}).c('query', {
- name: Candy.about.name,
- version: Candy.about.version,
- os: navigator.userAgent
- }));
+ xmlns: Strophe.NS.VERSION
+ })
+ .c('name', Candy.about.name).up()
+ .c('version', Candy.about.version).up()
+ .c('os', navigator.userAgent));
},
/** Function: SetNickname
@@ -72,10 +73,20 @@ Candy.Core.Action = (function(self, Strophe, $) {
* Sends a request for a roster
*/
Roster: function() {
- Candy.Core.getConnection().sendIQ($iq({
- type: 'get',
- xmlns: Strophe.NS.CLIENT
- }).c('query', {xmlns: Strophe.NS.ROSTER}).tree());
+ var roster = Candy.Core.getConnection().roster,
+ options = Candy.Core.getOptions();
+ roster.registerCallback(Candy.Core.Event.Jabber.RosterPush);
+ $.each(options.initialRosterItems, function (i, item) {
+ // Blank out resources because their cached value is not relevant
+ item.resources = {};
+ });
+ roster.get(
+ Candy.Core.Event.Jabber.RosterFetch,
+ options.initialRosterVersion,
+ options.initialRosterItems
+ );
+ // Bootstrap our roster with cached items
+ Candy.Core.Event.Jabber.RosterLoad(roster.items);
},
/** Function: Presence
@@ -153,6 +164,17 @@ Candy.Core.Action = (function(self, Strophe, $) {
}
},
+ /** Function: EnableCarbons
+ * Enable message carbons (XEP-0280)
+ */
+ EnableCarbons: function() {
+ Candy.Core.getConnection().sendIQ($iq({
+ type: 'set'
+ })
+ .c('enable', {xmlns: Strophe.NS.CARBONS })
+ .tree());
+ },
+
/** Function: ResetIgnoreList
* Create new ignore privacy list (and reset the previous one, if it exists).
*/
@@ -253,7 +275,6 @@ Candy.Core.Action = (function(self, Strophe, $) {
*/
Leave: function(roomJid) {
var user = Candy.Core.getRoom(roomJid).getUser();
- roomJid = Candy.Util.escapeJid(roomJid);
if (user) {
Candy.Core.getConnection().muc.leave(roomJid, user.getNick(), function() {});
}
diff --git a/src/core/chatRoom.js b/src/core/chatRoom.js
index ad37b4b..2d842bd 100644
--- a/src/core/chatRoom.js
+++ b/src/core/chatRoom.js
@@ -37,74 +37,74 @@ Candy.Core.ChatRoom = function(roomJid) {
* Candy.Core.ChatRoster instance
*/
this.roster = new Candy.Core.ChatRoster();
+};
- /** Function: setUser
- * Set user of this room.
- *
- * Parameters:
- * (Candy.Core.ChatUser) user - Chat user
- */
- this.setUser = function(user) {
- this.user = user;
- };
+/** Function: setUser
+ * Set user of this room.
+ *
+ * Parameters:
+ * (Candy.Core.ChatUser) user - Chat user
+ */
+Candy.Core.ChatRoom.prototype.setUser = function(user) {
+ this.user = user;
+};
- /** Function: getUser
- * Get current local user
- *
- * Returns:
- * (Object) - Candy.Core.ChatUser instance or null
- */
- this.getUser = function() {
- return this.user;
- };
+/** Function: getUser
+ * Get current local user
+ *
+ * Returns:
+ * (Object) - Candy.Core.ChatUser instance or null
+ */
+Candy.Core.ChatRoom.prototype.getUser = function() {
+ return this.user;
+};
- /** Function: getJid
- * Get room jid
- *
- * Returns:
- * (String) - Room jid
- */
- this.getJid = function() {
- return this.room.jid;
- };
+/** Function: getJid
+ * Get room jid
+ *
+ * Returns:
+ * (String) - Room jid
+ */
+Candy.Core.ChatRoom.prototype.getJid = function() {
+ return this.room.jid;
+};
- /** Function: setName
- * Set room name
- *
- * Parameters:
- * (String) name - Room name
- */
- this.setName = function(name) {
- this.room.name = name;
- };
+/** Function: setName
+ * Set room name
+ *
+ * Parameters:
+ * (String) name - Room name
+ */
+Candy.Core.ChatRoom.prototype.setName = function(name) {
+ this.room.name = name;
+};
- /** Function: getName
- * Get room name
- *
- * Returns:
- * (String) - Room name
- */
- this.getName = function() {
- return this.room.name;
- };
+/** Function: getName
+ * Get room name
+ *
+ * Returns:
+ * (String) - Room name
+ */
+Candy.Core.ChatRoom.prototype.getName = function() {
+ return this.room.name;
+};
- /** Function: setRoster
- * Set roster of room
- *
- * Parameters:
- * (Candy.Core.ChatRoster) roster - Chat roster
- */
- this.setRoster = function(roster) {
- this.roster = roster;
- };
+/** Function: setRoster
+ * Set roster of room
+ *
+ * Parameters:
+ * (Candy.Core.ChatRoster) roster - Chat roster
+ */
+Candy.Core.ChatRoom.prototype.setRoster = function(roster) {
+ this.roster = roster;
+};
- /** Function: getRoster
- * Get roster
- *
- * Returns
- * (Candy.Core.ChatRoster) - instance
- */
- this.getRoster = function() {
- return this.roster;
- };
+/** Function: getRoster
+ * Get roster
+ *
+ * Returns
+ * (Candy.Core.ChatRoster) - instance
+ */
+Candy.Core.ChatRoom.prototype.getRoster = function() {
+ return this.roster;
};
diff --git a/src/core/chatRoster.js b/src/core/chatRoster.js
index 6e7546e..777ef2f 100644
--- a/src/core/chatRoster.js
+++ b/src/core/chatRoster.js
@@ -21,47 +21,47 @@ Candy.Core.ChatRoster = function () {
* Roster items
*/
this.items = {};
+};
- /** Function: add
- * Add user to roster
- *
- * Parameters:
- * (Candy.Core.ChatUser) user - User to add
- */
- this.add = function(user) {
- this.items[user.getJid()] = user;
- };
+/** Function: add
+ * Add user to roster
+ *
+ * Parameters:
+ * (Candy.Core.ChatUser) user - User to add
+ */
+Candy.Core.ChatRoster.prototype.add = function(user) {
+ this.items[user.getJid()] = user;
+};
- /** Function: remove
- * Remove user from roster
- *
- * Parameters:
- * (String) jid - User jid
- */
- this.remove = function(jid) {
- delete this.items[jid];
- };
+/** Function: remove
+ * Remove user from roster
+ *
+ * Parameters:
+ * (String) jid - User jid
+ */
+Candy.Core.ChatRoster.prototype.remove = function(jid) {
+ delete this.items[jid];
+};
- /** Function: get
- * Get user from roster
- *
- * Parameters:
- * (String) jid - User jid
- *
- * Returns:
- * (Candy.Core.ChatUser) - User
- */
- this.get = function(jid) {
- return this.items[jid];
- };
+/** Function: get
+ * Get user from roster
+ *
+ * Parameters:
+ * (String) jid - User jid
+ *
+ * Returns:
+ * (Candy.Core.ChatUser) - User
+ */
+Candy.Core.ChatRoster.prototype.get = function(jid) {
+ return this.items[jid];
+};
- /** Function: getAll
- * Get all items
- *
- * Returns:
- * (Object) - all roster items
- */
- this.getAll = function() {
- return this.items;
- };
+/** Function: getAll
+ * Get all items
+ *
+ * Returns:
+ * (Object) - all roster items
+ */
+Candy.Core.ChatRoster.prototype.getAll = function() {
+ return this.items;
};
diff --git a/src/core/chatUser.js b/src/core/chatUser.js
index aecc6c7..5067afb 100644
--- a/src/core/chatUser.js
+++ b/src/core/chatUser.js
@@ -16,7 +16,7 @@
/** Class: Candy.Core.ChatUser
* Chat User
*/
-Candy.Core.ChatUser = function(jid, nick, affiliation, role) {
+Candy.Core.ChatUser = function(jid, nick, affiliation, role, realJid) {
/** Constant: ROLE_MODERATOR
* Moderator role
*/
@@ -30,6 +30,7 @@ Candy.Core.ChatUser = function(jid, nick, affiliation, role) {
/** Object: data
* User data containing:
* - jid
+ * - realJid
* - nick
* - affiliation
* - role
@@ -38,228 +39,291 @@ Candy.Core.ChatUser = function(jid, nick, affiliation, role) {
*/
this.data = {
jid: jid,
+ realJid: realJid,
nick: Strophe.unescapeNode(nick),
affiliation: affiliation,
role: role,
privacyLists: {},
customData: {},
- previousNick: undefined
+ previousNick: undefined,
+ status: 'unavailable'
};
+};
- /** Function: getJid
- * Gets an unescaped user jid
- *
- * See:
- * <Candy.Util.unescapeJid>
- *
- * Returns:
- * (String) - jid
- */
- this.getJid = function() {
- if(this.data.jid) {
- return Candy.Util.unescapeJid(this.data.jid);
- }
- return;
- };
+/** Function: getJid
+ * Gets an unescaped user jid
+ *
+ * See:
+ * <Candy.Util.unescapeJid>
+ *
+ * Returns:
+ * (String) - jid
+ */
+Candy.Core.ChatUser.prototype.getJid = function() {
+ if(this.data.jid) {
+ return Candy.Util.unescapeJid(this.data.jid);
+ }
+ return;
+};
- /** Function: getEscapedJid
- * Escapes the user's jid (node & resource get escaped)
- *
- * See:
- * <Candy.Util.escapeJid>
- *
- * Returns:
- * (String) - escaped jid
- */
- this.getEscapedJid = function() {
- return Candy.Util.escapeJid(this.data.jid);
- };
+/** Function: getEscapedJid
+ * Escapes the user's jid (node & resource get escaped)
+ *
+ * See:
+ * <Candy.Util.escapeJid>
+ *
+ * Returns:
+ * (String) - escaped jid
+ */
+Candy.Core.ChatUser.prototype.getEscapedJid = function() {
+ return Candy.Util.escapeJid(this.data.jid);
+};
- /** Function: setJid
- * Sets a user's jid
- *
- * Parameters:
- * (String) jid - New Jid
- */
- this.setJid = function(jid) {
- this.data.jid = jid;
- };
+/** Function: setJid
+ * Sets a user's jid
+ *
+ * Parameters:
+ * (String) jid - New Jid
+ */
+Candy.Core.ChatUser.prototype.setJid = function(jid) {
+ this.data.jid = jid;
+};
- /** Function: getNick
- * Gets user nick
- *
- * Returns:
- * (String) - nick
- */
- this.getNick = function() {
- return Strophe.unescapeNode(this.data.nick);
- };
+/** Function: getRealJid
+ * Gets an unescaped real jid if known
+ *
+ * See:
+ * <Candy.Util.unescapeJid>
+ *
+ * Returns:
+ * (String) - realJid
+ */
+Candy.Core.ChatUser.prototype.getRealJid = function() {
+ if(this.data.realJid) {
+ return Candy.Util.unescapeJid(this.data.realJid);
+ }
+ return;
+};
- /** Function: setNick
- * Sets a user's nick
- *
- * Parameters:
- * (String) nick - New nick
- */
- this.setNick = function(nick) {
- this.data.nick = nick;
- };
+/** Function: getNick
+ * Gets user nick
+ *
+ * Returns:
+ * (String) - nick
+ */
+Candy.Core.ChatUser.prototype.getNick = function() {
+ return Strophe.unescapeNode(this.data.nick);
+};
- /** Function: getRole
- * Gets user role
- *
- * Returns:
- * (String) - role
- */
- this.getRole = function() {
- return this.data.role;
- };
+/** Function: setNick
+ * Sets a user's nick
+ *
+ * Parameters:
+ * (String) nick - New nick
+ */
+Candy.Core.ChatUser.prototype.setNick = function(nick) {
+ this.data.nick = nick;
+};
- /** Function: setRole
- * Sets user role
- *
- * Parameters:
- * (String) role - Role
- */
- this.setRole = function(role) {
- this.data.role = role;
- };
+/** Function: getName
+ * Gets user's name (from contact or nick)
+ *
+ * Returns:
+ * (String) - name
+ */
+Candy.Core.ChatUser.prototype.getName = function() {
+ var contact = this.getContact();
+ if (contact) {
+ return contact.getName();
+ } else {
+ return this.getNick();
+ }
+};
- /** Function: setAffiliation
- * Sets user affiliation
- *
- * Parameters:
- * (String) affiliation - new affiliation
- */
- this.setAffiliation = function(affiliation) {
- this.data.affiliation = affiliation;
- };
+/** Function: getRole
+ * Gets user role
+ *
+ * Returns:
+ * (String) - role
+ */
+Candy.Core.ChatUser.prototype.getRole = function() {
+ return this.data.role;
+};
- /** Function: getAffiliation
- * Gets user affiliation
- *
- * Returns:
- * (String) - affiliation
- */
- this.getAffiliation = function() {
- return this.data.affiliation;
- };
+/** Function: setRole
+ * Sets user role
+ *
+ * Parameters:
+ * (String) role - Role
+ */
+Candy.Core.ChatUser.prototype.setRole = function(role) {
+ this.data.role = role;
+};
- /** Function: isModerator
- * Check if user is moderator. Depends on the room.
- *
- * Returns:
- * (Boolean) - true if user has role moderator or affiliation owner
- */
- this.isModerator = function() {
- return this.getRole() === this.ROLE_MODERATOR || this.getAffiliation() === this.AFFILIATION_OWNER;
- };
+/** Function: setAffiliation
+ * Sets user affiliation
+ *
+ * Parameters:
+ * (String) affiliation - new affiliation
+ */
+Candy.Core.ChatUser.prototype.setAffiliation = function(affiliation) {
+ this.data.affiliation = affiliation;
+};
- /** Function: addToOrRemoveFromPrivacyList
- * Convenience function for adding/removing users from ignore list.
- *
- * Check if user is already in privacy list. If yes, remove it. If no, add it.
- *
- * Parameters:
- * (String) list - To which privacy list the user should be added / removed from. Candy supports curently only the "ignore" list.
- * (String) jid - User jid to add/remove
- *
- * Returns:
- * (Array) - Current privacy list.
- */
- this.addToOrRemoveFromPrivacyList = function(list, jid) {
- if (!this.data.privacyLists[list]) {
- this.data.privacyLists[list] = [];
- }
- var index = -1;
- if ((index = this.data.privacyLists[list].indexOf(jid)) !== -1) {
- this.data.privacyLists[list].splice(index, 1);
- } else {
- this.data.privacyLists[list].push(jid);
- }
- return this.data.privacyLists[list];
- };
+/** Function: getAffiliation
+ * Gets user affiliation
+ *
+ * Returns:
+ * (String) - affiliation
+ */
+Candy.Core.ChatUser.prototype.getAffiliation = function() {
+ return this.data.affiliation;
+};
- /** Function: getPrivacyList
- * Returns the privacy list of the listname of the param.
- *
- * Parameters:
- * (String) list - To which privacy list the user should be added / removed from. Candy supports curently only the "ignore" list.
- *
- * Returns:
- * (Array) - Privacy List
- */
- this.getPrivacyList = function(list) {
- if (!this.data.privacyLists[list]) {
- this.data.privacyLists[list] = [];
- }
- return this.data.privacyLists[list];
- };
+/** Function: isModerator
+ * Check if user is moderator. Depends on the room.
+ *
+ * Returns:
+ * (Boolean) - true if user has role moderator or affiliation owner
+ */
+Candy.Core.ChatUser.prototype.isModerator = function() {
+ return this.getRole() === this.ROLE_MODERATOR || this.getAffiliation() === this.AFFILIATION_OWNER;
+};
- /** Function: setPrivacyLists
- * Sets privacy lists.
- *
- * Parameters:
- * (Object) lists - List object
- */
- this.setPrivacyLists = function(lists) {
- this.data.privacyLists = lists;
- };
+/** Function: addToOrRemoveFromPrivacyList
+ * Convenience function for adding/removing users from ignore list.
+ *
+ * Check if user is already in privacy list. If yes, remove it. If no, add it.
+ *
+ * Parameters:
+ * (String) list - To which privacy list the user should be added / removed from. Candy supports curently only the "ignore" list.
+ * (String) jid - User jid to add/remove
+ *
+ * Returns:
+ * (Array) - Current privacy list.
+ */
+Candy.Core.ChatUser.prototype.addToOrRemoveFromPrivacyList = function(list, jid) {
+ if (!this.data.privacyLists[list]) {
+ this.data.privacyLists[list] = [];
+ }
+ var index = -1;
+ if ((index = this.data.privacyLists[list].indexOf(jid)) !== -1) {
+ this.data.privacyLists[list].splice(index, 1);
+ } else {
+ this.data.privacyLists[list].push(jid);
+ }
+ return this.data.privacyLists[list];
+};
- /** Function: isInPrivacyList
- * Tests if this user ignores the user provided by jid.
- *
- * Parameters:
- * (String) list - Privacy list
- * (String) jid - Jid to test for
- *
- * Returns:
- * (Boolean)
- */
- this.isInPrivacyList = function(list, jid) {
- if (!this.data.privacyLists[list]) {
- return false;
- }
- return this.data.privacyLists[list].indexOf(jid) !== -1;
- };
+/** Function: getPrivacyList
+ * Returns the privacy list of the listname of the param.
+ *
+ * Parameters:
+ * (String) list - To which privacy list the user should be added / removed from. Candy supports curently only the "ignore" list.
+ *
+ * Returns:
+ * (Array) - Privacy List
+ */
+Candy.Core.ChatUser.prototype.getPrivacyList = function(list) {
+ if (!this.data.privacyLists[list]) {
+ this.data.privacyLists[list] = [];
+ }
+ return this.data.privacyLists[list];
+};
- /** Function: setCustomData
- * Stores custom data
- *
- * Parameter:
- * (Object) data - Object containing custom data
- */
- this.setCustomData = function(data) {
- this.data.customData = data;
- };
+/** Function: setPrivacyLists
+ * Sets privacy lists.
+ *
+ * Parameters:
+ * (Object) lists - List object
+ */
+Candy.Core.ChatUser.prototype.setPrivacyLists = function(lists) {
+ this.data.privacyLists = lists;
+};
- /** Function: getCustomData
- * Retrieve custom data
- *
- * Returns:
- * (Object) - Object containing custom data
- */
- this.getCustomData = function() {
- return this.data.customData;
- };
+/** Function: isInPrivacyList
+ * Tests if this user ignores the user provided by jid.
+ *
+ * Parameters:
+ * (String) list - Privacy list
+ * (String) jid - Jid to test for
+ *
+ * Returns:
+ * (Boolean)
+ */
+Candy.Core.ChatUser.prototype.isInPrivacyList = function(list, jid) {
+ if (!this.data.privacyLists[list]) {
+ return false;
+ }
+ return this.data.privacyLists[list].indexOf(jid) !== -1;
+};
- /** Function: setPreviousNick
- * If user has nickname changed, set previous nickname.
- *
- * Parameters:
- * (String) previousNick - the previous nickname
- */
- this.setPreviousNick = function(previousNick) {
- this.data.previousNick = previousNick;
- };
+/** Function: setCustomData
+ * Stores custom data
+ *
+ * Parameter:
+ * (Object) data - Object containing custom data
+ */
+Candy.Core.ChatUser.prototype.setCustomData = function(data) {
+ this.data.customData = data;
+};
- /** Function: hasNicknameChanged
- * Gets the previous nickname if available.
- *
- * Returns:
- * (String) - previous nickname
- */
- this.getPreviousNick = function() {
- return this.data.previousNick;
- };
+/** Function: getCustomData
+ * Retrieve custom data
+ *
+ * Returns:
+ * (Object) - Object containing custom data
+ */
+Candy.Core.ChatUser.prototype.getCustomData = function() {
+ return this.data.customData;
+};
+
+/** Function: setPreviousNick
+ * If user has nickname changed, set previous nickname.
+ *
+ * Parameters:
+ * (String) previousNick - the previous nickname
+ */
+Candy.Core.ChatUser.prototype.setPreviousNick = function(previousNick) {
+ this.data.previousNick = previousNick;
+};
+
+/** Function: hasNicknameChanged
+ * Gets the previous nickname if available.
+ *
+ * Returns:
+ * (String) - previous nickname
+ */
+Candy.Core.ChatUser.prototype.getPreviousNick = function() {
+ return this.data.previousNick;
+};
+
+/** Function: getContact
+ * Gets the contact matching this user from our roster
+ *
+ * Returns:
+ * (Candy.Core.Contact) - contact from roster
+ */
+Candy.Core.ChatUser.prototype.getContact = function() {
+ return Candy.Core.getRoster().get(Strophe.getBareJidFromJid(this.data.realJid));
+};
+
+/** Function: setStatus
+ * Set the user's status
+ *
+ * Parameters:
+ * (String) status - the new status
+ */
+Candy.Core.ChatUser.prototype.setStatus = function(status) {
+ this.data.status = status;
+};
+
+/** Function: getStatus
+ * Gets the user's status.
+ *
+ * Returns:
+ * (String) - status
+ */
+Candy.Core.ChatUser.prototype.getStatus = function() {
+ return this.data.status;
};
diff --git a/src/core/contact.js b/src/core/contact.js
new file mode 100644
index 0000000..04c3cf5
--- /dev/null
+++ b/src/core/contact.js
@@ -0,0 +1,152 @@
+/** File: contact.js
+ * Candy - Chats are not dead yet.
+ *
+ * Authors:
+ * - Patrick Stadler <patrick.stadler@gmail.com>
+ * - Michael Weibel <michael.weibel@gmail.com>
+ *
+ * Copyright:
+ * (c) 2011 Amiado Group AG. All rights reserved.
+ * (c) 2012-2014 Patrick Stadler & Michael Weibel. All rights reserved.
+ */
+'use strict';
+
+/* global Candy, Strophe, $ */
+
+/** Class: Candy.Core.Contact
+ * Roster contact
+ */
+Candy.Core.Contact = function(stropheRosterItem) {
+ /** Object: data
+ * Strophe Roster plugin item model containing:
+ * - jid
+ * - name
+ * - subscription
+ * - groups
+ */
+ this.data = stropheRosterItem;
+};
+
+/** Function: getJid
+ * Gets an unescaped user jid
+ *
+ * See:
+ * <Candy.Util.unescapeJid>
+ *
+ * Returns:
+ * (String) - jid
+ */
+Candy.Core.Contact.prototype.getJid = function() {
+ if(this.data.jid) {
+ return Candy.Util.unescapeJid(this.data.jid);
+ }
+ return;
+};
+
+/** Function: getEscapedJid
+ * Escapes the user's jid (node & resource get escaped)
+ *
+ * See:
+ * <Candy.Util.escapeJid>
+ *
+ * Returns:
+ * (String) - escaped jid
+ */
+Candy.Core.Contact.prototype.getEscapedJid = function() {
+ return Candy.Util.escapeJid(this.data.jid);
+};
+
+/** Function: getName
+ * Gets user name
+ *
+ * Returns:
+ * (String) - name
+ */
+Candy.Core.Contact.prototype.getName = function() {
+ if (!this.data.name) {
+ return this.getJid();
+ }
+ return Strophe.unescapeNode(this.data.name);
+};
+
+/** Function: getNick
+ * Gets user name
+ *
+ * Returns:
+ * (String) - name
+ */
+Candy.Core.Contact.prototype.getNick = Candy.Core.Contact.prototype.getName;
+
+/** Function: getSubscription
+ * Gets user subscription
+ *
+ * Returns:
+ * (String) - subscription
+ */
+Candy.Core.Contact.prototype.getSubscription = function() {
+ if (!this.data.subscription) {
+ return 'none';
+ }
+ return this.data.subscription;
+};
+
+/** Function: getGroups
+ * Gets user groups
+ *
+ * Returns:
+ * (Array) - groups
+ */
+Candy.Core.Contact.prototype.getGroups = function() {
+ return this.data.groups;
+};
+
+/** Function: getStatus
+ * Gets user status as an aggregate of all resources
+ *
+ * Returns:
+ * (String) - aggregate status, one of chat|dnd|available|away|xa|unavailable
+ */
+Candy.Core.Contact.prototype.getStatus = function() {
+ var status = 'unavailable',
+ self = this,
+ highestResourcePriority;
+
+ $.each(this.data.resources, function(resource, obj) {
+ var resourcePriority = parseInt(obj.priority, 10);
+
+ if (obj.show === '' || obj.show === null || obj.show === undefined) {
+ // TODO: Submit this as a bugfix to strophejs-plugins' roster plugin
+ obj.show = 'available';
+ }
+
+ if (highestResourcePriority === undefined || highestResourcePriority < resourcePriority) {
+ // This resource is higher priority than the ones we've checked so far, override with this one
+ status = obj.show;
+ highestResourcePriority = resourcePriority;
+ } else if (highestResourcePriority === resourcePriority) {
+ // Two resources with the same priority means we have to weight their status
+ if (self._weightForStatus(status) > self._weightForStatus(obj.show)) {
+ status = obj.show;
+ }
+ }
+ });
+
+ return status;
+};
+
+Candy.Core.Contact.prototype._weightForStatus = function(status) {
+ switch (status) {
+ case 'chat':
+ case 'dnd':
+ return 1;
+ case 'available':
+ case '':
+ return 2;
+ case 'away':
+ return 3;
+ case 'xa':
+ return 4;
+ case 'unavailable':
+ return 5;
+ }
+};
diff --git a/src/core/event.js b/src/core/event.js
index e9cfe26..48e18b0 100644
--- a/src/core/event.js
+++ b/src/core/event.js
@@ -63,7 +63,11 @@ Candy.Core.Event = (function(self, Strophe, $) {
/* falls through */
case Strophe.Status.ATTACHED:
Candy.Core.log('[Connection] Attached');
- Candy.Core.Action.Jabber.Presence();
+ $(Candy).on('candy:core:roster:fetched', function () {
+ Candy.Core.Action.Jabber.Presence();
+ });
+ Candy.Core.Action.Jabber.Roster();
+ Candy.Core.Action.Jabber.EnableCarbons();
Candy.Core.Action.Jabber.Autojoin();
Candy.Core.Action.Jabber.GetIgnoreList();
break;
@@ -94,7 +98,7 @@ Candy.Core.Event = (function(self, Strophe, $) {
break;
default:
- Candy.Core.log('[Connection] What?!');
+ Candy.Core.warn('[Connection] Unknown status received:', status);
break;
}
/** Event: candy:core.chat.connection
@@ -160,6 +164,118 @@ Candy.Core.Event = (function(self, Strophe, $) {
return true;
},
+ /** Function: RosterLoad
+ * Acts on the result of loading roster items from a cache
+ *
+ * Parameters:
+ * (String) items - List of roster items
+ *
+ * Triggers:
+ * candy:core.roster.loaded
+ *
+ * Returns:
+ * (Boolean) - true
+ */
+ RosterLoad: function(items) {
+ self.Jabber._addRosterItems(items);
+
+ /** Event: candy:core.roster.loaded
+ * Notification of the roster having been loaded from cache
+ */
+ $(Candy).triggerHandler('candy:core:roster:loaded', {roster: Candy.Core.getRoster()});
+
+ return true;
+ },
+
+ /** Function: RosterFetch
+ * Acts on the result of a roster fetch
+ *
+ * Parameters:
+ * (String) items - List of roster items
+ *
+ * Triggers:
+ * candy:core.roster.fetched
+ *
+ * Returns:
+ * (Boolean) - true
+ */
+ RosterFetch: function(items) {
+ self.Jabber._addRosterItems(items);
+
+ /** Event: candy:core.roster.fetched
+ * Notification of the roster having been fetched
+ */
+ $(Candy).triggerHandler('candy:core:roster:fetched', {roster: Candy.Core.getRoster()});
+
+ return true;
+ },
+
+ /** Function: RosterPush
+ * Acts on a roster push
+ *
+ * Parameters:
+ * (String) stanza - Raw XML Message
+ *
+ * Triggers:
+ * candy:core.roster.added
+ * candy:core.roster.updated
+ * candy:core.roster.removed
+ *
+ * Returns:
+ * (Boolean) - true
+ */
+ RosterPush: function(items, updatedItem) {
+ if (!updatedItem) {
+ return true;
+ }
+
+ if (updatedItem.subscription === "remove") {
+ var contact = Candy.Core.getRoster().get(updatedItem.jid);
+ Candy.Core.getRoster().remove(updatedItem.jid);
+ /** Event: candy:core.roster.removed
+ * Notification of a roster entry having been removed
+ *
+ * Parameters:
+ * (Candy.Core.Contact) contact - The contact that was removed from the roster
+ */
+ $(Candy).triggerHandler('candy:core:roster:removed', {contact: contact});
+ } else {
+ var user = Candy.Core.getRoster().get(updatedItem.jid);
+ if (!user) {
+ user = self.Jabber._addRosterItem(updatedItem);
+ /** Event: candy:core.roster.added
+ * Notification of a roster entry having been added
+ *
+ * Parameters:
+ * (Candy.Core.Contact) contact - The contact that was added
+ */
+ $(Candy).triggerHandler('candy:core:roster:added', {contact: user});
+ } else {
+ /** Event: candy:core.roster.updated
+ * Notification of a roster entry having been updated
+ *
+ * Parameters:
+ * (Candy.Core.Contact) contact - The contact that was updated
+ */
+ $(Candy).triggerHandler('candy:core:roster:updated', {contact: user});
+ }
+ }
+
+ return true;
+ },
+
+ _addRosterItem: function(item) {
+ var user = new Candy.Core.Contact(item);
+ Candy.Core.getRoster().add(user);
+ return user;
+ },
+
+ _addRosterItems: function(items) {
+ $.each(items, function(i, item) {
+ self.Jabber._addRosterItem(item);
+ });
+ },
+
/** Function: Bookmarks
* Acts on a bookmarks event. When a bookmark has the attribute autojoin set, joins this room.
*
@@ -247,205 +363,142 @@ Candy.Core.Event = (function(self, Strophe, $) {
Candy.Core.log('[Jabber] Message');
msg = $(msg);
- var fromJid = msg.attr('from'),
- type = msg.attr('type') || 'undefined',
- toJid = msg.attr('to');
-
- // Inspect the message type.
- if (type === 'normal' || type === 'undefined') {
- var mediatedInvite = msg.find('invite'),
- directInvite = msg.find('x[xmlns="jabber:x:conference"]');
-
- if(mediatedInvite.length > 0) {
- var passwordNode = msg.find('password'),
- password = null,
- continueNode = mediatedInvite.find('continue'),
- continuedThread = null;
-
- if(passwordNode) {
- password = passwordNode.text();
+ var type = msg.attr('type') || 'normal';
+
+ switch (type) {
+ case 'normal':
+ var invite = self.Jabber._findInvite(msg);
+
+ if (invite) {
+ /** Event: candy:core:chat:invite
+ * Incoming chat invite for a MUC.
+ *
+ * Parameters:
+ * (String) roomJid - The room the invite is to
+ * (String) from - User JID that invite is from text
+ * (String) reason - Reason for invite
+ * (String) password - Password for the room
+ * (String) continuedThread - The thread ID if this is a continuation of a 1-on-1 chat
+ */
+ $(Candy).triggerHandler('candy:core:chat:invite', invite);
}
- if(continueNode) {
- continuedThread = continueNode.attr('thread');
- }
-
- /** Event: candy:core:chat:invite
- * Incoming chat invite for a MUC.
+ /** Event: candy:core:chat:message:normal
+ * Messages with the type attribute of normal or those
+ * that do not have the optional type attribute.
*
* Parameters:
- * (String) roomJid - The room the invite is to
- * (String) from - User JID that invite is from text
- * (String) reason - Reason for invite [default: '']
- * (String) password - Password for the room [default: null]
- * (String) continuedThread - The thread ID if this is a continuation of a 1-on-1 chat [default: null]
+ * (String) type - Type of the message
+ * (Object) message - Message object.
*/
- $(Candy).triggerHandler('candy:core:chat:invite', {
- roomJid: fromJid,
- from: mediatedInvite.attr('from') || 'undefined',
- reason: mediatedInvite.find('reason').html() || '',
- password: password,
- continuedThread: continuedThread
+ $(Candy).triggerHandler('candy:core:chat:message:normal', {
+ type: type,
+ message: msg
});
- }
-
- if(directInvite.length > 0) {
- /** Event: candy:core:chat:invite
- * Incoming chat invite for a MUC.
+ break;
+ case 'headline':
+ // Admin message
+ if(!msg.attr('to')) {
+ /** Event: candy:core.chat.message.admin
+ * Admin message
+ *
+ * Parameters:
+ * (String) type - Type of the message
+ * (String) message - Message text
+ */
+ $(Candy).triggerHandler('candy:core.chat.message.admin', {
+ type: type,
+ message: msg.children('body').text()
+ });
+ // Server Message
+ } else {
+ /** Event: candy:core.chat.message.server
+ * Server message (e.g. subject)
+ *
+ * Parameters:
+ * (String) type - Message type
+ * (String) subject - Subject text
+ * (String) message - Message text
+ */
+ $(Candy).triggerHandler('candy:core.chat.message.server', {
+ type: type,
+ subject: msg.children('subject').text(),
+ message: msg.children('body').text()
+ });
+ }
+ break;
+ case 'groupchat':
+ case 'chat':
+ case 'error':
+ // Room message
+ self.Jabber.Room.Message(msg);
+ break;
+ default:
+ /** Event: candy:core:chat:message:other
+ * Messages with a type other than the ones listed in RFC3921
+ * section 2.1.1. This allows plugins to catch custom message
+ * types.
*
* Parameters:
- * (String) roomJid - The room the invite is to
- * (String) from - User JID that invite is from text
- * (String) reason - Reason for invite [default: '']
- * (String) password - Password for the room [default: null]
- * (String) continuedThread - The thread ID if this is a continuation of a 1-on-1 chat [default: null]
+ * (String) type - Type of the message [default: message]
+ * (Object) message - Message object.
*/
- $(Candy).triggerHandler('candy:core:chat:invite', {
- roomJid: directInvite.attr('jid'),
- from: fromJid,
- reason: directInvite.attr('reason') || '',
- password: directInvite.attr('password'),
- continuedThread: directInvite.attr('thread')
+ // Detect message with type normal or with no type.
+ $(Candy).triggerHandler('candy:core:chat:message:other', {
+ type: type,
+ message: msg
});
- }
-
- /** Event: candy:core:chat:message:normal
- * Messages with the type attribute of normal or those
- * that do not have the optional type attribute.
- *
- * Parameters:
- * (String) type - Type of the message [default: message]
- * (Object) message - Message object.
- */
- // Detect message with type normal or with no type.
- $(Candy).triggerHandler('candy:core:chat:message:normal', {
- type: (type || 'normal'),
- message: msg
- });
-
- return true;
- } else if (type !== 'groupchat' && type !== 'chat' && type !== 'error' && type !== 'headline') {
- /** Event: candy:core:chat:message:other
- * Messages with a type other than the ones listed in RFC3921
- * section 2.1.1. This allows plugins to catch custom message
- * types.
- *
- * Parameters:
- * (String) type - Type of the message [default: message]
- * (Object) message - Message object.
- */
- // Detect message with type normal or with no type.
- $(Candy).triggerHandler('candy:core:chat:message:other', {
- type: type,
- message: msg
- });
- return true;
}
- // Room message
- if(fromJid !== Strophe.getDomainFromJid(fromJid) && (type === 'groupchat' || type === 'chat' || type === 'error')) {
- self.Jabber.Room.Message(msg);
- // Admin message
- } else if(!toJid && fromJid === Strophe.getDomainFromJid(fromJid)) {
- /** 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)) {
- /** 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;
},
- /** Class: Candy.Core.Event.Jabber.Room
- * Room specific events
- */
- Room: {
- /** Function: Leave
- * Leaves a room and cleans up related data and notifies view.
- *
- * Parameters:
- * (String) msg - Raw XML Message
- *
- * Triggers:
- * candy:core.presence.leave using {roomJid, roomName, type, reason, actor, user}
- *
- * Returns:
- * (Boolean) - true
- */
- Leave: function(msg) {
- Candy.Core.log('[Jabber:Room] Leave');
- msg = $(msg);
- var from = Candy.Util.unescapeJid(msg.attr('from')),
- roomJid = Strophe.getBareJidFromJid(from);
-
- // if room is not joined yet, ignore.
- if (!Candy.Core.getRoom(roomJid)) {
- return true;
- }
+ _findInvite: function (msg) {
+ var mediatedInvite = msg.find('invite'),
+ directInvite = msg.find('x[xmlns="jabber:x:conference"]'),
+ invite;
- var roomName = Candy.Core.getRoom(roomJid).getName(),
- item = msg.find('item'),
- type = 'leave',
+ if(mediatedInvite.length > 0) {
+ var passwordNode = msg.find('password'),
+ password,
+ reasonNode = mediatedInvite.find('reason'),
reason,
- actor;
+ continueNode = mediatedInvite.find('continue');
- delete Candy.Core.getRooms()[roomJid];
- // if user gets kicked, role is none and there's a status code 307
- if(item.attr('role') === 'none') {
- var code = msg.find('status').attr('code');
- if(code === '307') {
- type = 'kick';
- } else if(code === '301') {
- type = 'ban';
- }
- reason = item.find('reason').text();
- actor = item.find('actor').attr('jid');
+ if(passwordNode.text() !== '') {
+ password = passwordNode.text();
}
- var user = new Candy.Core.ChatUser(from, Strophe.getResourceFromJid(from), item.attr('affiliation'), item.attr('role'));
+ if(reasonNode.text() !== '') {
+ reason = reasonNode.text();
+ }
- /** 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;
- },
+ invite = {
+ roomJid: msg.attr('from'),
+ from: mediatedInvite.attr('from'),
+ reason: reason,
+ password: password,
+ continuedThread: continueNode.attr('thread')
+ };
+ }
+
+ if(directInvite.length > 0) {
+ invite = {
+ roomJid: directInvite.attr('jid'),
+ from: msg.attr('from'),
+ reason: directInvite.attr('reason'),
+ password: directInvite.attr('password'),
+ continuedThread: directInvite.attr('thread')
+ };
+ }
+ return invite;
+ },
+
+ /** Class: Candy.Core.Event.Jabber.Room
+ * Room specific events
+ */
+ Room: {
/** Function: Disco
* Sets informations to rooms according to the disco info received.
*
@@ -502,22 +555,9 @@ Candy.Core.Event = (function(self, Strophe, $) {
var from = Candy.Util.unescapeJid(msg.attr('from')),
roomJid = Strophe.getBareJidFromJid(from),
presenceType = msg.attr('type'),
- status = msg.find('status'),
- nickAssign = false,
- nickChange = false;
-
- if(status.length) {
- // check if status code indicates a nick assignment or nick change
- for(var i = 0, l = status.length; i < l; i++) {
- var $status = $(status[i]),
- code = $status.attr('code');
- if(code === '303') {
- nickChange = true;
- } else if(code === '210') {
- nickAssign = true;
- }
- }
- }
+ isNewRoom = self.Jabber.Room._msgHasStatusCode(msg, 201),
+ nickAssign = self.Jabber.Room._msgHasStatusCode(msg, 210),
+ nickChange = self.Jabber.Room._msgHasStatusCode(msg, 303);
// Current User joined a room
var room = Candy.Core.getRoom(roomJid);
@@ -526,16 +566,11 @@ Candy.Core.Event = (function(self, Strophe, $) {
room = Candy.Core.getRoom(roomJid);
}
- // Current User left a room
- var currentUser = room.getUser() ? room.getUser() : Candy.Core.getUser();
- if(Strophe.getResourceFromJid(from) === currentUser.getNick() && presenceType === 'unavailable' && nickChange === false) {
- self.Jabber.Room.Leave(msg);
- return true;
- }
-
var roster = room.getRoster(),
+ currentUser = room.getUser() ? room.getUser() : Candy.Core.getUser(),
action, user,
nick,
+ show = msg.find('show'),
item = msg.find('item');
// User joined a room
if(presenceType !== 'unavailable') {
@@ -549,23 +584,31 @@ Candy.Core.Event = (function(self, Strophe, $) {
user.setRole(role);
user.setAffiliation(affiliation);
+ user.setStatus("available");
+
// FIXME: currently role/affilation changes are handled with this action
action = 'join';
} else {
nick = Strophe.getResourceFromJid(from);
- user = new Candy.Core.ChatUser(from, nick, item.attr('affiliation'), item.attr('role'));
+ user = new Candy.Core.ChatUser(from, nick, item.attr('affiliation'), item.attr('role'), item.attr('jid'));
// Room existed but client (myself) is not yet registered
if(room.getUser() === null && (Candy.Core.getUser().getNick() === nick || nickAssign)) {
room.setUser(user);
currentUser = user;
}
+ user.setStatus('available');
roster.add(user);
action = 'join';
}
+
+ if (show.length > 0) {
+ user.setStatus(show.text());
+ }
// User left a room
} else {
user = roster.get(from);
roster.remove(from);
+
if(nickChange) {
// user changed nick
nick = item.attr('nick');
@@ -577,12 +620,18 @@ Candy.Core.Event = (function(self, Strophe, $) {
} else {
action = 'leave';
if(item.attr('role') === 'none') {
- if(msg.find('status').attr('code') === '307') {
+ if(self.Jabber.Room._msgHasStatusCode(msg, 307)) {
action = 'kick';
- } else if(msg.find('status').attr('code') === '301') {
+ } else if(self.Jabber.Room._msgHasStatusCode(msg, 301)) {
action = 'ban';
}
}
+
+ if (Strophe.getResourceFromJid(from) === currentUser.getNick()) {
+ // Current User left a room
+ self.Jabber.Room._selfLeave(msg, from, roomJid, room.getName(), action);
+ return true;
+ }
}
}
/** Event: candy:core.presence.room
@@ -594,17 +643,62 @@ Candy.Core.Event = (function(self, Strophe, $) {
* (Candy.Core.ChatUser) user - User which does the presence update
* (String) action - Action [kick, ban, leave, join]
* (Candy.Core.ChatUser) currentUser - Current local user
+ * (Boolean) isNewRoom - Whether the room is new (has just been created)
*/
$(Candy).triggerHandler('candy:core.presence.room', {
'roomJid': roomJid,
'roomName': room.getName(),
'user': user,
'action': action,
- 'currentUser': currentUser
+ 'currentUser': currentUser,
+ 'isNewRoom': isNewRoom
});
return true;
},
+ _msgHasStatusCode: function (msg, code) {
+ return msg.find('status[code="' + code + '"]').length > 0;
+ },
+
+ _selfLeave: function(msg, from, roomJid, roomName, action) {
+ Candy.Core.log('[Jabber:Room] Leave');
+
+ Candy.Core.removeRoom(roomJid);
+
+ var item = msg.find('item'),
+ reason,
+ actor;
+
+ if(action === 'kick' || action === 'ban') {
+ reason = item.find('reason').text();
+ actor = item.find('actor').attr('jid');
+ }
+
+ var user = new Candy.Core.ChatUser(from, Strophe.getResourceFromJid(from), item.attr('affiliation'), item.attr('role'));
+
+ /** 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': action,
+ 'reason': reason,
+ 'actor': actor,
+ 'user': user
+ });
+ },
+
/** Function: PresenceError
* Acts when a presence of type error has been retrieved.
*
@@ -661,87 +755,116 @@ Candy.Core.Event = (function(self, Strophe, $) {
*/
Message: function(msg) {
Candy.Core.log('[Jabber:Room] Message');
+
+ var carbon = false,
+ partnerJid = Candy.Util.unescapeJid(msg.attr('from'));
+
+ if (msg.children('sent[xmlns="' + Strophe.NS.CARBONS + '"]').length > 0) {
+ carbon = true;
+ msg = $(msg.children('sent').children('forwarded').children('message'));
+ partnerJid = Candy.Util.unescapeJid(msg.attr('to'));
+ }
+
+ if (msg.children('received[xmlns="' + Strophe.NS.CARBONS + '"]').length > 0) {
+ carbon = true;
+ msg = $(msg.children('received').children('forwarded').children('message'));
+ partnerJid = Candy.Util.unescapeJid(msg.attr('from'));
+ }
+
// Room subject
- var roomJid, message, name;
+ var roomJid, roomName, from, message, name, room, sender;
if(msg.children('subject').length > 0 && msg.children('subject').text().length > 0 && msg.attr('type') === 'groupchat') {
- roomJid = Candy.Util.unescapeJid(Strophe.getBareJidFromJid(msg.attr('from')));
- message = { name: Strophe.getNodeFromJid(roomJid), body: msg.children('subject').text(), type: 'subject' };
+ roomJid = Candy.Util.unescapeJid(Strophe.getBareJidFromJid(partnerJid));
+ from = Candy.Util.unescapeJid(Strophe.getBareJidFromJid(msg.attr('from')));
+ roomName = Strophe.getNodeFromJid(roomJid);
+ message = { from: from, name: Strophe.getNodeFromJid(from), body: msg.children('subject').text(), type: 'subject' };
// Error messsage
} else if(msg.attr('type') === 'error') {
var error = msg.children('error');
if(error.children('text').length > 0) {
- roomJid = msg.attr('from');
- message = { type: 'info', body: error.children('text').text() };
+ roomJid = partnerJid;
+ roomName = Strophe.getNodeFromJid(roomJid);
+ message = { from: msg.attr('from'), type: 'info', body: error.children('text').text() };
}
// Chat message
} else if(msg.children('body').length > 0) {
// Private chat message
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.
- isNoConferenceRoomJid = !Candy.Core.getRoom(bareRoomJid);
+ from = Candy.Util.unescapeJid(msg.attr('from'));
+ var barePartner = Strophe.getBareJidFromJid(partnerJid),
+ bareFrom = Strophe.getBareJidFromJid(from),
+ isNoConferenceRoomJid = !Candy.Core.getRoom(barePartner);
+
+ if (isNoConferenceRoomJid) {
+ roomJid = barePartner;
+
+ var partner = Candy.Core.getRoster().get(barePartner);
+ if (partner) {
+ roomName = partner.getName();
+ } else {
+ roomName = Strophe.getNodeFromJid(barePartner);
+ }
- name = isNoConferenceRoomJid ? Strophe.getNodeFromJid(roomJid) : Strophe.getResourceFromJid(roomJid);
- message = { name: name, body: msg.children('body').text(), type: msg.attr('type'), isNoConferenceRoomJid: isNoConferenceRoomJid };
+ if (bareFrom === Candy.Core.getUser().getJid()) {
+ sender = Candy.Core.getUser();
+ } else {
+ sender = Candy.Core.getRoster().get(bareFrom);
+ }
+ if (sender) {
+ name = sender.getName();
+ } else {
+ name = Strophe.getNodeFromJid(from);
+ }
+ } else {
+ roomJid = partnerJid;
+ room = Candy.Core.getRoom(Candy.Util.unescapeJid(Strophe.getBareJidFromJid(from)));
+ sender = room.getRoster().get(from);
+ if (sender) {
+ name = sender.getName();
+ } else {
+ name = Strophe.getResourceFromJid(from);
+ }
+ roomName = name;
+ }
+ message = { from: from, name: name, body: msg.children('body').text(), type: msg.attr('type'), isNoConferenceRoomJid: isNoConferenceRoomJid };
// Multi-user chat message
} else {
- roomJid = Candy.Util.unescapeJid(Strophe.getBareJidFromJid(msg.attr('from')));
- var resource = Strophe.getResourceFromJid(msg.attr('from'));
+ from = Candy.Util.unescapeJid(msg.attr('from'));
+ roomJid = Candy.Util.unescapeJid(Strophe.getBareJidFromJid(partnerJid));
+ var resource = Strophe.getResourceFromJid(partnerJid);
// Message from a user
if(resource) {
- resource = Strophe.unescapeNode(resource);
- message = { name: resource, body: msg.children('body').text(), type: msg.attr('type') };
+ room = Candy.Core.getRoom(roomJid);
+ roomName = room.getName();
+ if (resource === Candy.Core.getUser().getNick()) {
+ sender = Candy.Core.getUser();
+ } else {
+ sender = room.getRoster().get(from);
+ }
+ if (sender) {
+ name = sender.getName();
+ } else {
+ name = Strophe.unescapeNode(resource);
+ }
+ message = { from: roomJid, name: name, 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')]) {
+ if(!Candy.Core.getRooms()[partnerJid]) {
return true;
}
- message = { name: '', body: msg.children('body').text(), type: 'info' };
+ roomName = '';
+ message = { from: roomJid, name: '', body: msg.children('body').text(), type: 'info' };
}
}
var xhtmlChild = msg.children('html[xmlns="' + Strophe.NS.XHTML_IM + '"]');
- if(Candy.View.getOptions().enableXHTML === true && xhtmlChild.length > 0) {
- var xhtmlMessage = xhtmlChild.children('body[xmlns="' + Strophe.NS.XHTML + '"]').first().html();
+ if(xhtmlChild.length > 0) {
+ var xhtmlMessage = $($('<div>').append(xhtmlChild.children('body').first().contents()).html());
message.xhtmlMessage = xhtmlMessage;
}
- // Typing notification
- } else if(msg.children('composing').length > 0 || msg.children('inactive').length > 0 || msg.children('paused').length > 0) {
- roomJid = Candy.Util.unescapeJid(msg.attr('from'));
- name = Strophe.getResourceFromJid(roomJid);
- var chatstate;
- if(msg.children('composing').length > 0) {
- chatstate = 'composing';
- } else if(msg.children('paused').length > 0) {
- chatstate = 'paused';
- } else if(msg.children('inactive').length > 0) {
- chatstate = 'inactive';
- } else if(msg.children('gone').length > 0) {
- chatstate = 'gone';
- }
- /** Event: candy:core.message.chatstate
- * Triggers on any recieved chatstate notification.
- *
- * The resulting message object contains the name of the person, the roomJid, and the indicated chatstate.
- *
- * The following lists explain those parameters:
- *
- * Message Object Parameters:
- * (String) name - User name
- * (String) roomJid - Room jid
- * (String) chatstate - Chatstate being indicated. ("paused", "inactive", "composing", "gone")
- *
- * TODO:
- * Perhaps handle blank "active" as specified by XEP-0085?
- */
- $(Candy).triggerHandler('candy:core.message.chatstate', {
- name: name,
- roomJid: roomJid,
- chatstate: chatstate
- });
- return true;
+
+ self.Jabber.Room._checkForChatStateNotification(msg, roomJid, name);
// Unhandled message
} else {
return true;
@@ -749,8 +872,19 @@ Candy.Core.Event = (function(self, Strophe, $) {
// besides the delayed delivery (XEP-0203), there exists also XEP-0091 which is the legacy delayed delivery.
// the x[xmlns=jabber:x:delay] is the format in XEP-0091.
- var delay = msg.children('delay') ? msg.children('delay') : msg.children('x[xmlns="' + Strophe.NS.DELAY +'"]'),
- timestamp = delay !== undefined ? delay.attr('stamp') : null;
+ var delay = msg.children('delay[xmlns="' + Strophe.NS.DELAY +'"]');
+
+ message.delay = false; // Default delay to being false.
+
+ if (delay.length < 1) {
+ // The jQuery xpath implementation doesn't support the or operator
+ delay = msg.children('x[xmlns="' + Strophe.NS.JABBER_DELAY +'"]');
+ } else {
+ // Add delay to the message object so that we can more easily tell if it's a delayed message or not.
+ message.delay = true;
+ }
+
+ var timestamp = delay.length > 0 ? delay.attr('stamp') : (new Date()).toISOString();
/** Event: candy:core.message
* Triggers on various message events (subject changed, private chat message, multi-user chat message).
@@ -761,7 +895,8 @@ Candy.Core.Event = (function(self, Strophe, $) {
* The following lists explain those parameters:
*
* Message Object Parameters:
- * (String) name - Room name
+ * (String) from - The unmodified JID that the stanza came from
+ * (String) name - Sender name
* (String) body - Message text
* (String) type - Message type ([normal, chat, groupchat])
* or 'info' which is used internally for displaying informational messages
@@ -769,9 +904,12 @@ Candy.Core.Event = (function(self, Strophe, $) {
* this user (not via the room) then the username is the node
* and not the resource.
* This flag tells if this is the case.
+ * (Boolean) delay - If there is a value for the delay element on a message it is a delayed message.
+ * This flag tells if this is the case.
*
* Parameters:
- * (String) roomJid - Room jid
+ * (String) roomJid - Room jid. For one-on-one messages, this is sanitized to the bare JID for indexing purposes.
+ * (String) roomName - Name of the contact
* (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}
@@ -779,16 +917,45 @@ Candy.Core.Event = (function(self, Strophe, $) {
* - 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
+ * (Boolean) carbon - Indication of wether or not the message was a carbon
+ * (String) stanza - The raw XML stanza
*
* TODO:
* Streamline those events sent and rename the parameters.
*/
$(Candy).triggerHandler('candy:core.message', {
roomJid: roomJid,
+ roomName: roomName,
message: message,
- timestamp: timestamp
+ timestamp: timestamp,
+ carbon: carbon,
+ stanza: msg
});
return true;
+ },
+
+ _checkForChatStateNotification: function (msg, roomJid, name) {
+ var chatStateElements = msg.children('*[xmlns="http://jabber.org/protocol/chatstates"]');
+ if (chatStateElements.length > 0) {
+ /** Event: candy:core:message:chatstate
+ * Triggers on any recieved chatstate notification.
+ *
+ * The resulting message object contains the name of the person, the roomJid, and the indicated chatstate.
+ *
+ * The following lists explain those parameters:
+ *
+ * Message Object Parameters:
+ * (String) name - User name
+ * (String) roomJid - Room jid
+ * (String) chatstate - Chatstate being indicated. ("active", "composing", "paused", "inactive", "gone")
+ *
+ */
+ $(Candy).triggerHandler('candy:core:message:chatstate', {
+ name: name,
+ roomJid: roomJid,
+ chatstate: chatStateElements[0].tagName
+ });
+ }
}
}
};
diff --git a/src/util.js b/src/util.js
index 69010c6..dfcf613 100644
--- a/src/util.js
+++ b/src/util.js
@@ -35,7 +35,7 @@ Candy.Util = (function(self, $){
};
/** Function: escapeJid
- * Escapes a jid (node & resource get escaped)
+ * Escapes a jid
*
* See:
* XEP-0106
@@ -243,7 +243,14 @@ Candy.Util = (function(self, $){
return undefined;
}
- var date = self.iso8601toDate(dateTime);
+ // See if we were passed a Date object
+ var date;
+ if (dateTime.toDateString) {
+ date = dateTime;
+ } else {
+ date = self.iso8601toDate(dateTime);
+ }
+
if(date.toDateString() === new Date().toDateString()) {
return date.format($.i18n._('timeFormat'));
} else {
@@ -351,10 +358,34 @@ Candy.Util = (function(self, $){
return ie;
};
+ /** Function: isMobile
+ * Checks to see if we're on a mobile device.
+ */
+ self.isMobile = function() {
+ var check = false;
+ (function(a){ if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od|ad)|android|ipad|playbook|silk|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) { check = true; } })(navigator.userAgent || navigator.vendor || window.opera);
+ return check;
+ };
+
/** Class: Candy.Util.Parser
* Parser for emoticons, links and also supports escaping.
*/
self.Parser = {
+ /** Function: jid
+ * Parse a JID into an object with each element
+ *
+ * Parameters:
+ * (String) jid - The string representation of a JID
+ */
+ jid: function (jid) {
+ var r = /^(([^@]+)@)?([^\/]+)(\/(.*))?$/i,
+ a = jid.match(r);
+
+ if (!a) { throw "not a valid jid (" + jid + ")"; }
+
+ return {node: a[2], domain: a[3], resource: a[4]};
+ },
+
/** PrivateVariable: _emoticonPath
* Path to emoticons.
*
@@ -472,13 +503,14 @@ Candy.Util = (function(self, $){
emotify: function(text) {
var i;
for(i = this.emoticons.length-1; i >= 0; i--) {
- text = text.replace(this.emoticons[i].regex, '$2<img class="emoticon" alt="$1" src="' + this._emoticonPath + this.emoticons[i].image + '" />$3');
+ text = text.replace(this.emoticons[i].regex, '$2<img class="emoticon" alt="$1" title="$1" src="' + this._emoticonPath + this.emoticons[i].image + '" />$3');
}
return text;
},
/** Function: linkify
* Replaces URLs with a HTML-link.
+ * big regex adapted from https://gist.github.com/dperini/729294 - Diego Perini, MIT license.
*
* Parameters:
* (String) text - Text to linkify
@@ -488,7 +520,9 @@ Candy.Util = (function(self, $){
*/
linkify: function(text) {
text = text.replace(/(^|[^\/])(www\.[^\.]+\.[\S]+(\b|$))/gi, '$1http://$2');
- return text.replace(/(\b(https?|ftp|file):\/\/[\-A-Z0-9+&@#\/%?=~_|!:,.;]*[\-A-Z0-9+&@#\/%=~_|])/ig, '<a href="$1" target="_blank">$1</a>');
+ return text.replace(/(\b(?:(?:https?|ftp|file):\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:1\d\d|2[01]\d|22[0-3]|[1-9]\d?)(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/\S*)?)/gi, function(matched, url) {
+ return '<a href="' + url + '" target="_blank">' + self.crop(url, Candy.View.getOptions().crop.message.url) + '</a>';
+ });
},
/** Function: escape
@@ -598,8 +632,7 @@ Candy.Util = (function(self, $){
el.append(self.createHtml(elem.childNodes[i], maxLength, currentLength));
}
} catch(e) { // invalid elements
- Candy.Core.log("[Util:createHtml] Error while parsing XHTML:");
- Candy.Core.log(e);
+ Candy.Core.warn("[Util:createHtml] Error while parsing XHTML:", e);
el = Strophe.xmlTextNode('');
}
} else {
diff --git a/src/view.js b/src/view.js
index f438670..ade660d 100644
--- a/src/view.js
+++ b/src/view.js
@@ -31,7 +31,7 @@ Candy.View = (function(self, $) {
* (String) language - language to use
* (String) assets - path to assets (res) directory (with trailing slash)
* (Object) messages - limit: clean up message pane when n is reached / remove: remove n messages after limit has been reached
- * (Object) crop - crop if longer than defined: message.nickname=15, message.body=1000, roster.nickname=15
+ * (Object) crop - crop if longer than defined: message.nickname=15, message.body=1000, message.url=undefined (not cropped), roster.nickname=15
* (Bool) enableXHTML - [default: false] enables XHTML messages sending & displaying
*/
_options = {
@@ -39,7 +39,7 @@ Candy.View = (function(self, $) {
assets: 'res/',
messages: { limit: 2000, remove: 500 },
crop: {
- message: { nickname: 15, body: 1000 },
+ message: { nickname: 15, body: 1000, url: undefined },
roster: { nickname: 15 }
},
enableXHTML: false
@@ -137,8 +137,7 @@ Candy.View = (function(self, $) {
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
+ toolbar: Candy.View.Template.Chat.toolbar
}));
// ... and let the elements dance.
diff --git a/src/view/observer.js b/src/view/observer.js
index 5ee456a..a2afcbc 100644
--- a/src/view/observer.js
+++ b/src/view/observer.js
@@ -215,6 +215,14 @@ 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 {
+ // Presence for a one-on-one chat
+ var bareJid = Strophe.getBareJidFromJid(args.from),
+ room = Candy.View.Pane.Chat.rooms[bareJid];
+ if(!room) {
+ return false;
+ }
+ room.targetJid = bareJid; // Reset the room's target JID
}
},
@@ -275,18 +283,28 @@ Candy.View.Observer = (function(self, $) {
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.init(args.roomJid, args.roomName);
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);
+ Candy.View.Pane.Chat.infoMessage(args.roomJid, null, 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.PrivateRoom.open(args.roomJid, args.roomName, false, args.message.isNoConferenceRoomJid);
+ }
+ var room = Candy.View.Pane.Chat.rooms[args.roomJid];
+ if (room.targetJid === args.roomJid && !args.carbon) {
+ // No messages yet received. Lock the room to this resource.
+ room.targetJid = args.message.from;
+ } else if (room.targetJid === args.message.from) {
+ // We're already locked to the correct resource.
+ } else {
+ // Message received from alternative resource. Release the resource lock.
+ room.targetJid = args.roomJid;
}
- Candy.View.Pane.Message.show(args.roomJid, args.message.name, args.message.body, args.message.xhtmlMessage, args.timestamp);
+ Candy.View.Pane.Message.show(args.roomJid, args.message.name, args.message.body, args.message.xhtmlMessage, args.timestamp, args.message.from, args.carbon, args.stanza);
}
};
diff --git a/src/view/pane.js b/src/view/pane.js
deleted file mode 100644
index 007d816..0000000
--- a/src/view/pane.js
+++ /dev/null
@@ -1,2094 +0,0 @@
-/** File: pane.js
- * Candy - Chats are not dead yet.
- *
- * Authors:
- * - Patrick Stadler <patrick.stadler@gmail.com>
- * - Michael Weibel <michael.weibel@gmail.com>
- *
- * Copyright:
- * (c) 2011 Amiado Group AG. All rights reserved.
- * (c) 2012-2014 Patrick Stadler & Michael Weibel. All rights reserved.
- */
-'use strict';
-
-/* global Candy, document, Mustache, Strophe, Audio, jQuery */
-
-/** Class: Candy.View.Pane
- * Candy view pane handles everything regarding DOM updates etc.
- *
- * Parameters:
- * (Candy.View.Pane) self - itself
- * (jQuery) $ - jQuery
- */
-Candy.View.Pane = (function(self, $) {
-
- /** Class: Candy.View.Pane.Window
- * Window related view updates
- */
- self.Window = {
- /** PrivateVariable: _hasFocus
- * Window has focus
- */
- _hasFocus: true,
- /** PrivateVariable: _plainTitle
- * Document title
- */
- _plainTitle: document.title,
- /** PrivateVariable: _unreadMessagesCount
- * Unread messages count
- */
- _unreadMessagesCount: 0,
-
- /** Variable: autoscroll
- * Boolean whether autoscroll is enabled
- */
- autoscroll: true,
-
- /** Function: hasFocus
- * Checks if window has focus
- *
- * Returns:
- * (Boolean)
- */
- hasFocus: function() {
- return self.Window._hasFocus;
- },
-
- /** Function: increaseUnreadMessages
- * Increases unread message count in window title by one.
- */
- increaseUnreadMessages: function() {
- self.Window.renderUnreadMessages(++self.Window._unreadMessagesCount);
- },
-
- /** Function: reduceUnreadMessages
- * Reduce unread message count in window title by `num`.
- *
- * Parameters:
- * (Integer) num - Unread message count will be reduced by this value
- */
- reduceUnreadMessages: function(num) {
- self.Window._unreadMessagesCount -= num;
- if(self.Window._unreadMessagesCount <= 0) {
- self.Window.clearUnreadMessages();
- } else {
- self.Window.renderUnreadMessages(self.Window._unreadMessagesCount);
- }
- },
-
- /** Function: clearUnreadMessages
- * Clear unread message count in window title.
- */
- clearUnreadMessages: function() {
- self.Window._unreadMessagesCount = 0;
- document.title = self.Window._plainTitle;
- },
-
- /** Function: renderUnreadMessages
- * Update window title to show message count.
- *
- * Parameters:
- * (Integer) count - Number of unread messages to show in window title
- */
- renderUnreadMessages: function(count) {
- document.title = Candy.View.Template.Window.unreadmessages.replace('{{count}}', count).replace('{{title}}', self.Window._plainTitle);
- },
-
- /** Function: onFocus
- * Window focus event handler.
- */
- onFocus: function() {
- self.Window._hasFocus = true;
- if (Candy.View.getCurrent().roomJid) {
- self.Room.setFocusToForm(Candy.View.getCurrent().roomJid);
- self.Chat.clearUnreadMessages(Candy.View.getCurrent().roomJid);
- }
- },
-
- /** Function: onBlur
- * Window blur event handler.
- */
- onBlur: function() {
- self.Window._hasFocus = false;
- }
- };
-
- /** Class: Candy.View.Pane.Chat
- * Chat-View related view updates
- */
- self.Chat = {
- /** Variable: rooms
- * Contains opened room elements
- */
- rooms: [],
-
- /** Function: addTab
- * Add a tab to the chat pane.
- *
- * Parameters:
- * (String) roomJid - JID of room
- * (String) roomName - Tab label
- * (String) roomType - Type of room: `groupchat` or `chat`
- */
- addTab: function(roomJid, roomName, roomType) {
- var roomId = Candy.Util.jidToId(roomJid),
- html = Mustache.to_html(Candy.View.Template.Chat.tab, {
- roomJid: roomJid,
- roomId: roomId,
- name: roomName || Strophe.getNodeFromJid(roomJid),
- privateUserChat: function() {return roomType === 'chat';},
- roomType: roomType
- }),
- tab = $(html).appendTo('#chat-tabs');
-
- tab.click(self.Chat.tabClick);
- // TODO: maybe we find a better way to get the close element.
- $('a.close', tab).click(self.Chat.tabClose);
-
- self.Chat.fitTabs();
- },
-
- /** Function: getTab
- * Get tab by JID.
- *
- * Parameters:
- * (String) roomJid - JID of room
- *
- * Returns:
- * (jQuery object) - Tab element
- */
- getTab: function(roomJid) {
- return $('#chat-tabs').children('li[data-roomjid="' + roomJid + '"]');
- },
-
- /** Function: removeTab
- * Remove tab element.
- *
- * Parameters:
- * (String) roomJid - JID of room
- */
- removeTab: function(roomJid) {
- self.Chat.getTab(roomJid).remove();
- self.Chat.fitTabs();
- },
-
- /** Function: setActiveTab
- * Set the active tab.
- *
- * Add CSS classname `active` to the choosen tab and remove `active` from all other.
- *
- * Parameters:
- * (String) roomJid - JID of room
- */
- setActiveTab: function(roomJid) {
- $('#chat-tabs').children().each(function() {
- var tab = $(this);
- if(tab.attr('data-roomjid') === roomJid) {
- tab.addClass('active');
- } else {
- tab.removeClass('active');
- }
- });
- },
-
- /** Function: increaseUnreadMessages
- * Increase unread message count in a tab by one.
- *
- * Parameters:
- * (String) roomJid - JID of room
- *
- * Uses:
- * - <Window.increaseUnreadMessages>
- */
- increaseUnreadMessages: function(roomJid) {
- var unreadElem = this.getTab(roomJid).find('.unread');
- unreadElem.show().text(unreadElem.text() !== '' ? parseInt(unreadElem.text(), 10) + 1 : 1);
- // only increase window unread messages in private chats
- if (self.Chat.rooms[roomJid].type === 'chat') {
- self.Window.increaseUnreadMessages();
- }
- },
-
- /** Function: clearUnreadMessages
- * Clear unread message count in a tab.
- *
- * Parameters:
- * (String) roomJid - JID of room
- *
- * Uses:
- * - <Window.reduceUnreadMessages>
- */
- clearUnreadMessages: function(roomJid) {
- var unreadElem = self.Chat.getTab(roomJid).find('.unread');
- self.Window.reduceUnreadMessages(unreadElem.text());
- unreadElem.hide().text('');
- },
-
- /** Function: tabClick
- * Tab click event: show the room associated with the tab and stops the event from doing the default.
- */
- tabClick: function(e) {
- // remember scroll position of current room
- var currentRoomJid = Candy.View.getCurrent().roomJid;
- self.Chat.rooms[currentRoomJid].scrollPosition = self.Room.getPane(currentRoomJid, '.message-pane-wrapper').scrollTop();
-
- self.Room.show($(this).attr('data-roomjid'));
- e.preventDefault();
- },
-
- /** Function: tabClose
- * Tab close (click) event: Leave the room (groupchat) or simply close the tab (chat).
- *
- * Parameters:
- * (DOMEvent) e - Event triggered
- *
- * Returns:
- * (Boolean) - false, this will stop the event from bubbling
- */
- tabClose: function() {
- var roomJid = $(this).parent().attr('data-roomjid');
- // close private user tab
- if(self.Chat.rooms[roomJid].type === 'chat') {
- self.Room.close(roomJid);
- // close multi-user room tab
- } else {
- Candy.Core.Action.Jabber.Room.Leave(roomJid);
- }
- return false;
- },
-
- /** Function: allTabsClosed
- * All tabs closed event: Disconnect from service. Hide sound control.
- *
- * TODO: Handle window close
- *
- * Returns:
- * (Boolean) - false, this will stop the event from bubbling
- */
- allTabsClosed: function() {
- Candy.Core.disconnect();
- self.Chat.Toolbar.hide();
- return;
- },
-
- /** Function: fitTabs
- * Fit tab size according to window size
- */
- fitTabs: function() {
- var availableWidth = $('#chat-tabs').innerWidth(),
- tabsWidth = 0,
- tabs = $('#chat-tabs').children();
- tabs.each(function() {
- tabsWidth += $(this).css({width: 'auto', overflow: 'visible'}).outerWidth(true);
- });
- if(tabsWidth > availableWidth) {
- // tabs.[outer]Width() measures the first element in `tabs`. It's no very readable but nearly two times faster than using :first
- var tabDiffToRealWidth = tabs.outerWidth(true) - tabs.width(),
- tabWidth = Math.floor((availableWidth) / tabs.length) - tabDiffToRealWidth;
- tabs.css({width: tabWidth, overflow: 'hidden'});
- }
- },
-
- /** Function: 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?
- var html = Mustache.to_html(Candy.View.Template.Chat.adminMessage, {
- subject: subject,
- message: message,
- sender: $.i18n._('administratorMessageSubject'),
- time: Candy.Util.localizedTime(new Date().toGMTString())
- });
- $('#chat-rooms').children().each(function() {
- self.Room.appendToMessagePane($(this).attr('data-roomjid'), html);
- });
- self.Room.scrollToBottom(Candy.View.getCurrent().roomJid);
-
- /** Event: candy:view.chat.admin-message
- * After admin message display
- *
- * Parameters:
- * (String) presetJid - Preset user JID
- */
- $(Candy).triggerHandler('candy:view.chat.admin-message', {
- 'subject' : subject,
- 'message' : message
- });
- }
- },
-
- /** Function: infoMessage
- * Display info message. This is a wrapper for <onInfoMessage> to be able to disable certain info messages.
- *
- * Parameters:
- * (String) roomJid - Room JID
- * (String) subject - Subject
- * (String) message - Message
- */
- infoMessage: function(roomJid, subject, message) {
- self.Chat.onInfoMessage(roomJid, subject, message);
- },
-
- /** Function: onInfoMessage
- * Display info message. Used by <infoMessage> and several other functions which do not wish that their info message
- * can be disabled (such as kick/ban message or leave/join message in private chats).
- *
- * Parameters:
- * (String) roomJid - Room JID
- * (String) subject - Subject
- * (String) message - Message
- */
- onInfoMessage: function(roomJid, subject, message) {
- if(Candy.View.getCurrent().roomJid) { // Simply dismiss info message if no room joined so far. TODO: maybe we should show those messages on a dedicated pane?
- var html = Mustache.to_html(Candy.View.Template.Chat.infoMessage, {
- subject: subject,
- message: $.i18n._(message),
- time: Candy.Util.localizedTime(new Date().toGMTString())
- });
- self.Room.appendToMessagePane(roomJid, html);
- if (Candy.View.getCurrent().roomJid === roomJid) {
- self.Room.scrollToBottom(Candy.View.getCurrent().roomJid);
- }
- }
- },
-
- /** Class: Candy.View.Pane.Toolbar
- * Chat toolbar for things like emoticons toolbar, room management etc.
- */
- Toolbar: {
- _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.
- */
- show: function() {
- $('#chat-toolbar').show();
- },
-
- /** Function: hide
- * Hide toolbar.
- */
- hide: function() {
- $('#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).
- */
- playSound: function() {
- self.Chat.Toolbar.onPlaySound();
- },
-
- /** Function: onPlaySound
- * 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() {
- try {
- if(self.Chat.Toolbar._supportsNativeAudio) {
- new Audio(Candy.View.getOptions().assets + 'notify.mp3').play();
- } else {
- var chatSoundPlayer = document.getElementById('chat-sound-player');
- chatSoundPlayer.SetVariable('method:stop', '');
- chatSoundPlayer.SetVariable('method:play', '');
- }
- } catch (e) {}
- },
-
- /** Function: onSoundControlClick
- * Sound control click event handler.
- *
- * Toggle sound (overwrite `playSound()`) and handle cookies.
- */
- onSoundControlClick: function() {
- var control = $('#chat-sound-control');
- if(control.hasClass('checked')) {
- self.Chat.Toolbar.playSound = function() {};
- Candy.Util.setCookie('candy-nosound', '1', 365);
- } else {
- self.Chat.Toolbar.playSound = function() {
- self.Chat.Toolbar.onPlaySound();
- };
- Candy.Util.deleteCookie('candy-nosound');
- }
- control.toggleClass('checked');
- },
-
- /** Function: onAutoscrollControlClick
- * Autoscroll control event handler.
- *
- * Toggle autoscroll
- */
- onAutoscrollControlClick: function() {
- var control = $('#chat-autoscroll-control');
- if(control.hasClass('checked')) {
- self.Room.scrollToBottom = function(roomJid) {
- self.Room.onScrollToStoredPosition(roomJid);
- };
- self.Window.autoscroll = false;
- } else {
- self.Room.scrollToBottom = function(roomJid) {
- self.Room.onScrollToBottom(roomJid);
- };
- self.Room.scrollToBottom(Candy.View.getCurrent().roomJid);
- self.Window.autoscroll = true;
- }
- control.toggleClass('checked');
- },
-
- /** Function: onStatusMessageControlClick
- * Status message control event handler.
- *
- * Toggle status message
- */
- onStatusMessageControlClick: function() {
- var control = $('#chat-statusmessage-control');
- if(control.hasClass('checked')) {
- self.Chat.infoMessage = function() {};
- Candy.Util.setCookie('candy-nostatusmessages', '1', 365);
- } else {
- self.Chat.infoMessage = function(roomJid, subject, message) {
- self.Chat.onInfoMessage(roomJid, subject, message);
- };
- Candy.Util.deleteCookie('candy-nostatusmessages');
- }
- control.toggleClass('checked');
- },
-
- /** Function: updateUserCount
- * Update usercount element with count.
- *
- * Parameters:
- * (Integer) count - Current usercount
- */
- updateUsercount: function(count) {
- $('#chat-usercount').text(count);
- }
- },
-
- /** Class: Candy.View.Pane.Modal
- * Modal window
- */
- Modal: {
- /** Function: show
- * Display modal window
- *
- * Parameters:
- * (String) html - HTML code to put into the modal window
- * (Boolean) showCloseControl - set to true if a close button should be displayed [default false]
- * (Boolean) showSpinner - set to true if a loading spinner should be shown [default false]
- */
- show: function(html, showCloseControl, showSpinner) {
- if(showCloseControl) {
- self.Chat.Modal.showCloseControl();
- } else {
- self.Chat.Modal.hideCloseControl();
- }
- if(showSpinner) {
- self.Chat.Modal.showSpinner();
- } else {
- self.Chat.Modal.hideSpinner();
- }
- $('#chat-modal').stop(false, true);
- $('#chat-modal-body').html(html);
- $('#chat-modal').fadeIn('fast');
- $('#chat-modal-overlay').show();
- },
-
- /** Function: hide
- * Hide modal window
- *
- * Parameters:
- * (Function) callback - Calls the specified function after modal window has been hidden.
- */
- hide: function(callback) {
- $('#chat-modal').fadeOut('fast', function() {
- $('#chat-modal-body').text('');
- $('#chat-modal-overlay').hide();
- });
- // restore initial esc handling
- $(document).keydown(function(e) {
- if(e.which === 27) {
- e.preventDefault();
- }
- });
- if (callback) {
- callback();
- }
- },
-
- /** Function: showSpinner
- * Show loading spinner
- */
- showSpinner: function() {
- $('#chat-modal-spinner').show();
- },
-
- /** Function: hideSpinner
- * Hide loading spinner
- */
- hideSpinner: function() {
- $('#chat-modal-spinner').hide();
- },
-
- /** Function: showCloseControl
- * Show a close button
- */
- showCloseControl: function() {
- $('#admin-message-cancel').show().click(function(e) {
- self.Chat.Modal.hide();
- // some strange behaviour on IE7 (and maybe other browsers) triggers onWindowUnload when clicking on the close button.
- // prevent this.
- e.preventDefault();
- });
-
- // enable esc to close modal
- $(document).keydown(function(e) {
- if(e.which === 27) {
- self.Chat.Modal.hide();
- e.preventDefault();
- }
- });
- },
-
- /** Function: hideCloseControl
- * Hide the close button
- */
- hideCloseControl: function() {
- $('#admin-message-cancel').hide().click(function() {});
- },
-
- /** Function: showLoginForm
- * Show the login form modal
- *
- * Parameters:
- * (String) message - optional message to display above the form
- * (String) presetJid - optional user jid. if set, the user will only be prompted for password.
- */
- showLoginForm: function(message, presetJid) {
- self.Chat.Modal.show((message ? message : '') + Mustache.to_html(Candy.View.Template.Login.form, {
- _labelNickname: $.i18n._('labelNickname'),
- _labelUsername: $.i18n._('labelUsername'),
- _labelPassword: $.i18n._('labelPassword'),
- _loginSubmit: $.i18n._('loginSubmit'),
- displayPassword: !Candy.Core.isAnonymousConnection(),
- displayUsername: !presetJid,
- displayNickname: Candy.Core.isAnonymousConnection(),
- presetJid: presetJid ? presetJid : false
- }));
- $('#login-form').children(':input:first').focus();
-
- // register submit handler
- $('#login-form').submit(function() {
- var username = $('#username').val(),
- password = $('#password').val();
-
- if (!Candy.Core.isAnonymousConnection()) {
- // guess the input and create a jid out of it
- var jid = Candy.Core.getUser() && username.indexOf("@") < 0 ?
- username + '@' + Strophe.getDomainFromJid(Candy.Core.getUser().getJid()) : username;
-
- if(jid.indexOf("@") < 0 && !Candy.Core.getUser()) {
- Candy.View.Pane.Chat.Modal.showLoginForm($.i18n._('loginInvalid'));
- } else {
- //Candy.View.Pane.Chat.Modal.hide();
- Candy.Core.connect(jid, password);
- }
- } else { // anonymous login
- Candy.Core.connect(presetJid, null, username);
- }
- return false;
- });
- },
-
- /** Function: showEnterPasswordForm
- * Shows a form for entering room password
- *
- * Parameters:
- * (String) roomJid - Room jid to join
- * (String) roomName - Room name
- * (String) message - [optional] Message to show as the label
- */
- showEnterPasswordForm: function(roomJid, roomName, message) {
- self.Chat.Modal.show(Mustache.to_html(Candy.View.Template.PresenceError.enterPasswordForm, {
- roomName: roomName,
- _labelPassword: $.i18n._('labelPassword'),
- _label: (message ? message : $.i18n._('enterRoomPassword', [roomName])),
- _joinSubmit: $.i18n._('enterRoomPasswordSubmit')
- }), true);
- $('#password').focus();
-
- // register submit handler
- $('#enter-password-form').submit(function() {
- var password = $('#password').val();
-
- self.Chat.Modal.hide(function() {
- Candy.Core.Action.Jabber.Room.Join(roomJid, password);
- });
- return false;
- });
- },
-
- /** Function: showNicknameConflictForm
- * Shows a form indicating that the nickname is already taken and
- * for chosing a new nickname
- *
- * Parameters:
- * (String) roomJid - Room jid to join
- */
- showNicknameConflictForm: function(roomJid) {
- self.Chat.Modal.show(Mustache.to_html(Candy.View.Template.PresenceError.nicknameConflictForm, {
- _labelNickname: $.i18n._('labelNickname'),
- _label: $.i18n._('nicknameConflict'),
- _loginSubmit: $.i18n._('loginSubmit')
- }));
- $('#nickname').focus();
-
- // register submit handler
- $('#nickname-conflict-form').submit(function() {
- var nickname = $('#nickname').val();
-
- self.Chat.Modal.hide(function() {
- Candy.Core.getUser().data.nick = nickname;
- Candy.Core.Action.Jabber.Room.Join(roomJid);
- });
- return false;
- });
- },
-
- /** Function: showError
- * Show modal containing error message
- *
- * Parameters:
- * (String) message - key of translation to display
- * (Array) replacements - array containing replacements for translation (%s)
- */
- showError: function(message, replacements) {
- self.Chat.Modal.show(Mustache.to_html(Candy.View.Template.PresenceError.displayError, {
- _error: $.i18n._(message, replacements)
- }), true);
- }
- },
-
- /** Class: Candy.View.Pane.Tooltip
- * Class to display tooltips over specific elements
- */
- Tooltip: {
- /** Function: show
- * Show a tooltip on event.currentTarget with content specified or content within the target's attribute data-tooltip.
- *
- * On mouseleave on the target, hide the tooltip.
- *
- * Parameters:
- * (Event) event - Triggered event
- * (String) content - Content to display [optional]
- */
- show: function(event, content) {
- var tooltip = $('#tooltip'),
- target = $(event.currentTarget);
-
- if(!content) {
- content = target.attr('data-tooltip');
- }
-
- if(tooltip.length === 0) {
- var html = Mustache.to_html(Candy.View.Template.Chat.tooltip);
- $('#chat-pane').append(html);
- tooltip = $('#tooltip');
- }
-
- $('#context-menu').hide();
-
- tooltip.stop(false, true);
- tooltip.children('div').html(content);
-
- var pos = target.offset(),
- posLeft = Candy.Util.getPosLeftAccordingToWindowBounds(tooltip, pos.left),
- posTop = Candy.Util.getPosTopAccordingToWindowBounds(tooltip, pos.top);
-
- tooltip
- .css({'left': posLeft.px, 'top': posTop.px})
- .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});});
- });
- }
- },
-
- /** Class: Candy.View.Pane.Context
- * Context menu for actions and settings
- */
- Context: {
- /** Function: init
- * Initialize context menu and setup mouseleave handler.
- */
- init: function() {
- if ($('#context-menu').length === 0) {
- var html = Mustache.to_html(Candy.View.Template.Chat.Context.menu);
- $('#chat-pane').append(html);
- $('#context-menu').mouseleave(function() {
- $(this).fadeOut('fast');
- });
- }
- },
-
- /** Function: show
- * Show context menu (positions it according to the window height/width)
- *
- * 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
- *
- * Triggers:
- * candy:view.roster.after-context-menu using {roomJid, user, elements}
- */
- show: function(elem, roomJid, user) {
- elem = $(elem);
- var roomId = self.Chat.rooms[roomJid].id,
- menu = $('#context-menu'),
- links = $('ul li', menu);
-
- $('#tooltip').hide();
-
- // add specific context-user class if a user is available (when context menu should be opened next to a user)
- if(!user) {
- user = Candy.Core.getUser();
- }
-
- links.remove();
-
- var menulinks = this.getMenuLinks(roomJid, user, elem),
- id,
- clickHandler = function(roomJid, user) {
- return function(event) {
- event.data.callback(event, roomJid, user);
- $('#context-menu').hide();
- };
- };
-
- for(id in menulinks) {
- if(menulinks.hasOwnProperty(id)) {
- var link = menulinks[id],
- html = Mustache.to_html(Candy.View.Template.Chat.Context.menulinks, {
- 'roomId' : roomId,
- 'class' : link['class'],
- 'id' : id,
- 'label' : link.label
- });
- $('ul', menu).append(html);
- $('#context-menu-' + id).bind('click', link, clickHandler(roomJid, user));
- }
- }
- // if `id` is set the menu is not empty
- if(id) {
- var pos = elem.offset(),
- posLeft = Candy.Util.getPosLeftAccordingToWindowBounds(menu, pos.left),
- posTop = Candy.Util.getPosTopAccordingToWindowBounds(menu, pos.top);
-
- menu
- .css({'left': posLeft.px, 'top': posTop.px})
- .removeClass('left-top left-bottom right-top right-bottom')
- .addClass(posLeft.backgroundPositionAlignment + '-' + posTop.backgroundPositionAlignment)
- .fadeIn('fast');
-
- /** 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', {
- 'roomJid' : roomJid,
- 'user' : user,
- 'element': menu
- });
-
- return true;
- }
- },
-
- /** Function: getMenuLinks
- * 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, id;
-
- var evtData = {
- 'roomJid' : roomJid,
- 'user' : user,
- 'elem': elem,
- 'menulinks': this.initialMenuLinks(elem)
- };
-
- /** 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)) {
- delete menulinks[id];
- }
- }
- return menulinks;
- },
-
- /** Function: initialMenuLinks
- * Returns initial menulinks. The following are initial:
- *
- * - Private Chat
- * - Ignore
- * - Unignore
- * - Kick
- * - Ban
- * - Change Subject
- *
- * Returns:
- * (Object) - object containing those menulinks
- */
- initialMenuLinks: function() {
- return {
- 'private': {
- requiredPermission: function(user, me) {
- return me.getNick() !== user.getNick() && Candy.Core.getRoom(Candy.View.getCurrent().roomJid) && !Candy.Core.getUser().isInPrivacyList('ignore', user.getJid());
- },
- 'class' : 'private',
- 'label' : $.i18n._('privateActionLabel'),
- 'callback' : function(e, roomJid, user) {
- $('#user-' + Candy.Util.jidToId(roomJid) + '-' + Candy.Util.jidToId(user.getJid())).click();
- }
- },
- 'ignore': {
- requiredPermission: function(user, me) {
- return me.getNick() !== user.getNick() && !Candy.Core.getUser().isInPrivacyList('ignore', user.getJid());
- },
- 'class' : 'ignore',
- 'label' : $.i18n._('ignoreActionLabel'),
- 'callback' : function(e, roomJid, user) {
- Candy.View.Pane.Room.ignoreUser(roomJid, user.getJid());
- }
- },
- 'unignore': {
- requiredPermission: function(user, me) {
- return me.getNick() !== user.getNick() && Candy.Core.getUser().isInPrivacyList('ignore', user.getJid());
- },
- 'class' : 'unignore',
- 'label' : $.i18n._('unignoreActionLabel'),
- 'callback' : function(e, roomJid, user) {
- Candy.View.Pane.Room.unignoreUser(roomJid, user.getJid());
- }
- },
- 'kick': {
- requiredPermission: function(user, me) {
- return me.getNick() !== user.getNick() && me.isModerator() && !user.isModerator();
- },
- 'class' : 'kick',
- 'label' : $.i18n._('kickActionLabel'),
- 'callback' : function(e, roomJid, user) {
- self.Chat.Modal.show(Mustache.to_html(Candy.View.Template.Chat.Context.contextModalForm, {
- _label: $.i18n._('reason'),
- _submit: $.i18n._('kickActionLabel')
- }), true);
- $('#context-modal-field').focus();
- $('#context-modal-form').submit(function() {
- Candy.Core.Action.Jabber.Room.Admin.UserAction(roomJid, user.getJid(), 'kick', $('#context-modal-field').val());
- self.Chat.Modal.hide();
- return false; // stop propagation & preventDefault, as otherwise you get disconnected (wtf?)
- });
- }
- },
- 'ban': {
- requiredPermission: function(user, me) {
- return me.getNick() !== user.getNick() && me.isModerator() && !user.isModerator();
- },
- 'class' : 'ban',
- 'label' : $.i18n._('banActionLabel'),
- 'callback' : function(e, roomJid, user) {
- self.Chat.Modal.show(Mustache.to_html(Candy.View.Template.Chat.Context.contextModalForm, {
- _label: $.i18n._('reason'),
- _submit: $.i18n._('banActionLabel')
- }), true);
- $('#context-modal-field').focus();
- $('#context-modal-form').submit(function() {
- Candy.Core.Action.Jabber.Room.Admin.UserAction(roomJid, user.getJid(), 'ban', $('#context-modal-field').val());
- self.Chat.Modal.hide();
- return false; // stop propagation & preventDefault, as otherwise you get disconnected (wtf?)
- });
- }
- },
- 'subject': {
- requiredPermission: function(user, me) {
- return me.getNick() === user.getNick() && me.isModerator();
- },
- 'class': 'subject',
- 'label' : $.i18n._('setSubjectActionLabel'),
- 'callback': function(e, roomJid) {
- self.Chat.Modal.show(Mustache.to_html(Candy.View.Template.Chat.Context.contextModalForm, {
- _label: $.i18n._('subject'),
- _submit: $.i18n._('setSubjectActionLabel')
- }), true);
- $('#context-modal-field').focus();
- $('#context-modal-form').submit(function(e) {
- Candy.Core.Action.Jabber.Room.Admin.SetSubject(roomJid, $('#context-modal-field').val());
- self.Chat.Modal.hide();
- e.preventDefault();
- });
- }
- }
- };
- },
-
- /** Function: showEmoticonsMenu
- * Shows the special emoticons menu
- *
- * Parameters:
- * (Element) elem - Element on which it should be positioned to.
- *
- * Returns:
- * (Boolean) - true
- */
- showEmoticonsMenu: function(elem) {
- elem = $(elem);
- var pos = elem.offset(),
- menu = $('#context-menu'),
- content = $('ul', menu),
- emoticons = '',
- i;
-
- $('#tooltip').hide();
-
- for(i = Candy.Util.Parser.emoticons.length-1; i >= 0; i--) {
- emoticons = '<img src="' + Candy.Util.Parser._emoticonPath + Candy.Util.Parser.emoticons[i].image + '" alt="' + Candy.Util.Parser.emoticons[i].plain + '" />' + emoticons;
- }
- content.html('<li class="emoticons">' + emoticons + '</li>');
- content.find('img').click(function() {
- var input = Candy.View.Pane.Room.getPane(Candy.View.getCurrent().roomJid, '.message-form').children('.field'),
- value = input.val(),
- emoticon = $(this).attr('alt') + ' ';
- input.val(value ? value + ' ' + emoticon : emoticon).focus();
- });
-
- var posLeft = Candy.Util.getPosLeftAccordingToWindowBounds(menu, pos.left),
- posTop = Candy.Util.getPosTopAccordingToWindowBounds(menu, pos.top);
-
- menu
- .css({'left': posLeft.px, 'top': posTop.px})
- .removeClass('left-top left-bottom right-top right-bottom')
- .addClass(posLeft.backgroundPositionAlignment + '-' + posTop.backgroundPositionAlignment)
- .fadeIn('fast');
-
- return true;
- }
- }
- };
-
- /** Class: Candy.View.Pane.Room
- * Everything which belongs to room view things belongs here.
- */
- self.Room = {
- /** Function: init
- * Initialize a new room and inserts the room html into the DOM
- *
- * Parameters:
- * (String) roomJid - Room JID
- * (String) roomName - Room name
- * (String) roomType - Type: either "groupchat" or "chat" (private chat)
- *
- * Uses:
- * - <Candy.Util.jidToId>
- * - <Candy.View.Pane.Chat.addTab>
- * - <getPane>
- *
- * Triggers:
- * candy:view.room.after-add using {roomJid, type, element}
- *
- * Returns:
- * (String) - the room id of the element created.
- */
- init: function(roomJid, roomName, roomType) {
- roomType = roomType || 'groupchat';
- roomJid = Candy.Util.unescapeJid(roomJid);
-
- var evtData = {
- roomJid: roomJid,
- type: roomType
- };
- /** Event: candy:view.room.before-add
- * Before initialising a room
- *
- * Parameters:
- * (String) roomJid - Room JID
- * (String) type - Room Type
- *
- * Returns:
- * Boolean - if you don't want to initialise the room, return false.
- */
- if($(Candy).triggerHandler('candy:view.room.before-add', evtData) === false) {
- return false;
- }
-
- // First room, show sound control
- if(Candy.Util.isEmptyObject(self.Chat.rooms)) {
- self.Chat.Toolbar.show();
- }
-
- var roomId = Candy.Util.jidToId(roomJid);
- self.Chat.rooms[roomJid] = {id: roomId, usercount: 0, name: roomName, type: roomType, messageCount: 0, scrollPosition: -1};
-
- $('#chat-rooms').append(Mustache.to_html(Candy.View.Template.Room.pane, {
- roomId: roomId,
- roomJid: roomJid,
- roomType: roomType,
- form: {
- _messageSubmit: $.i18n._('messageSubmit')
- },
- roster: {
- _userOnline: $.i18n._('userOnline')
- }
- }, {
- roster: Candy.View.Template.Roster.pane,
- messages: Candy.View.Template.Message.pane,
- form: Candy.View.Template.Room.form
- }));
- self.Chat.addTab(roomJid, roomName, roomType);
- self.Room.getPane(roomJid, '.message-form').submit(self.Message.submit);
-
- evtData.element = self.Room.getPane(roomJid);
-
- /** 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;
- },
-
- /** Function: show
- * Show a specific room and hides the other rooms (if there are any)
- *
- * 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,
- evtData;
-
- $('.room-pane').each(function() {
- var elem = $(this);
- evtData = {
- 'roomJid': elem.attr('data-roomjid'),
- 'element' : elem
- };
-
- if(elem.attr('id') === ('chat-room-' + roomId)) {
- elem.show();
- Candy.View.getCurrent().roomJid = roomJid;
- self.Chat.setActiveTab(roomJid);
- self.Chat.Toolbar.update(roomJid);
- self.Chat.clearUnreadMessages(roomJid);
- self.Room.setFocusToForm(roomJid);
- self.Room.scrollToBottom(roomJid);
-
- /** 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();
-
- /** 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);
- }
- });
- },
-
- /** 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
- */
- setSubject: function(roomJid, subject) {
- subject = Candy.Util.Parser.linkify(Candy.Util.Parser.escape(subject));
- var html = Mustache.to_html(Candy.View.Template.Room.subject, {
- subject: subject,
- roomName: self.Chat.rooms[roomJid].name,
- _roomSubject: $.i18n._('roomSubject'),
- time: Candy.Util.localizedTime(new Date().toGMTString())
- });
- self.Room.appendToMessagePane(roomJid, html);
- self.Room.scrollToBottom(roomJid);
-
- /** 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', {
- 'roomJid': roomJid,
- 'element' : self.Room.getPane(roomJid),
- 'subject' : subject
- });
- },
-
- /** Function: close
- * Close a room and remove everything in the DOM belonging to this room.
- *
- * NOTICE: There's a rendering bug in Opera when all rooms have been closed.
- * (Take a look in the source for a more detailed description)
- *
- * Triggers:
- * candy:view.room.after-close using {roomJid}
- *
- * Parameters:
- * (String) roomJid - Room to close
- */
- close: function(roomJid) {
- self.Chat.removeTab(roomJid);
- self.Window.clearUnreadMessages();
-
- /* TODO:
- There's a rendering bug in Opera which doesn't redraw (remove) the message form.
- Only a cosmetical issue (when all tabs are closed) but it's annoying...
- This happens when form has no focus too. Maybe it's because of CSS positioning.
- */
- self.Room.getPane(roomJid).remove();
- var openRooms = $('#chat-rooms').children();
- if(Candy.View.getCurrent().roomJid === roomJid) {
- Candy.View.getCurrent().roomJid = null;
- if(openRooms.length === 0) {
- self.Chat.allTabsClosed();
- } else {
- self.Room.show(openRooms.last().attr('data-roomjid'));
- }
- }
- delete self.Chat.rooms[roomJid];
-
- /** Event: candy:view.room.after-close
- * After closing a room
- *
- * Parameters:
- * (String) roomJid - Room JID
- */
- $(Candy).triggerHandler('candy:view.room.after-close', {
- 'roomJid' : roomJid
- });
- },
-
- /** Function: appendToMessagePane
- * Append a new message to the message pane.
- *
- * Parameters:
- * (String) roomJid - Room JID
- * (String) html - rendered message html
- */
- appendToMessagePane: function(roomJid, html) {
- self.Room.getPane(roomJid, '.message-pane').append(html);
- self.Chat.rooms[roomJid].messageCount++;
- self.Room.sliceMessagePane(roomJid);
- },
-
- /** Function: sliceMessagePane
- * Slices the message pane after the max amount of messages specified in the Candy View options (limit setting).
- *
- * This is done to hopefully prevent browsers from getting slow after a certain amount of messages in the DOM.
- *
- * The slice is only done when autoscroll is on, because otherwise someone might lose exactly the message he want to look for.
- *
- * Parameters:
- * (String) roomJid - Room JID
- */
- sliceMessagePane: function(roomJid) {
- // Only clean if autoscroll is enabled
- if(self.Window.autoscroll) {
- var options = Candy.View.getOptions().messages;
- if(self.Chat.rooms[roomJid].messageCount > options.limit) {
- self.Room.getPane(roomJid, '.message-pane').children().slice(0, options.remove).remove();
- self.Chat.rooms[roomJid].messageCount -= options.remove;
- }
- }
- },
-
- /** Function: scrollToBottom
- * Scroll to bottom wrapper for <onScrollToBottom> to be able to disable it by overwriting the function.
- *
- * Parameters:
- * (String) roomJid - Room JID
- *
- * Uses:
- * - <onScrollToBottom>
- */
- scrollToBottom: function(roomJid) {
- self.Room.onScrollToBottom(roomJid);
- },
-
- /** Function: onScrollToBottom
- * Scrolls to the latest message received/sent.
- *
- * Parameters:
- * (String) roomJid - Room JID
- */
- onScrollToBottom: function(roomJid) {
- var messagePane = self.Room.getPane(roomJid, '.message-pane-wrapper');
- messagePane.scrollTop(messagePane.prop('scrollHeight'));
- },
-
- /** Function: onScrollToStoredPosition
- * When autoscroll is off, the position where the scrollbar is has to be stored for each room, because it otherwise
- * goes to the top in the message window.
- *
- * Parameters:
- * (String) roomJid - Room JID
- */
- onScrollToStoredPosition: function(roomJid) {
- // This should only apply when entering a room...
- // ... therefore we set scrollPosition to -1 after execution.
- if(self.Chat.rooms[roomJid].scrollPosition > -1) {
- var messagePane = self.Room.getPane(roomJid, '.message-pane-wrapper');
- messagePane.scrollTop(self.Chat.rooms[roomJid].scrollPosition);
- self.Chat.rooms[roomJid].scrollPosition = -1;
- }
- },
-
- /** Function: setFocusToForm
- * Set focus to the message input field within the message form.
- *
- * Parameters:
- * (String) roomJid - Room JID
- */
- setFocusToForm: function(roomJid) {
- var pane = self.Room.getPane(roomJid, '.message-form');
- if (pane) {
- // IE8 will fail maybe, because the field isn't there yet.
- try {
- pane.children('.field')[0].focus();
- } catch(e) {
- // fail silently
- }
- }
- },
-
- /** Function: setUser
- * Sets or updates the current user in the specified room (called by <Candy.View.Pane.Roster.update>) and set specific informations
- * (roles and affiliations) on the room tab (chat-pane).
- *
- * Parameters:
- * (String) roomJid - Room in which the user is set to.
- * (Candy.Core.ChatUser) user - The user
- */
- setUser: function(roomJid, user) {
- self.Chat.rooms[roomJid].user = user;
- var roomPane = self.Room.getPane(roomJid),
- chatPane = $('#chat-pane');
-
- roomPane.attr('data-userjid', user.getJid());
- // Set classes based on user role / affiliation
- if(user.isModerator()) {
- if (user.getRole() === user.ROLE_MODERATOR) {
- chatPane.addClass('role-moderator');
- }
- if (user.getAffiliation() === user.AFFILIATION_OWNER) {
- chatPane.addClass('affiliation-owner');
- }
- } else {
- chatPane.removeClass('role-moderator affiliation-owner');
- }
- self.Chat.Context.init();
- },
-
- /** Function: getUser
- * Get the current user in the room specified with the jid
- *
- * Parameters:
- * (String) roomJid - Room of which the user should be returned from
- *
- * Returns:
- * (Candy.Core.ChatUser) - user
- */
- getUser: function(roomJid) {
- return self.Chat.rooms[roomJid].user;
- },
-
- /** Function: ignoreUser
- * Ignore specified user and add the ignore icon to the roster item of the user
- *
- * Parameters:
- * (String) roomJid - Room in which the user should be ignored
- * (String) userJid - User which should be ignored
- */
- ignoreUser: function(roomJid, userJid) {
- Candy.Core.Action.Jabber.Room.IgnoreUnignore(userJid);
- Candy.View.Pane.Room.addIgnoreIcon(roomJid, userJid);
- },
-
- /** Function: unignoreUser
- * Unignore an ignored user and remove the ignore icon of the roster item.
- *
- * Parameters:
- * (String) roomJid - Room in which the user should be unignored
- * (String) userJid - User which should be unignored
- */
- unignoreUser: function(roomJid, userJid) {
- Candy.Core.Action.Jabber.Room.IgnoreUnignore(userJid);
- Candy.View.Pane.Room.removeIgnoreIcon(roomJid, userJid);
- },
-
- /** Function: addIgnoreIcon
- * Add the ignore icon to the roster item of the specified user
- *
- * Parameters:
- * (String) roomJid - Room in which the roster item should be updated
- * (String) userJid - User of which the roster item should be updated
- */
- addIgnoreIcon: function(roomJid, userJid) {
- if (Candy.View.Pane.Chat.rooms[userJid]) {
- $('#user-' + Candy.View.Pane.Chat.rooms[userJid].id + '-' + Candy.Util.jidToId(userJid)).addClass('status-ignored');
- }
- if (Candy.View.Pane.Chat.rooms[Strophe.getBareJidFromJid(roomJid)]) {
- $('#user-' + Candy.View.Pane.Chat.rooms[Strophe.getBareJidFromJid(roomJid)].id + '-' + Candy.Util.jidToId(userJid)).addClass('status-ignored');
- }
- },
-
- /** Function: removeIgnoreIcon
- * Remove the ignore icon to the roster item of the specified user
- *
- * Parameters:
- * (String) roomJid - Room in which the roster item should be updated
- * (String) userJid - User of which the roster item should be updated
- */
- removeIgnoreIcon: function(roomJid, userJid) {
- if (Candy.View.Pane.Chat.rooms[userJid]) {
- $('#user-' + Candy.View.Pane.Chat.rooms[userJid].id + '-' + Candy.Util.jidToId(userJid)).removeClass('status-ignored');
- }
- if (Candy.View.Pane.Chat.rooms[Strophe.getBareJidFromJid(roomJid)]) {
- $('#user-' + Candy.View.Pane.Chat.rooms[Strophe.getBareJidFromJid(roomJid)].id + '-' + Candy.Util.jidToId(userJid)).removeClass('status-ignored');
- }
- },
-
- /** Function: getPane
- * Get the chat room pane or a subPane of it (if subPane is specified)
- *
- * Parameters:
- * (String) roomJid - Room in which the pane lies
- * (String) subPane - Sub pane of the chat room pane if needed [optional]
- */
- getPane: function(roomJid, subPane) {
- if (self.Chat.rooms[roomJid]) {
- if(subPane) {
- if(self.Chat.rooms[roomJid]['pane-' + subPane]) {
- return self.Chat.rooms[roomJid]['pane-' + subPane];
- } else {
- self.Chat.rooms[roomJid]['pane-' + subPane] = $('#chat-room-' + self.Chat.rooms[roomJid].id).find(subPane);
- return self.Chat.rooms[roomJid]['pane-' + subPane];
- }
- } else {
- return $('#chat-room-' + self.Chat.rooms[roomJid].id);
- }
- }
- },
-
- /** Function: changeDataUserJidIfUserIsMe
- * Changes the room's data-userjid attribute if the specified user is the current user.
- *
- * Parameters:
- * (String) roomId - Id of the room
- * (Candy.Core.ChatUser) user - User
- */
- changeDataUserJidIfUserIsMe: function(roomId, user) {
- if (user.getNick() === Candy.Core.getUser().getNick()) {
- var roomElement = $('#chat-room-' + roomId);
- roomElement.attr('data-userjid', Strophe.getBareJidFromJid(roomElement.attr('data-userjid')) + '/' + user.getNick());
- }
- }
- };
-
- /** Class: Candy.View.Pane.PrivateRoom
- * Private room handling
- */
- self.PrivateRoom = {
- /** Function: open
- * Opens a new private room
- *
- * Parameters:
- * (String) roomJid - Room jid to open
- * (String) roomName - Room name
- * (Boolean) switchToRoom - If true, displayed room switches automatically to this room
- * (e.g. when user clicks itself on another user to open a private chat)
- * (Boolean) isNoConferenceRoomJid - true if a 3rd-party client sends a direct message to this user (not via the room)
- * then the username is the node and not the resource. This param addresses this case.
- *
- * 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)),
- evtData = {
- 'roomJid': roomJid,
- 'roomName': roomName,
- 'type': 'chat',
- };
-
- /** Event: candy:view.private-room.before-open
- * Before opening a new private room
- *
- * Parameters:
- * (String) roomJid - Room JID
- * (String) roomName - Room name
- * (String) type - 'chat'
- *
- * Returns:
- * Boolean - if you don't want to open the private room, return false
- */
- if($(Candy).triggerHandler('candy:view.private-room.before-open', evtData) === false) {
- return false;
- }
-
- // if target user is in privacy list, don't open the private chat.
- if (Candy.Core.getUser().isInPrivacyList('ignore', roomJid)) {
- return false;
- }
- if(!self.Chat.rooms[roomJid]) {
- if(self.Room.init(roomJid, roomName, 'chat') === false) {
- return false;
- }
- }
- 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'));
- }
-
- evtData.element = self.Room.getPane(roomJid);
- /** 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
- * Set offline or online status for private rooms (when one of the participants leaves the room)
- *
- * Parameters:
- * (String) roomJid - Private room jid
- * (String) status - "leave"/"join"
- */
- setStatus: function(roomJid, status) {
- var messageForm = self.Room.getPane(roomJid, '.message-form');
- if(status === 'join') {
- self.Chat.getTab(roomJid).addClass('online').removeClass('offline');
-
- messageForm.children('.field').removeAttr('disabled');
- messageForm.children('.submit').removeAttr('disabled');
-
- self.Chat.getTab(roomJid);
- } else if(status === 'leave') {
- self.Chat.getTab(roomJid).addClass('offline').removeClass('online');
-
- messageForm.children('.field').attr('disabled', true);
- messageForm.children('.submit').attr('disabled', true);
- }
- },
-
- /** Function: changeNick
- * Changes the nick for every private room opened with this roomJid.
- *
- * Parameters:
- * (String) roomJid - Public room jid
- * (Candy.Core.ChatUser) user - User which changes his nick
- */
- changeNick: function changeNick(roomJid, user) {
- Candy.Core.log('[View:Pane:PrivateRoom] changeNick');
-
- var previousPrivateRoomJid = roomJid + '/' + user.getPreviousNick(),
- newPrivateRoomJid = roomJid + '/' + user.getNick(),
- previousPrivateRoomId = Candy.Util.jidToId(previousPrivateRoomJid),
- newPrivateRoomId = Candy.Util.jidToId(newPrivateRoomJid),
- room = self.Chat.rooms[previousPrivateRoomJid],
- roomElement,
- roomTabElement;
-
- // it could happen that the new private room is already existing -> close it first.
- // if this is not done, errors appear as two rooms would have the same id
- if (self.Chat.rooms[newPrivateRoomJid]) {
- self.Room.close(newPrivateRoomJid);
- }
-
- if (room) { /* someone I talk with, changed nick */
- room.name = user.getNick();
- room.id = newPrivateRoomId;
-
- self.Chat.rooms[newPrivateRoomJid] = room;
- delete self.Chat.rooms[previousPrivateRoomJid];
-
- roomElement = $('#chat-room-' + previousPrivateRoomId);
- if (roomElement) {
- roomElement.attr('data-roomjid', newPrivateRoomJid);
- roomElement.attr('id', 'chat-room-' + newPrivateRoomId);
-
- roomTabElement = $('#chat-tabs li[data-roomjid="' + previousPrivateRoomJid + '"]');
- roomTabElement.attr('data-roomjid', newPrivateRoomJid);
-
- /* TODO: The '@' is defined in the template. Somehow we should
- * extract both things into our CSS or do something else to prevent that.
- */
- roomTabElement.children('a.label').text('@' + user.getNick());
-
- if (Candy.View.getCurrent().roomJid === previousPrivateRoomJid) {
- Candy.View.getCurrent().roomJid = newPrivateRoomJid;
- }
- }
- } else { /* I changed the nick */
- roomElement = $('.room-pane.roomtype-chat[data-userjid="' + previousPrivateRoomJid + '"]');
- if (roomElement.length) {
- previousPrivateRoomId = Candy.Util.jidToId(roomElement.attr('data-roomjid'));
- roomElement.attr('data-userjid', newPrivateRoomJid);
- }
- }
- if (roomElement && roomElement.length) {
- self.Roster.changeNick(previousPrivateRoomId, user);
- }
- }
- };
-
- /** Class Candy.View.Pane.Roster
- * Handles everyhing regarding roster updates.
- */
- self.Roster = {
- /** Function: update
- * Called by <Candy.View.Observer.Presence.update> to update the roster if needed.
- * Adds/removes users from the roster list or updates informations on their items (roles, affiliations etc.)
- *
- * TODO: Refactoring, this method has too much LOC.
- *
- * Parameters:
- * (String) roomJid - Room JID in which the update happens
- * (Candy.Core.ChatUser) user - User on which the update happens
- * (String) action - one of "join", "leave", "kick" and "ban"
- * (Candy.Core.ChatUser) currentUser - Current user
- *
- * 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) {
- Candy.Core.log('[View:Pane:Roster] ' + action);
- var roomId = self.Chat.rooms[roomJid].id,
- userId = Candy.Util.jidToId(user.getJid()),
- usercountDiff = -1,
- userElem = $('#user-' + roomId + '-' + userId),
- evtData = {
- 'roomJid' : roomJid,
- 'user' : user,
- 'action': action,
- 'element': userElem
- };
-
- /** 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', evtData);
-
- // a user joined the room
- if(action === 'join') {
- usercountDiff = 1;
- var html = Mustache.to_html(Candy.View.Template.Roster.user, {
- roomId: roomId,
- userId : userId,
- userJid: user.getJid(),
- nick: user.getNick(),
- displayNick: Candy.Util.crop(user.getNick(), Candy.View.getOptions().crop.roster.nickname),
- role: user.getRole(),
- affiliation: user.getAffiliation(),
- me: currentUser !== undefined && user.getNick() === currentUser.getNick(),
- tooltipRole: $.i18n._('tooltipRole'),
- tooltipIgnored: $.i18n._('tooltipIgnored')
- });
-
- if(userElem.length < 1) {
- var userInserted = false,
- rosterPane = self.Room.getPane(roomJid, '.roster-pane');
-
- // there are already users in the roster
- if(rosterPane.children().length > 0) {
- // insert alphabetically
- var userSortCompare = user.getNick().toUpperCase();
- rosterPane.children().each(function() {
- var elem = $(this);
- if(elem.attr('data-nick').toUpperCase() > userSortCompare) {
- elem.before(html);
- userInserted = true;
- return false;
- }
- return true;
- });
- }
- // first user in roster
- if(!userInserted) {
- rosterPane.append(html);
- }
-
- self.Roster.showJoinAnimation(user, userId, roomId, roomJid, currentUser);
- // user is in room but maybe the affiliation/role has changed
- } else {
- 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
- if (currentUser !== undefined && currentUser.getNick() === user.getNick()) {
- self.Room.setUser(roomJid, user);
- // add click handler for private chat
- } else {
- $('#user-' + roomId + '-' + userId).click(self.Roster.userClick);
- }
-
- $('#user-' + roomId + '-' + userId + ' .context').click(function(e) {
- self.Chat.Context.show(e.currentTarget, roomJid, user);
- e.stopPropagation();
- });
-
- // check if current user is ignoring the user who has joined.
- if (currentUser !== undefined && currentUser.isInPrivacyList('ignore', user.getJid())) {
- Candy.View.Pane.Room.addIgnoreIcon(roomJid, user.getJid());
- }
- // a user left the room
- } else if(action === 'leave') {
- self.Roster.leaveAnimation('user-' + roomId + '-' + userId);
- // always show leave message in private room, even if status messages have been disabled
- if (self.Chat.rooms[roomJid].type === 'chat') {
- self.Chat.onInfoMessage(roomJid, $.i18n._('userLeftRoom', [user.getNick()]));
- } else {
- self.Chat.infoMessage(roomJid, $.i18n._('userLeftRoom', [user.getNick()]));
- }
-
- } else if(action === 'nickchange') {
- usercountDiff = 0;
- self.Roster.changeNick(roomId, user);
- self.Room.changeDataUserJidIfUserIsMe(roomId, user);
- self.PrivateRoom.changeNick(roomJid, user);
- var infoMessage = $.i18n._('userChangedNick', [user.getPreviousNick(), user.getNick()]);
- self.Chat.onInfoMessage(roomJid, infoMessage);
- // user has been kicked
- } else if(action === 'kick') {
- self.Roster.leaveAnimation('user-' + roomId + '-' + userId);
- self.Chat.onInfoMessage(roomJid, $.i18n._('userHasBeenKickedFromRoom', [user.getNick()]));
- // user has been banned
- } else if(action === 'ban') {
- self.Roster.leaveAnimation('user-' + roomId + '-' + userId);
- self.Chat.onInfoMessage(roomJid, $.i18n._('userHasBeenBannedFromRoom', [user.getNick()]));
- }
-
- // Update user count
- Candy.View.Pane.Chat.rooms[roomJid].usercount += usercountDiff;
-
- if(roomJid === Candy.View.getCurrent().roomJid) {
- Candy.View.Pane.Chat.Toolbar.updateUsercount(Candy.View.Pane.Chat.rooms[roomJid].usercount);
- }
-
-
- // in case there's been a join, the element is now there (previously not)
- evtData.element = $('#user-' + roomId + '-' + userId);
- /** 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
- * Click handler for opening a private room
- */
- userClick: function() {
- var elem = $(this);
- self.PrivateRoom.open(elem.attr('data-jid'), elem.attr('data-nick'), true);
- },
-
- /** Function: showJoinAnimation
- * Shows join animation if needed
- *
- * FIXME: Refactor. Part of this will be done by the big room improvements
- */
- showJoinAnimation: function(user, userId, roomId, roomJid, currentUser) {
- // don't show if the user has recently changed the nickname.
- var rosterUserId = 'user-' + roomId + '-' + userId,
- $rosterUserElem = $('#' + rosterUserId);
- if (!user.getPreviousNick() || !$rosterUserElem || $rosterUserElem.is(':visible') === false) {
- self.Roster.joinAnimation(rosterUserId);
- // only show other users joining & don't show if there's no message in the room.
- if(currentUser !== undefined && user.getNick() !== currentUser.getNick() && self.Room.getUser(roomJid)) {
- // always show join message in private room, even if status messages have been disabled
- if (self.Chat.rooms[roomJid].type === 'chat') {
- self.Chat.onInfoMessage(roomJid, $.i18n._('userJoinedRoom', [user.getNick()]));
- } else {
- self.Chat.infoMessage(roomJid, $.i18n._('userJoinedRoom', [user.getNick()]));
- }
- }
- }
- },
-
- /** Function: joinAnimation
- * Animates specified elementId on join
- *
- * Parameters:
- * (String) elementId - Specific element to do the animation on
- */
- joinAnimation: function(elementId) {
- $('#' + elementId).stop(true).slideDown('normal', function() {
- $(this).animate({opacity: 1});
- });
- },
-
- /** Function: leaveAnimation
- * Leave animation for specified element id and removes the DOM element on completion.
- *
- * Parameters:
- * (String) elementId - Specific element to do the animation on
- */
- leaveAnimation: function(elementId) {
- $('#' + elementId).stop(true).attr('id', '#' + elementId + '-leaving').animate({opacity: 0}, {
- complete: function() {
- $(this).slideUp('normal', function() {
- $(this).remove();
- });
- }
- });
- },
-
- /** Function: changeNick
- * Change nick of an existing user in the roster
- *
- * UserId has to be recalculated from the user because at the time of this call,
- * the user is already set with the new jid & nick.
- *
- * Parameters:
- * (String) roomId - Id of the room
- * (Candy.Core.ChatUser) user - User object
- */
- changeNick: function(roomId, user) {
- Candy.Core.log('[View:Pane:Roster] changeNick');
- var previousUserJid = Strophe.getBareJidFromJid(user.getJid()) + '/' + user.getPreviousNick(),
- elementId = 'user-' + roomId + '-' + Candy.Util.jidToId(previousUserJid),
- el = $('#' + elementId);
-
- el.attr('data-nick', user.getNick());
- el.attr('data-jid', user.getJid());
- el.children('div.label').text(user.getNick());
- el.attr('id', 'user-' + roomId + '-' + Candy.Util.jidToId(user.getJid()));
- }
- };
-
- /** Class: Candy.View.Pane.Message
- * Message submit/show handling
- */
- self.Message = {
- /** Function: submit
- * on submit handler for message field sends the message to the server and if it's a private chat, shows the message
- * immediately because the server doesn't send back those message.
- *
- * Parameters:
- * (Event) event - Triggered event
- *
- * Triggers:
- * candy:view.message.before-send using {message}
- *
- * FIXME: as everywhere, `roomJid` might be slightly incorrect in this case
- * - maybe rename this as part of a refactoring.
- */
- submit: function(event) {
- var roomJid = Candy.View.getCurrent().roomJid,
- roomType = Candy.View.Pane.Chat.rooms[roomJid].type,
- message = $(this).children('.field').val().substring(0, Candy.View.getOptions().crop.message.body),
- xhtmlMessage,
- evtData = {
- roomJid: roomJid,
- message: message,
- xhtmlMessage: xhtmlMessage
- };
-
- /** Event: candy:view.message.before-send
- * Before sending a message
- *
- * Parameters:
- * (String) roomJid - room to which the message should be sent
- * (String) message - Message text
- * (String) xhtmlMessage - XHTML formatted message [default: undefined]
- *
- * Returns:
- * Boolean|undefined - if you like to stop sending the message, return false.
- */
- if($(Candy).triggerHandler('candy:view.message.before-send', evtData) === false) {
- event.preventDefault();
- return;
- }
-
- message = evtData.message;
- xhtmlMessage = evtData.xhtmlMessage;
-
- Candy.Core.Action.Jabber.Room.Message(roomJid, message, roomType, xhtmlMessage);
- // Private user chat. Jabber won't notify the user who has sent the message. Just show it as the user hits the button...
- if(roomType === 'chat' && message) {
- self.Message.show(roomJid, self.Room.getUser(roomJid).getNick(), message);
- }
- // Clear input and set focus to it
- $(this).children('.field').val('').focus();
- event.preventDefault();
- },
-
- /** Function: show
- * Show a message in the message pane
- *
- * Parameters:
- * (String) roomJid - room in which the message has been sent to
- * (String) name - Name of the user which sent the message
- * (String) message - Message
- * (String) xhtmlMessage - XHTML formatted message [if options enableXHTML is true]
- * (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, xhtmlMessage, timestamp) {
- message = Candy.Util.Parser.all(message.substring(0, Candy.View.getOptions().crop.message.body));
- if(xhtmlMessage) {
- xhtmlMessage = Candy.Util.parseAndCropXhtml(xhtmlMessage, Candy.View.getOptions().crop.message.body);
- }
-
- var evtData = {
- 'roomJid': roomJid,
- 'name': name,
- 'message': message,
- 'xhtmlMessage': xhtmlMessage
- };
-
- /** 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
- *
- * Returns:
- * Boolean - if you don't want to show the message, return false
- */
- if($(Candy).triggerHandler('candy:view.message.before-show', evtData) === false) {
- return;
- }
-
- message = evtData.message;
- xhtmlMessage = evtData.xhtmlMessage;
- if(xhtmlMessage !== undefined && xhtmlMessage.length > 0) {
- message = xhtmlMessage;
- }
-
- if(!message) {
- return;
- }
-
- 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.label').click(function(event) {
- event.preventDefault();
- // Check if user is online and not myself
- var room = Candy.Core.getRoom(roomJid);
- if(room && name !== self.Room.getUser(Candy.View.getCurrent().roomJid).getNick() && room.getRoster().get(roomJid + '/' + name)) {
- if(Candy.View.Pane.PrivateRoom.open(roomJid + '/' + name, name, true) === false) {
- return false;
- }
- }
- });
-
- // Notify the user about a new private message
- if(Candy.View.getCurrent().roomJid !== roomJid || !self.Window.hasFocus()) {
- self.Chat.increaseUnreadMessages(roomJid);
- if(Candy.View.Pane.Chat.rooms[roomJid].type === 'chat' && !self.Window.hasFocus()) {
- self.Chat.Toolbar.playSound();
- }
- }
- if(Candy.View.getCurrent().roomJid === roomJid) {
- self.Room.scrollToBottom(roomJid);
- }
-
- evtData.element = elem;
-
- /** 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);
- }
- };
-
- return self;
-}(Candy.View.Pane || {}, jQuery));
diff --git a/src/view/pane/chat.js b/src/view/pane/chat.js
new file mode 100644
index 0000000..61d7692
--- /dev/null
+++ b/src/view/pane/chat.js
@@ -0,0 +1,1064 @@
+/** File: chat.js
+ * Candy - Chats are not dead yet.
+ *
+ * Authors:
+ * - Patrick Stadler <patrick.stadler@gmail.com>
+ * - Michael Weibel <michael.weibel@gmail.com>
+ *
+ * Copyright:
+ * (c) 2011 Amiado Group AG. All rights reserved.
+ * (c) 2012-2014 Patrick Stadler & Michael Weibel. All rights reserved.
+ */
+'use strict';
+
+/* global Candy, document, Mustache, Strophe, Audio, jQuery */
+
+/** Class: Candy.View.Pane
+ * Candy view pane handles everything regarding DOM updates etc.
+ *
+ * Parameters:
+ * (Candy.View.Pane) self - itself
+ * (jQuery) $ - jQuery
+ */
+Candy.View.Pane = (function(self, $) {
+
+ /** Class: Candy.View.Pane.Chat
+ * Chat-View related view updates
+ */
+ self.Chat = {
+ /** Variable: rooms
+ * Contains opened room elements
+ */
+ rooms: [],
+
+ /** Function: addTab
+ * Add a tab to the chat pane.
+ *
+ * Parameters:
+ * (String) roomJid - JID of room
+ * (String) roomName - Tab label
+ * (String) roomType - Type of room: `groupchat` or `chat`
+ */
+ addTab: function(roomJid, roomName, roomType) {
+ var roomId = Candy.Util.jidToId(roomJid);
+
+ var evtData = {
+ roomJid: roomJid,
+ roomName: roomName,
+ roomType: roomType,
+ roomId: roomId
+ };
+
+ /** Event: candy:view.pane.before-tab
+ * Before sending a message
+ *
+ * Parameters:
+ * (String) roomJid - JID of the room the tab is for.
+ * (String) roomName - Name of the room.
+ * (String) roomType - What type of room: `groupchat` or `chat`
+ *
+ * Returns:
+ * Boolean|undefined - If you want to handle displaying the tab on your own, return false.
+ */
+ if ($(Candy).triggerHandler('candy:view.pane.before-tab', evtData) === false) {
+ event.preventDefault();
+ return;
+ }
+
+ var html = Mustache.to_html(Candy.View.Template.Chat.tab, {
+ roomJid: roomJid,
+ roomId: roomId,
+ name: roomName || Strophe.getNodeFromJid(roomJid),
+ privateUserChat: function() {return roomType === 'chat';},
+ roomType: roomType
+ }),
+ tab = $(html).appendTo('#chat-tabs');
+
+ tab.click(self.Chat.tabClick);
+ // TODO: maybe we find a better way to get the close element.
+ $('a.close', tab).click(self.Chat.tabClose);
+
+ self.Chat.fitTabs();
+ },
+
+ /** Function: getTab
+ * Get tab by JID.
+ *
+ * Parameters:
+ * (String) roomJid - JID of room
+ *
+ * Returns:
+ * (jQuery object) - Tab element
+ */
+ getTab: function(roomJid) {
+ return $('#chat-tabs').children('li[data-roomjid="' + roomJid + '"]');
+ },
+
+ /** Function: removeTab
+ * Remove tab element.
+ *
+ * Parameters:
+ * (String) roomJid - JID of room
+ */
+ removeTab: function(roomJid) {
+ self.Chat.getTab(roomJid).remove();
+ self.Chat.fitTabs();
+ },
+
+ /** Function: setActiveTab
+ * Set the active tab.
+ *
+ * Add CSS classname `active` to the choosen tab and remove `active` from all other.
+ *
+ * Parameters:
+ * (String) roomJid - JID of room
+ */
+ setActiveTab: function(roomJid) {
+ $('#chat-tabs').children().each(function() {
+ var tab = $(this);
+ if(tab.attr('data-roomjid') === roomJid) {
+ tab.addClass('active');
+ } else {
+ tab.removeClass('active');
+ }
+ });
+ },
+
+ /** Function: increaseUnreadMessages
+ * Increase unread message count in a tab by one.
+ *
+ * Parameters:
+ * (String) roomJid - JID of room
+ *
+ * Uses:
+ * - <Window.increaseUnreadMessages>
+ */
+ increaseUnreadMessages: function(roomJid) {
+ var unreadElem = this.getTab(roomJid).find('.unread');
+ unreadElem.show().text(unreadElem.text() !== '' ? parseInt(unreadElem.text(), 10) + 1 : 1);
+ // only increase window unread messages in private chats
+ if (self.Chat.rooms[roomJid].type === 'chat' || Candy.View.getOptions().updateWindowOnAllMessages === true) {
+ self.Window.increaseUnreadMessages();
+ }
+ },
+
+ /** Function: clearUnreadMessages
+ * Clear unread message count in a tab.
+ *
+ * Parameters:
+ * (String) roomJid - JID of room
+ *
+ * Uses:
+ * - <Window.reduceUnreadMessages>
+ */
+ clearUnreadMessages: function(roomJid) {
+ var unreadElem = self.Chat.getTab(roomJid).find('.unread');
+ self.Window.reduceUnreadMessages(unreadElem.text());
+ unreadElem.hide().text('');
+ },
+
+ /** Function: tabClick
+ * Tab click event: show the room associated with the tab and stops the event from doing the default.
+ */
+ tabClick: function(e) {
+ // remember scroll position of current room
+ var currentRoomJid = Candy.View.getCurrent().roomJid;
+ var roomPane = self.Room.getPane(currentRoomJid, '.message-pane');
+ if (roomPane) {
+ self.Chat.rooms[currentRoomJid].scrollPosition = roomPane.scrollTop();
+ }
+
+ self.Room.show($(this).attr('data-roomjid'));
+ e.preventDefault();
+ },
+
+ /** Function: tabClose
+ * Tab close (click) event: Leave the room (groupchat) or simply close the tab (chat).
+ *
+ * Parameters:
+ * (DOMEvent) e - Event triggered
+ *
+ * Returns:
+ * (Boolean) - false, this will stop the event from bubbling
+ */
+ tabClose: function() {
+ var roomJid = $(this).parent().attr('data-roomjid');
+ // close private user tab
+ if(self.Chat.rooms[roomJid].type === 'chat') {
+ self.Room.close(roomJid);
+ // close multi-user room tab
+ } else {
+ Candy.Core.Action.Jabber.Room.Leave(roomJid);
+ }
+ return false;
+ },
+
+ /** Function: allTabsClosed
+ * All tabs closed event: Disconnect from service. Hide sound control.
+ *
+ * TODO: Handle window close
+ *
+ * Returns:
+ * (Boolean) - false, this will stop the event from bubbling
+ */
+ allTabsClosed: function() {
+ if (Candy.Core.getOptions().disconnectWithoutTabs) {
+ Candy.Core.disconnect();
+ self.Chat.Toolbar.hide();
+ return;
+ }
+ },
+
+ /** Function: fitTabs
+ * Fit tab size according to window size
+ */
+ fitTabs: function() {
+ var availableWidth = $('#chat-tabs').innerWidth(),
+ tabsWidth = 0,
+ tabs = $('#chat-tabs').children();
+ tabs.each(function() {
+ tabsWidth += $(this).css({width: 'auto', overflow: 'visible'}).outerWidth(true);
+ });
+ if(tabsWidth > availableWidth) {
+ // tabs.[outer]Width() measures the first element in `tabs`. It's no very readable but nearly two times faster than using :first
+ var tabDiffToRealWidth = tabs.outerWidth(true) - tabs.width(),
+ tabWidth = Math.floor((availableWidth) / tabs.length) - tabDiffToRealWidth;
+ tabs.css({width: tabWidth, overflow: 'hidden'});
+ }
+ },
+
+ /** Function: 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?
+ message = Candy.Util.Parser.all(message.substring(0, Candy.View.getOptions().crop.message.body));
+ if(Candy.View.getOptions().enableXHTML === true) {
+ message = Candy.Util.parseAndCropXhtml(message, Candy.View.getOptions().crop.message.body);
+ }
+ var timestamp = new Date();
+ var html = Mustache.to_html(Candy.View.Template.Chat.adminMessage, {
+ subject: subject,
+ message: message,
+ sender: $.i18n._('administratorMessageSubject'),
+ time: Candy.Util.localizedTime(timestamp),
+ timestamp: timestamp.toISOString()
+ });
+ $('#chat-rooms').children().each(function() {
+ self.Room.appendToMessagePane($(this).attr('data-roomjid'), html);
+ });
+ self.Room.scrollToBottom(Candy.View.getCurrent().roomJid);
+
+ /** Event: candy:view.chat.admin-message
+ * After admin message display
+ *
+ * Parameters:
+ * (String) presetJid - Preset user JID
+ */
+ $(Candy).triggerHandler('candy:view.chat.admin-message', {
+ 'subject' : subject,
+ 'message' : message
+ });
+ }
+ },
+
+ /** Function: infoMessage
+ * Display info message. This is a wrapper for <onInfoMessage> to be able to disable certain info messages.
+ *
+ * Parameters:
+ * (String) roomJid - Room JID
+ * (String) subject - Subject
+ * (String) message - Message
+ */
+ infoMessage: function(roomJid, subject, message) {
+ self.Chat.onInfoMessage(roomJid, subject, message);
+ },
+
+ /** Function: onInfoMessage
+ * Display info message. Used by <infoMessage> and several other functions which do not wish that their info message
+ * can be disabled (such as kick/ban message or leave/join message in private chats).
+ *
+ * Parameters:
+ * (String) roomJid - Room JID
+ * (String) subject - Subject
+ * (String) message - Message
+ */
+ onInfoMessage: function(roomJid, subject, message) {
+ message = message || '';
+ if(Candy.View.getCurrent().roomJid && self.Chat.rooms[roomJid]) { // Simply dismiss info message if no room joined so far. TODO: maybe we should show those messages on a dedicated pane?
+ message = Candy.Util.Parser.all(message.substring(0, Candy.View.getOptions().crop.message.body));
+ if(Candy.View.getOptions().enableXHTML === true) {
+ message = Candy.Util.parseAndCropXhtml(message, Candy.View.getOptions().crop.message.body);
+ }
+ var timestamp = new Date();
+ var html = Mustache.to_html(Candy.View.Template.Chat.infoMessage, {
+ subject: subject,
+ message: $.i18n._(message),
+ time: Candy.Util.localizedTime(timestamp),
+ timestamp: timestamp.toISOString()
+ });
+ self.Room.appendToMessagePane(roomJid, html);
+ if (Candy.View.getCurrent().roomJid === roomJid) {
+ self.Room.scrollToBottom(Candy.View.getCurrent().roomJid);
+ }
+ }
+ },
+
+ /** Class: Candy.View.Pane.Toolbar
+ * Chat toolbar for things like emoticons toolbar, room management etc.
+ */
+ Toolbar: {
+ _supportsNativeAudio: null,
+
+ /** 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);
+ try {
+ if( !!document.createElement('audio').canPlayType ) {
+ var a = document.createElement('audio');
+ if( !!(a.canPlayType('audio/mpeg;').replace(/no/, '')) ) {
+ self.Chat.Toolbar._supportsNativeAudio = "mp3";
+ }
+ else if( !!(a.canPlayType('audio/ogg; codecs="vorbis"').replace(/no/, '')) ) {
+ self.Chat.Toolbar._supportsNativeAudio = "ogg";
+ }
+ else if ( !!(a.canPlayType('audio/mp4; codecs="mp4a.40.2"').replace(/no/, '')) ) {
+ self.Chat.Toolbar._supportsNativeAudio = "m4a";
+ }
+ }
+ } catch(e){ }
+ $('#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.
+ */
+ show: function() {
+ $('#chat-toolbar').show();
+ },
+
+ /** Function: hide
+ * Hide toolbar.
+ */
+ hide: function() {
+ $('#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).
+ */
+ playSound: function() {
+ self.Chat.Toolbar.onPlaySound();
+ },
+
+ /** Function: onPlaySound
+ * Sound play event handler. Uses native (HTML5) audio if supported,
+ * otherwise it will attempt to use bgsound with autostart.
+ *
+ * Don't call this method directly. Call `playSound()` instead.
+ * `playSound()` will only call this method if sound is enabled.
+ */
+ onPlaySound: function() {
+ try {
+ if(self.Chat.Toolbar._supportsNativeAudio !== null) {
+ new Audio(Candy.View.getOptions().assets + 'notify.' + self.Chat.Toolbar._supportsNativeAudio).play();
+ } else {
+ $('#chat-sound-control bgsound').remove();
+ $('<bgsound/>').attr({ src: Candy.View.getOptions().assets + 'notify.mp3', loop: 1, autostart: true }).appendTo("#chat-sound-control");
+ }
+ } catch (e) {}
+ },
+
+ /** Function: onSoundControlClick
+ * Sound control click event handler.
+ *
+ * Toggle sound (overwrite `playSound()`) and handle cookies.
+ */
+ onSoundControlClick: function() {
+ var control = $('#chat-sound-control');
+ if(control.hasClass('checked')) {
+ self.Chat.Toolbar.playSound = function() {};
+ Candy.Util.setCookie('candy-nosound', '1', 365);
+ } else {
+ self.Chat.Toolbar.playSound = function() {
+ self.Chat.Toolbar.onPlaySound();
+ };
+ Candy.Util.deleteCookie('candy-nosound');
+ }
+ control.toggleClass('checked');
+ },
+
+ /** Function: onAutoscrollControlClick
+ * Autoscroll control event handler.
+ *
+ * Toggle autoscroll
+ */
+ onAutoscrollControlClick: function() {
+ var control = $('#chat-autoscroll-control');
+ if(control.hasClass('checked')) {
+ self.Room.scrollToBottom = function(roomJid) {
+ self.Room.onScrollToStoredPosition(roomJid);
+ };
+ self.Window.autoscroll = false;
+ } else {
+ self.Room.scrollToBottom = function(roomJid) {
+ self.Room.onScrollToBottom(roomJid);
+ };
+ self.Room.scrollToBottom(Candy.View.getCurrent().roomJid);
+ self.Window.autoscroll = true;
+ }
+ control.toggleClass('checked');
+ },
+
+ /** Function: onStatusMessageControlClick
+ * Status message control event handler.
+ *
+ * Toggle status message
+ */
+ onStatusMessageControlClick: function() {
+ var control = $('#chat-statusmessage-control');
+ if(control.hasClass('checked')) {
+ self.Chat.infoMessage = function() {};
+ Candy.Util.setCookie('candy-nostatusmessages', '1', 365);
+ } else {
+ self.Chat.infoMessage = function(roomJid, subject, message) {
+ self.Chat.onInfoMessage(roomJid, subject, message);
+ };
+ Candy.Util.deleteCookie('candy-nostatusmessages');
+ }
+ control.toggleClass('checked');
+ },
+
+ /** Function: updateUserCount
+ * Update usercount element with count.
+ *
+ * Parameters:
+ * (Integer) count - Current usercount
+ */
+ updateUsercount: function(count) {
+ $('#chat-usercount').text(count);
+ }
+ },
+
+ /** Class: Candy.View.Pane.Modal
+ * Modal window
+ */
+ Modal: {
+ /** Function: show
+ * Display modal window
+ *
+ * Parameters:
+ * (String) html - HTML code to put into the modal window
+ * (Boolean) showCloseControl - set to true if a close button should be displayed [default false]
+ * (Boolean) showSpinner - set to true if a loading spinner should be shown [default false]
+ * (String) modalClass - custom class (or space-separate classes) to attach to the modal
+ */
+ show: function(html, showCloseControl, showSpinner, modalClass) {
+ if(showCloseControl) {
+ self.Chat.Modal.showCloseControl();
+ } else {
+ self.Chat.Modal.hideCloseControl();
+ }
+ if(showSpinner) {
+ self.Chat.Modal.showSpinner();
+ } else {
+ self.Chat.Modal.hideSpinner();
+ }
+ // Reset classes to 'modal-common' only in case .show() is called
+ // with different arguments before .hide() can remove the last applied
+ // custom class
+ $('#chat-modal').removeClass().addClass('modal-common');
+ if( modalClass ) {
+ $('#chat-modal').addClass(modalClass);
+ }
+ $('#chat-modal').stop(false, true);
+ $('#chat-modal-body').html(html);
+ $('#chat-modal').fadeIn('fast');
+ $('#chat-modal-overlay').show();
+ },
+
+ /** Function: hide
+ * Hide modal window
+ *
+ * Parameters:
+ * (Function) callback - Calls the specified function after modal window has been hidden.
+ */
+ hide: function(callback) {
+ // Reset classes to include only `modal-common`.
+ $('#chat-modal').removeClass().addClass('modal-common');
+ $('#chat-modal').fadeOut('fast', function() {
+ $('#chat-modal-body').text('');
+ $('#chat-modal-overlay').hide();
+ });
+ // restore initial esc handling
+ $(document).keydown(function(e) {
+ if(e.which === 27) {
+ e.preventDefault();
+ }
+ });
+ if (callback) {
+ callback();
+ }
+ },
+
+ /** Function: showSpinner
+ * Show loading spinner
+ */
+ showSpinner: function() {
+ $('#chat-modal-spinner').show();
+ },
+
+ /** Function: hideSpinner
+ * Hide loading spinner
+ */
+ hideSpinner: function() {
+ $('#chat-modal-spinner').hide();
+ },
+
+ /** Function: showCloseControl
+ * Show a close button
+ */
+ showCloseControl: function() {
+ $('#admin-message-cancel').show().click(function(e) {
+ self.Chat.Modal.hide();
+ // some strange behaviour on IE7 (and maybe other browsers) triggers onWindowUnload when clicking on the close button.
+ // prevent this.
+ e.preventDefault();
+ });
+
+ // enable esc to close modal
+ $(document).keydown(function(e) {
+ if(e.which === 27) {
+ self.Chat.Modal.hide();
+ e.preventDefault();
+ }
+ });
+ },
+
+ /** Function: hideCloseControl
+ * Hide the close button
+ */
+ hideCloseControl: function() {
+ $('#admin-message-cancel').hide().click(function() {});
+ },
+
+ /** Function: showLoginForm
+ * Show the login form modal
+ *
+ * Parameters:
+ * (String) message - optional message to display above the form
+ * (String) presetJid - optional user jid. if set, the user will only be prompted for password.
+ */
+ showLoginForm: function(message, presetJid) {
+ var domains = Candy.Core.getOptions().domains;
+ var hideDomainList = Candy.Core.getOptions().hideDomainList;
+ domains = domains ? domains.map( function(d) {return {'domain':d};} )
+ : null;
+ var customClass = domains && !hideDomainList ? 'login-with-domains'
+ : null;
+ self.Chat.Modal.show((message ? message : '') + Mustache.to_html(Candy.View.Template.Login.form, {
+ _labelNickname: $.i18n._('labelNickname'),
+ _labelUsername: $.i18n._('labelUsername'),
+ domains: domains,
+ _labelPassword: $.i18n._('labelPassword'),
+ _loginSubmit: $.i18n._('loginSubmit'),
+ displayPassword: !Candy.Core.isAnonymousConnection(),
+ displayUsername: !presetJid,
+ displayDomain: domains ? true : false,
+ displayNickname: Candy.Core.isAnonymousConnection(),
+ presetJid: presetJid ? presetJid : false
+ }), null, null, customClass);
+ if(hideDomainList) {
+ $('#domain').hide();
+ $('.at-symbol').hide();
+ }
+ $('#login-form').children(':input:first').focus();
+
+ // register submit handler
+ $('#login-form').submit(function() {
+ var username = $('#username').val(),
+ password = $('#password').val(),
+ domain = $('#domain');
+ domain = domain.length ? domain.val().split(' ')[0] : null;
+
+ if (!Candy.Core.isAnonymousConnection()) {
+ var jid;
+ if(domain) { // domain is stipulated
+ // Ensure there is no domain part in username
+ username = username.split('@')[0];
+ jid = username + '@' + domain;
+ } else { // domain not stipulated
+ // guess the input and create a jid out of it
+ jid = Candy.Core.getUser() && username.indexOf("@") < 0 ?
+ username + '@' + Strophe.getDomainFromJid(Candy.Core.getUser().getJid()) : username;
+ }
+
+ if(jid.indexOf("@") < 0 && !Candy.Core.getUser()) {
+ Candy.View.Pane.Chat.Modal.showLoginForm($.i18n._('loginInvalid'));
+ } else {
+ //Candy.View.Pane.Chat.Modal.hide();
+ Candy.Core.connect(jid, password);
+ }
+ } else { // anonymous login
+ Candy.Core.connect(presetJid, null, username);
+ }
+ return false;
+ });
+ },
+
+ /** Function: showEnterPasswordForm
+ * Shows a form for entering room password
+ *
+ * Parameters:
+ * (String) roomJid - Room jid to join
+ * (String) roomName - Room name
+ * (String) message - [optional] Message to show as the label
+ */
+ showEnterPasswordForm: function(roomJid, roomName, message) {
+ self.Chat.Modal.show(Mustache.to_html(Candy.View.Template.PresenceError.enterPasswordForm, {
+ roomName: roomName,
+ _labelPassword: $.i18n._('labelPassword'),
+ _label: (message ? message : $.i18n._('enterRoomPassword', [roomName])),
+ _joinSubmit: $.i18n._('enterRoomPasswordSubmit')
+ }), true);
+ $('#password').focus();
+
+ // register submit handler
+ $('#enter-password-form').submit(function() {
+ var password = $('#password').val();
+
+ self.Chat.Modal.hide(function() {
+ Candy.Core.Action.Jabber.Room.Join(roomJid, password);
+ });
+ return false;
+ });
+ },
+
+ /** Function: showNicknameConflictForm
+ * Shows a form indicating that the nickname is already taken and
+ * for chosing a new nickname
+ *
+ * Parameters:
+ * (String) roomJid - Room jid to join
+ */
+ showNicknameConflictForm: function(roomJid) {
+ self.Chat.Modal.show(Mustache.to_html(Candy.View.Template.PresenceError.nicknameConflictForm, {
+ _labelNickname: $.i18n._('labelNickname'),
+ _label: $.i18n._('nicknameConflict'),
+ _loginSubmit: $.i18n._('loginSubmit')
+ }));
+ $('#nickname').focus();
+
+ // register submit handler
+ $('#nickname-conflict-form').submit(function() {
+ var nickname = $('#nickname').val();
+
+ self.Chat.Modal.hide(function() {
+ Candy.Core.getUser().data.nick = nickname;
+ Candy.Core.Action.Jabber.Room.Join(roomJid);
+ });
+ return false;
+ });
+ },
+
+ /** Function: showError
+ * Show modal containing error message
+ *
+ * Parameters:
+ * (String) message - key of translation to display
+ * (Array) replacements - array containing replacements for translation (%s)
+ */
+ showError: function(message, replacements) {
+ self.Chat.Modal.show(Mustache.to_html(Candy.View.Template.PresenceError.displayError, {
+ _error: $.i18n._(message, replacements)
+ }), true);
+ }
+ },
+
+ /** Class: Candy.View.Pane.Tooltip
+ * Class to display tooltips over specific elements
+ */
+ Tooltip: {
+ /** Function: show
+ * Show a tooltip on event.currentTarget with content specified or content within the target's attribute data-tooltip.
+ *
+ * On mouseleave on the target, hide the tooltip.
+ *
+ * Parameters:
+ * (Event) event - Triggered event
+ * (String) content - Content to display [optional]
+ */
+ show: function(event, content) {
+ var tooltip = $('#tooltip'),
+ target = $(event.currentTarget);
+
+ if(!content) {
+ content = target.attr('data-tooltip');
+ }
+
+ if(tooltip.length === 0) {
+ var html = Mustache.to_html(Candy.View.Template.Chat.tooltip);
+ $('#chat-pane').append(html);
+ tooltip = $('#tooltip');
+ }
+
+ $('#context-menu').hide();
+
+ tooltip.stop(false, true);
+ tooltip.children('div').html(content);
+
+ var pos = target.offset(),
+ posLeft = Candy.Util.getPosLeftAccordingToWindowBounds(tooltip, pos.left),
+ posTop = Candy.Util.getPosTopAccordingToWindowBounds(tooltip, pos.top);
+
+ tooltip
+ .css({'left': posLeft.px, 'top': posTop.px})
+ .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});});
+ });
+ }
+ },
+
+ /** Class: Candy.View.Pane.Context
+ * Context menu for actions and settings
+ */
+ Context: {
+ /** Function: init
+ * Initialize context menu and setup mouseleave handler.
+ */
+ init: function() {
+ if ($('#context-menu').length === 0) {
+ var html = Mustache.to_html(Candy.View.Template.Chat.Context.menu);
+ $('#chat-pane').append(html);
+ $('#context-menu').mouseleave(function() {
+ $(this).fadeOut('fast');
+ });
+ }
+ },
+
+ /** Function: show
+ * Show context menu (positions it according to the window height/width)
+ *
+ * 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
+ *
+ * Triggers:
+ * candy:view.roster.after-context-menu using {roomJid, user, elements}
+ */
+ show: function(elem, roomJid, user) {
+ elem = $(elem);
+ var roomId = self.Chat.rooms[roomJid].id,
+ menu = $('#context-menu'),
+ links = $('ul li', menu);
+
+ $('#tooltip').hide();
+
+ // add specific context-user class if a user is available (when context menu should be opened next to a user)
+ if(!user) {
+ user = Candy.Core.getUser();
+ }
+
+ links.remove();
+
+ var menulinks = this.getMenuLinks(roomJid, user, elem),
+ id,
+ clickHandler = function(roomJid, user) {
+ return function(event) {
+ event.data.callback(event, roomJid, user);
+ $('#context-menu').hide();
+ };
+ };
+
+ for(id in menulinks) {
+ if(menulinks.hasOwnProperty(id)) {
+ var link = menulinks[id],
+ html = Mustache.to_html(Candy.View.Template.Chat.Context.menulinks, {
+ 'roomId' : roomId,
+ 'class' : link['class'],
+ 'id' : id,
+ 'label' : link.label
+ });
+ $('ul', menu).append(html);
+ $('#context-menu-' + id).bind('click', link, clickHandler(roomJid, user));
+ }
+ }
+ // if `id` is set the menu is not empty
+ if(id) {
+ var pos = elem.offset(),
+ posLeft = Candy.Util.getPosLeftAccordingToWindowBounds(menu, pos.left),
+ posTop = Candy.Util.getPosTopAccordingToWindowBounds(menu, pos.top);
+
+ menu
+ .css({'left': posLeft.px, 'top': posTop.px})
+ .removeClass('left-top left-bottom right-top right-bottom')
+ .addClass(posLeft.backgroundPositionAlignment + '-' + posTop.backgroundPositionAlignment)
+ .fadeIn('fast');
+
+ /** 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', {
+ 'roomJid' : roomJid,
+ 'user' : user,
+ 'element': menu
+ });
+
+ return true;
+ }
+ },
+
+ /** Function: getMenuLinks
+ * 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, id;
+
+ var evtData = {
+ 'roomJid' : roomJid,
+ 'user' : user,
+ 'elem': elem,
+ 'menulinks': this.initialMenuLinks(elem)
+ };
+
+ /** 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)) {
+ delete menulinks[id];
+ }
+ }
+ return menulinks;
+ },
+
+ /** Function: initialMenuLinks
+ * Returns initial menulinks. The following are initial:
+ *
+ * - Private Chat
+ * - Ignore
+ * - Unignore
+ * - Kick
+ * - Ban
+ * - Change Subject
+ *
+ * Returns:
+ * (Object) - object containing those menulinks
+ */
+ initialMenuLinks: function() {
+ return {
+ 'private': {
+ requiredPermission: function(user, me) {
+ return me.getNick() !== user.getNick() && Candy.Core.getRoom(Candy.View.getCurrent().roomJid) && !Candy.Core.getUser().isInPrivacyList('ignore', user.getJid());
+ },
+ 'class' : 'private',
+ 'label' : $.i18n._('privateActionLabel'),
+ 'callback' : function(e, roomJid, user) {
+ $('#user-' + Candy.Util.jidToId(roomJid) + '-' + Candy.Util.jidToId(user.getJid())).click();
+ }
+ },
+ 'ignore': {
+ requiredPermission: function(user, me) {
+ return me.getNick() !== user.getNick() && !Candy.Core.getUser().isInPrivacyList('ignore', user.getJid());
+ },
+ 'class' : 'ignore',
+ 'label' : $.i18n._('ignoreActionLabel'),
+ 'callback' : function(e, roomJid, user) {
+ Candy.View.Pane.Room.ignoreUser(roomJid, user.getJid());
+ }
+ },
+ 'unignore': {
+ requiredPermission: function(user, me) {
+ return me.getNick() !== user.getNick() && Candy.Core.getUser().isInPrivacyList('ignore', user.getJid());
+ },
+ 'class' : 'unignore',
+ 'label' : $.i18n._('unignoreActionLabel'),
+ 'callback' : function(e, roomJid, user) {
+ Candy.View.Pane.Room.unignoreUser(roomJid, user.getJid());
+ }
+ },
+ 'kick': {
+ requiredPermission: function(user, me) {
+ return me.getNick() !== user.getNick() && me.isModerator() && !user.isModerator();
+ },
+ 'class' : 'kick',
+ 'label' : $.i18n._('kickActionLabel'),
+ 'callback' : function(e, roomJid, user) {
+ self.Chat.Modal.show(Mustache.to_html(Candy.View.Template.Chat.Context.contextModalForm, {
+ _label: $.i18n._('reason'),
+ _submit: $.i18n._('kickActionLabel')
+ }), true);
+ $('#context-modal-field').focus();
+ $('#context-modal-form').submit(function() {
+ Candy.Core.Action.Jabber.Room.Admin.UserAction(roomJid, user.getJid(), 'kick', $('#context-modal-field').val());
+ self.Chat.Modal.hide();
+ return false; // stop propagation & preventDefault, as otherwise you get disconnected (wtf?)
+ });
+ }
+ },
+ 'ban': {
+ requiredPermission: function(user, me) {
+ return me.getNick() !== user.getNick() && me.isModerator() && !user.isModerator();
+ },
+ 'class' : 'ban',
+ 'label' : $.i18n._('banActionLabel'),
+ 'callback' : function(e, roomJid, user) {
+ self.Chat.Modal.show(Mustache.to_html(Candy.View.Template.Chat.Context.contextModalForm, {
+ _label: $.i18n._('reason'),
+ _submit: $.i18n._('banActionLabel')
+ }), true);
+ $('#context-modal-field').focus();
+ $('#context-modal-form').submit(function() {
+ Candy.Core.Action.Jabber.Room.Admin.UserAction(roomJid, user.getJid(), 'ban', $('#context-modal-field').val());
+ self.Chat.Modal.hide();
+ return false; // stop propagation & preventDefault, as otherwise you get disconnected (wtf?)
+ });
+ }
+ },
+ 'subject': {
+ requiredPermission: function(user, me) {
+ return me.getNick() === user.getNick() && me.isModerator();
+ },
+ 'class': 'subject',
+ 'label' : $.i18n._('setSubjectActionLabel'),
+ 'callback': function(e, roomJid) {
+ self.Chat.Modal.show(Mustache.to_html(Candy.View.Template.Chat.Context.contextModalForm, {
+ _label: $.i18n._('subject'),
+ _submit: $.i18n._('setSubjectActionLabel')
+ }), true);
+ $('#context-modal-field').focus();
+ $('#context-modal-form').submit(function(e) {
+ Candy.Core.Action.Jabber.Room.Admin.SetSubject(roomJid, $('#context-modal-field').val());
+ self.Chat.Modal.hide();
+ e.preventDefault();
+ });
+ }
+ }
+ };
+ },
+
+ /** Function: showEmoticonsMenu
+ * Shows the special emoticons menu
+ *
+ * Parameters:
+ * (Element) elem - Element on which it should be positioned to.
+ *
+ * Returns:
+ * (Boolean) - true
+ */
+ showEmoticonsMenu: function(elem) {
+ elem = $(elem);
+ var pos = elem.offset(),
+ menu = $('#context-menu'),
+ content = $('ul', menu),
+ emoticons = '',
+ i;
+
+ $('#tooltip').hide();
+
+ for(i = Candy.Util.Parser.emoticons.length-1; i >= 0; i--) {
+ emoticons = '<img src="' + Candy.Util.Parser._emoticonPath + Candy.Util.Parser.emoticons[i].image + '" alt="' + Candy.Util.Parser.emoticons[i].plain + '" />' + emoticons;
+ }
+ content.html('<li class="emoticons">' + emoticons + '</li>');
+ content.find('img').click(function() {
+ var input = Candy.View.Pane.Room.getPane(Candy.View.getCurrent().roomJid, '.message-form').children('.field'),
+ value = input.val(),
+ emoticon = $(this).attr('alt') + ' ';
+ input.val(value ? value + ' ' + emoticon : emoticon).focus();
+
+ // Once you make a selction, hide the menu.
+ menu.hide();
+ });
+
+ var posLeft = Candy.Util.getPosLeftAccordingToWindowBounds(menu, pos.left),
+ posTop = Candy.Util.getPosTopAccordingToWindowBounds(menu, pos.top);
+
+ 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;
+ }
+ }
+ };
+
+ return self;
+}(Candy.View.Pane || {}, jQuery));
diff --git a/src/view/pane/message.js b/src/view/pane/message.js
new file mode 100644
index 0000000..b4bcbb6
--- /dev/null
+++ b/src/view/pane/message.js
@@ -0,0 +1,253 @@
+/** File: message.js
+ * Candy - Chats are not dead yet.
+ *
+ * Authors:
+ * - Patrick Stadler <patrick.stadler@gmail.com>
+ * - Michael Weibel <michael.weibel@gmail.com>
+ *
+ * Copyright:
+ * (c) 2011 Amiado Group AG. All rights reserved.
+ * (c) 2012-2014 Patrick Stadler & Michael Weibel. All rights reserved.
+ */
+'use strict';
+
+/* global Candy, Mustache, jQuery */
+
+/** Class: Candy.View.Pane
+ * Candy view pane handles everything regarding DOM updates etc.
+ *
+ * Parameters:
+ * (Candy.View.Pane) self - itself
+ * (jQuery) $ - jQuery
+ */
+Candy.View.Pane = (function(self, $) {
+
+ /** Class: Candy.View.Pane.Message
+ * Message submit/show handling
+ */
+ self.Message = {
+ /** Function: submit
+ * on submit handler for message field sends the message to the server and if it's a private chat, shows the message
+ * immediately because the server doesn't send back those message.
+ *
+ * Parameters:
+ * (Event) event - Triggered event
+ *
+ * Triggers:
+ * candy:view.message.before-send using {message}
+ *
+ * FIXME: as everywhere, `roomJid` might be slightly incorrect in this case
+ * - maybe rename this as part of a refactoring.
+ */
+ submit: function(event) {
+ var roomJid = Candy.View.getCurrent().roomJid,
+ room = Candy.View.Pane.Chat.rooms[roomJid],
+ roomType = room.type,
+ targetJid = room.targetJid,
+ message = $(this).children('.field').val().substring(0, Candy.View.getOptions().crop.message.body),
+ xhtmlMessage,
+ evtData = {
+ roomJid: roomJid,
+ message: message,
+ xhtmlMessage: xhtmlMessage
+ };
+
+ /** Event: candy:view.message.before-send
+ * Before sending a message
+ *
+ * Parameters:
+ * (String) roomJid - room to which the message should be sent
+ * (String) message - Message text
+ * (String) xhtmlMessage - XHTML formatted message [default: undefined]
+ *
+ * Returns:
+ * Boolean|undefined - if you like to stop sending the message, return false.
+ */
+ if($(Candy).triggerHandler('candy:view.message.before-send', evtData) === false) {
+ event.preventDefault();
+ return;
+ }
+
+ message = evtData.message;
+ xhtmlMessage = evtData.xhtmlMessage;
+
+ Candy.Core.Action.Jabber.Room.Message(targetJid, message, roomType, xhtmlMessage);
+ // Private user chat. Jabber won't notify the user who has sent the message. Just show it as the user hits the button...
+ if(roomType === 'chat' && message) {
+ self.Message.show(roomJid, self.Room.getUser(roomJid).getNick(), message, xhtmlMessage, undefined, Candy.Core.getUser().getJid());
+ }
+ // Clear input and set focus to it
+ $(this).children('.field').val('').focus();
+ event.preventDefault();
+ },
+
+ /** Function: show
+ * Show a message in the message pane
+ *
+ * Parameters:
+ * (String) roomJid - room in which the message has been sent to
+ * (String) name - Name of the user which sent the message
+ * (String) message - Message
+ * (String) xhtmlMessage - XHTML formatted message [if options enableXHTML is true]
+ * (String) timestamp - [optional] Timestamp of the message, if not present, current date.
+ * (Boolean) carbon - [optional] Indication of wether or not the message was a carbon
+ *
+ * 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, xhtmlMessage, timestamp, from, carbon, stanza) {
+ message = Candy.Util.Parser.all(message.substring(0, Candy.View.getOptions().crop.message.body));
+ if(Candy.View.getOptions().enableXHTML === true && xhtmlMessage) {
+ xhtmlMessage = Candy.Util.parseAndCropXhtml(xhtmlMessage, Candy.View.getOptions().crop.message.body);
+ }
+
+ timestamp = timestamp || new Date();
+
+ // Assume we have an ISO-8601 date string and convert it to a Date object
+ if (!timestamp.toDateString) {
+ timestamp = Candy.Util.iso8601toDate(timestamp);
+ }
+
+ // Before we add the new message, check to see if we should be automatically scrolling or not.
+ var messagePane = self.Room.getPane(roomJid, '.message-pane');
+ var enableScroll = ((messagePane.scrollTop() + messagePane.outerHeight()) === messagePane.prop('scrollHeight')) || !$(messagePane).is(':visible');
+ Candy.View.Pane.Chat.rooms[roomJid].enableScroll = enableScroll;
+
+ var evtData = {
+ 'roomJid': roomJid,
+ 'name': name,
+ 'message': message,
+ 'xhtmlMessage': xhtmlMessage,
+ 'from': from,
+ 'stanza': stanza
+ };
+
+ /** 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
+ *
+ * Returns:
+ * Boolean - if you don't want to show the message, return false
+ */
+ if($(Candy).triggerHandler('candy:view.message.before-show', evtData) === false) {
+ return;
+ }
+
+ message = evtData.message;
+ xhtmlMessage = evtData.xhtmlMessage;
+ if(xhtmlMessage !== undefined && xhtmlMessage.length > 0) {
+ message = xhtmlMessage;
+ }
+
+ if(!message) {
+ return;
+ }
+
+ 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),
+ timestamp: timestamp.toISOString(),
+ roomjid: roomJid,
+ from: from
+ },
+ stanza: stanza
+ };
+
+ /** 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 of message
+ * - (String) timestamp - ISO formatted timestamp of message
+ */
+ $(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.label').click(function(event) {
+ event.preventDefault();
+ // Check if user is online and not myself
+ var room = Candy.Core.getRoom(roomJid);
+ if(room && name !== self.Room.getUser(Candy.View.getCurrent().roomJid).getNick() && room.getRoster().get(roomJid + '/' + name)) {
+ if(Candy.View.Pane.PrivateRoom.open(roomJid + '/' + name, name, true) === false) {
+ return false;
+ }
+ }
+ });
+
+ if (!carbon) {
+ var notifyEvtData = {
+ name: name,
+ displayName: Candy.Util.crop(name, Candy.View.getOptions().crop.message.nickname),
+ roomJid: roomJid,
+ message: message,
+ time: Candy.Util.localizedTime(timestamp),
+ timestamp: timestamp.toISOString()
+ };
+ /** Event: candy:view.message.notify
+ * Notify the user (optionally) that a new message has been received
+ *
+ * Parameters:
+ * (Object) templateData - Template data consists of:
+ * - (String) name - Name of the sending user
+ * - (String) displayName - Cropped name of the sending user
+ * - (String) roomJid - JID into which the message was sent
+ * - (String) message - Message text
+ * - (String) time - Localized time of message
+ * - (String) timestamp - ISO formatted timestamp of message
+ * - (Boolean) carbon - Indication of wether or not the message was a carbon
+ */
+ $(Candy).triggerHandler('candy:view.message.notify', notifyEvtData);
+
+ // Check to see if in-core notifications are disabled
+ if(!Candy.Core.getOptions().disableCoreNotifications) {
+ if(Candy.View.getCurrent().roomJid !== roomJid || !self.Window.hasFocus()) {
+ self.Chat.increaseUnreadMessages(roomJid);
+ if(!self.Window.hasFocus()) {
+ // Notify the user about a new private message OR on all messages if configured
+ if(Candy.View.Pane.Chat.rooms[roomJid].type === 'chat' || Candy.View.getOptions().updateWindowOnAllMessages === true) {
+ self.Chat.Toolbar.playSound();
+ }
+ }
+ }
+ }
+
+ if(Candy.View.getCurrent().roomJid === roomJid) {
+ self.Room.scrollToBottom(roomJid);
+ }
+ }
+
+ evtData.element = elem;
+
+ /** 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);
+ }
+ };
+
+ return self;
+}(Candy.View.Pane || {}, jQuery));
diff --git a/src/view/pane/privateRoom.js b/src/view/pane/privateRoom.js
new file mode 100644
index 0000000..67ef58d
--- /dev/null
+++ b/src/view/pane/privateRoom.js
@@ -0,0 +1,181 @@
+/** File: privateRoom.js
+ * Candy - Chats are not dead yet.
+ *
+ * Authors:
+ * - Patrick Stadler <patrick.stadler@gmail.com>
+ * - Michael Weibel <michael.weibel@gmail.com>
+ *
+ * Copyright:
+ * (c) 2011 Amiado Group AG. All rights reserved.
+ * (c) 2012-2014 Patrick Stadler & Michael Weibel. All rights reserved.
+ */
+'use strict';
+
+/* global Candy, Strophe, jQuery */
+
+/** Class: Candy.View.Pane
+ * Candy view pane handles everything regarding DOM updates etc.
+ *
+ * Parameters:
+ * (Candy.View.Pane) self - itself
+ * (jQuery) $ - jQuery
+ */
+Candy.View.Pane = (function(self, $) {
+
+ /** Class: Candy.View.Pane.PrivateRoom
+ * Private room handling
+ */
+ self.PrivateRoom = {
+ /** Function: open
+ * Opens a new private room
+ *
+ * Parameters:
+ * (String) roomJid - Room jid to open
+ * (String) roomName - Room name
+ * (Boolean) switchToRoom - If true, displayed room switches automatically to this room
+ * (e.g. when user clicks itself on another user to open a private chat)
+ * (Boolean) isNoConferenceRoomJid - true if a 3rd-party client sends a direct message to this user (not via the room)
+ * then the username is the node and not the resource. This param addresses this case.
+ *
+ * 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)),
+ evtData = {
+ 'roomJid': roomJid,
+ 'roomName': roomName,
+ 'type': 'chat',
+ };
+
+ /** Event: candy:view.private-room.before-open
+ * Before opening a new private room
+ *
+ * Parameters:
+ * (String) roomJid - Room JID
+ * (String) roomName - Room name
+ * (String) type - 'chat'
+ *
+ * Returns:
+ * Boolean - if you don't want to open the private room, return false
+ */
+ if($(Candy).triggerHandler('candy:view.private-room.before-open', evtData) === false) {
+ return false;
+ }
+
+ // if target user is in privacy list, don't open the private chat.
+ if (Candy.Core.getUser().isInPrivacyList('ignore', roomJid)) {
+ return false;
+ }
+ if(!self.Chat.rooms[roomJid]) {
+ if(self.Room.init(roomJid, roomName, 'chat') === false) {
+ return false;
+ }
+ }
+ 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');
+
+ evtData.element = self.Room.getPane(roomJid);
+ /** 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
+ * Set offline or online status for private rooms (when one of the participants leaves the room)
+ *
+ * Parameters:
+ * (String) roomJid - Private room jid
+ * (String) status - "leave"/"join"
+ */
+ setStatus: function(roomJid, status) {
+ var messageForm = self.Room.getPane(roomJid, '.message-form');
+ if(status === 'join') {
+ self.Chat.getTab(roomJid).addClass('online').removeClass('offline');
+
+ messageForm.children('.field').removeAttr('disabled');
+ messageForm.children('.submit').removeAttr('disabled');
+
+ self.Chat.getTab(roomJid);
+ } else if(status === 'leave') {
+ self.Chat.getTab(roomJid).addClass('offline').removeClass('online');
+
+ messageForm.children('.field').attr('disabled', true);
+ messageForm.children('.submit').attr('disabled', true);
+ }
+ },
+
+ /** Function: changeNick
+ * Changes the nick for every private room opened with this roomJid.
+ *
+ * Parameters:
+ * (String) roomJid - Public room jid
+ * (Candy.Core.ChatUser) user - User which changes his nick
+ */
+ changeNick: function changeNick(roomJid, user) {
+ Candy.Core.log('[View:Pane:PrivateRoom] changeNick');
+
+ var previousPrivateRoomJid = roomJid + '/' + user.getPreviousNick(),
+ newPrivateRoomJid = roomJid + '/' + user.getNick(),
+ previousPrivateRoomId = Candy.Util.jidToId(previousPrivateRoomJid),
+ newPrivateRoomId = Candy.Util.jidToId(newPrivateRoomJid),
+ room = self.Chat.rooms[previousPrivateRoomJid],
+ roomElement,
+ roomTabElement;
+
+ // it could happen that the new private room is already existing -> close it first.
+ // if this is not done, errors appear as two rooms would have the same id
+ if (self.Chat.rooms[newPrivateRoomJid]) {
+ self.Room.close(newPrivateRoomJid);
+ }
+
+ if (room) { /* someone I talk with, changed nick */
+ room.name = user.getNick();
+ room.id = newPrivateRoomId;
+
+ self.Chat.rooms[newPrivateRoomJid] = room;
+ delete self.Chat.rooms[previousPrivateRoomJid];
+
+ roomElement = $('#chat-room-' + previousPrivateRoomId);
+ if (roomElement) {
+ roomElement.attr('data-roomjid', newPrivateRoomJid);
+ roomElement.attr('id', 'chat-room-' + newPrivateRoomId);
+
+ roomTabElement = $('#chat-tabs li[data-roomjid="' + previousPrivateRoomJid + '"]');
+ roomTabElement.attr('data-roomjid', newPrivateRoomJid);
+
+ /* TODO: The '@' is defined in the template. Somehow we should
+ * extract both things into our CSS or do something else to prevent that.
+ */
+ roomTabElement.children('a.label').text('@' + user.getNick());
+
+ if (Candy.View.getCurrent().roomJid === previousPrivateRoomJid) {
+ Candy.View.getCurrent().roomJid = newPrivateRoomJid;
+ }
+ }
+ } else { /* I changed the nick */
+ roomElement = $('.room-pane.roomtype-chat[data-userjid="' + previousPrivateRoomJid + '"]');
+ if (roomElement.length) {
+ previousPrivateRoomId = Candy.Util.jidToId(roomElement.attr('data-roomjid'));
+ roomElement.attr('data-userjid', newPrivateRoomJid);
+ }
+ }
+ if (roomElement && roomElement.length) {
+ self.Roster.changeNick(previousPrivateRoomId, user);
+ }
+ }
+ };
+
+ return self;
+}(Candy.View.Pane || {}, jQuery));
diff --git a/src/view/pane/room.js b/src/view/pane/room.js
new file mode 100644
index 0000000..bfb95f0
--- /dev/null
+++ b/src/view/pane/room.js
@@ -0,0 +1,484 @@
+/** File: room.js
+ * Candy - Chats are not dead yet.
+ *
+ * Authors:
+ * - Patrick Stadler <patrick.stadler@gmail.com>
+ * - Michael Weibel <michael.weibel@gmail.com>
+ *
+ * Copyright:
+ * (c) 2011 Amiado Group AG. All rights reserved.
+ * (c) 2012-2014 Patrick Stadler & Michael Weibel. All rights reserved.
+ */
+'use strict';
+
+/* global Candy, Mustache, Strophe, jQuery */
+
+/** Class: Candy.View.Pane
+ * Candy view pane handles everything regarding DOM updates etc.
+ *
+ * Parameters:
+ * (Candy.View.Pane) self - itself
+ * (jQuery) $ - jQuery
+ */
+Candy.View.Pane = (function(self, $) {
+
+ /** Class: Candy.View.Pane.Room
+ * Everything which belongs to room view things belongs here.
+ */
+ self.Room = {
+ /** Function: init
+ * Initialize a new room and inserts the room html into the DOM
+ *
+ * Parameters:
+ * (String) roomJid - Room JID
+ * (String) roomName - Room name
+ * (String) roomType - Type: either "groupchat" or "chat" (private chat)
+ *
+ * Uses:
+ * - <Candy.Util.jidToId>
+ * - <Candy.View.Pane.Chat.addTab>
+ * - <getPane>
+ *
+ * Triggers:
+ * candy:view.room.after-add using {roomJid, type, element}
+ *
+ * Returns:
+ * (String) - the room id of the element created.
+ */
+ init: function(roomJid, roomName, roomType) {
+ roomType = roomType || 'groupchat';
+ roomJid = Candy.Util.unescapeJid(roomJid);
+
+ var evtData = {
+ roomJid: roomJid,
+ type: roomType
+ };
+ /** Event: candy:view.room.before-add
+ * Before initialising a room
+ *
+ * Parameters:
+ * (String) roomJid - Room JID
+ * (String) type - Room Type
+ *
+ * Returns:
+ * Boolean - if you don't want to initialise the room, return false.
+ */
+ if($(Candy).triggerHandler('candy:view.room.before-add', evtData) === false) {
+ return false;
+ }
+
+ // First room, show sound control
+ if(Candy.Util.isEmptyObject(self.Chat.rooms)) {
+ self.Chat.Toolbar.show();
+ }
+
+ var roomId = Candy.Util.jidToId(roomJid);
+ self.Chat.rooms[roomJid] = {id: roomId, usercount: 0, name: roomName, type: roomType, messageCount: 0, scrollPosition: -1, targetJid: roomJid};
+
+ $('#chat-rooms').append(Mustache.to_html(Candy.View.Template.Room.pane, {
+ roomId: roomId,
+ roomJid: roomJid,
+ roomType: roomType,
+ form: {
+ _messageSubmit: $.i18n._('messageSubmit')
+ },
+ roster: {
+ _userOnline: $.i18n._('userOnline')
+ }
+ }, {
+ roster: Candy.View.Template.Roster.pane,
+ messages: Candy.View.Template.Message.pane,
+ form: Candy.View.Template.Room.form
+ }));
+ self.Chat.addTab(roomJid, roomName, roomType);
+ self.Room.getPane(roomJid, '.message-form').submit(self.Message.submit);
+ self.Room.scrollToBottom(roomJid);
+
+ evtData.element = self.Room.getPane(roomJid);
+
+ /** 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;
+ },
+
+ /** Function: show
+ * Show a specific room and hides the other rooms (if there are any)
+ *
+ * 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,
+ evtData;
+
+ $('.room-pane').each(function() {
+ var elem = $(this);
+ evtData = {
+ 'roomJid': elem.attr('data-roomjid'),
+ 'element' : elem
+ };
+
+ if(elem.attr('id') === ('chat-room-' + roomId)) {
+ elem.show();
+ Candy.View.getCurrent().roomJid = roomJid;
+ self.Chat.setActiveTab(roomJid);
+ self.Chat.Toolbar.update(roomJid);
+ self.Chat.clearUnreadMessages(roomJid);
+ self.Room.setFocusToForm(roomJid);
+ self.Room.scrollToBottom(roomJid);
+
+ /** 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();
+
+ /** 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);
+ }
+ });
+ },
+
+ /** 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
+ */
+ setSubject: function(roomJid, subject) {
+ subject = Candy.Util.Parser.linkify(Candy.Util.Parser.escape(subject));
+ var timestamp = new Date();
+ var html = Mustache.to_html(Candy.View.Template.Room.subject, {
+ subject: subject,
+ roomName: self.Chat.rooms[roomJid].name,
+ _roomSubject: $.i18n._('roomSubject'),
+ time: Candy.Util.localizedTime(timestamp),
+ timestamp: timestamp.toISOString()
+ });
+ self.Room.appendToMessagePane(roomJid, html);
+ self.Room.scrollToBottom(roomJid);
+
+ /** 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', {
+ 'roomJid': roomJid,
+ 'element' : self.Room.getPane(roomJid),
+ 'subject' : subject
+ });
+ },
+
+ /** Function: close
+ * Close a room and remove everything in the DOM belonging to this room.
+ *
+ * NOTICE: There's a rendering bug in Opera when all rooms have been closed.
+ * (Take a look in the source for a more detailed description)
+ *
+ * Triggers:
+ * candy:view.room.after-close using {roomJid}
+ *
+ * Parameters:
+ * (String) roomJid - Room to close
+ */
+ close: function(roomJid) {
+ self.Chat.removeTab(roomJid);
+ self.Window.clearUnreadMessages();
+
+ /* TODO:
+ There's a rendering bug in Opera which doesn't redraw (remove) the message form.
+ Only a cosmetical issue (when all tabs are closed) but it's annoying...
+ This happens when form has no focus too. Maybe it's because of CSS positioning.
+ */
+ self.Room.getPane(roomJid).remove();
+ var openRooms = $('#chat-rooms').children();
+ if(Candy.View.getCurrent().roomJid === roomJid) {
+ Candy.View.getCurrent().roomJid = null;
+ if(openRooms.length === 0) {
+ self.Chat.allTabsClosed();
+ } else {
+ self.Room.show(openRooms.last().attr('data-roomjid'));
+ }
+ }
+ delete self.Chat.rooms[roomJid];
+
+ /** Event: candy:view.room.after-close
+ * After closing a room
+ *
+ * Parameters:
+ * (String) roomJid - Room JID
+ */
+ $(Candy).triggerHandler('candy:view.room.after-close', {
+ 'roomJid' : roomJid
+ });
+ },
+
+ /** Function: appendToMessagePane
+ * Append a new message to the message pane.
+ *
+ * Parameters:
+ * (String) roomJid - Room JID
+ * (String) html - rendered message html
+ */
+ appendToMessagePane: function(roomJid, html) {
+ self.Room.getPane(roomJid, '.message-pane').append(html);
+ self.Chat.rooms[roomJid].messageCount++;
+ self.Room.sliceMessagePane(roomJid);
+ },
+
+ /** Function: sliceMessagePane
+ * Slices the message pane after the max amount of messages specified in the Candy View options (limit setting).
+ *
+ * This is done to hopefully prevent browsers from getting slow after a certain amount of messages in the DOM.
+ *
+ * The slice is only done when autoscroll is on, because otherwise someone might lose exactly the message he want to look for.
+ *
+ * Parameters:
+ * (String) roomJid - Room JID
+ */
+ sliceMessagePane: function(roomJid) {
+ // Only clean if autoscroll is enabled
+ if(self.Window.autoscroll) {
+ var options = Candy.View.getOptions().messages;
+ if(self.Chat.rooms[roomJid].messageCount > options.limit) {
+ self.Room.getPane(roomJid, '.message-pane').children().slice(0, options.remove).remove();
+ self.Chat.rooms[roomJid].messageCount -= options.remove;
+ }
+ }
+ },
+
+ /** Function: scrollToBottom
+ * Scroll to bottom wrapper for <onScrollToBottom> to be able to disable it by overwriting the function.
+ *
+ * Parameters:
+ * (String) roomJid - Room JID
+ *
+ * Uses:
+ * - <onScrollToBottom>
+ */
+ scrollToBottom: function(roomJid) {
+ self.Room.onScrollToBottom(roomJid);
+ },
+
+ /** Function: onScrollToBottom
+ * Scrolls to the latest message received/sent.
+ *
+ * Parameters:
+ * (String) roomJid - Room JID
+ */
+ onScrollToBottom: function(roomJid) {
+ var messagePane = self.Room.getPane(roomJid, '.message-pane');
+
+ if (Candy.View.Pane.Chat.rooms[roomJid].enableScroll === true) {
+ messagePane.scrollTop(messagePane.prop('scrollHeight'));
+ } else {
+ return false;
+ }
+ },
+
+ /** Function: onScrollToStoredPosition
+ * When autoscroll is off, the position where the scrollbar is has to be stored for each room, because it otherwise
+ * goes to the top in the message window.
+ *
+ * Parameters:
+ * (String) roomJid - Room JID
+ */
+ onScrollToStoredPosition: function(roomJid) {
+ // This should only apply when entering a room...
+ // ... therefore we set scrollPosition to -1 after execution.
+ if(self.Chat.rooms[roomJid].scrollPosition > -1) {
+ var messagePane = self.Room.getPane(roomJid, '.message-pane-wrapper');
+ messagePane.scrollTop(self.Chat.rooms[roomJid].scrollPosition);
+ self.Chat.rooms[roomJid].scrollPosition = -1;
+ }
+ },
+
+ /** Function: setFocusToForm
+ * Set focus to the message input field within the message form.
+ *
+ * Parameters:
+ * (String) roomJid - Room JID
+ */
+ setFocusToForm: function(roomJid) {
+ // If we're on mobile, don't focus the input field.
+ if (Candy.Util.isMobile()) { return true; }
+
+ var pane = self.Room.getPane(roomJid, '.message-form');
+ if (pane) {
+ // IE8 will fail maybe, because the field isn't there yet.
+ try {
+ pane.children('.field')[0].focus();
+ } catch(e) {
+ // fail silently
+ }
+ }
+ },
+
+ /** Function: setUser
+ * Sets or updates the current user in the specified room (called by <Candy.View.Pane.Roster.update>) and set specific informations
+ * (roles and affiliations) on the room tab (chat-pane).
+ *
+ * Parameters:
+ * (String) roomJid - Room in which the user is set to.
+ * (Candy.Core.ChatUser) user - The user
+ */
+ setUser: function(roomJid, user) {
+ self.Chat.rooms[roomJid].user = user;
+ var roomPane = self.Room.getPane(roomJid),
+ chatPane = $('#chat-pane');
+
+ roomPane.attr('data-userjid', user.getJid());
+ // Set classes based on user role / affiliation
+ if(user.isModerator()) {
+ if (user.getRole() === user.ROLE_MODERATOR) {
+ chatPane.addClass('role-moderator');
+ }
+ if (user.getAffiliation() === user.AFFILIATION_OWNER) {
+ chatPane.addClass('affiliation-owner');
+ }
+ } else {
+ chatPane.removeClass('role-moderator affiliation-owner');
+ }
+ self.Chat.Context.init();
+ },
+
+ /** Function: getUser
+ * Get the current user in the room specified with the jid
+ *
+ * Parameters:
+ * (String) roomJid - Room of which the user should be returned from
+ *
+ * Returns:
+ * (Candy.Core.ChatUser) - user
+ */
+ getUser: function(roomJid) {
+ return self.Chat.rooms[roomJid].user;
+ },
+
+ /** Function: ignoreUser
+ * Ignore specified user and add the ignore icon to the roster item of the user
+ *
+ * Parameters:
+ * (String) roomJid - Room in which the user should be ignored
+ * (String) userJid - User which should be ignored
+ */
+ ignoreUser: function(roomJid, userJid) {
+ Candy.Core.Action.Jabber.Room.IgnoreUnignore(userJid);
+ Candy.View.Pane.Room.addIgnoreIcon(roomJid, userJid);
+ },
+
+ /** Function: unignoreUser
+ * Unignore an ignored user and remove the ignore icon of the roster item.
+ *
+ * Parameters:
+ * (String) roomJid - Room in which the user should be unignored
+ * (String) userJid - User which should be unignored
+ */
+ unignoreUser: function(roomJid, userJid) {
+ Candy.Core.Action.Jabber.Room.IgnoreUnignore(userJid);
+ Candy.View.Pane.Room.removeIgnoreIcon(roomJid, userJid);
+ },
+
+ /** Function: addIgnoreIcon
+ * Add the ignore icon to the roster item of the specified user
+ *
+ * Parameters:
+ * (String) roomJid - Room in which the roster item should be updated
+ * (String) userJid - User of which the roster item should be updated
+ */
+ addIgnoreIcon: function(roomJid, userJid) {
+ if (Candy.View.Pane.Chat.rooms[userJid]) {
+ $('#user-' + Candy.View.Pane.Chat.rooms[userJid].id + '-' + Candy.Util.jidToId(userJid)).addClass('status-ignored');
+ }
+ if (Candy.View.Pane.Chat.rooms[Strophe.getBareJidFromJid(roomJid)]) {
+ $('#user-' + Candy.View.Pane.Chat.rooms[Strophe.getBareJidFromJid(roomJid)].id + '-' + Candy.Util.jidToId(userJid)).addClass('status-ignored');
+ }
+ },
+
+ /** Function: removeIgnoreIcon
+ * Remove the ignore icon to the roster item of the specified user
+ *
+ * Parameters:
+ * (String) roomJid - Room in which the roster item should be updated
+ * (String) userJid - User of which the roster item should be updated
+ */
+ removeIgnoreIcon: function(roomJid, userJid) {
+ if (Candy.View.Pane.Chat.rooms[userJid]) {
+ $('#user-' + Candy.View.Pane.Chat.rooms[userJid].id + '-' + Candy.Util.jidToId(userJid)).removeClass('status-ignored');
+ }
+ if (Candy.View.Pane.Chat.rooms[Strophe.getBareJidFromJid(roomJid)]) {
+ $('#user-' + Candy.View.Pane.Chat.rooms[Strophe.getBareJidFromJid(roomJid)].id + '-' + Candy.Util.jidToId(userJid)).removeClass('status-ignored');
+ }
+ },
+
+ /** Function: getPane
+ * Get the chat room pane or a subPane of it (if subPane is specified)
+ *
+ * Parameters:
+ * (String) roomJid - Room in which the pane lies
+ * (String) subPane - Sub pane of the chat room pane if needed [optional]
+ */
+ getPane: function(roomJid, subPane) {
+ if (self.Chat.rooms[roomJid]) {
+ if(subPane) {
+ if(self.Chat.rooms[roomJid]['pane-' + subPane]) {
+ return self.Chat.rooms[roomJid]['pane-' + subPane];
+ } else {
+ self.Chat.rooms[roomJid]['pane-' + subPane] = $('#chat-room-' + self.Chat.rooms[roomJid].id).find(subPane);
+ return self.Chat.rooms[roomJid]['pane-' + subPane];
+ }
+ } else {
+ return $('#chat-room-' + self.Chat.rooms[roomJid].id);
+ }
+ }
+ },
+
+ /** Function: changeDataUserJidIfUserIsMe
+ * Changes the room's data-userjid attribute if the specified user is the current user.
+ *
+ * Parameters:
+ * (String) roomId - Id of the room
+ * (Candy.Core.ChatUser) user - User
+ */
+ changeDataUserJidIfUserIsMe: function(roomId, user) {
+ if (user.getNick() === Candy.Core.getUser().getNick()) {
+ var roomElement = $('#chat-room-' + roomId);
+ roomElement.attr('data-userjid', Strophe.getBareJidFromJid(roomElement.attr('data-userjid')) + '/' + user.getNick());
+ }
+ }
+ };
+
+ return self;
+}(Candy.View.Pane || {}, jQuery));
diff --git a/src/view/pane/roster.js b/src/view/pane/roster.js
new file mode 100644
index 0000000..077ae50
--- /dev/null
+++ b/src/view/pane/roster.js
@@ -0,0 +1,295 @@
+/** File: roster.js
+ * Candy - Chats are not dead yet.
+ *
+ * Authors:
+ * - Patrick Stadler <patrick.stadler@gmail.com>
+ * - Michael Weibel <michael.weibel@gmail.com>
+ *
+ * Copyright:
+ * (c) 2011 Amiado Group AG. All rights reserved.
+ * (c) 2012-2014 Patrick Stadler & Michael Weibel. All rights reserved.
+ */
+'use strict';
+
+/* global Candy, Mustache, Strophe, jQuery */
+
+/** Class: Candy.View.Pane
+ * Candy view pane handles everything regarding DOM updates etc.
+ *
+ * Parameters:
+ * (Candy.View.Pane) self - itself
+ * (jQuery) $ - jQuery
+ */
+Candy.View.Pane = (function(self, $) {
+
+ /** Class Candy.View.Pane.Roster
+ * Handles everyhing regarding roster updates.
+ */
+ self.Roster = {
+ /** Function: update
+ * Called by <Candy.View.Observer.Presence.update> to update the roster if needed.
+ * Adds/removes users from the roster list or updates informations on their items (roles, affiliations etc.)
+ *
+ * TODO: Refactoring, this method has too much LOC.
+ *
+ * Parameters:
+ * (String) roomJid - Room JID in which the update happens
+ * (Candy.Core.ChatUser) user - User on which the update happens
+ * (String) action - one of "join", "leave", "kick" and "ban"
+ * (Candy.Core.ChatUser) currentUser - Current user
+ *
+ * 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) {
+ Candy.Core.log('[View:Pane:Roster] ' + action);
+ var roomId = self.Chat.rooms[roomJid].id,
+ userId = Candy.Util.jidToId(user.getJid()),
+ usercountDiff = -1,
+ userElem = $('#user-' + roomId + '-' + userId),
+ evtData = {
+ 'roomJid' : roomJid,
+ 'user' : user,
+ 'action': action,
+ 'element': userElem
+ };
+
+ /** 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', evtData);
+
+ // a user joined the room
+ if(action === 'join') {
+ usercountDiff = 1;
+
+ if(userElem.length < 1) {
+ self.Roster._insertUser(roomJid, roomId, user, userId, currentUser);
+ self.Roster.showJoinAnimation(user, userId, roomId, roomJid, currentUser);
+ // user is in room but maybe the affiliation/role has changed
+ } else {
+ usercountDiff = 0;
+ userElem.remove();
+ self.Roster._insertUser(roomJid, roomId, user, userId, currentUser);
+ // 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
+ if (currentUser !== undefined && currentUser.getNick() === user.getNick()) {
+ self.Room.setUser(roomJid, user);
+ // add click handler for private chat
+ } else {
+ $('#user-' + roomId + '-' + userId).click(self.Roster.userClick);
+ }
+
+ $('#user-' + roomId + '-' + userId + ' .context').click(function(e) {
+ self.Chat.Context.show(e.currentTarget, roomJid, user);
+ e.stopPropagation();
+ });
+
+ // check if current user is ignoring the user who has joined.
+ if (currentUser !== undefined && currentUser.isInPrivacyList('ignore', user.getJid())) {
+ Candy.View.Pane.Room.addIgnoreIcon(roomJid, user.getJid());
+ }
+ // a user left the room
+ } else if(action === 'leave') {
+ self.Roster.leaveAnimation('user-' + roomId + '-' + userId);
+ // always show leave message in private room, even if status messages have been disabled
+ if (self.Chat.rooms[roomJid].type === 'chat') {
+ self.Chat.onInfoMessage(roomJid, null, $.i18n._('userLeftRoom', [user.getNick()]));
+ } else {
+ self.Chat.infoMessage(roomJid, null, $.i18n._('userLeftRoom', [user.getNick()]), '');
+ }
+
+ } else if(action === 'nickchange') {
+ usercountDiff = 0;
+ self.Roster.changeNick(roomId, user);
+ self.Room.changeDataUserJidIfUserIsMe(roomId, user);
+ self.PrivateRoom.changeNick(roomJid, user);
+ var infoMessage = $.i18n._('userChangedNick', [user.getPreviousNick(), user.getNick()]);
+ self.Chat.infoMessage(roomJid, null, infoMessage);
+ // user has been kicked
+ } else if(action === 'kick') {
+ self.Roster.leaveAnimation('user-' + roomId + '-' + userId);
+ self.Chat.onInfoMessage(roomJid, null, $.i18n._('userHasBeenKickedFromRoom', [user.getNick()]));
+ // user has been banned
+ } else if(action === 'ban') {
+ self.Roster.leaveAnimation('user-' + roomId + '-' + userId);
+ self.Chat.onInfoMessage(roomJid, null, $.i18n._('userHasBeenBannedFromRoom', [user.getNick()]));
+ }
+
+ // Update user count
+ Candy.View.Pane.Chat.rooms[roomJid].usercount += usercountDiff;
+
+ if(roomJid === Candy.View.getCurrent().roomJid) {
+ Candy.View.Pane.Chat.Toolbar.updateUsercount(Candy.View.Pane.Chat.rooms[roomJid].usercount);
+ }
+
+
+ // in case there's been a join, the element is now there (previously not)
+ evtData.element = $('#user-' + roomId + '-' + userId);
+ /** 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);
+ },
+
+ _insertUser: function(roomJid, roomId, user, userId, currentUser) {
+ var contact = user.getContact();
+ var html = Mustache.to_html(Candy.View.Template.Roster.user, {
+ roomId: roomId,
+ userId : userId,
+ userJid: user.getJid(),
+ realJid: user.getRealJid(),
+ status: user.getStatus(),
+ contact_status: contact ? contact.getStatus() : 'unavailable',
+ nick: user.getNick(),
+ displayNick: Candy.Util.crop(user.getNick(), Candy.View.getOptions().crop.roster.nickname),
+ role: user.getRole(),
+ affiliation: user.getAffiliation(),
+ me: currentUser !== undefined && user.getNick() === currentUser.getNick(),
+ tooltipRole: $.i18n._('tooltipRole'),
+ tooltipIgnored: $.i18n._('tooltipIgnored')
+ });
+
+ var userInserted = false,
+ rosterPane = self.Room.getPane(roomJid, '.roster-pane');
+
+ // there are already users in the roster
+ if(rosterPane.children().length > 0) {
+ // insert alphabetically, sorted by status
+ var userSortCompare = self.Roster._userSortCompare(user.getNick(), user.getStatus());
+ rosterPane.children().each(function() {
+ var elem = $(this);
+ if(self.Roster._userSortCompare(elem.attr('data-nick'), elem.attr('data-status')) > userSortCompare) {
+ elem.before(html);
+ userInserted = true;
+ return false;
+ }
+ return true;
+ });
+ }
+ // first user in roster
+ if(!userInserted) {
+ rosterPane.append(html);
+ }
+ },
+
+ _userSortCompare: function(nick, status) {
+ var statusWeight;
+ switch (status) {
+ case 'available':
+ statusWeight = 1;
+ break;
+ case 'unavailable':
+ statusWeight = 9;
+ break;
+ default:
+ statusWeight = 8;
+ }
+ return statusWeight + nick.toUpperCase();
+ },
+
+ /** Function: userClick
+ * Click handler for opening a private room
+ */
+ userClick: function() {
+ var elem = $(this),
+ realJid = elem.attr('data-real-jid'),
+ useRealJid = Candy.Core.getOptions().useParticipantRealJid && (realJid !== undefined && realJid !== null && realJid !== ''),
+ targetJid = useRealJid && realJid ? Strophe.getBareJidFromJid(realJid) : elem.attr('data-jid');
+ self.PrivateRoom.open(targetJid, elem.attr('data-nick'), true, useRealJid);
+ },
+
+ /** Function: showJoinAnimation
+ * Shows join animation if needed
+ *
+ * FIXME: Refactor. Part of this will be done by the big room improvements
+ */
+ showJoinAnimation: function(user, userId, roomId, roomJid, currentUser) {
+ // don't show if the user has recently changed the nickname.
+ var rosterUserId = 'user-' + roomId + '-' + userId,
+ $rosterUserElem = $('#' + rosterUserId);
+ if (!user.getPreviousNick() || !$rosterUserElem || $rosterUserElem.is(':visible') === false) {
+ self.Roster.joinAnimation(rosterUserId);
+ // only show other users joining & don't show if there's no message in the room.
+ if(currentUser !== undefined && user.getNick() !== currentUser.getNick() && self.Room.getUser(roomJid)) {
+ // always show join message in private room, even if status messages have been disabled
+ if (self.Chat.rooms[roomJid].type === 'chat') {
+ self.Chat.onInfoMessage(roomJid, null, $.i18n._('userJoinedRoom', [user.getNick()]));
+ } else {
+ self.Chat.infoMessage(roomJid, null, $.i18n._('userJoinedRoom', [user.getNick()]));
+ }
+ }
+ }
+ },
+
+ /** Function: joinAnimation
+ * Animates specified elementId on join
+ *
+ * Parameters:
+ * (String) elementId - Specific element to do the animation on
+ */
+ joinAnimation: function(elementId) {
+ $('#' + elementId).stop(true).slideDown('normal', function() {
+ $(this).animate({opacity: 1});
+ });
+ },
+
+ /** Function: leaveAnimation
+ * Leave animation for specified element id and removes the DOM element on completion.
+ *
+ * Parameters:
+ * (String) elementId - Specific element to do the animation on
+ */
+ leaveAnimation: function(elementId) {
+ $('#' + elementId).stop(true).attr('id', '#' + elementId + '-leaving').animate({opacity: 0}, {
+ complete: function() {
+ $(this).slideUp('normal', function() {
+ $(this).remove();
+ });
+ }
+ });
+ },
+
+ /** Function: changeNick
+ * Change nick of an existing user in the roster
+ *
+ * UserId has to be recalculated from the user because at the time of this call,
+ * the user is already set with the new jid & nick.
+ *
+ * Parameters:
+ * (String) roomId - Id of the room
+ * (Candy.Core.ChatUser) user - User object
+ */
+ changeNick: function(roomId, user) {
+ Candy.Core.log('[View:Pane:Roster] changeNick');
+ var previousUserJid = Strophe.getBareJidFromJid(user.getJid()) + '/' + user.getPreviousNick(),
+ elementId = 'user-' + roomId + '-' + Candy.Util.jidToId(previousUserJid),
+ el = $('#' + elementId);
+
+ el.attr('data-nick', user.getNick());
+ el.attr('data-jid', user.getJid());
+ el.children('div.label').text(user.getNick());
+ el.attr('id', 'user-' + roomId + '-' + Candy.Util.jidToId(user.getJid()));
+ }
+ };
+
+ return self;
+}(Candy.View.Pane || {}, jQuery));
diff --git a/src/view/pane/window.js b/src/view/pane/window.js
new file mode 100644
index 0000000..b6bb489
--- /dev/null
+++ b/src/view/pane/window.js
@@ -0,0 +1,117 @@
+/** File: window.js
+ * Candy - Chats are not dead yet.
+ *
+ * Authors:
+ * - Patrick Stadler <patrick.stadler@gmail.com>
+ * - Michael Weibel <michael.weibel@gmail.com>
+ *
+ * Copyright:
+ * (c) 2011 Amiado Group AG. All rights reserved.
+ * (c) 2012-2014 Patrick Stadler & Michael Weibel. All rights reserved.
+ */
+'use strict';
+
+/* global Candy, jQuery, window */
+
+/** Class: Candy.View.Pane
+ * Candy view pane handles everything regarding DOM updates etc.
+ *
+ * Parameters:
+ * (Candy.View.Pane) self - itself
+ * (jQuery) $ - jQuery
+ */
+Candy.View.Pane = (function(self) {
+
+ /** Class: Candy.View.Pane.Window
+ * Window related view updates
+ */
+ self.Window = {
+ /** PrivateVariable: _hasFocus
+ * Window has focus
+ */
+ _hasFocus: true,
+ /** PrivateVariable: _plainTitle
+ * Document title
+ */
+ _plainTitle: window.top.document.title,
+ /** PrivateVariable: _unreadMessagesCount
+ * Unread messages count
+ */
+ _unreadMessagesCount: 0,
+
+ /** Variable: autoscroll
+ * Boolean whether autoscroll is enabled
+ */
+ autoscroll: true,
+
+ /** Function: hasFocus
+ * Checks if window has focus
+ *
+ * Returns:
+ * (Boolean)
+ */
+ hasFocus: function() {
+ return self.Window._hasFocus;
+ },
+
+ /** Function: increaseUnreadMessages
+ * Increases unread message count in window title by one.
+ */
+ increaseUnreadMessages: function() {
+ self.Window.renderUnreadMessages(++self.Window._unreadMessagesCount);
+ },
+
+ /** Function: reduceUnreadMessages
+ * Reduce unread message count in window title by `num`.
+ *
+ * Parameters:
+ * (Integer) num - Unread message count will be reduced by this value
+ */
+ reduceUnreadMessages: function(num) {
+ self.Window._unreadMessagesCount -= num;
+ if(self.Window._unreadMessagesCount <= 0) {
+ self.Window.clearUnreadMessages();
+ } else {
+ self.Window.renderUnreadMessages(self.Window._unreadMessagesCount);
+ }
+ },
+
+ /** Function: clearUnreadMessages
+ * Clear unread message count in window title.
+ */
+ clearUnreadMessages: function() {
+ self.Window._unreadMessagesCount = 0;
+ window.top.document.title = self.Window._plainTitle;
+ },
+
+ /** Function: renderUnreadMessages
+ * Update window title to show message count.
+ *
+ * Parameters:
+ * (Integer) count - Number of unread messages to show in window title
+ */
+ renderUnreadMessages: function(count) {
+ window.top.document.title = Candy.View.Template.Window.unreadmessages.replace('{{count}}', count).replace('{{title}}', self.Window._plainTitle);
+ },
+
+ /** Function: onFocus
+ * Window focus event handler.
+ */
+ onFocus: function() {
+ self.Window._hasFocus = true;
+ if (Candy.View.getCurrent().roomJid) {
+ self.Room.setFocusToForm(Candy.View.getCurrent().roomJid);
+ self.Chat.clearUnreadMessages(Candy.View.getCurrent().roomJid);
+ }
+ },
+
+ /** Function: onBlur
+ * Window blur event handler.
+ */
+ onBlur: function() {
+ self.Window._hasFocus = false;
+ }
+ };
+
+ return self;
+}(Candy.View.Pane || {}, jQuery));
diff --git a/src/view/template.js b/src/view/template.js
index 450386d..d116709 100644
--- a/src/view/template.js
+++ b/src/view/template.js
@@ -36,25 +36,19 @@ Candy.View.Template = (function(self){
'<span id="chat-modal-body"></span>' +
'<img src="{{assetsPath}}img/modal-spinner.gif" id="chat-modal-spinner" />' +
'</div><div id="chat-modal-overlay"></div>',
- adminMessage: '<li><small>{{time}}</small><div class="adminmessage">' +
+ adminMessage: '<li><small data-timestamp="{{timestamp}}">{{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>',
+ '<span class="spacer">▸</span>{{subject}} {{{message}}}</div></li>',
+ infoMessage: '<li><small data-timestamp="{{timestamp}}">{{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-sound-control" class="checked" data-tooltip="{{tooltipSound}}"></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="{{assetsPath}}audioplayer.swf"' +
- ' width="0" height="0"><param name="movie" value="{{assetsPath}}audioplayer.swf" /><param name="AllowScriptAccess"' +
- ' value="always" /><param name="FlashVars" value="listener=audioplayerListener&amp;mp3={{assetsPath}}notify.mp3" />' +
- '</object>',
Context: {
menu: '<div id="context-menu"><i class="arrow arrow-top"></i>' +
'<ul></ul><i class="arrow arrow-bottom"></i></div>',
@@ -73,7 +67,7 @@ Candy.View.Template = (function(self){
self.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">' +
+ subject: '<li><small data-timestamp="{{timestamp}}">{{time}}</small><div class="subject">' +
'<span class="label">{{roomName}}</span>' +
'<span class="spacer">▸</span>{{_roomSubject}} {{{subject}}}</div></li>',
form: '<div class="message-form-wrapper">' +
@@ -85,8 +79,8 @@ Candy.View.Template = (function(self){
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}}">' +
+ ' id="user-{{roomId}}-{{userId}}" data-jid="{{userJid}}" data-real-jid="{{realJid}}"' +
+ ' data-nick="{{nick}}" data-role="{{role}}" data-affiliation="{{affiliation}}" data-status="{{status}}">' +
'<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>' +
@@ -95,7 +89,7 @@ Candy.View.Template = (function(self){
self.Message = {
pane: '<div class="message-pane-wrapper"><ul class="message-pane"></ul></div>',
- item: '<li><small>{{time}}</small><div>' +
+ item: '<li><small data-timestamp="{{timestamp}}">{{time}}</small><div>' +
'<a class="label" href="#" class="name">{{displayName}}</a>' +
'<span class="spacer">▸</span>{{{message}}}</div></li>'
};
@@ -104,7 +98,11 @@ Candy.View.Template = (function(self){
form: '<form method="post" id="login-form" class="login-form">' +
'{{#displayNickname}}<label for="username">{{_labelNickname}}</label><input type="text" id="username" name="username"/>{{/displayNickname}}' +
'{{#displayUsername}}<label for="username">{{_labelUsername}}</label>' +
- '<input type="text" id="username" name="username"/>{{/displayUsername}}' +
+ '<input type="text" id="username" name="username"/>' +
+ '{{#displayDomain}} <span class="at-symbol">@</span> ' +
+ '<select id="domain" name="domain">{{#domains}}<option value="{{domain}}">{{domain}}</option>{{/domains}}</select>' +
+ '{{/displayDomain}}' +
+ '{{/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}}' +
diff --git a/src/view/translation.js b/src/view/translation.js
index a4ed8f5..c346234 100644
--- a/src/view/translation.js
+++ b/src/view/translation.js
@@ -56,10 +56,7 @@ Candy.View.Translation = {
'userLeftRoom' : '%s left the room.',
'userHasBeenKickedFromRoom': '%s has been kicked from the room.',
'userHasBeenBannedFromRoom': '%s has been banned from the room.',
- 'userChangedNick': '%1$s has changed his nickname to %2$s.',
-
- 'presenceUnknownWarningSubject': 'Notice:',
- 'presenceUnknownWarning' : 'This user might be offline. We can\'t track his presence.',
+ 'userChangedNick': '%1$s is now known as %2$s.',
'dateFormat': 'dd.mm.yyyy',
'timeFormat': 'HH:MM:ss',
@@ -126,9 +123,6 @@ Candy.View.Translation = {
'userHasBeenBannedFromRoom': '%s ist aus dem Raum verbannt worden.',
'userChangedNick': '%1$s hat den Nicknamen zu %2$s geändert.',
- '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',
@@ -154,25 +148,25 @@ Candy.View.Translation = {
'antiSpamMessage' : 'Bitte nicht spammen. Du wurdest für eine kurze Zeit blockiert.'
},
'fr' : {
- 'status': 'Status : %s',
+ 'status': 'Status&thinsp;: %s',
'statusConnecting': 'Connexion…',
- 'statusConnected' : 'Connecté.',
+ 'statusConnected' : 'Connecté',
'statusDisconnecting': 'Déconnexion…',
- 'statusDisconnected' : 'Déconnecté.',
- 'statusAuthfail': 'L\'authentification a échoué',
+ 'statusDisconnected' : 'Déconnecté',
+ 'statusAuthfail': 'L’identification a échoué',
- 'roomSubject' : 'Sujet :',
+ 'roomSubject' : 'Sujet&thinsp;:',
'messageSubmit': 'Envoyer',
- 'labelUsername': 'Nom d\'utilisateur :',
- 'labelNickname': 'Pseudo :',
- 'labelPassword': 'Mot de passe :',
+ 'labelUsername': 'Nom d’utilisateur&thinsp;:',
+ 'labelNickname': 'Pseudo&thinsp;:',
+ 'labelPassword': 'Mot de passe&thinsp;:',
'loginSubmit' : 'Connexion',
- 'loginInvalid' : 'JID invalide',
+ 'loginInvalid' : 'JID invalide',
- 'reason' : 'Motif :',
- 'subject' : 'Titre :',
- 'reasonWas' : 'Motif : %s.',
+ 'reason' : 'Motif&thinsp;:',
+ 'subject' : 'Titre&thinsp;:',
+ 'reasonWas' : 'Motif&thinsp;: %s.',
'kickActionLabel' : 'Kick',
'youHaveBeenKickedBy' : 'Vous avez été expulsé du salon %1$s (%2$s)',
'youHaveBeenKicked' : 'Vous avez été expulsé du salon %s',
@@ -182,42 +176,39 @@ Candy.View.Translation = {
'privateActionLabel' : 'Chat privé',
'ignoreActionLabel' : 'Ignorer',
- 'unignoreActionLabel' : 'Ne plus ignorer',
+ 'unignoreActionLabel': 'Ne plus ignorer',
'setSubjectActionLabel': 'Changer le sujet',
'administratorMessageSubject' : 'Administrateur',
- 'userJoinedRoom' : '%s vient d\'entrer dans le salon.',
+ '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',
+ 'tooltipSound' : 'Jouer un son lors de la réception de messages privés',
'tooltipAutoscroll' : 'Défilement automatique',
- 'tooltipStatusmessage' : 'Messages d\'état',
+ 'tooltipStatusmessage' : 'Afficher les changements d’état',
'tooltipAdministration' : 'Administration du salon',
- 'tooltipUsercount' : 'Nombre d\'utilisateurs dans 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 pour le salon "%s" est invalide.',
+ '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.',
+ 'nicknameConflict': 'Ce 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.',
+ 'errorMembersOnly': 'Vous ne pouvez pas entrer dans le salon %s&thinsp;: droits insuffisants.',
+ 'errorMaxOccupantsReached': 'Vous ne pouvez pas entrer dans le salon %s&thinsp;: limite d’utilisateurs atteinte.',
- 'antiSpamMessage' : 'Merci de ne pas envoyer de spam. Vous avez été bloqué pendant une courte période..'
+ 'antiSpamMessage' : 'Merci de ne pas spammer. Vous avez été bloqué pendant une courte période.'
},
'nl' : {
'status': 'Status: %s',
@@ -258,9 +249,6 @@ Candy.View.Translation = {
'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',
@@ -323,9 +311,6 @@ Candy.View.Translation = {
'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',
@@ -386,9 +371,6 @@ Candy.View.Translation = {
'userHasBeenKickedFromRoom': '%s 被请出这个房间',
'userHasBeenBannedFromRoom': '%s 被管理者禁言',
- 'presenceUnknownWarningSubject': '注意:',
- 'presenceUnknownWarning': '这个会员可能已经下线,不能追踪到他的连接信息',
-
'dateFormat': 'dd.mm.yyyy',
'timeFormat': 'HH:MM:ss',
@@ -451,9 +433,6 @@ Candy.View.Translation = {
'userHasBeenKickedFromRoom' : '%sは部屋からキックされました。',
'userHasBeenBannedFromRoom' : '%sは部屋からアカウントバンされました。',
- 'presenceUnknownWarningSubject' : '忠告:',
- 'presenceUnknownWarning' : 'このユーザーのステータスは不明です。',
-
'dateFormat' : 'dd.mm.yyyy',
'timeFormat' : 'HH:MM:ss',
@@ -516,9 +495,6 @@ Candy.View.Translation = {
'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',
@@ -581,9 +557,6 @@ Candy.View.Translation = {
'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',
@@ -607,6 +580,74 @@ Candy.View.Translation = {
'antiSpamMessage' : 'Per favore non scrivere messaggi pubblicitari. Sei stato bloccato per un po\' di tempo.'
},
+ 'pl' : {
+ 'status': 'Status: %s',
+ 'statusConnecting': 'Łączę...',
+ 'statusConnected' : 'Połączone',
+ 'statusDisconnecting': 'Rozłączam...',
+ 'statusDisconnected' : 'Rozłączone',
+ 'statusAuthfail': 'Nieprawidłowa autoryzacja',
+
+ 'roomSubject' : 'Temat:',
+ 'messageSubmit': 'Wyślij',
+
+ 'labelUsername': 'Nazwa użytkownika:',
+ 'labelNickname': 'Ksywka:',
+ 'labelPassword': 'Hasło:',
+ 'loginSubmit' : 'Zaloguj',
+ 'loginInvalid' : 'Nieprawidłowy JID',
+
+ 'reason' : 'Przyczyna:',
+ 'subject' : 'Temat:',
+ 'reasonWas' : 'Z powodu: %s.',
+ 'kickActionLabel' : 'Wykop',
+ 'youHaveBeenKickedBy' : 'Zostałeś wykopany z %2$s przez %1$s',
+ 'youHaveBeenKicked' : 'Zostałeś wykopany z %s',
+ 'banActionLabel' : 'Ban',
+ 'youHaveBeenBannedBy' : 'Zostałeś zbanowany na %1$s przez %2$s',
+ 'youHaveBeenBanned' : 'Zostałeś zbanowany na %s',
+
+ 'privateActionLabel' : 'Rozmowa prywatna',
+ 'ignoreActionLabel' : 'Zignoruj',
+ 'unignoreActionLabel' : 'Przestań ignorować',
+
+ 'setSubjectActionLabel': 'Zmień temat',
+
+ 'administratorMessageSubject' : 'Administrator',
+
+ 'userJoinedRoom' : '%s wszedł do pokoju.',
+ 'userLeftRoom' : '%s opuścił pokój.',
+ 'userHasBeenKickedFromRoom': '%s został wykopany z pokoju.',
+ 'userHasBeenBannedFromRoom': '%s został zbanowany w pokoju.',
+ 'userChangedNick': '%1$s zmienił ksywkę na %2$s.',
+
+ 'presenceUnknownWarningSubject': 'Uwaga:',
+ 'presenceUnknownWarning' : 'Rozmówca może nie być połączony. Nie możemy ustalić jego obecności.',
+
+ 'dateFormat': 'dd.mm.yyyy',
+ 'timeFormat': 'HH:MM:ss',
+
+ 'tooltipRole' : 'Moderator',
+ 'tooltipIgnored' : 'Ignorujesz tego rozmówcę',
+ 'tooltipEmoticons' : 'Emoty',
+ 'tooltipSound' : 'Sygnał dźwiękowy przy otrzymaniu wiadomości',
+ 'tooltipAutoscroll' : 'Autoprzewijanie',
+ 'tooltipStatusmessage' : 'Wyświetl statusy',
+ 'tooltipAdministration' : 'Administrator pokoju',
+ 'tooltipUsercount' : 'Obecni rozmówcy',
+
+ 'enterRoomPassword' : 'Pokój "%s" wymaga hasła.',
+ 'enterRoomPasswordSubmit' : 'Wejdź do pokoju',
+ 'passwordEnteredInvalid' : 'Niewłaściwie hasło do pokoju "%s".',
+
+ 'nicknameConflict': 'Nazwa w użyciu. Wybierz inną.',
+
+ 'errorMembersOnly': 'Nie możesz wejść do pokoju "%s": Niepełne uprawnienia.',
+ 'errorMaxOccupantsReached': 'Nie możesz wejść do pokoju "%s": Siedzi w nim zbyt wielu ludzi.',
+ 'errorAutojoinMissing': 'Konfiguracja nie zawiera parametru automatycznego wejścia do pokoju. Wskaż pokój do którego chcesz wejść.',
+
+ 'antiSpamMessage' : 'Please do not spam. You have been blocked for a short-time.'
+ },
'pt': {
'status': 'Status: %s',
'statusConnecting': 'Conectando...',
@@ -646,8 +687,6 @@ Candy.View.Translation = {
'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.',
-
'dateFormat': 'dd.mm.yyyy',
'timeFormat': 'HH:MM:ss',
@@ -710,9 +749,6 @@ Candy.View.Translation = {
'userHasBeenKickedFromRoom': '%s foi derrubado da sala.',
'userHasBeenBannedFromRoom': '%s foi banido da sala.',
- 'presenceUnknownWarningSubject': 'Aviso:',
- 'presenceUnknownWarning' : 'Este usuário pode estar desconectado.. Não conseguimos rastrear sua presença..',
-
'dateFormat': 'dd.mm.yyyy',
'timeFormat': 'HH:MM:ss',
@@ -748,6 +784,7 @@ Candy.View.Translation = {
'messageSubmit': 'Послать',
'labelUsername': 'Имя:',
+ 'labelNickname': 'Ник:',
'labelPassword': 'Пароль:',
'loginSubmit' : 'Логин',
'loginInvalid' : 'Неверный JID',
@@ -774,11 +811,12 @@ Candy.View.Translation = {
'userLeftRoom' : '%s вышел из чата.',
'userHasBeenKickedFromRoom': '%s выброшен из чата.',
'userHasBeenBannedFromRoom': '%s запрещён доступ в чат.',
+ 'userChangedNick': '%1$s сменил имя на %2$s.',
'presenceUnknownWarningSubject': 'Уведомление:',
'presenceUnknownWarning' : 'Этот пользователь вероятнее всего оффлайн.',
- 'dateFormat': 'mm.dd.yyyy',
+ 'dateFormat': 'dd.mm.yyyy',
'timeFormat': 'HH:MM:ss',
'tooltipRole' : 'Модератор',
@@ -798,6 +836,7 @@ Candy.View.Translation = {
'errorMembersOnly': 'Вы не можете войти в чат "%s": Недостаточно прав доступа.',
'errorMaxOccupantsReached': 'Вы не можете войти в чат "%s": Слишком много участников.',
+ 'errorAutojoinMissing': 'Параметры автовхода не устновлены. Настройте их для продолжения.',
'antiSpamMessage' : 'Пожалуйста не рассылайте спам. Вас заблокировали на короткое время.'
},
@@ -840,9 +879,6 @@ Candy.View.Translation = {
'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',
@@ -865,5 +901,73 @@ Candy.View.Translation = {
'errorMaxOccupantsReached': 'No pots unir-te a la sala "%s": hi ha masses participants.',
'antiSpamMessage' : 'Si us plau, no facis spam. Has estat bloquejat temporalment.'
- }
+ },
+ 'cs' : {
+ 'status': 'Stav: %s',
+ 'statusConnecting': 'Připojování...',
+ 'statusConnected': 'Připojeno',
+ 'statusDisconnecting': 'Odpojování...',
+ 'statusDisconnected': 'Odpojeno',
+ 'statusAuthfail': 'Přihlášení selhalo',
+
+ 'roomSubject': 'Předmět:',
+ 'messageSubmit': 'Odeslat',
+
+ 'labelUsername': 'Už. jméno:',
+ 'labelNickname': 'Přezdívka:',
+ 'labelPassword': 'Heslo:',
+ 'loginSubmit': 'Přihlásit se',
+ 'loginInvalid': 'Neplatné JID',
+
+ 'reason': 'Důvod:',
+ 'subject': 'Předmět:',
+ 'reasonWas': 'Důvod byl: %s.',
+ 'kickActionLabel': 'Vykopnout',
+ 'youHaveBeenKickedBy': 'Byl jsi vyloučen z %2$s uživatelem %1$s',
+ 'youHaveBeenKicked': 'Byl jsi vyloučen z %s',
+ 'banActionLabel': 'Ban',
+ 'youHaveBeenBannedBy': 'Byl jsi trvale vyloučen z %1$s uživatelem %2$s',
+ 'youHaveBeenBanned': 'Byl jsi trvale vyloučen z %s',
+
+ 'privateActionLabel': 'Soukromý chat',
+ 'ignoreActionLabel': 'Ignorovat',
+ 'unignoreActionLabel': 'Neignorovat',
+
+ 'setSubjectActionLabel': 'Změnit předmět',
+
+ 'administratorMessageSubject': 'Adminitrátor',
+
+ 'userJoinedRoom': '%s vešel do místnosti.',
+ 'userLeftRoom': '%s opustil místnost.',
+ 'userHasBeenKickedFromRoom': '%s byl vyloučen z místnosti.',
+ 'userHasBeenBannedFromRoom': '%s byl trvale vyloučen z místnosti.',
+ 'userChangedNick': '%1$s si změnil přezdívku na %2$s.',
+
+ 'presenceUnknownWarningSubject': 'Poznámka:',
+ 'presenceUnknownWarning': 'Tento uživatel může být offiline. Nemůžeme sledovat jeho přítmonost..',
+
+ 'dateFormat': 'dd.mm.yyyy',
+ 'timeFormat': 'HH:MM:ss',
+
+ 'tooltipRole': 'Moderátor',
+ 'tooltipIgnored': 'Tento uživatel je ignorován',
+ 'tooltipEmoticons': 'Emotikony',
+ 'tooltipSound': 'Přehrát zvuk při nové soukromé zprávě',
+ 'tooltipAutoscroll': 'Automaticky rolovat',
+ 'tooltipStatusmessage': 'Zobrazovat stavové zprávy',
+ 'tooltipAdministration': 'Správa místnosti',
+ 'tooltipUsercount': 'Uživatelé',
+
+ 'enterRoomPassword': 'Místnost "%s" je chráněna heslem.',
+ 'enterRoomPasswordSubmit': 'Připojit se do místnosti',
+ 'passwordEnteredInvalid': 'Neplatné heslo pro místnost "%s".',
+
+ 'nicknameConflict': 'Takové přihlašovací jméno je již použito. Vyberte si prosím jiné.',
+
+ 'errorMembersOnly': 'Nemůžete se připojit do místnosti "%s": Nedostatečné oprávnění.',
+ 'errorMaxOccupantsReached': 'Nemůžete se připojit do místnosti "%s": Příliš mnoho uživatelů.',
+ 'errorAutojoinMissing': 'Není nastaven parametr autojoin. Nastavte jej prosím.',
+
+ 'antiSpamMessage': 'Nespamujte prosím. Váš účet byl na chvilku zablokován.'
+ }
};
diff --git a/tests/candy/functional/all.js b/tests/candy/functional/all.js
new file mode 100644
index 0000000..62b7730
--- /dev/null
+++ b/tests/candy/functional/all.js
@@ -0,0 +1,5 @@
+/*global define */
+
+define([
+ 'tests/candy/functional/basics'
+], function () {});
diff --git a/tests/candy/functional/basics.js b/tests/candy/functional/basics.js
new file mode 100644
index 0000000..4699b08
--- /dev/null
+++ b/tests/candy/functional/basics.js
@@ -0,0 +1,24 @@
+/*global define */
+
+var exampleUrl;
+
+if (process.env.CANDY_VAGRANT === 'false' && process.env.CI !== 'true') {
+ exampleUrl = 'http://localhost:8080/';
+} else {
+ exampleUrl = 'http://localhost:80/';
+}
+
+define([
+ 'intern!bdd',
+ 'intern/chai!expect'
+], function (bdd, expect) {
+ bdd.describe('Basic chat operation', function () {
+ bdd.it('should load the correct page', function () {
+ return this.remote.get(exampleUrl)
+ .getPageTitle()
+ .then(function (title) {
+ expect(title).to.equal('Candy - Chats are not dead yet');
+ });
+ });
+ });
+});
diff --git a/tests/candy/unit/all.js b/tests/candy/unit/all.js
new file mode 100644
index 0000000..7d4a7a6
--- /dev/null
+++ b/tests/candy/unit/all.js
@@ -0,0 +1,17 @@
+/*global define */
+
+define([
+ 'intern/chai!'
+ , 'sinon-chai'
+ , 'tests/candy/unit/candy'
+ , 'tests/candy/unit/util'
+ , 'tests/candy/unit/core'
+ , 'tests/candy/unit/core/chatUser'
+ , 'tests/candy/unit/core/chatRoster'
+ , 'tests/candy/unit/core/chatRoom'
+ , 'tests/candy/unit/core/contact'
+ , 'tests/candy/unit/core/action'
+ , 'tests/candy/unit/core/event'
+], function (chai, sinonChai) {
+ chai.use(sinonChai);
+});
diff --git a/tests/candy/unit/candy.js b/tests/candy/unit/candy.js
new file mode 100644
index 0000000..bd3ea46
--- /dev/null
+++ b/tests/candy/unit/candy.js
@@ -0,0 +1,22 @@
+/*global define, Candy */
+
+define([
+ 'intern!bdd'
+ , 'intern/chai!expect'
+ , 'intern/order!jquery'
+ , 'intern/order!candy/libs.bundle.js'
+ , 'intern/order!candy/src/candy.js'
+], function (bdd, expect, $) {
+ bdd.describe('Candy', function () {
+ bdd.describe('event triggering', function () {
+ bdd.it('should bubble up exceptions from event handlers', function () {
+ $(Candy).on('candy:core.message', function(ev, obj) {
+ throw new ReferenceError(obj.msg);
+ });
+ expect(function () {
+ $(Candy).triggerHandler('candy:core.message', { msg: 'foo bar' });
+ }).to.throw(ReferenceError, /foo bar/);
+ });
+ });
+ });
+});
diff --git a/tests/candy/unit/core.js b/tests/candy/unit/core.js
new file mode 100644
index 0000000..9a4c25f
--- /dev/null
+++ b/tests/candy/unit/core.js
@@ -0,0 +1,28 @@
+/*global define, Candy */
+/*jshint -W030 */
+
+define([
+ 'intern!bdd'
+ , 'intern/chai!expect'
+ , 'intern/order!jquery'
+ , 'intern/order!candy/libs.bundle.js'
+ , 'intern/order!candy/src/candy.js'
+ , 'intern/order!candy/src/core.js'
+ , 'intern/order!candy/src/core/chatRoster.js'
+], function (bdd, expect) {
+ bdd.describe('Candy.Core', function () {
+ bdd.beforeEach(function () {
+ Candy.Core.init('http://example.com/http-bind');
+ });
+
+ bdd.describe('the main roster', function () {
+ bdd.it('should be a chatRoster', function () {
+ expect(Candy.Core.getRoster()).to.be.an.instanceof(Candy.Core.ChatRoster);
+ });
+
+ bdd.it('should be initially empty', function () {
+ expect(Candy.Core.getRoster().getAll()).to.be.empty;
+ });
+ });
+ });
+});
diff --git a/tests/candy/unit/core/action.js b/tests/candy/unit/core/action.js
new file mode 100644
index 0000000..6090bec
--- /dev/null
+++ b/tests/candy/unit/core/action.js
@@ -0,0 +1,259 @@
+/*global define, Candy, Strophe, $iq */
+/*jshint -W030 */
+
+define([
+ 'intern!bdd'
+ , 'intern/chai!expect'
+ , 'sinon'
+ , 'intern/order!candy/tests/helper.js'
+ , 'intern/order!jquery'
+ , 'intern/order!candy/libs.bundle.js'
+ , 'intern/order!candy/src/candy.js'
+ , 'intern/order!candy/src/core.js'
+ , 'intern/order!candy/src/core/action.js'
+ , 'intern/order!candy/src/core/event.js'
+ , 'intern/order!candy/src/core/contact.js'
+], function (bdd, expect, sinon, testHelper, $) {
+ bdd.describe('Candy.Core.Action', function () {
+ testHelper.setupTests(bdd, sinon);
+
+ bdd.describe('requesting the roster', function() {
+ bdd.it('sends a roster request stanza', function () {
+ var request = sinon.spy(Candy.Core.getConnection(), 'send');
+
+ Candy.Core.Action.Jabber.Roster();
+
+ var stanza = testHelper.stanzaFromRequest(request);
+ expect(stanza.prop('tagName')).to.eql('iq');
+ expect(stanza.attr('type')).to.eql('get');
+ expect(stanza.children('query').attr('xmlns')).to.eql('jabber:iq:roster');
+ });
+
+ bdd.describe('if roster versioning is supported server side', function () {
+ bdd.beforeEach(function () {
+ Candy.Core.getConnection().features = Strophe.xmlGenerator().createElement("stream:features");
+ var verFeature = Strophe.xmlGenerator().createElement('ver');
+ verFeature.setAttribute('xmlns', 'urn:xmpp:features:rosterver');
+ Candy.Core.getConnection().features.appendChild(verFeature);
+ });
+
+ bdd.it('bootstraps the roster from the initial items provided', function () {
+ Candy.Core.Action.Jabber.Roster();
+
+ var rosterItem = Candy.Core.getRoster().get('stored@guy.com');
+ expect(rosterItem.getName()).to.eql('Stored Guy');
+ expect(rosterItem.getJid()).to.eql('stored@guy.com');
+ expect(rosterItem.getSubscription()).to.eql('both');
+ expect(rosterItem.getGroups()).to.eql(['Some', 'People']);
+ expect(rosterItem.getStatus()).to.eql('unavailable'); // Throw away resources from the cache
+ });
+
+ bdd.it('includes the cached version in the roster request', function () {
+ var request = sinon.spy(Candy.Core.getConnection(), 'send');
+
+ Candy.Core.Action.Jabber.Roster();
+
+ var stanza = testHelper.stanzaFromRequest(request);
+ expect(stanza.children('query').attr('ver')).to.eql('abc');
+ });
+
+ bdd.it('emits an event indicating that the roster was loaded from cache', function () {
+ var foo;
+ $(Candy).on('candy:core:roster:loaded', function (ev, params) { foo = params; });
+ Candy.Core.Action.Jabber.Roster();
+ expect(foo).to.be.eql({roster: Candy.Core.getRoster()});
+ });
+ });
+
+ bdd.describe('once the roster is received', function () {
+ var receiveResponse = function () {
+ var rosterResponse = $iq({
+ type: 'result',
+ id: '1:roster'
+ })
+ .c('query', {xmlns: 'jabber:iq:roster'})
+ .c('item', {jid: 'foo@bar.com', name: 'Foo Bar', subscription: 'both'})
+ .c('group').t('Friends').up()
+ .c('group').t('Close Friends').up()
+ .up()
+ .c('item', {jid: 'doo@dah.com'});
+
+ testHelper.receiveStanza(rosterResponse);
+ };
+
+ bdd.beforeEach(Candy.Core.Action.Jabber.Roster);
+
+ bdd.it('makes the returned items available in the main roster', function () {
+ receiveResponse();
+ expect(Candy.Core.getRoster().getAll()).to.have.keys(['foo@bar.com', 'doo@dah.com']);
+ expect(Candy.Core.getRoster().get('foo@bar.com')).to.be.an.instanceof(Candy.Core.Contact);
+ expect(Candy.Core.getRoster().get('doo@dah.com')).to.be.an.instanceof(Candy.Core.Contact);
+ });
+
+ bdd.it('records the contact name properly', function () {
+ receiveResponse();
+ var rosterItem = Candy.Core.getRoster().get('foo@bar.com');
+ expect(rosterItem.getName()).to.eql('Foo Bar');
+ });
+
+ bdd.it('uses the JID as name if not available', function () {
+ receiveResponse();
+ var rosterItem = Candy.Core.getRoster().get('doo@dah.com');
+ expect(rosterItem.getName()).to.eql('doo@dah.com');
+ });
+
+ bdd.it('records the subscription', function () {
+ receiveResponse();
+ var rosterItem = Candy.Core.getRoster().get('foo@bar.com');
+ expect(rosterItem.getSubscription()).to.eql('both');
+ });
+
+ bdd.it('records the subscription as none by default', function () {
+ receiveResponse();
+ var rosterItem = Candy.Core.getRoster().get('doo@dah.com');
+ expect(rosterItem.getSubscription()).to.eql('none');
+ });
+
+ bdd.it('records groups', function () {
+ receiveResponse();
+ var rosterItem = Candy.Core.getRoster().get('foo@bar.com');
+ expect(rosterItem.getGroups()).to.eql(['Friends', 'Close Friends']);
+ });
+
+ bdd.it('emits an event indicating that the roster was fetched', function () {
+ var foo;
+ $(Candy).on('candy:core:roster:fetched', function (ev, params) { foo = params; });
+ receiveResponse();
+ expect(foo).to.be.eql({roster: Candy.Core.getRoster()});
+ });
+
+ bdd.describe('updating roster items from pushes', function () {
+ bdd.beforeEach(receiveResponse);
+
+ bdd.describe('modifying a user', function () {
+ var modifiedUser;
+
+ var receivePush = function () {
+ var rosterPush = $iq({
+ type: 'set'
+ })
+ .c('query', {xmlns: 'jabber:iq:roster'})
+ .c('item', {jid: 'foo@bar.com', name: 'My Friend', subscription: 'to'})
+ .c('group').t('Friends');
+
+ testHelper.receiveStanza(rosterPush);
+
+ modifiedUser = Candy.Core.getRoster().get('foo@bar.com');
+ };
+
+ bdd.it('updates the nick', function () {
+ receivePush();
+ expect(modifiedUser.getName()).to.equal('My Friend');
+ });
+
+ bdd.it('updates the affiliation', function () {
+ receivePush();
+ expect(modifiedUser.getSubscription()).to.equal('to');
+ });
+
+ bdd.it('updates the groups', function () {
+ receivePush();
+ expect(modifiedUser.getGroups()).to.eql(['Friends']);
+ });
+
+ bdd.it('emits an event indicating that the roster was updated', function () {
+ var eventParams = null;
+ $(Candy).on('candy:core:roster:updated', function (ev, params) { eventParams = params; });
+ receivePush();
+ expect(eventParams).to.eql({contact: modifiedUser});
+ });
+ });
+
+ bdd.describe('removing a user', function () {
+ var receivePush = function () {
+ var rosterPush = $iq({
+ type: 'set'
+ })
+ .c('query', {xmlns: 'jabber:iq:roster'})
+ .c('item', {jid: 'foo@bar.com', subscription: 'remove'});
+
+ testHelper.receiveStanza(rosterPush);
+ };
+
+ bdd.it('removes the user from the roster', function () {
+ receivePush();
+ expect(Candy.Core.getRoster().get('foo@bar.com')).to.be.undefined;
+ });
+
+ bdd.it('emits an event indicating that the roster was updated', function () {
+ var eventParams = null;
+ var contact = Candy.Core.getRoster().get('foo@bar.com');
+ $(Candy).on('candy:core:roster:removed', function (ev, params) { eventParams = params; });
+ receivePush();
+ expect(eventParams).to.eql({contact: contact});
+ });
+ });
+
+ bdd.describe('adding a user', function () {
+ var receivePush = function () {
+ var rosterPush = $iq({
+ type: 'set'
+ })
+ .c('query', {xmlns: 'jabber:iq:roster'})
+ .c('item', {jid: 'new@guy.com', name: 'Foo Bar', subscription: 'both'})
+ .c('group').t('Friends').up()
+ .c('group').t('Close Friends').up();
+
+ testHelper.receiveStanza(rosterPush);
+ };
+
+ bdd.it('makes the new item available in the main roster', function () {
+ receivePush();
+ expect(Candy.Core.getConnection().roster.findItem('new@guy.com').name).to.eql('Foo Bar');
+ expect(Candy.Core.getRoster().get('new@guy.com')).to.be.an.instanceof(Candy.Core.Contact);
+ });
+
+ bdd.it('records the contact name properly', function () {
+ receivePush();
+ var rosterItem = Candy.Core.getRoster().get('new@guy.com');
+ expect(rosterItem.getName()).to.eql('Foo Bar');
+ });
+
+ bdd.it('records the subscription type as affiliation', function () {
+ receivePush();
+ var rosterItem = Candy.Core.getRoster().get('new@guy.com');
+ expect(rosterItem.getSubscription()).to.eql('both');
+ });
+
+ bdd.it('records groups', function () {
+ receivePush();
+ var rosterItem = Candy.Core.getRoster().get('new@guy.com');
+ expect(rosterItem.getGroups()).to.eql(['Friends', 'Close Friends']);
+ });
+
+ bdd.it('emits an event indicating that the roster item was added', function () {
+ var eventParams = null;
+ $(Candy).on('candy:core:roster:added', function (ev, params) { eventParams = params; });
+ receivePush();
+ var contact = Candy.Core.getRoster().get('new@guy.com');
+ expect(eventParams).to.eql({contact: contact});
+ });
+ });
+ });
+ });
+ });
+
+ bdd.describe('enabling message carbons', function() {
+ bdd.it('sends a carbons enable request stanza', function () {
+ var request = sinon.spy(Candy.Core.getConnection(), 'send');
+
+ Candy.Core.Action.Jabber.EnableCarbons();
+
+ var stanza = testHelper.stanzaFromRequest(request);
+ expect(stanza.prop('tagName')).to.eql('iq');
+ expect(stanza.attr('type')).to.eql('set');
+ expect(stanza.children('enable').attr('xmlns')).to.eql('urn:xmpp:carbons:2');
+ });
+ });
+ });
+});
diff --git a/tests/candy/unit/core/chatRoom.js b/tests/candy/unit/core/chatRoom.js
new file mode 100644
index 0000000..5658ca9
--- /dev/null
+++ b/tests/candy/unit/core/chatRoom.js
@@ -0,0 +1,50 @@
+/*global define, Candy */
+/*jshint -W030 */
+
+define([
+ 'intern!bdd'
+ , 'intern/chai!expect'
+ , 'intern/order!jquery'
+ , 'intern/order!candy/libs.bundle.js'
+ , 'intern/order!candy/src/candy.js'
+ , 'intern/order!candy/src/core.js'
+ , 'intern/order!candy/src/core/chatUser.js'
+ , 'intern/order!candy/src/core/chatRoster.js'
+ , 'intern/order!candy/src/core/chatRoom.js'
+], function (bdd, expect) {
+ bdd.describe('Candy.Core.ChatRoom', function () {
+ var chatRoom;
+
+ bdd.beforeEach(function () {
+ chatRoom = new Candy.Core.ChatRoom('foo@conference.example.com');
+ });
+
+ bdd.it('reveals its JID', function () {
+ expect(chatRoom.getJid()).to.equal('foo@conference.example.com');
+ });
+
+ bdd.it('defaults its name to the node part of the JID', function () {
+ expect(chatRoom.getName()).to.equal('foo');
+ });
+
+ bdd.it('can set a new name', function () {
+ chatRoom.setName('SomeRoom');
+ expect(chatRoom.getName()).to.equal('SomeRoom');
+ });
+
+ bdd.it('can associate a user', function () {
+ expect(chatRoom.getUser()).to.be.null;
+
+ var chatUser = new Candy.Core.ChatUser('foo@bar.com', 'SomeNick');
+ chatRoom.setUser(chatUser);
+
+ expect(chatRoom.getUser()).to.eql(chatUser);
+ });
+
+ bdd.it('has an (initially empty) roster', function () {
+ var roster = chatRoom.getRoster();
+ expect(roster).to.be.an.instanceof(Candy.Core.ChatRoster);
+ expect(roster.getAll()).to.be.empty;
+ });
+ });
+});
diff --git a/tests/candy/unit/core/chatRoster.js b/tests/candy/unit/core/chatRoster.js
new file mode 100644
index 0000000..53229f2
--- /dev/null
+++ b/tests/candy/unit/core/chatRoster.js
@@ -0,0 +1,49 @@
+/*global define, Candy */
+/*jshint -W030 */
+
+define([
+ 'intern!bdd'
+ , 'intern/chai!expect'
+ , 'intern/order!jquery'
+ , 'intern/order!candy/libs.bundle.js'
+ , 'intern/order!candy/src/candy.js'
+ , 'intern/order!candy/src/core.js'
+ , 'intern/order!candy/src/core/chatUser.js'
+ , 'intern/order!candy/src/core/chatRoster.js'
+], function (bdd, expect) {
+ bdd.describe('Candy.Core.ChatRoster', function () {
+ var chatRoster;
+
+ bdd.beforeEach(function () {
+ chatRoster = new Candy.Core.ChatRoster();
+ });
+
+ bdd.it('can add and fetch users', function () {
+ expect(chatRoster.get('foo@bar.com')).to.be.undefined;
+
+ var chatUser = new Candy.Core.ChatUser('foo@bar.com', 'SomeNick');
+ chatRoster.add(chatUser);
+
+ expect(chatRoster.get('foo@bar.com')).to.be.equal(chatUser);
+ });
+
+ bdd.it('reveals the full set of users', function () {
+ expect(chatRoster.getAll()).to.be.empty;
+
+ var chatUser = new Candy.Core.ChatUser('foo@bar.com', 'SomeNick');
+ chatRoster.add(chatUser);
+
+ expect(chatRoster.getAll()).to.eql({'foo@bar.com': chatUser});
+ });
+
+ bdd.it('can remove users', function () {
+ var chatUser = new Candy.Core.ChatUser('foo@bar.com', 'SomeNick');
+ chatRoster.add(chatUser);
+
+ chatRoster.remove('foo@bar.com');
+
+ expect(chatRoster.get('foo@bar.com')).to.be.undefined;
+ expect(chatRoster.getAll()).to.be.empty;
+ });
+ });
+});
diff --git a/tests/candy/unit/core/chatUser.js b/tests/candy/unit/core/chatUser.js
new file mode 100644
index 0000000..1ca60c7
--- /dev/null
+++ b/tests/candy/unit/core/chatUser.js
@@ -0,0 +1,209 @@
+/*global define, Candy */
+/*jshint -W030 */
+
+define([
+ 'intern!bdd'
+ , 'intern/chai!expect'
+ , 'sinon'
+ , 'intern/order!candy/tests/helper.js'
+ , 'intern/order!jquery'
+ , 'intern/order!candy/libs.bundle.js'
+ , 'intern/order!candy/src/candy.js'
+ , 'intern/order!candy/src/core.js'
+ , 'intern/order!candy/src/core/chatUser.js'
+ , 'intern/order!candy/src/core/contact.js'
+], function (bdd, expect, sinon, testHelper) {
+ bdd.describe('Candy.Core.ChatUser', function () {
+ testHelper.setupTests(bdd, sinon);
+
+ var chatUser;
+
+ bdd.beforeEach(function () {
+ chatUser = new Candy.Core.ChatUser('foo bar@conference.baz.com/SomeNick', 'SomeNick', 'admin', 'member', 'foo@bar.com/somewhere');
+ });
+
+ bdd.it('reveals its JID', function () {
+ expect(chatUser.getJid()).to.equal('foo bar@conference.baz.com/SomeNick');
+ });
+
+ bdd.it('reveals its escaped JID', function () {
+ expect(chatUser.getEscapedJid()).to.equal('foo\\20bar@conference.baz.com/SomeNick');
+ });
+
+ bdd.it('allows setting its JID', function () {
+ chatUser.setJid('doo dah@bah.com');
+ expect(chatUser.getJid()).to.equal('doo dah@bah.com');
+ expect(chatUser.getEscapedJid()).to.equal('doo\\20dah@bah.com');
+ });
+
+ bdd.it('reveals its real JID', function () {
+ expect(chatUser.getRealJid()).to.equal('foo@bar.com/somewhere');
+ });
+
+ bdd.it('reveals its nick', function () {
+ expect(chatUser.getNick()).to.equal('SomeNick');
+ });
+
+ bdd.it('allows setting its nick', function () {
+ chatUser.setNick('OtherNick');
+ expect(chatUser.getNick()).to.equal('OtherNick');
+ });
+
+ bdd.describe('revealing its name', function () {
+ bdd.describe('when the user is not in our roster', function () {
+ bdd.it('returns the nick', function () {
+ expect(chatUser.getName()).to.equal('SomeNick');
+ });
+ });
+
+ bdd.describe('when the user is in our roster', function () {
+ var contact;
+
+ bdd.beforeEach(function () {
+ contact = new Candy.Core.Contact({
+ jid: 'foo@bar.com',
+ name: 'Some Name',
+ subscription: 'both',
+ groups: ['Friends'],
+ resources: {}
+ });
+
+ Candy.Core.getRoster().add(contact);
+ });
+
+ bdd.it("returns the contact's name", function () {
+ expect(chatUser.getName()).to.eql('Some Name');
+ });
+ });
+ });
+
+ bdd.it('reveals its role', function () {
+ expect(chatUser.getRole()).to.equal('member');
+ });
+
+ bdd.it('allows setting its role', function () {
+ chatUser.setRole('moderator');
+ expect(chatUser.getRole()).to.equal('moderator');
+ });
+
+ bdd.it('reveals its affiliation', function () {
+ expect(chatUser.getAffiliation()).to.equal('admin');
+ });
+
+ bdd.it('allows setting its affiliation', function () {
+ chatUser.setAffiliation('owner');
+ expect(chatUser.getAffiliation()).to.equal('owner');
+ });
+
+ bdd.describe('isModerator()', function () {
+ bdd.describe('when the user is not a moderator', function () {
+ bdd.it('returns false', function () {
+ expect(chatUser.getRole()).to.equal('member');
+ expect(chatUser.getAffiliation()).to.equal('admin');
+ expect(chatUser.isModerator()).to.equal(false);
+ });
+ });
+
+ bdd.describe('when the role is moderator', function () {
+ bdd.beforeEach(function () {
+ chatUser.setRole('moderator');
+ });
+
+ bdd.it('returns true', function () {
+ expect(chatUser.isModerator()).to.equal(true);
+ });
+ });
+
+ bdd.describe('when the affiliation is owner', function () {
+ bdd.beforeEach(function () {
+ chatUser.setAffiliation('owner');
+ });
+
+ bdd.it('returns true', function () {
+ expect(chatUser.isModerator()).to.equal(true);
+ });
+ });
+ });
+
+ bdd.describe('privacy lists', function () {
+ bdd.it('has only empty lists to start', function () {
+ expect(chatUser.getPrivacyList('somelist')).to.be.empty;
+ expect(chatUser.isInPrivacyList('somelist', 'some@jid.com')).to.be.false;
+ });
+
+ bdd.it('can have existing lists set', function () {
+ chatUser.setPrivacyLists({'somelist': ['some@jid.com']});
+ expect(chatUser.isInPrivacyList('somelist', 'some@jid.com')).to.be.true;
+ expect(chatUser.isInPrivacyList('otherlist', 'some@jid.com')).to.be.false;
+ });
+
+ bdd.it('can have contacts toggled in and out of a list', function () {
+ expect(chatUser.isInPrivacyList('somelist', 'some@jid.com')).to.be.false;
+ expect(chatUser.isInPrivacyList('otherlist', 'some@jid.com')).to.be.false;
+
+ chatUser.addToOrRemoveFromPrivacyList('somelist', 'some@jid.com');
+ expect(chatUser.isInPrivacyList('somelist', 'some@jid.com')).to.be.true;
+ expect(chatUser.isInPrivacyList('otherlist', 'some@jid.com')).to.be.false;
+
+ chatUser.addToOrRemoveFromPrivacyList('somelist', 'some@jid.com');
+ expect(chatUser.isInPrivacyList('somelist', 'some@jid.com')).to.be.false;
+ expect(chatUser.isInPrivacyList('otherlist', 'some@jid.com')).to.be.false;
+ });
+ });
+
+ bdd.describe('custom data', function () {
+ bdd.it('can be written and read', function () {
+ expect(chatUser.getCustomData()).to.eql({});
+ chatUser.setCustomData({foo: 'bar'});
+ expect(chatUser.getCustomData()).to.eql({foo: 'bar'});
+ });
+ });
+
+ bdd.describe('previous nick', function () {
+ bdd.it('can be written and read', function () {
+ expect(chatUser.getPreviousNick()).to.equal(undefined);
+ chatUser.setPreviousNick('oldNick');
+ expect(chatUser.getPreviousNick()).to.equal('oldNick');
+ });
+ });
+
+ bdd.describe("getting the user's contact from our roster", function () {
+ bdd.describe('when the user is not in our roster', function () {
+ bdd.it('returns null', function () {
+ expect(chatUser.getContact()).to.be.undefined;
+ });
+ });
+
+ bdd.describe('when the user is in our roster', function () {
+ var contact;
+
+ bdd.beforeEach(function () {
+ contact = new Candy.Core.Contact({
+ jid: 'foo@bar.com',
+ name: 'Some Name',
+ subscription: 'both',
+ groups: ['Friends'],
+ resources: {}
+ });
+
+ Candy.Core.getRoster().add(contact);
+ });
+
+ bdd.it('returns the contact', function () {
+ expect(chatUser.getContact()).to.eql(contact);
+ });
+ });
+ });
+
+ bdd.describe("status", function () {
+ bdd.it('is unavailable by default', function () {
+ expect(chatUser.getStatus()).to.equal('unavailable');
+ });
+
+ bdd.it('can be set', function () {
+ chatUser.setStatus('busy');
+ expect(chatUser.getStatus()).to.equal('busy');
+ });
+ });
+ });
+});
diff --git a/tests/candy/unit/core/contact.js b/tests/candy/unit/core/contact.js
new file mode 100644
index 0000000..2059630
--- /dev/null
+++ b/tests/candy/unit/core/contact.js
@@ -0,0 +1,201 @@
+/*global define, Candy */
+/*jshint -W030 */
+
+define([
+ 'intern!bdd'
+ , 'intern/chai!expect'
+ , 'intern/order!jquery'
+ , 'intern/order!candy/libs.bundle.js'
+ , 'intern/order!candy/src/candy.js'
+ , 'intern/order!candy/src/core.js'
+ , 'intern/order!candy/src/core/contact.js'
+], function (bdd, expect) {
+ bdd.describe('Candy.Core.Contact', function () {
+ var contact;
+
+ bdd.beforeEach(function () {
+ contact = new Candy.Core.Contact({
+ jid: 'foo bar@baz.com',
+ name: 'Some Name',
+ subscription: 'both',
+ groups: ['Friends'],
+ resources: {}
+ });
+ });
+
+ bdd.it('reveals its JID', function () {
+ expect(contact.getJid()).to.equal('foo bar@baz.com');
+ });
+
+ bdd.it('reveals its escaped JID', function () {
+ expect(contact.getEscapedJid()).to.equal('foo\\20bar@baz.com');
+ });
+
+ bdd.it('reveals its name', function () {
+ expect(contact.getName()).to.equal('Some Name');
+ });
+
+ bdd.it('reveals its name as its Nick for compatability with ChatUser', function () {
+ expect(contact.getNick()).to.equal('Some Name');
+ });
+
+ bdd.it('reveals its subscription', function () {
+ expect(contact.getSubscription()).to.equal('both');
+ });
+
+ bdd.it('reveals its groups', function () {
+ expect(contact.getGroups()).to.eql(['Friends']);
+ });
+
+ bdd.describe('aggregate status', function () {
+ bdd.describe('when there are no online resources', function () {
+ bdd.it('is unavailable when there are no online resources', function () {
+ expect(contact.getStatus()).to.eql('unavailable');
+ });
+ });
+
+ bdd.describe('when only one resource is online', function () {
+ bdd.beforeEach(function () {
+ contact = new Candy.Core.Contact({
+ jid: 'foo bar@baz.com',
+ name: 'Some Name',
+ subscription: 'both',
+ groups: ['Friends'],
+ resources: {
+ 'foo bar@baz.com/resource1': {
+ show: 'away',
+ status: 'Hanging out',
+ priority: 0
+ }
+ }
+ });
+ });
+
+ bdd.it('matches the show attribute of the online resource', function () {
+ expect(contact.getStatus()).to.eql('away');
+ });
+
+ bdd.describe('when its show is not available', function () {
+ bdd.beforeEach(function () {
+ contact = new Candy.Core.Contact({
+ jid: 'foo bar@baz.com',
+ name: 'Some Name',
+ subscription: 'both',
+ groups: ['Friends'],
+ resources: {
+ 'foo bar@baz.com/resource1': {
+ status: 'Hanging out',
+ priority: 0
+ }
+ }
+ });
+ });
+
+ bdd.it('appears available', function () {
+ expect(contact.getStatus()).to.eql('available');
+ });
+ });
+
+ bdd.describe('when its show is null', function () {
+ bdd.beforeEach(function () {
+ contact = new Candy.Core.Contact({
+ jid: 'foo bar@baz.com',
+ name: 'Some Name',
+ subscription: 'both',
+ groups: ['Friends'],
+ resources: {
+ 'foo bar@baz.com/resource1': {
+ show: null,
+ status: 'Hanging out',
+ priority: 0
+ }
+ }
+ });
+ });
+
+ bdd.it('appears available', function () {
+ expect(contact.getStatus()).to.eql('available');
+ });
+ });
+
+ bdd.describe('when its show is empty', function () {
+ bdd.beforeEach(function () {
+ contact = new Candy.Core.Contact({
+ jid: 'foo bar@baz.com',
+ name: 'Some Name',
+ subscription: 'both',
+ groups: ['Friends'],
+ resources: {
+ 'foo bar@baz.com/resource1': {
+ show: '',
+ status: 'Hanging out',
+ priority: 0
+ }
+ }
+ });
+ });
+
+ bdd.it('appears available', function () {
+ expect(contact.getStatus()).to.eql('available');
+ });
+ });
+ });
+
+ bdd.describe('when multiple resources are online', function () {
+ bdd.describe('with the same priority', function () {
+ bdd.beforeEach(function () {
+ contact = new Candy.Core.Contact({
+ jid: 'foo bar@baz.com',
+ name: 'Some Name',
+ subscription: 'both',
+ groups: ['Friends'],
+ resources: {
+ 'foo bar@baz.com/resource1': {
+ show: 'away',
+ status: 'Hanging out',
+ priority: 5
+ },
+ 'foo bar@baz.com/resource2': {
+ show: 'dnd',
+ status: 'Doing stuff',
+ priority: 5
+ }
+ }
+ });
+ });
+
+ bdd.it('matches the lowest weighted status', function () {
+ expect(contact.getStatus()).to.eql('dnd');
+ });
+ });
+
+ bdd.describe('with differing priority', function () {
+ bdd.beforeEach(function () {
+ contact = new Candy.Core.Contact({
+ jid: 'foo bar@baz.com',
+ name: 'Some Name',
+ subscription: 'both',
+ groups: ['Friends'],
+ resources: {
+ 'foo bar@baz.com/resource1': {
+ show: 'away',
+ status: 'Hanging out',
+ priority: 10
+ },
+ 'foo bar@baz.com/resource2': {
+ show: 'dnd',
+ status: 'Doing stuff',
+ priority: 5
+ }
+ }
+ });
+ });
+
+ bdd.it('matches the show attribute of the highest priority resource', function () {
+ expect(contact.getStatus()).to.eql('away');
+ });
+ });
+ });
+ });
+ });
+});
diff --git a/tests/candy/unit/core/event.js b/tests/candy/unit/core/event.js
new file mode 100644
index 0000000..a319b35
--- /dev/null
+++ b/tests/candy/unit/core/event.js
@@ -0,0 +1,2131 @@
+/*global define, Candy, $pres, $msg */
+/*jshint -W027*/
+/*jshint -W030 */
+
+define([
+ 'intern!bdd'
+ , 'intern/chai!expect'
+ , 'sinon'
+ , 'intern/order!candy/tests/helper.js'
+ , 'intern/order!jquery'
+ , 'intern/order!candy/libs.bundle.js'
+ , 'intern/order!candy/src/candy.js'
+ , 'intern/order!candy/src/core.js'
+ , 'intern/order!candy/src/core/event.js'
+ , 'intern/order!candy/src/core/chatUser.js'
+ , 'intern/order!candy/src/core/contact.js'
+], function (bdd, expect, sinon, testHelper, $) {
+ bdd.describe('Candy.Core.Event', function () {
+ testHelper.setupTests(bdd, sinon);
+
+ // TODO: Test me
+ bdd.describe('processing strophe connection status events', function () {});
+
+ // TODO: Test me
+ bdd.describe('processing requests for client version', function () {});
+
+ bdd.describe('processing non-MUC presence', function () {
+ var receivePresence = function () {
+ var presence = $pres({
+ from: 'foo@bar.com',
+ id: 'abc123'
+ });
+
+ testHelper.receiveStanza(presence);
+ };
+
+ bdd.it('should trigger the candy:core.presence event', function () {
+ var eventParams;
+ $(Candy).on('candy:core.presence', function (ev, params) { eventParams = params; });
+ receivePresence();
+ expect(eventParams.from).to.eql('foo@bar.com');
+ expect(eventParams.stanza.attr('id')).to.eql('abc123');
+ });
+ });
+
+ bdd.describe('processing room presence', function () {
+ var roomJid = 'coven@chat.shakespeare.lit';
+
+ var setMe = function () {
+ var me = new Candy.Core.ChatUser('foo@bar.com', 'SomeNick', 'admin', 'member');
+ Candy.Core.setUser(me);
+ };
+
+ bdd.afterEach(function () {
+ Candy.Core.removeRoom(roomJid);
+ });
+
+ bdd.describe('for a room we are in', function () {
+ var room,
+ participantJid = roomJid + '/secondwitch';
+
+ var createRoom = function () {
+ setMe();
+ room = new Candy.Core.ChatRoom(roomJid);
+ Candy.Core.getRooms()[roomJid] = room;
+ };
+
+ var receiveJoinPresence = function () {
+ var presence = $pres({
+ from: participantJid
+ })
+ .c('x', {xmlns: 'http://jabber.org/protocol/muc#user'})
+ .c('item', {affiliation: 'admin', role: 'moderator', jid: 'doo@dah.com/somewhere'});
+
+ testHelper.receiveStanza(presence);
+ };
+
+ bdd.beforeEach(createRoom);
+
+ bdd.describe('when a user joins a room', function () {
+ bdd.it('adds the user to the room roster', function () {
+ receiveJoinPresence();
+
+ var rosterEntry = room.getRoster().get(participantJid);
+ expect(rosterEntry.getJid()).to.eql(participantJid);
+ expect(rosterEntry.getNick()).to.eql('secondwitch');
+ expect(rosterEntry.getRole()).to.eql('moderator');
+ expect(rosterEntry.getAffiliation()).to.eql('admin');
+ expect(rosterEntry.getRealJid()).to.eql('doo@dah.com/somewhere');
+ expect(rosterEntry.getStatus()).to.eql('available');
+ });
+
+ bdd.it('emits a candy:core.presence.room event', function () {
+ var eventParams;
+ $(Candy).on('candy:core.presence.room', function (ev, params) { eventParams = params; });
+
+ receiveJoinPresence();
+
+ expect(eventParams).to.have.keys(['roomJid', 'roomName', 'user', 'action', 'currentUser', 'isNewRoom']);
+ expect(eventParams.roomJid).to.eql('coven@chat.shakespeare.lit');
+ expect(eventParams.roomName).to.eql('coven');
+ expect(eventParams.user).to.eql(room.getRoster().get(participantJid));
+ expect(eventParams.action).to.eql('join');
+ expect(eventParams.currentUser).to.eql(Candy.Core.getUser());
+ expect(eventParams.isNewRoom).to.eql(false);
+ });
+ });
+
+ bdd.describe('when a user updates their status in a room', function () {
+ bdd.beforeEach(receiveJoinPresence);
+
+ var receiveUpdatePresence = function () {
+ var presence = $pres({
+ from: participantJid
+ })
+ .c('show').t('busy')
+ .up()
+ .c('x', {xmlns: 'http://jabber.org/protocol/muc#user'})
+ .c('item', {affiliation: 'admin', role: 'moderator', jid: 'doo@dah.com/somewhere'});
+
+ testHelper.receiveStanza(presence);
+ };
+
+ bdd.it("updates the user's status in the room roster", function () {
+ receiveUpdatePresence();
+
+ var rosterEntry = room.getRoster().get(participantJid);
+ expect(rosterEntry.getStatus()).to.eql('busy');
+ });
+
+ bdd.it('emits a candy:core.presence.room event', function () {
+ var eventParams;
+ $(Candy).on('candy:core.presence.room', function (ev, params) { eventParams = params; });
+
+ receiveUpdatePresence();
+
+ expect(eventParams).to.have.keys(['roomJid', 'roomName', 'user', 'action', 'currentUser', 'isNewRoom']);
+ expect(eventParams.roomJid).to.eql('coven@chat.shakespeare.lit');
+ expect(eventParams.roomName).to.eql('coven');
+ expect(eventParams.user).to.eql(room.getRoster().get(participantJid));
+ expect(eventParams.action).to.eql('join');
+ expect(eventParams.currentUser).to.eql(Candy.Core.getUser());
+ expect(eventParams.isNewRoom).to.eql(false);
+ });
+ });
+
+ bdd.describe('when a user leaves a room', function () {
+ bdd.beforeEach(receiveJoinPresence);
+
+ bdd.describe('voluntarily', function () {
+ var receiveLeavePresence = function () {
+ var presence = $pres({
+ from: participantJid,
+ type: 'unavailable'
+ })
+ .c('x', {xmlns: 'http://jabber.org/protocol/muc#user'})
+ .c('item', {affiliation: 'admin', role: 'none', jid: 'doo@dah.com/somewhere'});
+
+ testHelper.receiveStanza(presence);
+ };
+
+ bdd.it('removes the user from the room roster', function () {
+ receiveLeavePresence();
+
+ expect(room.getRoster().get(participantJid)).to.be.undefined;
+ });
+
+ bdd.it('emits a candy:core.presence.room event', function () {
+ var eventParams;
+ $(Candy).on('candy:core.presence.room', function (ev, params) { eventParams = params; });
+
+ var user = room.getRoster().get(participantJid);
+ receiveLeavePresence();
+
+ expect(eventParams).to.have.keys(['roomJid', 'roomName', 'user', 'action', 'currentUser', 'isNewRoom']);
+ expect(eventParams.roomJid).to.eql('coven@chat.shakespeare.lit');
+ expect(eventParams.roomName).to.eql('coven');
+ expect(eventParams.user).to.eql(user);
+ expect(eventParams.action).to.eql('leave');
+ expect(eventParams.currentUser).to.eql(Candy.Core.getUser());
+ expect(eventParams.isNewRoom).to.eql(false);
+ });
+ });
+
+ bdd.describe('because they were kicked', function () {
+ var receiveLeavePresence = function () {
+ var presence = $pres({
+ from: participantJid,
+ type: 'unavailable'
+ })
+ .c('x', {xmlns: 'http://jabber.org/protocol/muc#user'})
+ .c('item', {affiliation: 'admin', role: 'none', jid: 'doo@dah.com/somewhere'}).up()
+ .c('status', {code: '307'});
+
+ testHelper.receiveStanza(presence);
+ };
+
+ bdd.it('emits the candy:core.presence.room event indicating they were kicked', function () {
+ var eventParams;
+ $(Candy).on('candy:core.presence.room', function (ev, params) { eventParams = params; });
+
+ receiveLeavePresence();
+
+ expect(eventParams.action).to.eql('kick');
+ });
+ });
+
+ bdd.describe('because they were banned', function () {
+ var receiveLeavePresence = function () {
+ var presence = $pres({
+ from: participantJid,
+ type: 'unavailable'
+ })
+ .c('x', {xmlns: 'http://jabber.org/protocol/muc#user'})
+ .c('item', {affiliation: 'admin', role: 'none', jid: 'doo@dah.com/somewhere'}).up()
+ .c('status', {code: '301'});
+
+ testHelper.receiveStanza(presence);
+ };
+
+ bdd.it('emits the candy:core.presence.room event indicating they were banned', function () {
+ var eventParams;
+ $(Candy).on('candy:core.presence.room', function (ev, params) { eventParams = params; });
+
+ receiveLeavePresence();
+
+ expect(eventParams.action).to.eql('ban');
+ });
+ });
+ });
+
+ bdd.describe('when a user changes their room nick', function () {
+ var receiveLeavePresence = function () {
+ var presence = $pres({
+ from: participantJid,
+ type: 'unavailable'
+ })
+ .c('x', {xmlns: 'http://jabber.org/protocol/muc#user'})
+ .c('item', {affiliation: 'admin', role: 'member', jid: 'doo@dah.com/somewhere', nick: 'newnick'}).up()
+ .c('status', {code: '303'});
+
+ testHelper.receiveStanza(presence);
+ };
+
+ bdd.beforeEach(receiveJoinPresence);
+
+ var newJid = roomJid + '/newnick';
+
+ bdd.it('updates the nick in the room roster', function () {
+ receiveLeavePresence();
+
+ var rosterEntry = room.getRoster().get(newJid);
+ expect(rosterEntry.getJid()).to.eql(newJid);
+ expect(rosterEntry.getNick()).to.eql('newnick');
+ expect(rosterEntry.getPreviousNick()).to.eql('secondwitch');
+ expect(rosterEntry.getRole()).to.eql('moderator');
+ expect(rosterEntry.getAffiliation()).to.eql('admin');
+ expect(rosterEntry.getRealJid()).to.eql('doo@dah.com/somewhere');
+ });
+
+ bdd.it('emits a candy:core.presence.room event', function () {
+ var eventParams;
+ $(Candy).on('candy:core.presence.room', function (ev, params) { eventParams = params; });
+
+ receiveLeavePresence();
+
+ expect(eventParams).to.have.keys(['roomJid', 'roomName', 'user', 'action', 'currentUser', 'isNewRoom']);
+ expect(eventParams.roomJid).to.eql(roomJid);
+ expect(eventParams.roomName).to.eql('coven');
+ expect(eventParams.user).to.eql(room.getRoster().get(newJid));
+ expect(eventParams.action).to.eql('nickchange');
+ expect(eventParams.currentUser).to.eql(Candy.Core.getUser());
+ expect(eventParams.isNewRoom).to.eql(false);
+ });
+ });
+
+ bdd.describe('when a user updates affiliation/role', function () {
+ var receiveUpdatePresence = function () {
+ var presence = $pres({
+ from: participantJid
+ })
+ .c('x', {xmlns: 'http://jabber.org/protocol/muc#user'})
+ .c('item', {affiliation: 'member', role: 'participant', jid: 'doo@dah.com/somewhere'});
+
+ testHelper.receiveStanza(presence);
+ };
+
+ bdd.beforeEach(receiveJoinPresence);
+
+ bdd.it('updates the user in the room roster', function () {
+ receiveUpdatePresence();
+
+ var rosterEntry = room.getRoster().get(participantJid);
+ expect(rosterEntry.getJid()).to.eql(participantJid);
+ expect(rosterEntry.getNick()).to.eql('secondwitch');
+ expect(rosterEntry.getRole()).to.eql('participant');
+ expect(rosterEntry.getAffiliation()).to.eql('member');
+ expect(rosterEntry.getRealJid()).to.eql('doo@dah.com/somewhere');
+ });
+
+ bdd.it('emits a candy:core.presence.room event', function () {
+ var eventParams;
+ $(Candy).on('candy:core.presence.room', function (ev, params) { eventParams = params; });
+
+ receiveUpdatePresence();
+
+ expect(eventParams).to.have.keys(['roomJid', 'roomName', 'user', 'action', 'currentUser', 'isNewRoom']);
+ expect(eventParams.roomJid).to.eql(roomJid);
+ expect(eventParams.roomName).to.eql('coven');
+ expect(eventParams.user).to.eql(room.getRoster().get(participantJid));
+ expect(eventParams.action).to.eql('join');
+ expect(eventParams.currentUser).to.eql(Candy.Core.getUser());
+ expect(eventParams.isNewRoom).to.eql(false);
+ });
+ });
+
+ bdd.describe('when we leave a room', function () {
+ var ourJid = roomJid + '/SomeNick';
+
+ var receiveJoinPresence = function () {
+ var presence = $pres({
+ from: ourJid
+ })
+ .c('x', {xmlns: 'http://jabber.org/protocol/muc#user'})
+ .c('item', {affiliation: 'admin', role: 'moderator', jid: 'doo@dah.com/somewhere'})
+ .c('status', {code: '110'});
+
+ testHelper.receiveStanza(presence);
+
+ room = Candy.Core.getRooms()[roomJid];
+ };
+
+ bdd.beforeEach(receiveJoinPresence);
+
+ bdd.describe('voluntarily', function () {
+ var receiveLeavePresence = function () {
+ var presence = $pres({
+ from: ourJid,
+ type: 'unavailable'
+ })
+ .c('x', {xmlns: 'http://jabber.org/protocol/muc#user'})
+ .c('item', {affiliation: 'admin', role: 'moderator', jid: 'doo@dah.com/somewhere'});
+
+ testHelper.receiveStanza(presence);
+ };
+
+ bdd.it('removes the room from our collection', function () {
+ receiveLeavePresence();
+
+ expect(Candy.Core.getRooms()).not.to.have.key(roomJid);
+ });
+
+ bdd.it('emits a candy:core.presence.leave event', function () {
+ var eventParams;
+ $(Candy).on('candy:core.presence.leave', function (ev, params) { eventParams = params; });
+
+ receiveLeavePresence();
+
+ expect(eventParams).to.have.keys(['roomJid', 'roomName', 'user', 'type', 'reason', 'actor']);
+ expect(eventParams.roomJid).to.eql('coven@chat.shakespeare.lit');
+ expect(eventParams.roomName).to.eql('coven');
+ expect(eventParams.type).to.eql('leave');
+ expect(eventParams.reason).to.be.undefined;
+ expect(eventParams.actor).to.be.undefined;
+
+ expect(eventParams.user.getJid()).to.eql(ourJid);
+ expect(eventParams.user.getNick()).to.eql('SomeNick');
+ expect(eventParams.user.getRole()).to.eql('moderator');
+ expect(eventParams.user.getAffiliation()).to.eql('admin');
+ });
+ });
+
+ bdd.describe('because we were kicked', function () {
+ var receiveLeavePresence = function () {
+ var presence = $pres({
+ from: ourJid,
+ type: 'unavailable'
+ })
+ .c('x', {xmlns: 'http://jabber.org/protocol/muc#user'})
+ .c('item', {affiliation: 'admin', role: 'none', jid: 'doo@dah.com/somewhere'})
+ .c('actor', {jid: roomJid + '/TheBoss'}).up()
+ .c('reason').t('Get out of here!').up()
+ .up()
+ .c('status', {code: '307'});
+
+ testHelper.receiveStanza(presence);
+ };
+
+ bdd.it('emits the candy:core.presence.leave event indicating they were kicked', function () {
+ var eventParams;
+ $(Candy).on('candy:core.presence.leave', function (ev, params) { eventParams = params; });
+
+ receiveLeavePresence();
+
+ expect(eventParams.type).to.eql('kick');
+ expect(eventParams.reason).to.eql('Get out of here!');
+ expect(eventParams.actor).to.eql(roomJid + '/TheBoss');
+ });
+ });
+
+ bdd.describe('because we were banned', function () {
+ var receiveLeavePresence = function () {
+ var presence = $pres({
+ from: ourJid,
+ type: 'unavailable'
+ })
+ .c('x', {xmlns: 'http://jabber.org/protocol/muc#user'})
+ .c('item', {affiliation: 'admin', role: 'none', jid: 'doo@dah.com/somewhere'})
+ .c('actor', {jid: roomJid + '/TheBoss'}).up()
+ .c('reason').t('Get out of here!').up()
+ .up()
+ .c('status', {code: '301'});
+
+ testHelper.receiveStanza(presence);
+ };
+
+ bdd.it('emits the candy:core.presence.leave event indicating they were banned', function () {
+ var eventParams;
+ $(Candy).on('candy:core.presence.leave', function (ev, params) { eventParams = params; });
+
+ receiveLeavePresence();
+
+ expect(eventParams.type).to.eql('ban');
+ expect(eventParams.reason).to.eql('Get out of here!');
+ expect(eventParams.actor).to.eql(roomJid + '/TheBoss');
+ });
+ });
+ });
+
+ bdd.describe('which is an error', function () {
+ var receiveErrorPresence = function () {
+ var presence = $pres({
+ from: roomJid,
+ type: 'error',
+ id: 'abc123'
+ })
+ .c('x', {xmlns: 'http://jabber.org/protocol/muc'}).up()
+ .c('error', {by: 'coven@chat.shakespeare.lit', type: 'modify'})
+ .c('jid-malformed', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'});
+
+ testHelper.receiveStanza(presence);
+ };
+
+ bdd.it('removes the room from our collection', function () {
+ expect(Candy.Core.getRooms()[roomJid]).to.be.an.instanceof(Candy.Core.ChatRoom);
+
+ receiveErrorPresence();
+
+ expect(Candy.Core.getRooms()).to.not.have.key(roomJid);
+ });
+
+ bdd.it('emits a candy:core.presence.error event', function () {
+ var eventParams;
+ $(Candy).on('candy:core.presence.error', function (ev, params) { eventParams = params; });
+
+ receiveErrorPresence();
+
+ expect(eventParams).to.have.keys(['roomJid', 'roomName', 'type', 'msg']);
+ expect(eventParams.roomJid).to.eql('coven@chat.shakespeare.lit');
+ expect(eventParams.roomName).to.eql('coven');
+ expect(eventParams.type).to.eql('jid-malformed');
+ expect(eventParams.msg.attr('id')).to.eql('abc123');
+ });
+ });
+ });
+
+ bdd.describe('when we join a room', function () {
+ var room,
+ participantJid = roomJid + '/SomeNick';
+
+ var receiveJoinPresence = function () {
+ var presence = $pres({
+ from: participantJid
+ })
+ .c('x', {xmlns: 'http://jabber.org/protocol/muc#user'})
+ .c('item', {affiliation: 'admin', role: 'moderator', jid: 'doo@dah.com/somewhere'})
+ .c('status', {code: '110'});
+
+ testHelper.receiveStanza(presence);
+
+ room = Candy.Core.getRooms()[roomJid];
+ };
+
+ bdd.beforeEach(setMe);
+
+ bdd.describe('and the room is being created', function() {
+ var receiveJoinPresence = function () {
+ var presence = $pres({
+ from: participantJid
+ })
+ .c('x', {xmlns: 'http://jabber.org/protocol/muc#user'})
+ .c('item', {affiliation: 'admin', role: 'moderator', jid: 'doo@dah.com/somewhere'})
+ .c('status', {code: '110'})
+ .c('status', {code: '201'});
+
+ testHelper.receiveStanza(presence);
+
+ room = Candy.Core.getRooms()[roomJid];
+ };
+
+ bdd.it('on a new room it emits a candy:core.presence.room event with isNewRoom set', function () {
+ var eventParams;
+ $(Candy).on('candy:core.presence.room', function (ev, params) { eventParams = params; });
+
+ receiveJoinPresence();
+
+ expect(eventParams).to.have.keys(['roomJid', 'roomName', 'user', 'action', 'currentUser', 'isNewRoom']);
+ expect(eventParams.roomJid).to.eql('coven@chat.shakespeare.lit');
+ expect(eventParams.roomName).to.eql('coven');
+ expect(eventParams.user).to.eql(room.getRoster().get(participantJid));
+ expect(eventParams.action).to.eql('join');
+ expect(eventParams.currentUser).to.eql(room.getUser());
+ expect(eventParams.isNewRoom).to.eql(true);
+ });
+ });
+
+ bdd.it('creates the room instance in our collection', function () {
+ expect(Candy.Core.getRooms()).not.to.have.key('roomJid');
+
+ receiveJoinPresence();
+
+ expect(room).to.be.an.instanceof(Candy.Core.ChatRoom);
+ expect(room.getJid()).to.eql(roomJid);
+ });
+
+ bdd.it('sets the room user to be me', function () {
+ receiveJoinPresence();
+
+ var roomUser = room.getUser();
+ expect(roomUser.getJid()).to.eql(participantJid);
+ expect(roomUser.getNick()).to.eql('SomeNick');
+ expect(roomUser.getRole()).to.eql('moderator');
+ expect(roomUser.getAffiliation()).to.eql('admin');
+ expect(roomUser.getRealJid()).to.eql('doo@dah.com/somewhere');
+ });
+
+ bdd.it('adds me to the room roster', function () {
+ receiveJoinPresence();
+
+ var rosterEntry = room.getRoster().get(participantJid);
+ expect(rosterEntry.getJid()).to.eql(participantJid);
+ expect(rosterEntry.getNick()).to.eql('SomeNick');
+ expect(rosterEntry.getRole()).to.eql('moderator');
+ expect(rosterEntry.getAffiliation()).to.eql('admin');
+ expect(rosterEntry.getRealJid()).to.eql('doo@dah.com/somewhere');
+ });
+
+ bdd.it('emits a candy:core.presence.room event', function () {
+ var eventParams;
+ $(Candy).on('candy:core.presence.room', function (ev, params) { eventParams = params; });
+
+ receiveJoinPresence();
+
+ expect(eventParams).to.have.keys(['roomJid', 'roomName', 'user', 'action', 'currentUser', 'isNewRoom']);
+ expect(eventParams.roomJid).to.eql('coven@chat.shakespeare.lit');
+ expect(eventParams.roomName).to.eql('coven');
+ expect(eventParams.user).to.eql(room.getRoster().get(participantJid));
+ expect(eventParams.action).to.eql('join');
+ expect(eventParams.currentUser).to.eql(room.getUser());
+ expect(eventParams.isNewRoom).to.eql(false);
+ });
+
+ bdd.describe('when I am assigned a different nick to the one I asked for', function () {
+ var newParticipantJid = roomJid + '/ServerAssignedNick';
+
+ var receiveJoinPresenceAssignedNick = function () {
+ var presence = $pres({
+ from: newParticipantJid
+ })
+ .c('x', {xmlns: 'http://jabber.org/protocol/muc#user'})
+ .c('item', {affiliation: 'admin', role: 'moderator', jid: 'doo@dah.com/somewhere'}).up()
+ .c('status', {code: '110'}).up()
+ .c('status', {code: '210'});
+
+ testHelper.receiveStanza(presence);
+
+ room = Candy.Core.getRooms()[roomJid];
+ };
+
+ bdd.it('sets the room user to be me with the new nick', function () {
+ receiveJoinPresenceAssignedNick();
+
+ var roomUser = room.getUser();
+ expect(roomUser.getJid()).to.eql(newParticipantJid);
+ expect(roomUser.getNick()).to.eql('ServerAssignedNick');
+ expect(roomUser.getRole()).to.eql('moderator');
+ expect(roomUser.getAffiliation()).to.eql('admin');
+ expect(roomUser.getRealJid()).to.eql('doo@dah.com/somewhere');
+ });
+
+ bdd.it('adds me to the room roster with the new nick', function () {
+ receiveJoinPresenceAssignedNick();
+
+ var rosterEntry = room.getRoster().get(newParticipantJid);
+ expect(rosterEntry.getJid()).to.eql(newParticipantJid);
+ expect(rosterEntry.getNick()).to.eql('ServerAssignedNick');
+ expect(rosterEntry.getRole()).to.eql('moderator');
+ expect(rosterEntry.getAffiliation()).to.eql('admin');
+ expect(rosterEntry.getRealJid()).to.eql('doo@dah.com/somewhere');
+ });
+
+ bdd.it('emits a candy:core.presence.room event with the new nick', function () {
+ var eventParams;
+ $(Candy).on('candy:core.presence.room', function (ev, params) { eventParams = params; });
+
+ receiveJoinPresenceAssignedNick();
+
+ expect(eventParams).to.have.keys(['roomJid', 'roomName', 'user', 'action', 'currentUser', 'isNewRoom']);
+ expect(eventParams.roomJid).to.eql('coven@chat.shakespeare.lit');
+ expect(eventParams.roomName).to.eql('coven');
+ expect(eventParams.user).to.eql(room.getRoster().get(newParticipantJid));
+ expect(eventParams.action).to.eql('join');
+ expect(eventParams.currentUser).to.eql(room.getUser());
+ expect(eventParams.isNewRoom).to.eql(false);
+ });
+ });
+ });
+ });
+
+ // TODO: Test me
+ bdd.describe('processing lists of bookmarks', function () {
+ bdd.describe('from pubsub', function () {});
+
+ bdd.describe('from private storage', function () {});
+ });
+
+ // TODO: Test me
+ bdd.describe('processing privacy lists', function () {
+ bdd.describe('which contain an error', function () {});
+ });
+
+ bdd.describe('processing messages', function () {
+ var setMe = function () {
+ var me = new Candy.Core.ChatUser('foo@bar.com', 'Me oh me', 'admin', 'member');
+ Candy.Core.setUser(me);
+ };
+
+ bdd.describe('which have no type', function () {
+ var receiveMessage = function () {
+ testHelper.receiveStanza(
+ $msg({
+ from: 'foo@bar.com'
+ })
+ .c('body').t('Some message text')
+ );
+ };
+
+ bdd.it('emits a candy:core:chat:message:normal event', function () {
+ var eventParams;
+ $(Candy).on('candy:core:chat:message:normal', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams.type).to.eql('normal');
+ expect(eventParams.message.attr('from')).to.eql('foo@bar.com');
+ });
+
+ bdd.describe('and contain a mediated MUC invite', function () {
+ var receiveMessage = function () {
+ var message = $msg({
+ from: 'coven@chat.shakespeare.lit'
+ })
+ .c('x', {xmlns: 'http://jabber.org/protocol/muc#user'})
+ .c('invite', {from: 'crone1@shakespeare.lit/desktop'})
+ .c('reason').t('Hey Hecate, this is the place for all good witches!')
+ .up()
+ .c('continue', {thread: 'e0ffe42b28561960c6b12b944a092794b9683a38'})
+ .up().up()
+ .c('password').t('cauldronburn');
+
+ testHelper.receiveStanza(message);
+ };
+
+ bdd.it('emits a candy:core:chat:invite event', function () {
+ var eventParams;
+ $(Candy).on('candy:core:chat:invite', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams).to.have.keys(['roomJid', 'from', 'reason', 'password', 'continuedThread']);
+ expect(eventParams.roomJid).to.eql('coven@chat.shakespeare.lit');
+ expect(eventParams.from).to.eql('crone1@shakespeare.lit/desktop');
+ expect(eventParams.reason).to.eql('Hey Hecate, this is the place for all good witches!');
+ expect(eventParams.password).to.eql('cauldronburn');
+ expect(eventParams.continuedThread).to.eql('e0ffe42b28561960c6b12b944a092794b9683a38');
+ });
+
+ bdd.it('emits a candy:core:chat:message:normal event', function () {
+ var eventParams;
+ $(Candy).on('candy:core:chat:message:normal', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams).to.have.keys(['type', 'message']);
+ expect(eventParams.type).to.eql('normal');
+ expect(eventParams.message.attr('from')).to.eql('coven@chat.shakespeare.lit');
+ });
+
+ bdd.describe('with only the minimal required data', function () {
+ var receiveMessage = function () {
+ var message = $msg({
+ from: 'coven@chat.shakespeare.lit'
+ })
+ .c('x', {xmlns: 'http://jabber.org/protocol/muc#user'})
+ .c('invite', {from: 'crone1@shakespeare.lit/desktop'});
+
+ testHelper.receiveStanza(message);
+ };
+
+ bdd.it('emits a candy:core:chat:invite event', function () {
+ var eventParams;
+ $(Candy).on('candy:core:chat:invite', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams).to.have.keys(['roomJid', 'from', 'reason', 'password', 'continuedThread']);
+ expect(eventParams.roomJid).to.eql('coven@chat.shakespeare.lit');
+ expect(eventParams.from).to.eql('crone1@shakespeare.lit/desktop');
+ expect(eventParams.reason).to.be.undefined;
+ expect(eventParams.password).to.be.undefined;
+ expect(eventParams.continuedThread).to.be.undefined;
+ });
+
+ bdd.it('emits a candy:core:chat:message:normal event', function () {
+ var eventParams;
+ $(Candy).on('candy:core:chat:message:normal', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams).to.have.keys(['type', 'message']);
+ expect(eventParams.type).to.eql('normal');
+ expect(eventParams.message.attr('from')).to.eql('coven@chat.shakespeare.lit');
+ });
+ });
+ });
+
+ bdd.describe('and contain a direct MUC invite', function () {
+ var receiveMessage = function () {
+ var message = $msg({
+ from: 'crone1@shakespeare.lit/desktop'
+ })
+ .c('x', {
+ xmlns: 'jabber:x:conference',
+ continue: 'true',
+ jid: 'coven@chat.shakespeare.lit',
+ password: 'cauldronburn',
+ reason: 'Hey Hecate, this is the place for all good witches!',
+ thread: 'e0ffe42b28561960c6b12b944a092794b9683a38'
+ });
+
+ testHelper.receiveStanza(message);
+ };
+
+ bdd.it('emits a candy:core:chat:invite event', function () {
+ var eventParams;
+ $(Candy).on('candy:core:chat:invite', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams).to.have.keys(['roomJid', 'from', 'reason', 'password', 'continuedThread']);
+ expect(eventParams.roomJid).to.eql('coven@chat.shakespeare.lit');
+ expect(eventParams.from).to.eql('crone1@shakespeare.lit/desktop');
+ expect(eventParams.reason).to.eql('Hey Hecate, this is the place for all good witches!');
+ expect(eventParams.password).to.eql('cauldronburn');
+ expect(eventParams.continuedThread).to.eql('e0ffe42b28561960c6b12b944a092794b9683a38');
+ });
+
+ bdd.it('emits a candy:core:chat:message:normal event', function () {
+ var eventParams;
+ $(Candy).on('candy:core:chat:message:normal', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams).to.have.keys(['type', 'message']);
+ expect(eventParams.type).to.eql('normal');
+ expect(eventParams.message.attr('from')).to.eql('crone1@shakespeare.lit/desktop');
+ });
+
+ bdd.describe('with only the minimal required data', function () {
+ var receiveMessage = function () {
+ var message = $msg({
+ from: 'crone1@shakespeare.lit/desktop'
+ })
+ .c('x', {
+ xmlns: 'jabber:x:conference',
+ jid: 'coven@chat.shakespeare.lit'
+ });
+
+ testHelper.receiveStanza(message);
+ };
+
+ bdd.it('emits a candy:core:chat:invite event', function () {
+ var eventParams;
+ $(Candy).on('candy:core:chat:invite', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams).to.have.keys(['roomJid', 'from', 'reason', 'password', 'continuedThread']);
+ expect(eventParams.roomJid).to.eql('coven@chat.shakespeare.lit');
+ expect(eventParams.from).to.eql('crone1@shakespeare.lit/desktop');
+ expect(eventParams.reason).to.be.undefined;
+ expect(eventParams.password).to.be.undefined;
+ expect(eventParams.continuedThread).to.be.undefined;
+ });
+
+ bdd.it('emits a candy:core:chat:message:normal event', function () {
+ var eventParams;
+ $(Candy).on('candy:core:chat:message:normal', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams).to.have.keys(['type', 'message']);
+ expect(eventParams.type).to.eql('normal');
+ expect(eventParams.message.attr('from')).to.eql('crone1@shakespeare.lit/desktop');
+ });
+ });
+ });
+ });
+
+ bdd.describe('which are normal', function () {
+ var receiveMessage = function () {
+ testHelper.receiveStanza(
+ $msg({
+ from: 'foo@bar.com',
+ type: 'normal'
+ })
+ .c('body').t('Some message text')
+ );
+ };
+
+ bdd.it('emits a candy:core:chat:message:normal event', function () {
+ var eventParams;
+ $(Candy).on('candy:core:chat:message:normal', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams.type).to.eql('normal');
+ expect(eventParams.message.attr('from')).to.eql('foo@bar.com');
+ });
+
+ bdd.describe('and contain a mediated MUC invite', function () {
+ var receiveMessage = function () {
+ var message = $msg({
+ from: 'coven@chat.shakespeare.lit',
+ type: 'normal'
+ })
+ .c('x', {xmlns: 'http://jabber.org/protocol/muc#user'})
+ .c('invite', {from: 'crone1@shakespeare.lit/desktop'})
+ .c('reason').t('Hey Hecate, this is the place for all good witches!')
+ .up()
+ .c('continue', {thread: 'e0ffe42b28561960c6b12b944a092794b9683a38'})
+ .up().up()
+ .c('password').t('cauldronburn');
+
+ testHelper.receiveStanza(message);
+ };
+
+ bdd.it('emits a candy:core:chat:invite event', function () {
+ var eventParams;
+ $(Candy).on('candy:core:chat:invite', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams).to.have.keys(['roomJid', 'from', 'reason', 'password', 'continuedThread']);
+ expect(eventParams.roomJid).to.eql('coven@chat.shakespeare.lit');
+ expect(eventParams.from).to.eql('crone1@shakespeare.lit/desktop');
+ expect(eventParams.reason).to.eql('Hey Hecate, this is the place for all good witches!');
+ expect(eventParams.password).to.eql('cauldronburn');
+ expect(eventParams.continuedThread).to.eql('e0ffe42b28561960c6b12b944a092794b9683a38');
+ });
+
+ bdd.it('emits a candy:core:chat:message:normal event', function () {
+ var eventParams;
+ $(Candy).on('candy:core:chat:message:normal', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams).to.have.keys(['type', 'message']);
+ expect(eventParams.type).to.eql('normal');
+ expect(eventParams.message.attr('from')).to.eql('coven@chat.shakespeare.lit');
+ });
+
+ bdd.describe('with only the minimal required data', function () {
+ var receiveMessage = function () {
+ var message = $msg({
+ from: 'coven@chat.shakespeare.lit',
+ type: 'normal'
+ })
+ .c('x', {xmlns: 'http://jabber.org/protocol/muc#user'})
+ .c('invite', {from: 'crone1@shakespeare.lit/desktop'});
+
+ testHelper.receiveStanza(message);
+ };
+
+ bdd.it('emits a candy:core:chat:invite event', function () {
+ var eventParams;
+ $(Candy).on('candy:core:chat:invite', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams).to.have.keys(['roomJid', 'from', 'reason', 'password', 'continuedThread']);
+ expect(eventParams.roomJid).to.eql('coven@chat.shakespeare.lit');
+ expect(eventParams.from).to.eql('crone1@shakespeare.lit/desktop');
+ expect(eventParams.reason).to.be.undefined;
+ expect(eventParams.password).to.be.undefined;
+ expect(eventParams.continuedThread).to.be.undefined;
+ });
+
+ bdd.it('emits a candy:core:chat:message:normal event', function () {
+ var eventParams;
+ $(Candy).on('candy:core:chat:message:normal', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams).to.have.keys(['type', 'message']);
+ expect(eventParams.type).to.eql('normal');
+ expect(eventParams.message.attr('from')).to.eql('coven@chat.shakespeare.lit');
+ });
+ });
+ });
+
+ bdd.describe('and contain a direct MUC invite', function () {
+ var receiveMessage = function () {
+ var message = $msg({
+ from: 'crone1@shakespeare.lit/desktop',
+ type: 'normal'
+ })
+ .c('x', {
+ xmlns: 'jabber:x:conference',
+ continue: 'true',
+ jid: 'coven@chat.shakespeare.lit',
+ password: 'cauldronburn',
+ reason: 'Hey Hecate, this is the place for all good witches!',
+ thread: 'e0ffe42b28561960c6b12b944a092794b9683a38'
+ });
+
+ testHelper.receiveStanza(message);
+ };
+
+ bdd.it('emits a candy:core:chat:invite event', function () {
+ var eventParams;
+ $(Candy).on('candy:core:chat:invite', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams).to.have.keys(['roomJid', 'from', 'reason', 'password', 'continuedThread']);
+ expect(eventParams.roomJid).to.eql('coven@chat.shakespeare.lit');
+ expect(eventParams.from).to.eql('crone1@shakespeare.lit/desktop');
+ expect(eventParams.reason).to.eql('Hey Hecate, this is the place for all good witches!');
+ expect(eventParams.password).to.eql('cauldronburn');
+ expect(eventParams.continuedThread).to.eql('e0ffe42b28561960c6b12b944a092794b9683a38');
+ });
+
+ bdd.it('emits a candy:core:chat:message:normal event', function () {
+ var eventParams;
+ $(Candy).on('candy:core:chat:message:normal', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams).to.have.keys(['type', 'message']);
+ expect(eventParams.type).to.eql('normal');
+ expect(eventParams.message.attr('from')).to.eql('crone1@shakespeare.lit/desktop');
+ });
+
+ bdd.describe('with only the minimal required data', function () {
+ var receiveMessage = function () {
+ var message = $msg({
+ from: 'crone1@shakespeare.lit/desktop',
+ type: 'normal'
+ })
+ .c('x', {
+ xmlns: 'jabber:x:conference',
+ jid: 'coven@chat.shakespeare.lit'
+ });
+
+ testHelper.receiveStanza(message);
+ };
+
+ bdd.it('emits a candy:core:chat:invite event', function () {
+ var eventParams;
+ $(Candy).on('candy:core:chat:invite', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams).to.have.keys(['roomJid', 'from', 'reason', 'password', 'continuedThread']);
+ expect(eventParams.roomJid).to.eql('coven@chat.shakespeare.lit');
+ expect(eventParams.from).to.eql('crone1@shakespeare.lit/desktop');
+ expect(eventParams.reason).to.be.undefined;
+ expect(eventParams.password).to.be.undefined;
+ expect(eventParams.continuedThread).to.be.undefined;
+ });
+
+ bdd.it('emits a candy:core:chat:message:normal event', function () {
+ var eventParams;
+ $(Candy).on('candy:core:chat:message:normal', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams).to.have.keys(['type', 'message']);
+ expect(eventParams.type).to.eql('normal');
+ expect(eventParams.message.attr('from')).to.eql('crone1@shakespeare.lit/desktop');
+ });
+ });
+ });
+ });
+
+ bdd.describe('which are a chat message', function () {
+ var receiveMessage = function () {
+ testHelper.receiveStanza(
+ $msg({
+ to: 'foo@bar.com',
+ from: 'doo@dah.com/resource1',
+ type: 'chat'
+ })
+ .c('body').t('Some message text')
+ .up()
+ .c('html', {xmlns: 'http://jabber.org/protocol/xhtml-im'})
+ .c('body', {xmlns: 'http://www.w3.org/1999/xhtml'})
+ .c('p', {style: 'font-weight: bold;'}).t('hi!')
+ );
+ };
+
+ bdd.it('emits a candy:core.message event', function () {
+ var eventParams;
+ $(Candy).on('candy:core.message', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams).to.have.keys(['roomJid', 'roomName', 'message', 'timestamp', 'carbon', 'stanza']);
+ expect(eventParams.roomJid).to.eql('doo@dah.com');
+ expect(eventParams.roomName).to.eql('doo');
+
+ var message = eventParams.message;
+ expect(message).to.have.keys(['from', 'name', 'body', 'type', 'delay', 'isNoConferenceRoomJid', 'xhtmlMessage']);
+ expect(message.from).to.eql('doo@dah.com/resource1');
+ expect(message.name).to.eql('doo');
+ expect(message.type).to.eql('chat');
+ expect(message.body).to.eql('Some message text');
+ expect(message.isNoConferenceRoomJid).to.be.true;
+ expect(message.xhtmlMessage.attr('style')).to.eql('font-weight: bold;');
+ });
+
+ bdd.describe('which is a carbon', function () {
+ bdd.describe('sent', function () {
+ var receiveMessage = function () {
+ testHelper.receiveStanza(
+ $msg({
+ to: 'foo@bar.com/resource1',
+ from: 'foo@bar.com',
+ type: 'chat'
+ })
+ .c('sent', {xmlns: 'urn:xmpp:carbons:2'})
+ .c('forwarded', {xmlns: 'urn:xmpp:forward:0'})
+ .c('message', {
+ to: 'doo@dah.com/resource1',
+ from: 'foo@bar.com/resource2',
+ type: 'chat'
+ })
+ .c('body').t('Some message text')
+ .up()
+ .c('html', {xmlns: 'http://jabber.org/protocol/xhtml-im'})
+ .c('body', {xmlns: 'http://www.w3.org/1999/xhtml'})
+ .c('p', {style: 'font-weight: bold;'}).t('hi!')
+ );
+ };
+
+ bdd.it('emits a candy:core.message event tagged as carbon', function () {
+ var eventParams;
+ $(Candy).on('candy:core.message', function (ev, params) { eventParams = params; });
+
+ setMe();
+ receiveMessage();
+
+ expect(eventParams).to.have.keys(['roomJid', 'roomName', 'message', 'timestamp', 'carbon', 'stanza']);
+ expect(eventParams.roomJid).to.eql('doo@dah.com');
+ expect(eventParams.roomName).to.eql('doo');
+ expect(eventParams.carbon).to.eql(true);
+
+ var message = eventParams.message;
+ expect(message).to.have.keys(['from', 'name', 'body', 'type', 'delay', 'isNoConferenceRoomJid', 'xhtmlMessage']);
+ expect(message.from).to.eql('foo@bar.com/resource2');
+ expect(message.name).to.eql('Me oh me');
+ expect(message.type).to.eql('chat');
+ expect(message.body).to.eql('Some message text');
+ expect(message.isNoConferenceRoomJid).to.be.true;
+ expect(message.xhtmlMessage.attr('style')).to.eql('font-weight: bold;');
+ });
+ });
+
+ bdd.describe('received', function () {
+ var receiveMessage = function () {
+ testHelper.receiveStanza(
+ $msg({
+ to: 'foo@bar.com/resource1',
+ from: 'foo@bar.com',
+ type: 'chat'
+ })
+ .c('received', {xmlns: 'urn:xmpp:carbons:2'})
+ .c('forwarded', {xmlns: 'urn:xmpp:forward:0'})
+ .c('message', {
+ to: 'foo@bar.com/resource2',
+ from: 'doo@dah.com/resource1',
+ type: 'chat'
+ })
+ .c('body').t('Some message text')
+ .up()
+ .c('html', {xmlns: 'http://jabber.org/protocol/xhtml-im'})
+ .c('body', {xmlns: 'http://www.w3.org/1999/xhtml'})
+ .c('p', {style: 'font-weight: bold;'}).t('hi!')
+ );
+ };
+
+ bdd.it('emits a candy:core.message event tagged as carbon', function () {
+ var eventParams;
+ $(Candy).on('candy:core.message', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams).to.have.keys(['roomJid', 'roomName', 'message', 'timestamp', 'carbon', 'stanza']);
+ expect(eventParams.roomJid).to.eql('doo@dah.com');
+ expect(eventParams.roomName).to.eql('doo');
+ expect(eventParams.carbon).to.eql(true);
+
+ var message = eventParams.message;
+ expect(message).to.have.keys(['from', 'name', 'body', 'type', 'delay', 'isNoConferenceRoomJid', 'xhtmlMessage']);
+ expect(message.from).to.eql('doo@dah.com/resource1');
+ expect(message.name).to.eql('doo');
+ expect(message.type).to.eql('chat');
+ expect(message.body).to.eql('Some message text');
+ expect(message.isNoConferenceRoomJid).to.be.true;
+ expect(message.xhtmlMessage.attr('style')).to.eql('font-weight: bold;');
+ });
+
+ bdd.describe('and they are in our roster', function () {
+ bdd.beforeEach(function () {
+ var contact = new Candy.Core.Contact({
+ jid: 'doo@dah.com',
+ name: 'Some Name',
+ subscription: 'both',
+ groups: ['Friends'],
+ resources: {}
+ });
+
+ Candy.Core.getRoster().add(contact);
+ });
+
+ bdd.it('uses the contact name as the originating name of the message', function () {
+ var eventParams;
+ $(Candy).on('candy:core.message', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams.message.name).to.eql('Some Name');
+ });
+ });
+ });
+ });
+
+ bdd.describe('and they are in our roster', function () {
+ bdd.beforeEach(function () {
+ var contact = new Candy.Core.Contact({
+ jid: 'doo@dah.com',
+ name: 'Some Name',
+ subscription: 'both',
+ groups: ['Friends'],
+ resources: {}
+ });
+
+ Candy.Core.getRoster().add(contact);
+ });
+
+ bdd.it('uses the contact name as the originating name of the message', function () {
+ var eventParams;
+ $(Candy).on('candy:core.message', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams.roomName).to.eql('Some Name');
+ expect(eventParams.message.name).to.eql('Some Name');
+ });
+ });
+
+ bdd.describe('with a delay', function () {
+ bdd.describe('according to XEP-0203', function () {
+ var receiveMessage = function () {
+ testHelper.receiveStanza(
+ $msg({
+ to: 'foo@bar.com',
+ from: 'doo@dah.com/resource1',
+ type: 'chat'
+ })
+ .c('body').t('Some message text')
+ .up()
+ .c('delay', {
+ xmlns: 'urn:xmpp:delay',
+ from: 'coven@chat.shakespeare.lit',
+ stamp: '2002-09-10T23:08:25Z'
+ }).t('Offline Storage')
+ );
+ };
+
+ bdd.it('emits a candy:core.message event with the timestamp and a delay marker', function () {
+ var eventParams;
+ $(Candy).on('candy:core.message', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams.timestamp).to.eql('2002-09-10T23:08:25Z');
+ expect(eventParams.message.delay).to.eql(true);
+ });
+ });
+
+ bdd.describe('according to XEP-0091', function () {
+ var receiveMessage = function () {
+ testHelper.receiveStanza(
+ $msg({
+ to: 'foo@bar.com',
+ from: 'doo@dah.com/resource1',
+ type: 'chat'
+ })
+ .c('body').t('Some message text')
+ .up()
+ .c('x', {
+ xmlns: 'jabber:x:delay',
+ from: 'coven@chat.shakespeare.lit',
+ stamp: '20020910T23:08:25'
+ }).t('Offline Storage')
+ );
+ };
+
+ bdd.it('emits a candy:core.message event with the timestamp', function () {
+ var eventParams;
+ $(Candy).on('candy:core.message', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams.timestamp).to.eql('20020910T23:08:25');
+ });
+ });
+ });
+
+ bdd.describe('without a delay', function() {
+ var receiveMessage = function () {
+ testHelper.receiveStanza(
+ $msg({
+ to: 'foo@bar.com',
+ from: 'doo@dah.com/resource1',
+ type: 'chat'
+ })
+ .c('body').t('Some message text')
+ );
+ };
+
+ bdd.it('emits a candy:core.message event with the timestamp', function () {
+ // TODO: Sinon.useFakeTimers() is undefined, so we can't make this test reliable. See https://groups.google.com/forum/#!topic/sinonjs/_TQugVk441s
+ return;
+ var eventParams;
+ $(Candy).on('candy:core.message', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams.timestamp).to.eql('20140413T10:56:00.000');
+ });
+ });
+
+ bdd.describe('including a chat state notification', function () {
+ bdd.describe('of state active', function () {
+ var receiveMessage = function () {
+ testHelper.receiveStanza(
+ $msg({
+ to: 'foo@bar.com',
+ from: 'doo@dah.com/resource1',
+ type: 'chat'
+ })
+ .c('body').t('Some message text')
+ .up()
+ .c('active', {xmlns: 'http://jabber.org/protocol/chatstates'})
+ );
+ };
+
+ bdd.it('emits a candy:core:message:chatstate event', function () {
+ var eventParams;
+ $(Candy).on('candy:core:message:chatstate', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams).to.have.keys(['name', 'roomJid', 'chatstate']);
+ expect(eventParams.name).to.eql('doo');
+ expect(eventParams.roomJid).to.eql('doo@dah.com');
+ expect(eventParams.chatstate).to.eql('active');
+ });
+ });
+
+ bdd.describe('of state composing', function () {
+ var receiveMessage = function () {
+ testHelper.receiveStanza(
+ $msg({
+ to: 'foo@bar.com',
+ from: 'doo@dah.com/resource1',
+ type: 'chat'
+ })
+ .c('body').t('Some message text')
+ .up()
+ .c('composing', {xmlns: 'http://jabber.org/protocol/chatstates'})
+ );
+ };
+
+ bdd.it('emits a candy:core:message:chatstate event', function () {
+ var eventParams;
+ $(Candy).on('candy:core:message:chatstate', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams).to.have.keys(['name', 'roomJid', 'chatstate']);
+ expect(eventParams.name).to.eql('doo');
+ expect(eventParams.roomJid).to.eql('doo@dah.com');
+ expect(eventParams.chatstate).to.eql('composing');
+ });
+ });
+
+ bdd.describe('of state paused', function () {
+ var receiveMessage = function () {
+ testHelper.receiveStanza(
+ $msg({
+ to: 'foo@bar.com',
+ from: 'doo@dah.com/resource1',
+ type: 'chat'
+ })
+ .c('body').t('Some message text')
+ .up()
+ .c('paused', {xmlns: 'http://jabber.org/protocol/chatstates'})
+ );
+ };
+
+ bdd.it('emits a candy:core:message:chatstate event', function () {
+ var eventParams;
+ $(Candy).on('candy:core:message:chatstate', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams).to.have.keys(['name', 'roomJid', 'chatstate']);
+ expect(eventParams.name).to.eql('doo');
+ expect(eventParams.roomJid).to.eql('doo@dah.com');
+ expect(eventParams.chatstate).to.eql('paused');
+ });
+ });
+
+ bdd.describe('of state inactive', function () {
+ var receiveMessage = function () {
+ testHelper.receiveStanza(
+ $msg({
+ to: 'foo@bar.com',
+ from: 'doo@dah.com/resource1',
+ type: 'chat'
+ })
+ .c('body').t('Some message text')
+ .up()
+ .c('inactive', {xmlns: 'http://jabber.org/protocol/chatstates'})
+ );
+ };
+
+ bdd.it('emits a candy:core:message:chatstate event', function () {
+ var eventParams;
+ $(Candy).on('candy:core:message:chatstate', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams).to.have.keys(['name', 'roomJid', 'chatstate']);
+ expect(eventParams.name).to.eql('doo');
+ expect(eventParams.roomJid).to.eql('doo@dah.com');
+ expect(eventParams.chatstate).to.eql('inactive');
+ });
+ });
+
+ bdd.describe('of state gone', function () {
+ var receiveMessage = function () {
+ testHelper.receiveStanza(
+ $msg({
+ to: 'foo@bar.com',
+ from: 'doo@dah.com/resource1',
+ type: 'chat'
+ })
+ .c('body').t('Some message text')
+ .up()
+ .c('gone', {xmlns: 'http://jabber.org/protocol/chatstates'})
+ );
+ };
+
+ bdd.it('emits a candy:core:message:chatstate event', function () {
+ var eventParams;
+ $(Candy).on('candy:core:message:chatstate', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams).to.have.keys(['name', 'roomJid', 'chatstate']);
+ expect(eventParams.name).to.eql('doo');
+ expect(eventParams.roomJid).to.eql('doo@dah.com');
+ expect(eventParams.chatstate).to.eql('gone');
+ });
+ });
+ });
+ });
+
+ bdd.describe('which are from a MUC room participant', function () {
+ var roomJid = 'coven@chat.shakespeare.lit',
+ participantJid = roomJid + '/thirdwitch';
+
+ var createRoom = function () {
+ var room = new Candy.Core.ChatRoom(roomJid);
+ Candy.Core.getRooms()[roomJid] = room;
+ };
+
+ var receiveJoinPresence = function () {
+ var presence = $pres({
+ from: participantJid
+ })
+ .c('x', {xmlns: 'http://jabber.org/protocol/muc#user'})
+ .c('item', {affiliation: 'admin', role: 'moderator', jid: 'foo@bar.com/somewhere'});
+
+ testHelper.receiveStanza(presence);
+ };
+
+ bdd.beforeEach(createRoom);
+ bdd.beforeEach(receiveJoinPresence);
+ bdd.afterEach(function () {
+ Candy.Core.removeRoom(roomJid);
+ });
+
+ bdd.describe('to the room', function () {
+ var receiveMessage = function () {
+ testHelper.receiveStanza(
+ $msg({
+ to: 'foo@bar.com',
+ from: 'coven@chat.shakespeare.lit/thirdwitch',
+ type: 'groupchat'
+ })
+ .c('body').t('Some message text')
+ .up()
+ .c('html', {xmlns: 'http://jabber.org/protocol/xhtml-im'})
+ .c('body', {xmlns: 'http://www.w3.org/1999/xhtml'})
+ .c('p', {style: 'font-weight: bold;'}).t('hi!')
+ );
+ };
+
+ bdd.it('emits a candy:core.message event', function () {
+ var eventParams;
+ $(Candy).on('candy:core.message', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams).to.have.keys(['roomJid', 'roomName', 'message', 'timestamp', 'carbon', 'stanza']);
+ expect(eventParams.roomJid).to.eql('coven@chat.shakespeare.lit');
+ expect(eventParams.roomName).to.eql('coven');
+
+ var message = eventParams.message;
+ expect(message).to.have.keys(['from', 'name', 'body', 'type', 'delay', 'xhtmlMessage']);
+ expect(message.from).to.eql('coven@chat.shakespeare.lit');
+ expect(message.name).to.eql('thirdwitch');
+ expect(message.type).to.eql('groupchat');
+ expect(message.body).to.eql('Some message text');
+ expect(message.xhtmlMessage.attr('style')).to.eql('font-weight: bold;');
+ });
+
+ bdd.describe('and they are in our roster', function () {
+ bdd.beforeEach(function () {
+ var contact = new Candy.Core.Contact({
+ jid: 'foo@bar.com',
+ name: 'Some Name',
+ subscription: 'both',
+ groups: ['Friends'],
+ resources: {}
+ });
+
+ Candy.Core.getRoster().add(contact);
+ });
+
+ bdd.it('uses the contact name as the originating name of the message', function () {
+ var eventParams;
+ $(Candy).on('candy:core.message', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams.message.name).to.eql('Some Name');
+ });
+ });
+
+ bdd.describe('with a delay', function () {
+ bdd.describe('according to XEP-0203', function () {
+ var receiveMessage = function () {
+ testHelper.receiveStanza(
+ $msg({
+ to: 'foo@bar.com',
+ from: 'coven@chat.shakespeare.lit/thirdwitch',
+ type: 'groupchat'
+ })
+ .c('body').t('Some message text')
+ .up()
+ .c('delay', {
+ xmlns: 'urn:xmpp:delay',
+ from: 'coven@chat.shakespeare.lit',
+ stamp: '2002-09-10T23:08:25Z'
+ }).t('Offline Storage')
+ );
+ };
+
+ bdd.it('emits a candy:core.message event with the timestamp', function () {
+ var eventParams;
+ $(Candy).on('candy:core.message', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams.timestamp).to.eql('2002-09-10T23:08:25Z');
+ });
+ });
+
+ bdd.describe('according to XEP-0091', function () {
+ var receiveMessage = function () {
+ testHelper.receiveStanza(
+ $msg({
+ to: 'foo@bar.com',
+ from: 'coven@chat.shakespeare.lit/thirdwitch',
+ type: 'groupchat'
+ })
+ .c('body').t('Some message text')
+ .up()
+ .c('x', {
+ xmlns: 'jabber:x:delay',
+ from: 'coven@chat.shakespeare.lit',
+ stamp: '20020910T23:08:25'
+ }).t('Offline Storage')
+ );
+ };
+
+ bdd.it('emits a candy:core.message event with the timestamp', function () {
+ var eventParams;
+ $(Candy).on('candy:core.message', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams.timestamp).to.eql('20020910T23:08:25');
+ });
+ });
+ });
+
+ bdd.describe('including a chat state notification', function () {
+ bdd.describe('of state active', function () {
+ var receiveMessage = function () {
+ testHelper.receiveStanza(
+ $msg({
+ to: 'foo@bar.com',
+ from: 'coven@chat.shakespeare.lit/thirdwitch',
+ type: 'groupchat'
+ })
+ .c('body').t('Some message text')
+ .up()
+ .c('active', {xmlns: 'http://jabber.org/protocol/chatstates'})
+ );
+ };
+
+ bdd.it('emits a candy:core:message:chatstate event', function () {
+ var eventParams;
+ $(Candy).on('candy:core:message:chatstate', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams).to.have.keys(['name', 'roomJid', 'chatstate']);
+ expect(eventParams.name).to.eql('thirdwitch');
+ expect(eventParams.roomJid).to.eql('coven@chat.shakespeare.lit');
+ expect(eventParams.chatstate).to.eql('active');
+ });
+ });
+
+ bdd.describe('of state composing', function () {
+ var receiveMessage = function () {
+ testHelper.receiveStanza(
+ $msg({
+ to: 'foo@bar.com',
+ from: 'coven@chat.shakespeare.lit/thirdwitch',
+ type: 'groupchat'
+ })
+ .c('body').t('Some message text')
+ .up()
+ .c('composing', {xmlns: 'http://jabber.org/protocol/chatstates'})
+ );
+ };
+
+ bdd.it('emits a candy:core:message:chatstate event', function () {
+ var eventParams;
+ $(Candy).on('candy:core:message:chatstate', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams).to.have.keys(['name', 'roomJid', 'chatstate']);
+ expect(eventParams.name).to.eql('thirdwitch');
+ expect(eventParams.roomJid).to.eql('coven@chat.shakespeare.lit');
+ expect(eventParams.chatstate).to.eql('composing');
+ });
+ });
+
+ bdd.describe('of state paused', function () {
+ var receiveMessage = function () {
+ testHelper.receiveStanza(
+ $msg({
+ to: 'foo@bar.com',
+ from: 'coven@chat.shakespeare.lit/thirdwitch',
+ type: 'groupchat'
+ })
+ .c('body').t('Some message text')
+ .up()
+ .c('paused', {xmlns: 'http://jabber.org/protocol/chatstates'})
+ );
+ };
+
+ bdd.it('emits a candy:core:message:chatstate event', function () {
+ var eventParams;
+ $(Candy).on('candy:core:message:chatstate', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams).to.have.keys(['name', 'roomJid', 'chatstate']);
+ expect(eventParams.name).to.eql('thirdwitch');
+ expect(eventParams.roomJid).to.eql('coven@chat.shakespeare.lit');
+ expect(eventParams.chatstate).to.eql('paused');
+ });
+ });
+
+ bdd.describe('of state inactive', function () {
+ var receiveMessage = function () {
+ testHelper.receiveStanza(
+ $msg({
+ to: 'foo@bar.com',
+ from: 'coven@chat.shakespeare.lit/thirdwitch',
+ type: 'groupchat'
+ })
+ .c('body').t('Some message text')
+ .up()
+ .c('inactive', {xmlns: 'http://jabber.org/protocol/chatstates'})
+ );
+ };
+
+ bdd.it('emits a candy:core:message:chatstate event', function () {
+ var eventParams;
+ $(Candy).on('candy:core:message:chatstate', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams).to.have.keys(['name', 'roomJid', 'chatstate']);
+ expect(eventParams.name).to.eql('thirdwitch');
+ expect(eventParams.roomJid).to.eql('coven@chat.shakespeare.lit');
+ expect(eventParams.chatstate).to.eql('inactive');
+ });
+ });
+
+ bdd.describe('of state gone', function () {
+ var receiveMessage = function () {
+ testHelper.receiveStanza(
+ $msg({
+ to: 'foo@bar.com',
+ from: 'coven@chat.shakespeare.lit/thirdwitch',
+ type: 'groupchat'
+ })
+ .c('body').t('Some message text')
+ .up()
+ .c('gone', {xmlns: 'http://jabber.org/protocol/chatstates'})
+ );
+ };
+
+ bdd.it('emits a candy:core:message:chatstate event', function () {
+ var eventParams;
+ $(Candy).on('candy:core:message:chatstate', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams).to.have.keys(['name', 'roomJid', 'chatstate']);
+ expect(eventParams.name).to.eql('thirdwitch');
+ expect(eventParams.roomJid).to.eql('coven@chat.shakespeare.lit');
+ expect(eventParams.chatstate).to.eql('gone');
+ });
+ });
+ });
+
+ bdd.describe('indicating a subject change', function () {
+ var receiveMessage = function () {
+ testHelper.receiveStanza(
+ $msg({
+ to: 'foo@bar.com',
+ from: 'coven@chat.shakespeare.lit/thirdwitch',
+ type: 'groupchat'
+ })
+ .c('subject').t('Some new subject!')
+ );
+ };
+
+ bdd.it('emits a candy:core.message event', function () {
+ var eventParams;
+ $(Candy).on('candy:core.message', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams).to.have.keys(['roomJid', 'roomName', 'message', 'timestamp', 'carbon', 'stanza']);
+ expect(eventParams.roomJid).to.eql('coven@chat.shakespeare.lit');
+ expect(eventParams.roomName).to.eql('coven');
+
+ var message = eventParams.message;
+ expect(message).to.have.keys(['from', 'name', 'body', 'type', 'delay']);
+ expect(message.from).to.eql('coven@chat.shakespeare.lit');
+ expect(message.name).to.eql('coven');
+ expect(message.body).to.eql('Some new subject!');
+ expect(message.type).to.eql('subject');
+ });
+ });
+ });
+
+ bdd.describe('as a private message', function () {
+ var receiveMessage = function () {
+ testHelper.receiveStanza(
+ $msg({
+ to: 'foo@bar.com',
+ from: 'coven@chat.shakespeare.lit/thirdwitch',
+ type: 'chat'
+ })
+ .c('body').t('Some message text')
+ .up()
+ .c('html', {xmlns: 'http://jabber.org/protocol/xhtml-im'})
+ .c('body', {xmlns: 'http://www.w3.org/1999/xhtml'})
+ .c('p', {style: 'font-weight: bold;'}).t('hi!')
+ );
+ };
+
+ bdd.it('emits a candy:core.message event', function () {
+ var eventParams;
+ $(Candy).on('candy:core.message', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams).to.have.keys(['roomJid', 'roomName', 'message', 'timestamp', 'carbon', 'stanza']);
+ expect(eventParams.roomJid).to.eql('coven@chat.shakespeare.lit/thirdwitch');
+ expect(eventParams.roomName).to.eql('thirdwitch');
+
+ var message = eventParams.message;
+ expect(message).to.have.keys(['from', 'name', 'body', 'type', 'delay', 'isNoConferenceRoomJid', 'xhtmlMessage']);
+ expect(message.from).to.eql('coven@chat.shakespeare.lit/thirdwitch');
+ expect(message.name).to.eql('thirdwitch');
+ expect(message.type).to.eql('chat');
+ expect(message.body).to.eql('Some message text');
+ expect(message.isNoConferenceRoomJid).to.be.false;
+ expect(message.xhtmlMessage.attr('style')).to.eql('font-weight: bold;');
+ });
+
+ bdd.describe('and they are in our roster', function () {
+ bdd.beforeEach(function () {
+ var contact = new Candy.Core.Contact({
+ jid: 'foo@bar.com',
+ name: 'Some Name',
+ subscription: 'both',
+ groups: ['Friends'],
+ resources: {}
+ });
+
+ Candy.Core.getRoster().add(contact);
+ });
+
+ bdd.it('uses the contact name as the originating name of the message', function () {
+ var eventParams;
+ $(Candy).on('candy:core.message', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams.message.name).to.eql('Some Name');
+ });
+ });
+
+ bdd.describe('with a delay', function () {
+ bdd.describe('according to XEP-0203', function () {
+ var receiveMessage = function () {
+ testHelper.receiveStanza(
+ $msg({
+ to: 'foo@bar.com',
+ from: 'coven@chat.shakespeare.lit/thirdwitch',
+ type: 'chat'
+ })
+ .c('body').t('Some message text')
+ .up()
+ .c('delay', {
+ xmlns: 'urn:xmpp:delay',
+ from: 'coven@chat.shakespeare.lit',
+ stamp: '2002-09-10T23:08:25Z'
+ }).t('Offline Storage')
+ );
+ };
+
+ bdd.it('emits a candy:core.message event with the timestamp', function () {
+ var eventParams;
+ $(Candy).on('candy:core.message', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams.timestamp).to.eql('2002-09-10T23:08:25Z');
+ });
+ });
+
+ bdd.describe('according to XEP-0091', function () {
+ var receiveMessage = function () {
+ testHelper.receiveStanza(
+ $msg({
+ to: 'foo@bar.com',
+ from: 'coven@chat.shakespeare.lit/thirdwitch',
+ type: 'chat'
+ })
+ .c('body').t('Some message text')
+ .up()
+ .c('x', {
+ xmlns: 'jabber:x:delay',
+ from: 'coven@chat.shakespeare.lit',
+ stamp: '20020910T23:08:25'
+ }).t('Offline Storage')
+ );
+ };
+
+ bdd.it('emits a candy:core.message event with the timestamp', function () {
+ var eventParams;
+ $(Candy).on('candy:core.message', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams.timestamp).to.eql('20020910T23:08:25');
+ });
+ });
+ });
+
+ bdd.describe('including a chat state notification', function () {
+ bdd.describe('of state active', function () {
+ var receiveMessage = function () {
+ testHelper.receiveStanza(
+ $msg({
+ to: 'foo@bar.com',
+ from: 'coven@chat.shakespeare.lit/thirdwitch',
+ type: 'chat'
+ })
+ .c('body').t('Some message text')
+ .up()
+ .c('active', {xmlns: 'http://jabber.org/protocol/chatstates'})
+ );
+ };
+
+ bdd.it('emits a candy:core:message:chatstate event', function () {
+ var eventParams;
+ $(Candy).on('candy:core:message:chatstate', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams).to.have.keys(['name', 'roomJid', 'chatstate']);
+ expect(eventParams.name).to.eql('thirdwitch');
+ expect(eventParams.roomJid).to.eql('coven@chat.shakespeare.lit/thirdwitch');
+ expect(eventParams.chatstate).to.eql('active');
+ });
+ });
+
+ bdd.describe('of state composing', function () {
+ var receiveMessage = function () {
+ testHelper.receiveStanza(
+ $msg({
+ to: 'foo@bar.com',
+ from: 'coven@chat.shakespeare.lit/thirdwitch',
+ type: 'chat'
+ })
+ .c('body').t('Some message text')
+ .up()
+ .c('composing', {xmlns: 'http://jabber.org/protocol/chatstates'})
+ );
+ };
+
+ bdd.it('emits a candy:core:message:chatstate event', function () {
+ var eventParams;
+ $(Candy).on('candy:core:message:chatstate', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams).to.have.keys(['name', 'roomJid', 'chatstate']);
+ expect(eventParams.name).to.eql('thirdwitch');
+ expect(eventParams.roomJid).to.eql('coven@chat.shakespeare.lit/thirdwitch');
+ expect(eventParams.chatstate).to.eql('composing');
+ });
+ });
+
+ bdd.describe('of state paused', function () {
+ var receiveMessage = function () {
+ testHelper.receiveStanza(
+ $msg({
+ to: 'foo@bar.com',
+ from: 'coven@chat.shakespeare.lit/thirdwitch',
+ type: 'chat'
+ })
+ .c('body').t('Some message text')
+ .up()
+ .c('paused', {xmlns: 'http://jabber.org/protocol/chatstates'})
+ );
+ };
+
+ bdd.it('emits a candy:core:message:chatstate event', function () {
+ var eventParams;
+ $(Candy).on('candy:core:message:chatstate', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams).to.have.keys(['name', 'roomJid', 'chatstate']);
+ expect(eventParams.name).to.eql('thirdwitch');
+ expect(eventParams.roomJid).to.eql('coven@chat.shakespeare.lit/thirdwitch');
+ expect(eventParams.chatstate).to.eql('paused');
+ });
+ });
+
+ bdd.describe('of state inactive', function () {
+ var receiveMessage = function () {
+ testHelper.receiveStanza(
+ $msg({
+ to: 'foo@bar.com',
+ from: 'coven@chat.shakespeare.lit/thirdwitch',
+ type: 'chat'
+ })
+ .c('body').t('Some message text')
+ .up()
+ .c('inactive', {xmlns: 'http://jabber.org/protocol/chatstates'})
+ );
+ };
+
+ bdd.it('emits a candy:core:message:chatstate event', function () {
+ var eventParams;
+ $(Candy).on('candy:core:message:chatstate', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams).to.have.keys(['name', 'roomJid', 'chatstate']);
+ expect(eventParams.name).to.eql('thirdwitch');
+ expect(eventParams.roomJid).to.eql('coven@chat.shakespeare.lit/thirdwitch');
+ expect(eventParams.chatstate).to.eql('inactive');
+ });
+ });
+
+ bdd.describe('of state gone', function () {
+ var receiveMessage = function () {
+ testHelper.receiveStanza(
+ $msg({
+ to: 'foo@bar.com',
+ from: 'coven@chat.shakespeare.lit/thirdwitch',
+ type: 'chat'
+ })
+ .c('body').t('Some message text')
+ .up()
+ .c('gone', {xmlns: 'http://jabber.org/protocol/chatstates'})
+ );
+ };
+
+ bdd.it('emits a candy:core:message:chatstate event', function () {
+ var eventParams;
+ $(Candy).on('candy:core:message:chatstate', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams).to.have.keys(['name', 'roomJid', 'chatstate']);
+ expect(eventParams.name).to.eql('thirdwitch');
+ expect(eventParams.roomJid).to.eql('coven@chat.shakespeare.lit/thirdwitch');
+ expect(eventParams.chatstate).to.eql('gone');
+ });
+ });
+ });
+ });
+
+ bdd.describe('indicating an error', function () {
+ var receiveMessage = function () {
+ testHelper.receiveStanza(
+ $msg({
+ to: 'foo@bar.com',
+ from: 'coven@chat.shakespeare.lit',
+ type: 'error'
+ })
+ .c('error', {code: '403', type: 'auth'})
+ .c('forbidden', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'})
+ .up()
+ .c('text', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).t('Visitors are not allowed to send messages to all occupants')
+ );
+ };
+
+ bdd.it('emits a candy:core.message event', function () {
+ var eventParams;
+ $(Candy).on('candy:core.message', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams).to.have.keys(['roomJid', 'roomName', 'message', 'timestamp', 'carbon', 'stanza']);
+ expect(eventParams.roomJid).to.eql('coven@chat.shakespeare.lit');
+ expect(eventParams.roomName).to.eql('coven');
+
+ var message = eventParams.message;
+ expect(message).to.have.keys(['from', 'body', 'type', 'delay']);
+ expect(message.from).to.eql('coven@chat.shakespeare.lit');
+ expect(message.type).to.eql('info');
+ expect(message.body).to.eql('Visitors are not allowed to send messages to all occupants');
+ });
+ });
+ });
+
+ bdd.describe('which are from a MUC room itself', function () {
+ var roomJid = 'coven@chat.shakespeare.lit';
+
+ var receiveMessage = function () {
+ var message = $msg({
+ from: roomJid,
+ type: 'groupchat'
+ })
+ .c('body').t('Some announcement');
+
+ testHelper.receiveStanza(message);
+ };
+
+ bdd.describe('in which we are present', function () {
+ var createRoom = function () {
+ var room = new Candy.Core.ChatRoom(roomJid);
+ Candy.Core.getRooms()[roomJid] = room;
+ };
+
+ bdd.beforeEach(createRoom);
+ bdd.afterEach(function () {
+ Candy.Core.removeRoom(roomJid);
+ });
+
+ bdd.it('emits a candy:core.message event', function () {
+ var eventParams;
+ $(Candy).on('candy:core.message', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams).to.have.keys(['roomJid', 'roomName', 'message', 'timestamp', 'carbon', 'stanza']);
+ expect(eventParams.roomJid).to.eql(roomJid);
+ expect(eventParams.roomName).to.eql('');
+
+ var message = eventParams.message;
+ expect(message).to.have.keys(['from', 'name', 'body', 'type', 'delay']);
+ expect(message.from).to.eql(roomJid);
+ expect(message.name).to.eql('');
+ expect(message.body).to.eql('Some announcement');
+ expect(message.type).to.eql('info');
+ });
+ });
+
+ bdd.describe('in which we are not present', function () {
+ bdd.it('does not emit a candy:core.message event', function () {
+ var emitted = false;
+ $(Candy).on('candy:core.message', function () { emitted = true; });
+
+ receiveMessage();
+
+ expect(emitted).to.be.false;
+ });
+ });
+ });
+
+ bdd.describe('which are of a type not listed in the XMPP spec', function () {
+ var receiveMessage = function () {
+ testHelper.receiveStanza(
+ $msg({
+ from: 'foo@bar.com',
+ type: 'randomtype'
+ })
+ .c('body').t('Some message text')
+ );
+ };
+
+ bdd.it('emits a candy:core:chat:message:other event', function () {
+ var eventParams;
+ $(Candy).on('candy:core:chat:message:other', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams.type).to.eql('randomtype');
+ expect(eventParams.message.attr('from')).to.eql('foo@bar.com');
+ });
+ });
+
+ bdd.describe('which are from a server admin (broadcast)', function () {
+ var receiveMessage = function () {
+ testHelper.receiveStanza(
+ $msg({
+ from: 'bar.com',
+ type: 'headline'
+ })
+ .c('body').t('Some message text')
+ );
+ };
+
+ bdd.it('emits a candy:core.chat.message.admin event', function () {
+ var eventParams;
+ $(Candy).on('candy:core.chat.message.admin', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams.type).to.eql('headline');
+ expect(eventParams.message).to.eql('Some message text');
+ });
+ });
+
+ bdd.describe('which are from the server', function () {
+ var receiveMessage = function () {
+ testHelper.receiveStanza(
+ $msg({
+ from: 'bar.com',
+ type: 'headline',
+ to: 'doo@dah.com'
+ })
+ .c('subject').t('Hey!').up()
+ .c('body').t('Some message text')
+ );
+ };
+
+ bdd.it('emits a candy:core.chat.message.server event', function () {
+ var eventParams;
+ $(Candy).on('candy:core.chat.message.server', function (ev, params) { eventParams = params; });
+
+ receiveMessage();
+
+ expect(eventParams.type).to.eql('headline');
+ expect(eventParams.subject).to.eql('Hey!');
+ expect(eventParams.message).to.eql('Some message text');
+ });
+ });
+ });
+
+ // TODO: Test me
+ bdd.describe('processing room disco info', function () {});
+ });
+});
diff --git a/tests/candy/unit/util.js b/tests/candy/unit/util.js
new file mode 100644
index 0000000..227d7b1
--- /dev/null
+++ b/tests/candy/unit/util.js
@@ -0,0 +1,198 @@
+/*global define, Candy */
+/*jshint -W027*/
+
+define([
+ 'intern!bdd'
+ , 'intern/chai!expect'
+ , 'intern/order!jquery'
+ , 'intern/order!candy/libs.bundle.js'
+ , 'intern/order!candy/src/candy.js'
+ , 'intern/order!candy/src/core.js'
+ , 'intern/order!candy/src/util.js'
+], function (bdd, expect) {
+ bdd.describe('Candy.Util', function () {
+ bdd.describe('jidToId', function () {
+ bdd.it('should MD5 hash a JID', function () {
+ expect(Candy.Util.jidToId('foo@bar.com')).to.equal('f3ada405ce890b6f8204094deb12d8a8');
+ });
+ });
+
+ bdd.describe('escapeJid', function () {
+ bdd.it('should escape JIDs per XEP-0106', function () {
+ expect(Candy.Util.escapeJid("space cadet@example.com")).to.equal("space\\20cadet@example.com");
+ });
+ bdd.it('should escape JIDs per XEP-0106', function () {
+ expect(Candy.Util.escapeJid("call me \"ishmael\"@example.com")).to.equal("call\\20me\\20\\22ishmael\\22@example.com");
+ });
+ bdd.it('should escape JIDs per XEP-0106', function () {
+ expect(Candy.Util.escapeJid("at&t guy@example.com")).to.equal("at\\26t\\20guy@example.com");
+ });
+ bdd.it('should escape JIDs per XEP-0106', function () {
+ expect(Candy.Util.escapeJid("d'artagnan@example.com")).to.equal("d\\27artagnan@example.com");
+ });
+ bdd.it('should escape JIDs per XEP-0106', function () {
+ return true; // Skip this for now. See https://github.com/theintern/intern/pull/195
+ expect(Candy.Util.escapeJid("/.fanboy@example.com")).to.equal("\\2f.fanboy@example.com");
+ });
+ bdd.it('should escape JIDs per XEP-0106', function () {
+ expect(Candy.Util.escapeJid("::foo::@example.com")).to.equal("\\3a\\3afoo\\3a\\3a@example.com");
+ });
+ bdd.it('should escape JIDs per XEP-0106', function () {
+ expect(Candy.Util.escapeJid("<foo>@example.com")).to.equal("\\3cfoo\\3e@example.com");
+ });
+ bdd.it('should escape JIDs per XEP-0106', function () {
+ return true; // Skip this for now. See https://github.com/theintern/intern/pull/195
+ expect(Candy.Util.escapeJid("user@host@example.com")).to.equal("user\\40host@example.com");
+ });
+ bdd.it('should escape JIDs per XEP-0106', function () {
+ expect(Candy.Util.escapeJid("c:\\net@example.com")).to.equal('c\\3a\\5cnet@example.com');
+ });
+ bdd.it('should escape JIDs per XEP-0106', function () {
+ expect(Candy.Util.escapeJid("c:\\net@example.com")).to.equal("c\\3a\\5cnet@example.com");
+ });
+ bdd.it('should escape JIDs per XEP-0106', function () {
+ expect(Candy.Util.escapeJid("c:\\cool stuff@example.com")).to.equal("c\\3a\\5ccool\\20stuff@example.com");
+ });
+ bdd.it('should escape JIDs per XEP-0106', function () {
+ expect(Candy.Util.escapeJid("c:\\5commas@example.com")).to.equal("c\\3a\\5c5commas@example.com");
+ });
+ });
+
+ bdd.describe('unescapeJid', function () {
+ bdd.it('should unescape JIDs per XEP-0106', function () {
+ expect(Candy.Util.unescapeJid("space\\20cadet@example.com")).to.equal("space cadet@example.com");
+ });
+ bdd.it('should unescape JIDs per XEP-0106', function () {
+ expect(Candy.Util.unescapeJid("call\\20me\\20\\22ishmael\\22@example.com")).to.equal("call me \"ishmael\"@example.com");
+ });
+ bdd.it('should unescape JIDs per XEP-0106', function () {
+ expect(Candy.Util.unescapeJid("at\\26t\\20guy@example.com")).to.equal("at&t guy@example.com");
+ });
+ bdd.it('should unescape JIDs per XEP-0106', function () {
+ expect(Candy.Util.unescapeJid("d\\27artagnan@example.com")).to.equal("d'artagnan@example.com");
+ });
+ bdd.it('should unescape JIDs per XEP-0106', function () {
+ expect(Candy.Util.unescapeJid("\\2f.fanboy@example.com")).to.equal("/.fanboy@example.com");
+ });
+ bdd.it('should unescape JIDs per XEP-0106', function () {
+ expect(Candy.Util.unescapeJid("\\3a\\3afoo\\3a\\3a@example.com")).to.equal("::foo::@example.com");
+ });
+ bdd.it('should unescape JIDs per XEP-0106', function () {
+ expect(Candy.Util.unescapeJid("\\3cfoo\\3e@example.com")).to.equal("<foo>@example.com");
+ });
+ bdd.it('should unescape JIDs per XEP-0106', function () {
+ expect(Candy.Util.unescapeJid("user\\40host@example.com")).to.equal("user@host@example.com");
+ });
+ bdd.it('should unescape JIDs per XEP-0106', function () {
+ expect(Candy.Util.unescapeJid('c\\3a\\5cnet@example.com')).to.equal("c:\\net@example.com");
+ });
+ bdd.it('should unescape JIDs per XEP-0106', function () {
+ expect(Candy.Util.unescapeJid("c\\3a\\5cnet@example.com")).to.equal("c:\\net@example.com");
+ });
+ bdd.it('should unescape JIDs per XEP-0106', function () {
+ expect(Candy.Util.unescapeJid("c\\3a\\5ccool\\20stuff@example.com")).to.equal("c:\\cool stuff@example.com");
+ });
+ bdd.it('should unescape JIDs per XEP-0106', function () {
+ expect(Candy.Util.unescapeJid("c\\3a\\5c5commas@example.com")).to.equal("c:\\5commas@example.com");
+ });
+ });
+
+ bdd.describe('crop', function () {
+ bdd.describe('with a string longer than the crop length', function () {
+ bdd.it('crops the string to the specified length and replaces the end with elipses', function () {
+ expect(Candy.Util.crop('FooBarBaz', 6)).to.equal('Foo...');
+ });
+ });
+
+ bdd.describe('with a string the same length as the crop length', function () {
+ bdd.it('does not modify the string', function () {
+ expect(Candy.Util.crop('FooBar', 6)).to.equal('FooBar');
+ });
+ });
+
+ bdd.describe('with a string shorter than the crop length', function () {
+ bdd.it('does not modify the string', function () {
+ expect(Candy.Util.crop('FooBar', 9)).to.equal('FooBar');
+ });
+ });
+ });
+
+ bdd.describe('Parser', function () {
+ bdd.describe('jid', function () {
+ bdd.it('should parse JIDs per RFC6122', function () {
+ var jid = Candy.Util.Parser.jid("space cadet@example.com");
+ expect(jid.node).to.equal("space cadet");
+ expect(jid.domain).to.equal("example.com");
+ expect(jid.resource).to.equal(undefined);
+ });
+ bdd.it('should parse JIDs per RFC6122', function () {
+ var jid = Candy.Util.Parser.jid("call me \"ishmael\"@example.com");
+ expect(jid.node).to.equal("call me \"ishmael\"");
+ expect(jid.domain).to.equal("example.com");
+ expect(jid.resource).to.equal(undefined);
+ });
+ bdd.it('should parse JIDs per RFC6122', function () {
+ var jid = Candy.Util.Parser.jid("at&t guy@example.com");
+ expect(jid.node).to.equal("at&t guy");
+ expect(jid.domain).to.equal("example.com");
+ expect(jid.resource).to.equal(undefined);
+ });
+ bdd.it('should parse JIDs per RFC6122', function () {
+ var jid = Candy.Util.Parser.jid("d'artagnan@example.com");
+ expect(jid.node).to.equal("d'artagnan");
+ expect(jid.domain).to.equal("example.com");
+ expect(jid.resource).to.equal(undefined);
+ });
+ bdd.it('should parse JIDs per RFC6122', function () {
+ var jid = Candy.Util.Parser.jid("/.fanboy@example.com");
+ expect(jid.node).to.equal("/.fanboy");
+ expect(jid.domain).to.equal("example.com");
+ expect(jid.resource).to.equal(undefined);
+ });
+ bdd.it('should parse JIDs per RFC6122', function () {
+ var jid = Candy.Util.Parser.jid("::foo::@example.com");
+ expect(jid.node).to.equal("::foo::");
+ expect(jid.domain).to.equal("example.com");
+ expect(jid.resource).to.equal(undefined);
+ });
+ bdd.it('should parse JIDs per RFC6122', function () {
+ var jid = Candy.Util.Parser.jid("<foo>@example.com");
+ expect(jid.node).to.equal("<foo>");
+ expect(jid.domain).to.equal("example.com");
+ expect(jid.resource).to.equal(undefined);
+ });
+ bdd.it('should parse JIDs per RFC6122', function () {
+ return true; // Skip this for now. See https://github.com/theintern/intern/pull/195
+ var jid = Candy.Util.Parser.jid("user@host@example.com");
+ expect(jid.node).to.equal("user@host");
+ expect(jid.domain).to.equal("example.com");
+ expect(jid.resource).to.equal(undefined);
+ });
+ bdd.it('should parse JIDs per RFC6122', function () {
+ var jid = Candy.Util.Parser.jid("c:\\net@example.com");
+ expect(jid.node).to.equal('c:\\net');
+ expect(jid.domain).to.equal('example.com');
+ expect(jid.resource).to.equal(undefined);
+ });
+ bdd.it('should parse JIDs per RFC6122', function () {
+ var jid = Candy.Util.Parser.jid("c:\\net@example.com");
+ expect(jid.node).to.equal("c:\\net");
+ expect(jid.domain).to.equal("example.com");
+ expect(jid.resource).to.equal(undefined);
+ });
+ bdd.it('should parse JIDs per RFC6122', function () {
+ var jid = Candy.Util.Parser.jid("c:\\cool stuff@example.com");
+ expect(jid.node).to.equal("c:\\cool stuff");
+ expect(jid.domain).to.equal("example.com");
+ expect(jid.resource).to.equal(undefined);
+ });
+ bdd.it('should parse JIDs per RFC6122', function () {
+ var jid = Candy.Util.Parser.jid("c:\\5commas@example.com");
+ expect(jid.node).to.equal("c:\\5commas");
+ expect(jid.domain).to.equal("example.com");
+ expect(jid.resource).to.equal(undefined);
+ });
+ });
+ });
+ });
+});
diff --git a/tests/helper.js b/tests/helper.js
new file mode 100644
index 0000000..72d0daf
--- /dev/null
+++ b/tests/helper.js
@@ -0,0 +1,90 @@
+/*global define, Strophe, Candy, $ */
+
+'use strict';
+
+(function (testHelper) {
+ // Module systems magic dance.
+ if (typeof require === "function" && typeof exports === "object" && typeof module === "object") {
+ // NodeJS
+ module.exports = testHelper({});
+ } else if (typeof define === "function" && define.amd) {
+ // AMD
+ define(function () {
+ return testHelper({});
+ });
+ }
+}(function testHelper(self) {
+ self.stanzaFromRequest = function (request) {
+ var builder = request.firstCall.args[0];
+ if (builder.tree) { builder.tree(); }
+ return $(builder);
+ };
+
+ self.createRequest = function (stanza) {
+ stanza = typeof stanza.tree === "function" ? stanza.tree() : stanza;
+ var req = new Strophe.Request(stanza, function() {});
+ req.getResponse = function() {
+ var env = new Strophe.Builder('env', {type: 'mock'}).tree();
+ env.appendChild(stanza);
+ return env;
+ };
+ return req;
+ };
+
+ self.receiveStanza = function (stanza) {
+ Candy.Core.getConnection()._dataRecv(self.createRequest(stanza));
+ };
+
+ self.bootstrapTestClient = function () {
+ Candy.Core.init(
+ 'http://foo.bar/http-bind',
+ {
+ initialRosterVersion: 'abc',
+ initialRosterItems: [
+ {
+ jid: 'stored@guy.com',
+ name: 'Stored Guy',
+ subscription: 'both',
+ groups: ['Some', 'People'],
+ resources: {
+ 'resource1': {
+ show: 'busy',
+ status: 'Stuff',
+ priority: 10
+ }
+ }
+ }
+ ]
+ }
+ );
+
+ var fakeConnection = Candy.Core.getConnection();
+ fakeConnection.authenticated = true;
+ fakeConnection.jid = 'n@d/r';
+
+ // The Strophe roster plugin adds its callbacks when we connect only (see https://github.com/strophe/strophejs-plugins/commit/4f3bcd25c43142f99c314f75a9bc10c8957a23d1). Add them manually here to compensate.
+ fakeConnection.addHandler(fakeConnection.roster._onReceivePresence.bind(fakeConnection.roster), null, 'presence', null, null, null);
+ fakeConnection.addHandler(fakeConnection.roster._onReceiveIQ.bind(fakeConnection.roster), Strophe.NS.ROSTER, 'iq', "set", null, null);
+
+ Candy.Core.registerEventHandlers();
+
+ // Remove all event handlers
+ $(Candy).off();
+
+ return fakeConnection;
+ };
+
+ self.setupTests = function (bdd, sinon) {
+ bdd.before(function () {
+ sinon.stub(Strophe.Bosh.prototype, '_processRequest');
+ });
+
+ bdd.after(function () {
+ Strophe.Bosh.prototype._processRequest.restore();
+ });
+
+ bdd.beforeEach(self.bootstrapTestClient);
+ };
+
+ return self;
+}));
diff --git a/tests/intern.js b/tests/intern.js
new file mode 100644
index 0000000..52d4140
--- /dev/null
+++ b/tests/intern.js
@@ -0,0 +1,71 @@
+/*global define */
+
+// Learn more about configuring this file at <https://github.com/theintern/intern/wiki/Configuring-Intern>.
+// These default settings work OK for most people. The options that *must* be changed below are the
+// packages, suites, excludeInstrumentation, and (if you want functional tests) functionalSuites.
+define({
+ // The port on which the instrumenting proxy will listen
+ proxyPort: 9000,
+
+ // A fully qualified URL to the Intern proxy
+ proxyUrl: 'http://localhost:9000/',
+
+ // Default desired capabilities for all environments. Individual capabilities can be overridden by any of the
+ // specified browser environments in the `environments` array below as well. See
+ // https://code.google.com/p/selenium/wiki/DesiredCapabilities for standard Selenium capabilities and
+ // https://saucelabs.com/docs/additional-config#desired-capabilities for Sauce Labs capabilities.
+ // Note that the `build` capability will be filled in with the current commit ID from the Travis CI environment
+ // automatically
+ capabilities: {
+ 'selenium-version': '2.41.0'
+ },
+
+ // Browsers to run integration testing against. Note that version numbers must be strings if used with Sauce
+ // OnDemand. Options that will be permutated are browserName, version, platform, and platformVersion; any other
+ // capabilities options specified for an environment will be copied as-is
+ environments: [
+ { browserName: 'internet explorer', version: '11', platform: 'Windows 8.1' },
+ { browserName: 'internet explorer', version: '10', platform: 'Windows 8' },
+ { browserName: 'firefox', version: '30', platform: [ 'OS X 10.9', 'Windows 7', 'Linux' ] },
+ { browserName: 'chrome', version: '31', platform: [ 'OS X 10.9', 'Windows 7', 'Linux' ] },
+ { browserName: 'safari', version: '6', platform: 'OS X 10.8' },
+ { browserName: 'safari', version: '7', platform: 'OS X 10.9' }
+ ],
+
+ // Maximum number of simultaneous integration tests that should be executed on the remote WebDriver service
+ maxConcurrency: 3,
+
+ // Name of the tunnel class to use for WebDriver tests
+ tunnel: 'SauceLabsTunnel',
+
+ // The desired AMD loader to use when running unit tests (client.html/client.js). Omit to use the default Dojo
+ // loader
+ useLoader: {
+ 'host-node': 'dojo/dojo',
+ 'host-browser': 'node_modules/dojo/dojo.js'
+ },
+
+ // Configuration options for the module loader; any AMD configuration options supported by the specified AMD loader
+ // can be used here
+ loader: {
+ // Packages that should be registered with the loader in each testing environment
+ packages: [
+ { name: 'candy', location: '.' }
+ , { name: 'jquery', location: 'bower_components/jquery', main: 'jquery' }
+ , { name: 'sinon', location: 'node_modules/sinon/lib', main: 'sinon' }
+ , { name: 'sinon-chai', location: 'node_modules/sinon-chai/lib', main: 'sinon-chai' }
+ , { name: 'lolex', location: 'node_modules/lolex', main: 'lolex' }
+ ]
+ },
+
+ // Non-functional test suite(s) to run in each browser
+ suites: [ 'tests/candy/unit/all' ],
+
+ // Functional test suite(s) to run in each browser once non-functional tests are completed
+ functionalSuites: [ 'tests/candy/functional/all' ],
+
+ // A regular expression matching URLs to files that should not be included in code coverage analysis
+ excludeInstrumentation: /^(?:tests\/|node_modules\/|bower_components\/|libs\.[^.]+\.js)/,
+
+ reporters: ['runner', 'lcov']
+});
diff --git a/tests/intern.local.js b/tests/intern.local.js
new file mode 100644
index 0000000..3ca4ac4
--- /dev/null
+++ b/tests/intern.local.js
@@ -0,0 +1,11 @@
+/*global define */
+
+define([
+ './intern'
+], function (intern) {
+ intern.tunnel = 'NullTunnel';
+
+ intern.environments = [ { browserName: 'phantomjs' } ];
+
+ return intern;
+});
diff --git a/tests/intern.vagrant.js b/tests/intern.vagrant.js
new file mode 100644
index 0000000..eb4b1cf
--- /dev/null
+++ b/tests/intern.vagrant.js
@@ -0,0 +1,9 @@
+/*global define */
+
+define([
+ './intern.local'
+], function (intern) {
+ intern.proxyUrl = 'http://192.168.88.1:9000/';
+
+ return intern;
+});
diff --git a/libs/dateformat/dateFormat.js b/vendor_libs/dateformat/dateFormat.js
index afbb8ee..afbb8ee 100644
--- a/libs/dateformat/dateFormat.js
+++ b/vendor_libs/dateformat/dateFormat.js