Feature🚀: Ability to see chat history by scrolling up (#4903)

* Feature🚀: Ability to see chat history by scrolling up

* Test samples updated

* Feature Enhancement 🛠 :  Jump back to recent messages

If you scroll far away just click this button to go back

![img](https://i.imgur.com/HlhB8Up.gif)

* Test case added

* Bug Fix 🐞: Scrolling problem with large screen and re-rendering issue
This commit is contained in:
Sarthak Sharma 2019-12-04 01:49:04 +05:30 committed by Ben Halpern
parent d9803fef13
commit 147b0936df
5 changed files with 130 additions and 8 deletions

View file

@ -1204,3 +1204,25 @@
width:96%;
}
}
.jumpback{
position: relative;
}
.jumpback__messages{
position: absolute;
bottom: 10px;
right: 20px;
background: var(--theme-background, #f5f6f7);
border: 2px solid #111;
padding: 10px;
border-radius: 20px;
cursor: pointer;
font-size: 14px;
text-transform: uppercase;
font-weight: bold;
}
.jumpback__hide{
display: none;
}

View file

@ -17,7 +17,7 @@ class ChatChannelsController < ApplicationController
end
def show
@chat_messages = @chat_channel.messages.includes(:user).order("created_at DESC").limit(50)
@chat_messages = @chat_channel.messages.includes(:user).order("created_at DESC").offset(params[:message_offset]).limit(150)
end
def create

View file

@ -176,12 +176,28 @@ exports[`<Chat /> should load chat 1`] = `
<div
class="activechatchannel__messages"
id="messagelist"
onScroll={[Function]}
ref={[Function]}
>
<div
class="messagelist__sentinel"
id="messagelist__sentinel"
/>
</div>
<div
class="jumpback jumpback__hide"
id="jumpback_button"
>
<div
class="jumpback__messages"
onClick={[Function]}
onKeyUp={[Function]}
role="button"
tabIndex="0"
>
Jump Back
</div>
</div>
<div
class="activechatchannel__alerts"
>

View file

@ -1,5 +1,5 @@
export function getAllMessages(channelId, successCb, failureCb) {
fetch(`/chat_channels/${channelId}`, {
export function getAllMessages(channelId, messageOffset, successCb, failureCb) {
fetch(`/chat_channels/${channelId}?message_offset=${messageOffset}`, {
Accept: 'application/json',
'Content-Type': 'application/json',
credentials: 'same-origin',

View file

@ -60,6 +60,9 @@ export default class Chat extends Component {
inviteChannels: [],
soundOn: true,
videoOn: true,
messageOffset: 0,
allMessagesLoaded: false,
currentMessageLocation: 0,
};
}
@ -72,6 +75,7 @@ export default class Chat extends Component {
isMobileDevice,
channelPaginationNum,
} = this.state;
this.setupChannels(chatChannels);
const channelsForPusherSub = chatChannels.filter(
this.channelTypeFilter('open'),
@ -113,12 +117,18 @@ export default class Chat extends Component {
}
componentDidUpdate() {
const { scrolled } = this.state;
if (document.getElementById('messagelist')) {
const { scrolled, currentMessageLocation } = this.state;
const messageList = document.getElementById('messagelist');
if (messageList) {
if (!scrolled) {
scrollToBottom();
}
}
if (currentMessageLocation && messageList.scrollTop === 0) {
messageList.scrollTop =
messageList.scrollHeight - (currentMessageLocation + 30);
}
}
liveCoding = e => {
@ -257,13 +267,13 @@ export default class Chat extends Component {
};
setupChannel = channelId => {
const { messages } = this.state;
const { messages, messageOffset } = this.state;
if (
!messages[channelId] ||
messages[channelId].length === 0 ||
messages[channelId][0].reception_method === 'pushed'
) {
getAllMessages(channelId, this.receiveAllMessages);
getAllMessages(channelId, messageOffset, this.receiveAllMessages);
}
this.subscribePusher(`presence-channel-${channelId}`);
};
@ -853,17 +863,91 @@ export default class Chat extends Component {
return '';
};
handleMessageScroll = () => {
const {
allMessagesLoaded,
messages,
activeChannelId,
messageOffset,
} = this.state;
const jumpbackButton = document.getElementById('jumpback_button');
if (this.scroller) {
const scrolledRatio =
(this.scroller.scrollTop + this.scroller.clientHeight) /
this.scroller.scrollHeight;
if (scrolledRatio < 0.7) {
jumpbackButton.classList.remove('jumpback__hide');
} else if (scrolledRatio > 0.8) {
jumpbackButton.classList.add('jumpback__hide');
}
if (this.scroller.scrollTop === 0 && !allMessagesLoaded) {
getAllMessages(
activeChannelId,
messageOffset + messages[activeChannelId].length,
this.addMoreMessages,
);
const curretPosition = this.scroller.scrollHeight;
this.setState({ currentMessageLocation: curretPosition });
}
}
};
addMoreMessages = res => {
const { chatChannelId, messages } = res;
if (messages.length > 0) {
this.setState(prevState => ({
messages: {
[chatChannelId]: [...messages, ...prevState.messages[chatChannelId]],
},
}));
} else {
this.setState({ allMessagesLoaded: true });
}
};
jumpBacktoBottom = () => {
scrollToBottom();
document
.getElementById('jumpback_button')
.classList.remove('jumpback__hide');
};
renderActiveChatChannel = (channelHeader, incomingCall) => {
const { state, props } = this;
return (
<div className="activechatchannel">
<div className="activechatchannel__conversation">
{channelHeader}
<div className="activechatchannel__messages" id="messagelist">
<div
className="activechatchannel__messages"
onScroll={this.handleMessageScroll}
ref={scroller => {
this.scroller = scroller;
}}
id="messagelist"
>
{this.renderMessages()}
{incomingCall}
<div className="messagelist__sentinel" id="messagelist__sentinel" />
</div>
<div className="jumpback jumpback__hide" id="jumpback_button">
<div
role="button"
className="jumpback__messages"
onClick={this.jumpBacktoBottom}
tabIndex="0"
onKeyUp={e => {
if (e.keyCode === 13) this.jumpBacktoBottom();
}}
>
Jump Back
</div>
</div>
<div className="activechatchannel__alerts">
<Alert showAlert={state.showAlert} />
</div>