ソースを参照

Cancel message correction by pressing the down arrow

Also, add a class `correcting` to the message being corrected, to
provide a visual cue.

updates #421
JC Brand 7 年 前
コミット
84a10d77d9

+ 1 - 1
.eslintrc.json

@@ -34,7 +34,7 @@
         "array-bracket-spacing": "off",
         "array-callback-return": "error",
         "arrow-body-style": "off",
-        "arrow-parens": "error",
+        "arrow-parens": "off",
         "arrow-spacing": "error",
         "block-scoped-var": "off",
         "block-spacing": "off",

+ 4 - 0
css/converse.css

@@ -8859,6 +8859,10 @@ body.reset {
     -webkit-animation: colorchange-chatmessage 1s; }
   #conversejs .message.chat-msg:hover {
     background-color: rgba(0, 0, 0, 0.035); }
+  #conversejs .message.chat-msg.correcting.groupchat {
+    background-color: #fdf1ee; }
+  #conversejs .message.chat-msg.correcting:not(.groupchat) {
+    background-color: #e7f7ee; }
   #conversejs .message.chat-msg .spoiler {
     margin-top: 0.5em; }
   #conversejs .message.chat-msg .spoiler-hint {

+ 52 - 32
dist/converse.js

@@ -68675,7 +68675,6 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
 
           return {
             'fullname': fullname,
-            'replace': this.correction,
             'from': _converse.bare_jid,
             'sender': 'me',
             'time': moment().format(),
@@ -68691,21 +68690,18 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
            *  Parameters:
            *    (Message) message - The chat message
            */
-          if (attrs.replace) {
-            const message = this.messages.findWhere({
-              'id': attrs.replace
+          const message = this.messages.findWhere('correcting');
+
+          if (message) {
+            const older_versions = message.get('older_versions') || [];
+            older_versions.push(message.get('message'));
+            message.save({
+              'message': attrs.message,
+              'older_versions': older_versions,
+              'edited': true,
+              'correcting': false
             });
-
-            if (message) {
-              const older_versions = message.get('older_versions') || [];
-              older_versions.push(message.get('message'));
-              message.save({
-                'message': attrs.message,
-                'older_versions': older_versions,
-                'edited': true
-              });
-              return this.sendMessageStanza(message);
-            }
+            return this.sendMessageStanza(message);
           }
 
           return this.sendMessageStanza(this.messages.create(attrs));
@@ -69344,6 +69340,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
   const KEY = {
     ENTER: 13,
     UP_ARROW: 38,
+    DOWN_ARROW: 40,
     FORWARD_SLASH: 47
   };
   converse.plugins.add('converse-chatview', {
@@ -69621,7 +69618,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
           'click .toggle-smiley ul.emoji-picker li': 'insertEmoji',
           'click .toggle-smiley': 'toggleEmojiMenu',
           'click .upload-file': 'toggleFileUpload',
-          'keyup .chat-textarea': 'keyPressed',
+          'keydown .chat-textarea': 'keyPressed',
           'input .chat-textarea': 'inputChanged'
         },
 
@@ -70103,6 +70100,10 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
            */
           this.showMessage(message);
 
+          if (message.get('correcting')) {
+            this.insertIntoTextArea(message.get('message'), true);
+          }
+
           _converse.emit('messageAdded', {
             'message': message,
             'chatbox': this.model
@@ -70142,7 +70143,6 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
           }
 
           const attrs = this.model.getOutgoingMessageAttributes(text, spoiler_hint);
-          delete this.model.correction;
           this.model.sendMessage(attrs);
         },
 
@@ -70203,10 +70203,16 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
         keyPressed(ev) {
           /* Event handler for when a key is pressed in a chat box textarea.
            */
-          if (ev.keyCode === KEY.ENTER && !ev.shiftKey) {
+          if (ev.shiftKey) {
+            return;
+          }
+
+          if (ev.keyCode === KEY.ENTER) {
             this.onFormSubmitted(ev);
-          } else if (ev.keyCode === KEY.UP_ARROW && !ev.shiftKey) {
+          } else if (ev.keyCode === KEY.UP_ARROW && !ev.target.selectionEnd) {
             this.editPreviousMessage();
+          } else if (ev.keyCode === KEY.DOWN_ARROW && ev.target.selectionEnd === ev.target.value.length) {
+            this.cancelMessageCorrection();
           } else if (ev.keyCode !== KEY.FORWARD_SLASH && this.model.get('chat_state') !== _converse.COMPOSING) {
             // Set chat state to composing if keyCode is not a forward-slash
             // (which would imply an internal command and not a message).
@@ -70214,16 +70220,19 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
           }
         },
 
+        cancelMessageCorrection() {
+          this.insertIntoTextArea('', true);
+          this.model.messages.where('correcting').forEach(msg => msg.save('correcting', false));
+        },
+
         editPreviousMessage() {
           const msg = _.findLast(this.model.messages.models, msg => msg.get('message'));
 
           if (msg) {
-            const textbox_el = this.el.querySelector('.chat-textarea');
-            textbox_el.value = msg.get('message');
-            textbox_el.focus(); // We don't set "correcting" the Backbone-way, because
+            this.insertIntoTextArea(msg.get('message'), true); // We don't set "correcting" the Backbone-way, because
             // we don't want it to persist to storage.
 
-            this.model.correction = msg.get('id');
+            msg.save('correcting', true);
           }
         },
 
@@ -70250,15 +70259,21 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
           return this;
         },
 
-        insertIntoTextArea(value) {
+        insertIntoTextArea(value, replace = false) {
           const textbox_el = this.el.querySelector('.chat-textarea');
-          let existing = textbox_el.value;
 
-          if (existing && existing[existing.length - 1] !== ' ') {
-            existing = existing + ' ';
+          if (replace) {
+            textbox_el.value = value;
+          } else {
+            let existing = textbox_el.value;
+
+            if (existing && existing[existing.length - 1] !== ' ') {
+              existing = existing + ' ';
+            }
+
+            textbox_el.value = existing + value + ' ';
           }
 
-          textbox_el.value = existing + value + ' ';
           textbox_el.focus();
         },
 
@@ -74572,10 +74587,11 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
 
         initialize() {
           this.model.vcard.on('change', this.render, this);
+          this.model.on('change:correcting', this.render, this);
+          this.model.on('change:message', this.render, this);
           this.model.on('change:progress', this.renderFileUploadProgresBar, this);
           this.model.on('change:type', this.render, this);
           this.model.on('change:upload', this.render, this);
-          this.model.on('change:message', this.render, this);
           this.model.on('destroy', this.remove, this);
           this.render();
         },
@@ -74746,6 +74762,10 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
             }
           }
 
+          if (this.model.get('correcting')) {
+            extra_classes += ' correcting';
+          }
+
           return extra_classes;
         }
 
@@ -75997,7 +76017,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
           'click .toggle-smiley ul.emoji-picker li': 'insertEmoji',
           'click .toggle-smiley': 'toggleEmojiMenu',
           'click .upload-file': 'toggleFileUpload',
-          'keypress .chat-textarea': 'keyPressed',
+          'keydown .chat-textarea': 'keyPressed',
           'input .chat-textarea': 'inputChanged'
         },
 
@@ -85632,13 +85652,13 @@ __p += '\n            </span>\n            <time timestamp="' +
 __e(o.isodate) +
 '" class="chat-msg-time">' +
 __e(o.pretty_time) +
-'</time>\n        </span>\n        <span class="chat-msg-text"></span>\n        <div class="chat-msg-media"></div>\n        ';
+'</time>\n        </span>\n        ';
  if (o.edited) { ;
 __p += ' <i title="' +
 __e(o.__('This message has been edited')) +
 '" class="fa fa-edit chat-msg-edited"></i> ';
  } ;
-__p += '\n    </div>\n</div>\n';
+__p += '\n        <span class="chat-msg-text"></span>\n        <div class="chat-msg-media"></div>\n    </div>\n</div>\n';
 return __p
 };
 

+ 8 - 0
sass/_messages.scss

@@ -73,6 +73,14 @@
             &:hover {
                 background-color: rgba(0, 0, 0, 0.035);
             }
+            &.correcting {
+                &.groupchat  {
+                    background-color: lighten($chatroom-head-color, 35%);
+                }
+                &:not(.groupchat) {
+                    background-color: lighten($chat-head-color, 50%);
+                }
+            }
 
             .spoiler {
                 margin-top: 0.5em;

+ 184 - 65
spec/messages.js

@@ -19,6 +19,97 @@
 
     describe("A Chat Message", function () {
 
+        it("can be sent as a correction",
+            mock.initConverseWithPromises(
+                null, ['rosterGroupsFetched'], {},
+                function (done, _converse) {
+
+            test_utils.createContacts(_converse, 'current', 1);
+            test_utils.openControlBox();
+            const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
+            test_utils.openChatBoxFor(_converse, contact_jid);
+
+            const view = _converse.chatboxviews.get(contact_jid);
+            const textarea = view.el.querySelector('textarea.chat-textarea');
+            expect(textarea.value).toBe('');
+            view.keyPressed({
+                target: textarea,
+                keyCode: 38 // Up arrow
+            });
+            expect(textarea.value).toBe('');
+
+            textarea.value = 'But soft, what light through yonder airlock breaks?';
+            view.keyPressed({
+                target: textarea,
+                preventDefault: _.noop,
+                keyCode: 13 // Enter
+            });
+            expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
+            expect(view.el.querySelector('.chat-msg-text').textContent)
+                .toBe('But soft, what light through yonder airlock breaks?');
+
+            const first_msg = view.model.messages.findWhere({'message': 'But soft, what light through yonder airlock breaks?'});
+            expect(textarea.value).toBe('');
+            view.keyPressed({
+                target: textarea,
+                keyCode: 38 // Up arrow
+            });
+            expect(textarea.value).toBe('But soft, what light through yonder airlock breaks?');
+            expect(view.model.messages.at(0).get('correcting')).toBe(true);
+            expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
+            expect(u.hasClass('correcting', view.el.querySelector('.chat-msg'))).toBe(true);
+
+            spyOn(_converse.connection, 'send');
+            textarea.value = 'But soft, what light through yonder window breaks?';
+            view.keyPressed({
+                target: textarea,
+                preventDefault: _.noop,
+                keyCode: 13 // Enter
+            });
+            expect(_converse.connection.send).toHaveBeenCalled();
+
+            const msg = _converse.connection.send.calls.all()[0].args[0];
+            expect(msg.toLocaleString())
+            .toBe(`<message from='dummy@localhost/resource' `+
+                    `to='max.frankfurter@localhost' type='chat' id='${msg.nodeTree.getAttribute('id')}' `+
+                    `xmlns='jabber:client'>`+
+                        `<body>But soft, what light through yonder window breaks?</body>`+
+                        `<active xmlns='http://jabber.org/protocol/chatstates'/>`+
+                        `<replace xmlns='urn:xmpp:message-correct:0' id='${first_msg.get('msgid')}'/>`+
+                `</message>`);
+            expect(view.model.messages.models.length).toBe(1);
+            const corrected_message = view.model.messages.at(0);
+            expect(corrected_message.get('msgid')).toBe(first_msg.get('msgid'));
+            expect(corrected_message.get('correcting')).toBe(false);
+            expect(corrected_message.get('older_versions').length).toBe(1);
+            expect(corrected_message.get('older_versions')[0]).toBe('But soft, what light through yonder airlock breaks?');
+
+            expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
+            expect(u.hasClass('correcting', view.el.querySelector('.chat-msg'))).toBe(false);
+
+            // Test that pressing the down arrow cancels message correction
+            expect(textarea.value).toBe('');
+            view.keyPressed({
+                target: textarea,
+                keyCode: 38 // Up arrow
+            });
+            expect(textarea.value).toBe('But soft, what light through yonder window breaks?');
+            expect(view.model.messages.at(0).get('correcting')).toBe(true);
+            expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
+            expect(u.hasClass('correcting', view.el.querySelector('.chat-msg'))).toBe(true);
+            expect(textarea.value).toBe('But soft, what light through yonder window breaks?');
+            view.keyPressed({
+                target: textarea,
+                keyCode: 40 // Down arrow
+            });
+            expect(textarea.value).toBe('');
+            expect(view.model.messages.at(0).get('correcting')).toBe(false);
+            expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
+            expect(u.hasClass('correcting', view.el.querySelector('.chat-msg'))).toBe(false);
+            done();
+        }));
+
+
         describe("when received from someone else", function () {
 
             it("will open a chatbox and be displayed inside it",
@@ -136,70 +227,6 @@
                 });
             }));
 
-            it("can be sent as a correction",
-                mock.initConverseWithPromises(
-                    null, ['rosterGroupsFetched'], {},
-                    function (done, _converse) {
-
-                test_utils.createContacts(_converse, 'current', 1);
-                test_utils.openControlBox();
-                const message = 'This is a received message';
-                const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
-                test_utils.openChatBoxFor(_converse, contact_jid);
-
-                const view = _converse.chatboxviews.get(contact_jid);
-                const textarea = view.el.querySelector('textarea.chat-textarea');
-                expect(textarea.value).toBe('');
-                view.keyPressed({
-                    target: textarea,
-                    keyCode: 38
-                });
-                expect(textarea.value).toBe('');
-
-                textarea.value = 'But soft, what light through yonder airlock breaks?';
-                view.keyPressed({
-                    target: textarea,
-                    preventDefault: _.noop,
-                    keyCode: 13
-                });
-                expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
-                expect(view.el.querySelector('.chat-msg-text').textContent)
-                    .toBe('But soft, what light through yonder airlock breaks?');
-
-                const first_msg = view.model.messages.findWhere({'message': 'But soft, what light through yonder airlock breaks?'});
-                expect(textarea.value).toBe('');
-                view.keyPressed({
-                    target: textarea,
-                    keyCode: 38
-                });
-                expect(textarea.value).toBe('But soft, what light through yonder airlock breaks?');
-
-                spyOn(_converse.connection, 'send');
-                textarea.value = 'But soft, what light through yonder window breaks?';
-                view.keyPressed({
-                    target: textarea,
-                    preventDefault: _.noop,
-                    keyCode: 13
-                });
-                expect(_converse.connection.send).toHaveBeenCalled();
-
-                const msg = _converse.connection.send.calls.all()[0].args[0];
-                expect(msg.toLocaleString())
-                .toBe(`<message from='dummy@localhost/resource' `+
-                        `to='max.frankfurter@localhost' type='chat' id='${msg.nodeTree.getAttribute('id')}' `+
-                        `xmlns='jabber:client'>`+
-                            `<body>But soft, what light through yonder window breaks?</body>`+
-                            `<active xmlns='http://jabber.org/protocol/chatstates'/>`+
-                            `<replace xmlns='urn:xmpp:message-correct:0' id='${first_msg.get('msgid')}'/>`+
-                    `</message>`);
-                expect(view.model.messages.models.length).toBe(1);
-                const corrected_message = view.model.messages.at(0);
-                expect(corrected_message.get('msgid')).toBe(first_msg.get('msgid'));
-                expect(corrected_message.get('older_versions').length).toBe(1);
-                expect(corrected_message.get('older_versions')[0]).toBe('But soft, what light through yonder airlock breaks?');
-                done();
-            }));
-
             describe("when a chatbox is opened for someone who is not in the roster", function () {
 
                 it("the VCard for that user is fetched and the chatbox updated with the results",
@@ -1663,7 +1690,6 @@
                 function (done, _converse) {
 
             let msg_id, view;
-
             test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy')
             .then(() => {
                 const jid = 'lounge@localhost';
@@ -1733,5 +1759,98 @@
                 done();
             });
         }));
+
+        it("can be sent as a correction",
+            mock.initConverseWithPromises(
+                null, ['rosterGroupsFetched'], {},
+                function (done, _converse) {
+
+            let msg_id, view;
+            test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy')
+            .then(() => {
+                const jid = 'lounge@localhost';
+                const room = _converse.api.rooms.get(jid);
+                view = _converse.chatboxviews.get(jid);
+
+                const textarea = view.el.querySelector('textarea.chat-textarea');
+                expect(textarea.value).toBe('');
+                view.keyPressed({
+                    target: textarea,
+                    keyCode: 38 // Up arrow
+                });
+                expect(textarea.value).toBe('');
+
+                textarea.value = 'But soft, what light through yonder airlock breaks?';
+                view.keyPressed({
+                    target: textarea,
+                    preventDefault: _.noop,
+                    keyCode: 13 // Enter
+                });
+                expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
+                expect(view.el.querySelector('.chat-msg-text').textContent)
+                    .toBe('But soft, what light through yonder airlock breaks?');
+
+                const first_msg = view.model.messages.findWhere({'message': 'But soft, what light through yonder airlock breaks?'});
+                expect(textarea.value).toBe('');
+                view.keyPressed({
+                    target: textarea,
+                    keyCode: 38 // Up arrow
+                });
+                expect(textarea.value).toBe('But soft, what light through yonder airlock breaks?');
+                expect(view.model.messages.at(0).get('correcting')).toBe(true);
+                expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
+                expect(u.hasClass('correcting', view.el.querySelector('.chat-msg'))).toBe(true);
+
+                spyOn(_converse.connection, 'send');
+                textarea.value = 'But soft, what light through yonder window breaks?';
+                view.keyPressed({
+                    target: textarea,
+                    preventDefault: _.noop,
+                    keyCode: 13 // Enter
+                });
+                expect(_converse.connection.send).toHaveBeenCalled();
+
+                const msg = _converse.connection.send.calls.all()[0].args[0];
+                expect(msg.toLocaleString())
+                .toBe(`<message from='dummy@localhost/resource' `+
+                        `to='lounge@localhost' type='groupchat' id='${msg.nodeTree.getAttribute('id')}' `+
+                        `xmlns='jabber:client'>`+
+                            `<body>But soft, what light through yonder window breaks?</body>`+
+                            `<active xmlns='http://jabber.org/protocol/chatstates'/>`+
+                            `<replace xmlns='urn:xmpp:message-correct:0' id='${first_msg.get('msgid')}'/>`+
+                    `</message>`);
+
+                expect(view.model.messages.models.length).toBe(1);
+                const corrected_message = view.model.messages.at(0);
+                expect(corrected_message.get('msgid')).toBe(first_msg.get('msgid'));
+                expect(corrected_message.get('correcting')).toBe(false);
+                expect(corrected_message.get('older_versions').length).toBe(1);
+                expect(corrected_message.get('older_versions')[0]).toBe('But soft, what light through yonder airlock breaks?');
+
+                expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
+                expect(u.hasClass('correcting', view.el.querySelector('.chat-msg'))).toBe(false);
+
+                // Test that pressing the down arrow cancels message correction
+                expect(textarea.value).toBe('');
+                view.keyPressed({
+                    target: textarea,
+                    keyCode: 38 // Up arrow
+                });
+                expect(textarea.value).toBe('But soft, what light through yonder window breaks?');
+                expect(view.model.messages.at(0).get('correcting')).toBe(true);
+                expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
+                expect(u.hasClass('correcting', view.el.querySelector('.chat-msg'))).toBe(true);
+                expect(textarea.value).toBe('But soft, what light through yonder window breaks?');
+                view.keyPressed({
+                    target: textarea,
+                    keyCode: 40 // Down arrow
+                });
+                expect(textarea.value).toBe('');
+                expect(view.model.messages.at(0).get('correcting')).toBe(false);
+                expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
+                expect(u.hasClass('correcting', view.el.querySelector('.chat-msg'))).toBe(false);
+                done();
+            });
+        }));
     });
 }));

