Browse Source

Merge branch 'consolidation'

JC Brand 11 years ago
parent
commit
24d58a5b0a

+ 63 - 0
activate

@@ -0,0 +1,63 @@
+# This file must be used with "source bin/activate" *from bash*
+# you cannot run it directly
+
+deactivate () {
+    # reset old environment variables
+    if [ -n "$_OLD_VIRTUAL_PATH" ] ; then
+        PATH="$_OLD_VIRTUAL_PATH"
+        export PATH
+        unset _OLD_VIRTUAL_PATH
+    fi
+
+    # This should detect bash and zsh, which have a hash command that must
+    # be called to get it to forget past commands.  Without forgetting
+    # past commands the $PATH changes we made may not be respected
+    if [ -n "$BASH" -o -n "$ZSH_VERSION" ] ; then
+        hash -r
+    fi
+
+    if [ -n "$_OLD_VIRTUAL_PS1" ] ; then
+        PS1="$_OLD_VIRTUAL_PS1"
+        export PS1
+        unset _OLD_VIRTUAL_PS1
+    fi
+
+    unset VIRTUAL_ENV
+    if [ ! "$1" = "nondestructive" ] ; then
+    # Self destruct!
+        unset -f deactivate
+    fi
+}
+
+# unset irrelavent variables
+deactivate nondestructive
+
+VIRTUAL_ENV="/home/jc/dev/converse.js"
+export VIRTUAL_ENV
+
+_OLD_VIRTUAL_PATH="$PATH"
+PATH="$VIRTUAL_ENV/node_modules/.bin:$PATH"
+export PATH
+
+if [ -z "$VIRTUAL_ENV_DISABLE_PROMPT" ] ; then
+    _OLD_VIRTUAL_PS1="$PS1"
+    if [ "x" != x ] ; then
+	PS1="$PS1"
+    else
+    if [ "`basename \"$VIRTUAL_ENV\"`" = "__" ] ; then
+        # special case for Aspen magic directories
+        # see http://www.zetadev.com/software/aspen/
+        PS1="[`basename \`dirname \"$VIRTUAL_ENV\"\``] $PS1"
+    else
+        PS1="(`basename \"$VIRTUAL_ENV\"`)$PS1"
+    fi
+    fi
+    export PS1
+fi
+
+# This should detect bash and zsh, which have a hash command that must
+# be called to get it to forget past commands.  Without forgetting
+# past commands the $PATH changes we made may not be respected
+if [ -n "$BASH" -o -n "$ZSH_VERSION" ] ; then
+    hash -r
+fi

+ 5 - 4
bower.json

@@ -17,16 +17,17 @@
     "backbone.localStorage": "1.1.7",
     "strophe": "git@github.com:strophe/strophejs-bower.git#v1.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.github.com/strophe/strophejs-plugins/02310ad1b8da2962cd05b0f4bceaecca134efed4/muc/strophe.muc.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.github.com/strophe/strophejs-plugins/02310ad1b8da2962cd05b0f4bceaecca134efed4/muc/strophe.muc.js",
     "otr": "0.2.12",
     "crypto-js-evanvosberg": "~3.1.2",
     "almond": "~0.2.9",
     "requirejs-text": "~2.0.12",
     "requirejs-tpl-jcbrand": "*",
     "momentjs": "~2.6.0",
-    "jquery.browser": "~0.0.6"
+    "jquery.browser": "~0.0.6",
+    "backbone.overview": "*"
   },
   "exportsOverride": {}
 }

+ 272 - 120
converse.js

@@ -3,7 +3,7 @@
  * http://conversejs.org
  *
  * Copyright (c) 2012, Jan-Carel Brand <jc@opkode.com>
- * Dual licensed under the MIT and GPL Licenses
+ * Licensed under the Mozilla Public License (MPL)
  */
 
 // AMD/global registrations
@@ -158,6 +158,7 @@
         this.forward_messages = false;
         this.hide_muc_server = false;
         this.i18n = locales.en;
+        this.no_trimming = false; // Set to true for phantomjs tests (where browser apparently has no width)
         this.prebind = false;
         this.show_controlbox_by_default = false;
         this.show_only_online_users = false;
@@ -195,6 +196,7 @@
             'fullname',
             'hide_muc_server',
             'i18n',
+            'no_trimming',
             'jid',
             'prebind',
             'rid',
@@ -863,8 +865,8 @@
             is_chatroom: false,  // This is not a multi-user chatroom
 
             events: {
-                'click .close-chatbox-button': 'closeChat',
-                'click .toggle-chatbox-button': 'toggleChatBox',
+                'click .close-chatbox-button': 'close',
+                'click .toggle-chatbox-button': 'minimize',
                 'keypress textarea.chat-textarea': 'keyPressed',
                 'click .toggle-smiley': 'toggleEmoticonMenu',
                 'click .toggle-smiley ul li': 'insertEmoticon',
@@ -892,13 +894,16 @@
                 this.model.on('showReceivedOTRMessage', function (text) {
                     this.showMessage({'message': text, 'sender': 'them'});
                 }, this);
+
                 this.updateVCard();
                 this.$el.insertAfter(converse.chatboxviews.get("controlbox").$el);
-                this.render().show().focus().model.messages.fetch({add: true});
-                if (this.model.get('status')) {
-                    this.showStatusMessage(this.model.get('status'));
+                this.model.messages.fetch({add: true});
+                this.render();
+                if (this.model.get('minimized')) {
+                    this.hide();
+                } else {
+                    this.show();
                 }
-                this.initDragResize();
                 if ((_.contains([UNVERIFIED, VERIFIED], this.model.get('otr_status'))) || converse.use_otr_by_default) {
                     this.model.initiateOTR();
                 }
@@ -919,7 +924,7 @@
                 setTimeout(function () {
                     converse.refreshWebkit();
                 }, 50);
-                return this;
+                return this.showStatusMessage();
             },
 
             initDragResize: function () {
@@ -937,17 +942,6 @@
                 this.scrollDown();
             },
 
-            updateUnreadMessagesCounter: function () {
-                /* If the chatbox is minimized, we show a counter with the
-                 * number of unread messages.
-                 */
-                var $count = this.$el.find('.chat-head-message-count');
-                var count = parseInt($count.data('count') || 0, 10) + 1;
-                $count.html(count).data('count', count);
-                if (!$count.is(':visible')) { $count.show('fast'); }
-                return this;
-            },
-
             clearChatRoomMessages: function (ev) {
                 ev.stopPropagation();
                 var result = confirm(__("Are you sure you want to clear the messages from this room?"));
@@ -982,9 +976,6 @@
                     'extra_classes': msg_dict.delayed && 'delayed' || ''
                 });
                 $content.append($(message).children('.chat-message-content').first().text(text).addHyperlinks().addEmoticons().parent());
-                if (this.model.get('minimized') && (!msg_time.isBefore(this.model.get('time_minimized')))) {
-                    this.updateUnreadMessagesCounter();
-                }
                 this.scrollDown();
             },
 
@@ -1276,7 +1267,7 @@
                     converse.emit('onBuddyStatusChanged', item.attributes, item.get('chat_status'));
                 }
                 if (_.has(item.changed, 'status')) {
-                    this.showStatusMessage(item.get('status'));
+                    this.showStatusMessage();
                     converse.emit('onBuddyStatusMessageChanged', item.attributes, item.get('status'));
                 }
                 if (_.has(item.changed, 'image')) {
@@ -1285,61 +1276,53 @@
                 if (_.has(item.changed, 'otr_status')) {
                     this.renderToolbar().informOTRChange();
                 }
+                if (_.has(item.changed, 'minimized')) {
+                    if (item.get('minimized')) {
+                        this.hide();
+                    } else {
+                        this.maximize();
+                    }
+                }
                 // TODO check for changed fullname as well
             },
 
             showStatusMessage: function (msg) {
-                this.$el.find('p.user-custom-message').text(msg).attr('title', msg);
+                msg = msg || this.model.get('status');
+                if (msg) {
+                    this.$el.find('p.user-custom-message').text(msg).attr('title', msg);
+                }
+                return this;
             },
 
-            closeChat: function () {
+            close: function () {
                 if (converse.connection) {
                     this.model.destroy();
                 } else {
                     this.model.trigger('hide');
                 }
+                converse.emit('onChatBoxClosed', this);
                 return this;
             },
 
-            trimChat: function () {
-                // TODO: Instead of closing the chat, we should add it to
-                // div#offscreen-chatboxes
-                this.$el.hide(); // Hide it immediately to avoid flashes on the screen
-                this.closeChat();
-            },
-
-            saveToggleState: function () {
-                var flyout = this.$el.find('.box-flyout');
-                if (flyout.hasClass('minimized')) {
-                    flyout.removeClass('minimized');
-                    this.model.save({'minimized': false});
-                } else {
-                    flyout.addClass('minimized');
-                    this.model.save({
-                        'minimized': true,
-                        'time_minimized': moment().format()
-                    });
-                }
-                return this;
+            maximize: function () {
+                /* Restores a minimized chat box
+                 */
+                this.$el.insertAfter(converse.chatboxviews.get("controlbox").$el).show();
+                this.focus();
+                converse.refreshWebkit();
+                converse.emit('onChatBoxMaximized', this);
+                this.model.trigger('maximized', this.model);
             },
 
-            toggleChatBox: function (ev) {
-                var $target = $(ev.target), $count;
-                this.saveToggleState();
-                this.$el.children('.box-flyout').attr('style', '');
-                this.$el.find('div.chat-body').slideToggle('fast');
-                if ($target.hasClass('icon-minus')) {
-                    $target.removeClass('icon-minus').addClass('icon-plus');
-                } else {
-                    $target.removeClass('icon-plus').addClass('icon-minus');
-                    $count = this.$el.find('.chat-head-message-count');
-                    $count.html(0).data('count', 0);
-                    if ($count.is(':visible')) { $count.hide('fast'); }
-                }
-                // Toggle drag resize ability
-                this.$el.find('.dragresize-tm').toggle();
-                this.setChatBoxHeight(this.height);
-                converse.emit('onChatBoxToggled', this);
+            minimize: function (ev) {
+                /* Minimizes a chat box
+                 */
+                this.model.save({
+                    'minimized': true,
+                    'time_minimized': moment().format()
+                });
+                this.$el.hide('fast', converse.refreshwebkit);
+                converse.emit('onChatBoxMinimized', this);
             },
 
             updateVCard: function () {
@@ -1445,8 +1428,8 @@
 
             hide: function () {
                 if (this.$el.is(':visible') && this.$el.css('opacity') == "1") {
-                    this.$el.fadeOut('fast', converse.refreshWebkit);
-                    converse.emit('onChatBoxClosed', this);
+                    this.$el.hide();
+                    converse.refreshWebkit();
                 }
                 return this;
             },
@@ -1460,13 +1443,16 @@
                     // Without a connection, we haven't yet initialized
                     // localstorage
                     this.model.save();
+                    this.initDragResize();
                 }
                 return this;
             },
 
             scrollDown: function () {
-                var $content = this.$el.find('.chat-content');
-                $content.scrollTop($content[0].scrollHeight);
+                var $content = this.$('.chat-content');
+                if ($content.is(':visible')) {
+                    $content.scrollTop($content[0].scrollHeight);
+                }
                 return this;
             }
         });
