소스 검색

Merge branch 'private-jquery' of github.com:jcbrand/converse.js into private-jquery

JC Brand 10 년 전
부모
커밋
c7abf54359
11개의 변경된 파일566개의 추가작업 그리고 401개의 파일을 삭제
  1. 2 3
      bower.json
  2. 126 100
      converse.js
  3. 1 0
      docs/CHANGES.rst
  4. 335 280
      docs/source/index.rst
  5. 2 2
      main.js
  6. 37 4
      spec/converse.js
  7. 4 3
      src/deps-full.js
  8. 4 3
      src/deps-no-otr.js
  9. 4 3
      src/deps-website-no-otr.js
  10. 4 3
      src/deps-website.js
  11. 47 0
      src/utils.js

+ 2 - 3
bower.json

@@ -17,9 +17,7 @@
     "backbone.overview": "*",
     "strophe": "~1.1.3",
     "strophe.roster": "https://raw.github.com/strophe/strophejs-plugins/b1f364eb6e854ffe844c57add38e885cfeb9b498/roster/strophe.roster.js",
-    "strophe.vcard": "https://raw.github.com/strophe/strophejs-plugins/f5c9e16b463610d501591452cded0359f53aae48/vcard/strophe.vcard.js",
-    "strophe.disco": "https://raw.github.com/jcbrand/strophejs-plugins/75c8693992bc357c699b6d615eeb396e799f5c02/disco/strophe.disco.js",
-    "strophe.muc": "https://raw.githubusercontent.com/strophe/strophejs-plugins/8056cbca4dcd69700c44e1e3341a9ce387a6af0c/muc/strophe.muc.js",
+    "strophe.muc": "https://raw.githubusercontent.com/strophe/strophejs-plugins/master/muc/strophe.muc.js",
     "otr": "0.2.12",
     "crypto-js-evanvosberg": "~3.1.2",
     "almond": "~0.2.9",
@@ -32,6 +30,7 @@
     "bootstrapJS": "https://raw.githubusercontent.com/jcbrand/bootstrap/7d96a5f60d26c67b5348b270a775518b96a702c8/dist/js/bootstrap.js",
     "fontawesome": "~4.1.0",
     "typeahead.js": "https://raw.githubusercontent.com/jcbrand/typeahead.js/eedfb10505dd3a20123d1fafc07c1352d83f0ab3/dist/typeahead.jquery.js"
+    "strophejs-plugins": "~0.0.4"
   },
   "exportsOverride": {}
 }

+ 126 - 100
converse.js