+ 11 - 13
src/converse-chatboxes.js

@@ -365,7 +365,6 @@
 
                     return {
                         'fullname': fullname,
-                        'replace': this.correction,
                         'from': _converse.bare_jid,
                         'sender': 'me',
                         'time': moment().format(),
@@ -381,18 +380,17 @@
                      *  Parameters:
                      *    (Message) message - The chat message
                      */
-                    if (attrs.replace) {
-                        const message = this.messages.findWhere({'id': attrs.replace})
-                        if (message) {
-                            const older_versions = message.get('older_versions') || [];
-                            older_versions.push(message.get('message'));
-                            message.save({
-                                'message': attrs.message,
-                                'older_versions': older_versions,
-                                'edited': true
-                            });
-                            return this.sendMessageStanza(message);
-                        }
+                    const message = this.messages.findWhere('correcting')
+                    if (message) {
+                        const older_versions = message.get('older_versions') || [];
+                        older_versions.push(message.get('message'));
+                        message.save({
+                            'message': attrs.message,
+                            'older_versions': older_versions,
+                            'edited': true,
+                            'correcting': false
+                        });
+                        return this.sendMessageStanza(message);
                     }
                     return this.sendMessageStanza(this.messages.create(attrs));
                 },

+ 27 - 14
src/converse-chatview.js

@@ -55,6 +55,7 @@
     const KEY = {
         ENTER: 13,
         UP_ARROW: 38,
+        DOWN_ARROW: 40,
         FORWARD_SLASH: 47
     };
 
@@ -334,7 +335,7 @@
                     'click .toggle-smiley ul.emoji-picker li': 'insertEmoji',
                     'click .toggle-smiley': 'toggleEmojiMenu',
                     'click .upload-file': 'toggleFileUpload',
-                    'keyup .chat-textarea': 'keyPressed',
+                    'keydown .chat-textarea': 'keyPressed',
                     'input .chat-textarea': 'inputChanged'
                 },
 
