2
0
Эх сурвалжийг харах

Re-add support for `muc_domain` and add `locked_muc_domain`.

updates #1373
JC Brand 6 жил өмнө
parent
commit
d3a4555165

+ 3 - 1
CHANGES.md

@@ -2,6 +2,8 @@
 
 
 ## 4.1.3 (Unreleased)
 ## 4.1.3 (Unreleased)
 
 
+- New config setting [locked_muc_domain](https://conversejs.org/docs/html/configuration.html#locked-muc-domain)
+- #1373: Re-add support for the [muc_domain](https://conversejs.org/docs/html/configuration.html#muc-domain) setting
 - #1437: List of groupchats in modal doesn't scroll
 - #1437: List of groupchats in modal doesn't scroll
 
 
 ## 4.1.2 (2019-02-22)
 ## 4.1.2 (2019-02-22)
@@ -23,7 +25,7 @@
 - Bugfix: MUC invite form not appearing
 - Bugfix: MUC invite form not appearing
 - #1369 Don't wrongly interpret message with `subject` as a topic change.
 - #1369 Don't wrongly interpret message with `subject` as a topic change.
 - #1405 Status of contacts list are not displayed properly
 - #1405 Status of contacts list are not displayed properly
-- #1408 New config option `roomconfig_whitelist`
+- #1408 New config option [roomconfig_whitelist](https://conversejs.org/docs/html/configuration.html#roomconfig-whitelist)
 - #1410 HTTP upload not working if conversations push proxy is used
 - #1410 HTTP upload not working if conversations push proxy is used
 - #1412 MUC moderator commands can be disabled selectively by config
 - #1412 MUC moderator commands can be disabled selectively by config
 - #1413 Fix moderator commands that change affiliation
 - #1413 Fix moderator commands that change affiliation

+ 55 - 12
dist/converse.js

@@ -53303,6 +53303,8 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_3__["default"].plugins
     _converse.api.settings.update({
     _converse.api.settings.update({
       'auto_list_rooms': false,
       'auto_list_rooms': false,
       'muc_disable_moderator_commands': false,
       'muc_disable_moderator_commands': false,
+      'muc_domain': undefined,
+      'locked_muc_domain': undefined,
       'muc_show_join_leave': true,
       'muc_show_join_leave': true,
       'roomconfig_whitelist': [],
       'roomconfig_whitelist': [],
       'visible_toolbar_buttons': {
       'visible_toolbar_buttons': {
@@ -53310,6 +53312,10 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_3__["default"].plugins
       }
       }
     });
     });
 
 
+    if (_converse.locked_muc_domain && !_.isString(_converse.muc_domain)) {
+      throw new Error("Config Error: it makes no sense to set locked_muc_domain " + "to true when muc_domain is not set");
+    }
+
     function ___(str) {
     function ___(str) {
       /* This is part of a hack to get gettext to scan strings to be
       /* This is part of a hack to get gettext to scan strings to be
       * translated. Strings we cannot send to the function above because
       * translated. Strings we cannot send to the function above because
@@ -53459,22 +53465,31 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_3__["default"].plugins
       initialize() {
       initialize() {
         _converse.BootstrapModal.prototype.initialize.apply(this, arguments);
         _converse.BootstrapModal.prototype.initialize.apply(this, arguments);
 
 
+        if (_converse.muc_domain && !this.model.get('muc_domain')) {
+          this.model.save('muc_domain', _converse.muc_domain);
+        }
+
         this.model.on('change:muc_domain', this.onDomainChange, this);
         this.model.on('change:muc_domain', this.onDomainChange, this);
       },
       },
 
 
       toHTML() {
       toHTML() {
+        const muc_domain = this.model.get('muc_domain') || _converse.muc_domain;
+
         return templates_list_chatrooms_modal_html__WEBPACK_IMPORTED_MODULE_19___default()(_.extend(this.model.toJSON(), {
         return templates_list_chatrooms_modal_html__WEBPACK_IMPORTED_MODULE_19___default()(_.extend(this.model.toJSON(), {
           'heading_list_chatrooms': __('Query for Groupchats'),
           'heading_list_chatrooms': __('Query for Groupchats'),
           'label_server_address': __('Server address'),
           'label_server_address': __('Server address'),
           'label_query': __('Show groupchats'),
           'label_query': __('Show groupchats'),
-          'server_placeholder': __('conference.example.org')
+          'show_form': !_converse.locked_muc_domain,
+          'server_placeholder': muc_domain ? muc_domain : __('conference.example.org')
         }));
         }));
       },
       },
 
 
       afterRender() {
       afterRender() {
-        this.el.addEventListener('shown.bs.modal', () => {
-          this.el.querySelector('input[name="server"]').focus();
-        }, false);
+        if (_converse.locked_muc_domain) {
+          this.updateRoomsList();
+        } else {
+          this.el.addEventListener('shown.bs.modal', () => this.el.querySelector('input[name="server"]').focus(), false);
+        }
       },
       },
 
 
       openRoom(ev) {
       openRoom(ev) {
@@ -53588,12 +53603,26 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_3__["default"].plugins
         'submit form.add-chatroom': 'openChatRoom'
         'submit form.add-chatroom': 'openChatRoom'
       },
       },
 
 
+      initialize() {
+        _converse.BootstrapModal.prototype.initialize.apply(this, arguments);
+
+        this.model.on('change:muc_domain', this.render, this);
+      },
+
       toHTML() {
       toHTML() {
+        let placeholder = '';
+
+        if (!_converse.locked_muc_domain) {
+          const muc_domain = this.model.get('muc_domain') || _converse.muc_domain;
+
+          placeholder = muc_domain ? `name@${muc_domain}` : __('name@conference.example.org');
+        }
+
         return templates_add_chatroom_modal_html__WEBPACK_IMPORTED_MODULE_5___default()(_.extend(this.model.toJSON(), {
         return templates_add_chatroom_modal_html__WEBPACK_IMPORTED_MODULE_5___default()(_.extend(this.model.toJSON(), {
           'heading_new_chatroom': __('Enter a new Groupchat'),
           'heading_new_chatroom': __('Enter a new Groupchat'),
-          'label_room_address': __('Groupchat address'),
+          'label_room_address': _converse.muc_domain ? __('Groupchat name') : __('Groupchat address'),
           'label_nickname': __('Optional nickname'),
           'label_nickname': __('Optional nickname'),
-          'chatroom_placeholder': __('name@conference.example.org'),
+          'chatroom_placeholder': placeholder,
           'label_join': __('Join')
           'label_join': __('Join')
         }));
         }));
       },
       },
@@ -53623,7 +53652,17 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_3__["default"].plugins
           data.nick = undefined;
           data.nick = undefined;
         }
         }
 
 
-        _converse.api.rooms.open(data.jid, data);
+        let jid;
+
+        if (_converse.locked_muc_domain || _converse.muc_domain && !u.isValidJID(data.jid)) {
+          jid = `${Strophe.escapeNode(data.jid)}@${_converse.muc_domain}`;
+        } else {
+          jid = data.jid;
+        }
+
+        _converse.api.rooms.open(jid, _.extend(data, {
+          jid
+        }));
 
 
         this.modal.hide();
         this.modal.hide();
         ev.target.reset();
         ev.target.reset();
@@ -66093,7 +66132,6 @@ _converse_core__WEBPACK_IMPORTED_MODULE_6__["default"].plugins.add('converse-muc
       auto_join_on_invite: false,
       auto_join_on_invite: false,
       auto_join_rooms: [],
       auto_join_rooms: [],
       auto_register_muc_nickname: false,
       auto_register_muc_nickname: false,
-      muc_domain: undefined,
       muc_history_max_stanzas: undefined,
       muc_history_max_stanzas: undefined,
       muc_instant_rooms: true,
       muc_instant_rooms: true,
       muc_nickname_from_jid: false
       muc_nickname_from_jid: false
@@ -93667,18 +93705,23 @@ return __p
 
 
 var _ = {escape:__webpack_require__(/*! ./node_modules/lodash/escape.js */ "./node_modules/lodash/escape.js")};
 var _ = {escape:__webpack_require__(/*! ./node_modules/lodash/escape.js */ "./node_modules/lodash/escape.js")};
 module.exports = function(o) {
 module.exports = function(o) {
-var __t, __p = '', __e = _.escape;
+var __t, __p = '', __e = _.escape, __j = Array.prototype.join;
+function print() { __p += __j.call(arguments, '') }
 __p += '<!-- src/templates/list_chatrooms_modal.html -->\n<div class="modal fade" id="list-chatrooms-modal" tabindex="-1" role="dialog" aria-labelledby="list-chatrooms-modal-label" aria-hidden="true">\n    <div class="modal-dialog" role="document">\n        <div class="modal-content">\n            <div class="modal-header">\n                <h5 class="modal-title"\n                    id="list-chatrooms-modal-label">' +
 __p += '<!-- src/templates/list_chatrooms_modal.html -->\n<div class="modal fade" id="list-chatrooms-modal" tabindex="-1" role="dialog" aria-labelledby="list-chatrooms-modal-label" aria-hidden="true">\n    <div class="modal-dialog" role="document">\n        <div class="modal-content">\n            <div class="modal-header">\n                <h5 class="modal-title"\n                    id="list-chatrooms-modal-label">' +
 __e(o.heading_list_chatrooms) +
 __e(o.heading_list_chatrooms) +
-'</h5>\n                <button type="button" class="close" data-dismiss="modal" aria-label="Close">\n                    <span aria-hidden="true">×</span>\n                </button>\n            </div>\n            <div class="modal-body d-flex flex-column">\n                <form class="converse-form list-chatrooms">\n                    <div class="form-group">\n                        <label for="chatroom">' +
+'</h5>\n                <button type="button" class="close" data-dismiss="modal" aria-label="Close">\n                    <span aria-hidden="true">×</span>\n                </button>\n            </div>\n            <div class="modal-body d-flex flex-column">\n                ';
+ if (o.show_form) { ;
+__p += '\n                <form class="converse-form list-chatrooms">\n                    <div class="form-group">\n                        <label for="chatroom">' +
 __e(o.label_server_address) +
 __e(o.label_server_address) +
 ':</label>\n                        <input type="text" value="' +
 ':</label>\n                        <input type="text" value="' +
 __e(o.muc_domain) +
 __e(o.muc_domain) +
 '" required="required" name="server" class="form-control" placeholder="' +
 '" required="required" name="server" class="form-control" placeholder="' +
 __e(o.server_placeholder) +
 __e(o.server_placeholder) +
-'"/>\n                    </div>\n                    <input type="submit" class="btn btn-primary" name="join" value="' +
+'"/>\n                    </div>\n                    <input type="submit" class="btn btn-primary" name="list" value="' +
 __e(o.label_query) +
 __e(o.label_query) +
-'"/>\n                </form>\n                <ul class="available-chatrooms list-group"></ul>\n            </div>\n        </div>\n    </div>\n</div>\n';
+'"/>\n                </form>\n                ';
+ } ;
+__p += '\n                <ul class="available-chatrooms list-group"></ul>\n            </div>\n        </div>\n    </div>\n</div>\n';
 return __p
 return __p
 };
 };
 
 

+ 24 - 5
docs/source/configuration.rst

@@ -256,7 +256,8 @@ auto_list_rooms
 * Default:  ``false``
 * Default:  ``false``
 
 
 If true, and the XMPP server on which the current user is logged in supports
 If true, and the XMPP server on which the current user is logged in supports
-multi-user chat, then a list of rooms on that server will be fetched.
+multi-user chat, then a list of rooms on that server will be fetched in the
+"Query for Groupchats" modal.
 
 
 Not recommended for servers with lots of chatrooms.
 Not recommended for servers with lots of chatrooms.
 
 
@@ -264,6 +265,10 @@ For each room on the server a query is made to fetch further details (e.g.
 features, number of occupants etc.), so on servers with many rooms this
 features, number of occupants etc.), so on servers with many rooms this
 option will create lots of extra connection traffic.
 option will create lots of extra connection traffic.
 
 
