Commit 84a10d77 authored by JC Brand's avatar JC Brand

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
parent 39c85db1
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
"array-bracket-spacing": "off", "array-bracket-spacing": "off",
"array-callback-return": "error", "array-callback-return": "error",
"arrow-body-style": "off", "arrow-body-style": "off",
"arrow-parens": "error", "arrow-parens": "off",
"arrow-spacing": "error", "arrow-spacing": "error",
"block-scoped-var": "off", "block-scoped-var": "off",
"block-spacing": "off", "block-spacing": "off",
......
...@@ -8859,6 +8859,10 @@ body.reset { ...@@ -8859,6 +8859,10 @@ body.reset {
-webkit-animation: colorchange-chatmessage 1s; } -webkit-animation: colorchange-chatmessage 1s; }
#conversejs .message.chat-msg:hover { #conversejs .message.chat-msg:hover {
background-color: rgba(0, 0, 0, 0.035); } 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 { #conversejs .message.chat-msg .spoiler {
margin-top: 0.5em; } margin-top: 0.5em; }
#conversejs .message.chat-msg .spoiler-hint { #conversejs .message.chat-msg .spoiler-hint {
......
...@@ -68675,7 +68675,6 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ ...@@ -68675,7 +68675,6 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
return { return {
'fullname': fullname, 'fullname': fullname,
'replace': this.correction,
'from': _converse.bare_jid, 'from': _converse.bare_jid,
'sender': 'me', 'sender': 'me',
'time': moment().format(), 'time': moment().format(),
...@@ -68691,21 +68690,18 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ ...@@ -68691,21 +68690,18 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
* Parameters: * Parameters:
* (Message) message - The chat message * (Message) message - The chat message
*/ */
if (attrs.replace) { const message = this.messages.findWhere('correcting');
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,
'correcting': false
}); });
return this.sendMessageStanza(message);
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(this.messages.create(attrs)); return this.sendMessageStanza(this.messages.create(attrs));
...@@ -69344,6 +69340,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ ...@@ -69344,6 +69340,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
const KEY = { const KEY = {
ENTER: 13, ENTER: 13,
UP_ARROW: 38, UP_ARROW: 38,
DOWN_ARROW: 40,
FORWARD_SLASH: 47 FORWARD_SLASH: 47
}; };
converse.plugins.add('converse-chatview', { converse.plugins.add('converse-chatview', {
...@@ -69621,7 +69618,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ ...@@ -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 ul.emoji-picker li': 'insertEmoji',
'click .toggle-smiley': 'toggleEmojiMenu', 'click .toggle-smiley': 'toggleEmojiMenu',
'click .upload-file': 'toggleFileUpload', 'click .upload-file': 'toggleFileUpload',
'keyup .chat-textarea': 'keyPressed', 'keydown .chat-textarea': 'keyPressed',
'input .chat-textarea': 'inputChanged' 'input .chat-textarea': 'inputChanged'
}, },
...@@ -70103,6 +70100,10 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ ...@@ -70103,6 +70100,10 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
*/ */
this.showMessage(message); this.showMessage(message);
if (message.get('correcting')) {
this.insertIntoTextArea(message.get('message'), true);
}
_converse.emit('messageAdded', { _converse.emit('messageAdded', {
'message': message, 'message': message,
'chatbox': this.model 'chatbox': this.model
...@@ -70142,7 +70143,6 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ ...@@ -70142,7 +70143,6 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
} }
const attrs = this.model.getOutgoingMessageAttributes(text, spoiler_hint); const attrs = this.model.getOutgoingMessageAttributes(text, spoiler_hint);
delete this.model.correction;
this.model.sendMessage(attrs); this.model.sendMessage(attrs);
}, },
...@@ -70203,10 +70203,16 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ ...@@ -70203,10 +70203,16 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
keyPressed(ev) { keyPressed(ev) {
/* Event handler for when a key is pressed in a chat box textarea. /* 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); this.onFormSubmitted(ev);
} else if (ev.keyCode === KEY.UP_ARROW && !ev.shiftKey) { } else if (ev.keyCode === KEY.UP_ARROW && !ev.target.selectionEnd) {
this.editPreviousMessage(); 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) { } 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 // Set chat state to composing if keyCode is not a forward-slash
// (which would imply an internal command and not a message). // (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_ ...@@ -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() { editPreviousMessage() {
const msg = _.findLast(this.model.messages.models, msg => msg.get('message')); const msg = _.findLast(this.model.messages.models, msg => msg.get('message'));
if (msg) { if (msg) {
const textbox_el = this.el.querySelector('.chat-textarea'); this.insertIntoTextArea(msg.get('message'), true); // We don't set "correcting" the Backbone-way, because
textbox_el.value = msg.get('message');
textbox_el.focus(); // We don't set "correcting" the Backbone-way, because
// we don't want it to persist to storage. // 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_ ...@@ -70250,15 +70259,21 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
return this; return this;
}, },
insertIntoTextArea(value) { insertIntoTextArea(value, replace = false) {
const textbox_el = this.el.querySelector('.chat-textarea'); const textbox_el = this.el.querySelector('.chat-textarea');
let existing = textbox_el.value;
if (existing && existing[existing.length - 1] !== ' ') { if (replace) {
existing = existing + ' '; 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(); textbox_el.focus();
}, },
...@@ -74572,10 +74587,11 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ ...@@ -74572,10 +74587,11 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
initialize() { initialize() {
this.model.vcard.on('change', this.render, this); 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:progress', this.renderFileUploadProgresBar, this);
this.model.on('change:type', this.render, this); this.model.on('change:type', this.render, this);
this.model.on('change:upload', 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.model.on('destroy', this.remove, this);
this.render(); this.render();
}, },
...@@ -74746,6 +74762,10 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ ...@@ -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; return extra_classes;
} }
...@@ -75997,7 +76017,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ ...@@ -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 ul.emoji-picker li': 'insertEmoji',
'click .toggle-smiley': 'toggleEmojiMenu', 'click .toggle-smiley': 'toggleEmojiMenu',
'click .upload-file': 'toggleFileUpload', 'click .upload-file': 'toggleFileUpload',
'keypress .chat-textarea': 'keyPressed', 'keydown .chat-textarea': 'keyPressed',
'input .chat-textarea': 'inputChanged' 'input .chat-textarea': 'inputChanged'
}, },
...@@ -85632,13 +85652,13 @@ __p += '\n </span>\n <time timestamp="' + ...@@ -85632,13 +85652,13 @@ __p += '\n </span>\n <time timestamp="' +
__e(o.isodate) + __e(o.isodate) +
'" class="chat-msg-time">' + '" class="chat-msg-time">' +
__e(o.pretty_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) { ; if (o.edited) { ;
__p += ' <i title="' + __p += ' <i title="' +
__e(o.__('This message has been edited')) + __e(o.__('This message has been edited')) +
'" class="fa fa-edit chat-msg-edited"></i> '; '" 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 return __p
}; };
...@@ -73,6 +73,14 @@ ...@@ -73,6 +73,14 @@
&:hover { &:hover {
background-color: rgba(0, 0, 0, 0.035); 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 { .spoiler {
margin-top: 0.5em; margin-top: 0.5em;
......
...@@ -19,6 +19,97 @@ ...@@ -19,6 +19,97 @@
describe("A Chat Message", function () { 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 () { describe("when received from someone else", function () {
it("will open a chatbox and be displayed inside it", it("will open a chatbox and be displayed inside it",
...@@ -136,70 +227,6 @@ ...@@ -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 () { 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", it("the VCard for that user is fetched and the chatbox updated with the results",
...@@ -1663,7 +1690,6 @@ ...@@ -1663,7 +1690,6 @@
function (done, _converse) { function (done, _converse) {
let msg_id, view; let msg_id, view;
test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy') test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy')
.then(() => { .then(() => {
const jid = 'lounge@localhost'; const jid = 'lounge@localhost';
...@@ -1733,5 +1759,98 @@ ...@@ -1733,5 +1759,98 @@
done(); 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();
});
}));
}); });
})); }));
...@@ -365,7 +365,6 @@ ...@@ -365,7 +365,6 @@
return { return {
'fullname': fullname, 'fullname': fullname,
'replace': this.correction,
'from': _converse.bare_jid, 'from': _converse.bare_jid,
'sender': 'me', 'sender': 'me',
'time': moment().format(), 'time': moment().format(),
...@@ -381,18 +380,17 @@ ...@@ -381,18 +380,17 @@
* Parameters: * Parameters:
* (Message) message - The chat message * (Message) message - The chat message
*/ */
if (attrs.replace) { const message = this.messages.findWhere('correcting')
const message = this.messages.findWhere({'id': attrs.replace}) if (message) {
if (message) { const older_versions = message.get('older_versions') || [];
const older_versions = message.get('older_versions') || []; older_versions.push(message.get('message'));
older_versions.push(message.get('message')); message.save({
message.save({ 'message': attrs.message,
'message': attrs.message, 'older_versions': older_versions,
'older_versions': older_versions, 'edited': true,
'edited': true 'correcting': false
}); });
return this.sendMessageStanza(message); return this.sendMessageStanza(message);
}
} }
return this.sendMessageStanza(this.messages.create(attrs)); return this.sendMessageStanza(this.messages.create(attrs));
}, },
......
...@@ -55,6 +55,7 @@ ...@@ -55,6 +55,7 @@
const KEY = { const KEY = {
ENTER: 13, ENTER: 13,
UP_ARROW: 38, UP_ARROW: 38,
DOWN_ARROW: 40,
FORWARD_SLASH: 47 FORWARD_SLASH: 47
}; };
...@@ -334,7 +335,7 @@ ...@@ -334,7 +335,7 @@
'click .toggle-smiley ul.emoji-picker li': 'insertEmoji', 'click .toggle-smiley ul.emoji-picker li': 'insertEmoji',
'click .toggle-smiley': 'toggleEmojiMenu', 'click .toggle-smiley': 'toggleEmojiMenu',
'click .upload-file': 'toggleFileUpload', 'click .upload-file': 'toggleFileUpload',
'keyup .chat-textarea': 'keyPressed', 'keydown .chat-textarea': 'keyPressed',
'input .chat-textarea': 'inputChanged' 'input .chat-textarea': 'inputChanged'
}, },
...@@ -802,7 +803,9 @@ ...@@ -802,7 +803,9 @@
* (Object) message - The message Backbone object that was added. * (Object) message - The message Backbone object that was added.
*/ */
this.showMessage(message); this.showMessage(message);
if (message.get('correcting')) {
this.insertIntoTextArea(message.get('message'), true);
}
_converse.emit('messageAdded', { _converse.emit('messageAdded', {
'message': message, 'message': message,
'chatbox': this.model 'chatbox': this.model
...@@ -848,7 +851,6 @@ ...@@ -848,7 +851,6 @@
return; return;
} }
const attrs = this.model.getOutgoingMessageAttributes(text, spoiler_hint); const attrs = this.model.getOutgoingMessageAttributes(text, spoiler_hint);
delete this.model.correction;
this.model.sendMessage(attrs); this.model.sendMessage(attrs);
}, },
...@@ -912,10 +914,14 @@ ...@@ -912,10 +914,14 @@
keyPressed (ev) { keyPressed (ev) {
/* Event handler for when a key is pressed in a chat box textarea. /* 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); this.onFormSubmitted(ev);
} else if (ev.keyCode === KEY.UP_ARROW && !ev.shiftKey) { } else if (ev.keyCode === KEY.UP_ARROW && !ev.target.selectionEnd) {
this.editPreviousMessage(); 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) { } 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 // Set chat state to composing if keyCode is not a forward-slash
// (which would imply an internal command and not a message). // (which would imply an internal command and not a message).
...@@ -923,15 +929,18 @@ ...@@ -923,15 +929,18 @@
} }
}, },
cancelMessageCorrection () {
this.insertIntoTextArea('', true);
this.model.messages.where('correcting').forEach(msg => msg.save('correcting', false));
},
editPreviousMessage () { editPreviousMessage () {
const msg = _.findLast(this.model.messages.models, (msg) => msg.get('message')); const msg = _.findLast(this.model.messages.models, (msg) => msg.get('message'));
if (msg) { if (msg) {
const textbox_el = this.el.querySelector('.chat-textarea'); this.insertIntoTextArea(msg.get('message'), true);
textbox_el.value = msg.get('message');
textbox_el.focus()
// We don't set "correcting" the Backbone-way, because // We don't set "correcting" the Backbone-way, because
// we don't want it to persist to storage. // we don't want it to persist to storage.
this.model.correction = msg.get('id'); msg.save('correcting', true);
} }
}, },
...@@ -951,13 +960,17 @@ ...@@ -951,13 +960,17 @@
return this; return this;
}, },
insertIntoTextArea (value) { insertIntoTextArea (value, replace=false) {
const textbox_el = this.el.querySelector('.chat-textarea'); const textbox_el = this.el.querySelector('.chat-textarea');
let existing = textbox_el.value; if (replace) {
if (existing && (existing[existing.length-1] !== ' ')) { textbox_el.value = value;
existing = existing + ' '; } 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() textbox_el.focus()
}, },
......
...@@ -91,10 +91,11 @@ ...@@ -91,10 +91,11 @@
initialize () { initialize () {
this.model.vcard.on('change', this.render, this); 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:progress', this.renderFileUploadProgresBar, this);
this.model.on('change:type', this.render, this); this.model.on('change:type', this.render, this);
this.model.on('change:upload', 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.model.on('destroy', this.remove, this);
this.render(); this.render();
}, },
...@@ -262,6 +263,9 @@ ...@@ -262,6 +263,9 @@
extra_classes += ' mentioned'; extra_classes += ' mentioned';
} }
} }
if (this.model.get('correcting')) {
extra_classes += ' correcting';
}
return extra_classes; return extra_classes;
} }
}); });
......
...@@ -529,7 +529,7 @@ ...@@ -529,7 +529,7 @@
'click .toggle-smiley ul.emoji-picker li': 'insertEmoji', 'click .toggle-smiley ul.emoji-picker li': 'insertEmoji',
'click .toggle-smiley': 'toggleEmojiMenu', 'click .toggle-smiley': 'toggleEmojiMenu',
'click .upload-file': 'toggleFileUpload', 'click .upload-file': 'toggleFileUpload',
'keypress .chat-textarea': 'keyPressed', 'keydown .chat-textarea': 'keyPressed',
'input .chat-textarea': 'inputChanged' 'input .chat-textarea': 'inputChanged'
}, },
......
...@@ -9,8 +9,8 @@ ...@@ -9,8 +9,8 @@
</span> </span>
<time timestamp="{{{o.isodate}}}" class="chat-msg-time">{{{o.pretty_time}}}</time> <time timestamp="{{{o.isodate}}}" class="chat-msg-time">{{{o.pretty_time}}}</time>
</span> </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> <span class="chat-msg-text"></span>
<div class="chat-msg-media"></div> <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>
</div> </div>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment