Pārlūkot izejas kodu

Allow the full app to be embedded.

- new config option `singleton`.
- new plugin `converse-uniview`
- removed `converse-embedded`.
- various CSS changes, to properly render an embedded full app
- don't re-open cached and non-autojoined chats in singleton mode

The goal here is to extend the `embedded` `view_mode` so that the full app can
also be embedded, not just a single MUC or private chat.

To do this, we'll need to differentiate between multi and singleton chat apps.

* A singleton chat app contains only a single chat.
* A multi-chat app can contain zero or more chats

So we introduce a new config option, `singleton`, which when used with
`view_mode` set to `embedded` will determine whether a single chat or the full
app is embedded.

Similarly, in `overlayed`, `fullscreen` and `mobile` view modes, `singleton`
set to true will allow only one chat within the parameters of that view mode.

We're appropriating the word `singleton` and introducing the concepts of
`uniview` and `multiview` (see a785ca8) to cover what was
previously meant with `singleton`.

updates #1297
JC Brand 6 gadi atpakaļ
vecāks
revīzija
f387c947f5

+ 3 - 0
.gitignore

@@ -21,6 +21,9 @@ pip-selfcheck.json
 3rdparty/libsignal-protocol-javascript/
 *.map
 
+locale.zip
+sounds.zip
+
 analytics.js
 
 # virtualenv/python/buildout

+ 4 - 0
CHANGES.md