+If the `muc_domain`_ is locked with the `locked_muc_domain`_ setting, then
+rooms will automatically be fetched in the "Query for Groupchats" modal,
+regardless of the value of this setting.
+
 .. _`auto_login`:
 .. _`auto_login`:
 
 
 auto_login
 auto_login
@@ -869,6 +874,15 @@ For example, if ``locked_domain`` is set to ``example.org``, then the user
 Additionally, only users registered on the ``example.org`` host can log in, no
 Additionally, only users registered on the ``example.org`` host can log in, no
 other users are allowed to log in.
 other users are allowed to log in.
 
 
+locked_muc_domain
+-----------------
+
+* Default: ``false``
+
+This setting allows you to restrict the multi-user chat (MUC) domain to only the value
+specified in `muc_domain`_.
+
+
 message_archiving
 message_archiving
 -----------------
 -----------------
 
 
@@ -941,11 +955,16 @@ muc_domain
 
 
 * Default:  ``undefined``
 * Default:  ``undefined``
 
 
-The MUC (multi-user chat) domain that should be used. By default Converse
-will attempt to get the MUC domain from the XMPP host of the currently logged in
-user.
+The default MUC (multi-user chat) domain that should be used.
+
+When setting this value, users can only enter the name when opening a new MUC,
+and don't have to add the whole address (i.e. including the domain part).
+
+Users can however still enter the domain and they can still open MUCs with
+other domains.
 
 
-This setting will override that.
+If you want to restrict MUCs to only this domain, then set `locked_domain`_ to
+``true``.
 
 
 muc_history_max_stanzas
 muc_history_max_stanzas
 -----------------------
 -----------------------

