diff options
Diffstat (limited to 'webapp/components/post_view')
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} |