@@ -13,6 +13,10 @@
 - Replace `moment` with [DayJS](https://github.com/iamkun/dayjs).
 - New API method [\_converse.api.disco.features.get](https://conversejs.org/docs/html/api/-_converse.api.disco.features.html#.get)
 - New config setting [muc_show_join_leave_status](https://conversejs.org/docs/html/configuration.html#muc-show-join-leave-status)
+- New config option [singleton](https://conversejs.org/docs/html/configuration.html#singleton).
+  By setting this option to `false` and `view_mode` to `'embedded'`, it's now possible to
+  "embed" the full app and not just a single chat. To embed just a single chat,
+  it's now necessary to explicitly set `singleton` to `true`.
 - New event: `chatBoxBlurred`.
 - New event: [chatBoxBlurred](https://conversejs.org/docs/html/api/-_converse.html#event:chatBoxBlurred)
 - New event: [chatReconnected](https://conversejs.org/docs/html/api/-_converse.html#event:chatReconnected)

+ 1 - 0
demo/embedded.html

@@ -90,6 +90,7 @@
         notify_all_room_messages: [
             'anonymous@conference.nomnom.im',
         ],
+        singleton: true,
         locales_url: "../locale/{{{locale}}}/LC_MESSAGES/converse.json",
         view_mode: 'embedded',
     });

+ 15 - 0
docs/source/configuration.rst

@@ -1383,6 +1383,21 @@ show_send_button
 
 If set to ``true``, a button will be visible which can be clicked to send a message.
 
+singleton
+---------
+
+* Default:  ``false``
+
+If set to ``true``, then only one chat (one-on-one or groupchat) will be allowed.
+
+The chat must be specified with the `auto_join_rooms`_ or `auto_join_private_chats`_ options.
+
+This setting is useful together with `view_mode`_ set to ``embedded``, when you
+want to embed a chat into the page.
+
+Alternatively you could use it with `view_mode`_ set to ``overlayed`` to create
+a single helpdesk-type chat.
+
 sounds_path
 -----------
 

+ 79 - 21
sass/_chatbox.scss

@@ -474,12 +474,6 @@
 
 /* ******************* Overlay and embedded styles *************************** */
 
-#conversejs.converse-embedded {
-    .chat-textarea {
-        max-height: var(--fullpage-max-chat-textarea-height);
-    }
-}
-
 #conversejs.converse-embedded,
 #conversejs.converse-overlayed {
     .chat-head {
@@ -580,8 +574,31 @@
 	}
 }
 
-/* ******************* Fullpage styles *************************** */
 
+#conversejs.converse-embedded.converse-singleton {
+    .flyout {
+        border: none !important;
+    }
+    .chat-head {
+        height: var(--fullpage-chat-head-height);
+        padding: 0.5em;
+    }
+    .chatbox {
+        margin: 0;
+        @include make-col-ready();
+        @include media-breakpoint-up(md) {
+            @include make-col(12);
+        }
+        @include media-breakpoint-up(lg) {
+            @include make-col(12);
+        }
+        @include media-breakpoint-up(xl) {
+            @include make-col(12);
+        }
+    }
+}
+
+#conversejs.converse-embedded,
 #conversejs.converse-fullscreen  {
     .flyout {
         border-radius: 0;
@@ -589,13 +606,9 @@
         border: var(--flyout-padding) solid var(--chat-head-color);
         bottom: 0;
     }
-    .chatbox-btn {
-        font-size: var(--fullpage-chatbox-button-size);
-        margin: 0 0.3em;
-    }
+
     .chat-head {
         height: var(--fullpage-chat-head-height);
-        font-size: var(--font-size-huge);
         padding: 0;
         .user-custom-message {
             font-size: 70%;
@@ -609,18 +622,9 @@
             @include make-col(2);
         }
     }
-    .chat-textarea {
-        max-height: var(--fullpage-max-chat-textarea-height);
-    }
-    .emoji-picker {
-        height: var(--fullpage-emoji-picker-height);
-    }
 
     .chatbox {
-        width: 100%;
-        height: 100%;
         margin: 0;
-
         @include make-col-ready();
         @include media-breakpoint-up(md) {
             @include make-col(8);
@@ -632,6 +636,60 @@
             @include make-col(10);
         }
 
+        .box-flyout {
+            box-shadow: none;
+            overflow: hidden;
+        }
+    }
+}
+
+#conversejs.converse-embedded {
+    .converse-chatboxes {
+        z-index: 1031; // One more than bootstrap navbar
+        position: inherit;
+        flex-wrap: nowrap;
+        bottom: auto;
+        height: 100%;
+        width: 100%;
+        margin-left: -15px;
+    }
+
+    .chatbox {
+        .box-flyout {
+            bottom: 0;
+            height: 100%;
+            min-width: auto;
+            width: 100%;
+        }
+
+        .chat-title {
+            padding: 0.3em;
+            font-size: 120%;
+        }
+    }
+
+    .chat-textarea {
+        max-height: var(--fullpage-max-chat-textarea-height);
+    }
+}
+
+/* ******************* Fullpage styles *************************** */
+
+#conversejs.converse-fullscreen  {
+    .chatbox-btn {
+        font-size: var(--fullpage-chatbox-button-size);
+        margin: 0 0.3em;
+    }
+    .chat-head {
+        font-size: var(--font-size-huge);
+    }
+    .chat-textarea {
+        max-height: var(--fullpage-max-chat-textarea-height);
+    }
+    .emoji-picker {
+        height: var(--fullpage-emoji-picker-height);
+    }
+    .chatbox {
         .box-flyout {
             background-color: var(--chat-head-color);
             box-shadow: none;

+ 41 - 0
sass/_chatrooms.scss

@@ -429,6 +429,7 @@
     }
 }
 
+
 #conversejs.converse-fullscreen {
     .chatroom {
         .box-flyout {
@@ -462,6 +463,7 @@
     }
 }
 
+#conversejs.converse-embedded,
 #conversejs.converse-fullscreen,
 #conversejs.converse-mobile {
 
@@ -519,3 +521,42 @@
         }
     }
 }
+
+#conversejs.converse-embedded {
+    .chatroom {
+        margin: 0;
+        width: 100%;
+        .box-flyout {
+            .occupants-heading {
+                font-size: 120%;
+            }
+            .chat-content {
+                .chat-message {
+                    margin: 0.5em;
+                    font-size: 120%;
+                }
+            }
+            .sendXMPPMessage {
+                .chat-textarea {
+                    padding: 0.5em;
+                    font-size: 110%;
+                }
+            }
+            .chatroom-body {
+                height: 100%;
+                .chatroom-form-container {
+                    height: 100%;
+                    position: relative;
+                }
+            }
+            .occupants {
+                .occupant-list {
+                    padding-left: 0.3em;
+                    li.occupant {
+                        font-size: 120%;
+                    }
+                }
+            }
+        }
+    }
+}

+ 2 - 1
sass/_controlbox.scss

