From 36549bf61d38549724077526188c0db10ae50575 Mon Sep 17 00:00:00 2001 From: JC Brand Date: Wed, 19 Jun 2019 11:04:09 +0200 Subject: [PATCH] Revert `flexbox`, `column-reverse` changes Unfortunately this doesn't work on Firefox and there's no proper workaround. https://github.com/philipwalton/flexbugs/issues/108 Reverts: Revert "Bugfix. Properly insert error messages and spinner" This reverts commit 6a419cc145f939d5fc6a2c70a3f64f2913395770. Revert "Use flexbox to keep the chat scrolled down" This reverts commit dd91d3cc55cda7e111647b32f1a75bc13cd8b9fe. --- CHANGES.md | 2 - sass/_chatbox.scss | 2 - sass/_messages.scss | 6 +- spec/chatbox.js | 16 +-- spec/messages.js | 176 +++++++++++++++----------------- spec/muc.js | 210 +++++++++++++++++++------------------- spec/omemo.js | 4 +- src/converse-chatview.js | 129 +++++++++++++++-------- src/converse-muc-views.js | 37 ++++--- 9 files changed, 310 insertions(+), 272 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index ace7b417f..af4846bdf 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -61,8 +61,6 @@ - Removed events `statusChanged` and `statusMessageChanged`. Instead, you can listen on the `change:status` or `change:status\_message` events on `_converse.xmppstatus`. -- Use flexbox instead of JavaScript to keep chat scrolled down. Due to this - change, messages are now inserted into the DOM in reverse order than before. ### API changes diff --git a/sass/_chatbox.scss b/sass/_chatbox.scss index 05ce467cf..008c11b4d 100644 --- a/sass/_chatbox.scss +++ b/sass/_chatbox.scss @@ -207,8 +207,6 @@ margin-bottom: 0.25em; } .chat-content { - display: flex; - flex-direction: column-reverse; padding: 1em 0; height: 100%; font-size: var(--message-font-size); diff --git a/sass/_messages.scss b/sass/_messages.scss index 1020e7054..c28c5846b 100644 --- a/sass/_messages.scss +++ b/sass/_messages.scss @@ -77,9 +77,10 @@ } &.chat-msg { - display: flex; + display: inline-flex; width: 100%; flex-direction: row; + overflow: auto; // Ensures that content stays inside padding: 0.125rem 1rem; &.onload { @@ -151,6 +152,7 @@ display: flex; flex-direction: row; justify-content: space-between; + width: 100%; } .chat-msg__message { @@ -267,7 +269,7 @@ } &.chat-msg--action { .chat-msg__content { - flex-wrap: wrap; + flex-wrap: nowrap; flex-direction: row; justify-content: flex-start; } diff --git a/spec/chatbox.js b/spec/chatbox.js index 838ede461..b82bc3edb 100644 --- a/spec/chatbox.js +++ b/spec/chatbox.js @@ -44,7 +44,7 @@ }).c('body').t('hello world').tree(); await _converse.chatboxes.onMessage(msg); await test_utils.waitUntil(() => view.content.querySelectorAll('.chat-msg').length); - expect(view.content.firstElementChild.textContent.trim().indexOf('hello world')).not.toBe(-1); + expect(view.content.lastElementChild.textContent.trim().indexOf('hello world')).not.toBe(-1); done(); })); @@ -78,22 +78,22 @@ message = '/me is as well'; await test_utils.sendMessage(view, message); expect(view.el.querySelectorAll('.chat-msg--action').length).toBe(2); - await test_utils.waitUntil(() => sizzle('.chat-msg__author:first', view.el).pop().textContent.trim() === '**Romeo Montague'); - const last_el = sizzle('.chat-msg__text:first', view.el).pop(); + await test_utils.waitUntil(() => sizzle('.chat-msg__author:last', view.el).pop().textContent.trim() === '**Romeo Montague'); + const last_el = sizzle('.chat-msg__text:last', view.el).pop(); expect(last_el.textContent).toBe('is as well'); expect(u.hasClass('chat-msg--followup', last_el)).toBe(false); // Check that /me messages after a normal message don't // get the 'chat-msg--followup' class. message = 'This a normal message'; await test_utils.sendMessage(view, message); - let message_el = view.el.querySelector('.message:first-child'); + let message_el = view.el.querySelector('.message:last-child'); expect(u.hasClass('chat-msg--followup', message_el)).toBeFalsy(); message = '/me wrote a 3rd person message'; await test_utils.sendMessage(view, message); - message_el = view.el.querySelector('.message:first-child'); + message_el = view.el.querySelector('.message:last-child'); expect(view.el.querySelectorAll('.chat-msg--action').length).toBe(3); - expect(sizzle('.chat-msg__text:first', view.el).pop().textContent).toBe('wrote a 3rd person message'); - expect(u.isVisible(sizzle('.chat-msg__author:first', view.el).pop())).toBeTruthy(); + expect(sizzle('.chat-msg__text:last', view.el).pop().textContent).toBe('wrote a 3rd person message'); + expect(u.isVisible(sizzle('.chat-msg__author:last', view.el).pop())).toBeTruthy(); expect(u.hasClass('chat-msg--followup', message_el)).toBeFalsy(); done(); })); @@ -267,7 +267,7 @@ const jid = el.textContent.replace(/ /g,'.').toLowerCase() + '@montague.lit'; spyOn(_converse.api, "trigger"); el.click(); - await test_utils.waitUntil(() => _converse.api.trigger.calls.count(), 1000); + await test_utils.waitUntil(() => _converse.api.trigger.calls.count(), 500); expect(_converse.chatboxes.length).toEqual(2); expect(_converse.api.trigger).toHaveBeenCalledWith('chatBoxFocused', jasmine.any(Object)); done(); diff --git a/spec/messages.js b/spec/messages.js index f3f01d285..31b5bc3ef 100644 --- a/spec/messages.js +++ b/spec/messages.js @@ -260,7 +260,7 @@ expect(view.model.messages.at(0).get('correcting')).toBeFalsy(); expect(view.model.messages.at(1).get('correcting')).toBeFalsy(); expect(view.model.messages.at(2).get('correcting')).toBe(true); - await test_utils.waitUntil(() => u.hasClass('correcting', sizzle('.chat-msg:first', view.el).pop()), 500); + await test_utils.waitUntil(() => u.hasClass('correcting', sizzle('.chat-msg:last', view.el).pop()), 500); textarea.selectionEnd = 0; // Happens by pressing up, // but for some reason not in tests, so we set it manually. @@ -285,11 +285,11 @@ expect(textarea.value).toBe(''); const messages = view.el.querySelectorAll('.chat-msg'); expect(messages.length).toBe(3); - expect(messages[2].querySelector('.chat-msg__text').textContent) + expect(messages[0].querySelector('.chat-msg__text').textContent) .toBe('But soft, what light through yonder window breaks?'); expect(messages[1].querySelector('.chat-msg__text').textContent) .toBe('It is the east, and Juliet is the sun.'); - expect(messages[0].querySelector('.chat-msg__text').textContent) + expect(messages[2].querySelector('.chat-msg__text').textContent) .toBe('Arise, fair sun, and kill the envious moon'); expect(view.model.messages.at(0).get('correcting')).toBeFalsy(); @@ -434,53 +434,53 @@ view.clearSpinner(); //cleanup expect(chat_content.querySelectorAll('.date-separator').length).toEqual(4); - let day = sizzle('.date-separator:last', chat_content).pop(); + let day = sizzle('.date-separator:first', chat_content).pop(); expect(day.getAttribute('data-isodate')).toEqual(dayjs('2017-12-31T00:00:00').toISOString()); - let time = sizzle('time:last', chat_content).pop(); + let time = sizzle('time:first', chat_content).pop(); expect(time.textContent).toEqual('Sunday Dec 31st 2017') - day = sizzle('.date-separator:last', chat_content).pop(); - expect(day.previousElementSibling.querySelector('.chat-msg__text').textContent).toBe('Older message'); + day = sizzle('.date-separator:first', chat_content).pop(); + expect(day.nextElementSibling.querySelector('.chat-msg__text').textContent).toBe('Older message'); - let el = sizzle('.chat-msg:last', chat_content).pop().querySelector('.chat-msg__text') + let el = sizzle('.chat-msg:first', chat_content).pop().querySelector('.chat-msg__text') expect(u.hasClass('chat-msg--followup', el)).toBe(false); expect(el.textContent).toEqual('Older message'); - time = sizzle('time.separator-text:eq(2)', chat_content).pop(); + time = sizzle('time.separator-text:eq(1)', chat_content).pop(); expect(time.textContent).toEqual("Monday Jan 1st 2018"); - day = sizzle('.date-separator:eq(2)', chat_content).pop(); + day = sizzle('.date-separator:eq(1)', chat_content).pop(); expect(day.getAttribute('data-isodate')).toEqual(dayjs('2018-01-01T00:00:00').toISOString()); - expect(day.previousElementSibling.querySelector('.chat-msg__text').textContent).toBe('Inbetween message'); + expect(day.nextElementSibling.querySelector('.chat-msg__text').textContent).toBe('Inbetween message'); - el = sizzle('.chat-msg:eq(5)', chat_content).pop(); + el = sizzle('.chat-msg:eq(1)', chat_content).pop(); expect(el.querySelector('.chat-msg__text').textContent).toEqual('Inbetween message'); - expect(el.previousElementSibling.querySelector('.chat-msg__text').textContent).toEqual('another inbetween message'); - el = sizzle('.chat-msg:eq(4)', chat_content).pop(); + expect(el.nextElementSibling.querySelector('.chat-msg__text').textContent).toEqual('another inbetween message'); + el = sizzle('.chat-msg:eq(2)', chat_content).pop(); expect(el.querySelector('.chat-msg__text').textContent) .toEqual('another inbetween message'); expect(u.hasClass('chat-msg--followup', el)).toBe(true); - time = sizzle('time.separator-text:nth(1)', chat_content).pop(); + time = sizzle('time.separator-text:nth(2)', chat_content).pop(); expect(time.textContent).toEqual("Tuesday Jan 2nd 2018"); - day = sizzle('.date-separator:nth(1)', chat_content).pop(); + day = sizzle('.date-separator:nth(2)', chat_content).pop(); expect(day.getAttribute('data-isodate')).toEqual(dayjs('2018-01-02T00:00:00').toISOString()); - expect(day.previousElementSibling.querySelector('.chat-msg__text').textContent).toBe('An earlier message on the next day'); + expect(day.nextElementSibling.querySelector('.chat-msg__text').textContent).toBe('An earlier message on the next day'); el = sizzle('.chat-msg:eq(3)', chat_content).pop(); expect(el.querySelector('.chat-msg__text').textContent).toEqual('An earlier message on the next day'); expect(u.hasClass('chat-msg--followup', el)).toBe(false); - el = sizzle('.chat-msg:eq(2)', chat_content).pop(); + el = sizzle('.chat-msg:eq(4)', chat_content).pop(); expect(el.querySelector('.chat-msg__text').textContent).toEqual('message'); - expect(el.previousElementSibling.querySelector('.chat-msg__text').textContent).toEqual('newer message from the next day'); + expect(el.nextElementSibling.querySelector('.chat-msg__text').textContent).toEqual('newer message from the next day'); expect(u.hasClass('chat-msg--followup', el)).toBe(false); - day = sizzle('.date-separator:first', chat_content).pop(); + day = sizzle('.date-separator:last', chat_content).pop(); expect(day.getAttribute('data-isodate')).toEqual(dayjs().startOf('day').toISOString()); - expect(day.previousElementSibling.querySelector('.chat-msg__text').textContent).toBe('latest message'); + expect(day.nextElementSibling.querySelector('.chat-msg__text').textContent).toBe('latest message'); expect(u.hasClass('chat-msg--followup', el)).toBe(false); done(); })); @@ -800,12 +800,12 @@ expect(chat_content.querySelectorAll('time.separator-text').length).toEqual(2); // There are now two time elements const message_date = new Date(); - day = sizzle('.date-separator:first', chat_content); + day = sizzle('.date-separator:last', chat_content); expect(day.length).toEqual(1); expect(day[0].getAttribute('class')).toEqual('message date-separator'); expect(day[0].getAttribute('data-isodate')).toEqual(dayjs(message_date).startOf('day').toISOString()); - time = sizzle('time.separator-text:first', chat_content).pop(); + time = sizzle('time.separator-text:last', chat_content).pop(); expect(time.textContent).toEqual(dayjs(message_date).startOf('day').format("dddd MMM Do YYYY")); // Normal checks for the 2nd message @@ -815,12 +815,12 @@ expect(msg_obj.get('fullname')).toBeUndefined(); expect(msg_obj.get('sender')).toEqual('them'); expect(msg_obj.get('is_delayed')).toEqual(false); - const msg_txt = sizzle('.chat-msg:first .chat-msg__text', chat_content).pop().textContent; + const msg_txt = sizzle('.chat-msg:last .chat-msg__text', chat_content).pop().textContent; expect(msg_txt).toEqual(message); - expect(chat_content.querySelector('.chat-msg:first-child .chat-msg__text').textContent).toEqual(message); - expect(chat_content.querySelector('.chat-msg:first-child .chat-msg__time').textContent.match(/^[0-9][0-9]:[0-9][0-9]/)).toBeTruthy(); - expect(chat_content.querySelector('.chat-msg:first-child .chat-msg__author').textContent.trim()).toBe('Juliet Capulet'); + expect(chat_content.querySelector('.chat-msg:last-child .chat-msg__text').textContent).toEqual(message); + expect(chat_content.querySelector('.chat-msg:last-child .chat-msg__time').textContent.match(/^[0-9][0-9]:[0-9][0-9]/)).toBeTruthy(); + expect(chat_content.querySelector('.chat-msg:last-child .chat-msg__author').textContent.trim()).toBe('Juliet Capulet'); done(); })); @@ -911,21 +911,21 @@ message = 'http://www.opkode.com/"onmouseover="alert(1)"whatever'; await test_utils.sendMessage(view, message); - msg = sizzle('.chat-content .chat-msg:first .chat-msg__text', view.el).pop(); + msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop(); expect(msg.textContent).toEqual(message); expect(msg.innerHTML).toEqual('http://www.opkode.com/"onmouseover="alert(1)"whatever'); message = "https://en.wikipedia.org/wiki/Ender's_Game"; await test_utils.sendMessage(view, message); - msg = sizzle('.chat-content .chat-msg:first .chat-msg__text', view.el).pop(); + msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop(); expect(msg.textContent).toEqual(message); expect(msg.innerHTML).toEqual(''+message+''); message = ""; await test_utils.sendMessage(view, message); - msg = sizzle('.chat-content .chat-msg:first .chat-msg__text', view.el).pop(); + msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop(); expect(msg.textContent).toEqual(message); expect(msg.innerHTML).toEqual( `<https://bugs.documentfoundation.org/show_bug.cgi?id=123737>`); @@ -933,7 +933,7 @@ message = ''; await test_utils.sendMessage(view, message); - msg = sizzle('.chat-content .chat-msg:first .chat-msg__text', view.el).pop(); + msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop(); expect(msg.textContent).toEqual(message); expect(msg.innerHTML).toEqual( '<http://www.opkode.com/"onmouseover="alert(1)"whatever>'); @@ -941,7 +941,7 @@ message = `https://www.google.com/maps/place/Kochstraat+6,+2041+CE+Zandvoort/@52.3775999,4.548971,3a,15y,170.85h,88.39t/data=!3m6!1e1!3m4!1sQ7SdHo_bPLPlLlU8GSGWaQ!2e0!7i13312!8i6656!4m5!3m4!1s0x47c5ec1e56f845ad:0x1de0bc4a5771fb08!8m2!3d52.3773668!4d4.5489388!5m1!1e2` await test_utils.sendMessage(view, message); - msg = sizzle('.chat-content .chat-msg:first .chat-msg__text', view.el).pop(); + msg = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop(); expect(msg.textContent).toEqual(message); expect(msg.innerHTML).toEqual( `https://www.google.com/maps/place/Kochstraat+6,+2041+CE+Zandvoort/@52.3775999,4.548971,3a,15y,170.85h,88.39t/data=!3m6!1e1!3m4!1sQ7SdHo_bPLPlLlU8GSGWaQ!2e0!7i13312!8i6656!4m5!3m4!1s0x47c5ec1e56f845ad:0x1de0bc4a5771fb08!8m2!3d52.3773668!4d4.5489388!5m1!1e2`); @@ -998,7 +998,7 @@ `); _converse.connection._dataRecv(test_utils.createRequest(stanza)); await new Promise((resolve, reject) => view.once('messageInserted', resolve)); - expect(chat_content.querySelector('.message:first-child .chat-msg__text').innerHTML).toBe('Hey

