瀏覽代碼

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.
JC Brand 6 年之前
父節點
當前提交
d051085626
共有 9 個文件被更改,包括 115 次插入116 次删除
  1. 2 0
      css/converse.css
  2. 30 35
      dist/converse.js
  3. 4 0
      sass/_core.scss
  4. 10 8
      spec/chatbox.js
  5. 40 32
      spec/chatroom.js
  6. 0 4
      spec/spoilers.js
  7. 25 36
      src/converse-chatview.js
  8. 2 0
      src/converse-omemo.js
  9. 2 1
      src/headless/converse-chatboxes.js

+ 2 - 0
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 {

+ 30 - 35
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,26 +50235,37 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_5__["default"].plugins
           return;
         }
 
-        let spoiler_hint;
+        if (!_converse.connection.authenticated) {
+          this.showHelpMessages(['Sorry, the connection has been lost, and your message could not be sent'], 'error');
+          return;
+        }
+
+        let spoiler_hint,
+            hint_el = {};
 
         if (this.model.get('composing_spoiler')) {
-          const hint_el = this.el.querySelector('form.sendXMPPMessage input.spoiler-hint');
+          hint_el = this.el.querySelector('form.sendXMPPMessage input.spoiler-hint');
           spoiler_hint = hint_el.value;
-          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
+        _converse_headless_utils_emoji__WEBPACK_IMPORTED_MODULE_21__["default"].addClass('disabled', textarea);
+        textarea.setAttribute('disabled', 'disabled');
 
-        const event = document.createEvent('Event');
-        event.initEvent('input', true, true);
-        textarea.dispatchEvent(event);
-        this.onMessageSubmitted(message, spoiler_hint);
+        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
 
-        _converse.emit('messageSend', message); // Suppress events, otherwise superfluous CSN gets set
-        // immediately after the message, causing rate-limiting issues.
+          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() {

+ 4 - 0
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;
     }

+ 10 - 8
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

+ 40 - 32
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();

+ 0 - 4
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 

+ 25 - 36
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 (!_converse.connection.authenticated) {
+                    this.showHelpMessages(
+                        ['Sorry, the connection has been lost, and your message could not be sent'],
+                        'error'
+                    );
+                    return;
+                }
+                let spoiler_hint, hint_el = {};
                 if (this.model.get('composing_spoiler')) {
-                    const hint_el = this.el.querySelector('form.sendXMPPMessage input.spoiler-hint');
+                    hint_el = this.el.querySelector('form.sendXMPPMessage input.spoiler-hint');
                     spoiler_hint = hint_el.value;
-                    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);
+                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});

+ 2 - 0
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);
                 }

+ 2 - 1
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 () {