+ 159 - 7
spec/muc.js

@@ -3958,13 +3958,19 @@
                     async function (done, _converse) {
                     async function (done, _converse) {
 
 
                 test_utils.openControlBox();
                 test_utils.openControlBox();
-                _converse.emit('rosterContactsFetched');
+                await test_utils.waitForRoster(_converse, 'current', 0);
 
 
                 const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel;
                 const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel;
                 roomspanel.el.querySelector('.show-add-muc-modal').click();
                 roomspanel.el.querySelector('.show-add-muc-modal').click();
                 test_utils.closeControlBox(_converse);
                 test_utils.closeControlBox(_converse);
                 const modal = roomspanel.add_room_modal;
                 const modal = roomspanel.add_room_modal;
                 await test_utils.waitUntil(() => u.isVisible(modal.el), 1000)
                 await test_utils.waitUntil(() => u.isVisible(modal.el), 1000)
+
+                let label_name = modal.el.querySelector('label[for="chatroom"]');
+                expect(label_name.textContent).toBe('Groupchat address:');
+                let name_input = modal.el.querySelector('input[name="chatroom"]');
+                expect(name_input.placeholder).toBe('name@conference.example.org');
+
                 expect(modal.el.querySelector('.modal-title').textContent).toBe('Enter a new Groupchat');
                 expect(modal.el.querySelector('.modal-title').textContent).toBe('Enter a new Groupchat');
                 spyOn(_converse.ChatRoom.prototype, 'getRoomFeatures').and.callFake(() => Promise.resolve());
                 spyOn(_converse.ChatRoom.prototype, 'getRoomFeatures').and.callFake(() => Promise.resolve());
                 roomspanel.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
                 roomspanel.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
@@ -3972,6 +3978,83 @@
                 modal.el.querySelector('form input[type="submit"]').click();
                 modal.el.querySelector('form input[type="submit"]').click();
                 await test_utils.waitUntil(() => _converse.chatboxes.length);
                 await test_utils.waitUntil(() => _converse.chatboxes.length);
                 await test_utils.waitUntil(() => sizzle('.chatroom', _converse.el).filter(u.isVisible).length === 1);
                 await test_utils.waitUntil(() => sizzle('.chatroom', _converse.el).filter(u.isVisible).length === 1);
+
+                roomspanel.model.set('muc_domain', 'muc.example.org');
+                roomspanel.el.querySelector('.show-add-muc-modal').click();
+                label_name = modal.el.querySelector('label[for="chatroom"]');
+                expect(label_name.textContent).toBe('Groupchat address:');
+                name_input = modal.el.querySelector('input[name="chatroom"]');
+                expect(name_input.placeholder).toBe('name@muc.example.org');
+                done();
+            }));
+
+            it("doesn't require the domain when muc_domain is set",
+                mock.initConverse(
+                    null, ['rosterGroupsFetched', 'chatBoxesFetched'], {'muc_domain': 'muc.example.org'},
+                    async function (done, _converse) {
+
+                test_utils.openControlBox();
+                const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel;
+                roomspanel.el.querySelector('.show-add-muc-modal').click();
+                const modal = roomspanel.add_room_modal;
+                await test_utils.waitUntil(() => u.isVisible(modal.el), 1000)
+                expect(modal.el.querySelector('.modal-title').textContent).toBe('Enter a new Groupchat');
+                spyOn(_converse.ChatRoom.prototype, 'getRoomFeatures').and.callFake(() => Promise.resolve());
+                roomspanel.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
+                const label_name = modal.el.querySelector('label[for="chatroom"]');
+                expect(label_name.textContent).toBe('Groupchat name:');
+                let name_input = modal.el.querySelector('input[name="chatroom"]');
+                expect(name_input.placeholder).toBe('name@muc.example.org');
+                name_input.value = 'lounge';
+                modal.el.querySelector('form input[type="submit"]').click();
+                await test_utils.waitUntil(() => _converse.chatboxes.length);
+                await test_utils.waitUntil(() => sizzle('.chatroom', _converse.el).filter(u.isVisible).length === 1);
+                expect(_.includes(_converse.chatboxes.models.map(m => m.get('id')), 'lounge@muc.example.org')).toBe(true);
+
+                // However, you can still open MUCs with different domains
+                roomspanel.el.querySelector('.show-add-muc-modal').click();
+                await test_utils.waitUntil(() => u.isVisible(modal.el), 1000);
+                name_input = modal.el.querySelector('input[name="chatroom"]');
+                name_input.value = 'lounge@conference.example.org';
+                modal.el.querySelector('form input[type="submit"]').click();
+                await test_utils.waitUntil(() => _converse.chatboxes.models.filter(c => c.get('type') === 'chatroom').length === 2);
+                await test_utils.waitUntil(() => sizzle('.chatroom', _converse.el).filter(u.isVisible).length === 2);
+                expect(_.includes(_converse.chatboxes.models.map(m => m.get('id')), 'lounge@conference.example.org')).toBe(true);
+                done();
+            }));
+
+            it("only uses the muc_domain is locked_muc_domain is true",
+                mock.initConverse(
+                    null, ['rosterGroupsFetched', 'chatBoxesFetched'], {'muc_domain': 'muc.example.org', 'locked_muc_domain': true},
+                    async function (done, _converse) {
+
+                test_utils.openControlBox();
+                const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel;
+                roomspanel.el.querySelector('.show-add-muc-modal').click();
+                const modal = roomspanel.add_room_modal;
+                await test_utils.waitUntil(() => u.isVisible(modal.el), 1000)
+                expect(modal.el.querySelector('.modal-title').textContent).toBe('Enter a new Groupchat');
+                spyOn(_converse.ChatRoom.prototype, 'getRoomFeatures').and.callFake(() => Promise.resolve());
+                roomspanel.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
+                const label_name = modal.el.querySelector('label[for="chatroom"]');
+                expect(label_name.textContent).toBe('Groupchat name:');
+                let name_input = modal.el.querySelector('input[name="chatroom"]');
+                expect(name_input.placeholder).toBe('');
+                name_input.value = 'lounge';
+                modal.el.querySelector('form input[type="submit"]').click();
+                await test_utils.waitUntil(() => _converse.chatboxes.length);
+                await test_utils.waitUntil(() => sizzle('.chatroom', _converse.el).filter(u.isVisible).length === 1);
+                expect(_.includes(_converse.chatboxes.models.map(m => m.get('id')), 'lounge@muc.example.org')).toBe(true);
+
+                // However, you can still open MUCs with different domains
+                roomspanel.el.querySelector('.show-add-muc-modal').click();
+                await test_utils.waitUntil(() => u.isVisible(modal.el), 1000);
+                name_input = modal.el.querySelector('input[name="chatroom"]');
+                name_input.value = 'lounge@conference';
+                modal.el.querySelector('form input[type="submit"]').click();
+                await test_utils.waitUntil(() => _converse.chatboxes.models.filter(c => c.get('type') === 'chatroom').length === 2);
+                await test_utils.waitUntil(() => sizzle('.chatroom', _converse.el).filter(u.isVisible).length === 2);
+                expect(_.includes(_converse.chatboxes.models.map(m => m.get('id')), 'lounge\\40conference@muc.example.org')).toBe(true);
                 done();
                 done();
             }));
             }));
         });
         });
