diff --git a/app/soapbox/features/chats/components/chat_message_list.js b/app/soapbox/features/chats/components/chat_message_list.js
index 2ff423143..1cfc41442 100644
--- a/app/soapbox/features/chats/components/chat_message_list.js
+++ b/app/soapbox/features/chats/components/chat_message_list.js
@@ -13,8 +13,6 @@ import { escape, throttle } from 'lodash';
import { MediaGallery } from 'soapbox/features/ui/util/async-components';
import Bundle from 'soapbox/features/ui/components/bundle';
-const scrollBottom = (elem) => elem.scrollHeight - elem.offsetHeight - elem.scrollTop;
-
const makeEmojiMap = record => record.get('emojis', ImmutableList()).reduce((map, emoji) => {
return map.set(`:${emoji.get('shortcode')}:`, emoji);
}, ImmutableMap());
@@ -45,12 +43,13 @@ class ChatMessageList extends ImmutablePureComponent {
}
state = {
+ initialLoad: true,
isLoading: false,
}
scrollToBottom = () => {
if (!this.messagesEnd) return;
- this.messagesEnd.scrollIntoView();
+ this.messagesEnd.scrollIntoView(false);
}
setMessageEndRef = (el) => {
@@ -82,22 +81,45 @@ class ChatMessageList extends ImmutablePureComponent {
});
}
+ isNearBottom = () => {
+ const elem = this.node;
+ if (!elem) return false;
+
+ const scrollBottom = elem.scrollHeight - elem.offsetHeight - elem.scrollTop;
+ return scrollBottom < elem.offsetHeight * 1.5;
+ }
+
componentDidMount() {
const { dispatch, chatId } = this.props;
dispatch(fetchChatMessages(chatId));
this.node.addEventListener('scroll', this.handleScroll);
+ this.scrollToBottom();
}
- componentDidUpdate(prevProps) {
+ getSnapshotBeforeUpdate(prevProps, prevState) {
+ const { scrollHeight, scrollTop } = this.node;
+ return scrollHeight - scrollTop;
+ }
+
+ restoreScrollPosition = (scrollBottom) => {
+ this.lastComputedScroll = this.node.scrollHeight - scrollBottom;
+ this.node.scrollTop = this.lastComputedScroll;
+ }
+
+ componentDidUpdate(prevProps, prevState, scrollBottom) {
+ const { initialLoad } = this.state;
const oldCount = prevProps.chatMessages.count();
const newCount = this.props.chatMessages.count();
- const isNearBottom = scrollBottom(this.node) < 150;
- const historyAdded = prevProps.chatMessages.getIn([-1, 'id']) !== this.props.chatMessages.getIn([-1, 'id']);
+ const isNearBottom = this.isNearBottom();
+ const historyAdded = prevProps.chatMessages.getIn([0, 'id']) !== this.props.chatMessages.getIn([0, 'id']);
+
+ // Retain scroll bar position when loading old messages
+ this.restoreScrollPosition(scrollBottom);
if (oldCount !== newCount) {
- if (isNearBottom) this.scrollToBottom();
- if (historyAdded) this.setState({ isLoading: false });
+ if (isNearBottom || initialLoad) this.scrollToBottom();
+ if (historyAdded) this.setState({ isLoading: false, initialLoad: false });
}
}
@@ -107,13 +129,20 @@ class ChatMessageList extends ImmutablePureComponent {
handleLoadMore = () => {
const { dispatch, chatId, chatMessages } = this.props;
- const maxId = chatMessages.getIn([-1, 'id']);
+ const maxId = chatMessages.getIn([0, 'id']);
dispatch(fetchChatMessages(chatId, maxId));
this.setState({ isLoading: true });
}
handleScroll = throttle(() => {
- if (this.node.scrollTop < 150 && !this.state.isLoading) this.handleLoadMore();
+ const { lastComputedScroll } = this;
+ const { isLoading, initialLoad } = this.state;
+ const { scrollTop, offsetHeight } = this.node;
+ const computedScroll = lastComputedScroll === scrollTop;
+ const nearTop = scrollTop < offsetHeight * 2;
+
+ if (nearTop && !isLoading && !initialLoad && !computedScroll)
+ this.handleLoadMore();
}, 150, {
trailing: true,
});
@@ -126,15 +155,17 @@ class ChatMessageList extends ImmutablePureComponent {
const attachment = chatMessage.get('attachment');
if (!attachment) return null;
return (
-