@@ -1767,7 +1753,7 @@
                     }
                 }
                 if (!nick) { return; }
-                chatroom = converse.chatboxviews.showChatBox({
+                chatroom = converse.chatboxviews.showChat({
                     'id': jid,
                     'jid': jid,
                     'name': Strophe.unescapeNode(Strophe.getNodeFromJid(jid)),
@@ -1786,7 +1772,7 @@
             className: 'chatbox',
             id: 'controlbox',
             events: {
-                'click a.close-chatbox-button': 'closeChat',
+                'click a.close-chatbox-button': 'close',
                 'click ul#controlbox-tabs li a': 'switchTab',
                 'mousedown .dragresize-tm': 'onDragResizeStart'
             },
@@ -1890,8 +1876,8 @@
             tagName: 'div',
             className: 'chatroom',
             events: {
-                'click .close-chatbox-button': 'closeChat',
-                'click .toggle-chatbox-button': 'toggleChatBox',
+                'click .close-chatbox-button': 'close',
+                'click .toggle-chatbox-button': 'minimize',
                 'click .configure-chatroom-button': 'configureChatRoom',
                 'click .toggle-smiley': 'toggleEmoticonMenu',
                 'click .toggle-smiley ul li': 'insertEmoticon',
@@ -1904,6 +1890,13 @@
             initialize: function () {
                 this.connect(null);
                 this.model.messages.on('add', this.onMessageAdded, this);
+                this.model.on('change:minimized', function (item) {
+                    if (item.get('minimized')) {
+                        this.hide();
+                    } else {
+                        this.maximize();
+                    }
+                }, this);
                 this.model.on('destroy', function (model, response, options) {
                     this.hide();
                     converse.connection.muc.leave(
@@ -1913,9 +1906,13 @@
                         undefined);
                 },
                 this);
-                this.$el.appendTo(converse.chatboxviews.$el);
-                this.render().show().model.messages.fetch({add: true});
-                this.initDragResize();
+                this.$el.insertAfter(converse.chatboxviews.get("controlbox").$el);
+                this.render().model.messages.fetch({add: true});
+                if (this.model.get('minimized')) {
+                    this.hide();
+                } else {
+                    this.show();
+                }
             },
 
             render: function () {
@@ -2387,7 +2384,7 @@
 
         this.ChatBoxes = Backbone.Collection.extend({
             model: converse.ChatBox,
-            comparator : 'time_opened',
+            comparator: 'time_opened',
 
             registerMessageHandler: function () {
                 converse.connection.addHandler(
@@ -2478,43 +2475,67 @@
             }
         });
 
-        this.ChatBoxViews = Backbone.View.extend({
-            el: '#conversejs',
+        this.ChatBoxViews = Backbone.Overview.extend({
 
             initialize: function () {
-                var views = {};
-                this.get = function (id) { return views[id]; };
-                this.set = function (id, view) { views[id] = view; };
-                this.getAll = function () { return views; };
+                this.trimmed_chatboxes_view = new converse.MinimizedChatBoxesView({model: this.model});
+                this.render();
+                this.model.on("add", this.onChatAdded, this);
+                this.model.on("maximized", function (item) {
+                    this.trimChats(this.get(item.get('id')));
+                }, this);
+            },
 
-                this.model.on("add", function (item) {
-                    var view = this.get(item.get('id'));
-                    if (!view) {
-                        if (item.get('chatroom')) {
-                            view = new converse.ChatRoomView({'model': item});
-                        } else if (item.get('box_id') === 'controlbox') {
-                            view = new converse.ControlBoxView({model: item});
-                            view.render();
-                        } else {
-                            view = new converse.ChatBoxView({model: item});
-                        }
-                        this.set(item.get('id'), view);
+            render: function () {
+                this.$el.html(this.trimmed_chatboxes_view.render());
+            },
+
+            _ensureElement: function() {
+                /* Override method from backbone.js
+                 * If the #conversejs element doesn't exist, create it.
+                 */
+                if (!this.el) {
+                    var $el = $('#conversejs');
+                    if (!$el.length) {
+                        $el = $('<div id="conversejs">');
+                        $('body').append($el);
+                    }
+                    this.setElement($el, false);
+                } else {
+                    this.setElement(_.result(this, 'el'), false);
+                }
+            },
+
+            onChatAdded: function (item) {
+                var view = this.get(item.get('id'));
+                if (!view) {
+                    if (item.get('chatroom')) {
+                        view = new converse.ChatRoomView({'model': item});
+                    } else if (item.get('box_id') === 'controlbox') {
+                        view = new converse.ControlBoxView({model: item}).render();
                     } else {
-                        delete view.model; // Remove ref to old model to help garbage collection
-                        view.model = item;
-                        view.initialize();
+                        view = new converse.ChatBoxView({model: item});
                     }
-                    this.trimOpenChats(view);
-                }, this);
+                    this.add(item.get('id'), view);
+                } else {
+                    delete view.model; // Remove ref to old model to help garbage collection
+                    view.model = item;
+                    view.initialize();
+                }
+                this.trimChats(view);
             },
 
-            trimOpenChats: function (view) {
+            trimChats: function (view) {
                 /* This method is called before a new chat box will be opened.
                  *
                  * Check whether there is enough space in the page to show
                  * another chat box. Otherwise, close the oldest chat box.
                  */
-                var toggle_width = 0, 
+                if (converse.no_trimming) {
+                    return;
+                }
+                var toggle_width = 0,
+                    trimmed_chats_width,
                     boxes_width = view.$el.outerWidth(true),
                     controlbox = this.get('controlbox');
                 if (!controlbox || !controlbox.$el.is(':visible')) {
@@ -2529,16 +2550,31 @@
                 if (this.model.length <= 1) {
                     return;
                 }
-                if ((boxes_width + toggle_width) > this.$el.width()) {
-                    // trim oldest view (which is not controlbox)
-                    this.get(this.model.at(1).get('id')).trimChat();
+                trimmed_chats_width = this.trimmed_chatboxes_view.$('.box-flyout').outerWidth(true) || 0;
+                if ((trimmed_chats_width + boxes_width + toggle_width) > this.$el.width()) {
+                    this.getOldestMaximizedChat().set('minimized', true);
                 }
             },
 
-            showChatBox: function (attrs) {
+            getOldestMaximizedChat: function () {
+                // Get oldest view (which is not controlbox)
+                var i = 0;
+                var model = this.model.sort().at(i);
+                while (model.get('id') === 'controlbox' || model.get('minimized') === true) {
+                    i++;
+                    model = this.model.at(i);
+                }
+                return model;
+            },
+
+            showChat: function (attrs) {
                 var chatbox  = this.model.get(attrs.jid);
                 if (chatbox) {
-                    chatbox.trigger('show');
+                    if (chatbox.get('minimized')) {
+                        chatbox.set({'minimized': false});
+                    } else {
+                        chatbox.trigger('show');
+                    }
                 } else {
                     chatbox = this.model.create(attrs, {
                         'error': function (model, response) {
@@ -2550,6 +2586,126 @@
             }
         });
 
+        this.MinimizedChatBoxView = Backbone.View.extend({
+            tagName: 'div',
+            className: 'chat-head',
+
+            events: {
+                'click .close-chatbox-button': 'close',
+                'click .restore-chat': 'restore'
+            },
+
+            initialize: function () {
+                this.model.messages.on('add', function (msg) {
+                    this.updateUnreadMessagesCounter(_.clone(msg.attributes));
+                }, this);
+                this.model.on('showSentOTRMessage', this.updateUnreadMessagesCounter, this);
+                this.model.on('showReceivedOTRMessage', this.updateUnreadMessagesCounter, this);
+                this.model.on('change:minimized', this.clearUnreadMessagesCounter, this);
+            },
+
+            render: function () {
+                var data = this.model.toJSON();
+                if (this.model.get('chatroom')) {
+                    data.title = this.model.get('name');
+                    this.$el.addClass('chat-head-chatroom');
+                } else {
+                    data.title = this.model.get('fullname');
+                    this.$el.addClass('chat-head-chatbox');
+                }
+                return this.$el.html(converse.templates.trimmed_chat(data));
+            },
+
+            clearUnreadMessagesCounter: function () {
+                if (!this.model.get('minimized')) {
+                    this.$el.find('.chat-head-message-count').html(0).data('count', 0).hide();
+                }
+            },
+
+            updateUnreadMessagesCounter: function (msg_dict) {
+                var count, $count;
+                var msg_time = (typeof msg_dict === 'object' && moment(msg_dict.time)) || moment;
+                if (this.model.get('minimized') && (!msg_time.isBefore(this.model.get('time_minimized')))) {
+                    $count = this.$el.find('.chat-head-message-count');
+                    count = parseInt($count.data('count') || 0, 10) + 1;
+                    $count.html(count).data('count', count);
+                    if (!$count.is(':visible')) { $count.show('fast'); }
+                }
+                return this;
+            },
+
+            close: function (ev) {
+                if (ev && ev.preventDefault) {
+                    ev.preventDefault();
+                }
+                ev.preventDefault();
+                this.$el.remove();
+                this.model.destroy();
+                converse.emit('onChatBoxClosed', this);
+                return this;
+            },
+
+            restore: function (ev) {
+                if (ev && ev.preventDefault) {
+                    ev.preventDefault();
+                }
+                this.$el.remove();
+                this.model.set({
+                    'time_opened': moment().format(),
+                    'minimized': false
+                });
+                return this;
+            }
+        });
+
+        this.MinimizedChatBoxesView = Backbone.Overview.extend({
+
+            initialize: function () {
+                this.model.on("add", function (item) {
+                    if (item.get('minimized')) {
+                        this.addChat(item);
+                    }
+                }, this);
+                this.model.on("change:minimized", function (item) {
+                    this.onChanged(item);
+                }, this);
+            },
+
+            render: function () {
+                return this.$el;
+            },
+
+            _ensureElement: function () {
+                /* Override method from backbone.js
+                * Make sure that the el and $el attributes point to a DOM snippet
+                * from src/templates/trimmed_chats.html
+                */
+                if (!this.el) {
+                    var $el = $(converse.templates.trimmed_chats());
+                    this.setElement($el, false);
+                } else {
+                    this.setElement(_.result(this, 'el'), false);
+                }
+            },
+
+            onChanged: function (item) {
+                var view;
+                if (item.get('minimized')) {
+                    this.addChat(item);
+                } else {
+                    view = this.get(item.get('id'));
+                    view.restore();
+                }
+            },
+
+            addChat: function (item) {
+                var view = new converse.MinimizedChatBoxView({model: item});
+                this.$('.box-flyout').append(view.render());
+                this.add(item.get('id'), view);
+            }
+
+        });
+
         this.RosterItem = Backbone.Model.extend({
             initialize: function (attributes, options) {
                 var jid = attributes.jid;
@@ -2580,7 +2736,7 @@
 
             openChat: function (ev) {
                 ev.preventDefault();
-                return converse.chatboxviews.showChatBox({
+                return converse.chatboxviews.showChat({
                     'id': this.model.get('jid'),
                     'jid': this.model.get('jid'),
                     'fullname': this.model.get('fullname'),
@@ -2963,17 +3119,11 @@
             }
         });
 
-        this.RosterView = Backbone.View.extend({
+        this.RosterView = Backbone.Overview.extend({
             tagName: 'dl',
             id: 'converse-roster',
 
             initialize: function () {
-                var views = {};
-                this.get = function (id) {
-                    return views[id];
-                };
-                this.set = function (id, view) { views[id] = view; };
-
                 this.model.on("add", function (item) {
                     this.addRosterItemView(item).render(item);
                     if (!item.get('vcard_updated')) {
@@ -3007,7 +3157,6 @@
                         });
                 }
                 this.$el.hide().html(roster_markup);
-
                 this.model.fetch({add: true}); // Get the cached roster items from localstorage
             },
 
@@ -3029,7 +3178,7 @@
 
             addRosterItemView: function (item) {
                 var view = new converse.RosterItemView({model: item});
-                this.set(item.id, view);
+                this.add(item.id, view);
                 return this;
             },
 
@@ -3460,7 +3609,7 @@
                 if (converse.show_controlbox_by_default) {
                     toggle.hide(); // It's either or
                 }
-                $('#conversejs').append(toggle);
+                $('#conversejs').prepend(toggle);
                 return this;
             },
 
@@ -3503,14 +3652,17 @@
             }
         });
 
+        this._initialize = function () {
+            this.chatboxes = new this.ChatBoxes();
+            this.chatboxviews = new this.ChatBoxViews({model: this.chatboxes});
+            this.controlboxtoggle = new this.ControlBoxToggle();
+            this.otr = new this.OTR();
+        };
+
         // Initialization
         // --------------
         // This is the end of the initialize method.
-        this.chatboxes = new this.ChatBoxes();
-        this.chatboxviews = new this.ChatBoxViews({model: this.chatboxes});
-        this.controlboxtoggle = new this.ControlBoxToggle();
-        this.otr = new this.OTR();
-
+        this._initialize();
         if ((this.prebind) && (!this.connection)) {
             if ((!this.jid) || (!this.sid) || (!this.rid) || (!this.bosh_service_url)) {
                 this.log('If you set prebind=true, you MUST supply JID, RID and SID values');

File diff suppressed because it is too large
+ 0 - 11
converse.min.css


+ 15 - 18
css/converse.css

@@ -568,13 +568,7 @@ span.spinner.hor_centered {
   margin-right: 5px;
   color: white;
 }
-#conversejs #offscreen-chatboxes {
-  float: left;
-  height: 25px;
-  margin-left: 5px;
-  display: block;
-}
-#conversejs #offscreen-chatboxes .box-flyout {
+#conversejs #trimmed-chatboxes .box-flyout {
   position: absolute;
   display: block;
   bottom: 1px;
@@ -582,15 +576,16 @@ span.spinner.hor_centered {
   border-radius: 4px;
   height: auto;
 }
-#conversejs #offscreen-chatboxes .box-flyout .chat-head {
+#conversejs #trimmed-chatboxes .box-flyout .chat-head {
   font-size: 100%;
   border-radius: 4px;
   padding: 3px 0 0 5px;
   margin: 0 0 2px 2px;
   box-shadow: 1px 3px 5px 3px rgba(0, 0, 0, 0.4);
   height: 24px;
+  width: 130px;
 }
-#conversejs #offscreen-chatboxes .chat-head-chatroom {
+#conversejs #trimmed-chatboxes .chat-head-chatroom {
   width: 100px;
 }
 #conversejs #toggle-controlbox {
@@ -745,10 +740,11 @@ input.error {
   text-overflow: ellipsis;
   white-space: nowrap;
   text-shadow: rgba(0, 0, 0, 0.51) 0 -1px 0;
-  height: 1em;
 }
 #conversejs .chat-title a {
   color: white;
+  width: 100%;
+  display: block;
 }
 #conversejs .chat-head-chatbox,
 #conversejs .chat-head-chatroom {
@@ -836,23 +832,20 @@ dl.add-converse-contact {
 #conversejs .chat-head-message-count {
   font-weight: bold;
   position: absolute;
-  left: -6px;
-  top: -6px;
+  left: -5px;
+  top: 2px;
   background: -webkit-gradient(linear, left top, left bottom, color-stop(0.35, #f6f6f6), color-stop(1, #808080));
   background: -moz-linear-gradient(center top, #ffff00 5%, #f6f6f6 100%);
   filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='yellow', endColorstr='#f6f6f6');
-  border: 3px solid #4f6a72;
+  border: 1px solid;
   text-shadow: 1px 1px 0 #ccc;
   color: darkred;
   border-radius: 20%;
-  padding: 2px 10px;
-  font-size: 18px;
+  padding: 2px 4px;
+  font-size: 15px;
   text-align: center;
   display: none;
 }
-#conversejs .chat-head-chatroom .chat-head-message-count {
-  border: 3px solid #2D617A;
-}
 #conversejs a.configure-chatroom-button,
 #conversejs a.toggle-chatbox-button,
 #conversejs a.close-chatbox-button {
@@ -1040,6 +1033,7 @@ dl.add-converse-contact {
 #conversejs #converse-roster dd.pending-xmpp-contact:hover span {
   width: 70%;
 }
+#conversejs #trimmed-chatboxes,
 #conversejs .chatbox,
 #conversejs .chatroom {
   height: 25px;
@@ -1047,6 +1041,9 @@ dl.add-converse-contact {
   margin-right: 15px;
   display: block;
 }
+#conversejs #trimmed-chatboxes {
+  width: 130px;
+}
 #conversejs .chatbox {
   width: 200px;
 }

+ 1 - 0
docs/CHANGES.rst

@@ -9,6 +9,7 @@ Changelog
     2. Configuration options for the chat toolbar have changed.
        Please refer to the `relevant documentation http://devbox:8890/docs/html/index.html#visible-toolbar-buttons`_.
 
+* No initial HTML markup is now needed in the document body for converse.js to work. [jcbrand]
 * All date handling is now done with moment.js. [jcbrand]
 * Add a new toolbar button for clearing chat messages. [jcbrand]
 * Chat boxes and rooms can now be resized vertically. [jcbrand]

+ 0 - 7
docs/source/index.rst

@@ -52,13 +52,6 @@ bottom of your page (after the closing *</body>* element).
         });
     });
 
-
-Finally, Converse.js requires a special snippet of HTML markup to be included in your page:
-
-::
-
-    <div id="conversejs"></div>
-
 The `index.html <https://github.com/jcbrand/converse.js/blob/master/index.html>`_ file inside the
 Converse.js repository serves as a nice usable example of this.
 

+ 5 - 9
index.html

@@ -13,6 +13,11 @@
     <link type="text/css" rel="stylesheet" media="screen" href="css/theme.css" />
     <link type="text/css" rel="stylesheet" media="screen" href="css/converse.css" />
     <script data-main="main" src="components/requirejs/require.js"></script>
+
+    <script src="../components/jquery/dist/jquery.min.js"></script>
+    <script src="http://netdna.bootstrapcdn.com/bootstrap/3.0.3/js/bootstrap.min.js"></script>
+    <script src="http://cdnjs.cloudflare.com/ajax/libs/jquery-easing/1.3/jquery.easing.min.js"></script>
+    <script src="js/init.js"></script>
 </head>
 
 <body id="page-top" data-spy="scroll" data-target=".navbar-custom">
@@ -209,15 +214,6 @@
             </div>
         </div>
     </section>
-
-    <!-- Core JavaScript Files -->
-    <script src="../components/jquery/dist/jquery.min.js"></script>
-    <script src="http://netdna.bootstrapcdn.com/bootstrap/3.0.3/js/bootstrap.min.js"></script>
-    <script src="http://cdnjs.cloudflare.com/ajax/libs/jquery-easing/1.3/jquery.easing.min.js"></script>
-    <!-- Custom Theme JavaScript -->
-    <script src="js/init.js"></script>
-
-    <div id="conversejs"></div>
 </body>
 
 <script type="text/javascript">

+ 16 - 20
less/converse.less

@@ -598,14 +598,7 @@ span.spinner.hor_centered {
     color: white;
 }
 
-#conversejs #offscreen-chatboxes {
-    float: left;
-    height: 25px;
-    margin-left: 5px;
-    display: block;
-}
-
-#conversejs #offscreen-chatboxes .box-flyout {
+#conversejs #trimmed-chatboxes .box-flyout {
     position: absolute;
     display: block;
     bottom: 1px;
@@ -614,16 +607,17 @@ span.spinner.hor_centered {
     height: auto;
 }
 
-#conversejs #offscreen-chatboxes .box-flyout .chat-head {
+#conversejs #trimmed-chatboxes .box-flyout .chat-head {
     font-size: 100%;
     border-radius: 4px;
     padding: 3px 0 0 5px;
     margin: 0 0 2px 2px;
     box-shadow: 1px 3px 5px 3px rgba(0,0,0,0.4);
     height: 24px;
+    width: 130px;
 }
 
-#conversejs #offscreen-chatboxes .chat-head-chatroom {
+#conversejs #trimmed-chatboxes .chat-head-chatroom {
     width: 100px;
 }
 
@@ -808,11 +802,12 @@ input.error {
     text-overflow: ellipsis;
     white-space: nowrap;
     text-shadow: rgba(0,0,0,0.51) 0 -1px 0;
-    height: 1em;
 }
 
 #conversejs .chat-title a {
     color: white;
+    width: 100%;
+    display: block;
 }
 
 #conversejs .chat-head-chatbox,
@@ -916,25 +911,21 @@ dl.add-converse-contact {
 #conversejs .chat-head-message-count {
     font-weight: bold;
     position: absolute;
-    left: -6px;
-    top: -6px;
+    left: -5px;
+    top: 2px;
     background: -webkit-gradient(linear, left top, left bottom, color-stop(0.35, #f6f6f6), color-stop(1, grey) );
     background: -moz-linear-gradient(center top, yellow 5%, #f6f6f6 100%);
     filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='yellow', endColorstr='#f6f6f6');
-    border: 3px solid rgb(79, 106, 114);
+    border: 1px solid;
     text-shadow: 1px 1px 0 #ccc;
     color: darkred;
     border-radius: 20%;
-    padding: 2px 10px;
-    font-size: 18px;
+    padding: 2px 4px;
+    font-size: 15px;
     text-align: center;
     display: none;
 }
 
-#conversejs .chat-head-chatroom .chat-head-message-count {
-    border: 3px solid #2D617A;
-}
-
 #conversejs a.configure-chatroom-button,
 #conversejs a.toggle-chatbox-button,
 #conversejs a.close-chatbox-button {
@@ -1152,6 +1143,7 @@ dl.add-converse-contact {
     width: 70%;
 }
 
+#conversejs #trimmed-chatboxes,
 #conversejs .chatbox,
 #conversejs .chatroom {
     height: 25px;
@@ -1160,6 +1152,10 @@ dl.add-converse-contact {
     display: block;
 }
 
+#conversejs #trimmed-chatboxes {
+    width: 130px;
+}
+
 #conversejs .chatbox {
     width: 200px;
 }

+ 1 - 0
main.js

@@ -8,6 +8,7 @@ config = {
         "underscore": "components/underscore/underscore",
         "backbone": "components/backbone/backbone",
         "backbone.localStorage": "components/backbone.localStorage/backbone.localStorage",
+        "backbone.overview": "components/backbone.overview/backbone.overview",
         "text": 'components/requirejs-text/text',
         "tpl": 'components/requirejs-tpl-jcbrand/tpl',
         "converse-templates": "src/templates",

+ 51 - 12
mockup/index.html

@@ -373,27 +373,72 @@
         </div>
     </div>
 
-    <div id="offscreen-chatboxes">
+    <div id="trimmed-chatboxes">
         <div class="box-flyout">
             <div class="chat-head chat-head-chatroom">
                 <a class="close-chatbox-button icon-close"></a>
-                <div class="chat-title"> Restricted Chatroom</div>
+                <div class="chat-title">
+                    <a href="#" class="restore-chat" title="Click to maximize this chat">
+                        <div class="chat-head-message-count" style="display:block">3</div>
+                        Restricted Chatroom
+                    </a>
+                </div>
             </div>
             <div class="chat-head chat-head-chatbox">
                 <a class="close-chatbox-button icon-close"></a>
                 <div class="chat-title">
-                    <a href="http://opkode.com" target="_blank" class="user">
+                    <a href="#" class="restore-chat" title="Click to maximize this chat">
+                        <div class="chat-head-message-count" style="display:block">42</div>
                         JC Brand
                     </a>
                 </div>
             </div>
             <div class="chat-head chat-head-chatroom">
                 <a class="close-chatbox-button icon-close"></a>
-                <div class="chat-title"> My Chatroom</div>
+                <div class="chat-title">
+                    <a href="#" class="restore-chat" title="Click to maximize this chat">
+                        My Chatroom
+                    </a>
+                </div>
+            </div>
+            <div class="chat-head chat-head-chatbox"><a class="close-chatbox-button icon-close"></a>
+                <div class="chat-title">
+                    <a href="#" class="restore-chat" title="Click to maximize this chat">
+                        Annegreet Gomez
+                    </a>
+                </div>
+            </div>
+            <div class="chat-head chat-head-chatbox"><a class="close-chatbox-button icon-close"></a>
+                <div class="chat-title">
+                    <a href="#" class="restore-chat" title="Click to maximize this chat">
+                        <div class="chat-head-message-count" style="display:block">842</div>
+                        Asmaa Haakman
+                    </a>
+                </div>
+            </div>
+            <div class="chat-head chat-head-chatbox"><a class="close-chatbox-button icon-close"></a>
+                <div class="chat-title">
+                    <a href="#" class="restore-chat" title="Click to maximize this chat">
+                        Candice van der Knijff
+                    </a>
+                </div>
+            </div>
+            <div class="chat-head chat-head-chatbox"><a class="close-chatbox-button icon-close"></a>
+                <div class="chat-title">
+                    <a href="#" class="restore-chat" title="Click to maximize this chat">
+                        Laura Grunewald
+                    </a>
+                </div>
+            </div>
+            <div class="chat-head chat-head-chatbox"><a class="close-chatbox-button icon-close"></a>
+                <div class="chat-title">
+                    <a href="#" class="restore-chat">
+                        Lena Grunewald
+                    </a>
+                </div>
             </div>
         </div>
     </div>
-
 </div>
 
 <script>
@@ -458,13 +503,7 @@ $(document).ready(function () {
 
         $('.toggle-chatbox-button').click(function(ev) {
             var $grandparent = $(ev.target).parent().parent().parent();
-            $grandparent.find('.chat-body').slideToggle('fast');
-            var flyout = $grandparent.find('.box-flyout');
-            if (flyout.hasClass('minimized')) {
-                flyout.removeClass('minimized');
-            } else {
-                flyout.addClass('minimized');
-            }
+            $grandparent.fadeOut('fast');
         });
 
         // Clickable Dropdown

+ 0 - 274
mockup/minimized.html

@@ -1,274 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-    <title id="pageTitle">Converse.js: Mockup of minimized chats</title>
-    <meta charset="utf-8">
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
-    <meta name="description" content="Converse.js: Chat Client for Websites" />
-    <link type="text/css" href="../css/theme.css" rel="stylesheet" media="screen" />
-    <link type="text/css" href="../css/converse.css" rel="stylesheet" media="screen" />
-    <script src="../components/jquery/dist/jquery.min.js"></script>
-</head>
-<body>
-<!-- HEADER -->
-<div id="header_wrap" class="outer">
-    <header class="inner">
-        <h1 id="project_title"><a href="http://conversejs.org">Converse.js</a></h1>
-        <h2 id="project_tagline">Static Mockup</h2>
-    </header>
-</div>
-
-<div id="conversejs">
-
-    <div class="chatbox" id="37c0c87392010303765fe36b05c0967d62c6b70f">
-        <div class="box-flyout minimized">
-            <div class="dragresize dragresize-tm"></div>
-            <div class="chat-head chat-head-chatbox">
-                <div class="chat-head-message-count" style="display:block">3</div>
-                <a class="close-chatbox-button icon-close"></a>
-                <a class="toggle-chatbox-button icon-minus"></a>
-                <canvas height="31px" width="31px" class="avatar" style="background-color: black"></canvas>
-                <div class="chat-title">
-                    <a href="http://opkode.com" target="_blank" class="user">
-                        JC Brand
-                    </a>
-                </div>
-                <p class="user-custom-message" title="10000ft in the air">10000ft in the air</p>
-
-            </div>
-            <div class="chat-body" style="display: none">
-                <div class="chat-content">
-                    <div class="chat-info"><strong>/help</strong>:This is an info message</div>
-                    <div class="chat-error">This is an error message</div>
-                    <div class="chat-message">
-                        <span class="chat-message-me">09:35 me:&nbsp;</span>
-                        <span class="chat-message-content">
-                            Hello world
-                            <span class="icon-smiley"></span>
-                        </span>
-                    </div>
-                    <div class="chat-message ">
-                        <span class="chat-message-them">19:25 Benedict-John:&nbsp;</span>
-                        <span class="chat-message-content">Dagsê</span>
-                    </div>
-                    <div class="chat-message">
-                        <span class="chat-message-me">19:39 me:&nbsp;</span>
-                        <span class="chat-message-content">This is a relatively long message to check that wrapping works as expected.</span>
-                    </div>
-                    <div class="chat-message">
-                        <span class="chat-message-me">19:42 me:&nbsp;</span>
-                        <span class="chat-message-content">Supercalifragilisticexpialidociousstillnotlongenough</span>
-                    </div>
-                    <div class="chat-event">JC Brand is busy</div>
-                    <div class="chat-message ">
-                        <span class="chat-message-me">19:43 me:&nbsp;</span>
-                        <span class="chat-message-content">Another message to check that scrolling works.</span>
-                    </div>
-                </div>
-                <form class="sendXMPPMessage" action="" method="post">
-                    <ul class="chat-toolbar no-text-select">
-                        <li class="toggle-smiley icon-happy" title="Insert a smilery">
-                            <ul>
-                                <li><a class="icon-smiley" href="#" data-emoticon=":)"></a></li>
-                                <li><a class="icon-wink" href="#" data-emoticon=";)"></a></li>
-                                <li><a class="icon-grin" href="#" data-emoticon=":D"></a></li>
-                                <li><a class="icon-tongue" href="#" data-emoticon=":P"></a></li>
-                                <li><a class="icon-cool" href="#" data-emoticon="8)"></a></li>
-                                <li><a class="icon-evil" href="#" data-emoticon=">:)"></a></li>
-                                <li><a class="icon-confused" href="#" data-emoticon=":S"></a></li>
-                                <li><a class="icon-wondering" href="#" data-emoticon=":\"></a></li>
-                                <li><a class="icon-angry" href="#" data-emoticon=">:("></a></li>
-                                <li><a class="icon-sad" href="#" data-emoticon=":("></a></li>
-                                <li><a class="icon-shocked" href="#" data-emoticon=":O"></a></li>
-                                <li><a class="icon-thumbs-up" href="#" data-emoticon="(^.^)b"></a></li>
-                                <li><a class="icon-heart" href="#" data-emoticon="<3"></a></li>
-                            </ul>
-                        </li>
-                        <li class="toggle-otr unencrypted" title="Turn on 'off-the-record' chat encryption">
-                            <span class="chat-toolbar-text">unencrypted</span>
-                            <span class="icon-unlocked"></span>
-                            <ul>
-                                <li><a href="#">Start private conversation</a></li>
-                                <li><a href="#">End private conversation</a></li>
-                                <li><a href="#">Authenticate buddy</a></li>
-                                <li><a href="http://www.cypherpunks.ca/otr/help/3.2.0/levels.php" target="_blank">What's this?</a></li>
-                            </ul>
-                        </li>
-                    </ul>
-                    <textarea type="text" class="chat-textarea" placeholder="Personal message"></textarea>
-                </form>
-            </div>
-        </div>
-    </div>
-
-    <div class="chatroom" id="4a77380f1cd9d392627b0e1469688f9ca44e9392">
-        <div class="box-flyout minimized">
-            <div class="dragresize dragresize-tm"></div>
-            <div class="chat-head chat-head-chatroom">
-                <div class="chat-head-message-count" style="display:block">42</div>
-                <a class="close-chatbox-button icon-close"></a>
-                <a class="toggle-chatbox-button icon-minus"></a>
-                <a class="configure-chatroom-button icon-wrench" style=""></a>
-                <div class="chat-title"> Chatroom </div>
-                <p class="chatroom-topic">May the force be with you</p>
-            </div>
-            <div class="chat-body" style="display: none">
-                <div class="chat-area">
-                    <div class="chat-content">
-                        <time class="chat-date" datetime="2013-06-04T00:00:00.000Z">Tue Jun 04 2013</time>
-                        <div class="chat-message ">
-                            <span class="chat-message-room">18:50 luke:&nbsp;</span>
-                            <span class="chat-message-content">leia: hi :)</span>
-                        </div>
-                        <div class="chat-message ">
-                            <span class="chat-message-room">19:40 leia:&nbsp;</span>
-                            <span class="chat-message-content">
-                                I'll be gone for a while, will be back in about an hour</span>
-                        </div>
-                        <div class="chat-message ">
-                            <span class="chat-message-room">19:40 Obi-wan Kenobi, Jedi Master:&nbsp;</span>
-                            <span class="chat-message-content">
-                                I'll be gone for a while, will be back in about an hour</span>
-                        </div>
-                        <div class="chat-message">
-                            <span class="chat-message-me">19:42 me:&nbsp;</span>
-                            <span class="chat-message-content">Supercalifragilisticexpialidociousstillnotlongenough</span>
-                        </div>
-                        <div class="chat-message ">
-                            <span class="chat-message-room">19:43 Obi-wan Kenobi, Jedi Master:&nbsp;</span>
-                            <span class="chat-message-content">Another message to check that scrolling works.</span>
-                        </div>
-                    </div>
-
-                    <form class="sendXMPPMessage" action="" method="post">
-                        <ul class="chat-toolbar no-text-select">
-                            <li class="toggle-smiley icon-happy" title="Insert a smilery">
-                                <ul>
-                                    <li><a class="icon-smiley" href="#" data-emoticon=":)"></a></li>
-                                    <li><a class="icon-wink" href="#" data-emoticon=";)"></a></li>
-                                    <li><a class="icon-grin" href="#" data-emoticon=":D"></a></li>
-                                    <li><a class="icon-tongue" href="#" data-emoticon=":P"></a></li>
-                                    <li><a class="icon-cool" href="#" data-emoticon="8)"></a></li>
-                                    <li><a class="icon-evil" href="#" data-emoticon=">:)"></a></li>
-                                    <li><a class="icon-confused" href="#" data-emoticon=":S"></a></li>
-                                    <li><a class="icon-wondering" href="#" data-emoticon=":\"></a></li>
-                                    <li><a class="icon-angry" href="#" data-emoticon=">:("></a></li>
-                                    <li><a class="icon-sad" href="#" data-emoticon=":("></a></li>
-                                    <li><a class="icon-shocked" href="#" data-emoticon=":O"></a></li>
-                                    <li><a class="icon-thumbs-up" href="#" data-emoticon="(^.^)b"></a></li>
-                                    <li><a class="icon-heart" href="#" data-emoticon="<3"></a></li>
-                                </ul>
-                            </li>
-                        </ul>
-                        <textarea type="text" class="chat-textarea" placeholder="Message"></textarea>
-                    </form>
-                </div>
-                <div class="participants">
-                    <ul class="participant-list">
-                        <li class="participant" title="This user can send messages in this room">Obi-wan Kenobi, Jedi Master</li>
-                        <li class="participant" title="This user can send messages in this room">jabber the hut</li>
-                        <li class="participant" title="This user can send messages in this room">leia</li>
-                        <li class="moderator" title="This user is a moderator">luke</li>
-                    </ul>
-                </div>
-            </div>
-        </div>
-    </div>
-</div>
-
-<script>
-$(document).ready(function () {
-    $('a[href=#chatrooms]').click(function (ev) { 
-        switchTab(ev);
-    });
-    $('a[href=#users]').click(function (ev) {
-        switchTab(ev); 
-    });
-
-    $("a.choose-xmpp-status").click(function (ev) {
-        ev.preventDefault();
-        $(ev.target).parent().parent().siblings('dd').find('ul').toggle('fast');
-    });
-
-    $("a.change-xmpp-status-message").click(function (ev) {
-        ev.preventDefault();
-        var form = ''+
-                '<form id="set-custom-xmpp-status">' +
-                    '<input type="text" class="custom-xmpp-status"I am online"'+
-                        'placeholder="I am online"/>' +
-                    '<button type="submit">Save</button>' +
-                '</form>';
-
-        $(ev.target).closest('.xmpp-status').replaceWith(form);
-        $(ev.target).closest('.custom-xmpp-status').focus().focus();
-    });
-
-    $('.toggle-xmpp-contact-form').click(function (ev) {
-        ev.preventDefault();
-        $(ev.target).parent().parent().find('.search-xmpp').toggle('fast', function () {
-            if ($(this).is(':visible')) {
-                $(this).find('input.username').focus();
-            }
-        });
-    });
-
-    var switchTab = function (ev) {
-        ev.preventDefault();
-        var $tab = $(ev.target),
-            $sibling = $tab.parent().siblings('li').children('a'),
-            $tab_panel = $($tab.attr('href')),
-            $sibling_panel = $($sibling.attr('href'));
-
-        $sibling_panel.fadeOut('fast', function () {
-            $sibling.removeClass('current');
-            $tab.addClass('current');
-            $tab_panel.fadeIn('fast', function () {
-            });
-        });
-    }
-
-    $(function() {
-        $('.close-chatbox-button').click(function(ev) {
-            var $grandparent = $(ev.target).parent().parent().parent();
-            $grandparent.hide(300, function () {
-                // Webkit fix
-                document.getElementById('conversejs').style.display = 'none';
-                document.getElementById('conversejs').offsetHeight; // no need to store this anywhere, the reference is enough
-                document.getElementById('conversejs').style.display = 'block';
-            });
-        });
-
-        $('.toggle-chatbox-button').click(function(ev) {
-            var $grandparent = $(ev.target).parent().parent().parent();
-            $grandparent.find('.chat-body').slideToggle(300);
-            var flyout = $grandparent.find('.box-flyout');
-            if (flyout.hasClass('minimized')) {
-                flyout.removeClass('minimized');
-            } else {
-                flyout.addClass('minimized');
-            }
-        });
-
-        // Clickable Dropdown
-        $('.toggle-otr').click(function(e) {
-            $('.toggle-otr ul').slideToggle(200);
-            e.stopPropagation();
-        });
-
-        $('.toggle-smiley').click(function(e) {
-            $(e.target).find('ul').slideToggle(200);
-            e.stopPropagation();
-        });
-        $(document).click(function() {
-            if ($('.toggle-otr ul').is(':visible')) {
-                $('.toggle-otr ul', this).slideUp(200);
-            }
-            if ($('.toggle-smiley ul').is(':visible')) {
-                $('.toggle-smiley ul', this).slideUp(200);
-            }
-        });
-    });
-});
-</script>
-
-</html>

+ 100 - 42
spec/chatbox.js

@@ -30,11 +30,12 @@
             });
 
             it("is created when you click on a roster item", $.proxy(function () {
-                var i, $el, click, jid, view;
+                var i, $el, click, jid, view, chatboxview;
                 // openControlBox was called earlier, so the controlbox is
                 // visible, but no other chat boxes have been created.
                 expect(this.chatboxes.length).toEqual(1);
-                spyOn(this.chatboxviews, 'trimOpenChats');
+                spyOn(this.chatboxviews, 'trimChats');
+                expect($("#conversejs .chatbox").length).toBe(1); // Controlbox is open
 
                 var online_contacts = this.rosterview.$el.find('dt#xmpp-contacts').siblings('dd.current-xmpp-contact').find('a.open-chat');
                 for (i=0; i<online_contacts.length; i++) {
@@ -44,9 +45,59 @@
                     spyOn(view, 'openChat').andCallThrough();
                     view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
                     $el.click();
+                    chatboxview = this.chatboxviews.get(jid);
                     expect(view.openChat).toHaveBeenCalled();
                     expect(this.chatboxes.length).toEqual(i+2);
-                    expect(this.chatboxviews.trimOpenChats).toHaveBeenCalled();
+                    expect(this.chatboxviews.trimChats).toHaveBeenCalled();
+                    // Check that new chat boxes are created to the left of the
+                    // controlbox (but to the right of all existing chat boxes)
+                    expect($("#conversejs .chatbox").length).toBe(i+2);
+                    expect($("#conversejs .chatbox")[1].id).toBe(chatboxview.model.get('box_id'));
+                }
+            }, converse));
+
+            it("can be trimmed to conserve space", $.proxy(function () {
+                var i, $el, click, jid, key, view, chatboxview;
+                // openControlBox was called earlier, so the controlbox is
+                // visible, but no other chat boxes have been created.
+                var trimmed_chatboxes = converse.chatboxviews.trimmed_chatboxes_view;
+                expect(this.chatboxes.length).toEqual(1);
+                spyOn(this.chatboxviews, 'trimChats');
+                spyOn(trimmed_chatboxes, 'onChanged').andCallThrough();
+                expect($("#conversejs .chatbox").length).toBe(1); // Controlbox is open
+
+                // Test that they can be trimmed
+                var online_contacts = this.rosterview.$el.find('dt#xmpp-contacts').siblings('dd.current-xmpp-contact').find('a.open-chat');
+                for (i=0; i<online_contacts.length; i++) {
+                    $el = $(online_contacts[i]);
+                    jid = $el.text().replace(' ','.').toLowerCase() + '@localhost';
+                    view = this.rosterview.get(jid);
+                    view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
+                    $el.click();
+                    expect(this.chatboxviews.trimChats).toHaveBeenCalled();
+
+                    chatboxview = this.chatboxviews.get(jid);
+                    spyOn(chatboxview, 'hide').andCallThrough();
+                    chatboxview.model.set({'minimized': true});
+                    expect(trimmed_chatboxes.onChanged).toHaveBeenCalled();
+                    expect(chatboxview.hide).toHaveBeenCalled();
+                    trimmedview = trimmed_chatboxes.get(jid);
+                    expect(trimmedview.$el.is(":visible")).toBeTruthy();
+                }
+                // Test that they can be maximized again
+                var chatboxviews = this.chatboxviews.getAll();
+                var keys = _.keys(chatboxviews);
+                for (i=0; i<keys.length; i++) {
+                    key = keys[i];
+                    if (key === 'controlbox') {
+                        continue;
+                    }
+                    chatboxview = chatboxviews[key];
+                    trimmedview = trimmed_chatboxes.get(key);
+                    spyOn(chatboxview, 'maximize').andCallThrough();
+                    trimmedview.$("a.restore-chat").click();
+                    expect(trimmed_chatboxes.onChanged).toHaveBeenCalled();
+                    expect(chatboxview.maximize).toHaveBeenCalled();
                 }
             }, converse));
 
@@ -72,14 +123,14 @@
 
             it("can be saved to, and retrieved from, localStorage", $.proxy(function () {
                 spyOn(converse, 'emit');
-                spyOn(this.chatboxviews, 'trimOpenChats');
+                spyOn(this.chatboxviews, 'trimChats');
                 runs(function () {
                     utils.openControlBox();
                 });
                 waits(250);
                 runs(function () {
                     utils.openChatBoxes(6);
-                    expect(this.chatboxviews.trimOpenChats).toHaveBeenCalled();
+                    expect(this.chatboxviews.trimChats).toHaveBeenCalled();
                     // We instantiate a new ChatBoxes collection, which by default
                     // will be empty.
                     var newchatboxes = new this.ChatBoxes();
@@ -104,8 +155,8 @@
                 var chatbox = utils.openChatBoxes(1)[0],
                     controlview = this.chatboxviews.get('controlbox'), // The controlbox is currently open
                     chatview = this.chatboxviews.get(chatbox.get('jid'));
-                spyOn(chatview, 'closeChat').andCallThrough();
-                spyOn(controlview, 'closeChat').andCallThrough();
+                spyOn(chatview, 'close').andCallThrough();
+                spyOn(controlview, 'close').andCallThrough();
                 spyOn(converse, 'emit');
 
                 // We need to rebind all events otherwise our spy won't be called
@@ -117,56 +168,60 @@
                 });
                 waits(250);
                 runs(function () {
-                    expect(controlview.closeChat).toHaveBeenCalled();
+                    expect(controlview.close).toHaveBeenCalled();
                     expect(converse.emit).toHaveBeenCalledWith('onChatBoxClosed', jasmine.any(Object));
                     expect(converse.emit.callCount, 1);
                     chatview.$el.find('.close-chatbox-button').click();
                 });
                 waits(250);
                 runs(function () {
-                    expect(chatview.closeChat).toHaveBeenCalled();
+                    expect(chatview.close).toHaveBeenCalled();
                     expect(converse.emit).toHaveBeenCalledWith('onChatBoxClosed', jasmine.any(Object));
                     expect(converse.emit.callCount, 2);
                 });
             }, converse));
 
-            it("can be toggled by clicking a DOM element with class 'toggle-chatbox-button'", function () {
+            it("can be minimized by clicking a DOM element with class 'toggle-chatbox-button'", function () {
                 var chatbox = utils.openChatBoxes(1)[0],
-                    chatview = this.chatboxviews.get(chatbox.get('jid'));
-                spyOn(chatview, 'toggleChatBox').andCallThrough();
+                    chatview = this.chatboxviews.get(chatbox.get('jid')),
+                    trimmed_chatboxes = this.chatboxviews.trimmed_chatboxes_view;
+                spyOn(chatview, 'maximize').andCallThrough();
+                spyOn(chatview, 'minimize').andCallThrough();
                 spyOn(converse, 'emit');
+                spyOn(trimmed_chatboxes, 'onChanged').andCallThrough();
                 // We need to rebind all events otherwise our spy won't be called
                 chatview.delegateEvents();
 
                 runs(function () {
                     chatview.$el.find('.toggle-chatbox-button').click();
                 });
-                waits(250);
+                waits(50);
                 runs(function () {
-                    expect(chatview.toggleChatBox).toHaveBeenCalled();
-                    expect(converse.emit).toHaveBeenCalledWith('onChatBoxToggled', jasmine.any(Object));
+                    expect(chatview.minimize).toHaveBeenCalled();
+                    expect(converse.emit).toHaveBeenCalledWith('onChatBoxMinimized', jasmine.any(Object));
                     expect(converse.emit.callCount, 2);
-                    expect(chatview.$el.find('.chat-body').is(':visible')).toBeFalsy();
-                    expect(chatview.$el.find('.toggle-chatbox-button').hasClass('icon-minus')).toBeFalsy();
-                    expect(chatview.$el.find('.toggle-chatbox-button').hasClass('icon-plus')).toBeTruthy();
+                    expect(chatview.$el.is(':visible')).toBeFalsy();
                     expect(chatview.model.get('minimized')).toBeTruthy();
                     chatview.$el.find('.toggle-chatbox-button').click();
+
+                    trimmedview = trimmed_chatboxes.get(chatview.model.get('id'));
+                    trimmedview.$("a.restore-chat").click();
                 });
-                waits(250);
+                waits(50);
                 runs(function () {
-                    expect(chatview.toggleChatBox).toHaveBeenCalled();
-                    expect(converse.emit).toHaveBeenCalledWith('onChatBoxToggled', jasmine.any(Object));
+                    expect(trimmed_chatboxes.onChanged).toHaveBeenCalled();
+                    expect(chatview.maximize).toHaveBeenCalled();
+                    expect(converse.emit).toHaveBeenCalledWith('onChatBoxMaximized', jasmine.any(Object));
                     expect(chatview.$el.find('.chat-body').is(':visible')).toBeTruthy();
                     expect(chatview.$el.find('.toggle-chatbox-button').hasClass('icon-minus')).toBeTruthy();
                     expect(chatview.$el.find('.toggle-chatbox-button').hasClass('icon-plus')).toBeFalsy();
                     expect(chatview.model.get('minimized')).toBeFalsy();
-                    expect(converse.emit.callCount, 3);
                 });
             }.bind(converse));
 
             it("will be removed from localStorage when closed", $.proxy(function () {
                 spyOn(converse, 'emit');
-                spyOn(converse.chatboxviews, 'trimOpenChats');
+                spyOn(converse.chatboxviews, 'trimChats');
                 this.chatboxes.localStorage._clear();
                 runs(function () {
                     utils.closeControlBox();
@@ -176,7 +231,7 @@
                     expect(converse.emit).toHaveBeenCalledWith('onChatBoxClosed', jasmine.any(Object));
                     expect(converse.chatboxes.length).toEqual(0);
                     utils.openChatBoxes(6);
-                    expect(converse.chatboxviews.trimOpenChats).toHaveBeenCalled();
+                    expect(converse.chatboxviews.trimChats).toHaveBeenCalled();
                     expect(converse.chatboxes.length).toEqual(6);
                     expect(converse.emit).toHaveBeenCalledWith('onChatBoxOpened', jasmine.any(Object));
                     utils.closeAllChatBoxes();
@@ -303,7 +358,7 @@
                     $toolbar = view.$el.find('ul.chat-toolbar');
                     callButton = $toolbar.find('.toggle-call');
                     expect(callButton.length).toBe(0);
-                    view.closeChat();
+                    view.close();
                     // Now check that it's shown if enabled and that it emits
                     // onCallButtonClicked
                     converse.visible_toolbar_buttons.call = true; // enable the button
@@ -328,7 +383,7 @@
                     $toolbar = view.$el.find('ul.chat-toolbar');
                     clearButton = $toolbar.find('.toggle-clear');
                     expect(clearButton.length).toBe(0);
-                    view.closeChat();
+                    view.close();
                     // Now check that it's shown if enabled and that it calls
                     // clearMessages
                     converse.visible_toolbar_buttons.clear = true; // enable the button
@@ -409,13 +464,13 @@
                     runs(function () {
                         utils.openChatBoxFor(contact_jid);
                     });
-                    waits(250);
+                    waits(50);
                     runs(function () {
                         var chatview = converse.chatboxviews.get(contact_jid);
                         expect(chatview.model.get('minimized')).toBeFalsy();
                         chatview.$el.find('.toggle-chatbox-button').click();
                     });
-                    waits(250);
+                    waits(50);
                     runs($.proxy(function () {
                         var chatview = this.chatboxviews.get(contact_jid);
                         expect(chatview.model.get('minimized')).toBeTruthy();
@@ -431,11 +486,12 @@
                         this.chatboxes.onMessage(msg);
                         expect(this.emit).toHaveBeenCalledWith('onMessage', msg);
                     }, converse));
-                    waits(250);
+                    waits(50);
                     runs($.proxy(function () {
-                        var chatview = this.chatboxviews.get(contact_jid);
-                        var $count = chatview.$el.find('.chat-head-message-count');
-                        expect(chatview.model.get('minimized')).toBeTruthy();
+                        var trimmed_chatboxes = this.chatboxviews.trimmed_chatboxes_view;
+                        var trimmedview = trimmed_chatboxes.get(contact_jid);
+                        var $count = trimmedview.$el.find('.chat-head-message-count');
+                        expect(trimmedview.model.get('minimized')).toBeTruthy();
                         expect($count.is(':visible')).toBeTruthy();
                         expect($count.data('count')).toBe(1);
                         expect($count.html()).toBe('1');
@@ -449,23 +505,25 @@
                             .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree()
                         );
                     }, converse));
-                    waits(100);
+                    waits(50);
                     runs($.proxy(function () {
-                        var chatview = this.chatboxviews.get(contact_jid);
-                        var $count = chatview.$el.find('.chat-head-message-count');
-                        expect(chatview.model.get('minimized')).toBeTruthy();
+                        var trimmed_chatboxes = this.chatboxviews.trimmed_chatboxes_view;
+                        var trimmedview = trimmed_chatboxes.get(contact_jid);
+                        var $count = trimmedview.$el.find('.chat-head-message-count');
+                        expect(trimmedview.model.get('minimized')).toBeTruthy();
                         expect($count.is(':visible')).toBeTruthy();
                         expect($count.data('count')).toBe(2);
                         expect($count.html()).toBe('2');
-                        chatview.$el.find('.toggle-chatbox-button').click();
+                        trimmedview.$el.find('.restore-chat').click();
                     }, converse));
-                    waits(250);
+                    waits(50);
                     runs($.proxy(function () {
-                        var chatview = this.chatboxviews.get(contact_jid);
-                        var $count = chatview.$el.find('.chat-head-message-count');
-                        expect(chatview.model.get('minimized')).toBeFalsy();
+                        var trimmed_chatboxes = this.chatboxviews.trimmed_chatboxes_view;
+                        var trimmedview = trimmed_chatboxes.get(contact_jid);
+                        var $count = trimmedview.$el.find('.chat-head-message-count');
+                        expect(trimmedview.model.get('minimized')).toBeFalsy();
                         expect($count.is(':visible')).toBeFalsy();
-                        expect($count.data('count')).toBe(0);
+                        expect($count.data('count')).toBeFalsy();
                         expect($count.html()).toBe('0');
                     }, converse));
                 }, converse));

+ 23 - 22
spec/chatroom.js

@@ -115,14 +115,14 @@
             it("can be saved to, and retrieved from, localStorage", $.proxy(function () {
                 // We instantiate a new ChatBoxes collection, which by default
                 // will be empty.
-                spyOn(this.chatboxviews, 'trimOpenChats');
+                spyOn(this.chatboxviews, 'trimChats');
                 utils.openControlBox();
                 var newchatboxes = new this.ChatBoxes();
                 expect(newchatboxes.length).toEqual(0);
                 // The chatboxes will then be fetched from localStorage inside the
                 // onConnected method
                 newchatboxes.onConnected();
-                expect(this.chatboxviews.trimOpenChats).toHaveBeenCalled();
+                expect(this.chatboxviews.trimChats).toHaveBeenCalled();
                 expect(newchatboxes.length).toEqual(2); // XXX: Includes controlbox, is this a bug?
                 // Check that the chatrooms retrieved from localStorage
                 // have the same attributes values as the original ones.
@@ -139,33 +139,34 @@
                 this.rosterview.render();
             }, converse));
 
-            it("can be toggled by clicking a DOM element with class 'toggle-chatbox-button'", function () {
+            it("can be minimized by clicking a DOM element with class 'toggle-chatbox-button'", function () {
                 var view = this.chatboxviews.get('lounge@muc.localhost'),
-                    chatroom = view.model, $el;
-                spyOn(view, 'toggleChatBox').andCallThrough();
+                    trimmed_chatboxes = this.chatboxviews.trimmed_chatboxes_view;
+
+                spyOn(view, 'minimize').andCallThrough();
+                spyOn(view, 'maximize').andCallThrough();
                 spyOn(converse, 'emit');
                 view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
                 runs(function () {
                     view.$el.find('.toggle-chatbox-button').click();
                 });
-                waits(250);
+                waits(50);
                 runs(function () {
-                    expect(view.toggleChatBox).toHaveBeenCalled();
-                    expect(converse.emit).toHaveBeenCalledWith('onChatBoxToggled', jasmine.any(Object));
+                    expect(view.minimize).toHaveBeenCalled();
+                    expect(converse.emit).toHaveBeenCalledWith('onChatBoxMinimized', jasmine.any(Object));
                     expect(converse.emit.callCount, 2);
-                    expect(view.$el.find('.chat-body').is(':visible')).toBeFalsy();
-                    expect(view.$el.find('.toggle-chatbox-button').hasClass('icon-minus')).toBeFalsy();
-                    expect(view.$el.find('.toggle-chatbox-button').hasClass('icon-plus')).toBeTruthy();
+                    expect(view.$el.is(':visible')).toBeFalsy();
                     expect(view.model.get('minimized')).toBeTruthy();
-                    view.$el.find('.toggle-chatbox-button').click();
+                    expect(view.minimize).toHaveBeenCalled();
+
+                    trimmedview = trimmed_chatboxes.get(view.model.get('id'));
+                    trimmedview.$("a.restore-chat").click();
                 });
-                waits(250);
+                waits(50);
                 runs(function () {
-                    expect(view.toggleChatBox).toHaveBeenCalled();
-                    expect(converse.emit).toHaveBeenCalledWith('onChatBoxToggled', jasmine.any(Object));
-                    expect(view.$el.find('.chat-body').is(':visible')).toBeTruthy();
-                    expect(view.$el.find('.toggle-chatbox-button').hasClass('icon-minus')).toBeTruthy();
-                    expect(view.$el.find('.toggle-chatbox-button').hasClass('icon-plus')).toBeFalsy();
+                    expect(view.maximize).toHaveBeenCalled();
+                    expect(converse.emit).toHaveBeenCalledWith('onChatBoxMaximized', jasmine.any(Object));
+                    expect(view.$el.is(':visible')).toBeTruthy();
                     expect(view.model.get('minimized')).toBeFalsy();
                     expect(converse.emit.callCount, 3);
                 });
@@ -174,16 +175,16 @@
 
             it("can be closed again by clicking a DOM element with class 'close-chatbox-button'", $.proxy(function () {
                 var view = this.chatboxviews.get('lounge@muc.localhost'), chatroom = view.model, $el;
-                spyOn(view, 'closeChat').andCallThrough();
+                spyOn(view, 'close').andCallThrough();
                 spyOn(converse, 'emit');
                 spyOn(converse.connection.muc, 'leave');
                 view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
                 runs(function () {
                     view.$el.find('.close-chatbox-button').click();
                 });
-                waits(250);
+                waits(50);
                 runs(function () {
-                    expect(view.closeChat).toHaveBeenCalled();
+                    expect(view.close).toHaveBeenCalled();
                     expect(this.connection.muc.leave).toHaveBeenCalled();
                     expect(this.emit).toHaveBeenCalledWith('onChatBoxClosed', jasmine.any(Object));
                 }.bind(converse));
@@ -204,7 +205,7 @@
 
             afterEach($.proxy(function () {
                 var view = this.chatboxviews.get('problematic@muc.localhost');
-                view.closeChat();
+                view.close();
             }, converse));
 
             it("will show an error message if the room requires a password", $.proxy(function () {

+ 0 - 7
spec/controlbox.js

@@ -8,13 +8,10 @@
     );
 } (this, function (mock, utils) {
     describe("The Control Box", $.proxy(function (mock, utils) {
-
         beforeEach(function () {
             runs(function () {
                 utils.openControlBox();
             });
-            waits(250);
-            runs(function () {});
         });
 
         it("can be opened by clicking a DOM element with class 'toggle-online-users'", $.proxy(function () {
@@ -501,8 +498,6 @@
             runs(function () {
                 utils.openControlBox();
             });
-            waits(250);
-            runs(function () {});
         }, converse));
 
         it("contains two tabs, 'Contacts' and 'ChatRooms'", $.proxy(function () {
@@ -524,8 +519,6 @@
                 runs(function () {
                     utils.openControlBox();
                 });
-                waits(250);
-                runs(function () {});
             }, converse));
 
             it("is opened by clicking the 'Chatrooms' tab", $.proxy(function () {

+ 1 - 0
src/deps-full.js

@@ -3,6 +3,7 @@ define("converse-dependencies", [
     "moment",
     "locales",
     "backbone.localStorage",
+    "backbone.overview",
     "jquery.tinysort",
     "jquery.browser",
     "strophe",

+ 1 - 0
src/deps-no-otr.js

@@ -2,6 +2,7 @@ define("converse-dependencies", [
     "moment",
     "locales",
     "backbone.localStorage",
+    "backbone.overview",
     "jquery.tinysort",
     "jquery.browser",
     "strophe",

+ 65 - 61
src/templates.js

@@ -1,77 +1,81 @@
 define("converse-templates", [
     "tpl!src/templates/action",
-    "tpl!src/templates/message",
-    "tpl!src/templates/new_day",
-    "tpl!src/templates/info",
-    "tpl!src/templates/controlbox",
-    "tpl!src/templates/chatbox",
-    "tpl!src/templates/toolbar",
-    "tpl!src/templates/contacts_tab",
-    "tpl!src/templates/contacts_panel",
-    "tpl!src/templates/chatrooms_tab",
-    "tpl!src/templates/login_tab",
     "tpl!src/templates/add_contact_dropdown",
     "tpl!src/templates/add_contact_form",
-    "tpl!src/templates/room_item",
-    "tpl!src/templates/room_description",
-    "tpl!src/templates/room_panel",
-    "tpl!src/templates/chatroom",
+    "tpl!src/templates/change_status_message",
+    "tpl!src/templates/chat_status",
     "tpl!src/templates/chatarea",
+    "tpl!src/templates/chatbox",
+    "tpl!src/templates/chatroom",
+    "tpl!src/templates/chatrooms_tab",
+    "tpl!src/templates/choose_status",
+    "tpl!src/templates/contacts",
+    "tpl!src/templates/contacts_panel",
+    "tpl!src/templates/contacts_tab",
+    "tpl!src/templates/controlbox",
+    "tpl!src/templates/controlbox_toggle",
+    "tpl!src/templates/field",
+    "tpl!src/templates/form_checkbox",
     "tpl!src/templates/form_input",
-    "tpl!src/templates/select_option",
     "tpl!src/templates/form_select",
-    "tpl!src/templates/form_checkbox",
-    "tpl!src/templates/field",
+    "tpl!src/templates/info",
+    "tpl!src/templates/login_panel",
+    "tpl!src/templates/login_tab",
+    "tpl!src/templates/message",
+    "tpl!src/templates/new_day",
     "tpl!src/templates/occupant",
-    "tpl!src/templates/roster_item",
     "tpl!src/templates/pending_contact",
+    "tpl!src/templates/pending_contacts",
     "tpl!src/templates/requesting_contact",
     "tpl!src/templates/requesting_contacts",
-    "tpl!src/templates/pending_contacts",
-    "tpl!src/templates/contacts",
-    "tpl!src/templates/chat_status",
-    "tpl!src/templates/change_status_message",
-    "tpl!src/templates/choose_status",
+    "tpl!src/templates/room_description",
+    "tpl!src/templates/room_item",
+    "tpl!src/templates/room_panel",
+    "tpl!src/templates/roster_item",
+    "tpl!src/templates/select_option",
     "tpl!src/templates/status_option",
-    "tpl!src/templates/login_panel",
-    "tpl!src/templates/controlbox_toggle"
+    "tpl!src/templates/toolbar",
+    "tpl!src/templates/trimmed_chat",
+    "tpl!src/templates/trimmed_chats"
 ], function () {
     return {
-        action: arguments[0],
-        message: arguments[1],
-        new_day: arguments[2],
-        info: arguments[3],
-        controlbox: arguments[4],
-        chatbox: arguments[5],
-        toolbar: arguments[6],
-        contacts_tab: arguments[7],
-        contacts_panel: arguments[8],
-        chatrooms_tab: arguments[9],
-        login_tab: arguments[10],
-        add_contact_dropdown: arguments[11],
-        add_contact_form: arguments[12],
-        room_item: arguments[13],
-        room_description: arguments[14],
-        room_panel: arguments[15],
-        chatroom: arguments[16],
-        chatarea: arguments[17],
-        form_input: arguments[18],
-        select_option: arguments[19],
-        form_select: arguments[20],
-        form_checkbox: arguments[21],
-        field: arguments[22],
-        occupant: arguments[23],
-        roster_item: arguments[24],
-        pending_contact: arguments[25],
-        requesting_contact: arguments[26],
-        requesting_contacts: arguments[27],
-        pending_contacts: arguments[28],
-        contacts: arguments[29],
-        chat_status: arguments[30],
-        change_status_message: arguments[31],
-        choose_status: arguments[32],
-        status_option: arguments[33],
-        login_panel: arguments[34],
-        controlbox_toggle: arguments[35]
+        action:                 arguments[0],
+        add_contact_dropdown:   arguments[1],
+        add_contact_form:       arguments[2],
+        change_status_message:  arguments[3],
+        chat_status:            arguments[4],
+        chatarea:               arguments[5],
+        chatbox:                arguments[6],
+        chatroom:               arguments[7],
+        chatrooms_tab:          arguments[8],
+        choose_status:          arguments[9],
+        contacts:               arguments[10],
+        contacts_panel:         arguments[11],
+        contacts_tab:           arguments[12],
+        controlbox:             arguments[13],
+        controlbox_toggle:      arguments[14],
+        field:                  arguments[15],
+        form_checkbox:          arguments[16],
+        form_input:             arguments[17],
+        form_select:            arguments[18],
+        info:                   arguments[19],
+        login_panel:            arguments[20],
+        login_tab:              arguments[21],
+        message:                arguments[22],
+        new_day:                arguments[23],
+        occupant:               arguments[24],
+        pending_contact:        arguments[25],
+        pending_contacts:       arguments[26],
+        requesting_contact:     arguments[27],
+        requesting_contacts:    arguments[28],
+        room_description:       arguments[29],
+        room_item:              arguments[30],
+        room_panel:             arguments[31],
+        roster_item:            arguments[32],
+        select_option:          arguments[33],
+        status_option:          arguments[34],
+        toolbar:                arguments[35],
+        trimmed_chat:           arguments[36],
+        trimmed_chats:          arguments[37]
     };
 });

+ 4 - 9
src/templates/chatbox.html

@@ -1,13 +1,8 @@
-<div class="box-flyout {[if (minimized) {]} minimized {[}]}"
-    {[if (!minimized) {]} style="height: {{height}}px" {[}]}>
-    <div class="dragresize dragresize-tm" {[ if (minimized) { ]} style="display:none" {[ } ]}></div>
+<div class="box-flyout" style="height: {{height}}px">
+    <div class="dragresize dragresize-tm"></div>
     <div class="chat-head chat-head-chatbox">
-        <div class="chat-head-message-count">0</div>
         <a class="close-chatbox-button icon-close"></a>
-        <a class="toggle-chatbox-button
-                  {[ if (minimized) { ]} icon-plus {[ } ]}
-                  {[ if (!minimized) { ]} icon-minus {[ } ]}
-                 "></a>
+        <a class="toggle-chatbox-button icon-minus"></a>
         <div class="chat-title">
             {[ if (url) { ]}
                 <a href="{{url}}" target="_blank" class="user">
@@ -19,7 +14,7 @@
         </div>
         <p class="user-custom-message"><p/>
     </div>
-    <div class="chat-body" {[ if (minimized) { ]} style="display:none" {[ } ]}>
+    <div class="chat-body">
         <div class="chat-content"></div>
         <form class="sendXMPPMessage" action="" method="post">
             {[ if (show_toolbar) { ]}

+ 5 - 10
src/templates/chatroom.html

@@ -1,18 +1,13 @@
-<div class="box-flyout {[if (minimized) {]} minimized {[}]}"
-    {[if (!minimized) {]} style="height: {{height}}px" {[}]}>
-    <div class="dragresize dragresize-tm" {[ if (minimized) { ]} style="display:none" {[ } ]}></div>
+<div class="box-flyout" style="height: {{height}}px"
+    {[ if (minimized) { ]} style="display:none" {[ } ]}>
+    <div class="dragresize dragresize-tm"></div>
     <div class="chat-head chat-head-chatroom">
         <div class="chat-head-message-count">0</div>
         <a class="close-chatbox-button icon-close"></a>
-        <a class="toggle-chatbox-button
-                  {[ if (minimized) { ]} icon-plus {[ } ]}
-                  {[ if (!minimized) { ]} icon-minus {[ } ]}
-                 "></a>
+        <a class="toggle-chatbox-button icon-minus"></a>
         <a class="configure-chatroom-button icon-wrench" style="display:none"></a>
         <div class="chat-title"> {{ name }} </div>
         <p class="chatroom-topic"><p/>
     </div>
-    <div class="chat-body" {[ if (minimized) { ]} style="display:none" {[ } ]}>
-        <span class="spinner centered"/>
-    </div>
+    <div class="chat-body"><span class="spinner centered"/></div>
 </div>

+ 7 - 0
src/templates/trimmed_chat.html

@@ -0,0 +1,7 @@
+<a class="close-chatbox-button icon-close"></a>
+<div class="chat-title">
+    <a href="#" class="restore-chat">
+        <div class="chat-head-message-count">0</div>
+        {{ title }}
+    </a>
+</div>

+ 1 - 0
src/templates/trimmed_chats.html

@@ -0,0 +1 @@
+<div id="trimmed-chatboxes"><div class="box-flyout"></div></div>

+ 4 - 3
tests.html

@@ -6,8 +6,10 @@
     <meta name="description" content="Converse.js: A chat client for your website" />
     <link rel="shortcut icon" type="image/png" href="components/jasmine/images/jasmine_favicon.png">
     <link rel="stylesheet" type="text/css" href="components/jasmine/src/html/jasmine.css">
-    <link rel="stylesheet" type="text/css" media="screen" href="stylesheets/stylesheet.css">
-    <link rel="stylesheet" type="text/css" media="screen" href="converse.css">
+    <link type="text/css" rel="stylesheet" media="screen" href="http://netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css" />
+    <link type="text/css" rel="stylesheet" media="screen" href="http://netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css" />
+    <link type="text/css" rel="stylesheet" media="screen" href="css/theme.css" />
+    <link type="text/css" rel="stylesheet" media="screen" href="css/converse.css" />
     <script src="main.js"></script>
     <script data-main="tests/main" src="components/requirejs/require.js"></script>
 </head>
@@ -19,6 +21,5 @@
           <h2 id="project_tagline">Tests</h2>
         </header>
     </div>
-    <div id="conversejs"></div>
 </body>
 </html>

+ 1 - 1
tests/main.js

@@ -45,7 +45,7 @@ require([
             auto_subscribe: false,
             animate: false,
             connection: mock.mock_connection,
-            testing: true
+            no_trimming: true
         }, function (converse) {
             window.converse = converse;
             window.crypto = {

+ 4 - 5
tests/utils.js

@@ -13,7 +13,7 @@
         var i, chatbox;
         for (i=converse.chatboxes.models.length-1; i>-1; i--) {
             chatbox = converse.chatboxes.models[i];
-            converse.chatboxviews.get(chatbox.get('id')).closeChat();
+            converse.chatboxviews.get(chatbox.get('id')).close();
         }
         return this;
     };
@@ -22,17 +22,16 @@
         var i, chatbox, num_chatboxes = converse.chatboxes.models.length;
         for (i=num_chatboxes-1; i>-1; i--) {
             chatbox = converse.chatboxes.models[i];
-            converse.chatboxviews.get(chatbox.get('id')).closeChat();
+            converse.chatboxviews.get(chatbox.get('id')).close();
             converse.chatboxviews.get(chatbox.get('id')).$el.remove();
         }
-        converse.chatboxviews.get('controlbox').closeChat();
+        converse.chatboxviews.get('controlbox').close();
         converse.chatboxviews.get('controlbox').$el.remove();
         return this;
     };
 
     utils.initConverse = function () {
-        converse.chatboxes = new converse.ChatBoxes();
-        converse.chatboxviews = new converse.ChatBoxViews({model: converse.chatboxes});
+        converse._initialize();
         converse.onConnected();
     };
 

Some files were not shown because too many files changed in this diff