@@ -4002,7 +4085,9 @@
                 // See: http://xmpp.org/extensions/xep-0045.html#disco-rooms
                 // See: http://xmpp.org/extensions/xep-0045.html#disco-rooms
                 expect(modal.el.querySelectorAll('.available-chatrooms li').length).toBe(0);
                 expect(modal.el.querySelectorAll('.available-chatrooms li').length).toBe(0);
 
 
-                const input = modal.el.querySelector('input[name="server"]').value = 'chat.shakespear.lit';
+                const server_input = modal.el.querySelector('input[name="server"]');
+                expect(server_input.placeholder).toBe('conference.example.org');
+                const input = server_input.value = 'chat.shakespear.lit';
                 modal.el.querySelector('input[type="submit"]').click();
                 modal.el.querySelector('input[type="submit"]').click();
                 await test_utils.waitUntil(() => _converse.chatboxes.length);
                 await test_utils.waitUntil(() => _converse.chatboxes.length);
                 expect(sent_stanza.toLocaleString()).toBe(
                 expect(sent_stanza.toLocaleString()).toBe(
@@ -4010,7 +4095,6 @@
                         `<query xmlns="http://jabber.org/protocol/disco#items"/>`+
                         `<query xmlns="http://jabber.org/protocol/disco#items"/>`+
                     `</iq>`
                     `</iq>`
                 );
                 );
