From d0510856264631dee683bce71f9f83d94706207c Mon Sep 17 00:00:00 2001 From: JC Brand Date: Thu, 20 Dec 2018 12:29:55 +0100 Subject: [PATCH] Only clear textarea once message was sent This now requires `sendMessage` to return a boolean to indicate success. Disable the textarea while message is being sent. --- css/converse.css | 2 + dist/converse.js | 71 ++++++++++++++--------------- sass/_core.scss | 4 ++ spec/chatbox.js | 18 ++++---- spec/chatroom.js | 72 +++++++++++++++++------------- spec/spoilers.js | 4 -- src/converse-chatview.js | 65 +++++++++++---------------- src/converse-omemo.js | 2 + src/headless/converse-chatboxes.js | 3 +- 9 files changed, 120 insertions(+), 121 deletions(-) diff --git a/css/converse.css b/css/converse.css index 2cf276d9c..2eb6cdac3 100644 --- a/css/converse.css +++ b/css/converse.css @@ -9583,6 +9583,8 @@ body.reset { font-size: var(--font-size); direction: ltr; z-index: 1031; } + #conversejs textarea:disabled { + background-color: #EEE !important; } #conversejs .nopadding { padding: 0 !important; } #conversejs.converse-overlayed > .row { diff --git a/dist/converse.js b/dist/converse.js index 8134f9820..47d49d65c 100644 --- a/dist/converse.js +++ b/dist/converse.js @@ -50200,27 +50200,6 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_5__["default"].plugins } }, - onMessageSubmitted(text, spoiler_hint) { - /* This method gets called once the user has typed a message - * and then pressed enter in a chat box. - * - * Parameters: - * (String) text - The chat message text. - * (String) spoiler_hint - A hint in case the message - * text is a hidden/spoiler message. See XEP-0382 - */ - if (!_converse.connection.authenticated) { - return this.showHelpMessages(['Sorry, the connection has been lost, ' + 'and your message could not be sent'], 'error'); - } - - if (this.parseMessageForCommands(text)) { - return; - } - - const attrs = this.model.getOutgoingMessageAttributes(text, spoiler_hint); - this.model.sendMessage(attrs); - }, - setChatState(state, options) { /* Mutator for setting the chat state of this chat session. * Handles clearing of any chat state notification timeouts and @@ -50247,7 +50226,7 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_5__["default"].plugins return this; }, - onFormSubmitted(ev) { + async onFormSubmitted(ev) { ev.preventDefault(); const textarea = this.el.querySelector('.chat-textarea'), message = textarea.value; @@ -50256,27 +50235,38 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_5__["default"].plugins return; } - let spoiler_hint; - - if (this.model.get('composing_spoiler')) { - const hint_el = this.el.querySelector('form.sendXMPPMessage input.spoiler-hint'); - spoiler_hint = hint_el.value; - hint_el.value = ''; + if (!_converse.connection.authenticated) { + this.showHelpMessages(['Sorry, the connection has been lost, and your message could not be sent'], 'error'); + return; } - textarea.value = ''; - _converse_headless_utils_emoji__WEBPACK_IMPORTED_MODULE_21__["default"].removeClass('correcting', textarea); - textarea.focus(); // Trigger input event, so that the textarea resizes + let spoiler_hint, + hint_el = {}; - const event = document.createEvent('Event'); - event.initEvent('input', true, true); - textarea.dispatchEvent(event); - this.onMessageSubmitted(message, spoiler_hint); + if (this.model.get('composing_spoiler')) { + hint_el = this.el.querySelector('form.sendXMPPMessage input.spoiler-hint'); + spoiler_hint = hint_el.value; + } - _converse.emit('messageSend', message); // Suppress events, otherwise superfluous CSN gets set + _converse_headless_utils_emoji__WEBPACK_IMPORTED_MODULE_21__["default"].addClass('disabled', textarea); + textarea.setAttribute('disabled', 'disabled'); + + if (this.parseMessageForCommands(message) || (await this.model.sendMessage(this.model.getOutgoingMessageAttributes(message, spoiler_hint)))) { + hint_el.value = ''; + textarea.value = ''; + _converse_headless_utils_emoji__WEBPACK_IMPORTED_MODULE_21__["default"].removeClass('correcting', textarea); + textarea.focus(); // Trigger input event, so that the textarea resizes + + const event = document.createEvent('Event'); + event.initEvent('input', true, true); + textarea.dispatchEvent(event); + + _converse.emit('messageSend', message); + } + + textarea.removeAttribute('disabled'); // Suppress events, otherwise superfluous CSN gets set // immediately after the message, causing rate-limiting issues. - this.setChatState(_converse.ACTIVE, { 'silent': true }); @@ -56296,7 +56286,11 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins }); _converse.log(e, Strophe.LogLevel.ERROR); + + return false; } + + return true; } else { return this.__super__.sendMessage.apply(this, arguments); } @@ -61901,7 +61895,8 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-cha message = this.messages.create(attrs); } - return this.sendMessageStanza(this.createMessageStanza(message)); + this.sendMessageStanza(this.createMessageStanza(message)); + return true; }, sendChatState() { diff --git a/sass/_core.scss b/sass/_core.scss index c04cf3891..2a86c8b27 100644 --- a/sass/_core.scss +++ b/sass/_core.scss @@ -115,6 +115,10 @@ body.reset { direction: ltr; z-index: 1031; // One more than bootstrap navbar + textarea:disabled { + background-color: #EEE !important; + } + .nopadding { padding: 0 !important; } diff --git a/spec/chatbox.js b/spec/chatbox.js index ace5a15e9..bb0be237f 100644 --- a/spec/chatbox.js +++ b/spec/chatbox.js @@ -284,7 +284,7 @@ done(); })); - it("can be closed by clicking a DOM element with class 'close-chatbox-button'", + it("can be closed by clicking a DOM element with class 'close-chatbox-button'", mock.initConverseWithPromises( null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, async function (done, _converse) { @@ -1024,21 +1024,23 @@ await test_utils.openChatBoxFor(_converse, contact_jid); const view = _converse.chatboxviews.get(contact_jid); let message = 'This message is another sent from this chatbox'; - // Lets make sure there is at least one message already - // (e.g for when this test is run on its own). - test_utils.sendMessage(view, message); + await test_utils.sendMessage(view, message); + expect(view.model.messages.length > 0).toBeTruthy(); expect(view.model.messages.browserStorage.records.length > 0).toBeTruthy(); - expect(_converse.emit).toHaveBeenCalledWith('messageSend', message); + await test_utils.waitUntil(() => view.el.querySelector('.chat-msg')); message = '/clear'; - spyOn(view, 'onMessageSubmitted').and.callThrough(); spyOn(view, 'clearMessages').and.callThrough(); spyOn(window, 'confirm').and.callFake(function () { return true; }); - test_utils.sendMessage(view, message); - expect(view.onMessageSubmitted).toHaveBeenCalled(); + view.el.querySelector('.chat-textarea').value = message; + view.keyPressed({ + target: view.el.querySelector('textarea.chat-textarea'), + preventDefault: _.noop, + keyCode: 13 + }); expect(view.clearMessages).toHaveBeenCalled(); expect(window.confirm).toHaveBeenCalled(); expect(view.model.messages.length, 0); // The messages must be removed from the chatbox diff --git a/spec/chatroom.js b/spec/chatroom.js index 2f2867cd4..1ef4d4e8b 100644 --- a/spec/chatroom.js +++ b/spec/chatroom.js @@ -2535,7 +2535,6 @@ await test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy'); const view = _converse.chatboxviews.get('lounge@localhost'); - spyOn(view, 'onMessageSubmitted').and.callThrough(); var textarea = view.el.querySelector('.chat-textarea'); textarea.value = '/help This is the groupchat subject'; view.keyPressed({ @@ -2544,7 +2543,6 @@ keyCode: 13 }); - expect(view.onMessageSubmitted).toHaveBeenCalled(); const info_messages = Array.prototype.slice.call(view.el.querySelectorAll('.chat-info'), 0); expect(info_messages.length).toBe(18); expect(info_messages.pop().textContent).toBe('/voice: Allow muted user to post messages'); @@ -2723,7 +2721,6 @@ await test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy'); const view = _converse.chatboxviews.get('lounge@localhost'); - spyOn(view, 'onMessageSubmitted').and.callThrough(); spyOn(view, 'clearMessages'); let sent_stanza; spyOn(_converse.connection, 'send').and.callFake(function (stanza) { @@ -2737,7 +2734,6 @@ preventDefault: _.noop, keyCode: 13 }); - expect(view.onMessageSubmitted).toHaveBeenCalled(); expect(_converse.connection.send).toHaveBeenCalled(); expect(sent_stanza.textContent).toBe('This is the groupchat subject'); @@ -2777,7 +2773,6 @@ await test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy'); const view = _converse.chatboxviews.get('lounge@localhost'); - spyOn(view, 'onMessageSubmitted').and.callThrough(); spyOn(view, 'clearMessages'); const textarea = view.el.querySelector('.chat-textarea') textarea.value = '/clear'; @@ -2786,7 +2781,6 @@ preventDefault: _.noop, keyCode: 13 }); - expect(view.onMessageSubmitted).toHaveBeenCalled(); expect(view.clearMessages).toHaveBeenCalled(); done(); })); @@ -2805,7 +2799,6 @@ await test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy'); const view = _converse.chatboxviews.get('lounge@localhost'); - spyOn(view, 'onMessageSubmitted').and.callThrough(); spyOn(view.model, 'setAffiliation').and.callThrough(); spyOn(view, 'showErrorMessage').and.callThrough(); spyOn(view, 'validateRoleChangeCommand').and.callThrough(); @@ -2830,22 +2823,27 @@ preventDefault: _.noop, keyCode: 13 }); - expect(view.onMessageSubmitted).toHaveBeenCalled(); expect(view.validateRoleChangeCommand).toHaveBeenCalled(); expect(view.showErrorMessage).toHaveBeenCalledWith( "Error: the \"owner\" command takes two arguments, the user's nickname and optionally a reason."); expect(view.model.setAffiliation).not.toHaveBeenCalled(); + // XXX: Calling onFormSubmitted directly, trying + // again via triggering Event doesn't work for some weird + // reason. + textarea.value = '/owner nobody You\'re responsible'; + view.onFormSubmitted(new Event('submit')); - view.onMessageSubmitted('/owner nobody You\'re responsible'); expect(view.showErrorMessage).toHaveBeenCalledWith( 'Error: couldn\'t find a groupchat participant "nobody"'); expect(view.model.setAffiliation).not.toHaveBeenCalled(); - // Call now with the correct amount of arguments. - // XXX: Calling onMessageSubmitted directly, trying + // Call now with the correct of arguments. + // XXX: Calling onFormSubmitted directly, trying // again via triggering Event doesn't work for some weird // reason. - view.onMessageSubmitted('/owner annoyingGuy You\'re responsible'); + textarea.value = '/owner annoyingGuy You\'re responsible'; + view.onFormSubmitted(new Event('submit')); + expect(view.validateRoleChangeCommand.calls.count()).toBe(3); expect(view.model.setAffiliation).toHaveBeenCalled(); expect(view.showErrorMessage.calls.count()).toBe(2); @@ -2889,7 +2887,6 @@ await test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy'); const view = _converse.chatboxviews.get('lounge@localhost'); - spyOn(view, 'onMessageSubmitted').and.callThrough(); spyOn(view.model, 'setAffiliation').and.callThrough(); spyOn(view, 'showErrorMessage').and.callThrough(); spyOn(view, 'validateRoleChangeCommand').and.callThrough(); @@ -2914,17 +2911,17 @@ preventDefault: _.noop, keyCode: 13 }); - expect(view.onMessageSubmitted).toHaveBeenCalled(); expect(view.validateRoleChangeCommand).toHaveBeenCalled(); expect(view.showErrorMessage).toHaveBeenCalledWith( "Error: the \"ban\" command takes two arguments, the user's nickname and optionally a reason."); expect(view.model.setAffiliation).not.toHaveBeenCalled(); // Call now with the correct amount of arguments. - - // XXX: Calling onMessageSubmitted directly, trying + // XXX: Calling onFormSubmitted directly, trying // again via triggering Event doesn't work for some weird // reason. - view.onMessageSubmitted('/ban annoyingGuy You\'re annoying'); + textarea.value = '/ban annoyingGuy You\'re annoying'; + view.onFormSubmitted(new Event('submit')); + expect(view.validateRoleChangeCommand.calls.count()).toBe(2); expect(view.showErrorMessage.calls.count()).toBe(1); expect(view.model.setAffiliation).toHaveBeenCalled(); @@ -2970,7 +2967,6 @@ await test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy'); const view = _converse.chatboxviews.get('lounge@localhost'); - spyOn(view, 'onMessageSubmitted').and.callThrough(); spyOn(view, 'modifyRole').and.callThrough(); spyOn(view, 'showErrorMessage').and.callThrough(); spyOn(view, 'validateRoleChangeCommand').and.callThrough(); @@ -2995,16 +2991,17 @@ preventDefault: _.noop, keyCode: 13 }); - expect(view.onMessageSubmitted).toHaveBeenCalled(); expect(view.validateRoleChangeCommand).toHaveBeenCalled(); expect(view.showErrorMessage).toHaveBeenCalledWith( "Error: the \"kick\" command takes two arguments, the user's nickname and optionally a reason."); expect(view.modifyRole).not.toHaveBeenCalled(); // Call now with the correct amount of arguments. - // XXX: Calling onMessageSubmitted directly, trying + // XXX: Calling onFormSubmitted directly, trying // again via triggering Event doesn't work for some weird // reason. - view.onMessageSubmitted('/kick annoyingGuy You\'re annoying'); + textarea.value = '/kick annoyingGuy You\'re annoying'; + view.onFormSubmitted(new Event('submit')); + expect(view.validateRoleChangeCommand.calls.count()).toBe(2); expect(view.showErrorMessage.calls.count()).toBe(1); expect(view.modifyRole).toHaveBeenCalled(); @@ -3058,7 +3055,6 @@ IQ_id = sendIQ.bind(this)(iq, callback, errback); }); var view = _converse.chatboxviews.get('lounge@localhost'); - spyOn(view, 'onMessageSubmitted').and.callThrough(); spyOn(view, 'modifyRole').and.callThrough(); spyOn(view, 'showErrorMessage').and.callThrough(); spyOn(view, 'showChatEvent').and.callThrough(); @@ -3097,17 +3093,18 @@ keyCode: 13 }); - expect(view.onMessageSubmitted).toHaveBeenCalled(); expect(view.validateRoleChangeCommand).toHaveBeenCalled(); expect(view.showErrorMessage).toHaveBeenCalledWith( "Error: the \"op\" command takes two arguments, the user's nickname and optionally a reason."); expect(view.modifyRole).not.toHaveBeenCalled(); // Call now with the correct amount of arguments. - // XXX: Calling onMessageSubmitted directly, trying + // XXX: Calling onFormSubmitted directly, trying // again via triggering Event doesn't work for some weird // reason. - view.onMessageSubmitted('/op trustworthyguy You\'re trustworthy'); + textarea.value = '/op trustworthyguy You\'re trustworthy'; + view.onFormSubmitted(new Event('submit')); + expect(view.validateRoleChangeCommand.calls.count()).toBe(2); expect(view.showErrorMessage.calls.count()).toBe(1); expect(view.modifyRole).toHaveBeenCalled(); @@ -3143,8 +3140,13 @@ _converse.connection._dataRecv(test_utils.createRequest(presence)); info_msgs = Array.prototype.slice.call(view.el.querySelectorAll('.chat-info'), 0); expect(info_msgs.pop().textContent).toBe("trustworthyguy is now a moderator"); + // Call now with the correct amount of arguments. + // XXX: Calling onFormSubmitted directly, trying + // again via triggering Event doesn't work for some weird + // reason. + textarea.value = '/deop trustworthyguy Perhaps not'; + view.onFormSubmitted(new Event('submit')); - view.onMessageSubmitted('/deop trustworthyguy Perhaps not'); expect(view.validateRoleChangeCommand.calls.count()).toBe(3); expect(view.showChatEvent.calls.count()).toBe(1); expect(view.modifyRole).toHaveBeenCalled(); @@ -3195,7 +3197,6 @@ IQ_id = sendIQ.bind(this)(iq, callback, errback); }); var view = _converse.chatboxviews.get('lounge@localhost'); - spyOn(view, 'onMessageSubmitted').and.callThrough(); spyOn(view, 'modifyRole').and.callThrough(); spyOn(view, 'showErrorMessage').and.callThrough(); spyOn(view, 'showChatEvent').and.callThrough(); @@ -3226,7 +3227,7 @@ var info_msgs = Array.prototype.slice.call(view.el.querySelectorAll('.chat-info'), 0); expect(info_msgs.pop().textContent).toBe("annoyingGuy has entered the groupchat"); - var textarea = view.el.querySelector('.chat-textarea') + const textarea = view.el.querySelector('.chat-textarea') textarea.value = '/mute'; view.keyPressed({ target: textarea, @@ -3234,16 +3235,17 @@ keyCode: 13 }); - expect(view.onMessageSubmitted).toHaveBeenCalled(); expect(view.validateRoleChangeCommand).toHaveBeenCalled(); expect(view.showErrorMessage).toHaveBeenCalledWith( "Error: the \"mute\" command takes two arguments, the user's nickname and optionally a reason."); expect(view.modifyRole).not.toHaveBeenCalled(); // Call now with the correct amount of arguments. - // XXX: Calling onMessageSubmitted directly, trying + // XXX: Calling onFormSubmitted directly, trying // again via triggering Event doesn't work for some weird // reason. - view.onMessageSubmitted('/mute annoyingGuy You\'re annoying'); + textarea.value = '/mute annoyingGuy You\'re annoying'; + view.onFormSubmitted(new Event('submit')); + expect(view.validateRoleChangeCommand.calls.count()).toBe(2); expect(view.showErrorMessage.calls.count()).toBe(1); expect(view.modifyRole).toHaveBeenCalled(); @@ -3280,7 +3282,13 @@ info_msgs = Array.prototype.slice.call(view.el.querySelectorAll('.chat-info'), 0); expect(info_msgs.pop().textContent).toBe("annoyingGuy has been muted"); - view.onMessageSubmitted('/voice annoyingGuy Now you can talk again'); + // Call now with the correct of arguments. + // XXX: Calling onFormSubmitted directly, trying + // again via triggering Event doesn't work for some weird + // reason. + textarea.value = '/voice annoyingGuy Now you can talk again'; + view.onFormSubmitted(new Event('submit')); + expect(view.validateRoleChangeCommand.calls.count()).toBe(3); expect(view.showChatEvent.calls.count()).toBe(1); expect(view.modifyRole).toHaveBeenCalled(); diff --git a/spec/spoilers.js b/spec/spoilers.js index f26c1795c..68fb45aea 100644 --- a/spec/spoilers.js +++ b/spec/spoilers.js @@ -102,7 +102,6 @@ await test_utils.openChatBoxFor(_converse, contact_jid); await test_utils.waitUntilDiscoConfirmed(_converse, contact_jid+'/phone', [], [Strophe.NS.SPOILER]); const view = _converse.chatboxviews.get(contact_jid); - spyOn(view, 'onMessageSubmitted').and.callThrough(); spyOn(_converse.connection, 'send'); await test_utils.waitUntil(() => view.el.querySelector('.toggle-compose-spoiler')); @@ -116,7 +115,6 @@ preventDefault: _.noop, keyCode: 13 }); - expect(view.onMessageSubmitted).toHaveBeenCalled(); await new Promise((resolve, reject) => view.once('messageInserted', resolve)); /* Test the XML stanza @@ -184,7 +182,6 @@ let spoiler_toggle = view.el.querySelector('.toggle-compose-spoiler'); spoiler_toggle.click(); - spyOn(view, 'onMessageSubmitted').and.callThrough(); spyOn(_converse.connection, 'send'); const textarea = view.el.querySelector('.chat-textarea'); @@ -197,7 +194,6 @@ preventDefault: _.noop, keyCode: 13 }); - expect(view.onMessageSubmitted).toHaveBeenCalled(); await new Promise((resolve, reject) => view.once('messageInserted', resolve)); /* Test the XML stanza diff --git a/src/converse-chatview.js b/src/converse-chatview.js index 4010290fb..036e373cb 100644 --- a/src/converse-chatview.js +++ b/src/converse-chatview.js @@ -818,29 +818,6 @@ converse.plugins.add('converse-chatview', { } }, - onMessageSubmitted (text, spoiler_hint) { - /* This method gets called once the user has typed a message - * and then pressed enter in a chat box. - * - * Parameters: - * (String) text - The chat message text. - * (String) spoiler_hint - A hint in case the message - * text is a hidden/spoiler message. See XEP-0382 - */ - if (!_converse.connection.authenticated) { - return this.showHelpMessages( - ['Sorry, the connection has been lost, '+ - 'and your message could not be sent'], - 'error' - ); - } - if (this.parseMessageForCommands(text)) { - return; - } - const attrs = this.model.getOutgoingMessageAttributes(text, spoiler_hint); - this.model.sendMessage(attrs); - }, - setChatState (state, options) { /* Mutator for setting the chat state of this chat session. * Handles clearing of any chat state notification timeouts and @@ -873,7 +850,7 @@ converse.plugins.add('converse-chatview', { return this; }, - onFormSubmitted (ev) { + async onFormSubmitted (ev) { ev.preventDefault(); const textarea = this.el.querySelector('.chat-textarea'), message = textarea.value; @@ -881,22 +858,34 @@ converse.plugins.add('converse-chatview', { if (!message.replace(/\s/g, '').length) { return; } - let spoiler_hint; - if (this.model.get('composing_spoiler')) { - const hint_el = this.el.querySelector('form.sendXMPPMessage input.spoiler-hint'); - spoiler_hint = hint_el.value; - hint_el.value = ''; + if (!_converse.connection.authenticated) { + this.showHelpMessages( + ['Sorry, the connection has been lost, and your message could not be sent'], + 'error' + ); + return; } - textarea.value = ''; - u.removeClass('correcting', textarea); - textarea.focus(); - // Trigger input event, so that the textarea resizes - const event = document.createEvent('Event'); - event.initEvent('input', true, true); - textarea.dispatchEvent(event); + let spoiler_hint, hint_el = {}; + if (this.model.get('composing_spoiler')) { + hint_el = this.el.querySelector('form.sendXMPPMessage input.spoiler-hint'); + spoiler_hint = hint_el.value; + } + u.addClass('disabled', textarea); + textarea.setAttribute('disabled', 'disabled'); + if (this.parseMessageForCommands(message) || + await this.model.sendMessage(this.model.getOutgoingMessageAttributes(message, spoiler_hint))) { - this.onMessageSubmitted(message, spoiler_hint); - _converse.emit('messageSend', message); + hint_el.value = ''; + textarea.value = ''; + u.removeClass('correcting', textarea); + textarea.focus(); + // Trigger input event, so that the textarea resizes + const event = document.createEvent('Event'); + event.initEvent('input', true, true); + textarea.dispatchEvent(event); + _converse.emit('messageSend', message); + } + textarea.removeAttribute('disabled'); // Suppress events, otherwise superfluous CSN gets set // immediately after the message, causing rate-limiting issues. this.setChatState(_converse.ACTIVE, {'silent': true}); diff --git a/src/converse-omemo.js b/src/converse-omemo.js index 9d8c44627..6e4c159d3 100644 --- a/src/converse-omemo.js +++ b/src/converse-omemo.js @@ -326,7 +326,9 @@ converse.plugins.add('converse-omemo', { 'type': 'error', }); _converse.log(e, Strophe.LogLevel.ERROR); + return false; } + return true; } else { return this.__super__.sendMessage.apply(this, arguments); } diff --git a/src/headless/converse-chatboxes.js b/src/headless/converse-chatboxes.js index c5f77a724..4bd98bfc9 100644 --- a/src/headless/converse-chatboxes.js +++ b/src/headless/converse-chatboxes.js @@ -433,7 +433,8 @@ converse.plugins.add('converse-chatboxes', { } else { message = this.messages.create(attrs); } - return this.sendMessageStanza(this.createMessageStanza(message)); + this.sendMessageStanza(this.createMessageStanza(message)); + return true; }, sendChatState () {