@@ -802,7 +803,9 @@
                      *    (Object) message - The message Backbone object that was added.
                      */
                     this.showMessage(message);
-
+                    if (message.get('correcting')) {
+                        this.insertIntoTextArea(message.get('message'), true);
+                    }
                     _converse.emit('messageAdded', {
                         'message': message,
                         'chatbox': this.model
@@ -848,7 +851,6 @@
                         return;
                     }
                     const attrs = this.model.getOutgoingMessageAttributes(text, spoiler_hint);
-                    delete this.model.correction;
                     this.model.sendMessage(attrs);
                 },
 
@@ -912,10 +914,14 @@
                 keyPressed (ev) {
                     /* Event handler for when a key is pressed in a chat box textarea.
                      */
-                    if (ev.keyCode === KEY.ENTER && !ev.shiftKey) {
+                    if (ev.shiftKey) { return; }
+
+                    if (ev.keyCode === KEY.ENTER) {
                         this.onFormSubmitted(ev);
-                    } else if (ev.keyCode === KEY.UP_ARROW && !ev.shiftKey) {
+                    } else if (ev.keyCode === KEY.UP_ARROW && !ev.target.selectionEnd) {
                         this.editPreviousMessage();
+                    } else if (ev.keyCode === KEY.DOWN_ARROW && ev.target.selectionEnd === ev.target.value.length) {
+                        this.cancelMessageCorrection();
                     } else if (ev.keyCode !== KEY.FORWARD_SLASH && this.model.get('chat_state') !== _converse.COMPOSING) {
                         // Set chat state to composing if keyCode is not a forward-slash
                         // (which would imply an internal command and not a message).
@@ -923,15 +929,18 @@
                     }
                 },
 
+                cancelMessageCorrection () {
+                    this.insertIntoTextArea('', true);
+                    this.model.messages.where('correcting').forEach(msg => msg.save('correcting', false));
+                },
+
                 editPreviousMessage () {
                     const msg = _.findLast(this.model.messages.models, (msg) => msg.get('message'));
                     if (msg) {
-                        const textbox_el = this.el.querySelector('.chat-textarea');
-                        textbox_el.value = msg.get('message');
-                        textbox_el.focus()
+                        this.insertIntoTextArea(msg.get('message'), true);
                         // We don't set "correcting" the Backbone-way, because
                         // we don't want it to persist to storage.
-                        this.model.correction = msg.get('id');
+                        msg.save('correcting', true);
                     }
                 },
 
@@ -951,13 +960,17 @@
                     return this;
                 },
 