-
                 const iq = $iq({
                 const iq = $iq({
                     from:'muc.localhost',
                     from:'muc.localhost',
                     to:'dummy@localhost/pda',
                     to:'dummy@localhost/pda',
@@ -4029,13 +4113,19 @@
                 .c('item', { jid:'street@chat.shakespeare.lit', name:'A street'}).nodeTree;
                 .c('item', { jid:'street@chat.shakespeare.lit', name:'A street'}).nodeTree;
                 _converse.connection._dataRecv(test_utils.createRequest(iq));
                 _converse.connection._dataRecv(test_utils.createRequest(iq));
 
 
-                await test_utils.waitUntil(() => modal.el.querySelectorAll('.available-chatrooms li').length === 5);
+                await test_utils.waitUntil(() => modal.el.querySelectorAll('.available-chatrooms li').length === 11);
                 const rooms = modal.el.querySelectorAll('.available-chatrooms li');
                 const rooms = modal.el.querySelectorAll('.available-chatrooms li');
                 expect(rooms[0].textContent.trim()).toBe("Groupchats found:");
                 expect(rooms[0].textContent.trim()).toBe("Groupchats found:");
                 expect(rooms[1].textContent.trim()).toBe("A Lonely Heath");
                 expect(rooms[1].textContent.trim()).toBe("A Lonely Heath");
                 expect(rooms[2].textContent.trim()).toBe("A Dark Cave");
                 expect(rooms[2].textContent.trim()).toBe("A Dark Cave");
                 expect(rooms[3].textContent.trim()).toBe("The Palace");
                 expect(rooms[3].textContent.trim()).toBe("The Palace");
                 expect(rooms[4].textContent.trim()).toBe("Macbeth's Castle");
                 expect(rooms[4].textContent.trim()).toBe("Macbeth's Castle");
+                expect(rooms[5].textContent.trim()).toBe('Capulet\'s Orchard');
+                expect(rooms[6].textContent.trim()).toBe('Friar Laurence\'s cell');
+                expect(rooms[7].textContent.trim()).toBe('Hall in Capulet\'s house');
+                expect(rooms[8].textContent.trim()).toBe('Juliet\'s chamber');
+                expect(rooms[9].textContent.trim()).toBe('A public place');
+                expect(rooms[10].textContent.trim()).toBe('A street');
 
 
                 rooms[4].querySelector('.open-room').click();
                 rooms[4].querySelector('.open-room').click();
                 await test_utils.waitUntil(() => _converse.chatboxes.length > 1);
                 await test_utils.waitUntil(() => _converse.chatboxes.length > 1);
@@ -4044,6 +4134,71 @@
                 expect(view.el.querySelector('.chat-head-chatroom').textContent.trim()).toBe("Macbeth's Castle");
                 expect(view.el.querySelector('.chat-head-chatroom').textContent.trim()).toBe("Macbeth's Castle");
                 done();
                 done();
             }));
             }));
