From f4fdc36d3194e4440c6346e9f22f508108b48ea2 Mon Sep 17 00:00:00 2001 From: JC Brand Date: Sun, 6 Feb 2022 16:15:04 +0100 Subject: [PATCH] Add modal for changing your nickname... once you're already in a MUC. --- CHANGES.md | 2 +- src/headless/plugins/muc/muc.js | 23 ++++++++----- src/plugins/muc-views/heading.js | 10 ++++++ src/plugins/muc-views/modals/nickname.js | 15 ++++++++ .../muc-views/modals/templates/nickname.js | 21 ++++++++++++ src/plugins/muc-views/nickname-form.js | 22 +++++++++++- .../muc-views/templates/muc-bottom-panel.js | 6 ++-- .../muc-views/templates/muc-nickname-form.js | 16 +++------ src/plugins/muc-views/tests/nickname.js | 34 +++++++++++++++++-- webpack.html | 4 +-- webpack/webpack.serve.js | 2 +- 11 files changed, 126 insertions(+), 29 deletions(-) create mode 100644 src/plugins/muc-views/modals/nickname.js create mode 100644 src/plugins/muc-views/modals/templates/nickname.js diff --git a/CHANGES.md b/CHANGES.md index 005d761aa..a0ddd8f41 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,8 +5,8 @@ - Updated translations: lt - Increased stanza timeout from 10 to 20 seconds - Replace various font icons with SVG icons -- Fix OMEMO race condition related to automatic reconnection - #1761: Add a new dark theme based on the [Dracula](https://draculatheme.com/) theme +- #2733: Fix OMEMO race condition related to automatic reconnection - #2751: Media not rendered when Converse runs in a browser extension - #2786: Fix webpack configuration not working on Windows OS - #2788: `TypeError` when trying to use `@converse/headless` diff --git a/src/headless/plugins/muc/muc.js b/src/headless/plugins/muc/muc.js index a6b62c739..a7964bf33 100644 --- a/src/headless/plugins/muc/muc.js +++ b/src/headless/plugins/muc/muc.js @@ -124,6 +124,10 @@ const ChatRoomMixin = { this.initialized.resolve(); }, + isEntered () { + return this.session.get('connection_status') === converse.ROOMSTATUS.ENTERED; + }, + /** * Checks whether we're still joined and if so, restores the MUC state from cache. * @private @@ -131,7 +135,7 @@ const ChatRoomMixin = { * @returns { Boolean } Returns `true` if we're still joined, otherwise returns `false`. */ async restoreFromCache () { - if (this.session.get('connection_status') === converse.ROOMSTATUS.ENTERED && (await this.isJoined())) { + if (this.isEntered() && (await this.isJoined())) { // We've restored the room from cache and we're still joined. await new Promise(resolve => this.features.fetch({ 'success': resolve, 'error': resolve })); await this.fetchOccupants().catch(e => log.error(e)); @@ -154,7 +158,7 @@ const ChatRoomMixin = { * model (if available). */ async join (nick, password) { - if (this.session.get('connection_status') === converse.ROOMSTATUS.ENTERED) { + if (this.isEntered()) { // We have restored a groupchat from session storage, // so we don't send out a presence stanza again. return this; @@ -293,7 +297,7 @@ const ChatRoomMixin = { onOccupantRemoved (occupant) { if ( _converse.isInfoVisible(converse.MUC_TRAFFIC_STATES.EXITED) && - this.session.get('connection_status') === converse.ROOMSTATUS.ENTERED && + this.isEntered() && occupant.get('show') === 'online' ) { this.updateNotifications(occupant.get('nick'), converse.MUC_TRAFFIC_STATES.EXITED); @@ -339,7 +343,7 @@ const ChatRoomMixin = { }, async onConnectionStatusChanged () { - if (this.session.get('connection_status') === converse.ROOMSTATUS.ENTERED) { + if (this.isEntered()) { if (this.get('hidden') && api.settings.get('muc_subscribe_to_rai') && this.getOwnAffiliation() !== 'none') { try { await this.leave(); @@ -491,8 +495,7 @@ const ChatRoomMixin = { * @param { XMLElement } stanza */ handleMessageFromMUCHost (stanza) { - const conn_status = this.session.get('connection_status'); - if (conn_status === converse.ROOMSTATUS.ENTERED) { + if (this.isEntered()) { // We're not interested in activity indicators when already joined to the room return; } @@ -513,7 +516,7 @@ const ChatRoomMixin = { * @param { XMLElement } stanza */ handleForwardedMentions (stanza) { - if (this.session.get('connection_status') === converse.ROOMSTATUS.ENTERED) { + if (this.isEntered()) { // Avoid counting mentions twice return; } @@ -1023,7 +1026,7 @@ const ChatRoomMixin = { if ( !api.settings.get('send_chat_state_notifications') || !this.get('chat_state') || - this.session.get('connection_status') !== converse.ROOMSTATUS.ENTERED || + !this.isEntered() || (this.features.get('moderated') && this.getOwnRole() === 'visitor') ) { return; @@ -1404,6 +1407,10 @@ const ChatRoomMixin = { return this.occupants.getOwnOccupant(); }, + /** + * Send a presence stanza to update the user's nickname in this MUC. + * @param { String } nick + */ async setNickname (nick) { if ( api.settings.get('auto_register_muc_nickname') && diff --git a/src/plugins/muc-views/heading.js b/src/plugins/muc-views/heading.js index 9f91ea81a..d12ec3cd7 100644 --- a/src/plugins/muc-views/heading.js +++ b/src/plugins/muc-views/heading.js @@ -1,5 +1,6 @@ import { ElementView } from '@converse/skeletor/src/element.js'; import MUCInviteModal from 'modals/muc-invite.js'; +import NicknameModal from './modals/nickname.js'; import RoomDetailsModal from 'modals/muc-details.js'; import debounce from 'lodash-es/debounce'; import tpl_muc_head from './templates/muc-head.js'; @@ -107,6 +108,15 @@ export default class MUCHeading extends ElementView { }); } + buttons.push({ + 'i18n_text': __('Nickname'), + 'i18n_title': __("Change the nickname you're using in this groupchat"), + 'handler': ev => api.modal.show(NicknameModal, { 'model': this.model }, ev), + 'a_class': 'open-nickname-modal', + 'icon_class': 'fa-smile', + 'name': 'nickname' + }); + if (this.model.invitesAllowed()) { buttons.push({ 'i18n_text': __('Invite'), diff --git a/src/plugins/muc-views/modals/nickname.js b/src/plugins/muc-views/modals/nickname.js new file mode 100644 index 000000000..2542540ca --- /dev/null +++ b/src/plugins/muc-views/modals/nickname.js @@ -0,0 +1,15 @@ +import tpl_nickname from "./templates/nickname.js"; +import BaseModal from "plugins/modal/base.js"; + +export default BaseModal.extend({ + id: 'change-nickname-modal', + + initialize (attrs) { + this.model = attrs.model; + BaseModal.prototype.initialize.apply(this, arguments); + }, + + toHTML () { + return tpl_nickname(this); + }, +}); diff --git a/src/plugins/muc-views/modals/templates/nickname.js b/src/plugins/muc-views/modals/templates/nickname.js new file mode 100644 index 000000000..4f7eaae9e --- /dev/null +++ b/src/plugins/muc-views/modals/templates/nickname.js @@ -0,0 +1,21 @@ +import { __ } from 'i18n'; +import { html } from "lit"; +import { modal_header_close_button } from "plugins/modal/templates/buttons.js" + +export default (modal) => { + const jid = modal.model.get('jid'); + const i18n_heading = __('Change your nickname'); + return html` + `; +} diff --git a/src/plugins/muc-views/nickname-form.js b/src/plugins/muc-views/nickname-form.js index cd9b78189..6fd852872 100644 --- a/src/plugins/muc-views/nickname-form.js +++ b/src/plugins/muc-views/nickname-form.js @@ -19,7 +19,27 @@ class MUCNicknameForm extends CustomElement { } render () { - return tpl_muc_nickname_form(this.model); + return tpl_muc_nickname_form(this); + } + + submitNickname (ev) { + ev.preventDefault(); + const nick = ev.target.nick.value.trim(); + if (!nick) { + return; + } + if (this.model.isEntered()) { + this.model.setNickname(nick); + this.closeModal(); + } else { + this.model.join(nick); + } + } + + closeModal () { + const evt = document.createEvent('Event'); + evt.initEvent('hide.bs.modal', true, true); + this.dispatchEvent(evt); } } diff --git a/src/plugins/muc-views/templates/muc-bottom-panel.js b/src/plugins/muc-views/templates/muc-bottom-panel.js index 2aca3dbd1..1f47fd16b 100644 --- a/src/plugins/muc-views/templates/muc-bottom-panel.js +++ b/src/plugins/muc-views/templates/muc-bottom-panel.js @@ -1,6 +1,6 @@ import '../message-form.js'; +import '../nickname-form.js'; import 'shared/chat/toolbar.js'; -import tpl_muc_nickname_form from './muc-nickname-form.js'; import { __ } from 'i18n'; import { api, converse } from "@converse/headless/core"; import { html } from "lit"; @@ -45,7 +45,9 @@ export default (o) => { ${(o.can_edit) ? tpl_can_edit(o) : html`${i18n_not_allowed}`}`; } else if (conn_status == converse.ROOMSTATUS.NICKNAME_REQUIRED) { if (api.settings.get('muc_show_logs_before_join')) { - return html`${tpl_muc_nickname_form(o.model)}`; + return html` + + `; } } else { return ''; diff --git a/src/plugins/muc-views/templates/muc-nickname-form.js b/src/plugins/muc-views/templates/muc-nickname-form.js index 1f50ee908..dd55ccc76 100644 --- a/src/plugins/muc-views/templates/muc-nickname-form.js +++ b/src/plugins/muc-views/templates/muc-nickname-form.js @@ -2,24 +2,18 @@ import { __ } from 'i18n'; import { api } from "@converse/headless/core"; import { html } from "lit"; -function submitNickname (ev, model) { - ev.preventDefault(); - const nick = ev.target.nick.value.trim(); - nick && model.join(nick); -} - -export default (model) => { +export default (el) => { const i18n_nickname = __('Nickname'); - const i18n_join = __('Enter groupchat'); + const i18n_join = el.model?.isEntered() ? __('Change nickname') : __('Enter groupchat'); const i18n_heading = api.settings.get('muc_show_logs_before_join') ? __('Choose a nickname to enter') : __('Please choose your nickname'); - const validation_message = model.get('nickname_validation_message'); + const validation_message = el.model?.get('nickname_validation_message'); return html`
submitNickname(ev, model)}> + @submit=${ev => el.submitNickname(ev)}>
@@ -27,7 +21,7 @@ export default (model) => {
diff --git a/src/plugins/muc-views/tests/nickname.js b/src/plugins/muc-views/tests/nickname.js index 3d3c4282b..152f1ed5e 100644 --- a/src/plugins/muc-views/tests/nickname.js +++ b/src/plugins/muc-views/tests/nickname.js @@ -2,7 +2,36 @@ const { $pres, $iq, Strophe, sizzle, u } = converse.env; -fdescribe("A MUC", function () { +describe("A MUC", function () { + + it("allows you to change your nickname via a modal", + mock.initConverse([], {'view_mode': 'fullscreen'}, async function (_converse) { + + const muc_jid = 'lounge@montague.lit'; + const nick = 'romeo'; + await mock.openAndEnterChatRoom(_converse, muc_jid, nick); + + const view = _converse.chatboxviews.get(muc_jid); + const dropdown_item = view.querySelector(".open-nickname-modal"); + dropdown_item.click(); + + const modal = _converse.api.modal.get('change-nickname-modal'); + await u.waitUntil(() => u.isVisible(modal.el)); + + const input = modal.el.querySelector('input[name="nick"]'); + expect(input.value).toBe(nick); + + const newnick = 'loverboy'; + input.value = newnick; + modal.el.querySelector('input[type="submit"]')?.click(); + + await u.waitUntil(() => !u.isVisible(modal.el)); + + const { sent_stanzas } = _converse.connection; + const sent_stanza = sent_stanzas.pop() + expect(Strophe.serialize(sent_stanza).toLocaleString()).toBe( + ``); + })); it("informs users if their nicknames have been changed.", mock.initConverse([], {}, async function (_converse) { @@ -44,9 +73,8 @@ fdescribe("A MUC", function () { */ const __ = _converse.__; await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'oldnick'); - const view = _converse.chatboxviews.get('lounge@montague.lit'); - expect(view.model.session.get('connection_status')).toBe(converse.ROOMSTATUS.ENTERED); + const view = _converse.chatboxviews.get('lounge@montague.lit'); await u.waitUntil(() => view.querySelectorAll('li .occupant-nick').length, 500); let occupants = view.querySelector('.occupant-list'); expect(occupants.childElementCount).toBe(1); diff --git a/webpack.html b/webpack.html index 4309bc7b3..4fdf93cba 100644 --- a/webpack.html +++ b/webpack.html @@ -38,8 +38,8 @@ muc_domain: 'conference.chat.example.org', muc_respect_autojoin: true, view_mode: 'fullscreen', - // websocket_url: 'ws://chat.example.org:5380/xmpp-websocket', - websocket_url: 'wss://conversejs.org/xmpp-websocket', + websocket_url: 'ws://chat.example.org:5380/xmpp-websocket', + // websocket_url: 'wss://conversejs.org/xmpp-websocket', // bosh_service_url: 'http://chat.example.org:5280/http-bind', allow_user_defined_connection_url: true, muc_show_logs_before_join: true, diff --git a/webpack/webpack.serve.js b/webpack/webpack.serve.js index 988b981fd..b29fff513 100644 --- a/webpack/webpack.serve.js +++ b/webpack/webpack.serve.js @@ -9,7 +9,7 @@ module.exports = merge(common, { devtool: "inline-source-map", devServer: { static: [ path.resolve(__dirname, '../') ], - port: 3004 + port: 3003 }, plugins: [ new HTMLWebpackPlugin({