Have you heard the news?'); + expect(chat_content.querySelector('.message:last-child .chat-msg__text').innerHTML).toBe('Hey

Have you heard the news?'); stanza = u.toStanza(` `); _converse.connection._dataRecv(test_utils.createRequest(stanza)); await new Promise((resolve, reject) => view.once('messageInserted', resolve)); - expect(chat_content.querySelector('.message:first-child .chat-msg__text').innerHTML).toBe('Hey
Have you heard
the news?'); + expect(chat_content.querySelector('.message:last-child .chat-msg__text').innerHTML).toBe('Hey
Have you heard
the news?'); done(); })); @@ -1026,7 +1026,7 @@ test_utils.sendMessage(view, message); await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-image').length, 1000) expect(view.model.sendMessage).toHaveBeenCalled(); - let msg = sizzle('.chat-content .chat-msg:first .chat-msg__text').pop(); + let msg = sizzle('.chat-content .chat-msg:last .chat-msg__text').pop(); expect(msg.innerHTML.trim()).toEqual( '\n'+ ' view.el.querySelectorAll('.chat-content .chat-image').length === 2, 1000); expect(view.model.sendMessage).toHaveBeenCalled(); - msg = sizzle('.chat-content .chat-msg:first .chat-msg__text').pop(); + msg = sizzle('.chat-content .chat-msg:last .chat-msg__text').pop(); expect(msg.innerHTML.trim()).toEqual( '\n'+ ' view.el.querySelectorAll('.chat-content .chat-image').length === 4, 1000); expect(view.model.sendMessage).toHaveBeenCalled(); - msg = sizzle('.chat-content .chat-msg:first .chat-msg__text').pop(); + msg = sizzle('.chat-content .chat-msg:last .chat-msg__text').pop(); expect(msg.textContent.trim()).toEqual('hello world'); expect(msg.querySelectorAll('img').length).toEqual(2); @@ -1076,10 +1076,10 @@ expect(chatbox.messages.models.length, 1); const msg_object = chatbox.messages.models[0]; - const msg_author = view.el.querySelector('.chat-content .chat-msg:first-child .chat-msg__author'); + const msg_author = view.el.querySelector('.chat-content .chat-msg:last-child .chat-msg__author'); expect(msg_author.textContent.trim()).toBe('Romeo Montague'); - const msg_time = view.el.querySelector('.chat-content .chat-msg:first-child .chat-msg__time'); + const msg_time = view.el.querySelector('.chat-content .chat-msg:last-child .chat-msg__time'); const time = dayjs(msg_object.get('time')).format(_converse.time_format); expect(msg_time.textContent).toBe(time); done(); @@ -1165,19 +1165,19 @@ expect(chat_content.querySelectorAll('.message').length).toBe(6); expect(chat_content.querySelectorAll('.chat-msg').length).toBe(5); - expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(5)'))).toBe(false); - expect(chat_content.querySelector('.message:nth-child(5) .chat-msg__text').textContent).toBe("A message"); - expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(4)'))).toBe(true); - expect(chat_content.querySelector('.message:nth-child(4) .chat-msg__text').textContent).toBe( - "Another message 3 minutes later"); - expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(3)'))).toBe(false); + expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(2)'))).toBe(false); + expect(chat_content.querySelector('.message:nth-child(2) .chat-msg__text').textContent).toBe("A message"); + expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(3)'))).toBe(true); expect(chat_content.querySelector('.message:nth-child(3) .chat-msg__text').textContent).toBe( + "Another message 3 minutes later"); + expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(4)'))).toBe(false); + expect(chat_content.querySelector('.message:nth-child(4) .chat-msg__text').textContent).toBe( "Another message 14 minutes since we started"); - expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(2)'))).toBe(true); - expect(chat_content.querySelector('.message:nth-child(2) .chat-msg__text').textContent).toBe( + expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(5)'))).toBe(true); + expect(chat_content.querySelector('.message:nth-child(5) .chat-msg__text').textContent).toBe( "Another message 1 minute and 1 second since the previous one"); - expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(1)'))).toBe(false); - expect(chat_content.querySelector('.message:nth-child(1) .chat-msg__text').textContent).toBe( + expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(6)'))).toBe(false); + expect(chat_content.querySelector('.message:nth-child(6) .chat-msg__text').textContent).toBe( "Another message within 10 minutes, but from a different person"); // Let's add a delayed, inbetween message @@ -1197,21 +1197,21 @@ expect(chat_content.querySelectorAll('.message').length).toBe(7); expect(chat_content.querySelectorAll('.chat-msg').length).toBe(6); - expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(6)'))).toBe(false); - expect(chat_content.querySelector('.message:nth-child(6) .chat-msg__text').textContent).toBe("A message"); - expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(5)'))).toBe(true); - expect(chat_content.querySelector('.message:nth-child(5) .chat-msg__text').textContent).toBe( + expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(2)'))).toBe(false); + expect(chat_content.querySelector('.message:nth-child(2) .chat-msg__text').textContent).toBe("A message"); + expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(3)'))).toBe(true); + expect(chat_content.querySelector('.message:nth-child(3) .chat-msg__text').textContent).toBe( "Another message 3 minutes later"); expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(4)'))).toBe(true); expect(chat_content.querySelector('.message:nth-child(4) .chat-msg__text').textContent).toBe( "A delayed message, sent 5 minutes since we started"); - expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(3)'))).toBe(true); - expect(chat_content.querySelector('.message:nth-child(3) .chat-msg__text').textContent).toBe( + expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(5)'))).toBe(true); + expect(chat_content.querySelector('.message:nth-child(5) .chat-msg__text').textContent).toBe( "Another message 14 minutes since we started"); - expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(2)'))).toBe(true); - expect(chat_content.querySelector('.message:nth-child(2) .chat-msg__text').textContent).toBe( + expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(6)'))).toBe(true); + expect(chat_content.querySelector('.message:nth-child(6) .chat-msg__text').textContent).toBe( "Another message 1 minute and 1 second since the previous one"); - expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(1)'))).toBe(false); + expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(7)'))).toBe(false); _converse.chatboxes.onMessage($msg({'id': 'aeb213', 'to': _converse.bare_jid}) .c('forwarded', {'xmlns': 'urn:xmpp:forward:0'}) @@ -1227,25 +1227,25 @@ expect(chat_content.querySelectorAll('.message').length).toBe(8); expect(chat_content.querySelectorAll('.chat-msg').length).toBe(7); - expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(7)'))).toBe(false); - expect(chat_content.querySelector('.message:nth-child(7) .chat-msg__text').textContent).toBe("A message"); - expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(6)'))).toBe(true); - expect(chat_content.querySelector('.message:nth-child(6) .chat-msg__text').textContent).toBe( - "Another message 3 minutes later"); - expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(5)'))).toBe(false); - expect(chat_content.querySelector('.message:nth-child(5) .chat-msg__text').textContent).toBe( - "A carbon message 4 minutes later"); - expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(4)'))).toBe(false); - expect(chat_content.querySelector('.message:nth-child(4) .chat-msg__text').textContent).toBe( - "A delayed message, sent 5 minutes since we started"); + expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(2)'))).toBe(false); + expect(chat_content.querySelector('.message:nth-child(2) .chat-msg__text').textContent).toBe("A message"); expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(3)'))).toBe(true); expect(chat_content.querySelector('.message:nth-child(3) .chat-msg__text').textContent).toBe( + "Another message 3 minutes later"); + expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(4)'))).toBe(false); + expect(chat_content.querySelector('.message:nth-child(4) .chat-msg__text').textContent).toBe( + "A carbon message 4 minutes later"); + expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(5)'))).toBe(false); + expect(chat_content.querySelector('.message:nth-child(5) .chat-msg__text').textContent).toBe( + "A delayed message, sent 5 minutes since we started"); + expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(6)'))).toBe(true); + expect(chat_content.querySelector('.message:nth-child(6) .chat-msg__text').textContent).toBe( "Another message 14 minutes since we started"); - expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(2)'))).toBe(true); - expect(chat_content.querySelector('.message:nth-child(2) .chat-msg__text').textContent).toBe( + expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(7)'))).toBe(true); + expect(chat_content.querySelector('.message:nth-child(7) .chat-msg__text').textContent).toBe( "Another message 1 minute and 1 second since the previous one"); - expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(1)'))).toBe(false); - expect(chat_content.querySelector('.message:nth-child(1) .chat-msg__text').textContent).toBe( + expect(u.hasClass('chat-msg--followup', chat_content.querySelector('.message:nth-child(8)'))).toBe(false); + expect(chat_content.querySelector('.message:nth-child(8) .chat-msg__text').textContent).toBe( "Another message within 10 minutes, but from a different person"); jasmine.clock().uninstall(); @@ -1683,7 +1683,7 @@ }); view.model.sendMessage(msg_text); await new Promise((resolve, reject) => view.once('messageInserted', resolve)); - msg_txt = sizzle('.chat-msg:first .chat-msg__text', chat_content).pop().textContent; + msg_txt = sizzle('.chat-msg:last .chat-msg__text', chat_content).pop().textContent; expect(msg_txt).toEqual(msg_text); /* view.once('messageInserted', resolve)); - msg_txt = sizzle('.chat-msg:first .chat-msg__text', chat_content).pop().textContent; + msg_txt = sizzle('.chat-msg:last .chat-msg__text', chat_content).pop().textContent; expect(msg_txt).toEqual(msg_text); // A different error message will however render @@ -1831,20 +1831,14 @@ .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree()); promises.push(new Promise((resolve, reject) => view.once('messageInserted', resolve))); } - await Promise.all(promises); // XXX Fails on Travis // await test_utils.waitUntil(() => view.content.scrollTop, 1000) await test_utils.waitUntil(() => !view.model.get('auto_scrolled'), 500); view.content.scrollTop = 0; // XXX Fails on Travis - // await test_utils.waitUntil(() => view.scrolled, 900); - view.scrolled = true; - - const text = Array.from(view.el.querySelectorAll('.chat-content .chat-msg__text')) - .map(e => e.textContent) - .join(' '); - expect(text).toBe(_.range(20).reverse().map(n => `Message: ${n}`).join(' ')); + // await test_utils.waitUntil(() => view.model.get('scrolled'), 900); + view.model.set('scrolled', true); const message = 'This message is received while the chat area is scrolled up'; _converse.chatboxes.onMessage($msg({ @@ -1858,10 +1852,10 @@ await test_utils.waitUntil(() => view.model.messages.length > 20, 1000); // Now check that the message appears inside the chatbox in the DOM const chat_content = view.el.querySelector('.chat-content'); - const msg_txt = sizzle('.chat-content .chat-msg:first .chat-msg__text', view.el).pop().textContent; + const msg_txt = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop().textContent; expect(msg_txt).toEqual(message); await test_utils.waitUntil(() => u.isVisible(view.el.querySelector('.new-msgs-indicator')), 900); - expect(view.scrolled).toBe(true); + expect(view.model.get('scrolled')).toBe(true); expect(view.content.scrollTop).toBe(0); expect(u.isVisible(view.el.querySelector('.new-msgs-indicator'))).toBeTruthy(); // Scroll down again @@ -1962,9 +1956,9 @@ `); _converse.connection._dataRecv(test_utils.createRequest(stanza)); await new Promise((resolve, reject) => view.once('messageInserted', resolve)); - msg = view.el.querySelector('.chat-msg:first-child .chat-msg__text'); + msg = view.el.querySelector('.chat-msg:last-child .chat-msg__text'); expect(msg.innerHTML).toEqual(''); // Emtpy - media = view.el.querySelector('.chat-msg:first-child .chat-msg__media'); + media = view.el.querySelector('.chat-msg:last-child .chat-msg__media'); expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "")).toEqual( ''+ ''+ @@ -2012,9 +2006,9 @@
`); _converse.connection._dataRecv(test_utils.createRequest(stanza)); await new Promise((resolve, reject) => view.once('messageInserted', resolve)); - msg = view.el.querySelector('.chat-msg:first-child .chat-msg__text'); + msg = view.el.querySelector('.chat-msg:last-child .chat-msg__text'); expect(msg.innerHTML).toEqual(''); // Emtpy - media = view.el.querySelector('.chat-msg:first-child .chat-msg__media'); + media = view.el.querySelector('.chat-msg:last-child .chat-msg__media'); expect(media.innerHTML.replace(/(\r\n|\n|\r)/gm, "")).toEqual( ''+ ''+ @@ -2355,7 +2349,7 @@ expect(view.model.messages.last().get('affiliation')).toBe('member'); expect(view.model.messages.last().get('role')).toBe('participant'); expect(view.el.querySelectorAll('.chat-msg').length).toBe(2); - expect(sizzle('.chat-msg__author:first', view.el).pop().classList.value.trim()).toBe('chat-msg__author participant'); + expect(sizzle('.chat-msg__author', view.el).pop().classList.value.trim()).toBe('chat-msg__author participant'); presence = $pres({ to:'romeo@montague.lit/orchard', @@ -2561,7 +2555,7 @@ 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(2); - await test_utils.waitUntil(() => u.hasClass('correcting', sizzle('.chat-msg.groupchat:last', view.el).pop()), 500); + await test_utils.waitUntil(() => u.hasClass('correcting', view.el.querySelector('.chat-msg')), 500); expect(textarea.value).toBe('But soft, what light through yonder window breaks?'); view.onKeyDown({ target: textarea, diff --git a/spec/muc.js b/spec/muc.js index 9cd2c7ac8..1ec5839da 100644 --- a/spec/muc.js +++ b/spec/muc.js @@ -375,8 +375,8 @@ await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-info').length === 2); const info_texts = Array.from(view.el.querySelectorAll('.chat-content .chat-info')).map(e => e.textContent); - expect(info_texts[1]).toBe('A new groupchat has been created'); - expect(info_texts[0]).toBe('nicky has entered the groupchat'); + expect(info_texts[0]).toBe('A new groupchat has been created'); + expect(info_texts[1]).toBe('nicky has entered the groupchat'); // An instant room is created by saving the default configuratoin. // @@ -482,9 +482,9 @@ _converse.connection._dataRecv(test_utils.createRequest(presence)); await test_utils.waitUntil(() => chat_content.querySelectorAll('.chat-info').length === 2); - expect(sizzle('div.chat-info:last', chat_content).pop().textContent) - .toBe("This groupchat is not anonymous"); expect(sizzle('div.chat-info:first', chat_content).pop().textContent) + .toBe("This groupchat is not anonymous"); + expect(sizzle('div.chat-info:last', chat_content).pop().textContent) .toBe("some1 has entered the groupchat"); done(); })); @@ -495,7 +495,7 @@ null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, async function (done, _converse) { - await test_utils.openAndEnterChatRoom(_converse, "coven", 'chat.shakespeare.lit', 'some1'); + await test_utils.openChatRoom(_converse, "coven", 'chat.shakespeare.lit', 'some1'); const view = _converse.chatboxviews.get('coven@chat.shakespeare.lit'); const chat_content = view.el.querySelector('.chat-content'); /* We don't show join/leave messages for existing occupants. We @@ -512,10 +512,7 @@ 'role': 'participant' }); _converse.connection._dataRecv(test_utils.createRequest(presence)); - const info_msgs = sizzle('.chat-info', chat_content); - expect(info_msgs.length).toBe(2); - expect(info_msgs.pop().textContent).toBe('some1 has entered the groupchat'); - expect(info_msgs.pop().textContent).toBe('oldguy has entered the groupchat'); + expect(chat_content.querySelectorAll('div.chat-info').length).toBe(0); /* @@ -536,7 +533,7 @@ }).up() .c('status', {code: '110'}); _converse.connection._dataRecv(test_utils.createRequest(presence)); - expect(sizzle('div.chat-info:last', chat_content).pop().textContent) + expect(sizzle('div.chat-info:first', chat_content).pop().textContent) .toBe("some1 has entered the groupchat"); presence = $pres({ @@ -550,8 +547,8 @@ 'role': 'participant' }); _converse.connection._dataRecv(test_utils.createRequest(presence)); - expect(chat_content.querySelectorAll('div.chat-info').length).toBe(3); - expect(sizzle('div.chat-info:first', chat_content).pop().textContent) + expect(chat_content.querySelectorAll('div.chat-info').length).toBe(2); + expect(sizzle('div.chat-info:last', chat_content).pop().textContent) .toBe("newguy has entered the groupchat"); const msg = $msg({ @@ -576,8 +573,8 @@ 'role': 'participant' }); _converse.connection._dataRecv(test_utils.createRequest(presence)); - expect(chat_content.querySelectorAll('div.chat-info').length).toBe(4); - expect(sizzle('div.chat-info:first', chat_content).pop().textContent) + expect(chat_content.querySelectorAll('div.chat-info').length).toBe(3); + expect(sizzle('div.chat-info:last', chat_content).pop().textContent) .toBe("newgirl has entered the groupchat"); // Don't show duplicate join messages @@ -591,7 +588,7 @@ 'role': 'participant' }); _converse.connection._dataRecv(test_utils.createRequest(presence)); - expect(chat_content.querySelectorAll('div.chat-info').length).toBe(4); + expect(chat_content.querySelectorAll('div.chat-info').length).toBe(3); /* `); _converse.connection._dataRecv(test_utils.createRequest(presence)); expect(sizzle('div.chat-info', chat_content).length).toBe(2); - expect(sizzle('div.chat-info:first', chat_content).pop().textContent).toBe("fabio has entered the groupchat"); + expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe("fabio has entered the groupchat"); presence = u.toStanza( ` @@ -800,7 +796,7 @@ `); _converse.connection._dataRecv(test_utils.createRequest(presence)); expect(sizzle('div.chat-info', chat_content).length).toBe(3); - expect(sizzle('div.chat-info:first', chat_content).pop().textContent).toBe("Dele Olajide has entered the groupchat"); + expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe("Dele Olajide has entered the groupchat"); presence = u.toStanza( ` @@ -811,7 +807,7 @@ `); _converse.connection._dataRecv(test_utils.createRequest(presence)); expect(sizzle('div.chat-info', chat_content).length).toBe(4); - expect(sizzle('div.chat-info:first', chat_content).pop().textContent).toBe("jcbrand has entered the groupchat"); + expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe("jcbrand has entered the groupchat"); presence = u.toStanza( ` @@ -821,7 +817,7 @@ `); _converse.connection._dataRecv(test_utils.createRequest(presence)); expect(sizzle('div.chat-info', chat_content).length).toBe(4); - expect(sizzle('div.chat-info:first', chat_content).pop().textContent).toBe("Dele Olajide has entered and left the groupchat"); + expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe("Dele Olajide has entered and left the groupchat"); presence = u.toStanza( ` @@ -831,7 +827,7 @@ `); _converse.connection._dataRecv(test_utils.createRequest(presence)); expect(sizzle('div.chat-info', chat_content).length).toBe(4); - expect(sizzle('div.chat-info:first', chat_content).pop().textContent).toBe("Dele Olajide has entered the groupchat"); + expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe("Dele Olajide has entered the groupchat"); presence = u.toStanza( ` @@ -843,7 +839,7 @@ `); _converse.connection._dataRecv(test_utils.createRequest(presence)); expect(sizzle('div.chat-info', chat_content).length).toBe(5); - expect(sizzle('div.chat-info:first', chat_content).pop().textContent).toBe("fuvuv has entered the groupchat"); + expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe("fuvuv has entered the groupchat"); presence = u.toStanza( ` @@ -853,7 +849,7 @@ `); _converse.connection._dataRecv(test_utils.createRequest(presence)); expect(sizzle('div.chat-info', chat_content).length).toBe(5); - expect(sizzle('div.chat-info:first', chat_content).pop().textContent).toBe("fuvuv has entered and left the groupchat"); + expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe("fuvuv has entered and left the groupchat"); presence = u.toStanza( ` @@ -864,7 +860,7 @@ `); _converse.connection._dataRecv(test_utils.createRequest(presence)); expect(sizzle('div.chat-info', chat_content).length).toBe(5); - expect(sizzle('div.chat-info:first', chat_content).pop().textContent).toBe( + expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe( `fabio has entered and left the groupchat. "Disconnected: Replaced by new connection"`); presence = u.toStanza( @@ -877,7 +873,7 @@ `); _converse.connection._dataRecv(test_utils.createRequest(presence)); expect(sizzle('div.chat-info', chat_content).length).toBe(5); - expect(sizzle('div.chat-info:first', chat_content).pop().textContent).toBe( + expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe( `fabio has entered the groupchat. "Ready for a new day"`); // XXX: hack so that we can test leave/enter of occupants @@ -904,7 +900,7 @@ `); _converse.connection._dataRecv(test_utils.createRequest(presence)); expect(sizzle('div.chat-info', chat_content).length).toBe(2); - expect(sizzle('div.chat-info:first', chat_content).pop().textContent).toBe( + expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe( `Dele Olajide has left the groupchat`); presence = u.toStanza( @@ -916,7 +912,7 @@ `); _converse.connection._dataRecv(test_utils.createRequest(presence)); expect(sizzle('div.chat-info', chat_content).length).toBe(2); - expect(sizzle('div.chat-info:first', chat_content).pop().textContent).toBe( + expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe( `fabio has left and re-entered the groupchat`); done(); })); @@ -942,7 +938,7 @@ }); _converse.connection._dataRecv(test_utils.createRequest(presence)); expect(chat_content.querySelectorAll('div.chat-info').length).toBe(2); - expect(sizzle('.chat-info:first', chat_content).pop().textContent).toBe('newguy has entered the groupchat'); + expect(sizzle('div.chat-info', chat_content).pop().textContent).toBe('newguy has entered the groupchat'); presence = $pres({ to: 'romeo@montague.lit/orchard', @@ -958,7 +954,7 @@ }); _converse.connection._dataRecv(test_utils.createRequest(presence)); expect(chat_content.querySelectorAll('div.chat-info').length).toBe(2); - expect(sizzle('.chat-info:first', chat_content).pop().textContent).toBe('newguy has entered and left the groupchat'); + expect(sizzle('div.chat-info', chat_content).pop().textContent).toBe('newguy has entered and left the groupchat'); presence = u.toStanza( ` @@ -970,7 +966,7 @@ `); _converse.connection._dataRecv(test_utils.createRequest(presence)); - expect(sizzle('.chat-info:first', chat_content).pop().textContent).toBe(`fabio has entered the groupchat`); + expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe(`fabio has entered the groupchat`); presence = u.toStanza( ` @@ -979,8 +975,8 @@ `); _converse.connection._dataRecv(test_utils.createRequest(presence)); - expect(sizzle('.chat-info', chat_content).length).toBe(4); - expect(sizzle('.chat-info:first', chat_content).pop().textContent).toBe("Dele Olajide has entered the groupchat"); + expect(sizzle('div.chat-info', chat_content).length).toBe(4); + expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe("Dele Olajide has entered the groupchat"); await test_utils.sendMessage(view, 'hello world'); presence = u.toStanza( @@ -991,8 +987,8 @@ `); _converse.connection._dataRecv(test_utils.createRequest(presence)); - expect(sizzle('.chat-info', chat_content).length).toBe(5); - expect(sizzle('.chat-info:first', chat_content).pop().textContent).toBe(`Dele Olajide has left the groupchat`); + expect(sizzle('div.chat-info', chat_content).length).toBe(5); + expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe(`Dele Olajide has left the groupchat`); done(); })); @@ -1049,11 +1045,11 @@ _converse.connection._dataRecv(test_utils.createRequest(stanza)); const chat_content = view.el.querySelector('.chat-content'); - const messages = chat_content.querySelectorAll('.chat-info'); + const messages = chat_content.querySelectorAll('div.chat-info'); expect(messages.length).toBe(3); - expect(messages[2].textContent).toBe('romeo has entered the groupchat'); + expect(messages[0].textContent).toBe('romeo has entered the groupchat'); expect(messages[1].textContent).toBe('Guus has entered the groupchat'); - expect(messages[0].textContent).toBe('Guus has left and re-entered the groupchat'); + expect(messages[2].textContent).toBe('Guus has left and re-entered the groupchat'); done(); })); @@ -1106,8 +1102,8 @@ expect(indicator.getAttribute('data-isodate')).toEqual(dayjs().startOf('day').toISOString()); expect(indicator.querySelector('time').getAttribute('class')).toEqual('separator-text'); expect(indicator.querySelector('time').textContent).toEqual(dayjs().startOf('day').format("dddd MMM Do YYYY")); - expect(chat_content.querySelectorAll('.chat-info').length).toBe(2); - expect(chat_content.querySelector('.chat-info:first-child').textContent).toBe( + expect(chat_content.querySelectorAll('div.chat-info').length).toBe(2); + expect(chat_content.querySelector('div.chat-info:last-child').textContent).toBe( "some1 has entered the groupchat" ); @@ -1135,8 +1131,8 @@ expect(indicator.getAttribute('data-isodate')).toEqual(dayjs().startOf('day').toISOString()); expect(indicator.querySelector('time').textContent).toEqual(dayjs().startOf('day').format("dddd MMM Do YYYY")); - expect(chat_content.querySelectorAll('.chat-info').length).toBe(3); - expect(sizzle('div.chat-info:first', chat_content).pop().textContent).toBe( + expect(chat_content.querySelectorAll('div.chat-info').length).toBe(3); + expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe( 'some1 has left the groupchat. '+ '"Disconnected: Replaced by new connection"'); @@ -1167,12 +1163,12 @@ let time = chat_content.querySelectorAll('time.separator-text'); expect(time.length).toEqual(4); - indicator = sizzle('.date-separator:eq(0)', chat_content).pop(); + indicator = sizzle('.date-separator:eq(3)', chat_content).pop(); expect(indicator.getAttribute('class')).toEqual('message date-separator'); expect(indicator.getAttribute('data-isodate')).toEqual(dayjs().startOf('day').toISOString()); expect(indicator.querySelector('time').textContent).toEqual(dayjs().startOf('day').format("dddd MMM Do YYYY")); - expect(chat_content.querySelectorAll('.chat-info').length).toBe(4); - expect(sizzle('.chat-info:first', chat_content).pop().textContent) + expect(chat_content.querySelectorAll('div.chat-info').length).toBe(4); + expect(sizzle('div.chat-info:last', chat_content).pop().textContent) .toBe("newguy has entered the groupchat"); jasmine.clock().tick(ONE_DAY_LATER); @@ -1207,12 +1203,12 @@ time = chat_content.querySelectorAll('time.separator-text'); expect(time.length).toEqual(6); - indicator = sizzle('.date-separator:eq(0)', chat_content).pop(); + indicator = sizzle('.date-separator:eq(5)', chat_content).pop(); expect(indicator.getAttribute('class')).toEqual('message date-separator'); expect(indicator.getAttribute('data-isodate')).toEqual(dayjs().startOf('day').toISOString()); expect(indicator.querySelector('time').textContent).toEqual(dayjs().startOf('day').format("dddd MMM Do YYYY")); - expect(chat_content.querySelectorAll('.chat-info').length).toBe(5); - expect(sizzle('.chat-info:first', chat_content).pop().textContent).toBe( + expect(chat_content.querySelectorAll('div.chat-info').length).toBe(5); + expect(sizzle('div.chat-info:last', chat_content).pop().textContent).toBe( 'newguy has left the groupchat. '+ '"Disconnected: Replaced by new connection"'); jasmine.clock().uninstall(); @@ -1255,8 +1251,8 @@ }).c('body').t(message).tree(); await view.model.onMessage(msg); await new Promise((resolve, reject) => view.once('messageInserted', resolve)); - expect(_.includes(sizzle('.chat-msg__author:first', view.el).pop().textContent, '**Romeo Montague')).toBeTruthy(); - expect(sizzle('.chat-msg__text:first', view.el).pop().textContent).toBe('is as well'); + expect(_.includes(sizzle('.chat-msg__author:last', view.el).pop().textContent, '**Romeo Montague')).toBeTruthy(); + expect(sizzle('.chat-msg__text:last', view.el).pop().textContent).toBe('is as well'); done(); })); @@ -1803,7 +1799,7 @@ _converse.connection._dataRecv(test_utils.createRequest(presence)); await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-info').length === 2); - const info_text = sizzle('.chat-content .chat-info:last', view.el).pop().textContent; + const info_text = sizzle('.chat-content .chat-info:first', view.el).pop().textContent; expect(info_text).toBe('Your nickname has been automatically set to thirdwitch'); done(); })); @@ -2021,7 +2017,7 @@ // Now check that the message appears inside the chatbox in the DOM const chat_content = view.el.querySelector('.chat-content'); - const msg_txt = sizzle('.chat-msg:first .chat-msg__text', chat_content).pop().textContent; + const msg_txt = sizzle('.chat-msg:last .chat-msg__text', chat_content).pop().textContent; expect(msg_txt).toEqual(message); expect(view.content.scrollTop).toBe(0); done(); @@ -2159,8 +2155,8 @@ _converse.connection._dataRecv(test_utils.createRequest(stanza)); await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-info').length === 2); const info_messages = view.el.querySelectorAll('.chat-content .chat-info'); - expect(info_messages[1].textContent).toBe('romeo has entered the groupchat'); - expect(info_messages[0].textContent).toBe('groupchat logging is now enabled'); + expect(info_messages[0].textContent).toBe('romeo has entered the groupchat'); + expect(info_messages[1].textContent).toBe('groupchat logging is now enabled'); done(); })); @@ -2238,7 +2234,7 @@ _converse.connection._dataRecv(test_utils.createRequest(presence)); await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-info').length === 2); - expect(sizzle('.chat-info:first').pop().textContent).toBe( + expect(sizzle('div.chat-info:last').pop().textContent).toBe( __(_converse.muc.new_nickname_messages["303"], "newnick") ); expect(view.model.get('connection_status')).toBe(converse.ROOMSTATUS.ENTERED); @@ -2581,7 +2577,7 @@ _converse.connection._dataRecv(test_utils.createRequest(message)); await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-content .chat-info').length === 3); const chat_body = view.el.querySelector('.chatroom-body'); - expect(sizzle('.message:first', chat_body).pop().textContent) + expect(sizzle('.message:last', chat_body).pop().textContent) .toBe('This groupchat is now no longer anonymous'); done(); })); @@ -3181,7 +3177,7 @@ }); expect(view.validateRoleOrAffiliationChangeArgs).toHaveBeenCalled(); expect(view.showErrorMessage).toHaveBeenCalled(); - expect(view.el.querySelector('.message:first-child').textContent).toBe( + expect(view.el.querySelector('.message:last-child').textContent).toBe( "Error: the \"ban\" command takes two arguments, the user's nickname and optionally a reason."); expect(view.model.setAffiliation).not.toHaveBeenCalled(); @@ -3235,7 +3231,7 @@ textarea.value = '/ban joe22'; view.onFormSubmitted(new Event('submit')); - expect(view.el.querySelector('.message:first-child').textContent).toBe( + expect(view.el.querySelector('.message:last-child').textContent).toBe( "Error: couldn't find a groupchat participant based on your arguments"); done(); })); @@ -3326,7 +3322,7 @@ _converse.connection._dataRecv(test_utils.createRequest(presence)); await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-info').length === 4); - expect(view.el.querySelectorAll('.chat-info')[0].textContent).toBe("annoying guy has been kicked out"); + expect(view.el.querySelectorAll('.chat-info')[3].textContent).toBe("annoying guy has been kicked out"); expect(view.el.querySelectorAll('.chat-info').length).toBe(4); done(); })); @@ -3372,10 +3368,10 @@ 'role': 'participant' }); _converse.connection._dataRecv(test_utils.createRequest(presence)); - const info_msg = view.el.querySelector('.chat-info:first-child'); - expect(info_msg.textContent).toBe("trustworthyguy has entered the groupchat"); + var info_msgs = Array.prototype.slice.call(view.el.querySelectorAll('.chat-info'), 0); + expect(info_msgs.pop().textContent).toBe("trustworthyguy has entered the groupchat"); - const textarea = view.el.querySelector('.chat-textarea') + var textarea = view.el.querySelector('.chat-textarea') textarea.value = '/op'; view.onKeyDown({ target: textarea, @@ -3428,7 +3424,7 @@ 'role': 'moderator' }); _converse.connection._dataRecv(test_utils.createRequest(presence)); - let info_msgs = Array.prototype.slice.call(view.el.querySelectorAll('.chat-info'), 0); + 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 @@ -3502,7 +3498,7 @@ * * */ - let presence = $pres({ + var presence = $pres({ 'from': 'lounge@montague.lit/annoyingGuy', 'id':'27C55F89-1C6A-459A-9EB5-77690145D624', 'to': 'romeo@montague.lit/desktop' @@ -3514,8 +3510,8 @@ 'role': 'participant' }); _converse.connection._dataRecv(test_utils.createRequest(presence)); - const info_msg = sizzle('.chat-info:first', view.el).pop(); - expect(info_msg.textContent).toBe("annoyingGuy has entered the groupchat"); + var info_msgs = Array.prototype.slice.call(view.el.querySelectorAll('.chat-info'), 0); + expect(info_msgs.pop().textContent).toBe("annoyingGuy has entered the groupchat"); const textarea = view.el.querySelector('.chat-textarea') textarea.value = '/mute'; @@ -3569,7 +3565,7 @@ 'role': 'visitor' }); _converse.connection._dataRecv(test_utils.createRequest(presence)); - let info_msgs = Array.prototype.slice.call(view.el.querySelectorAll('.chat-info'), 0); + info_msgs = Array.prototype.slice.call(view.el.querySelectorAll('.chat-info'), 0); expect(info_msgs.pop().textContent).toBe("annoyingGuy has been muted"); // Call now with the correct of arguments. @@ -4707,7 +4703,7 @@ }); _converse.connection._dataRecv(test_utils.createRequest(presence)); expect(chat_content.querySelectorAll('div.chat-info').length).toBe(2); - expect(sizzle('.chat-info:first', chat_content).pop().textContent) + expect(sizzle('div.chat-info:last', chat_content).pop().textContent) .toBe("newguy has entered the groupchat"); presence = $pres({ @@ -4722,7 +4718,7 @@ }); _converse.connection._dataRecv(test_utils.createRequest(presence)); expect(chat_content.querySelectorAll('div.chat-info').length).toBe(3); - expect(sizzle('.chat-info:first', chat_content).pop().textContent) + expect(sizzle('div.chat-info:last', chat_content).pop().textContent) .toBe("nomorenicks has entered the groupchat"); // See XEP-0085 https://xmpp.org/extensions/xep-0085.html#definitions @@ -4741,9 +4737,9 @@ // Check that the notification appears inside the chatbox in the DOM let events = view.el.querySelectorAll('.chat-event'); expect(events.length).toBe(3); - expect(events[2].textContent).toEqual('some1 has entered the groupchat'); + expect(events[0].textContent).toEqual('some1 has entered the groupchat'); expect(events[1].textContent).toEqual('newguy has entered the groupchat'); - expect(events[0].textContent).toEqual('nomorenicks has entered the groupchat'); + expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat'); let notifications = view.el.querySelectorAll('.chat-state-notification'); expect(notifications.length).toBe(1); @@ -4763,9 +4759,9 @@ events = view.el.querySelectorAll('.chat-event'); expect(events.length).toBe(3); - expect(events[2].textContent).toEqual('some1 has entered the groupchat'); + expect(events[0].textContent).toEqual('some1 has entered the groupchat'); expect(events[1].textContent).toEqual('newguy has entered the groupchat'); - expect(events[0].textContent).toEqual('nomorenicks has entered the groupchat'); + expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat'); notifications = view.el.querySelectorAll('.chat-state-notification'); expect(notifications.length).toBe(1); @@ -4782,15 +4778,15 @@ await view.model.onMessage(msg); events = view.el.querySelectorAll('.chat-event'); expect(events.length).toBe(3); - expect(events[2].textContent).toEqual('some1 has entered the groupchat'); + expect(events[0].textContent).toEqual('some1 has entered the groupchat'); expect(events[1].textContent).toEqual('newguy has entered the groupchat'); - expect(events[0].textContent).toEqual('nomorenicks has entered the groupchat'); + expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat'); await test_utils.waitUntil(() => (view.el.querySelectorAll('.chat-state-notification').length === 2)); notifications = view.el.querySelectorAll('.chat-state-notification'); expect(notifications.length).toBe(2); - expect(notifications[1].textContent).toEqual('nomorenicks is typing'); - expect(notifications[0].textContent).toEqual('newguy is typing'); + expect(notifications[0].textContent).toEqual('nomorenicks is typing'); + expect(notifications[1].textContent).toEqual('newguy is typing'); // Check that new messages appear under the chat state notifications msg = $msg({ @@ -4811,9 +4807,9 @@ timeout_functions[0](); events = view.el.querySelectorAll('.chat-event'); expect(events.length).toBe(3); - expect(events[2].textContent).toEqual('some1 has entered the groupchat'); + expect(events[0].textContent).toEqual('some1 has entered the groupchat'); expect(events[1].textContent).toEqual('newguy has entered the groupchat'); - expect(events[0].textContent).toEqual('nomorenicks has entered the groupchat'); + expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat'); notifications = view.el.querySelectorAll('.chat-state-notification'); expect(notifications.length).toBe(1); @@ -4822,9 +4818,9 @@ timeout_functions[1](); events = view.el.querySelectorAll('.chat-event'); expect(events.length).toBe(3); - expect(events[2].textContent).toEqual('some1 has entered the groupchat'); + expect(events[0].textContent).toEqual('some1 has entered the groupchat'); expect(events[1].textContent).toEqual('newguy has entered the groupchat'); - expect(events[0].textContent).toEqual('nomorenicks has entered the groupchat'); + expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat'); notifications = view.el.querySelectorAll('.chat-state-notification'); expect(notifications.length).toBe(0); @@ -4877,7 +4873,7 @@ }); _converse.connection._dataRecv(test_utils.createRequest(presence)); expect(chat_content.querySelectorAll('div.chat-info').length).toBe(2); - expect(sizzle('.chat-info:first', chat_content).pop().textContent) + expect(sizzle('div.chat-info:last', chat_content).pop().textContent) .toBe("newguy has entered the groupchat"); presence = $pres({ @@ -4892,7 +4888,7 @@ }); _converse.connection._dataRecv(test_utils.createRequest(presence)); expect(chat_content.querySelectorAll('div.chat-info').length).toBe(3); - expect(sizzle('.chat-info:first', chat_content).pop().textContent) + expect(sizzle('div.chat-info:last', chat_content).pop().textContent) .toBe("nomorenicks has entered the groupchat"); // See XEP-0085 https://xmpp.org/extensions/xep-0085.html#definitions @@ -4909,9 +4905,9 @@ // Check that the notification appears inside the chatbox in the DOM var events = view.el.querySelectorAll('.chat-event'); expect(events.length).toBe(3); - expect(events[2].textContent).toEqual('some1 has entered the groupchat'); + expect(events[0].textContent).toEqual('some1 has entered the groupchat'); expect(events[1].textContent).toEqual('newguy has entered the groupchat'); - expect(events[0].textContent).toEqual('nomorenicks has entered the groupchat'); + expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat'); await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-state-notification').length); let notifications = view.el.querySelectorAll('.chat-state-notification'); @@ -4929,9 +4925,9 @@ events = view.el.querySelectorAll('.chat-event'); expect(events.length).toBe(3); - expect(events[2].textContent).toEqual('some1 has entered the groupchat'); + expect(events[0].textContent).toEqual('some1 has entered the groupchat'); expect(events[1].textContent).toEqual('newguy has entered the groupchat'); - expect(events[0].textContent).toEqual('nomorenicks has entered the groupchat'); + expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat'); notifications = view.el.querySelectorAll('.chat-state-notification'); expect(notifications.length).toBe(1); @@ -4947,9 +4943,9 @@ await view.model.onMessage(msg); events = view.el.querySelectorAll('.chat-event'); expect(events.length).toBe(3); - expect(events[2].textContent).toEqual('some1 has entered the groupchat'); + expect(events[0].textContent).toEqual('some1 has entered the groupchat'); expect(events[1].textContent).toEqual('newguy has entered the groupchat'); - expect(events[0].textContent).toEqual('nomorenicks has entered the groupchat'); + expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat'); await test_utils.waitUntil(() => view.el.querySelectorAll('.chat-state-notification').length === 2); notifications = view.el.querySelectorAll('.chat-state-notification'); @@ -4970,9 +4966,9 @@ await view.model.onMessage(msg); events = view.el.querySelectorAll('.chat-event'); expect(events.length).toBe(3); - expect(events[2].textContent).toEqual('some1 has entered the groupchat'); + expect(events[0].textContent).toEqual('some1 has entered the groupchat'); expect(events[1].textContent).toEqual('newguy has entered the groupchat'); - expect(events[0].textContent).toEqual('nomorenicks has entered the groupchat'); + expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat'); await test_utils.waitUntil(() => { return _.map( diff --git a/spec/omemo.js b/spec/omemo.js index 5fadf552f..3daa9b002 100644 --- a/spec/omemo.js +++ b/spec/omemo.js @@ -208,7 +208,7 @@ _converse.connection._dataRecv(test_utils.createRequest(stanza)); await new Promise((resolve, reject) => view.once('messageInserted', resolve)); expect(view.model.messages.length).toBe(2); - expect(view.el.querySelectorAll('.chat-msg__body')[0].textContent.trim()) + expect(view.el.querySelectorAll('.chat-msg__body')[1].textContent.trim()) .toBe('This is an encrypted message from the contact'); // #1193 Check for a received message without tag @@ -228,7 +228,7 @@ await new Promise((resolve, reject) => view.once('messageInserted', resolve)); await test_utils.waitUntil(() => view.model.messages.length > 1); expect(view.model.messages.length).toBe(3); - expect(view.el.querySelectorAll('.chat-msg__body')[0].textContent.trim()) + expect(view.el.querySelectorAll('.chat-msg__body')[2].textContent.trim()) .toBe('Another received encrypted message without fallback'); done(); })); diff --git a/src/converse-chatview.js b/src/converse-chatview.js index a91813190..48bb8e3f1 100644 --- a/src/converse-chatview.js +++ b/src/converse-chatview.js @@ -345,6 +345,7 @@ converse.plugins.add('converse-chatview', { initialize () { this.initDebounced(); this.model.messages.on('add', this.onMessageAdded, this); + this.model.messages.on('rendered', this.scrollDown, this); this.model.messages.on('reset', () => { this.content.innerHTML = ''; this.removeAll(); @@ -366,6 +367,7 @@ converse.plugins.add('converse-chatview', { }, initDebounced () { + this.scrollDown = _.debounce(this._scrollDown, 100); this.markScrolled = _.debounce(this._markScrolled, 100); this.show = _.debounce(this._show, 500, {'leading': true}); }, @@ -544,7 +546,8 @@ converse.plugins.add('converse-chatview', { await this.model.messages.fetched; await Promise.all(this.model.messages.map(m => this.onMessageAdded(m))); this.insertIntoDOM(); - this.content.addEventListener('scroll', () => this.markScrolled()); + this.scrollDown(); + this.content.addEventListener('scroll', this.markScrolled.bind(this)); }, insertIntoDOM () { @@ -565,23 +568,26 @@ converse.plugins.add('converse-chatview', { 'message': message, 'isodate': isodate, })); - this.insertDayIndicator(this.content.firstElementChild); + this.insertDayIndicator(this.content.lastElementChild); + this.scrollDown(); return isodate; }, showErrorMessage (message) { this.content.insertAdjacentHTML( - 'afterBegin', + 'beforeend', tpl_error_message({'message': message, 'isodate': (new Date()).toISOString() }) ); + this.scrollDown(); }, addSpinner (append=false) { if (_.isNull(this.el.querySelector('.spinner'))) { if (append) { - this.content.insertAdjacentHTML('afterBegin', tpl_spinner()); + this.content.insertAdjacentHTML('beforeend', tpl_spinner()); + this.scrollDown(); } else { - this.content.insertAdjacentHTML('beforeEnd', tpl_spinner()); + this.content.insertAdjacentHTML('afterbegin', tpl_spinner()); } } }, @@ -602,16 +608,16 @@ converse.plugins.add('converse-chatview', { * which specifies its creation date. */ insertDayIndicator (next_msg_el) { - const earlier_msg_el = u.getNextElement(next_msg_el, ".message:not(.chat-state-notification)"), - earlier_msg_date = _.isNull(earlier_msg_el) ? null : earlier_msg_el.getAttribute('data-isodate'), + const prev_msg_el = u.getPreviousElement(next_msg_el, ".message:not(.chat-state-notification)"), + prev_msg_date = _.isNull(prev_msg_el) ? null : prev_msg_el.getAttribute('data-isodate'), next_msg_date = next_msg_el.getAttribute('data-isodate'); - if (_.isNull(earlier_msg_date) && _.isNull(next_msg_date)) { + if (_.isNull(prev_msg_date) && _.isNull(next_msg_date)) { return; } - if (_.isNull(earlier_msg_date) || dayjs(next_msg_date).isAfter(earlier_msg_date, 'day')) { + if (_.isNull(prev_msg_date) || dayjs(next_msg_date).isAfter(prev_msg_date, 'day')) { const day_date = dayjs(next_msg_date).startOf('day'); - next_msg_el.insertAdjacentHTML('afterEnd', + next_msg_el.insertAdjacentHTML('beforeBegin', tpl_new_day({ 'isodate': day_date.toISOString(), 'datestring': day_date.format("dddd MMM Do YYYY") @@ -629,15 +635,14 @@ converse.plugins.add('converse-chatview', { * @returns { Date } */ getLastMessageDate (cutoff) { - const most_recent_msg = u.getFirstChildElement(this.content, '.message:not(.chat-state-notification)'); - const most_recent_date = most_recent_msg ? most_recent_msg.getAttribute('data-isodate') : null; - if (_.isNull(most_recent_date)) { + const first_msg = u.getFirstChildElement(this.content, '.message:not(.chat-state-notification)'); + const oldest_date = first_msg ? first_msg.getAttribute('data-isodate') : null; + if (!_.isNull(oldest_date) && dayjs(oldest_date).isAfter(cutoff)) { return null; } - - const oldest_message = u.getLastChildElement(this.content, '.message:not(.chat-state-notification)'); - const oldest_date = oldest_message ? oldest_message.getAttribute('data-isodate') : null; - if (!_.isNull(oldest_date) && dayjs(oldest_date).isAfter(cutoff)) { + const last_msg = u.getLastChildElement(this.content, '.message:not(.chat-state-notification)'); + const most_recent_date = last_msg ? last_msg.getAttribute('data-isodate') : null; + if (_.isNull(most_recent_date)) { return null; } if (dayjs(most_recent_date).isBefore(cutoff)) { @@ -664,6 +669,29 @@ converse.plugins.add('converse-chatview', { } }, + setScrollPosition (message_el) { + /* Given a newly inserted message, determine whether we + * should keep the scrollbar in place (so as to not scroll + * up when using infinite scroll). + */ + if (this.model.get('scrolled')) { + const next_msg_el = u.getNextElement(message_el, ".chat-msg"); + if (next_msg_el) { + // The currently received message is not new, there + // are newer messages after it. So let's see if we + // should maintain our current scroll position. + if (this.content.scrollTop === 0 || this.model.get('top_visible_message')) { + const top_visible_message = this.model.get('top_visible_message') || next_msg_el; + + this.model.set('top_visible_message', top_visible_message); + this.content.scrollTop = top_visible_message.offsetTop - 30; + } + } + } else { + this.scrollDown(); + } + }, + showHelpMessages (msgs, type, spinner) { msgs.forEach(msg => { this.content.insertAdjacentHTML( @@ -680,6 +708,7 @@ converse.plugins.add('converse-chatview', { } else if (spinner === false) { this.clearSpinner(); } + return this.scrollDown(); }, shouldShowOnTextMessage () { @@ -697,24 +726,24 @@ converse.plugins.add('converse-chatview', { if (view.model.get('type') === 'error') { const previous_msg_el = this.content.querySelector(`[data-msgid="${view.model.get('msgid')}"]`); if (previous_msg_el) { - previous_msg_el.insertAdjacentElement('beforeBegin', view.el); + previous_msg_el.insertAdjacentElement('afterend', view.el); return this.trigger('messageInserted', view.el); } } - const current_msg_date = dayjs(view.model.get('time')).toDate() || new Date(); - const previous_msg_date = this.getLastMessageDate(current_msg_date); + const current_msg_date = dayjs(view.model.get('time')).toDate() || new Date(), + previous_msg_date = this.getLastMessageDate(current_msg_date); if (_.isNull(previous_msg_date)) { - this.content.insertAdjacentElement('beforeEnd', view.el); + this.content.insertAdjacentElement('afterbegin', view.el); } else { - const previous_msg_el = sizzle(`[data-isodate="${previous_msg_date.toISOString()}"]:first`, this.content).pop(); + const previous_msg_el = sizzle(`[data-isodate="${previous_msg_date.toISOString()}"]:last`, this.content).pop(); if (view.model.get('type') === 'error' && u.hasClass('chat-error', previous_msg_el) && previous_msg_el.textContent === view.model.get('message')) { // We don't show a duplicate error message return; } - previous_msg_el.insertAdjacentElement('beforeBegin', view.el); + previous_msg_el.insertAdjacentElement('afterend', view.el); this.markFollowups(view.el); } return this.trigger('messageInserted', view.el); @@ -736,27 +765,26 @@ converse.plugins.add('converse-chatview', { * @param { HTMLElement } el - The message element */ markFollowups (el) { - const from = el.getAttribute('data-from'); - const earlier_msg_el = el.nextElementSibling; - const date = dayjs(el.getAttribute('data-isodate')); + const from = el.getAttribute('data-from'), + previous_el = el.previousElementSibling, + date = dayjs(el.getAttribute('data-isodate')), + next_el = el.nextElementSibling; - if (earlier_msg_el && - !u.hasClass('chat-msg--action', el) && !u.hasClass('chat-msg--action', earlier_msg_el) && - earlier_msg_el.getAttribute('data-from') === from && - date.isBefore(dayjs(earlier_msg_el.getAttribute('data-isodate')).add(10, 'minutes')) && - el.getAttribute('data-encrypted') === earlier_msg_el.getAttribute('data-encrypted')) { + if (!u.hasClass('chat-msg--action', el) && !u.hasClass('chat-msg--action', previous_el) && + previous_el.getAttribute('data-from') === from && + date.isBefore(dayjs(previous_el.getAttribute('data-isodate')).add(10, 'minutes')) && + el.getAttribute('data-encrypted') === previous_el.getAttribute('data-encrypted')) { u.addClass('chat-msg--followup', el); } + if (!next_el) { return; } - const later_msg_el = el.previousElementSibling; - if (!later_msg_el) { return; } if (!u.hasClass('chat-msg--action', el) && - later_msg_el.getAttribute('data-from') === from && - dayjs(later_msg_el.getAttribute('data-isodate')).isBefore(date.add(10, 'minutes')) && - el.getAttribute('data-encrypted') === later_msg_el.getAttribute('data-encrypted')) { - u.addClass('chat-msg--followup', later_msg_el); + next_el.getAttribute('data-from') === from && + dayjs(next_el.getAttribute('data-isodate')).isBefore(date.add(10, 'minutes')) && + el.getAttribute('data-encrypted') === next_el.getAttribute('data-encrypted')) { + u.addClass('chat-msg--followup', next_el); } else { - u.removeClass('chat-msg--followup', later_msg_el); + u.removeClass('chat-msg--followup', next_el); } }, @@ -781,6 +809,7 @@ converse.plugins.add('converse-chatview', { this.insertMessage(view); this.insertDayIndicator(view.el); + this.setScrollPosition(view.el); if (u.isNewMessage(message)) { if (message.get('sender') === 'me') { @@ -788,8 +817,8 @@ converse.plugins.add('converse-chatview', { // gets scrolled down. We always want to scroll down // when the user writes a message as opposed to when a // message is received. - this.scrolled = false; - } else if (this.scrolled && !u.isOnlyChatStateNotification(message)) { + this.model.set('scrolled', false); + } else if (this.model.get('scrolled', true) && !u.isOnlyChatStateNotification(message)) { this.showNewMessagesIndicator(); } } @@ -1230,6 +1259,7 @@ converse.plugins.add('converse-chatview', { 'message': text, 'isodate': (new Date()).toISOString(), })); + this.scrollDown(); } } }, @@ -1300,6 +1330,7 @@ converse.plugins.add('converse-chatview', { if (_converse.auto_focus) { this.focus(); } + this.focus(); }, _show () { @@ -1341,16 +1372,26 @@ converse.plugins.add('converse-chatview', { scrolled = false; this.onScrolledDown(); } - this.scrolled = scrolled; + u.safeSave(this.model, { + 'scrolled': scrolled, + 'top_visible_message': null + }); }, viewUnreadMessages () { - this.scrolled = false; + this.model.save({ + 'scrolled': false, + 'top_visible_message': null + }); this.scrollDown(); }, - scrollDown () { - if (this.content && u.isVisible(this.content) && !this.scrolled) { + _scrollDown () { + /* Inner method that gets debounced */ + if (_.isUndefined(this.content)) { + return; + } + if (u.isVisible(this.content) && !this.model.get('scrolled')) { this.content.scrollTop = this.content.scrollHeight; } }, diff --git a/src/converse-muc-views.js b/src/converse-muc-views.js index 65f5e04e7..a70d5cbad 100644 --- a/src/converse-muc-views.js +++ b/src/converse-muc-views.js @@ -470,6 +470,7 @@ converse.plugins.add('converse-muc-views', { this.initDebounced(); this.model.messages.on('add', this.onMessageAdded, this); + this.model.messages.on('rendered', this.scrollDown, this); this.model.messages.on('reset', () => { this.content.innerHTML = ''; this.removeAll(); @@ -679,6 +680,7 @@ converse.plugins.add('converse-muc-views', { this.model.clearUnreadMsgCounter(); this.model.save(); } + this.scrollDown(); this.renderEmojiPicker(); }, @@ -706,6 +708,7 @@ converse.plugins.add('converse-muc-views', { } else if (conn_status === converse.ROOMSTATUS.ENTERED) { this.hideSpinner(); this.setChatState(_converse.ACTIVE); + this.scrollDown(); if (_converse.auto_focus) { this.focus(); } @@ -763,6 +766,7 @@ converse.plugins.add('converse-muc-views', { ev.stopPropagation(); } this.model.save({'hidden_occupants': true}); + this.scrollDown(); }, toggleOccupants (ev) { @@ -774,6 +778,7 @@ converse.plugins.add('converse-muc-views', { ev.stopPropagation(); } this.model.save({'hidden_occupants': !this.model.get('hidden_occupants')}); + this.scrollDown(); }, onOccupantClicked (ev) { @@ -1321,7 +1326,7 @@ converse.plugins.add('converse-muc-views', { return; } if (!dayjs(el.getAttribute('data-isodate')).isSame(new Date(), "day")) { - el = el.nextElementSibling; + el = el.previousElementSibling; continue; } if (data.join === nick || @@ -1330,7 +1335,7 @@ converse.plugins.add('converse-muc-views', { data.joinleave === nick) { return el; } - el = el.nextElementSibling; + el = el.previousElementSibling; } }, @@ -1341,7 +1346,7 @@ converse.plugins.add('converse-muc-views', { } const nick = occupant.get('nick'), stat = _converse.muc_show_join_leave_status ? occupant.get('status') : null, - prev_info_el = this.getPreviousJoinOrLeaveNotification(this.content.firstElementChild, nick), + prev_info_el = this.getPreviousJoinOrLeaveNotification(this.content.lastElementChild, nick), data = _.get(prev_info_el, 'dataset', {}); if (data.leave === nick) { @@ -1359,8 +1364,8 @@ converse.plugins.add('converse-muc-views', { 'message': message }; this.content.removeChild(prev_info_el); - this.content.insertAdjacentHTML('afterBegin', tpl_info(data)); - const el = this.content.firstElementChild; + this.content.insertAdjacentHTML('beforeend', tpl_info(data)); + const el = this.content.lastElementChild; setTimeout(() => u.addClass('fade-out', el), 5000); setTimeout(() => el.parentElement && el.parentElement.removeChild(el), 5500); } else { @@ -1379,12 +1384,13 @@ converse.plugins.add('converse-muc-views', { }; if (prev_info_el) { this.content.removeChild(prev_info_el); - this.content.insertAdjacentHTML('afterBegin', tpl_info(data)); + this.content.insertAdjacentHTML('beforeend', tpl_info(data)); } else { - this.content.insertAdjacentHTML('afterBegin', tpl_info(data)); - this.insertDayIndicator(this.content.firstElementChild); + this.content.insertAdjacentHTML('beforeend', tpl_info(data)); + this.insertDayIndicator(this.content.lastElementChild); } } + this.scrollDown(); }, showLeaveNotification (occupant) { @@ -1395,7 +1401,7 @@ converse.plugins.add('converse-muc-views', { } const nick = occupant.get('nick'), stat = _converse.muc_show_join_leave_status ? occupant.get('status') : null, - prev_info_el = this.getPreviousJoinOrLeaveNotification(this.content.firstElementChild, nick), + prev_info_el = this.getPreviousJoinOrLeaveNotification(this.content.lastElementChild, nick), dataset = _.get(prev_info_el, 'dataset', {}); if (dataset.join === nick) { @@ -1413,8 +1419,8 @@ converse.plugins.add('converse-muc-views', { 'message': message }; this.content.removeChild(prev_info_el); - this.content.insertAdjacentHTML('afterBegin', tpl_info(data)); - const el = this.content.firstElementChild; + this.content.insertAdjacentHTML('beforeend', tpl_info(data)); + const el = this.content.lastElementChild; setTimeout(() => u.addClass('fade-out', el), 5000); setTimeout(() => el.parentElement && el.parentElement.removeChild(el), 5500); } else { @@ -1433,12 +1439,13 @@ converse.plugins.add('converse-muc-views', { } if (prev_info_el) { this.content.removeChild(prev_info_el); - this.content.insertAdjacentHTML('afterBegin', tpl_info(data)); + this.content.insertAdjacentHTML('beforeend', tpl_info(data)); } else { - this.content.insertAdjacentHTML('afterBegin', tpl_info(data)); - this.insertDayIndicator(this.content.firstElementChild); + this.content.insertAdjacentHTML('beforeend', tpl_info(data)); + this.insertDayIndicator(this.content.lastElementChild); } } + this.scrollDown(); }, renderAfterTransition () { @@ -1453,6 +1460,7 @@ converse.plugins.add('converse-muc-views', { } else { u.showElement(this.el.querySelector('.chat-area')); u.showElement(this.el.querySelector('.occupants')); + this.scrollDown(); } }, @@ -1502,6 +1510,7 @@ converse.plugins.add('converse-muc-views', { 'render_message': true })); } + this.scrollDown(); } });