-                insertIntoTextArea (value) {
+                insertIntoTextArea (value, replace=false) {
                     const textbox_el = this.el.querySelector('.chat-textarea');
-                    let existing = textbox_el.value;
-                    if (existing && (existing[existing.length-1] !== ' ')) {
-                        existing = existing + ' ';
+                    if (replace) {
+                        textbox_el.value = value;
+                    } else {
+                        let existing = textbox_el.value;
+                        if (existing && (existing[existing.length-1] !== ' ')) {
+                            existing = existing + ' ';
+                        }
+                        textbox_el.value = existing+value+' ';
                     }
-                    textbox_el.value = existing+value+' ';
                     textbox_el.focus()
                 },
 

+ 5 - 1
src/converse-message-view.js

@@ -91,10 +91,11 @@
 
                 initialize () {
                     this.model.vcard.on('change', this.render, this);
+                    this.model.on('change:correcting', this.render, this);
+                    this.model.on('change:message', this.render, this);
                     this.model.on('change:progress', this.renderFileUploadProgresBar, this);
                     this.model.on('change:type', this.render, this);
                     this.model.on('change:upload', this.render, this);
-                    this.model.on('change:message', this.render, this);
                     this.model.on('destroy', this.remove, this);
                     this.render();
                 },
@@ -262,6 +263,9 @@
                             extra_classes += ' mentioned';
                         }
                     }
