diff options
author | Michael Weibel <michael.weibel@gmail.com> | 2015-07-10 08:40:48 +0300 |
---|---|---|
committer | Michael Weibel <michael.weibel@gmail.com> | 2015-07-10 08:40:48 +0300 |
commit | cd048fd58fc48e02c7192885b6b81804ef1f7850 (patch) | |
tree | 97a3420016396c07f38127ac7b9b47fcd09a873c | |
parent | a9dcafe1a1995b61755986b56777fb2f44c832b2 (diff) | |
parent | 7e439479ccce1495ab51ed9c87d191ede195e725 (diff) |
Merge branch 'dev'v2.0.0
Update version to 2.0.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 @@ -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 @@ -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 +}; @@ -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 @@ -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)<3|<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&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}}">▾</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":"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"}
\ 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)<3|<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&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}}">▾</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, "&"); - text = text.replace(/</g, "<"); - text = text.replace(/>/g, ">"); - text = text.replace(/'/g, "'"); - text = text.replace(/"/g, """); - 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, "&").replace(/\'/g, "'").replace(/>/g, ">").replace(/</g, "<") + "'"; - } - } - 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 "&"; - - case "\\": - return "\\\\"; - - case '"': - return """; - - case "'": - return "'"; - - case "<": - return "<"; - - case ">": - return ">"; - - 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,"&"),a=a.replace(/</g,"<"),a=a.replace(/>/g,">"),a=a.replace(/'/g,"'"),a=a.replace(/"/g,""")},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,"&").replace(/\'/g,"'").replace(/>/g,">").replace(/</g,"<")+"'");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"&";case"\\":return"\\\\";case'"':return""";case"'":return"'";case"<":return"<";case">":return">";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 Binary files differdeleted file mode 100644 index 72390d4..0000000 --- a/res/audioplayer.swf +++ /dev/null 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 Binary files differnew file mode 100644 index 0000000..b090b29 --- /dev/null +++ b/res/notify.m4a diff --git a/res/notify.mp3 b/res/notify.mp3 Binary files differindex c00d997..c7f7495 100644 --- a/res/notify.mp3 +++ b/res/notify.mp3 diff --git a/res/notify.ogg b/res/notify.ogg Binary files differnew file mode 100644 index 0000000..c6663d3 --- /dev/null +++ b/res/notify.ogg 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&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}}">▾</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 : %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 :', 'messageSubmit': 'Envoyer', - 'labelUsername': 'Nom d\'utilisateur :', - 'labelNickname': 'Pseudo :', - 'labelPassword': 'Mot de passe :', + 'labelUsername': 'Nom d’utilisateur :', + 'labelNickname': 'Pseudo :', + 'labelPassword': 'Mot de passe :', 'loginSubmit' : 'Connexion', - 'loginInvalid' : 'JID invalide', + 'loginInvalid' : 'JID invalide', - 'reason' : 'Motif :', - 'subject' : 'Titre :', - 'reasonWas' : 'Motif : %s.', + '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', @@ -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 : droits insuffisants.', + 'errorMaxOccupantsReached': 'Vous ne pouvez pas entrer dans le salon %s : 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 |