message pagination
This commit is contained in:
@@ -1447,3 +1447,39 @@ ul.tabbar li.active > a:after {
|
|||||||
width: 0.25rem;
|
width: 0.25rem;
|
||||||
height: 1.175rem;
|
height: 1.175rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.load-spinner-box {
|
||||||
|
position: absolute;
|
||||||
|
box-shadow: 0.1rem 0.1rem 0.15rem 1px rgba(0,0,0,0.25);
|
||||||
|
border-radius: 50%;
|
||||||
|
padding: 0.25rem;
|
||||||
|
width: 2.5rem;
|
||||||
|
height: 2.5rem;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 5rem;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
background-color: white;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader-spinner {
|
||||||
|
border: 0.35rem solid #f3f3f3;
|
||||||
|
border-radius: 50%;
|
||||||
|
border-top: 0.35rem solid #1e88e5;
|
||||||
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
-webkit-animation: spin 1.5s linear infinite;
|
||||||
|
animation: spin 1.5s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes spin {
|
||||||
|
0% { -webkit-transform: rotate(0deg); }
|
||||||
|
100% { -webkit-transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|||||||
+108
-37
@@ -47,9 +47,12 @@
|
|||||||
// Mediaquery breakpoint between desktop and mobile, in px. Should match the value
|
// Mediaquery breakpoint between desktop and mobile, in px. Should match the value
|
||||||
// in @meadia (max-size: 640px) in base.css
|
// in @meadia (max-size: 640px) in base.css
|
||||||
var MEDIA_BREAKPOINT = 640;
|
var MEDIA_BREAKPOINT = 640;
|
||||||
// Size of rem unit in pixels. Default 1rem = 10pt = 13px.
|
// Size of css 'rem' unit in pixels. Default 1rem = 10pt = 13px.
|
||||||
var REM_SIZE = 13;
|
var REM_SIZE = 13;
|
||||||
|
|
||||||
|
// Number of chat messages to fetch in one call.
|
||||||
|
var MESSAGES_PAGE = 24;
|
||||||
|
|
||||||
// Short representation of time in the past.
|
// Short representation of time in the past.
|
||||||
function shortDateFormat(then) {
|
function shortDateFormat(then) {
|
||||||
var locale = window.navigator.userLanguage || window.navigator.language;
|
var locale = window.navigator.userLanguage || window.navigator.language;
|
||||||
@@ -285,6 +288,13 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
class LoadSpinner extends React.PureComponent {
|
||||||
|
render() {
|
||||||
|
return (this.props.show ?
|
||||||
|
<div className="load-spinner-box"><div className="loader-spinner"></div></div> : null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* BEGIN Lettertile: Avatar box: either a bitmap or a letter tile or a stock icon. */
|
/* BEGIN Lettertile: Avatar box: either a bitmap or a letter tile or a stock icon. */
|
||||||
class LetterTile extends React.PureComponent {
|
class LetterTile extends React.PureComponent {
|
||||||
render() {
|
render() {
|
||||||
@@ -2318,25 +2328,35 @@
|
|||||||
messages: [],
|
messages: [],
|
||||||
topic: '',
|
topic: '',
|
||||||
title: '',
|
title: '',
|
||||||
avatar: null
|
avatar: null,
|
||||||
|
scrollPosition: 0
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
// Scroll last message into view on component update e.g. on message received.
|
// Scroll last message into view on component update e.g. on message received.
|
||||||
componentDidUpdate: function() {
|
componentDidUpdate: function() {
|
||||||
var node = this.refs.messagesPanel;
|
if (this.messagesScroller) {
|
||||||
if (node) {
|
this.messagesScroller.scrollTop = this.messagesScroller.scrollHeight - this.state.scrollPosition;
|
||||||
node.scrollTop = node.scrollHeight;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
this.propsChange(this.props);
|
this.propsChange(this.props);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
componentWillUnmount: function() {
|
||||||
|
this.leave();
|
||||||
|
if (this.messagesScroller) {
|
||||||
|
this.messagesScroller.removeEventListener('scroll', this.fetchMoreMessages);
|
||||||
|
}
|
||||||
|
},
|
||||||
componentWillReceiveProps: function(nextProps) {
|
componentWillReceiveProps: function(nextProps) {
|
||||||
this.propsChange(nextProps);
|
this.propsChange(nextProps);
|
||||||
},
|
},
|
||||||
propsChange: function(props) {
|
propsChange: function(props) {
|
||||||
if (!props.topic) {
|
if (!props || !props.topic) {
|
||||||
this.setState({messages: [], topic: null});
|
this.setState({messages: [], topic: null});
|
||||||
|
this.leave();
|
||||||
} else if (props.topic != this.state.topic) {
|
} else if (props.topic != this.state.topic) {
|
||||||
var msgs = [];
|
var msgs = [];
|
||||||
var topic = Tinode.getTopic(props.topic);
|
var topic = Tinode.getTopic(props.topic);
|
||||||
@@ -2346,8 +2366,29 @@
|
|||||||
topic.onMetaDesc = this.handleDescChange;
|
topic.onMetaDesc = this.handleDescChange;
|
||||||
topic.onSubsUpdated = this.handleSubsUpdated;
|
topic.onSubsUpdated = this.handleSubsUpdated;
|
||||||
// Unbind the previous topic from this component.
|
// Unbind the previous topic from this component.
|
||||||
if (this.state.topic) {
|
this.leave();
|
||||||
var oldTopic = Tinode.getTopic(this.state.topic);
|
|
||||||
|
if (topic.isSubscribed()) {
|
||||||
|
this.handleDescChange(topic);
|
||||||
|
topic.messages(function(msg) {
|
||||||
|
msgs = msgs.concat(msg);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
topic.subscribe({get: {desc: {}, sub: {}, data: {limit: MESSAGES_PAGE}}});
|
||||||
|
}
|
||||||
|
|
||||||
|
// The user switched to the new topic before the timer for
|
||||||
|
// the previous topic has triggered, kill it.
|
||||||
|
clearTimeout(this.readTimer);
|
||||||
|
|
||||||
|
this.setState({messages: msgs, topic: props.topic, scrollPosition: 0});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
leave: function() {
|
||||||
|
if (this.state.topic) {
|
||||||
|
var oldTopic = Tinode.getTopic(this.state.topic);
|
||||||
|
if (oldTopic) {
|
||||||
if (oldTopic.isSubscribed()) {
|
if (oldTopic.isSubscribed()) {
|
||||||
oldTopic.leave(false);
|
oldTopic.leave(false);
|
||||||
}
|
}
|
||||||
@@ -2356,23 +2397,38 @@
|
|||||||
oldTopic.onMetaDesc = undefined;
|
oldTopic.onMetaDesc = undefined;
|
||||||
oldTopic.onSubsUpdated = undefined;
|
oldTopic.onSubsUpdated = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (topic.isSubscribed()) {
|
|
||||||
this.handleDescChange(topic);
|
|
||||||
topic.messages(function(msg) {
|
|
||||||
msgs = msgs.concat(msg);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
topic.subscribe({get: {desc: {}, sub: {}, data: {}}});
|
|
||||||
}
|
|
||||||
|
|
||||||
// The user switched to the new topic before the timer for
|
|
||||||
// the previous topic has triggered, kill it.
|
|
||||||
clearTimeout(this.readTimer);
|
|
||||||
|
|
||||||
this.setState({messages: msgs, topic: props.topic});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
handleScrollReference: function(node) {
|
||||||
|
if (node) {
|
||||||
|
node.addEventListener('scroll', this.fetchMoreMessages);
|
||||||
|
this.messagesScroller = node;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
fetchMoreMessages: function(event) {
|
||||||
|
var instance = this;
|
||||||
|
if (event.target.scrollTop <= 0) {
|
||||||
|
var started = false;
|
||||||
|
var newState = {scrollPosition: event.target.scrollHeight - event.target.scrollTop};
|
||||||
|
this.setState(function(prevState, props) {
|
||||||
|
if (!prevState.fetchingMessages) {
|
||||||
|
var topic = Tinode.getTopic(instance.state.topic);
|
||||||
|
if (topic && topic.msgHasMore()) {
|
||||||
|
newState.fetchingMessages = true;
|
||||||
|
console.log("START fetching messages from " + topic._minSeq);
|
||||||
|
topic.getMessagesPage(MESSAGES_PAGE).then(function() {
|
||||||
|
instance.setState({fetchingMessages: false});
|
||||||
|
console.log("DONE fetching messages");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newState;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
handleDescChange: function(desc) {
|
handleDescChange: function(desc) {
|
||||||
if (desc.public) {
|
if (desc.public) {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -2386,22 +2442,28 @@
|
|||||||
},
|
},
|
||||||
handleNewMessage: function(msg) {
|
handleNewMessage: function(msg) {
|
||||||
clearTimeout(this.readTimer);
|
clearTimeout(this.readTimer);
|
||||||
|
// Regenerate messages list
|
||||||
|
var topic = Tinode.getTopic(this.state.topic);
|
||||||
|
var newState = {messages: []};
|
||||||
|
topic.messages(function(m) {
|
||||||
|
newState.messages = newState.messages.concat(m);
|
||||||
|
});
|
||||||
|
|
||||||
|
// msg could be null if one or more messages were deleted.
|
||||||
if (msg) {
|
if (msg) {
|
||||||
this.setState({messages: this.state.messages.concat(msg)});
|
// If the message is added to the end of the message list,
|
||||||
|
// scroll to the bottom.
|
||||||
|
if (topic.msgPosition(msg.seq) <= 1) {
|
||||||
|
newState.scrollPosition = 0;
|
||||||
|
}
|
||||||
// Aknowledge all messages, including own messges.
|
// Aknowledge all messages, including own messges.
|
||||||
this.readTimer = setTimeout(function() {
|
this.readTimer = setTimeout(function() {
|
||||||
Tinode.getTopic(msg.topic).noteRead(msg.seq);
|
topic.noteRead(msg.seq);
|
||||||
}, READ_DELAY);
|
}, READ_DELAY);
|
||||||
this.props.onData(msg);
|
this.props.onData(msg);
|
||||||
} else {
|
|
||||||
// msg could be null if one or more messages were deleted.
|
|
||||||
// Just regenerate the message list;
|
|
||||||
var _messages = [];
|
|
||||||
Tinode.getTopic(this.state.topic).messages(function(m) {
|
|
||||||
_messages.concat(m);
|
|
||||||
});
|
|
||||||
this.setState({messages: _messages});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.setState(newState);
|
||||||
},
|
},
|
||||||
handleInfoReceipt: function(info) {
|
handleInfoReceipt: function(info) {
|
||||||
switch (info.what) {
|
switch (info.what) {
|
||||||
@@ -2424,6 +2486,10 @@
|
|||||||
this.props.showContextMenu(this.state.topic, e.pageY, e.pageX);
|
this.props.showContextMenu(this.state.topic, e.pageY, e.pageX);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
handleBackNavigation: function() {
|
||||||
|
this.props.onHideMessagesView();
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var component = null;
|
var component = null;
|
||||||
if (this.state.topic) {
|
if (this.state.topic) {
|
||||||
@@ -2493,7 +2559,7 @@
|
|||||||
<div id="topic-view" className={this.props.hideSelf ? 'nodisplay' : null}>
|
<div id="topic-view" className={this.props.hideSelf ? 'nodisplay' : null}>
|
||||||
<div id="topic-caption-panel" className="caption-panel">
|
<div id="topic-caption-panel" className="caption-panel">
|
||||||
{this.props.displayMobile ?
|
{this.props.displayMobile ?
|
||||||
<a href="#" id="hide-message-view" onClick={this.props.onHideMessagesView}>
|
<a href="#" id="hide-message-view" onClick={this.handleBackNavigation}>
|
||||||
<i className="material-icons big">arrow_back</i>
|
<i className="material-icons big">arrow_back</i>
|
||||||
</a>
|
</a>
|
||||||
:
|
:
|
||||||
@@ -2514,8 +2580,9 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="messages-panel" ref="messagesPanel">
|
<LoadSpinner show={this.state.fetchingMessages} />
|
||||||
<ul className={chatBoxClass}>
|
<div id="messages-panel" ref={this.handleScrollReference}>
|
||||||
|
<ul id="scroller" className={chatBoxClass}>
|
||||||
{messageNodes}
|
{messageNodes}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -2847,7 +2914,11 @@
|
|||||||
},
|
},
|
||||||
// In mobile view user requested to show sidepanel
|
// In mobile view user requested to show sidepanel
|
||||||
handleHideMessagesView: function() {
|
handleHideMessagesView: function() {
|
||||||
this.setState({mobilePanel: 'sidepanel'});
|
this.setState({
|
||||||
|
mobilePanel: 'sidepanel',
|
||||||
|
topicSelected: null,
|
||||||
|
showInfoPanel: false
|
||||||
|
});
|
||||||
},
|
},
|
||||||
// User clicked on a message send button.
|
// User clicked on a message send button.
|
||||||
handleSendMessage: function(msg) {
|
handleSendMessage: function(msg) {
|
||||||
|
|||||||
Reference in New Issue
Block a user