+                    if (this.model.get('correcting')) {
+                        extra_classes += ' correcting';
+                    }
                     return extra_classes;
                 }
             });

+ 1 - 1
src/converse-muc-views.js

@@ -529,7 +529,7 @@
                     'click .toggle-smiley ul.emoji-picker li': 'insertEmoji',
                     'click .toggle-smiley': 'toggleEmojiMenu',
                     'click .upload-file': 'toggleFileUpload',
-                    'keypress .chat-textarea': 'keyPressed',
+                    'keydown .chat-textarea': 'keyPressed',
                     'input .chat-textarea': 'inputChanged'
                 },
 

+ 1 - 1
src/templates/message.html

@@ -9,8 +9,8 @@
             </span>
             <time timestamp="{{{o.isodate}}}" class="chat-msg-time">{{{o.pretty_time}}}</time>
         </span>
+        {[ if (o.edited) { ]} <i title="{{{o.__('This message has been edited')}}}" class="fa fa-edit chat-msg-edited"></i> {[ } ]}
         <span class="chat-msg-text"></span>
         <div class="chat-msg-media"></div>
-        {[ if (o.edited) { ]} <i title="{{{o.__('This message has been edited')}}}" class="fa fa-edit chat-msg-edited"></i> {[ } ]}
     </div>
 </div>