summaryrefslogtreecommitdiffstats
path: root/web/react/components/posts_view.jsx
diff options
context:
space:
mode:
Diffstat (limited to 'web/react/components/posts_view.jsx')
-rw-r--r--web/react/components/posts_view.jsx187
1 files changed, 143 insertions, 44 deletions
diff --git a/web/react/components/posts_view.jsx b/web/react/components/posts_view.jsx
index b782268fa..b7ac92672 100644
--- a/web/react/components/posts_view.jsx
+++ b/web/react/components/posts_view.jsx
@@ -1,18 +1,23 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-const UserStore = require('../stores/user_store.jsx');
-const Utils = require('../utils/utils.jsx');
-const Post = require('./post.jsx');
-const Constants = require('../utils/constants.jsx');
+import UserStore from '../stores/user_store.jsx';
+import PreferenceStore from '../stores/preference_store.jsx';
+import * as EventHelpers from '../dispatcher/event_helpers.jsx';
+import * as Utils from '../utils/utils.jsx';
+import Post from './post.jsx';
+import Constants from '../utils/constants.jsx';
+const Preferences = Constants.Preferences;
export default class PostsView extends React.Component {
constructor(props) {
super(props);
+ this.updateState = this.updateState.bind(this);
this.handleScroll = this.handleScroll.bind(this);
this.isAtBottom = this.isAtBottom.bind(this);
this.loadMorePostsTop = this.loadMorePostsTop.bind(this);
+ this.loadMorePostsBottom = this.loadMorePostsBottom.bind(this);
this.createPosts = this.createPosts.bind(this);
this.updateScrolling = this.updateScrolling.bind(this);
this.handleResize = this.handleResize.bind(this);
@@ -20,6 +25,8 @@ export default class PostsView extends React.Component {
this.jumpToPostNode = null;
this.wasAtBottom = true;
this.scrollHeight = 0;
+
+ this.state = {displayNameType: PreferenceStore.getPreference(Preferences.CATEGORY_DISPLAY_SETTINGS, 'name_format', {value: 'false'}).value};
}
static get SCROLL_TYPE_FREE() {
return 1;
@@ -27,12 +34,18 @@ export default class PostsView extends React.Component {
static get SCROLL_TYPE_BOTTOM() {
return 2;
}
- static get SIDEBAR_OPEN() {
+ static get SCROLL_TYPE_SIDEBAR_OPEN() {
return 3;
}
static get SCROLL_TYPE_NEW_MESSAGE() {
return 4;
}
+ static get SCROLL_TYPE_POST() {
+ return 5;
+ }
+ updateState() {
+ this.setState({displayNameType: PreferenceStore.getPreference(Preferences.CATEGORY_DISPLAY_SETTINGS, 'name_format', {value: 'false'}).value});
+ }
isAtBottom() {
return ((this.refs.postlist.scrollHeight - this.refs.postlist.scrollTop) === this.refs.postlist.clientHeight);
}
@@ -47,15 +60,22 @@ export default class PostsView extends React.Component {
}
}
this.wasAtBottom = this.isAtBottom();
+ if (!this.jumpToPostNode && childNodes.length > 0) {
+ this.jumpToPostNode = childNodes[childNodes.length - 1];
+ }
// --- --------
this.props.postViewScrolled(this.isAtBottom());
this.prevScrollHeight = this.refs.postlist.scrollHeight;
+ this.prevOffsetTop = this.jumpToPostNode.offsetTop;
}
loadMorePostsTop() {
this.props.loadMorePostsTopClicked();
}
+ loadMorePostsBottom() {
+ this.props.loadMorePostsBottomClicked();
+ }
createPosts(posts, order) {
const postCtls = [];
let previousPostDay = new Date(0);
@@ -63,15 +83,11 @@ export default class PostsView extends React.Component {
let renderedLastViewed = false;
- let numToDisplay = this.props.numPostsToDisplay;
- if (order.length - 1 < numToDisplay) {
- numToDisplay = order.length - 1;
- }
-
- for (let i = numToDisplay; i >= 0; i--) {
+ for (let i = order.length - 1; i >= 0; i--) {
const post = posts[order[i]];
const parentPost = posts[post.parent_id];
const prevPost = posts[order[i + 1]];
+ const postUserId = Utils.isSystemMessage(post) ? '' : post.user_id;
// If the post is a comment whose parent has been deleted, don't add it to the list.
if (parentPost && parentPost.state === Constants.POST_DELETED) {
@@ -83,32 +99,73 @@ export default class PostsView extends React.Component {
let hideProfilePic = false;
if (prevPost) {
- sameUser = prevPost.user_id === post.user_id && post.create_at - prevPost.create_at <= 1000 * 60 * 5;
+ const postIsComment = Utils.isComment(post);
+ const prevPostIsComment = Utils.isComment(prevPost);
+ const postFromWebhook = Boolean(post.props && post.props.from_webhook);
+ const prevPostFromWebhook = Boolean(prevPost.props && prevPost.props.from_webhook);
+ const prevPostUserId = Utils.isSystemMessage(prevPost) ? '' : prevPost.user_id;
+ let prevWebhookName = '';
+ if (prevPost.props && prevPost.props.override_username) {
+ prevWebhookName = prevPost.props.override_username;
+ }
+ let curWebhookName = '';
+ if (post.props && post.props.override_username) {
+ curWebhookName = post.props.override_username;
+ }
- sameRoot = Utils.isComment(post) && (prevPost.id === post.root_id || prevPost.root_id === post.root_id);
+ // consider posts from the same user if:
+ // the previous post was made by the same user as the current post,
+ // the previous post was made within 5 minutes of the current post,
+ // the previous post and current post are both from webhooks or both not,
+ // the previous post and current post have the same webhook usernames
+ if (prevPostUserId === postUserId &&
+ post.create_at - prevPost.create_at <= 1000 * 60 * 5 &&
+ postFromWebhook === prevPostFromWebhook &&
+ prevWebhookName === curWebhookName) {
+ sameUser = true;
+ }
+
+ // consider posts from the same root if:
+ // the current post is a comment,
+ // the current post has the same root as the previous post
+ if (postIsComment && (prevPost.id === post.root_id || prevPost.root_id === post.root_id)) {
+ sameRoot = true;
+ }
+
+ // consider posts from the same root if:
+ // the current post is not a comment,
+ // the previous post is not a comment,
+ // the previous post is from the same user
+ if (!postIsComment && !prevPostIsComment && sameUser) {
+ sameRoot = true;
+ }
// hide the profile pic if:
// the previous post was made by the same user as the current post,
// the previous post is not a comment,
// the current post is not a comment,
- // the current post is not from a webhook
- // and the previous post is not from a webhook
- if ((prevPost.user_id === post.user_id) &&
- !Utils.isComment(prevPost) &&
- !Utils.isComment(post) &&
- (!post.props || !post.props.from_webhook) &&
- (!prevPost.props || !prevPost.props.from_webhook)) {
+ // the previous post and current post are both from webhooks or both not,
+ // the previous post and current post have the same webhook usernames
+ if (prevPostUserId === postUserId &&
+ !prevPostIsComment &&
+ !postIsComment &&
+ postFromWebhook === prevPostFromWebhook &&
+ prevWebhookName === curWebhookName) {
hideProfilePic = true;
}
}
// check if it's the last comment in a consecutive string of comments on the same post
// it is the last comment if it is last post in the channel or the next post has a different root post
- var isLastComment = Utils.isComment(post) && (i === 0 || posts[order[i - 1]].root_id !== post.root_id);
+ const isLastComment = Utils.isComment(post) && (i === 0 || posts[order[i - 1]].root_id !== post.root_id);
+
+ const keyPrefix = post.id ? post.id : i;
+
+ const shouldHighlight = this.props.postsToHighlight && this.props.postsToHighlight.hasOwnProperty(post.id);
- var postCtl = (
+ const postCtl = (
<Post
- key={post.id + 'postKey'}
+ key={keyPrefix + 'postKey'}
ref={post.id}
sameUser={sameUser}
sameRoot={sameRoot}
@@ -117,6 +174,9 @@ export default class PostsView extends React.Component {
posts={posts}
hideProfilePic={hideProfilePic}
isLastComment={isLastComment}
+ shouldHighlight={shouldHighlight}
+ onClick={() => EventHelpers.emitPostFocusEvent(post.id)} //eslint-disable-line no-loop-func
+ displayNameType={this.state.displayNameType}
/>
);
@@ -133,7 +193,7 @@ export default class PostsView extends React.Component {
);
}
- if (post.user_id !== userId &&
+ if (postUserId !== userId &&
this.props.messageSeparatorTime !== 0 &&
post.create_at > this.props.messageSeparatorTime &&
!renderedLastViewed) {
@@ -178,9 +238,12 @@ export default class PostsView extends React.Component {
this.refs.postlist.scrollTop = this.refs.postlist.scrollHeight;
}
});
- } else if (this.props.scrollType === PostsView.SCROLL_TYPE_POST && this.props.scrollPost) {
+ } else if (this.props.scrollType === PostsView.SCROLL_TYPE_POST && this.props.scrollPostId) {
window.requestAnimationFrame(() => {
- const postNode = ReactDOM.findDOMNode(this.refs[this.props.scrollPost]);
+ const postNode = ReactDOM.findDOMNode(this.refs[this.props.scrollPostId]);
+ if (postNode == null) {
+ return;
+ }
postNode.scrollIntoView();
if (this.refs.postlist.scrollTop === postNode.offsetTop) {
this.refs.postlist.scrollTop -= (this.refs.postlist.offsetHeight / 3);
@@ -188,7 +251,7 @@ export default class PostsView extends React.Component {
this.refs.postlist.scrollTop -= (this.refs.postlist.offsetHeight / 3) + (this.refs.postlist.scrollTop - postNode.offsetTop);
}
});
- } else if (this.props.scrollType === PostsView.SIDEBAR_OPEN) {
+ } else if (this.props.scrollType === PostsView.SCROLL_TYPE_SIDEBAR_OPEN) {
// If we are at the bottom then stay there
if (this.wasAtBottom) {
this.refs.postlist.scrollTop = this.refs.postlist.scrollHeight;
@@ -204,7 +267,10 @@ export default class PostsView extends React.Component {
}
} else if (this.refs.postlist.scrollHeight !== this.prevScrollHeight) {
window.requestAnimationFrame(() => {
- this.refs.postlist.scrollTop += (this.refs.postlist.scrollHeight - this.prevScrollHeight);
+ // Only need to jump if we added posts to the top.
+ if (this.jumpToPostNode && (this.jumpToPostNode.offsetTop !== this.prevOffsetTop)) {
+ this.refs.postlist.scrollTop += (this.refs.postlist.scrollHeight - this.prevScrollHeight);
+ }
});
}
}
@@ -212,35 +278,47 @@ export default class PostsView extends React.Component {
this.updateScrolling();
}
componentDidMount() {
- this.updateScrolling();
+ if (this.props.postList != null) {
+ this.updateScrolling();
+ }
window.addEventListener('resize', this.handleResize);
}
componentWillUnmount() {
window.removeEventListener('resize', this.handleResize);
}
componentDidUpdate() {
- this.updateScrolling();
+ if (this.props.postList != null) {
+ this.updateScrolling();
+ }
+ }
+ componentWillReceiveProps(nextProps) {
+ if (!this.props.isActive && nextProps.isActive) {
+ this.updateState();
+ PreferenceStore.addChangeListener(this.updateState);
+ } else if (this.props.isActive && !nextProps.isActive) {
+ PreferenceStore.removeChangeListener(this.updateState);
+ }
}
- shouldComponentUpdate(nextProps) {
+ shouldComponentUpdate(nextProps, nextState) {
if (this.props.isActive !== nextProps.isActive) {
return true;
}
if (this.props.postList !== nextProps.postList) {
return true;
}
- if (this.props.scrollPost !== nextProps.scrollPost) {
+ if (this.props.scrollPostId !== nextProps.scrollPostId) {
return true;
}
if (this.props.scrollType !== nextProps.scrollType && nextProps.scrollType !== PostsView.SCROLL_TYPE_FREE) {
return true;
}
- if (this.props.numPostsToDisplay !== nextProps.numPostsToDisplay) {
+ if (this.props.messageSeparatorTime !== nextProps.messageSeparatorTime) {
return true;
}
- if (this.props.messageSeparatorTime !== nextProps.messageSeparatorTime) {
+ if (!Utils.areObjectsEqual(this.props.postList, nextProps.postList)) {
return true;
}
- if (!Utils.areStatesEqual(this.props.postList, nextProps.postList)) {
+ if (nextState.displayNameType !== this.state.displayNameType) {
return true;
}
@@ -249,7 +327,8 @@ export default class PostsView extends React.Component {
render() {
let posts = [];
let order = [];
- let moreMessages;
+ let moreMessagesTop;
+ let moreMessagesBottom;
let postElements;
let activeClass = 'inactive';
if (this.props.postList != null) {
@@ -257,10 +336,10 @@ export default class PostsView extends React.Component {
order = this.props.postList.order;
// Create intro message or top loadmore link
- if (order.length >= this.props.numPostsToDisplay) {
- moreMessages = (
+ if (this.props.showMoreMessagesTop) {
+ moreMessagesTop = (
<a
- ref='loadmore'
+ ref='loadmoretop'
className='more-messages-text theme'
href='#'
onClick={this.loadMorePostsTop}
@@ -269,7 +348,23 @@ export default class PostsView extends React.Component {
</a>
);
} else {
- moreMessages = this.props.introText;
+ moreMessagesTop = this.props.introText;
+ }
+
+ // Give option to load more posts at bottom if nessisary
+ if (this.props.showMoreMessagesBottom) {
+ moreMessagesBottom = (
+ <a
+ ref='loadmorebottom'
+ className='more-messages-text theme'
+ href='#'
+ onClick={this.loadMorePostsBottom}
+ >
+ {'Load more messages'}
+ </a>
+ );
+ } else {
+ moreMessagesBottom = null;
}
// Create post elements
@@ -292,8 +387,9 @@ export default class PostsView extends React.Component {
ref='postlistcontent'
className='post-list__content'
>
- {moreMessages}
+ {moreMessagesTop}
{postElements}
+ {moreMessagesBottom}
</div>
</div>
</div>
@@ -306,11 +402,14 @@ PostsView.defaultProps = {
PostsView.propTypes = {
isActive: React.PropTypes.bool,
postList: React.PropTypes.object,
- scrollPost: React.PropTypes.string,
+ scrollPostId: React.PropTypes.string,
scrollType: React.PropTypes.number,
postViewScrolled: React.PropTypes.func.isRequired,
loadMorePostsTopClicked: React.PropTypes.func.isRequired,
- numPostsToDisplay: React.PropTypes.number,
+ loadMorePostsBottomClicked: React.PropTypes.func.isRequired,
+ showMoreMessagesTop: React.PropTypes.bool,
+ showMoreMessagesBottom: React.PropTypes.bool,
introText: React.PropTypes.element,
- messageSeparatorTime: React.PropTypes.number
+ messageSeparatorTime: React.PropTypes.number,
+ postsToHighlight: React.PropTypes.object
};