diff options
Diffstat (limited to 'web/react/components/posts_view.jsx')
-rw-r--r-- | web/react/components/posts_view.jsx | 187 |
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 }; |