@@ -435,6 +435,7 @@
     }
 }
 
+#conversejs.converse-embedded,
 #conversejs.converse-fullscreen,
 #conversejs.converse-mobile {
     #controlbox {
@@ -545,7 +546,7 @@
 
 @include media-breakpoint-down(sm) {
 
-    #conversejs:not(.converse-embedded)  {
+    #conversejs {
         left: 0;
         right: 0;
         padding-left: env(safe-area-inset-left);

+ 11 - 0
sass/_core.scss

@@ -120,6 +120,17 @@ body.converse-fullscreen {
     &.converse-overlayed {
         height: 3em;
     }
+    &.converse-embedded {
+        @include box-sizing(border-box);
+        *, *:before, *:after {
+            @include box-sizing(border-box);
+        }
+        bottom: auto;
+        height: 100%; // When embedded, it fills the containing element
+        position: relative;
+        right: auto;
+        width: 100%;
+    }
 
     .brand-heading-container {
         text-align: center;

+ 0 - 79
sass/_embedded.scss

@@ -1,79 +0,0 @@
-#conversejs.converse-embedded {
-
-    @include box-sizing(border-box);
-    *, *:before, *:after {
-        @include box-sizing(border-box);
-    }
-
-    bottom: auto;
-    height: 100%; // When embedded, it fills the containing element
-    position: relative;
-    right: auto;
-    width: 100%;
-
-    .converse-chatboxes {
-        z-index: 1031; // One more than bootstrap navbar
-        position: inherit;
-        bottom: auto;
-        height: 100%;
-        width: 100%;
-    }
-
-    .chatbox {
-        margin: 0;
-        height: 100%;
-        width: 100%;
-
-        .flyout.box-flyout {
-            bottom: 0;
-            box-shadow: none;
-            height: 100%;
-            min-width: auto;
-            width: 100%;
-        }
-
-        .chat-title {
-            padding: 0.3em;
-            font-size: 120%;
-        }
-    }
-    .chatbox-btn {
-        display: none;
-    }
-    .chatroom {
-        margin: 0;
-        width: 100%;
-        .box-flyout {
-            .occupants-heading {
-                font-size: 120%;
-            }
-            .chat-content {
-                .chat-message {
-                    margin: 0.5em;
-                    font-size: 120%;
-                }
-            }
-            .sendXMPPMessage {
-                .chat-textarea {
-                    padding: 0.5em;
-                    font-size: 110%;
-                }
-            }
-            .chatroom-body {
-                height: 100%;
-                .chatroom-form-container {
-                    height: 100%;
-                    position: relative;
-                }
-            }
-            .occupants {
-                .occupant-list {
-                    padding-left: 0.3em;
-                    li.occupant {
-                        font-size: 120%;
-                    }
-                }
-            }
-        }
-    }
-}

+ 0 - 1
sass/converse.scss

@@ -63,4 +63,3 @@
 @import "minimized_chats";
 @import "bookmarks";
 @import "autocomplete";
-@import "embedded";

+ 4 - 6
spec/roomslist.js

@@ -115,15 +115,11 @@
 
         it("is highlighted if its currently open", mock.initConverse(
             null, ['rosterGroupsFetched', 'chatBoxesFetched'],
-            { whitelisted_plugins: ['converse-roomslist'],
-              allow_bookmarks: false // Makes testing easier, otherwise we
-                                     // have to mock stanza traffic.
+            { view_mode: 'fullscreen',
+              allow_bookmarks: false // Makes testing easier, otherwise we have to mock stanza traffic.
             }, async function (done, _converse) {
 
-            spyOn(_converse, 'isUniView').and.callFake(() => true);
-
             let room_els, item;
-            test_utils.openControlBox();
             await _converse.api.rooms.open('coven@chat.shakespeare.lit', {'nick': 'some1'});
             room_els = _converse.rooms_list_view.el.querySelectorAll(".available-chatroom");
             expect(room_els.length).toBe(1);
@@ -139,6 +135,8 @@
             expect(room_els.length).toBe(1);
             item = room_els[0];
             expect(item.textContent.trim()).toBe('balcony@chat.shakespeare.lit');
+            const conv_el = document.querySelector('#conversejs');
+            conv_el.parentElement.removeChild(conv_el);
             done();
         }));
 

+ 3 - 0
src/converse-chatboxviews.js

@@ -115,6 +115,9 @@ converse.plugins.add('converse-chatboxviews', {
                 const body = document.querySelector('body');
                 body.classList.add(`converse-${_converse.view_mode}`);
                 this.el.classList.add(`converse-${_converse.view_mode}`);
+                if (_converse.singleton) {
+                    this.el.classList.add(`converse-singleton`);
+                }
                 this.render();
             },
 

+ 2 - 2
src/converse-controlbox.js

@@ -73,7 +73,7 @@ converse.plugins.add('converse-controlbox', {
     dependencies: ["converse-modal", "converse-chatboxes", "converse-rosterview", "converse-chatview"],
 
     enabled (_converse) {
-        return _converse.view_mode !== 'embedded';
+        return !_converse.singleton;
     },
 
     overrides: {
@@ -112,7 +112,7 @@ converse.plugins.add('converse-controlbox', {
             validate (attrs, options) {
                 const { _converse } = this.__super__;
                 if (attrs.type === _converse.CONTROLBOX_TYPE) {
-                    if (_converse.view_mode === 'embedded')  {
+                    if (_converse.view_mode === 'embedded' && _converse.singleton)  {
                         return 'Controlbox not relevant in embedded view mode';
                     }
                     return;

+ 0 - 38
src/converse-embedded.js

@@ -1,38 +0,0 @@
-// Converse.js
-// https://conversejs.org
-//
-// Copyright (c) 2013-2019, the Converse.js developers
-// Licensed under the Mozilla Public License (MPLv2)
-
-import "@converse/headless/converse-muc";
-import converse from "@converse/headless/converse-core";
-
-const { Backbone, _ } = converse.env;
-
-converse.plugins.add('converse-embedded', {
-
-    enabled (_converse) {
-        return _converse.view_mode === 'embedded';
-    },
-
-    initialize () {
-        /* The initialize function gets called as soon as the plugin is
-         * loaded by converse.js's plugin machinery.
-         */
-        this._converse.api.settings.update({
-            'allow_logout': false, // No point in logging out when we have auto_login as true.
-            'allow_muc_invitations': false, // Doesn't make sense to allow because only
-                                            // roster contacts can be invited
-            'hide_muc_server': true
-        });
-        const { _converse } = this;
-        if (!Array.isArray(_converse.auto_join_rooms) && !_.isArray(_converse.auto_join_private_chats)) {
-            throw new Error("converse-embedded: auto_join_rooms must be an Array");
-        }
-        if (_converse.auto_join_rooms.length > 1 && _converse.auto_join_private_chats.length > 1) {
-            throw new Error("converse-embedded: It doesn't make "+
-                "sense to have the auto_join_rooms setting more then one, "+
-                "since only one chat room can be open at any time.");
-        }
-    }
-});

+ 2 - 1
src/converse-fullscreen.js

@@ -14,10 +14,11 @@ import tpl_brand_heading from "templates/inverse_brand_heading.html";
 
 const { Strophe, _ } = converse.env;
 
+
 converse.plugins.add('converse-fullscreen', {
 
     enabled (_converse) {
-        return _.includes(['fullscreen', 'embedded'], _converse.view_mode);
+        return _converse.isUniView();
     },
 
     overrides: {

+ 1 - 1
src/converse-muc-views.js

@@ -720,8 +720,8 @@ converse.plugins.add('converse-muc-views', {
                  */
                 return tpl_chatroom_head(
                     Object.assign(this.model.toJSON(), {
-                        '_converse': _converse,
                         'Strophe': Strophe,
+                        '_converse': _converse,
                         'info_close': __('Close and leave this groupchat'),
                         'info_configure': __('Configure this groupchat'),
                         'info_details': __('Show more details about this groupchat'),

+ 23 - 94
src/converse-singleton.js

@@ -7,110 +7,39 @@
 /* converse-singleton
  * ******************
  *
- * A plugin which ensures that only one chat (private or groupchat) is
- * visible at any one time. All other ongoing chats are hidden and kept in the
- * background.
- *
- * This plugin makes sense in mobile or fullscreen chat environments (as
- * configured by the `view_mode` setting).
+ * A plugin which restricts Converse to only one chat.
  */
 
-import "converse-chatview";
 import converse from "@converse/headless/converse-core";
 
 const { _, Strophe } = converse.env;
 const u = converse.env.utils;
 
 
-function hideChat (view) {
-    if (view.model.get('id') === 'controlbox') { return; }
-    u.safeSave(view.model, {'hidden': true});
-    view.hide();
-}
-
-
 converse.plugins.add('converse-singleton', {
-    // It's possible however to make optional dependencies non-optional.
-    // If the setting "strict_plugin_dependencies" is set to true,
-    // an error will be raised if the plugin is not found.
-    //
-    // NB: These plugins need to have already been loaded via require.js.
-    dependencies: ['converse-chatboxes', 'converse-muc', 'converse-muc-views', 'converse-controlbox', 'converse-rosterview'],
-
-    overrides: {
-        // overrides mentioned here will be picked up by converse.js's
-        // plugin architecture they will replace existing methods on the
-        // relevant objects or classes.
-        //
-        // new functions which don't exist yet can also be added.
-
-        ChatBox: {
-            maybeShow (force) {
-                // This method must return the chatbox
-                const { _converse } = this.__super__;
-                if (!force && _converse.isUniView()) {
-                    if (this.get('id') === 'controlbox') {
-                        return this.trigger('show');
-                    }
-                    const any_chats_visible = _converse.chatboxes
-                        .filter(cb => cb.get('id') != 'controlbox')
-                        .filter(cb => !cb.get('hidden')).length > 0;
-
-                    if (!any_chats_visible || !this.get('hidden')) {
-                        return this.trigger('show');
-                    }
-                } else {
-                    return this.__super__.maybeShow.apply(this, arguments);
-                }
-            }
-        },
 
-        ChatBoxes: {
-            createChatBox (jid, attrs) {
-                /* Make sure new chat boxes are hidden by default. */
-                const { _converse } = this.__super__;
-                if (_converse.isUniView()) {
-                    attrs = attrs || {};
-                    attrs.hidden = true;
-                }
-                return this.__super__.createChatBox.call(this, jid, attrs);
-            }
-        },
-
-        ChatBoxView: {
-            shouldShowOnTextMessage () {
-                const { _converse } = this.__super__;
-                if (_converse.isUniView()) {
-                    return false;
-                } else {
-                    return this.__super__.shouldShowOnTextMessage.apply(this, arguments);
-                }
-            },
-
-            _show (focus) {
-                /* We only have one chat visible at any one
-                 * time. So before opening a chat, we make sure all other
-                 * chats are hidden.
-                 */
-                const { _converse } = this.__super__;
-                if (_converse.isUniView()) {
-                    _.each(this.__super__._converse.chatboxviews.xget(this.model.get('id')), hideChat);
-                    u.safeSave(this.model, {'hidden': false});
-                }
-                return this.__super__._show.apply(this, arguments);
-            }
-        },
-
-        ChatRoomView: {
-            show (focus) {
-                const { _converse } = this.__super__;
-                if (_converse.isUniView()) {
-                    _.each(this.__super__._converse.chatboxviews.xget(this.model.get('id')), hideChat);
-                    u.safeSave(this.model, {'hidden': false});
-                }
-                return this.__super__.show.apply(this, arguments);
-            }
+    enabled (_converse) {
+        return _converse.singleton;
+    },
+
+    initialize () {
+        /* The initialize function gets called as soon as the plugin is
+         * loaded by converse.js's plugin machinery.
+         */
+        this._converse.api.settings.update({
+            'allow_logout': false,          // No point in logging out when we have auto_login as true.
+            'allow_muc_invitations': false, // Doesn't make sense to allow because only
+                                            // roster contacts can be invited
+            'hide_muc_server': true
+        });
+        const { _converse } = this;
+        if (!_.isArray(_converse.auto_join_rooms) && !_.isArray(_converse.auto_join_private_chats)) {
+            throw new Error("converse-singleton: auto_join_rooms must be an Array");
+        }
+        if (_converse.auto_join_rooms.length > 1 || _converse.auto_join_private_chats.length > 1) {
+            throw new Error("It doesn't make sense to have singleton set to true and " +
+                "auto_join_rooms or auto_join_private_chats set to more then one, " +
+                "since only one chat room may be open at any time.");
         }
     }
 });
-

+ 107 - 0
src/converse-uniview.js

@@ -0,0 +1,107 @@
+// Converse.js
+// http://conversejs.org
+//
+// Copyright (c) 2013-2018, the Converse.js developers
+// Licensed under the Mozilla Public License (MPLv2)
+
+/* converse-uniview
+ * ****************
+ *
+ * A plugin which ensures that only one chat (private or groupchat) is
+ * visible at any one time. All other ongoing chats are hidden and kept in the
+ * background.
+ *
+ * This plugin makes sense in mobile, embedded or fullscreen chat environments
+ * (as configured by the `view_mode` setting).
+ */
+
+import "converse-chatview";
+import converse from "@converse/headless/converse-core";
+
+const { _, Strophe } = converse.env;
+const u = converse.env.utils;
+
+
+function hideChat (view) {
+    if (view.model.get('id') === 'controlbox') { return; }
+    u.safeSave(view.model, {'hidden': true});
+    view.hide();
+}
+
+function visibleChats (_converse) {
+    return _converse.chatboxes
+        .filter(cb => (cb.get('id') !== 'controlbox' && !cb.get('hidden'))).length > 0;
+}
+
+
+converse.plugins.add('converse-uniview', {
+    // It's possible however to make optional dependencies non-optional.
+    // If the setting "strict_plugin_dependencies" is set to true,
+    // an error will be raised if the plugin is not found.
+    dependencies: ['converse-chatboxes', 'converse-muc-views', 'converse-controlbox', 'converse-rosterview'],
+
+    overrides: {
+        // overrides mentioned here will be picked up by converse.js's
+        // plugin architecture they will replace existing methods on the
+        // relevant objects or classes.
+        //
+        // new functions which don't exist yet can also be added.
+        ChatBoxes: {
+            createChatBox (jid, attrs) {
+                /* Make sure new chat boxes are hidden by default. */
+                const { _converse } = this.__super__;
+                if (_converse.isUniView()) {
+                    attrs = attrs || {};
+                    attrs.hidden = true;
+                }
+                return this.__super__.createChatBox.call(this, jid, attrs);
+            }
+        },
+
+        ChatBox: {
+            maybeShow () {
+                const { _converse } = this.__super__;
+                if (_converse.isUniView() && (!this.get('hidden') || !visibleChats(_converse))) {
+                    return this.trigger("show");
+                } else {
+                    return this.__super__.maybeShow.apply(this, arguments);
+                }
+            }
+        },
+
+        ChatBoxView: {
+            shouldShowOnTextMessage () {
+                const { _converse } = this.__super__;
+                if (_converse.isUniView()) {
+                    return false;
+                } else {
+                    return this.__super__.shouldShowOnTextMessage.apply(this, arguments);
+                }
+            },
+
+            _show (focus) {
+                /* We only have one chat visible at any one
+                 * time. So before opening a chat, we make sure all other
+                 * chats are hidden.
+                 */
+                const { _converse } = this.__super__;
+                if (_converse.isUniView()) {
+                    _.each(this.__super__._converse.chatboxviews.xget(this.model.get('id')), hideChat);
+                    u.safeSave(this.model, {'hidden': false});
+                }
+                return this.__super__._show.apply(this, arguments);
+            }
+        },
+
+        ChatRoomView: {
+            show (focus) {
+                const { _converse } = this.__super__;
+                if (_converse.isUniView()) {
+                    _.each(this.__super__._converse.chatboxviews.xget(this.model.get('id')), hideChat);
+                    u.safeSave(this.model, {'hidden': false});
+                }
+                return this.__super__.show.apply(this, arguments);
+            }
+        }
+    }
+});

+ 4 - 3
src/converse.js

@@ -14,7 +14,6 @@ import "converse-bookmark-views";  // Views for XEP-0048 Bookmarks
 import "converse-chatview";        // Renders standalone chat boxes for single user chat
 import "converse-controlbox";      // The control box
 import "converse-dragresize";      // Allows chat boxes to be resized by dragging them
-import "converse-embedded";
 import "converse-fullscreen";
 import "converse-headline";        // Support for headline messages
 import "converse-mam-views";
@@ -26,6 +25,8 @@ import "converse-push";            // XEP-0357 Push Notifications
 import "converse-register";        // XEP-0077 In-band registration
 import "converse-roomslist";       // Show currently open chat rooms
 import "converse-rosterview";
+import "converse-singleton";
+import "converse-uniview";
 /* END: Removable components */
 
 import converse from "@converse/headless/converse-core";
@@ -37,7 +38,6 @@ const WHITELISTED_PLUGINS = [
     'converse-chatview',
     'converse-controlbox',
     'converse-dragresize',
-    'converse-embedded',
     'converse-fullscreen',
     'converse-headline',
     'converse-mam-views',
@@ -52,7 +52,8 @@ const WHITELISTED_PLUGINS = [
     'converse-register',
     'converse-roomslist',
     'converse-rosterview',
-    'converse-singleton'
+    'converse-singleton',
+    'converse-uniview'
 ];
 
 const initialize = converse.initialize;

+ 7 - 3
src/headless/converse-chatboxes.js

@@ -369,10 +369,13 @@ converse.plugins.add('converse-chatboxes', {
             },
 
             validate (attrs, options) {
-                const { _converse } = this.__super__;
                 if (!attrs.jid) {
                     return 'Ignored ChatBox without JID';
                 }
+                const auto_join = _converse.auto_join_private_chats.concat(_converse.auto_join_rooms);
+                if (_converse.singleton && !_.includes(auto_join, attrs.jid)) {
+                    return "Ignored ChatBox that's not being auto joined in singleton mode";
+                }
             },
 
             getDisplayName () {
@@ -938,7 +941,8 @@ converse.plugins.add('converse-chatboxes', {
 
             onChatBoxesFetched (collection) {
                 /* Show chat boxes upon receiving them from sessionStorage */
-                collection.each(chatbox => chatbox.maybeShow());
+                collection.filter(c => !c.isValid()).forEach(c => c.destroy());
+                collection.forEach(c => c.maybeShow());
                 /**
                  * Triggered when a message stanza is been received and processed.
                  * @event _converse#message
@@ -957,7 +961,7 @@ converse.plugins.add('converse-chatboxes', {
                 this.registerMessageHandler();
                 this.fetch({
                     'add': true,
-                    'success': this.onChatBoxesFetched.bind(this)
+                    'success': c => this.onChatBoxesFetched(c)
                 });
             },
 

+ 2 - 1
src/headless/converse-core.js

@@ -221,6 +221,7 @@ _converse.default_settings = {
     rid: undefined,
     root: window.document,
     sid: undefined,
+    singleton: false,
     strict_plugin_dependencies: false,
     trusted: true,
     view_mode: 'overlayed', // Choices are 'overlayed', 'fullscreen', 'mobile'
@@ -337,7 +338,7 @@ function initPlugins() {
     const whitelist = _converse.core_plugins.concat(
         _converse.whitelisted_plugins);
 
-    if (_converse.view_mode === 'embedded') {
+    if (_converse.singleton) {
         _.forEach([ // eslint-disable-line lodash/prefer-map
             "converse-bookmarks",
             "converse-controlbox",

+ 4 - 2
src/templates/chatroom_head.html

@@ -13,9 +13,11 @@
     <p class="chatroom-description">{{o.description}}</p>
 </div>
 <div class="chatbox-buttons row no-gutters">
-    <a class="chatbox-btn close-chatbox-button fa fa-sign-out-alt" title="{{{o.info_close}}}"></a>
+    {[ if (!o._converse.singleton) { ]}
+        <a class="chatbox-btn close-chatbox-button fa fa-sign-out-alt" title="{{{o.info_close}}}"></a>
+    {[ } ]}
     {[ if (o.affiliation == 'owner') { ]}
-    <a class="chatbox-btn configure-chatroom-button fa fa-wrench" title="{{{o.info_configure}}} "></a>
+        <a class="chatbox-btn configure-chatroom-button fa fa-wrench" title="{{{o.info_configure}}} "></a>
     {[ } ]}
     <a class="chatbox-btn show-room-details-modal fa fa-info-circle" title="{{{o.info_details}}}"></a>
 </div>