Maintain scroll position when re-inserting #conversejs element

This commit is contained in:
JC Brand 2020-07-28 10:41:07 +02:00
parent 867f80e95e
commit 4927d561a5
4 changed files with 47 additions and 36 deletions

View File

@ -176,6 +176,10 @@ converse.plugins.add('converse-chatboxviews', {
const el = _converse.chatboxviews?.el;
if (el && !container.contains(el)) {
container.insertAdjacentElement('afterBegin', el);
api.chatviews.get()
.filter(v => v.model.get('id') !== 'controlbox')
.forEach(v => v.maintainScrollTop());
} else if (!el) {
throw new Error("Cannot insert non-existing #conversejs element into the DOM");
}

View File

@ -208,7 +208,7 @@ converse.plugins.add('converse-chatview', {
this.listenTo(this.model.messages, 'add', this.onMessageAdded);
this.listenTo(this.model.messages, 'change', this.renderChatHistory);
this.listenTo(this.model.messages, 'remove', this.renderChatHistory);
this.listenTo(this.model.messages, 'rendered', this.maybeScrollDownOnMessage);
this.listenTo(this.model.messages, 'rendered', this.maybeScrollDown);
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);
@ -241,7 +241,7 @@ converse.plugins.add('converse-chatview', {
render () {
const result = tpl_chatbox(
Object.assign(this.model.toJSON(), {'markScrolled': () => this.markScrolled()})
Object.assign(this.model.toJSON(), {'markScrolled': ev => this.markScrolled(ev)})
);
render(result, this.el);
this.content = this.el.querySelector('.chat-content');
@ -486,19 +486,35 @@ converse.plugins.add('converse-chatview', {
api.trigger('afterMessagesFetched', this.model);
},
maybeScrollDownOnMessage (message) {
if (message.get('sender') === 'me' || !this.model.get('scrolled')) {
/**
* Scrolls the chat down, *if* appropriate.
*
* Will only scroll down if we have received a message from
* ourselves, or if the chat was scrolled down before (i.e. the
* `scrolled` flag is `false`);
* @param { _converse.Message|_converse.ChatRoomMessage } [message]
* - An optional message that serves as the cause for needing to scroll down.
*/
maybeScrollDown (message) {
if (message?.get('sender') === 'me' || !this.model.get('scrolled')) {
this.debouncedScrollDown();
}
},
/**
* Scrolls the chat down.
*
* This method will always scroll the chat down, regardless of
* whether the user scrolled up manually or not.
* @param { Event } [ev] - An optional event that is the cause for needing to scroll down.
*/
scrollDown (ev) {
ev?.preventDefault?.();
ev?.stopPropagation?.();
if (this.model.get('scrolled')) {
u.safeSave(this.model, {
'scrolled': false,
'top_visible_message': null,
'scrollTop': null,
});
}
if (this.msgs_container.scrollTo) {
@ -510,6 +526,19 @@ converse.plugins.add('converse-chatview', {
this.onScrolledDown();
},
/**
* Scroll to the previously saved scrollTop position, or scroll
* down if it wasn't set.
*/
maintainScrollTop () {
const pos = this.model.get('scrollTop');
if (pos) {
this.msgs_container.scrollTop = pos;
} else {
this.scrollDown();
}
},
insertIntoDOM () {
_converse.chatboxviews.insertRowColumn(this.el);
/**
@ -537,28 +566,6 @@ converse.plugins.add('converse-chatview', {
this.content.querySelectorAll('.spinner').forEach(u.removeElement);
},
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();
}
},
onStatusMessageChanged (item) {
this.renderHeading();
/**
@ -1091,8 +1098,9 @@ converse.plugins.add('converse-chatview', {
* which debounces this method by 100ms.
* @private
*/
_markScrolled: function () {
_markScrolled: function (ev) {
let scrolled = true;
let scrollTop = null;
const is_at_bottom =
(this.msgs_container.scrollTop + this.msgs_container.clientHeight) >=
this.msgs_container.scrollHeight - 62; // sigh...
@ -1108,15 +1116,14 @@ converse.plugins.add('converse-chatview', {
* @example _converse.api.listen.on('chatBoxScrolledUp', obj => { ... });
*/
api.trigger('chatBoxScrolledUp', this);
} else {
scrollTop = ev.target.scrollTop;
}
u.safeSave(this.model, {
'scrolled': scrolled,
'top_visible_message': null
});
u.safeSave(this.model, { scrolled, scrollTop });
},
viewUnreadMessages () {
this.model.save({'scrolled': false, 'top_visible_message': null});
this.model.save({'scrolled': false, 'scrollTop': null});
this.scrollDown();
},

View File

@ -190,7 +190,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.maybeScrollDownOnMessage);
this.listenTo(this.model.messages, 'rendered', this.maybeScrollDown);
this.listenTo(this.model.session, 'change:connection_status', this.onConnectionStatusChanged);
// Bind so that we can pass it to addEventListener and removeEventListener

View File

@ -168,12 +168,12 @@ class MessageBodyRenderer {
// image loads, it triggers 'scroll' and the chat will be marked as scrolled,
// which is technically true, but not what we want because the user
// didn't initiate the scrolling.
this.scrolled = this.chatview.model.get('scrolled');
this.was_scrolled_up = this.chatview.model.get('scrolled');
this.text = this.component.model.getMessageText();
}
scrollDownOnImageLoad () {
if (!this.scrolled) {
if (!this.was_scrolled_up) {
this.chatview.scrollDown();
}
}