diff --git a/CHANGES.md b/CHANGES.md index e240eff2d..6c32a325e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -36,6 +36,7 @@ Soon we'll deprecate the latter, so prepare now. - #2220: fix rendering of emojis in case `use_system_emojis == false` (again). - #2092: fixes room list update loop when having the `locked_muc_domain` truthy or `'hidden'` - #2285: Rename config option `muc_hats_from_vcard` to [muc_hats](https://conversejs.org/docs/html/configuration.html#muc-hats). Now accepts a list instead of a boolean and allows for more flexible choices regarding user badges. +- #2300: Fix message reorder issue after message correction. - #2304: Custom emojis (stickers) images not shown - #2307: BootstrapModal is not accessible to plugins - #2308: Allow getHats method to be overriden in the `overrides` object in plugins. diff --git a/spec/muc_messages.js b/spec/muc_messages.js index b3f3c2924..c74667de6 100644 --- a/spec/muc_messages.js +++ b/spec/muc_messages.js @@ -592,6 +592,102 @@ describe("A Groupchat Message", function () { done(); })); + it("keeps the same position in history after a correction", + mock.initConverse( + ['rosterGroupsFetched'], {}, + async function (done, _converse) { + + const muc_jid = 'lounge@montague.lit'; + await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo'); + const view = _converse.api.chatviews.get(muc_jid); + const stanza = $pres({ + to: 'romeo@montague.lit/_converse.js-29092160', + from: 'coven@chat.shakespeare.lit/newguy' + }) + .c('x', {xmlns: Strophe.NS.MUC_USER}) + .c('item', { + 'affiliation': 'none', + 'jid': 'newguy@montague.lit/_converse.js-290929789', + 'role': 'participant' + }).tree(); + _converse.connection._dataRecv(mock.createRequest(stanza)); + const msg_id = u.getUniqueId(); + + // Receiving the first message + await view.model.handleMessageStanza($msg({ + 'from': 'lounge@montague.lit/newguy', + 'to': _converse.connection.jid, + 'type': 'groupchat', + 'id': msg_id, + }).c('body').t('But soft, what light through yonder airlock breaks?').tree()); + + // Receiving own message to check order against + await view.model.handleMessageStanza($msg({ + 'from': 'lounge@montague.lit/romeo', + 'to': _converse.connection.jid, + 'type': 'groupchat', + 'id': msg_id, + }).c('body').t('But soft, what light through yonder airlock breaks?').tree()); + + await u.waitUntil(() => view.el.querySelectorAll('.chat-msg').length === 2); + expect(view.el.querySelectorAll('.chat-msg').length).toBe(2); + expect(view.el.querySelectorAll('.chat-msg__text')[0].textContent) + .toBe('But soft, what light through yonder airlock breaks?'); + expect(view.el.querySelectorAll('.chat-msg__text')[1].textContent) + .toBe('But soft, what light through yonder airlock breaks?'); + + // First message correction + await view.model.handleMessageStanza($msg({ + 'from': 'lounge@montague.lit/newguy', + 'to': _converse.connection.jid, + 'type': 'groupchat', + 'id': u.getUniqueId(), + }).c('body').t('But soft, what light through yonder chimney breaks?').up() + .c('replace', {'id': msg_id, 'xmlns': 'urn:xmpp:message-correct:0'}).tree()); + + await u.waitUntil(() => view.el.querySelector('.chat-msg__text').textContent === + 'But soft, what light through yonder chimney breaks?', 500); + expect(view.el.querySelectorAll('.chat-msg').length).toBe(2); + await u.waitUntil(() => view.el.querySelector('.chat-msg__content .fa-edit')); + + // Second message correction + await view.model.handleMessageStanza($msg({ + 'from': 'lounge@montague.lit/newguy', + 'to': _converse.connection.jid, + 'type': 'groupchat', + 'id': u.getUniqueId(), + }).c('body').t('But soft, what light through yonder window breaks?').up() + .c('replace', {'id': msg_id, 'xmlns': 'urn:xmpp:message-correct:0'}).tree()); + + // Second own message + await view.model.handleMessageStanza($msg({ + 'from': 'lounge@montague.lit/romeo', + 'to': _converse.connection.jid, + 'type': 'groupchat', + 'id': u.getUniqueId(), + }).c('body').t('But soft, what light through yonder window breaks?').tree()); + + await u.waitUntil(() => view.el.querySelectorAll('.chat-msg__text')[0].textContent === + 'But soft, what light through yonder window breaks?', 500); + await u.waitUntil(() => view.el.querySelectorAll('.chat-msg__text').length === 3); + await u.waitUntil(() => view.el.querySelectorAll('.chat-msg__text')[2].textContent === + 'But soft, what light through yonder window breaks?', 500); + + expect(view.el.querySelectorAll('.chat-msg').length).toBe(3); + expect(view.el.querySelectorAll('.chat-msg__content .fa-edit').length).toBe(1); + const edit = await u.waitUntil(() => view.el.querySelector('.chat-msg__content .fa-edit')); + edit.click(); + const modal = await u.waitUntil(() => view.el.querySelectorAll('converse-chat-message')[0].message_versions_modal); + await u.waitUntil(() => u.isVisible(modal.el), 1000); + const older_msgs = modal.el.querySelectorAll('.older-msg'); + expect(older_msgs.length).toBe(2); + expect(older_msgs[0].childNodes[2].textContent).toBe('But soft, what light through yonder airlock breaks?'); + expect(older_msgs[0].childNodes[0].nodeName).toBe('TIME'); + expect(older_msgs[1].childNodes[0].nodeName).toBe('TIME'); + expect(older_msgs[1].childNodes[2].textContent).toBe('But soft, what light through yonder chimney breaks?'); + done(); + })); + it("can be sent as a correction by using the up arrow", mock.initConverse( ['rosterGroupsFetched'], {}, diff --git a/src/components/message.js b/src/components/message.js index 4998cc07a..7071a370c 100644 --- a/src/components/message.js +++ b/src/components/message.js @@ -63,7 +63,7 @@ export default class Message extends CustomElement { render () { const format = api.settings.get('time_format'); - this.pretty_time = dayjs(this.time).format(format); + this.pretty_time = dayjs(this.edited || this.time).format(format); if (this.show_spinner) { return tpl_spinner(); } else if (this.model.get('file') && !this.model.get('oob_url')) { @@ -264,7 +264,7 @@ export default class Message extends CustomElement { renderAvatarByline () { return html` ${ this.hats.map(h => html`${h.title}`) } - + `; } diff --git a/src/headless/converse-chat.js b/src/headless/converse-chat.js index 9ae71ddd4..d9705305b 100644 --- a/src/headless/converse-chat.js +++ b/src/headless/converse-chat.js @@ -744,9 +744,14 @@ converse.plugins.add('converse-chat', { message.save({'older_versions': older_versions}); } else { // This is a correction of an earlier message we already received - older_versions[message.get('time')] = message.get('message'); + if(Object.keys(older_versions).length) { + older_versions[message.get('edited')] = message.get('message'); + }else { + older_versions[message.get('time')] = message.get('message'); + } attrs = Object.assign(attrs, {'older_versions': older_versions}); delete attrs['id']; // Delete id, otherwise a new cache entry gets created + attrs['time'] = message.get('time'); message.save(attrs); } return message; diff --git a/src/templates/chat_message.js b/src/templates/chat_message.js index ca34930eb..a0e9a875a 100644 --- a/src/templates/chat_message.js +++ b/src/templates/chat_message.js @@ -28,7 +28,7 @@ export default (o) => {
${ (o.is_me_message) ? html` -   +   ${ o.is_me_message ? '**' : ''}${o.username} ` : '' } ${ o.is_retracted ? o.renderRetraction() : o.renderMessageText() }