Don't smooth-scroll upon first appearance of the chat

it's annoying in overlayed view-mode where the user might be navigating
around the site.

Fixes #2091
This commit is contained in:
JC Brand 2020-06-30 10:59:11 +02:00
parent 0c5f0e247b
commit f8efd94249
8 changed files with 42 additions and 53 deletions

View File

@ -100,10 +100,6 @@ body.converse-fullscreen {
background-color: #EEE !important;
}
.smooth-scroll {
scroll-behavior: smooth !important;
}
.nopadding {
padding: 0 !important;
}

View File

@ -1322,7 +1322,7 @@ describe("Chatboxes", function () {
['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) {
await mock.waitForRoster(_converse, 'current');
await mock.waitForRoster(_converse, 'current', 1);
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit',
msg = mock.createChatMessage(_converse, sender_jid, 'This message will be unread');
@ -1347,10 +1347,9 @@ describe("Chatboxes", function () {
['rosterGroupsFetched', 'chatBoxesFetched'], {},
async function (done, _converse) {
await mock.waitForRoster(_converse, 'current');
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit',
msg = mock.createChatMessage(_converse, sender_jid, 'This message will be read');
await mock.waitForRoster(_converse, 'current', 1);
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
const msg = mock.createChatMessage(_converse, sender_jid, 'This message will be read');
const sent_stanzas = [];
spyOn(_converse.connection, 'send').and.callFake(s => sent_stanzas.push(s));
await mock.openChatBoxFor(_converse, sender_jid);
@ -1470,7 +1469,7 @@ describe("Chatboxes", function () {
await u.waitUntil(() => chatbox.sendMarker.calls.count() === 1);
expect(sent_stanzas[0].nodeTree.querySelector('received')).toBeDefined();
_converse.saveWindowState(null, 'focus');
expect(chatbox.get('num_unread')).toBe(1);
await u.waitUntil(() => chatbox.get('num_unread') === 1);
expect(chatbox.get('first_unread_id')).toBe(msgid);
await u.waitUntil(() => chatbox.sendMarker.calls.count() === 1);
expect(sent_stanzas[0].nodeTree.querySelector('received')).toBeDefined();

View File

@ -1743,6 +1743,9 @@ describe("A Chat Message", function () {
const view = _converse.api.chatviews.get(sender_jid);
// Create enough messages so that there's a scrollbar.
const promises = [];
view.content.scrollTop = 0;
view.model.set('scrolled', true);
for (let i=0; i<20; i++) {
_converse.handleMessageStanza($msg({
from: sender_jid,
@ -1754,35 +1757,15 @@ describe("A Chat Message", function () {
promises.push(new Promise(resolve => view.model.messages.once('rendered', resolve)));
}
await Promise.all(promises);
// XXX Fails on Travis
// await u.waitUntil(() => view.content.scrollTop, 1000)
await u.waitUntil(() => !view.model.get('auto_scrolled'), 500);
view.content.scrollTop = 0;
// XXX Fails on Travis
// await u.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.handleMessageStanza($msg({
from: sender_jid,
to: _converse.connection.jid,
type: 'chat',
id: u.getUniqueId()
}).c('body').t(message).up()
.c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}).tree());
await new Promise(resolve => view.model.messages.once('rendered', resolve));
await u.waitUntil(() => view.model.messages.length > 20, 1000);
// Now check that the message appears inside the chatbox in the DOM
const msg_txt = sizzle('.chat-content .chat-msg:last .chat-msg__text', view.el).pop().textContent;
expect(msg_txt).toEqual(message);
await u.waitUntil(() => u.isVisible(view.el.querySelector('.new-msgs-indicator')), 900);
const indicator_el = view.el.querySelector('.new-msgs-indicator');
expect(u.isVisible(indicator_el)).toBeTruthy();
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
view.content.scrollTop = view.content.scrollHeight;
// XXX Fails on Travis
// await u.waitUntil(() => !u.isVisible(view.el.querySelector('.new-msgs-indicator')), 900);
indicator_el.click();
expect(u.isVisible(indicator_el)).toBeFalsy();
expect(view.model.get('scrolled')).toBe(false);
done();
}));

View File

@ -25,16 +25,6 @@ class ChatContent extends CustomElement {
<div class="chat-content__notifications">${unsafeHTML(notifications)}</div>
`;
}
scrollDown () {
if (!this.chatview.model.get('scrolled')) {
this.parentElement.scrollTop = this.parentElement.scrollHeight;
}
}
updated () {
this.scrollDown();
}
}
customElements.define('converse-chat-content', ChatContent);

View File

@ -211,6 +211,7 @@ converse.plugins.add('converse-chatview', {
// Need to be registered after render has been called.
this.listenTo(this.model.messages, 'add', this.onMessageAdded);
this.listenTo(this.model.messages, 'change', this.renderChatHistory);
this.listenTo(this.model.messages, 'rendered', this.maybeScrollDownOnMessage);
this.listenTo(this.model.messages, 'reset', this.renderChatHistory);
this.listenTo(this.model.notifications, 'change', this.renderNotifications);
this.listenTo(this.model, 'change:show_help_messages', this.renderHelpMessages);
@ -228,6 +229,8 @@ converse.plugins.add('converse-chatview', {
initDebounced () {
this.markScrolled = debounce(this._markScrolled, 100);
this.debouncedScrollDown = debounce(this.scrollDown, 100);
// For tests that use Jasmine.Clock we want to turn of
// debouncing, since setTimeout breaks.
if (api.settings.get('debounced_content_rendering')) {
@ -552,9 +555,28 @@ converse.plugins.add('converse-chatview', {
api.trigger('afterMessagesFetched', this.model);
},
scrollDown () {
const el = this.msgs_container.firstElementChild;
el && el.scrollDown();
maybeScrollDownOnMessage (message) {
if (message.get('sender') !== 'me' && !this.model.get('scrolled')) {
this.debouncedScrollDown();
}
},
scrollDown (ev) {
ev?.preventDefault?.();
ev?.stopPropagation?.();
if (this.model.get('scrolled')) {
u.safeSave(this.model, {
'scrolled': false,
'top_visible_message': null,
});
}
if (this.msgs_container.scrollTo) {
const behavior = this.msgs_container.scrollTop ? 'smooth' : 'auto';
this.msgs_container.scrollTo({'top': this.msgs_container.scrollHeight, behavior});
} else {
this.msgs_container.scrollTop = this.msgs_container.scrollHeight;
}
this.onScrolledDown();
},
insertIntoDOM () {
@ -597,7 +619,6 @@ converse.plugins.add('converse-chatview', {
// 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;
}

View File

@ -466,7 +466,7 @@ converse.plugins.add('converse-muc-views', {
this.listenTo(this.model, 'show', this.show);
this.listenTo(this.model.features, 'change:moderated', this.renderBottomPanel);
this.listenTo(this.model.features, 'change:open', this.renderHeading);
this.listenTo(this.model.messages, 'rendered', this.scrollDown);
this.listenTo(this.model.messages, 'rendered', this.maybeScrollDownOnMessage);
this.listenTo(this.model.session, 'change:connection_status', this.onConnectionStatusChanged);
// Bind so that we can pass it to addEventListener and removeEventListener

View File

@ -5,7 +5,7 @@ export default (o) => html`
<div class="chat-head chat-head-chatbox row no-gutters"></div>
<div class="chat-body">
<div class="chat-content ${ o.show_send_button ? 'chat-content-sendbutton' : '' }" aria-live="polite">
<div class="chat-content__messages smooth-scroll" @scroll=${o.markScrolled}></div>
<div class="chat-content__messages" @scroll=${o.markScrolled}></div>
<div class="chat-content__help"></div>
</div>
<div class="bottom-panel">

View File

@ -6,7 +6,7 @@ export default (o) => html`
<div class="chat-body chatroom-body row no-gutters">
<div class="chat-area col">
<div class="chat-content ${ o.show_send_button ? 'chat-content-sendbutton' : '' }" aria-live="polite">
<div class="chat-content__messages smooth-scroll" @scroll=${o.markScrolled}></div>
<div class="chat-content__messages" @scroll=${o.markScrolled}></div>
<div class="chat-content__help"></div>
</div>
<div class="bottom-panel"></div>