@@ -11,20 +11,35 @@
     if (typeof define === 'function' && define.amd) {
         define("converse",
               ["converse-dependencies", "converse-templates"],
-            function(dependencies, templates) {
-                var otr = dependencies.otr,
-                    moment = dependencies.moment;
+            function (dependencies, templates) {
+                var otr = dependencies.otr;
                 if (typeof otr !== "undefined") {
-                    return factory(dependencies.jQuery, _, otr.OTR, otr.DSA, templates, moment);
+                    return factory(
+                        dependencies.jQuery,
+                        _,
+                        otr.OTR,
+                        otr.DSA,
+                        templates,
+                        dependencies.moment,
+                        dependencies.utils
+                    );
                 } else {
-                    return factory(dependencies.jQuery, _, undefined, undefined, templates, moment);
+                    return factory(
+                        dependencies.jQuery,
+                        _,
+                        undefined,
+                        undefined,
+                        templates,
+                        dependencies.moment,
+                        dependencies.utils
+                    );
                 }
             }
         );
     } else {
-        root.converse = factory(jQuery, _, OTR, DSA, JST, moment);
+        root.converse = factory(jQuery, _, OTR, DSA, JST, moment, utils);
     }
-}(this, function ($, _, OTR, DSA, templates, moment) {
+}(this, function ($, _, OTR, DSA, templates, moment, utils) {
     // "use strict";
     // Cannot use this due to Safari bug.
     // See https://github.com/jcbrand/converse.js/issues/196
@@ -41,25 +56,6 @@
         interpolate : /\{\{([\s\S]+?)\}\}/g
     };
 
-    // TODO: these non-backbone methods should all be moved to utils.
-    $.fn.addHyperlinks = function() {
-        if (this.length > 0) {
-            this.each(function(i, obj) {
-                var x = $(obj).html();
-                var list = x.match(/\b(https?:\/\/|www\.|https?:\/\/www\.)[^\s<]{2,200}\b/g );
-                if (list) {
-                    for (i=0; i<list.length; i++) {
-                        var prot = list[i].indexOf('http://') === 0 || list[i].indexOf('https://') === 0 ? '' : 'http://';
-                        var escaped_url = encodeURI(decodeURI(list[i])).replace(/[!'()]/g, escape).replace(/\*/g, "%2A");
-                        x = x.replace(list[i], "<a target='_blank' href='" + prot + escaped_url + "'>"+ list[i] + "</a>" );
-                    }
-                }
-                $(obj).html(x);
-            });
-        }
-        return this;
-    };
-
     var contains = function (attr, query) {
         return function (item) {
             if (typeof attr === 'object') {
@@ -81,28 +77,28 @@
         };
     };
 
+    // XXX: these can perhaps be moved to src/polyfills.js
     String.prototype.splitOnce = function (delimiter) {
         var components = this.split(delimiter);
         return [components.shift(), components.join(delimiter)];
     };
 
-    var playNotification = function () {
-        var audio;
-        if (converse.play_sounds && typeof Audio !== "undefined"){
-            audio = new Audio("sounds/msg_received.ogg");
-            if (audio.canPlayType('/audio/ogg')) {
-                audio.play();
-            } else {
-                audio = new Audio("/sounds/msg_received.mp3");
-                audio.play();
-            }
+    String.prototype.hash = function() {
+        // XXX: We should probably use the crypto libs we already use for OTR
+        var hash = 0, i, chr, len;
+        if (this.length === 0) return hash;
+        for (i = 0, len = this.length; i < len; i++) {
+            chr   = this.charCodeAt(i);
+            hash  = ((hash << 5) - hash) + chr;
+            hash |= 0; // Convert to 32bit integer
         }
+        return Math.abs(hash);
     };
 
-    $.fn.addEmoticons = function() {
+    $.fn.addEmoticons = function () {
         if (converse.visible_toolbar_buttons.emoticons) {
             if (this.length > 0) {
-                this.each(function(i, obj) {
+                this.each(function (i, obj) {
                     var text = $(obj).html();
                     text = text.replace(/&gt;:\)/g, '<span class="emoticon icon-evil"></span>');
                     text = text.replace(/:\)/g, '<span class="emoticon icon-smiley"></span>');
@@ -134,18 +130,32 @@
         return this;
     };
 
+    var playNotification = function () {
+        var audio;
+        if (converse.play_sounds && typeof Audio !== "undefined"){
+            audio = new Audio("sounds/msg_received.ogg");
+            if (audio.canPlayType('/audio/ogg')) {
+                audio.play();
+            } else {
+                audio = new Audio("/sounds/msg_received.mp3");
+                audio.play();
+            }
+        }
+    };
+
     var converse = {
+        plugins: {},
         templates: templates,
-        emit: function(evt, data) {
+        emit: function (evt, data) {
             $(this).trigger(evt, data);
         },
-        once: function(evt, handler) {
+        once: function (evt, handler) {
             $(this).one(evt, handler);
         },
-        on: function(evt, handler) {
+        on: function (evt, handler) {
             $(this).bind(evt, handler);
         },
-        off: function(evt, handler) {
+        off: function (evt, handler) {
             $(this).unbind(evt, handler);
         },
         refreshWebkit: function () {
@@ -302,30 +312,8 @@
 
         // Translation machinery
         // ---------------------
-        var __ = $.proxy(function (str) {
-            // Translation factory
-            if (this.i18n === undefined) {
-                this.i18n = locales.en;
-            }
-            var t = this.i18n.translate(str);
-            if (arguments.length>1) {
-                return t.fetch.apply(t, [].slice.call(arguments,1));
-            } else {
-                return t.fetch();
-            }
-        }, this);
-
-        var ___ = function (str) {
-            /* XXX: This is part of a hack to get gettext to scan strings to be
-             * translated. Strings we cannot send to the function above because
-             * they require variable interpolation and we don't yet have the
-             * variables at scan time.
-             *
-             * See actionInfoMessages
-             */
-            return str;
-        };
-
+        var __ = utils.__;
+        var ___ = utils.___;
         // Translation aware constants
         // ---------------------------
         var OTR_CLASS_MAPPING = {};
@@ -588,7 +576,7 @@
         };
 
         this.registerGlobalEventHandlers = function () {
-            $(document).click(function() {
+            $(document).click(function () {
                 if ($('.toggle-otr ul').is(':visible')) {
                     $('.toggle-otr ul', this).slideUp();
                 }
@@ -653,7 +641,7 @@
               })
               .c('enable', {xmlns: 'urn:xmpp:carbons:2'});
             this.connection.send(carbons_iq);
-            this.connection.addHandler(function(iq) {
+            this.connection.addHandler(function (iq) {
                 //TODO: check if carbons was enabled:
             }, null, "iq", null, "enablecarbons");
         };
@@ -1105,7 +1093,6 @@
                     // are mentioned.
                     extra_classes += ' mentioned';
                 }
-
                 var message = template({
                     'sender': msg_dict.sender,
                     'time': msg_time.format('hh:mm'),
@@ -1550,7 +1537,7 @@
                 }
                 var ctx = canvas.getContext('2d');
                 var img = new Image();   // Create new Image object
-                img.onload = function() {
+                img.onload = function () {
                     var ratio = img.width/img.height;
                     ctx.drawImage(img, 0,0, 35*ratio, 35);
                 };
@@ -2307,10 +2294,8 @@
                 this.showStatusNotification(__("Error: could not execute the command"), true);
             },
 
-            sendChatRoomMessage: function (body) {
-                var match = body.replace(/^\s*/, "").match(/^\/(.*?)(?: (.*))?$/) || [false],
-                    $chat_content, args;
-
+            sendChatRoomMessage: function (text) {
+                var match = text.replace(/^\s*/, "").match(/^\/(.*?)(?: (.*))?$/) || [false], args, fullname, time;
                 switch (match[1]) {
                     case 'ban':
                         args = match[2].splitOnce(' ');
@@ -2324,8 +2309,7 @@
                         converse.connection.muc.deop(this.model.get('jid'), args[0], args[1], undefined, $.proxy(this.onCommandError, this));
                         break;
                     case 'help':
-                        $chat_content = this.$el.find('.chat-content');
-                        msgs = [
+                        this.showHelpMessages([
                             '<strong>/ban</strong>: '   +__('Ban user from room'),
                             '<strong>/clear</strong>: ' +__('Remove messages'),
                             '<strong>/help</strong>: '  +__('Show this menu'),
@@ -2335,8 +2319,7 @@
                             '<strong>/nick</strong>: '  +__('Change your nickname'),
                             '<strong>/topic</strong>: ' +__('Set room topic'),
                             '<strong>/voice</strong>: ' +__('Allow muted user to post messages')
-                            ];
-                        this.showHelpMessages(msgs);
+                        ]);
                         break;
                     case 'kick':
                         args = match[2].splitOnce(' ');
@@ -2361,7 +2344,15 @@
                         converse.connection.muc.voice(this.model.get('jid'), args[0], args[1], undefined, $.proxy(this.onCommandError, this));
                         break;
                     default:
-                        this.last_msgid = converse.connection.muc.groupchat(this.model.get('jid'), body);
+                        fullname = converse.xmppstatus.get('fullname');
+                        time = moment().format();
+                        this.model.messages.create({
+                            fullname: _.isEmpty(fullname)? converse.bare_jid: fullname,
+                            sender: 'me',
+                            time: time,
+                            message: text,
+                            msgid: converse.connection.muc.groupchat(this.model.get('jid'), text, undefined, String((time+text).hash()))
+                        });
                     break;
                 }
             },
@@ -2722,10 +2713,15 @@
                 var $message = $(message),
                     body = $message.children('body').text(),
                     jid = $message.attr('from'),
+                    msgid = $message.attr('id'),
                     resource = Strophe.getResourceFromJid(jid),
                     sender = resource && Strophe.unescapeNode(resource) || '',
                     delayed = $message.find('delay').length > 0,
                     subject = $message.children('subject').text();
+
+                if (this.model.messages.findWhere({msgid: msgid})) {
+                    return true; // We already have this message stored.
+                }
                 this.showStatusMessages($message);
                 if (subject) {
                     this.$el.find('.chatroom-topic').text(subject).attr('title', subject);
@@ -2911,7 +2907,7 @@
                 }, this);
             },
 
-            _ensureElement: function() {
+            _ensureElement: function () {
                 /* Override method from backbone.js
                  * If the #conversejs element doesn't exist, create it.
                  */
@@ -3019,19 +3015,18 @@
                  * If it doesn't exist, create it.
                  */
                 var chatbox  = this.model.get(attrs.jid);
-                if (chatbox) {
-                    if (chatbox.get('minimized')) {
-                        chatbox.maximize();
-                    } else {
-                        chatbox.trigger('show');
-                    }
-                } else {
+                if (!chatbox) {
                     chatbox = this.model.create(attrs, {
                         'error': function (model, response) {
                             converse.log(response.responseText);
                         }
                     });
                 }
+                if (chatbox.get('minimized')) {
+                    chatbox.maximize();
+                } else {
+                    chatbox.trigger('show');
+                }
                 return chatbox;
             }
         });
@@ -3085,14 +3080,10 @@
                 return this;
             },
 
-            restore: function(ev) {
+            restore: _.debounce(function (ev) {
                 if (ev && ev.preventDefault) {
                     ev.preventDefault();
                 }
-                this._restore();
-            },
-
-            _restore: _.debounce(function () {
                 this.remove();
                 this.model.maximize();
             }, 200)
@@ -3262,6 +3253,8 @@
 
             openChat: function (ev) {
                 if (ev && ev.preventDefault) { ev.preventDefault(); }
+                // XXX: Can this.model.attributes be used here, instead of
+                // manually specifying all attributes?
                 return converse.chatboxviews.showChat({
                     'id': this.model.get('jid'),
                     'jid': this.model.get('jid'),
@@ -4240,7 +4233,7 @@
                             'desc_change_status': __('Click to change your chat status')
                             }));
                 // iterate through all the <option> elements and add option values
-                options.each(function(){
+                options.each(function (){
                     options_list.push(converse.templates.status_option({
                         'value': $(this).val(),
                         'text': this.text
@@ -4623,23 +4616,44 @@
             return this;
         };
 
+        this._initializePlugins = function () {
+            _.each(this.plugins, $.proxy(function (plugin) {
+                $.proxy(plugin, this)(this);
+            }, this));
+        };
+
         // Initialization
         // --------------
         // This is the end of the initialize method.
+        this._initializePlugins();
         this._initialize();
         this.registerGlobalEventHandlers();
         converse.emit('initialized');
     };
+
+    var wrappedChatBox = function (chatbox) {
+        return {
+            'endOTR': $.proxy(chatbox.endOTR, chatbox),
+            'get': $.proxy(chatbox.get, chatbox),
+            'initiateOTR': $.proxy(chatbox.initiateOTR, chatbox),
+            'maximize': $.proxy(chatbox.maximize, chatbox),
+            'minimize': $.proxy(chatbox.minimize, chatbox),
+            'set': $.proxy(chatbox.set, chatbox)
+        };
+    };
     return {
-        'initialize': function (settings, callback) {
-            converse.initialize(settings, callback);
-        },
         'getBuddy': function (jid) {
             var contact = converse.roster.get(Strophe.getBareJidFromJid(jid));
             if (contact) {
                 return contact.attributes;
             }
         },
+        'getChatBox': function (jid) {
+            var chatbox = converse.chatboxes.get(jid);
+            if (chatbox) {
+                return wrappedChatBox(chatbox);
+            }
+        },
         'getRID': function () {
             if (converse.expose_rid_and_sid && typeof converse.connection !== "undefined") {
                 return converse.connection.rid || converse.connection._proto.rid;
@@ -4652,15 +4666,27 @@
             }
             return null;
         },
-        'once': function(evt, handler) {
+        'initialize': function (settings, callback) {
+            converse.initialize(settings, callback);
+        },
+        'jQuery': $,
+        'openChatBox': function (jid) {
+            var contact = converse.roster.get(Strophe.getBareJidFromJid(jid));
+            if (contact) {
+                return wrappedChatBox(converse.chatboxviews.showChat(contact.attributes));
+            }
+        },
+        'once': function (evt, handler) {
             converse.once(evt, handler);
         },
-        'on': function(evt, handler) {
+        'on': function (evt, handler) {
             converse.on(evt, handler);
         },
-        'off': function(evt, handler) {
+        'off': function (evt, handler) {
             converse.off(evt, handler);
         },
-        'jQuery': $
+        'registerPlugin': function (name, callback) {
+            converse.plugins[name] = callback;
+        },
     };
 }));

+ 1 - 0
docs/CHANGES.rst

@@ -8,6 +8,7 @@ Changelog
 * Bugfix. Cannot read property "top" of undefined. [jcbrand]
 * Add new event, noResumeableSession, for when keepalive=true and there aren't
   any prebind session tokens. [jcbrand]
+* Add 2 new API methods, getChatBox and openChatBox. [jcbrand]
 
 0.8.3 (2014-09-22)
 ------------------

+ 335 - 280
docs/source/index.rst

@@ -28,7 +28,7 @@ tags:
     <link rel="stylesheet" type="text/css" media="screen" href="css/converse.min.css">
     <script src="builds/converse.min.js"></script>
 
-You need to initialize Converse.js with configuration settings particular to
+You need to initialize Converse.js with configuration settings according to
 your requirements.
 
 Please refer to the `Configuration variables`_ section further below for info on
@@ -56,12 +56,11 @@ The `index.html <https://github.com/jcbrand/converse.js/blob/master/index.html>`
 Converse.js repository may serve as a nice usable example.
 
 These minified files provide the same demo-like functionality as is available
-on the `conversejs.org <http://conversejs.org>`_ website. Useful for testing or demoing, but not very
-practical.
+on the `conversejs.org <http://conversejs.org>`_ website. Useful for testing or demoing.
 
-You'll most likely want to implement some kind of single-signon solution for
+You'll most likely want to implement some kind of single persistent session solution for
 your website, where users authenticate once in your website and then stay
-logged into their XMPP session upon page reload.
+logged in to their XMPP session upon the next page reload.
 
 For more info on this, read: `Prebinding and Single Session Support`_.
 
@@ -90,19 +89,106 @@ requirement for many sites dealing with sensitive information.
 
 You'll need to set up your own XMPP server and in order to have
 `Session Support`_ (i.e. single-signon functionality whereby users are authenticated once and stay
-logged in to XMPP upon page reload) you will also have to add some server-side
+logged in to XMPP upon page reload) you will likely also have to add some server-side
 code.
 
 The `What you will need`_ section has more information on all these
 requirements.
 
 
+========
+Features
+========
+
+Off-the-record encryption
+=========================
+
+Converse.js supports `Off-the-record (OTR) <https://otr.cypherpunks.ca/>`_
+encrypted messaging.
+
+The OTR protocol not only **encrypts your messages**, it provides ways to
+**verify the identity** of the person you are talking to,
+**plausible deniability** and **perfect forward secrecy** by generating
+new encryption keys for each conversation.
+
+In its current state, Javascript cryptography is fraught with dangers and
+challenges that make it impossible to reach the same standard of security that
+is available with native "desktop" software.
+
+This is due to its runtime malleability, the way it is "installed" (e.g.
+served) and the browser's lack of cryptographic primitives needed to implement
+secure crypto.
+
+For harsh but fairly valid criticism of Javascript cryptography, read:
+`Javascript Cryptography Considered Harmful <http://www.matasano.com/articles/javascript-cryptography/>`_.
+
+To get an idea on how this applies to OTR support in Converse.js, please read
+`my thoughts on it <https://opkode.com/media/blog/2013/11/11/conversejs-otr-support>`_.
+
+For now, suffice to say that although its useful to have OTR support in
+Converse.js in order to avoid most eavesdroppers, if you need serious
+communications privacy, then you're much better off using native software.
+
+Sound Notifications
+===================
+
+From version 0.8.1 Converse.js can play a sound notification when you receive a
+message.
+
+For more info, please see the `play_sounds`_ configuration setting.
+
+Multilingual Support
+====================
+
+Converse.js is translated into multiple languages. The default build,
+``converse.min.js``, includes all languages.
+
+Languages increase the size of the Converse.js significantly.
+
+If you only need one, or a subset of the available languages, it's better to
+make a custom build which includes only those languages that you need.
+
+Chat Rooms
+==========
+
+Commands
+--------
+
+Here are the different commands that may be used in a chat room:
+
++------------+----------------------------------------------------------------------------------------------+---------------------------------------------------------------+
+| Event Type | When is it triggered?                                                                        | Example (substitue $nickname with an actual user's nickname)  |
++============+==============================================================================================+===============================================================+
+| **ban**    | Ban a user from the chat room. They will not be able to join again.                          | /ban $nickname                                                |
++------------+----------------------------------------------------------------------------------------------+---------------------------------------------------------------+
+| **clear**  | Clear the messages shown in the chat room.                                                   | /clear                                                        |
++------------+----------------------------------------------------------------------------------------------+---------------------------------------------------------------+
+| **deop**   | Make a moderator a normal participant.                                                       | /deop $nickname [$reason]                                     |
++------------+----------------------------------------------------------------------------------------------+---------------------------------------------------------------+
+| **help**   | Show the list of available commands.                                                         | /help                                                         |
++------------+----------------------------------------------------------------------------------------------+---------------------------------------------------------------+
+| **kick**   | Kick a user out of a room. They will be able to join again.                                  | /kick $nickname [$reason]                                     |
++------------+----------------------------------------------------------------------------------------------+---------------------------------------------------------------+
+| **me**     | Speak in the 3rd person.                                                                     | /me $message                                                  |
++------------+----------------------------------------------------------------------------------------------+---------------------------------------------------------------+
+| **mute**   | Remove a user's ability to post messages to the room. They will still be able to observe.    | /mute $nickname [$reason]                                     |
++------------+----------------------------------------------------------------------------------------------+---------------------------------------------------------------+
+| **nick**   | Change your nickname.                                                                        | /nick $nickname                                               |
++------------+----------------------------------------------------------------------------------------------+---------------------------------------------------------------+
+| **op**     | Make a normal participant a moderator.                                                       | /op $nickname [$reason]                                       |
++------------+----------------------------------------------------------------------------------------------+---------------------------------------------------------------+
+| **topic**  | Set the topic of the chat room.                                                              | /topic ${topic text}                                          |
++------------+----------------------------------------------------------------------------------------------+---------------------------------------------------------------+
+| **voice**  | Allow a muted user to post messages to the room.                                             | /voice $nickname [$reason]                                    |
++------------+----------------------------------------------------------------------------------------------+---------------------------------------------------------------+
+
+
 ==================
 What you will need
 ==================
 
-An XMPP/Jabber server
-=====================
+An XMPP server
+==============
 
 *Converse.js* implements `XMPP`_ as its messaging protocol, and therefore needs
 to connect to an XMPP/Jabber server (Jabber is really just a synonym for XMPP).
@@ -288,92 +374,6 @@ Example code for server-side prebinding
     See this `example Django application`_ by Jack Moffitt.
 
 
-========
-Features
-========
-
-Off-the-record encryption
-=========================
-
-Converse.js supports `Off-the-record (OTR) <https://otr.cypherpunks.ca/>`_
-encrypted messaging.
-
-The OTR protocol not only **encrypts your messages**, it provides ways to
-**verify the identity** of the person you are talking to,
-**plausible deniability** and **perfect forward secrecy** by generating
-new encryption keys for each conversation.
-
-In its current state, Javascript cryptography is fraught with dangers and
-challenges that make it impossible to reach the same standard of security that
-is available with native "desktop" software.
-
-This is due to its runtime malleability, the way it is "installed" (e.g.
-served) and the browser's lack of cryptographic primitives needed to implement
-secure crypto.
-
-For harsh but fairly valid criticism of Javascript cryptography, read:
-`Javascript Cryptography Considered Harmful <http://www.matasano.com/articles/javascript-cryptography/>`_.
-
-To get an idea on how this applies to OTR support in Converse.js, please read
-`my thoughts on it <https://opkode.com/media/blog/2013/11/11/conversejs-otr-support>`_.
-
-For now, suffice to say that although its useful to have OTR support in
-Converse.js in order to avoid most eavesdroppers, if you need serious
-communications privacy, then you're much better off using native software.
-
-Sound Notifications
-===================
-
-From version 0.8.1 Converse.js can play a sound notification when you receive a
-message.
-
-For more info, please see the `play_sounds`_ configuration setting.
-
-Multilingual Support
-====================
-
-Converse.js is translated into multiple languages. The default build,
-``converse.min.js``, includes all languages.
-
-Languages increase the size of the Converse.js significantly.
-
-If you only need one, or a subset of the available languages, it's better to
-make a custom build which includes only those languages that you need.
-
-Chat Rooms
-==========
-
-Commands
---------
-
-Here are the different commands that may be used in a chat room:
-
-+------------+----------------------------------------------------------------------------------------------+---------------------------------------------------------------+
-| Event Type | When is it triggered?                                                                        | Example (substitue $nickname with an actual user's nickname)  |
-+============+==============================================================================================+===============================================================+
-| **ban**    | Ban a user from the chat room. They will not be able to join again.                          | /ban $nickname                                                |
-+------------+----------------------------------------------------------------------------------------------+---------------------------------------------------------------+
-| **clear**  | Clear the messages shown in the chat room.                                                   | /clear                                                        |
-+------------+----------------------------------------------------------------------------------------------+---------------------------------------------------------------+
-| **deop**   | Make a moderator a normal participant.                                                       | /deop $nickname [$reason]                                     |
-+------------+----------------------------------------------------------------------------------------------+---------------------------------------------------------------+
-| **help**   | Show the list of available commands.                                                         | /help                                                         |
-+------------+----------------------------------------------------------------------------------------------+---------------------------------------------------------------+
-| **kick**   | Kick a user out of a room. They will be able to join again.                                  | /kick $nickname [$reason]                                     |
-+------------+----------------------------------------------------------------------------------------------+---------------------------------------------------------------+
-| **me**     | Speak in the 3rd person.                                                                     | /me $message                                                  |
-+------------+----------------------------------------------------------------------------------------------+---------------------------------------------------------------+
-| **mute**   | Remove a user's ability to post messages to the room. They will still be able to observe.    | /mute $nickname [$reason]                                     |
-+------------+----------------------------------------------------------------------------------------------+---------------------------------------------------------------+
-| **nick**   | Change your nickname.                                                                        | /nick $nickname                                               |
-+------------+----------------------------------------------------------------------------------------------+---------------------------------------------------------------+
-| **op**     | Make a normal participant a moderator.                                                       | /op $nickname [$reason]                                       |
-+------------+----------------------------------------------------------------------------------------------+---------------------------------------------------------------+
-| **topic**  | Set the topic of the chat room.                                                              | /topic ${topic text}                                          |
-+------------+----------------------------------------------------------------------------------------------+---------------------------------------------------------------+
-| **voice**  | Allow a muted user to post messages to the room.                                             | /voice $nickname [$reason]                                    |
-+------------+----------------------------------------------------------------------------------------------+---------------------------------------------------------------+
-
 ===========
 Development
 ===========
@@ -511,6 +511,246 @@ You can run both the tests and jshint in one go by calling:
 
     grunt check
 
+
+Developer API
+=============
+
+.. note:: see also the `event api methods`_, not listed here.
+
+initialize
+----------
+
+Initializes converse.js. This method must always be called when using
+converse.js.
+
+The `initialize` method takes a map (also called a hash or dictionary) of
+`configuration variables`_.
+
+Example::
+
+    converse.initialize({
+            allow_otr: true,
+            auto_list_rooms: false,
+            auto_subscribe: false,
+            bosh_service_url: 'https://bind.example.com',
+            hide_muc_server: false,
+            i18n: locales['en'],
+            keepalive: true,
+            play_sounds: true,
+            prebind: false,
+            show_controlbox_by_default: true,
+            debug: false,
+            roster_groups: true
+        });
+
+
+getBuddy
+--------
+
+Returns a map of attributes for a given buddy (i.e. roster contact), specified
+by JID (Jabber ID).
+
+Example::
+
+    converse.getBuddy('buddy@example.com')
+
+The map of attributes:
+
++----------------+--------------------------------------------------------------------------------------------------------------------------------------+
+| Attribute      |                                                                                                                                      |
++================+======================================================================================================================================+
+| ask            | If ask === 'subscribe', then we have asked this person to be our chat buddy.                                                         |
++----------------+--------------------------------------------------------------------------------------------------------------------------------------+
+| fullname       | The person's full name.                                                                                                              |
++----------------+--------------------------------------------------------------------------------------------------------------------------------------+
+| jid            | The person's Jabber/XMPP username.                                                                                                   |
++----------------+--------------------------------------------------------------------------------------------------------------------------------------+
+| requesting     | If true, then this person is asking to be our chat buddy.                                                                            |
++----------------+--------------------------------------------------------------------------------------------------------------------------------------+
+| subscription   | The subscription state between the current user and this chat buddy. Can be `none`, `to`, `from` or `both`.                          |
++----------------+--------------------------------------------------------------------------------------------------------------------------------------+
+| id             | A unique id, same as the jid.                                                                                                        |
++----------------+--------------------------------------------------------------------------------------------------------------------------------------+
+| chat_status    | The person's chat status. Can be `online`, `offline`, `busy`, `xa` (extended away) or `away`.                                        |
++----------------+--------------------------------------------------------------------------------------------------------------------------------------+
+| user_id        | The user id part of the JID (the part before the `@`).                                                                               |
++----------------+--------------------------------------------------------------------------------------------------------------------------------------+
+| resources      | The known resources for this chat buddy. Each resource denotes a separate and connected chat client.                                 |
++----------------+--------------------------------------------------------------------------------------------------------------------------------------+
+| groups         | The roster groups in which this chat buddy was placed.                                                                               |
++----------------+--------------------------------------------------------------------------------------------------------------------------------------+
+| status         | Their human readable custom status message.                                                                                          |
++----------------+--------------------------------------------------------------------------------------------------------------------------------------+
+| image_type     | The image's file type.                                                                                                               |
++----------------+--------------------------------------------------------------------------------------------------------------------------------------+
+| image          | The Base64 encoded image data.                                                                                                       |
++----------------+--------------------------------------------------------------------------------------------------------------------------------------+
+| url            | The buddy's website URL, as specified in their VCard data.                                                                           |
++----------------+--------------------------------------------------------------------------------------------------------------------------------------+
+| vcard_updated  | When last the buddy's VCard was updated.                                                                                             |
++----------------+--------------------------------------------------------------------------------------------------------------------------------------+
+
+getChatBox
+----------
+
+Returns an object/map representing a chat box (without opening or affecting that chat box). 
+
+Example::
+
+    converse.getChatBox('buddy@example.com')
+
+The returned chat box contains the following methods:
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
++-------------+------------------------------------------+
+| Method      | Description                              |
++=============+==========================================+
+| endOTR      | End an OTR (Off-the-record) session.     |
++-------------+------------------------------------------+
+| get         | Get an attribute (i.e. accessor).        |
++-------------+------------------------------------------+
+| initiateOTR | Start an OTR (off-the-record) session.   |
++-------------+------------------------------------------+
+| maximize    | Minimize the chat box.                   |
++-------------+------------------------------------------+
+| minimize    | Maximize the chat box.                   |
++-------------+------------------------------------------+
+| set         | Set an attribute (i.e. mutator).         |
++-------------+------------------------------------------+
+
+The get and set methods can be used to retrieve and change the following attributes:
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
++-------------+-----------------------------------------------------+
+| Attribute   | Description                                         |
++=============+=====================================================+
+| height      | The height of the chat box.                         |
++-------------+-----------------------------------------------------+
+| url         | The URL of the chat box heading.                    |
++-------------+-----------------------------------------------------+
+
+getRID
+------
+
+Returns the current RID (request ID) value.
+
+getSID
+------
+
+Returns the current SID (Session ID) value.
+
+openChatBox
+-----------
+
+Opens a chat box and returns an object/map representating that chat box.
+If the chat box is already open, its text area will be focused.
+
+Example::
+
+    converse.openChatBox('buddy@example.com')
+
+Refer to `getChatBox`_ for more information on the object returned by this
+method (which is the same for both).
+
+
+Events
+======
+
+Converse.js emits events to which you can subscribe from your own Javascript.
+
+Concerning events, the following methods are available:
+
+Event API Methods
+-----------------
+
+* **on(eventName, callback)**:
+
+    Calling the ``on`` method allows you to subscribe to an event.
+    Every time the event fires, the callback method specified by ``callback`` will be
+    called.
+
+    Parameters:
+
+    * ``eventName`` is the event name as a string.
+    * ``callback`` is the callback method to be called when the event is emitted.
+
+    For example::
+
+        converse.on('message', function (messageXML) { ... });
+
+* **once(eventName, callback)**:
+
+    Calling the ``once`` method allows you to listen to an event
+    exactly once.
+
+    Parameters:
+
+    * ``eventName`` is the event name as a string.
+    * ``callback`` is the callback method to be called when the event is emitted.
+
+    For example::
+
+        converse.once('message', function (messageXML) { ... });
+
+* **off(eventName, callback)**
+
+    To stop listening to an event, you can use the ``off`` method.
+
+    Parameters:
+
+    * ``eventName`` is the event name as a string.
+    * ``callback`` refers to the function that is to be no longer executed.
+
+
+Event Types
+-----------
+
+Here are the different events that are emitted:
+
++--------------------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------+
+| Event Type                     | When is it triggered?                                                                             | Example                                                                                 |
++================================+===================================================================================================+=========================================================================================+
+| **initialized**                | Once converse.js has been initialized.                                                            | ``converse.on('initialized', function () { ... });``                                    |
++--------------------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------+
+| **ready**                      | After connection has been established and converse.js has got all its ducks in a row.             | ``converse.on('ready', function () { ... });``                                          |
++--------------------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------+
+| **reconnect**                  | After the connection has dropped. Converse.js will attempt to reconnect when not in prebind mode. | ``converse.on('reconnect', function () { ... });``                                      |
++--------------------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------+
+| **message**                    | When a message is received.                                                                       | ``converse.on('message', function (messageXML) { ... });``                              |
++--------------------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------+
+| **messageSend**                | When a message will be sent out.                                                                  | ``converse.on('messageSend', function (messageText) { ... });``                         |
++--------------------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------+
+| **noResumeableSession**        | When keepalive=true but there aren't any stored prebind tokens.                                   | ``converse.on('noResumeableSession', function () { ... });``                            |
++--------------------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------+
+| **roster**                     | When the roster is updated.                                                                       | ``converse.on('roster', function (items) { ... });``                                    |
++--------------------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------+
+| **callButtonClicked**          | When a call button (i.e. with class .toggle-call) on a chat box has been clicked.                 | ``converse.on('callButtonClicked', function (connection, model) { ... });``             |
++--------------------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------+
+| **chatBoxOpened**              | When a chat box has been opened.                                                                  | ``converse.on('chatBoxOpened', function (chatbox) { ... });``                           |
++--------------------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------+
+| **chatRoomOpened**             | When a chat room has been opened.                                                                 | ``converse.on('chatRoomOpened', function (chatbox) { ... });``                          |
++--------------------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------+
+| **chatBoxClosed**              | When a chat box has been closed.                                                                  | ``converse.on('chatBoxClosed', function (chatbox) { ... });``                           |
++--------------------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------+
+| **chatBoxFocused**             | When the focus has been moved to a chat box.                                                      | ``converse.on('chatBoxFocused', function (chatbox) { ... });``                          |
++--------------------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------+
+| **chatBoxToggled**             | When a chat box has been minimized or maximized.                                                  | ``converse.on('chatBoxToggled', function (chatbox) { ... });``                          |
++--------------------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------+
+| **roomInviteSent**             | After the user has sent out a direct invitation, to a roster contact, asking them to join a room. | ``converse.on('roomInvite', function (roomview, invitee_jid, reason) { ... });``        |
++--------------------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------+
+| **roomInviteReceived**         | After the user has sent out a direct invitation, to a roster contact, asking them to join a room. | ``converse.on('roomInvite', function (roomview, invitee_jid, reason) { ... });``        |
++--------------------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------+
+| **statusChanged**              | When own chat status has changed.                                                                 | ``converse.on('statusChanged', function (status) { ... });``                            |
++--------------------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------+
+| **statusMessageChanged**       | When own custom status message has changed.                                                       | ``converse.on('statusMessageChanged', function (message) { ... });``                    |
++--------------------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------+
+| **buddyStatusChanged**         | When a chat buddy's chat status has changed.                                                      | ``converse.on('buddyStatusChanged', function (buddy, status) { ... });``                |
++--------------------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------+
+| **buddyStatusMessageChanged**  | When a chat buddy's custom status message has changed.                                            | ``converse.on('buddyStatusMessageChanged', function (buddy, messageText) { ... });``    |
++--------------------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------+
+
+
+
 Minification
 ============
 
@@ -643,6 +883,7 @@ Congratulations, you've now succesfully added your translations. Sorry for all
 those hoops you had to jump through.
 
 
+
 ===============
 Troubleshooting
 ===============
@@ -713,192 +954,6 @@ your own libraries, making sure that they are loaded in the correct order (e.g.
 jQuery plugins must load after jQuery).
 
 
-======
-Events
-======
-
-Converse.js emits events to which you can subscribe from your own Javascript.
-
-Concerning events, the following methods are available:
-
-Event API Methods
-=================
-
-* **on(eventName, callback)**:
-
-    Calling the ``on`` method allows you to subscribe to an event.
-    Every time the event fires, the callback method specified by ``callback`` will be
-    called.
-
-    Parameters:
-
-    * ``eventName`` is the event name as a string.
-    * ``callback`` is the callback method to be called when the event is emitted.
-
-    For example::
-
-        converse.on('message', function (messageXML) { ... });
-
-* **once(eventName, callback)**:
-
-    Calling the ``once`` method allows you to listen to an event
-    exactly once.
-
-    Parameters:
-
-    * ``eventName`` is the event name as a string.
-    * ``callback`` is the callback method to be called when the event is emitted.
-
-    For example::
-
-        converse.once('message', function (messageXML) { ... });
-
-* **off(eventName, callback)**
-
-    To stop listening to an event, you can use the ``off`` method.
-
-    Parameters:
-
-    * ``eventName`` is the event name as a string.
-    * ``callback`` refers to the function that is to be no longer executed.
-
-
-Event Types
-===========
-
-Here are the different events that are emitted:
-
-+----------------------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------+
-| Event Type                       | When is it triggered?                                                                             | Example                                                                                 |
-+==================================+===================================================================================================+=========================================================================================+
-| **initialized**                  | Once converse.js has been initialized.                                                            | ``converse.on('initialized', function () { ... });``                                    |
-+----------------------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------+
-| **ready**                        | After connection has been established and converse.js has got all its ducks in a row.             | ``converse.on('ready', function () { ... });``                                          |
-+----------------------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------+
-| **reconnect**                    | After the connection has dropped. Converse.js will attempt to reconnect when not in prebind mode. | ``converse.on('reconnect', function () { ... });``                                      |
-+----------------------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------+
-| **message**                      | When a message is received.                                                                       | ``converse.on('message', function (messageXML) { ... });``                              |
-+----------------------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------+
-| **messageSend**                  | When a message will be sent out.                                                                  | ``converse.on('messageSend', function (messageText) { ... });``                         |
-+----------------------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------+
-| **noResumeableSession**          | When keepalive=true but there aren't any stored prebind tokens.                                   | ``converse.on('noResumeableSession', function () { ... });``                            |
-+----------------------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------+
-| **roster**                       | When the roster is updated.                                                                       | ``converse.on('roster', function (items) { ... });``                                    |
-+----------------------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------+
-| **callButtonClicked**            | When a call button (i.e. with class .toggle-call) on a chat box has been clicked.                 | ``converse.on('callButtonClicked', function (connection, model) { ... });``             |
-+----------------------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------+
-| **chatBoxOpened**                | When a chat box has been opened.                                                                  | ``converse.on('chatBoxOpened', function (chatbox) { ... });``                           |
-+----------------------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------+
-| **chatRoomOpened**               | When a chat room has been opened.                                                                 | ``converse.on('chatRoomOpened', function (chatbox) { ... });``                          |
-+----------------------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------+
-| **chatBoxClosed**                | When a chat box has been closed.                                                                  | ``converse.on('chatBoxClosed', function (chatbox) { ... });``                           |
-+----------------------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------+
-| **chatBoxFocused**               | When the focus has been moved to a chat box.                                                      | ``converse.on('chatBoxFocused', function (chatbox) { ... });``                          |
-+----------------------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------+
-| **chatBoxToggled**               | When a chat box has been minimized or maximized.                                                  | ``converse.on('chatBoxToggled', function (chatbox) { ... });``                          |
-+----------------------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------+
-| **roomInviteSent**               | After the user has sent out a direct invitation, to a roster contact, asking them to join a room. | ``converse.on('roomInvite', function (roomview, invitee_jid, reason) { ... });``        |
-+----------------------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------+
-| **roomInviteReceived**           | After the user has sent out a direct invitation, to a roster contact, asking them to join a room. | ``converse.on('roomInvite', function (roomview, invitee_jid, reason) { ... });``        |
-+----------------------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------+
-| **statusChanged**                | When own chat status has changed.                                                                 | ``converse.on('statusChanged', function (status) { ... });``                            |
-+----------------------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------+
-| **statusMessageChanged**         | When own custom status message has changed.                                                       | ``converse.on('statusMessageChanged', function (message) { ... });``                    |
-+----------------------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------+
-| **buddyStatusChanged**           | When a chat buddy's chat status has changed.                                                      | ``converse.on('buddyStatusChanged', function (buddy, status) { ... });``                |
-+----------------------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------+
-| **buddyStatusMessageChanged**    | When a chat buddy's custom status message has changed.                                            | ``converse.on('buddyStatusMessageChanged', function (buddy, messageText) { ... });``    |
-+----------------------------------+---------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------+
-
-=============
-Developer API
-=============
-
-.. note:: see also the `event api methods`_, not listed here.
-
-initialize
-==========
-
-Initializes converse.js. This method must always be called when using
-converse.js.
-
-The `initialize` method takes a map (also called a hash or dictionary) of
-`configuration variables`_.
-
-Example::
-
-    converse.initialize({
-            allow_otr: true,
-            auto_list_rooms: false,
-            auto_subscribe: false,
-            bosh_service_url: 'https://bind.example.com',
-            hide_muc_server: false,
-            i18n: locales['en'],
-            keepalive: true,
-            play_sounds: true,
-            prebind: false,
-            show_controlbox_by_default: true,
-            debug: false,
-            roster_groups: true
-        });
-
-
-getBuddy
-========
-
-Returns a map of attributes for a given buddy (i.e. roster contact), specified
-by JID (Jabber ID).
-
-Example::
-
-    converse.getBuddy('buddy@example.com')
-
-The map of attributes:
-
-+----------------+--------------------------------------------------------------------------------------------------------------------------------------+
-| Attribute      |                                                                                                                                      |
-+================+======================================================================================================================================+
-| ask            | If ask === 'subscribe', then we have asked this person to be our chat buddy.                                                         |
-+----------------+--------------------------------------------------------------------------------------------------------------------------------------+
-| fullname       | The person's full name.                                                                                                              |
-+----------------+--------------------------------------------------------------------------------------------------------------------------------------+
-| jid            | The person's Jabber/XMPP username.                                                                                                   |
-+----------------+--------------------------------------------------------------------------------------------------------------------------------------+
-| requesting     | If true, then this person is asking to be our chat buddy.                                                                            |
-+----------------+--------------------------------------------------------------------------------------------------------------------------------------+
-| subscription   | The subscription state between the current user and this chat buddy. Can be `none`, `to`, `from` or `both`.                          |
-+----------------+--------------------------------------------------------------------------------------------------------------------------------------+
-| id             | A unique id, same as the jid.                                                                                                        |
-+----------------+--------------------------------------------------------------------------------------------------------------------------------------+
-| chat_status    | The person's chat status. Can be `online`, `offline`, `busy`, `xa` (extended away) or `away`.                                        |
-+----------------+--------------------------------------------------------------------------------------------------------------------------------------+
-| user_id        | The user id part of the JID (the part before the `@`).                                                                               |
-+----------------+--------------------------------------------------------------------------------------------------------------------------------------+
-| resources      | The known resources for this chat buddy. Each resource denotes a separate and connected chat client.                                 |
-+----------------+--------------------------------------------------------------------------------------------------------------------------------------+
-| groups         | The roster groups in which this chat buddy was placed.                                                                               |
-+----------------+--------------------------------------------------------------------------------------------------------------------------------------+
-| status         | Their human readable custom status message.                                                                                          |
-+----------------+--------------------------------------------------------------------------------------------------------------------------------------+
-| image_type     | The image's file type.                                                                                                               |
-+----------------+--------------------------------------------------------------------------------------------------------------------------------------+
-| image          | The Base64 encoded image data.                                                                                                       |
-+----------------+--------------------------------------------------------------------------------------------------------------------------------------+
-| url            | The buddy's website URL, as specified in their VCard data.                                                                           |
-+----------------+--------------------------------------------------------------------------------------------------------------------------------------+
-| vcard_updated  | When last the buddy's VCard was updated.                                                                                             |
-+----------------+--------------------------------------------------------------------------------------------------------------------------------------+
-
-getRID
-======
-
-Returns the current RID (request ID) value.
-
-getSID
-======
-
-Returns the current SID (Session ID) value.
-
 =============
 Configuration
 =============

+ 2 - 2
main.js

@@ -15,10 +15,10 @@ config = {
         "jquery.easing":            "components/jquery-easing-original/index",          // XXX: Only required for https://conversejs.org website
         "moment":                   "components/momentjs/moment",
         "strophe":                  "components/strophe/strophe",
-        "strophe.disco":            "components/strophe.disco/index",
+        "strophe.disco":            "components/strophejs-plugins/disco/strophe.disco",
         "strophe.muc":              "components/strophe.muc/index",
         "strophe.roster":           "components/strophe.roster/index",
-        "strophe.vcard":            "components/strophe.vcard/index",
+        "strophe.vcard":            "components/strophejs-plugins/vcard/strophe.vcard",
         "text":                     'components/requirejs-text/text',
         "tpl":                      'components/requirejs-tpl-jcbrand/tpl',
         "typeahead":                "components/typeahead.js/index",

+ 37 - 4
spec/converse.js

@@ -10,8 +10,10 @@
     return describe("Converse", $.proxy(function(mock, test_utils) {
 
         beforeEach($.proxy(function () {
-            window.localStorage.clear();
-            window.sessionStorage.clear();
+            test_utils.closeAllChatBoxes();
+            test_utils.clearBrowserStorage();
+            converse.rosterview.model.reset();
+            test_utils.createContacts('current');
         }, converse));
 
         it("has an API method for retrieving the next RID", $.proxy(function () {
@@ -46,12 +48,43 @@
 
         it("has an API method for retrieving a buddy's attributes", $.proxy(function () {
             var jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
-            expect(converse_api.getBuddy(jid)).toBeFalsy();
-            test_utils.createContacts('current');
+            expect(converse_api.getBuddy('non-existing@jabber.org')).toBeFalsy();
             var attrs = converse_api.getBuddy(jid);
             expect(typeof attrs).toBe('object');
             expect(attrs.fullname).toBe(mock.cur_names[0]);
             expect(attrs.jid).toBe(jid);
         }, converse));
+
+        it("has an API method, openChatBox, for opening a chat box for a buddy", $.proxy(function () {
+            expect(converse_api.openChatBox('non-existing@jabber.org')).toBeFalsy(); // test on user that doesn't exist.
+            var jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
+            var box = converse_api.openChatBox(jid);
+            expect(box instanceof Object).toBeTruthy();
+            expect(box.get('box_id')).toBe(b64_sha1(jid));
+            var chatboxview = this.chatboxviews.get(jid);
+            expect(chatboxview.$el.is(':visible')).toBeTruthy();
+        }, converse));
+
+        it("will focus an already open chat box, if the openChatBox API method is called for it.", $.proxy(function () {
+            // Calling openChatBox on an already open chat will focus it.
+            var jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
+            var chatboxview = this.chatboxviews.get(jid);
+            spyOn(chatboxview, 'focus');
+            test_utils.openChatBoxFor(jid);
+            box = converse_api.openChatBox(jid);
+            expect(chatboxview.focus).toHaveBeenCalled();
+            expect(box.get('box_id')).toBe(b64_sha1(jid));
+
+        }, converse));
+
+        it("has an API method, getChatBox, for retrieving chat box", $.proxy(function () {
+            var jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
+            expect(converse_api.getChatBox(jid)).toBeFalsy();
+            test_utils.openChatBoxFor(jid);
+            var box = converse_api.getChatBox(jid);
+            expect(box instanceof Object).toBeTruthy();
+            expect(box.get('box_id')).toBe(b64_sha1(jid));
+        }, converse));
+
     }, converse, mock, test_utils));
 }));

+ 4 - 3
src/deps-full.js

@@ -1,5 +1,6 @@
 define("converse-dependencies", [
     "jquery",
+    "utils",
     "otr",
     "moment",
     "locales",
@@ -7,16 +8,16 @@ define("converse-dependencies", [
     "backbone.overview",
     "jquery.browser",
     "typeahead",
-    "utils",
     "strophe",
     "strophe.muc",
     "strophe.roster",
     "strophe.vcard",
     "strophe.disco"
-], function($, otr, moment) {
+], function($, utils, otr, moment) {
     return {
         'jQuery': $,
+        'moment': moment,
         'otr': otr,
-        'moment': moment
+        'utils': utils
     };
 });

+ 4 - 3
src/deps-no-otr.js

@@ -1,21 +1,22 @@
 define("converse-dependencies", [
     "jquery",
+    "utils",
     "moment",
     "locales",
     "backbone.browserStorage",
     "backbone.overview",
     "jquery.browser",
     "typeahead",
-    "utils",
     "strophe",
     "strophe.muc",
     "strophe.roster",
     "strophe.vcard",
     "strophe.disco"
-], function($, moment) {
+], function($, utils, moment) {
     return {
         'jQuery': $,
         'otr': undefined,
-        'moment': moment
+        'moment': moment,
+        'utils': utils
     };
 });

+ 4 - 3
src/deps-website-no-otr.js

@@ -1,5 +1,6 @@
 define("converse-dependencies", [
     "jquery",
+    "utils",
     "moment",
     "locales",
     "bootstrapJS", // XXX: Can be removed, only for https://conversejs.org
@@ -8,16 +9,16 @@ define("converse-dependencies", [
     "jquery.browser",
     "jquery.easing", // XXX: Can be removed, only for https://conversejs.org
     "typeahead",
-    "utils",
     "strophe",
     "strophe.muc",
     "strophe.roster",
     "strophe.vcard",
     "strophe.disco"
-], function($, moment) {
+], function($, utils, moment) {
     return {
         'jQuery': $,
         'otr': undefined,
-        'moment': moment
+        'moment': moment,
+        'utils': utils
     };
 });

+ 4 - 3
src/deps-website.js

@@ -1,5 +1,6 @@
 define("converse-dependencies", [
     "jquery",
+    "utils",
     "otr",
     "moment",
     "locales",
@@ -9,16 +10,16 @@ define("converse-dependencies", [
     "jquery.browser",
     "jquery.easing", // XXX: Only for https://conversejs.org
     "typeahead",
-    "utils",
     "strophe",
     "strophe.muc",
     "strophe.roster",
     "strophe.vcard",
     "strophe.disco"
-], function($, otr, moment) {
+], function($, utils, otr, moment) {
     return {
         'jQuery': $,
         'otr': otr,
-        'moment': moment
+        'moment': moment,
+        'utils': utils
     };
 });

+ 47 - 0
src/utils.js

@@ -8,4 +8,51 @@ define(["jquery"], function ($) {
         }
         return false;
     };
+
+    $.fn.addHyperlinks = function () {
+        if (this.length > 0) {
+            this.each(function (i, obj) {
+                var x = $(obj).html();
+                var list = x.match(/\b(https?:\/\/|www\.|https?:\/\/www\.)[^\s<]{2,200}\b/g );
+                if (list) {
+                    for (i=0; i<list.length; i++) {
+                        var prot = list[i].indexOf('http://') === 0 || list[i].indexOf('https://') === 0 ? '' : 'http://';
+                        var escaped_url = encodeURI(decodeURI(list[i])).replace(/[!'()]/g, escape).replace(/\*/g, "%2A");
+                        x = x.replace(list[i], "<a target='_blank' href='" + prot + escaped_url + "'>"+ list[i] + "</a>" );
+                    }
+                }
+                $(obj).html(x);
+            });
+        }
+        return this;
+    };
+
+    var utils = {
+        // Translation machinery
+        // ---------------------
+        __: $.proxy(function (str) {
+            // Translation factory
+            if (this.i18n === undefined) {
+                this.i18n = locales.en;
+            }
+            var t = this.i18n.translate(str);
+            if (arguments.length>1) {
+                return t.fetch.apply(t, [].slice.call(arguments,1));
+            } else {
+                return t.fetch();
+            }
+        }, this),
+
+        ___: function (str) {
+            /* XXX: This is part of a hack to get gettext to scan strings to be
+                * translated. Strings we cannot send to the function above because
+                * they require variable interpolation and we don't yet have the
+                * variables at scan time.
+                *
+                * See actionInfoMessages
+                */
+            return str;
+        }
+    };
+    return utils;
 });