diff --git a/CHANGES.md b/CHANGES.md index ad2e96f9a5..255d00628f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,9 +10,10 @@ To add custom emojis, edit the `emojis.json` file. - Refactor some presence and status handling code from `converse-core` into `@converse/headless/converse-status`. - New API [\_converse.api.headlines](https://conversejs.org/docs/html/api/-_converse.api.headlines.html#.get) -- New config option [muc-mention-autocomplete-filter](https://conversejs.org/docs/html/configuration.html#muc-mention-autocomplete-filter) -- New config option [muc-mention-autocomplete-show-avatar](https://conversejs.org/docs/html/configuration.html#muc-mention-autocomplete-show-avatar) - New config option [allow_message_retraction](https://conversejs.org/docs/html/configuration.html#allow-message-retraction) +- New config option [muc-show-logs-before-join](https://conversejs.org/docs/html/configuration.html#muc-show-logs-before-join) +_ New config option [muc_mention_autocomplete_filter](https://conversejs.org/docs/html/configuration.html#muc_mention_autocomplete_filter) +_ New config option [muc_mention_autocomplete_show_avatar](https://conversejs.org/docs/html/configuration.html#muc_mention_autocomplete_show_avatar) - #129: Add support for XEP-0156: Disovering Alternative XMPP Connection Methods. Only XML is supported for now. - #1105: Preliminary support for storing persistent data in IndexedDB instead of localStorage diff --git a/sass/_chatrooms.scss b/sass/_chatrooms.scss index 82c2f60e1a..c464af1c17 100644 --- a/sass/_chatrooms.scss +++ b/sass/_chatrooms.scss @@ -316,6 +316,18 @@ } } + .empty-history-feedback { + position: relative; + height: 100%; + color: var(--text-color-lighten-15-percent); + span { + width: 100%; + text-align: center; + position: absolute; + margin-top: 50%; + } + } + .muc-bottom-panel { border-top: var(--message-input-border-top); height: 3em; @@ -324,6 +336,20 @@ font-size: var(--font-size-small); background-color: var(--chatroom-head-color); color: white; + + &.muc-bottom-panel--nickname { + padding: 0; + height: 16em; + + .chatroom-form-container { + border-radius: 0 !important; + + .chatroom-form { + padding-top: 2em; + padding-bottom: 0; + } + } + } } .sendXMPPMessage { diff --git a/spec/mam.js b/spec/mam.js index 6b1dce5129..1550dfd9b1 100644 --- a/spec/mam.js +++ b/spec/mam.js @@ -196,7 +196,7 @@ `); _converse.connection._dataRecv(test_utils.createRequest(result)); await u.waitUntil(() => view.model.messages.length === 5); - expect(view.model.fetchArchivedMessages.calls.count()).toBe(3); + expect(view.model.fetchArchivedMessages.calls.count()).toBe(5); const msg_els = view.content.querySelectorAll('.chat-msg__text'); expect(Array.from(msg_els).map(e => e.textContent).join(' ')).toBe("2nd Message 3rd Message 4th Message 5th Message 6th Message"); done(); diff --git a/spec/muc.js b/spec/muc.js index 6ada69462d..844c3e9060 100644 --- a/spec/muc.js +++ b/spec/muc.js @@ -1547,7 +1547,7 @@ expect(presencebroadcast.length).toBe(1); presencebroadcast[0].value = ['moderator']; - view.el.querySelector('input[type="submit"]').click(); + view.el.querySelector('.chatroom-form input[type="submit"]').click(); const sent_stanza = sent_IQ.nodeTree; expect(sent_stanza.querySelector('field[var="muc#roomconfig_membersonly"] value').textContent.trim()).toBe('1'); @@ -2578,7 +2578,7 @@ expect(el.textContent.trim()).toBe("Configuration for room@conference.example.org"); sizzle('[name="muc#roomconfig_membersonly"]', chatroomview.el).pop().click(); sizzle('[name="muc#roomconfig_roomname"]', chatroomview.el).pop().value = "New room name" - chatroomview.el.querySelector('.btn-primary').click(); + chatroomview.el.querySelector('.chatroom-form input[type="submit"]').click(); iq = await u.waitUntil(() => _.filter(IQs, iq => u.matchesSelector(iq, `iq[to="${jid}"][type="set"]`)).pop()); const result = $iq({ diff --git a/spec/muc_messages.js b/spec/muc_messages.js index 0b106dfbcd..b66b106a73 100644 --- a/spec/muc_messages.js +++ b/spec/muc_messages.js @@ -979,7 +979,7 @@ await u.waitUntil(() => view.el.querySelector('.chat-msg__text').textContent === 'hello z3r0 gibson sw0rdf1sh, how are you?', 500); - const correction = _converse.connection.send.calls.all()[2].args[0]; + const correction = _converse.connection.send.calls.all()[3].args[0]; expect(correction.toLocaleString()) .toBe(` (this.auto_completing = false)); }, + /** + * Get the nickname value from the form and then join the groupchat with it. + * @private + * @method _converse.ChatRoomView#submitNickname + * @param { Event } + */ + submitNickname (ev) { + ev.preventDefault(); + const nick = ev.target.nick.value.trim(); + if (nick) { + this.model.join(nick); + this.model.set({'nickname': nick}); + } + }, + onKeyDown (ev) { if (this.mention_auto_complete.onKeyDown(ev)) { return; @@ -997,9 +1020,12 @@ converse.plugins.add('converse-muc-views', { } }, + /** + * Returns the groupchat heading HTML to be rendered. + * @private + * @method _converse.ChatRoomView#generateHeadingHTML + */ generateHeadingHTML () { - /* Returns the heading HTML to be rendered. - */ return tpl_chatroom_head( Object.assign(this.model.toJSON(), { 'isOwner': this.model.getOwnAffiliation() === 'owner', @@ -1028,7 +1054,7 @@ converse.plugins.add('converse-muc-views', { onConnectionStatusChanged () { const conn_status = this.model.get('connection_status'); if (conn_status === converse.ROOMSTATUS.NICKNAME_REQUIRED) { - this.renderNicknameButton(); + this.renderNicknameForm(); } else if (conn_status === converse.ROOMSTATUS.PASSWORD_REQUIRED) { this.renderPasswordForm(); } else if (conn_status === converse.ROOMSTATUS.CONNECTING) { @@ -1083,10 +1109,13 @@ converse.plugins.add('converse-muc-views', { } }, + /** + * Show or hide the right sidebar containing the chat + * occupants (and the invite widget). + * @private + * @method _converse.ChatRoomView#hideOccupants + */ hideOccupants (ev) { - /* Show or hide the right sidebar containing the chat - * occupants (and the invite widget). - */ if (ev) { ev.preventDefault(); ev.stopPropagation(); @@ -1095,10 +1124,13 @@ converse.plugins.add('converse-muc-views', { this.scrollDown(); }, + /** + * Show or hide the right sidebar containing the chat + * occupants (and the invite widget). + * @private + * @method _converse.ChatRoomView#toggleOccupants + */ toggleOccupants (ev) { - /* Show or hide the right sidebar containing the chat - * occupants (and the invite widget). - */ if (ev) { ev.preventDefault(); ev.stopPropagation(); @@ -1107,13 +1139,6 @@ converse.plugins.add('converse-muc-views', { this.scrollDown(); }, - onOccupantClicked (ev) { - /* When an occupant is clicked, insert their nickname into - * the chat textarea input. - */ - this.insertIntoTextArea(ev.target.textContent); - }, - verifyRoles (roles, occupant, show_error=true) { if (!Array.isArray(roles)) { throw new TypeError('roles must be an Array'); @@ -1436,6 +1461,34 @@ converse.plugins.add('converse-muc-views', { u.showElement(this.config_form.el); }, + /** + * Renders a form which allows the user to choose theirnickname. + * @private + * @method _converse.ChatRoomView#renderNicknameForm + */ + renderNicknameForm () { + const heading = _converse.muc_show_logs_before_join ? + __('Choose a nickname to enter') : + __('Please choose your nickname'); + + const html = tpl_chatroom_nickname_form({ + heading, + 'label_nickname': __('Nickname'), + 'label_join': __('Enter groupchat'), + 'nickname': this.model.get('nickname') + }); + if (_converse.muc_show_logs_before_join) { + const container = this.el.querySelector('.muc-bottom-panel'); + container.innerHTML = html; + u.addClass('muc-bottom-panel--nickname', container); + } else { + this.hideChatRoomContents(); + const container = this.el.querySelector('.chatroom-body'); + container.insertAdjacentHTML('beforeend', html); + } + u.safeSave(this.model, {'connection_status': converse.ROOMSTATUS.NICKNAME_REQUIRED}); + }, + closeForm () { /* Remove the configuration form without submitting and * return to the chat view. @@ -1444,22 +1497,21 @@ converse.plugins.add('converse-muc-views', { this.renderAfterTransition(); }, + /** + * Start the process of configuring a groupchat, either by + * rendering a configuration form, or by auto-configuring + * based on the "roomconfig" data stored on the + * {@link _converse.ChatRoom}. + * Stores the new configuration on the {@link _converse.ChatRoom} + * once completed. + * @private + * @method _converse.ChatRoomView#getAndRenderConfigurationForm + * @param { Event } ev - DOM event that might be passed in if this + * method is called due to a user action. In this + * case, auto-configure won't happen, regardless of + * the settings. + */ getAndRenderConfigurationForm () { - /* Start the process of configuring a groupchat, either by - * rendering a configuration form, or by auto-configuring - * based on the "roomconfig" data stored on the - * Backbone.Model. - * - * Stores the new configuration on the Backbone.Model once - * completed. - * - * Paremeters: - * (Event) ev: DOM event that might be passed in if this - * method is called due to a user action. In this - * case, auto-configure won't happen, regardless of - * the settings. - */ - if (!this.config_form || !u.isVisible(this.config_form.el)) { this.showSpinner(); this.model.fetchRoomConfiguration() @@ -1471,35 +1523,10 @@ converse.plugins.add('converse-muc-views', { }, hideChatRoomContents () { - return; - }, - - renderNicknameButton () { - this.el.querySelector('.setNicknameButtonForm').classList.remove('hidden'); - this.el.querySelector('.sendXMPPMessage').classList.add('hidden'); - this.el.querySelector('.setNicknameButtonForm').addEventListener('submit', this.renderNicknameModal.bind(this), false); - }, - - renderNicknameModal (ev) { - /* Render a button which allows the user to get a modal to set their nickname. - */ - ev.preventDefault(); - const message = this.model.get('nickname_validation_message'); - this.model.save('nickname_validation_message', undefined); - if (this.nickname_form_modal === undefined) { - this.nickname_form_modal = new _converse.NicknameFormModal({ - //'model': new Backbone.Model({'validation_message': message}), - 'model': this.model, - 'chatroomview': this, - }); - const container_el = this.el.querySelector('.chatroom-body'); - container_el.insertAdjacentElement('beforeend', this.nickname_form_modal.el); - } else { - this.nickname_form_modal.model.set('validation_message', message); + const container_el = this.el.querySelector('.chatroom-body'); + if (container_el !== null) { + [].forEach.call(container_el.children, child => child.classList.add('hidden')); } - this.nickname_form_modal.show(ev); - console.log(this.nickname_form_modal); - u.safeSave(this.model, {'connection_status': converse.ROOMSTATUS.NICKNAME_REQUIRED}); }, renderPasswordForm () { @@ -1598,7 +1625,19 @@ converse.plugins.add('converse-muc-views', { } }, + insertMessage (view) { + if (_converse.muc_show_logs_before_join && + this.content.firstElementChild.matches('.empty-history-feedback')) { + this.content.removeChild(this.content.firstElementChild); + } + return _converse.ChatBoxView.prototype.insertMessage.call(this, view); + }, + insertNotification (message) { + if (_converse.muc_show_logs_before_join && + this.content.firstElementChild.matches('.empty-history-feedback')) { + this.content.removeChild(this.content.firstElementChild); + } this.content.insertAdjacentHTML( 'beforeend', tpl_info({ @@ -1777,13 +1816,16 @@ converse.plugins.add('converse-muc-views', { this.scrollDown(); }, + /** + * Rerender the groupchat after some kind of transition. For + * example after the spinner has been removed or after a + * form has been submitted and removed. + * @private + * @method _converse.ChatRoomView#renderAfterTransition + */ renderAfterTransition () { - /* Rerender the groupchat after some kind of transition. For - * example after the spinner has been removed or after a - * form has been submitted and removed. - */ if (this.model.get('connection_status') == converse.ROOMSTATUS.NICKNAME_REQUIRED) { - this.renderNicknameButton(); + this.renderNicknameForm(); } else if (this.model.get('connection_status') == converse.ROOMSTATUS.PASSWORD_REQUIRED) { this.renderPasswordForm(); } else if (this.model.get('connection_status') == converse.ROOMSTATUS.ENTERED) { @@ -1801,11 +1843,14 @@ converse.plugins.add('converse-muc-views', { container_el.insertAdjacentHTML('afterbegin', tpl_spinner()); }, + /** + * Check if the spinner is being shown and if so, hide it. + * Also make sure then that the chat area and occupants + * list are both visible. + * @private + * @method _converse.ChatRoomView#hideSpinner + */ hideSpinner () { - /* Check if the spinner is being shown and if so, hide it. - * Also make sure then that the chat area and occupants - * list are both visible. - */ const spinner = this.el.querySelector('.spinner'); if (spinner !== null) { u.removeElement(spinner); @@ -1845,9 +1890,13 @@ converse.plugins.add('converse-muc-views', { }); + /** + * Backbone.NativeView which renders MUC section of the control box. + * @class + * @namespace _converse.RoomsPanel + * @memberOf _converse + */ _converse.RoomsPanel = Backbone.NativeView.extend({ - /* Backbone.NativeView which renders MUC section of the control box. - */ tagName: 'div', className: 'controlbox-section', id: 'chatrooms', @@ -1884,7 +1933,7 @@ converse.plugins.add('converse-muc-views', { _converse.MUCConfigForm = Backbone.VDOMView.extend({ className: 'muc-config-form', events: { - 'submit form': 'submitConfigForm', + 'submit .chatroom-form': 'submitConfigForm', 'click .button-cancel': 'closeConfigForm' }, @@ -1961,55 +2010,6 @@ converse.plugins.add('converse-muc-views', { }); - _converse.NicknameFormModal = _converse.BootstrapModal.extend({ - - events: { - 'submit form': 'submitNickname', - }, - - initialize (attrs) { - _converse.BootstrapModal.prototype.initialize.apply(this, arguments); - this.chatroomview = attrs.chatroomview; - this.listenTo(this.model, 'change:validation_message', this.render); - this.render(); - }, - - toHTML () { - const err_msg = this.model.get('validation_message'); - return tpl_chatroom_nickname_form_modal({ - '__': __, - 'heading': __('Please choose your nickname'), - 'label_nickname': __('Nickname'), - 'label_join': __('Enter groupchat'), - 'error_class': err_msg ? 'error' : '', - 'validation_message': err_msg, - 'nickname': this.model.get('nickname') - }); - }, - - submitNickname (ev) { - /* Get the nickname value from the form and then join the - * groupchat with it. - */ - ev.preventDefault(); - const nick_el = ev.target.nick; - const nick = nick_el.value.trim(); - if (nick) { - this.chatroomview.model.join(nick); - this.model.set({ - 'validation_message': null, - 'nickname': nick - }); - } else { - return this.model.set({ - 'validation_message': __('You need to provide a nickname') - }); - } - this.modal.hide(); - } - }); - - _converse.ChatRoomOccupantView = Backbone.VDOMView.extend({ tagName: 'li', initialize () { diff --git a/src/headless/converse-mam.js b/src/headless/converse-mam.js index ec0df9480d..eec2212d49 100644 --- a/src/headless/converse-mam.js +++ b/src/headless/converse-mam.js @@ -58,6 +58,7 @@ converse.plugins.add('converse-mam', { /** * Fetches messages that might have been archived *after* * the last archived message in our local cache. + * @private */ fetchNewestMessages () { if (this.disable_mam) { @@ -165,8 +166,9 @@ converse.plugins.add('converse-mam', { Object.assign(_converse.ChatRoom.prototype, { fetchArchivedMessagesIfNecessary () { - if (this.get('connection_status') !== converse.ROOMSTATUS.ENTERED || - !this.get('mam_enabled') || + const conn_status = this.get('connection_status'); + if ((!_converse.muc_show_logs_before_join && conn_status !== converse.ROOMSTATUS.ENTERED) || + !this.features.get('mam_enabled') || this.get('mam_initialized')) { return; } @@ -177,7 +179,7 @@ converse.plugins.add('converse-mam', { _converse.onMAMError = function (iq) { - if (iq.querySelectorAll('feature-not-implemented').length) { + if (iq && iq.querySelectorAll('feature-not-implemented').length) { log.warn("Message Archive Management (XEP-0313) not supported by this server"); } else { log.error("An error occured while trying to set archiving preferences."); @@ -232,7 +234,11 @@ converse.plugins.add('converse-mam', { _converse.api.listen.on('addClientFeatures', () => _converse.api.disco.own.features.add(Strophe.NS.MAM)); _converse.api.listen.on('serviceDiscovered', getMAMPrefsFromFeature); - _converse.api.listen.on('enteredNewRoom', chat => chat.fetchNewestMessages()); + _converse.api.listen.on('enteredNewRoom', room => room.features.get('mam_enabled') && room.fetchNewestMessages()); + _converse.api.listen.on('chatRoomOpened', (view) => { + view.listenTo(view.model, 'change:connection_status', () => view.model.fetchArchivedMessagesIfNecessary()); + }); + _converse.api.listen.on('chatReconnected', chat => { // XXX: For MUCs, we listen to enteredNewRoom instead if (chat.get('type') === _converse.PRIVATE_CHAT_TYPE) { @@ -250,11 +256,6 @@ converse.plugins.add('converse-mam', { chat.fetchNewestMessages(); } }); - - _converse.api.listen.on('chatRoomOpened', (room) => { - room.on('change:mam_enabled', room.fetchArchivedMessagesIfNecessary, room); - room.on('change:connection_status', room.fetchArchivedMessagesIfNecessary, room); - }); /************************ END Event Handlers **************************/ diff --git a/src/headless/converse-muc.js b/src/headless/converse-muc.js index 6cc63a7ce0..4ca700995f 100644 --- a/src/headless/converse-muc.js +++ b/src/headless/converse-muc.js @@ -129,7 +129,8 @@ converse.plugins.add('converse-muc', { 'muc_fetch_members': true, 'muc_history_max_stanzas': undefined, 'muc_instant_rooms': true, - 'muc_nickname_from_jid': false + 'muc_nickname_from_jid': false, + 'muc_show_logs_before_join': false }); _converse.api.promises.add(['roomsAutoJoined']); diff --git a/src/templates/chatarea.html b/src/templates/chatarea.html index 6c2dcecbe0..4bb25ea366 100644 --- a/src/templates/chatarea.html +++ b/src/templates/chatarea.html @@ -1,4 +1,8 @@
-
+
+ {[ if (o.muc_show_logs_before_join) { ]} +
{{{o.__('No message history available.')}}}
+ {[ } ]} +
diff --git a/src/templates/chatroom_bottom_panel.html b/src/templates/chatroom_bottom_panel.html index 6fd93eeed5..90c4b22522 100644 --- a/src/templates/chatroom_bottom_panel.html +++ b/src/templates/chatroom_bottom_panel.html @@ -1,6 +1,10 @@ -{[ if (o.can_edit) { ]} -
-
+{[ if (o.entered) { ]} + {[ if (o.can_edit) { ]} +
+
+ {[ } else { ]} +
{{{o.__("You're not allowed to send messages in this room")}}}
+ {[ } ]} {[ } else { ]} -
{{{o.__("You're not allowed to send messages in this room")}}}
+
{[ } ]} diff --git a/src/templates/chatroom_nickname_form.html b/src/templates/chatroom_nickname_form.html new file mode 100644 index 0000000000..9d28807f68 --- /dev/null +++ b/src/templates/chatroom_nickname_form.html @@ -0,0 +1,13 @@ +
+
+
+ +

{{{o.validation_message}}}

+ +
+
+ +
+
+
diff --git a/src/templates/chatroom_nickname_form_modal.html b/src/templates/chatroom_nickname_form_modal.html deleted file mode 100644 index a8dde6acb5..0000000000 --- a/src/templates/chatroom_nickname_form_modal.html +++ /dev/null @@ -1,26 +0,0 @@ - diff --git a/webpack.html b/webpack.html index be3d81b616..a4a1975fd0 100644 --- a/webpack.html +++ b/webpack.html @@ -19,7 +19,7 @@ converse.initialize({ auto_away: 300, auto_register_muc_nickname: true, - debug: true, + loglevel: 'debug', enable_smacks: true, i18n: 'en', message_archiving: 'always', @@ -27,6 +27,7 @@ muc_respect_autojoin: true, view_mode: 'fullscreen', websocket_url: 'ws://chat.example.org:5380/xmpp-websocket', + muc_show_logs_before_join: true, whitelisted_plugins: ['converse-debug'], });