diff --git a/CHANGES.md b/CHANGES.md index 1659f93b9..581317188 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,8 @@ ## 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 ## 4.1.2 (2019-02-22) @@ -23,7 +25,7 @@ - Bugfix: MUC invite form not appearing - #1369 Don't wrongly interpret message with `subject` as a topic change. - #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 - #1412 MUC moderator commands can be disabled selectively by config - #1413 Fix moderator commands that change affiliation diff --git a/dist/converse.js b/dist/converse.js index 24c482c70..154343208 100644 --- a/dist/converse.js +++ b/dist/converse.js @@ -53303,6 +53303,8 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_3__["default"].plugins _converse.api.settings.update({ 'auto_list_rooms': false, 'muc_disable_moderator_commands': false, + 'muc_domain': undefined, + 'locked_muc_domain': undefined, 'muc_show_join_leave': true, 'roomconfig_whitelist': [], '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) { /* This is part of a hack to get gettext to scan strings to be * 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() { _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); }, 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(), { 'heading_list_chatrooms': __('Query for Groupchats'), 'label_server_address': __('Server address'), '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() { - 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) { @@ -53588,12 +53603,26 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_3__["default"].plugins 'submit form.add-chatroom': 'openChatRoom' }, + initialize() { + _converse.BootstrapModal.prototype.initialize.apply(this, arguments); + + this.model.on('change:muc_domain', this.render, this); + }, + 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(), { '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'), - 'chatroom_placeholder': __('name@conference.example.org'), + 'chatroom_placeholder': placeholder, 'label_join': __('Join') })); }, @@ -53623,7 +53652,17 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_3__["default"].plugins 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(); 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_rooms: [], auto_register_muc_nickname: false, - muc_domain: undefined, muc_history_max_stanzas: undefined, muc_instant_rooms: true, 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")}; 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 += '\n\n'; +'"/>\n \n '; + } ; +__p += '\n \n \n \n \n\n'; return __p }; diff --git a/docs/source/configuration.rst b/docs/source/configuration.rst index c9d6597d1..d278c0092 100644 --- a/docs/source/configuration.rst +++ b/docs/source/configuration.rst @@ -256,7 +256,8 @@ auto_list_rooms * Default: ``false`` 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. @@ -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 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 @@ -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 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 ----------------- @@ -941,11 +955,16 @@ muc_domain * 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. -This setting will override that. +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. + +If you want to restrict MUCs to only this domain, then set `locked_domain`_ to +``true``. muc_history_max_stanzas ----------------------- diff --git a/spec/muc.js b/spec/muc.js index 7baa2ae2d..1e4abff78 100644 --- a/spec/muc.js +++ b/spec/muc.js @@ -3958,13 +3958,19 @@ async function (done, _converse) { test_utils.openControlBox(); - _converse.emit('rosterContactsFetched'); + await test_utils.waitForRoster(_converse, 'current', 0); const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel; roomspanel.el.querySelector('.show-add-muc-modal').click(); test_utils.closeControlBox(_converse); const modal = roomspanel.add_room_modal; 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'); spyOn(_converse.ChatRoom.prototype, 'getRoomFeatures').and.callFake(() => Promise.resolve()); 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(); await test_utils.waitUntil(() => _converse.chatboxes.length); 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(); })); }); @@ -4002,7 +4085,9 @@ // See: http://xmpp.org/extensions/xep-0045.html#disco-rooms 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(); await test_utils.waitUntil(() => _converse.chatboxes.length); expect(sent_stanza.toLocaleString()).toBe( @@ -4010,7 +4095,6 @@ ``+ `` ); - const iq = $iq({ from:'muc.localhost', to:'dummy@localhost/pda', @@ -4029,13 +4113,19 @@ .c('item', { jid:'street@chat.shakespeare.lit', name:'A street'}).nodeTree; _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'); 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"); 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(); 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"); 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( + ``+ + ``+ + `` + ); + 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 () { @@ -4052,9 +4207,6 @@ mock.initConverse( null, ['rosterGroupsFetched'], {'allow_bookmarks': false}, 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(); const roomspanel = _converse.chatboxviews.get('controlbox').roomspanel; diff --git a/src/converse-muc-views.js b/src/converse-muc-views.js index 92242e2b0..ccdaf5144 100644 --- a/src/converse-muc-views.js +++ b/src/converse-muc-views.js @@ -103,6 +103,8 @@ converse.plugins.add('converse-muc-views', { _converse.api.settings.update({ 'auto_list_rooms': false, 'muc_disable_moderator_commands': false, + 'muc_domain': undefined, + 'locked_muc_domain': undefined, 'muc_show_join_leave': true, 'roomconfig_whitelist': [], '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) { /* 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 () { _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); }, toHTML () { + const muc_domain = this.model.get('muc_domain') || _converse.muc_domain; return tpl_list_chatrooms_modal(_.extend(this.model.toJSON(), { 'heading_list_chatrooms': __('Query for Groupchats'), 'label_server_address': __('Server address'), '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 () { - 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) { @@ -384,12 +400,22 @@ converse.plugins.add('converse-muc-views', { 'submit form.add-chatroom': 'openChatRoom' }, + initialize () { + _converse.BootstrapModal.prototype.initialize.apply(this, arguments); + this.model.on('change:muc_domain', this.render, this); + }, + 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(), { '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'), - 'chatroom_placeholder': __('name@conference.example.org'), + 'chatroom_placeholder': placeholder, 'label_join': __('Join'), })); }, @@ -417,7 +443,13 @@ converse.plugins.add('converse-muc-views', { // Make sure defaults apply if no nick is provided. 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(); ev.target.reset(); } diff --git a/src/headless/converse-muc.js b/src/headless/converse-muc.js index cf968f296..f466dafad 100644 --- a/src/headless/converse-muc.js +++ b/src/headless/converse-muc.js @@ -118,7 +118,6 @@ converse.plugins.add('converse-muc', { auto_join_on_invite: false, auto_join_rooms: [], auto_register_muc_nickname: false, - muc_domain: undefined, muc_history_max_stanzas: undefined, muc_instant_rooms: true, muc_nickname_from_jid: false diff --git a/src/templates/list_chatrooms_modal.html b/src/templates/list_chatrooms_modal.html index e90c6b29b..696e0beb5 100644 --- a/src/templates/list_chatrooms_modal.html +++ b/src/templates/list_chatrooms_modal.html @@ -9,13 +9,15 @@