+
+            it("is pre-filled with the muc_domain",
+                mock.initConverse(
+                    null, ['rosterGroupsFetched', 'chatBoxesFetched'],
+                    {'muc_domain': 'muc.example.org'},
+                    async function (done, _converse) {
+
+                test_utils.openControlBox();
+                const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel;
+                roomspanel.el.querySelector('.show-list-muc-modal').click();
+                test_utils.closeControlBox(_converse);
+                const modal = roomspanel.list_rooms_modal;
+                await test_utils.waitUntil(() => u.isVisible(modal.el), 1000);
+                const server_input = modal.el.querySelector('input[name="server"]');
+                expect(server_input.value).toBe('muc.example.org');
+                done();
+            }));
+
+            it("doesn't let you set the MUC domain if it's locked",
+                mock.initConverse(
+                    null, ['rosterGroupsFetched', 'chatBoxesFetched'],
+                    {'muc_domain': 'chat.shakespeare.lit', 'locked_muc_domain': true},
+                    async function (done, _converse) {
+
+                test_utils.openControlBox();
+                const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel;
+                roomspanel.el.querySelector('.show-list-muc-modal').click();
+                test_utils.closeControlBox(_converse);
+                const modal = roomspanel.list_rooms_modal;
+                await test_utils.waitUntil(() => u.isVisible(modal.el), 1000);
+                spyOn(_converse.ChatRoom.prototype, 'getRoomFeatures').and.callFake(() => Promise.resolve());
+                roomspanel.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
+
+                expect(modal.el.querySelector('input[name="server"]')).toBe(null);
+                expect(modal.el.querySelector('input[type="submit"]')).toBe(null);
+                await test_utils.waitUntil(() => _converse.chatboxes.length);
+                const sent_stanza = await test_utils.waitUntil(() =>
+                    _converse.connection.sent_stanzas.filter(
+                        s => sizzle(`query[xmlns="http://jabber.org/protocol/disco#items"]`, s).length).pop()
+                );
+                expect(Strophe.serialize(sent_stanza)).toBe(
+                    `<iq from="dummy@localhost/resource" id="${sent_stanza.getAttribute('id')}" `+
+                            `to="chat.shakespeare.lit" type="get" xmlns="jabber:client">`+
+                        `<query xmlns="http://jabber.org/protocol/disco#items"/>`+
+                    `</iq>`
+                );
+                const iq = $iq({
+                    from:'muc.localhost',
+                    to:'dummy@localhost/pda',
+                    id: sent_stanza.getAttribute('id'),
+                    type:'result'
+                }).c('query')
+                .c('item', { jid:'heath@chat.shakespeare.lit', name:'A Lonely Heath'}).up()
+                .c('item', { jid:'coven@chat.shakespeare.lit', name:'A Dark Cave'}).up()
+                .c('item', { jid:'forres@chat.shakespeare.lit', name:'The Palace'}).up()
+                _converse.connection._dataRecv(test_utils.createRequest(iq));
+
+                await test_utils.waitUntil(() => modal.el.querySelectorAll('.available-chatrooms li').length === 4);
+                const rooms = modal.el.querySelectorAll('.available-chatrooms li');
+                expect(rooms[0].textContent.trim()).toBe("Groupchats found:");
+                expect(rooms[1].textContent.trim()).toBe("A Lonely Heath");
+                expect(rooms[2].textContent.trim()).toBe("A Dark Cave");
+                expect(rooms[3].textContent.trim()).toBe("The Palace");
+                done();
+            }));
         });
         });
 
 
         describe("The \"Groupchats\" section", function () {
         describe("The \"Groupchats\" section", function () {
@@ -4052,9 +4207,6 @@
                 mock.initConverse(
                 mock.initConverse(
                     null, ['rosterGroupsFetched'], {'allow_bookmarks': false},
                     null, ['rosterGroupsFetched'], {'allow_bookmarks': false},
                     async function (done, _converse) {
                     async function (done, _converse) {
-                // XXX: we set `allow_bookmarks` to false, so that the groupchats
-                // list gets rendered. Otherwise we would have to mock
-                // the bookmark stanza exchange.
 
 
                 test_utils.openControlBox();
                 test_utils.openControlBox();
                 const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel;
                 const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel;

+ 39 - 7
src/converse-muc-views.js

@@ -103,6 +103,8 @@ converse.plugins.add('converse-muc-views', {
         _converse.api.settings.update({
         _converse.api.settings.update({
             'auto_list_rooms': false,
             'auto_list_rooms': false,
             'muc_disable_moderator_commands': false,
             'muc_disable_moderator_commands': false,
+            'muc_domain': undefined,
+            'locked_muc_domain': undefined,
             'muc_show_join_leave': true,
             'muc_show_join_leave': true,
             'roomconfig_whitelist': [],
             'roomconfig_whitelist': [],
             'visible_toolbar_buttons': {
             'visible_toolbar_buttons': {
@@ -110,6 +112,10 @@ converse.plugins.add('converse-muc-views', {
             }
             }
         });
         });
 
 
+        if (_converse.locked_muc_domain && !_.isString(_converse.muc_domain)) {
+            throw new Error("Config Error: it makes no sense to set locked_muc_domain "+
+                            "to true when muc_domain is not set");
+        }
 
 
         function ___ (str) {
         function ___ (str) {
             /* This is part of a hack to get gettext to scan strings to be
             /* This is part of a hack to get gettext to scan strings to be
@@ -266,22 +272,32 @@ converse.plugins.add('converse-muc-views', {
 
 
             initialize () {
             initialize () {
                 _converse.BootstrapModal.prototype.initialize.apply(this, arguments);
                 _converse.BootstrapModal.prototype.initialize.apply(this, arguments);
+                if (_converse.muc_domain && !this.model.get('muc_domain')) {
+                    this.model.save('muc_domain', _converse.muc_domain);
+                }
                 this.model.on('change:muc_domain', this.onDomainChange, this);
                 this.model.on('change:muc_domain', this.onDomainChange, this);
             },
             },
 
 
             toHTML () {
             toHTML () {
+                const muc_domain = this.model.get('muc_domain') || _converse.muc_domain;
                 return tpl_list_chatrooms_modal(_.extend(this.model.toJSON(), {
                 return tpl_list_chatrooms_modal(_.extend(this.model.toJSON(), {
                     'heading_list_chatrooms': __('Query for Groupchats'),
                     'heading_list_chatrooms': __('Query for Groupchats'),
                     'label_server_address': __('Server address'),
                     'label_server_address': __('Server address'),
                     'label_query': __('Show groupchats'),
                     'label_query': __('Show groupchats'),
-                    'server_placeholder': __('conference.example.org')
+                    'show_form': !_converse.locked_muc_domain,
+                    'server_placeholder': muc_domain ? muc_domain : __('conference.example.org')
                 }));
                 }));
             },
             },
 
 
             afterRender () {
             afterRender () {
-                this.el.addEventListener('shown.bs.modal', () => {
-                    this.el.querySelector('input[name="server"]').focus();
-                }, false);
+                if (_converse.locked_muc_domain) {
+                    this.updateRoomsList();
+                } else {
+                    this.el.addEventListener('shown.bs.modal',
+                        () => this.el.querySelector('input[name="server"]').focus(),
+                        false
+                    );
+                }
             },
             },
 
 
             openRoom (ev) {
             openRoom (ev) {
@@ -384,12 +400,22 @@ converse.plugins.add('converse-muc-views', {
                 'submit form.add-chatroom': 'openChatRoom'
                 'submit form.add-chatroom': 'openChatRoom'
             },
             },
 
 
+            initialize () {
+                _converse.BootstrapModal.prototype.initialize.apply(this, arguments);
+                this.model.on('change:muc_domain', this.render, this);
+            },
+
             toHTML () {
             toHTML () {
+                let placeholder = '';
+                if (!_converse.locked_muc_domain) {
+                    const muc_domain = this.model.get('muc_domain') || _converse.muc_domain;
+                    placeholder = muc_domain ? `name@${muc_domain}` : __('name@conference.example.org');
+                }
                 return tpl_add_chatroom_modal(_.extend(this.model.toJSON(), {
                 return tpl_add_chatroom_modal(_.extend(this.model.toJSON(), {
                     'heading_new_chatroom': __('Enter a new Groupchat'),
                     'heading_new_chatroom': __('Enter a new Groupchat'),
-                    'label_room_address': __('Groupchat address'),
+                    'label_room_address': _converse.muc_domain ? __('Groupchat name') :  __('Groupchat address'),
                     'label_nickname': __('Optional nickname'),
                     'label_nickname': __('Optional nickname'),
-                    'chatroom_placeholder': __('name@conference.example.org'),
+                    'chatroom_placeholder': placeholder,
                     'label_join': __('Join'),
                     'label_join': __('Join'),
                 }));
                 }));
             },
             },
@@ -417,7 +443,13 @@ converse.plugins.add('converse-muc-views', {
                     // Make sure defaults apply if no nick is provided.
                     // Make sure defaults apply if no nick is provided.
                     data.nick = undefined;
                     data.nick = undefined;
                 }
                 }
-                _converse.api.rooms.open(data.jid, data);
+                let jid;
+                if (_converse.locked_muc_domain || (_converse.muc_domain && !u.isValidJID(data.jid))) {
+                    jid = `${Strophe.escapeNode(data.jid)}@${_converse.muc_domain}`;
+                } else {
+                    jid = data.jid
+                }
+                _converse.api.rooms.open(jid, _.extend(data, {jid}));
                 this.modal.hide();
                 this.modal.hide();
                 ev.target.reset();
                 ev.target.reset();
             }
             }

+ 0 - 1
src/headless/converse-muc.js

@@ -118,7 +118,6 @@ converse.plugins.add('converse-muc', {
             auto_join_on_invite: false,
             auto_join_on_invite: false,
             auto_join_rooms: [],
             auto_join_rooms: [],
             auto_register_muc_nickname: false,
             auto_register_muc_nickname: false,
-            muc_domain: undefined,
             muc_history_max_stanzas: undefined,
             muc_history_max_stanzas: undefined,
             muc_instant_rooms: true,
             muc_instant_rooms: true,
             muc_nickname_from_jid: false
             muc_nickname_from_jid: false

+ 3 - 1
src/templates/list_chatrooms_modal.html

@@ -9,13 +9,15 @@
                 </button>
                 </button>
             </div>
             </div>
             <div class="modal-body d-flex flex-column">
             <div class="modal-body d-flex flex-column">
+                {[ if (o.show_form) { ]}
                 <form class="converse-form list-chatrooms">
                 <form class="converse-form list-chatrooms">
                     <div class="form-group">
                     <div class="form-group">
                         <label for="chatroom">{{{o.label_server_address}}}:</label>
                         <label for="chatroom">{{{o.label_server_address}}}:</label>
                         <input type="text" value="{{{o.muc_domain}}}" required="required" name="server" class="form-control" placeholder="{{{o.server_placeholder}}}"/>
                         <input type="text" value="{{{o.muc_domain}}}" required="required" name="server" class="form-control" placeholder="{{{o.server_placeholder}}}"/>
                     </div>
                     </div>
-                    <input type="submit" class="btn btn-primary" name="join" value="{{{o.label_query}}}"/>
+                    <input type="submit" class="btn btn-primary" name="list" value="{{{o.label_query}}}"/>
                 </form>
                 </form>
+                {[ } ]}
                 <ul class="available-chatrooms list-group"></ul>
                 <ul class="available-chatrooms list-group"></ul>
             </div>
             </div>
         </div>
         </div>