diff options
Diffstat (limited to 'webapp/components/post_view/components/post_list.jsx')
-rw-r--r-- | webapp/components/post_view/components/post_list.jsx | 690 |
1 files changed, 0 insertions, 690 deletions
diff --git a/webapp/components/post_view/components/post_list.jsx b/webapp/components/post_view/components/post_list.jsx deleted file mode 100644 index ec3c8dc6a..000000000 --- a/webapp/components/post_view/components/post_list.jsx +++ /dev/null @@ -1,690 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. -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'; - -import {createChannelIntroMessage} from 'utils/channel_intro_messages.jsx'; - -import * as UserAgent from 'utils/user_agent.jsx'; -import * as Utils from 'utils/utils.jsx'; -import * as PostUtils from 'utils/post_utils.jsx'; -import DelayedAction from 'utils/delayed_action.jsx'; - -import * as ChannelActions from 'actions/channel_actions.jsx'; - -import Constants from 'utils/constants.jsx'; -const ScrollTypes = Constants.ScrollTypes; - -import PostStore from 'stores/post_store.jsx'; -import PreferenceStore from 'stores/preference_store.jsx'; -import ScrollStore from 'stores/scroll_store.jsx'; -import {FormattedDate, FormattedMessage} from 'react-intl'; - -import PropTypes from 'prop-types'; - -import React from 'react'; -import ReactDOM from 'react-dom'; - -const Preferences = Constants.Preferences; - -export default class PostList extends React.Component { - constructor(props) { - super(props); - - this.handleScroll = this.handleScroll.bind(this); - this.handleScrollStop = this.handleScrollStop.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); - this.scrollToBottom = this.scrollToBottom.bind(this); - this.scrollToBottomAnimated = this.scrollToBottomAnimated.bind(this); - this.handleKeyDown = this.handleKeyDown.bind(this); - this.childComponentDidUpdate = this.childComponentDidUpdate.bind(this); - this.checkAndUpdateScrolling = this.checkAndUpdateScrolling.bind(this); - - this.jumpToPostNode = null; - this.wasAtBottom = true; - this.scrollHeight = 0; - this.animationFrameId = 0; - - this.scrollStopAction = new DelayedAction(this.handleScrollStop); - - 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, - unViewedCount: 0 - }; - - if (props.channel) { - this.introText = createChannelIntroMessage(props.channel, this.state.fullWidthIntro); - } else { - this.introText = this.getArchivesIntroMessage(); - } - } - - componentWillReceiveProps(nextProps) { - if (this.props.channel && this.props.channel.type === Constants.DM_CHANNEL) { - const teammateId = Utils.getUserIdFromChannelName(this.props.channel); - if (!this.props.profiles[teammateId] && nextProps.profiles[teammateId]) { - 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) { - unViewedCount = order.reduce((count, orderId) => { - const post = posts[orderId]; - if (post.create_at > nextProps.lastViewedBottom && - post.user_id !== nextProps.currentUser.id && - post.state !== Constants.POST_DELETED) { - return count + 1; - } - return count; - }, 0); - } - this.setState({unViewedCount}); - - if (this.props.channelId !== nextProps.channelId) { - PostStore.removePostDraftChangeListener(this.props.channelId, this.handlePostDraftChange); - PostStore.addPostDraftChangeListener(nextProps.channelId, this.handlePostDraftChange); - } - } - - handleKeyDown(e) { - if (e.which === Constants.KeyCodes.ESCAPE && $('.popover.in,.modal.in').length === 0) { - e.preventDefault(); - ChannelActions.setChannelAsRead(); - } - } - - isAtBottom() { - if (!this.refs.postlist) { - return this.wasAtBottom; - } - - // consider the view to be at the bottom if it's within this many pixels of the bottom - const atBottomMargin = 10; - - return this.refs.postlist.clientHeight + this.refs.postlist.scrollTop >= this.refs.postlist.scrollHeight - atBottomMargin; - } - - handleScroll() { - // HACK FOR RHS -- REMOVE WHEN RHS DIES - const childNodes = this.refs.postlistcontent.childNodes; - for (let i = 0; i < childNodes.length; i++) { - // If the node is 1/3 down the page - if (childNodes[i].offsetTop >= (this.refs.postlist.scrollTop + (this.refs.postlist.offsetHeight / Constants.SCROLL_PAGE_FRACTION))) { - this.jumpToPostNode = childNodes[i]; - break; - } - } - if (!this.jumpToPostNode && childNodes.length > 0) { - this.jumpToPostNode = childNodes[childNodes.length - 1]; - } - - this.updateFloatingTimestamp(); - - if (!this.state.isScrolling) { - this.setState({ - isScrolling: true - }); - } - - // Postpone all DOM related calculations to next frame. - // scrollHeight etc might return wrong data at this point - setTimeout(() => { - if (!this.refs.postlist) { - return; - } - - this.wasAtBottom = this.isAtBottom(); - this.props.postListScrolled(this.isAtBottom()); - this.prevScrollHeight = this.refs.postlist.scrollHeight; - this.prevOffsetTop = this.jumpToPostNode.offsetTop; - }, 0); - - this.scrollStopAction.fireAfter(Constants.SCROLL_DELAY); - } - - handleScrollStop() { - this.setState({ - isScrolling: false - }); - } - - updateFloatingTimestamp() { - // skip this in non-mobile view since that's when the timestamp is visible - if (!Utils.isMobile()) { - return; - } - - if (this.props.postList) { - // iterate through posts starting at the bottom since users are more likely to be viewing newer posts - for (let i = 0; i < this.props.postList.order.length; i++) { - const id = this.props.postList.order[i]; - const element = this.refs[id]; - - if (!element || !element.domNode || element.domNode.offsetTop + element.domNode.clientHeight <= this.refs.postlist.scrollTop) { - // this post is off the top of the screen so the last one is at the top of the screen - let topPostId; - - if (i > 0) { - topPostId = this.props.postList.order[i - 1]; - } else { - // the first post we look at should always be on the screen, but handle that case anyway - topPostId = id; - } - - if (topPostId !== this.state.topPostId) { - this.setState({ - topPostId - }); - } - - break; - } - } - } - } - - loadMorePostsTop(e) { - e.preventDefault(); - - if (this.props.isFocusPost) { - return GlobalActions.emitLoadMorePostsFocusedTopEvent(); - } - return GlobalActions.emitLoadMorePostsEvent(); - } - - loadMorePostsBottom() { - GlobalActions.emitLoadMorePostsFocusedBottomEvent(); - } - - createPosts(posts, order) { - const postCtls = []; - let previousPostDay = new Date(0); - const userId = this.props.currentUser.id; - const profiles = this.props.profiles || {}; - - let renderedLastViewed = false; - - 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 = PostUtils.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) { - continue; - } - - let sameUser = false; - let sameRoot = false; - let hideProfilePic = false; - - if (prevPost) { - const postIsComment = PostUtils.isComment(post); - const prevPostIsComment = PostUtils.isComment(prevPost); - const postFromWebhook = Boolean(post.props && post.props.from_webhook); - const prevPostFromWebhook = Boolean(prevPost.props && prevPost.props.from_webhook); - const prevPostUserId = PostUtils.isSystemMessage(prevPost) ? '' : prevPost.user_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 current post is not from a webhook - // the previous post is not from a webhook - if (prevPostUserId === postUserId && - post.create_at - prevPost.create_at <= Constants.POST_COLLAPSE_TIMEOUT && - !postFromWebhook && !prevPostFromWebhook) { - 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 previous post is not from a webhook - // the current post is not from a webhook - if (prevPostUserId === postUserId && - !prevPostIsComment && - !postIsComment && - !prevPostFromWebhook && - !postFromWebhook) { - 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 - const isLastComment = PostUtils.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); - - let profile; - if (userId === post.user_id) { - profile = this.props.currentUser; - } else { - profile = profiles[post.user_id]; - } - - let commentCount = 0; - let isCommentMention = false; - let shouldHighlightThreads = false; - let commentRootId; - if (parentPost) { - commentRootId = post.root_id; - } else { - commentRootId = post.id; - } - - if (commentRootId) { - for (const postId in posts) { - if (posts[postId].root_id === commentRootId && !PostUtils.isSystemMessage(posts[postId])) { - commentCount += 1; - if (posts[postId].user_id === userId) { - shouldHighlightThreads = true; - } - } - } - } - - if (parentPost && commentRootId) { - const commentsNotifyLevel = this.props.currentUser.notify_props.comments || 'never'; - const notCurrentUser = post.user_id !== userId || (post.props && post.props.from_webhook); - if (notCurrentUser) { - if (commentsNotifyLevel === 'any' && (posts[commentRootId].user_id === userId || shouldHighlightThreads)) { - isCommentMention = true; - } else if (commentsNotifyLevel === 'root' && posts[commentRootId].user_id === userId) { - isCommentMention = true; - } - } - } - - let isFlagged = false; - if (this.props.flaggedPosts) { - isFlagged = this.props.flaggedPosts.get(post.id) === 'true'; - } - - let status = ''; - if (this.props.statuses && profile) { - status = this.props.statuses[profile.id] || 'offline'; - } - - const postCtl = ( - <Post - key={keyPrefix + 'postKey'} - ref={post.id} - lastPostCount={(i >= 0 && i < Constants.TEST_ID_COUNT) ? i : -1} - sameUser={sameUser} - sameRoot={sameRoot} - post={post} - parentPost={parentPost} - hideProfilePic={hideProfilePic} - isLastComment={isLastComment} - shouldHighlight={shouldHighlight} - displayNameType={this.props.displayNameType} - user={profile} - currentUser={this.props.currentUser} - center={this.props.displayPostsInCenter} - commentCount={commentCount} - isCommentMention={isCommentMention} - compactDisplay={this.props.compactDisplay} - previewCollapsed={this.props.previewsCollapsed} - useMilitaryTime={this.props.useMilitaryTime} - isFlagged={isFlagged} - status={status} - isBusy={this.props.isBusy} - childComponentDidUpdateFunction={this.childComponentDidUpdate} - getPostList={this.getPostList} - /> - ); - - const currentPostDay = Utils.getDateForUnixTicks(post.create_at); - if (currentPostDay.toDateString() !== previousPostDay.toDateString()) { - postCtls.push( - <div - key={currentPostDay.toDateString()} - className='date-separator' - > - <hr className='separator__hr'/> - <div className='separator__text'> - <FormattedDate - value={currentPostDay} - weekday='short' - month='short' - day='2-digit' - year='numeric' - /> - </div> - </div> - ); - } - - if ((postUserId !== userId || this.props.ownNewMessage) && - this.props.lastViewed !== 0 && - post.create_at > this.props.lastViewed && - !Utils.isPostEphemeral(post) && - !renderedLastViewed) { - renderedLastViewed = true; - - // Temporary fix to solve ie11 rendering issue - let newSeparatorId = ''; - if (!UserAgent.isInternetExplorer()) { - newSeparatorId = 'new_message_' + post.id; - } - postCtls.push( - <div - id={newSeparatorId} - key='unviewed' - ref='newMessageSeparator' - className='new-separator' - > - <hr - className='separator__hr' - /> - <div className='separator__text'> - <FormattedMessage - id='posts_view.newMsg' - defaultMessage='New Messages' - /> - </div> - </div> - ); - } - postCtls.push(postCtl); - previousPostDay = currentPostDay; - } - - return postCtls; - } - - updateScrolling() { - if (this.props.scrollType === ScrollTypes.BOTTOM) { - this.scrollToBottom(); - } else if (this.props.scrollType === ScrollTypes.NEW_MESSAGE) { - window.requestAnimationFrame(() => { - // If separator exists scroll to it. Otherwise scroll to bottom. - if (this.refs.newMessageSeparator) { - var objDiv = this.refs.postlist; - objDiv.scrollTop = this.refs.newMessageSeparator.offsetTop; //scrolls node to top of Div - } else if (this.refs.postlist) { - this.scrollToBottom(); - } - }); - - // This avoids the scroll jumping from top to bottom after the page has rendered (PLT-5025). - if (!this.refs.newMessageSeparator) { - this.scrollToBottom(); - } - } else if (this.props.scrollType === ScrollTypes.POST && this.props.scrollPostId) { - window.requestAnimationFrame(() => { - 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 / Constants.SCROLL_PAGE_FRACTION); - } else { - this.refs.postlist.scrollTop -= (this.refs.postlist.offsetHeight / Constants.SCROLL_PAGE_FRACTION) + (this.refs.postlist.scrollTop - postNode.offsetTop); - } - }); - } else if (this.props.scrollType === ScrollTypes.SIDEBAR_OPEN) { - // If we are at the bottom then stay there - if (this.wasAtBottom) { - this.refs.postlist.scrollTop = this.refs.postlist.scrollHeight; - } else { - window.requestAnimationFrame(() => { - this.jumpToPostNode.scrollIntoView(); - if (this.refs.postlist.scrollTop === this.jumpToPostNode.offsetTop) { - this.refs.postlist.scrollTop -= (this.refs.postlist.offsetHeight / Constants.SCROLL_PAGE_FRACTION); - } else { - this.refs.postlist.scrollTop -= (this.refs.postlist.offsetHeight / Constants.SCROLL_PAGE_FRACTION) + (this.refs.postlist.scrollTop - this.jumpToPostNode.offsetTop); - } - }); - } - } else if (this.refs.postlist.scrollHeight !== this.prevScrollHeight) { - window.requestAnimationFrame(() => { - if (this.jumpToPostNode && this.refs.postlist) { - this.refs.postlist.scrollTop += (this.jumpToPostNode.offsetTop - this.prevOffsetTop); - } - }); - } - } - - handleResize() { - this.updateScrolling(); - } - - scrollToBottom() { - this.animationFrameId = window.requestAnimationFrame(() => { - if (this.refs.postlist) { - this.refs.postlist.scrollTop = this.refs.postlist.scrollHeight; - } - }); - } - - scrollToBottomAnimated() { - if (UserAgent.isIos()) { - // JQuery animation doesn't work on iOS - this.refs.postlist.scrollTop = this.refs.postlist.scrollHeight; - } else { - var postList = $(this.refs.postlist); - - postList.animate({scrollTop: this.refs.postlist.scrollHeight}, '500'); - } - } - - getArchivesIntroMessage() { - return ( - <div className={'channel-intro'}> - <h4 className='channel-intro__title'> - <FormattedMessage - id='post_focus_view.beginning' - defaultMessage='Beginning of Channel Archives' - /> - </h4> - </div> - ); - } - - checkAndUpdateScrolling() { - if (this.props.postList != null && this.refs.postlist) { - this.updateScrolling(); - } - } - - componentDidMount() { - if (this.props.postList != null) { - this.updateScrolling(); - } - - window.addEventListener('resize', this.handleResize); - window.addEventListener('keydown', this.handleKeyDown); - - PostStore.addPostDraftChangeListener(this.props.channelId, this.handlePostDraftChange); - ScrollStore.addPostScrollListener(this.checkAndUpdateScrolling); - } - - handlePostDraftChange = (draft) => { - // this.state.draft isn't used anywhere, but this will cause an update to the scroll position - // without causing two updates to trigger when something else changes - this.setState({ - draft - }); - } - - componentWillUnmount() { - window.cancelAnimationFrame(this.animationFrameId); - window.removeEventListener('resize', this.handleResize); - window.removeEventListener('keydown', this.handleKeyDown); - ScrollStore.removePostScrollListener(this.checkAndUpdateScrolling); - this.scrollStopAction.cancel(); - - PostStore.removePostDraftChangeListener(this.props.channelId, this.handlePostDraftChange); - } - - componentDidUpdate() { - this.checkAndUpdateScrolling(); - } - - childComponentDidUpdate() { - this.checkAndUpdateScrolling(); - } - - getPostList = () => { - return this.refs.postlist; - } - - render() { - // Create intro message or top loadmore link - let moreMessagesTop; - if (this.props.showMoreMessagesTop) { - moreMessagesTop = ( - <a - ref='loadmoretop' - className='more-messages-text theme' - href='#' - onClick={this.loadMorePostsTop} - > - <FormattedMessage - id='posts_view.loadMore' - defaultMessage='Load more messages' - /> - </a> - ); - } else { - moreMessagesTop = this.introText; - } - - // Give option to load more posts at bottom if necessary - let moreMessagesBottom; - if (this.props.showMoreMessagesBottom) { - moreMessagesBottom = ( - <a - ref='loadmorebottom' - className='more-messages-text theme' - href='#' - onClick={this.loadMorePostsBottom} - > - <FormattedMessage id='posts_view.loadMore'/> - </a> - ); - } - - // Create post elements - let postElements = null; - let topPostCreateAt = 0; - if (this.props.postList) { - const posts = this.props.postList.posts; - const order = this.props.postList.order; - - postElements = this.createPosts(posts, order); - - if (this.state.topPostId && this.props.postList.posts[this.state.topPostId]) { - topPostCreateAt = this.props.postList.posts[this.state.topPostId].create_at; - } - } - - return ( - <div> - <FloatingTimestamp - isScrolling={this.state.isScrolling} - isMobile={Utils.isMobile()} - createAt={topPostCreateAt} - /> - <ScrollToBottomArrows - isScrolling={this.state.isScrolling} - atBottom={this.wasAtBottom} - onClick={this.scrollToBottomAnimated} - /> - <NewMessageIndicator - newMessages={this.state.unViewedCount} - onClick={this.scrollToBottomAnimated} - /> - <div - ref='postlist' - className='post-list-holder-by-time' - onScroll={this.handleScroll} - > - <div className='post-list__table'> - <div - ref='postlistcontent' - className='post-list__content' - > - {moreMessagesTop} - {postElements} - {moreMessagesBottom} - </div> - </div> - </div> - </div> - ); - } -} - -PostList.defaultProps = { - lastViewed: 0, - lastViewedBottom: Number.MAX_VALUE, - ownNewMessage: false -}; - -PostList.propTypes = { - postList: PropTypes.object, - profiles: PropTypes.object, - channel: PropTypes.object, - channelId: PropTypes.string.isRequired, - currentUser: PropTypes.object, - scrollPostId: PropTypes.string, - scrollType: PropTypes.number, - postListScrolled: PropTypes.func.isRequired, - showMoreMessagesTop: PropTypes.bool, - showMoreMessagesBottom: PropTypes.bool, - lastViewed: PropTypes.number, - lastViewedBottom: PropTypes.number, - ownNewMessage: PropTypes.bool, - postsToHighlight: PropTypes.object, - displayNameType: PropTypes.string, - displayPostsInCenter: PropTypes.bool, - compactDisplay: PropTypes.bool, - previewsCollapsed: PropTypes.string, - useMilitaryTime: PropTypes.bool.isRequired, - isFocusPost: PropTypes.bool, - flaggedPosts: PropTypes.object, - statuses: PropTypes.object, - isBusy: PropTypes.bool -}; |