summaryrefslogtreecommitdiffstats
path: root/webapp/components/post_view
diff options
context:
space:
mode:
authorPepijn <pepijnfens@gmail.com>2016-12-08 00:08:37 +0100
committerHarrison Healey <harrisonmhealey@gmail.com>2016-12-07 18:08:37 -0500
commit7399d349dedc35c8c05b223a3c143a420bf2411f (patch)
tree77233100ea3b5083c8a4f78c733f1b3d0eb1ac90 /webapp/components/post_view
parent9b96fcc7c61075d1e5ec2d9ab5b720f7c9fdab2d (diff)
downloadchat-7399d349dedc35c8c05b223a3c143a420bf2411f.tar.gz
chat-7399d349dedc35c8c05b223a3c143a420bf2411f.tar.bz2
chat-7399d349dedc35c8c05b223a3c143a420bf2411f.zip
Created a new message indicator component to indicate new messages (#4299)
* Created a new message indicator component to indicate new messages are present outside the user's view: * Created new component with styling * Theming and i18n support for new messages indicator * Count new unviewed messages and integrate with component * Coding style for new message indicator * Fixed bugs with new message indicator: * Fix display issues with new message indicator * Update text to deal with plurals in React * Fix coded style for ticket 'PLT-1917': add new message indicator. * Use only server generated timestamps for checking new messages at the bottom * Move transitionend to prop, fix style selectors
Diffstat (limited to 'webapp/components/post_view')
-rw-r--r--webapp/components/post_view/components/new_message_indicator.jsx65
-rw-r--r--webapp/components/post_view/components/post_list.jsx27
-rw-r--r--webapp/components/post_view/post_view_controller.jsx13
3 files changed, 101 insertions, 4 deletions
diff --git a/webapp/components/post_view/components/new_message_indicator.jsx b/webapp/components/post_view/components/new_message_indicator.jsx
new file mode 100644
index 000000000..7407c1024
--- /dev/null
+++ b/webapp/components/post_view/components/new_message_indicator.jsx
@@ -0,0 +1,65 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+import React from 'react';
+import {FormattedMessage} from 'react-intl';
+
+export default class NewMessageIndicator extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ visible: false,
+ rendered: false
+ };
+ }
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.newMessages > 0) {
+ this.setState({rendered: true}, () => {
+ this.setState({visible: true});
+ });
+ } else {
+ this.setState({visible: false});
+ }
+ }
+ render() {
+ let className = 'nav-pills__unread-indicator-bottom';
+ if (this.state.visible > 0) {
+ className += ' visible';
+ }
+ if (!this.state.rendered) {
+ className += ' disabled';
+ }
+ return (
+ <div
+ className={className}
+ onClick={this.props.onClick}
+ onTransitionEnd={this.setRendered.bind(this)}
+ ref='indicator'
+ >
+ <span>
+ <i
+ className='fa fa-arrow-circle-o-down'
+ />
+ <FormattedMessage
+ id='posts_view_newMsgBelow'
+ defaultMessage='{count} new {count, plural, one {message} other {messages}} below'
+ values={{count: this.props.newMessages}}
+ />
+ </span>
+ </div>
+ );
+ }
+
+ // Sync 'rendered' state with visibility param, only after transitions
+ // have ended
+ setRendered() {
+ this.setState({rendered: this.state.visible});
+ }
+}
+NewMessageIndicator.defaultProps = {
+ newMessages: 0
+};
+
+NewMessageIndicator.propTypes = {
+ onClick: React.PropTypes.func.isRequired,
+ newMessages: React.PropTypes.number
+};
diff --git a/webapp/components/post_view/components/post_list.jsx b/webapp/components/post_view/components/post_list.jsx
index a0cfb208e..157e0dde2 100644
--- a/webapp/components/post_view/components/post_list.jsx
+++ b/webapp/components/post_view/components/post_list.jsx
@@ -6,6 +6,7 @@ import $ from 'jquery';
import Post from './post.jsx';
import FloatingTimestamp from './floating_timestamp.jsx';
import ScrollToBottomArrows from './scroll_to_bottom_arrows.jsx';
+import NewMessageIndicator from './new_message_indicator.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
@@ -56,7 +57,8 @@ export default class PostList extends React.Component {
this.state = {
isScrolling: false,
fullWidthIntro: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.CHANNEL_DISPLAY_MODE, Preferences.CHANNEL_DISPLAY_MODE_DEFAULT) === Preferences.CHANNEL_DISPLAY_MODE_FULL_SCREEN,
- topPostId: null
+ topPostId: null,
+ unViewedCount: 0
};
if (props.channel) {
@@ -74,6 +76,23 @@ export default class PostList extends React.Component {
this.introText = createChannelIntroMessage(this.props.channel, this.state.fullWidthIntro);
}
}
+
+ const posts = nextProps.postList.posts;
+ const order = nextProps.postList.order;
+ let unViewedCount = 0;
+
+ // Only count if we're not at the bottom, not in highlight view,
+ // or anything else
+ if (nextProps.scrollType === Constants.ScrollTypes.FREE) {
+ for (let i = order.length - 1; i >= 0; i--) {
+ const post = posts[order[i]];
+ if (post.create_at > nextProps.lastViewedBottom &&
+ post.user_id !== nextProps.currentUser.id) {
+ unViewedCount++;
+ }
+ }
+ }
+ this.setState({unViewedCount});
}
handleKeyDown(e) {
@@ -553,6 +572,10 @@ export default class PostList extends React.Component {
atBottom={this.wasAtBottom}
onClick={this.scrollToBottomAnimated}
/>
+ <NewMessageIndicator
+ newMessages={this.state.unViewedCount}
+ onClick={this.scrollToBottomAnimated}
+ />
<div
ref='postlist'
className='post-list-holder-by-time'
@@ -576,6 +599,7 @@ export default class PostList extends React.Component {
PostList.defaultProps = {
lastViewed: 0,
+ lastViewedBottom: Number.MAX_VALUE,
ownNewMessage: false
};
@@ -590,6 +614,7 @@ PostList.propTypes = {
showMoreMessagesTop: React.PropTypes.bool,
showMoreMessagesBottom: React.PropTypes.bool,
lastViewed: React.PropTypes.number,
+ lastViewedBottom: React.PropTypes.number,
ownNewMessage: React.PropTypes.bool,
postsToHighlight: React.PropTypes.object,
displayNameType: React.PropTypes.string,
diff --git a/webapp/components/post_view/post_view_controller.jsx b/webapp/components/post_view/post_view_controller.jsx
index 53cd0b28c..e053db118 100644
--- a/webapp/components/post_view/post_view_controller.jsx
+++ b/webapp/components/post_view/post_view_controller.jsx
@@ -37,9 +37,11 @@ export default class PostViewController extends React.Component {
const profiles = UserStore.getProfiles();
let lastViewed = Number.MAX_VALUE;
+ let lastViewedBottom = Number.MAX_VALUE;
const member = ChannelStore.getMyMember(channel.id);
if (member != null) {
lastViewed = member.last_viewed_at;
+ lastViewedBottom = member.last_viewed_at;
}
const joinLeaveEnabled = PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'join_leave', true);
@@ -58,6 +60,7 @@ export default class PostViewController extends React.Component {
statuses,
atTop: PostStore.getVisibilityAtTop(channel.id),
lastViewed,
+ lastViewedBottom,
ownNewMessage: false,
loading,
scrollType: ScrollTypes.NEW_MESSAGE,
@@ -187,9 +190,11 @@ export default class PostViewController extends React.Component {
onPostsViewJumpRequest(type, postId) {
switch (type) {
- case Constants.PostsViewJumpTypes.BOTTOM:
- this.setState({scrollType: ScrollTypes.BOTTOM});
+ case Constants.PostsViewJumpTypes.BOTTOM: {
+ const lastPost = PostStore.getLatestPost(this.state.channel.id);
+ this.setState({scrollType: ScrollTypes.BOTTOM, lastViewedBottom: lastPost.create_at || new Date().getTime()});
break;
+ }
case Constants.PostsViewJumpTypes.POST:
this.setState({
scrollType: ScrollTypes.POST,
@@ -213,7 +218,8 @@ export default class PostViewController extends React.Component {
onPostListScroll(atBottom) {
if (atBottom) {
- this.setState({scrollType: ScrollTypes.BOTTOM});
+ const lastPost = PostStore.getLatestPost(this.state.channel.id);
+ this.setState({scrollType: ScrollTypes.BOTTOM, lastViewedBottom: lastPost.create_at || new Date().getTime()});
} else {
this.setState({scrollType: ScrollTypes.FREE});
}
@@ -334,6 +340,7 @@ export default class PostViewController extends React.Component {
useMilitaryTime={this.state.useMilitaryTime}
flaggedPosts={this.state.flaggedPosts}
lastViewed={this.state.lastViewed}
+ lastViewedBottom={this.state.lastViewedBottom}
ownNewMessage={this.state.ownNewMessage}
statuses={this.state.statuses}
isBusy={this.state.isBusy}