summaryrefslogtreecommitdiffstats
path: root/webapp/components/post_view/post_list.jsx
diff options
context:
space:
mode:
Diffstat (limited to 'webapp/components/post_view/post_list.jsx')
-rw-r--r--webapp/components/post_view/post_list.jsx582
1 files changed, 0 insertions, 582 deletions
diff --git a/webapp/components/post_view/post_list.jsx b/webapp/components/post_view/post_list.jsx
deleted file mode 100644
index c13de7096..000000000
--- a/webapp/components/post_view/post_list.jsx
+++ /dev/null
@@ -1,582 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import Post from './post';
-import LoadingScreen from 'components/loading_screen.jsx';
-import FloatingTimestamp from './floating_timestamp.jsx';
-import ScrollToBottomArrows from './scroll_to_bottom_arrows.jsx';
-import NewMessageIndicator from './new_message_indicator.jsx';
-
-import * as UserAgent from 'utils/user_agent.jsx';
-import * as Utils from 'utils/utils.jsx';
-import Constants from 'utils/constants.jsx';
-import {createChannelIntroMessage} from 'utils/channel_intro_messages.jsx';
-import DelayedAction from 'utils/delayed_action.jsx';
-import EventTypes from 'utils/event_types.jsx';
-import GlobalEventEmitter from 'utils/global_event_emitter.jsx';
-
-import {FormattedDate, FormattedMessage} from 'react-intl';
-
-import React from 'react';
-import ReactDOM from 'react-dom';
-import PropTypes from 'prop-types';
-
-const CLOSE_TO_BOTTOM_SCROLL_MARGIN = 10;
-const POSTS_PER_PAGE = Constants.POST_CHUNK_SIZE / 2;
-
-export default class PostList extends React.PureComponent {
- static propTypes = {
-
- /**
- * Array of posts in the channel, ordered from oldest to newest
- */
- posts: PropTypes.array,
-
- /**
- * The number of posts that should be rendered
- */
- postVisibility: PropTypes.number,
-
- /**
- * The channel the posts are in
- */
- channel: PropTypes.object.isRequired,
-
- /**
- * The last time the channel was viewed, sets the new message separator
- */
- lastViewedAt: PropTypes.number,
-
- /**
- * Set if more posts are being loaded
- */
- loadingPosts: PropTypes.bool,
-
- /**
- * The user id of the logged in user
- */
- currentUserId: PropTypes.string,
-
- /**
- * Set to focus this post
- */
- focusedPostId: PropTypes.array,
-
- /**
- * Whether to display the channel intro at full width
- */
- fullWidth: PropTypes.bool,
-
- actions: PropTypes.shape({
-
- /**
- * Function to get posts in the channel
- */
- getPosts: PropTypes.func.isRequired,
-
- /**
- * Function to get posts in the channel older than the focused post
- */
- getPostsBefore: PropTypes.func.isRequired,
-
- /**
- * Function to get posts in the channel newer than the focused post
- */
- getPostsAfter: PropTypes.func.isRequired,
-
- /**
- * Function to get the post thread for the focused post
- */
- getPostThread: PropTypes.func.isRequired,
-
- /**
- * Function to increase the number of posts being rendered
- */
- increasePostVisibility: PropTypes.func.isRequired
- }).isRequired
- }
-
- constructor(props) {
- super(props);
-
- this.scrollStopAction = new DelayedAction(this.handleScrollStop);
-
- this.previousScrollTop = Number.MAX_SAFE_INTEGER;
- this.previousScrollHeight = 0;
- this.previousClientHeight = 0;
- this.atBottom = false;
-
- this.state = {
- atEnd: false,
- unViewedCount: 0,
- isScrolling: false,
- lastViewed: props.lastViewedAt
- };
- }
-
- componentDidMount() {
- this.loadPosts(this.props.channel.id, this.props.focusedPostId);
- GlobalEventEmitter.addListener(EventTypes.POST_LIST_SCROLL_CHANGE, this.handleResize);
-
- window.addEventListener('resize', () => this.handleResize());
- }
-
- componentWillUnmount() {
- GlobalEventEmitter.removeListener(EventTypes.POST_LIST_SCROLL_CHANGE, this.handleResize);
- window.removeEventListener('resize', () => this.handleResize());
- }
-
- componentWillReceiveProps(nextProps) {
- // Focusing on a new post so load posts around it
- if (nextProps.focusedPostId && this.props.focusedPostId !== nextProps.focusedPostId) {
- this.hasScrolledToFocusedPost = false;
- this.hasScrolledToNewMessageSeparator = false;
- this.setState({atEnd: false});
- this.loadPosts(nextProps.channel.id, nextProps.focusedPostId);
- return;
- }
-
- const channel = this.props.channel || {};
- const nextChannel = nextProps.channel || {};
-
- if (nextProps.focusedPostId == null) {
- // Channel changed so load posts for new channel
- if (channel.id !== nextChannel.id) {
- this.hasScrolled = false;
- this.hasScrolledToFocusedPost = false;
- this.hasScrolledToNewMessageSeparator = false;
- this.atBottom = false;
- this.setState({atEnd: false, lastViewed: nextProps.lastViewedAt});
-
- if (nextChannel.id) {
- this.loadPosts(nextChannel.id);
- }
- }
-
- const nextPosts = nextProps.posts || [];
- const posts = this.props.posts || [];
- const hasNewPosts = (posts.length === 0 && nextPosts.length > 0) || (posts.length > 0 && nextPosts.length > 0 && posts[0].id !== nextPosts[0].id);
-
- if (!this.checkBottom() && hasNewPosts) {
- this.setUnreadsBelow(nextPosts, nextProps.currentUserId);
- }
- }
- }
-
- componentWillUpdate() {
- if (this.refs.postlist) {
- this.previousScrollTop = this.refs.postlist.scrollTop;
- this.previousScrollHeight = this.refs.postlist.scrollHeight;
- this.previousClientHeight = this.refs.postlist.clientHeight;
- }
- }
-
- componentDidUpdate(prevProps, prevState) {
- // Do not update scrolling unless posts, visibility or intro message change
- if (this.props.posts === prevProps.posts && this.props.postVisibility === prevProps.postVisibility && this.state.atEnd === prevState.atEnd) {
- return;
- }
-
- const prevPosts = prevProps.posts;
- const posts = this.props.posts;
- const postList = this.refs.postlist;
-
- if (!postList) {
- return;
- }
-
- // Scroll to focused post on first load
- const focusedPost = this.refs[this.props.focusedPostId];
- if (focusedPost && this.props.posts) {
- if (!this.hasScrolledToFocusedPost) {
- const element = ReactDOM.findDOMNode(focusedPost);
- const rect = element.getBoundingClientRect();
- const listHeight = postList.clientHeight / 2;
- postList.scrollTop += rect.top - listHeight;
- } else if (this.previousScrollHeight !== postList.scrollHeight && posts[0].id === prevPosts[0].id) {
- postList.scrollTop = this.previousScrollTop + (postList.scrollHeight - this.previousScrollHeight);
- }
- return;
- }
-
- // Scroll to new message indicator or bottom on first load
- const messageSeparator = this.refs.newMessageSeparator;
- if (messageSeparator && !this.hasScrolledToNewMessageSeparator) {
- const element = ReactDOM.findDOMNode(messageSeparator);
- element.scrollIntoView();
- if (!this.checkBottom()) {
- this.setUnreadsBelow(posts, this.props.currentUserId);
- }
- return;
- } else if (postList && !this.hasScrolledToNewMessageSeparator) {
- postList.scrollTop = postList.scrollHeight;
- this.atBottom = true;
- return;
- }
-
- if (postList && prevPosts && posts && posts[0] && prevPosts[0]) {
- // A new message was posted, so scroll to bottom if user
- // was already scrolled close to bottom
- let doScrollToBottom = false;
- const postId = posts[0].id;
- const prevPostId = prevPosts[0].id;
- const pendingPostId = posts[0].pending_post_id;
- if (postId !== prevPostId && pendingPostId !== prevPostId) {
- // If already scrolled to bottom
- if (this.atBottom) {
- doScrollToBottom = true;
- }
-
- // If new post was ephemeral
- if (Utils.isPostEphemeral(posts[0])) {
- doScrollToBottom = true;
- }
- }
-
- if (doScrollToBottom) {
- this.atBottom = true;
- postList.scrollTop = postList.scrollHeight;
- return;
- }
-
- // New posts added at the top, maintain scroll position
- if (this.previousScrollHeight !== postList.scrollHeight && posts[0].id === prevPosts[0].id) {
- postList.scrollTop = this.previousScrollTop + (postList.scrollHeight - this.previousScrollHeight);
- }
- }
- }
-
- setUnreadsBelow = (posts, currentUserId) => {
- const unViewedCount = posts.reduce((count, post) => {
- if (post.create_at > this.state.lastViewed &&
- post.user_id !== currentUserId &&
- post.state !== Constants.POST_DELETED) {
- return count + 1;
- }
- return count;
- }, 0);
- this.setState({unViewedCount});
- }
-
- handleScrollStop = () => {
- this.setState({
- isScrolling: false
- });
- }
-
- checkBottom = () => {
- if (!this.refs.postlist) {
- return true;
- }
-
- // No scroll bar so we're at the bottom
- if (this.refs.postlist.scrollHeight <= this.refs.postlist.clientHeight) {
- return true;
- }
-
- return this.refs.postlist.clientHeight + this.refs.postlist.scrollTop >= this.refs.postlist.scrollHeight - CLOSE_TO_BOTTOM_SCROLL_MARGIN;
- }
-
- handleResize = (forceScrollToBottom) => {
- const postList = this.refs.postlist;
- const messageSeparator = this.refs.newMessageSeparator;
- const doScrollToBottom = this.atBottom || forceScrollToBottom;
-
- if (postList) {
- if (doScrollToBottom) {
- postList.scrollTop = postList.scrollHeight;
- } else if (!this.hasScrolled && messageSeparator) {
- const element = ReactDOM.findDOMNode(messageSeparator);
- element.scrollIntoView();
- }
-
- this.previousScrollHeight = postList.scrollHeight;
- this.previousScrollTop = postList.scrollTop;
- this.previousClientHeight = postList.clientHeight;
-
- this.atBottom = this.checkBottom();
- }
- }
-
- loadPosts = async (channelId, focusedPostId) => {
- let posts;
- if (focusedPostId) {
- const getPostThreadAsync = this.props.actions.getPostThread(focusedPostId, false);
- const getPostsBeforeAsync = this.props.actions.getPostsBefore(channelId, focusedPostId, 0, POSTS_PER_PAGE);
- const getPostsAfterAsync = this.props.actions.getPostsAfter(channelId, focusedPostId, 0, POSTS_PER_PAGE);
-
- posts = await getPostsBeforeAsync;
- await getPostsAfterAsync;
- await getPostThreadAsync;
-
- this.hasScrolledToFocusedPost = true;
- } else {
- posts = await this.props.actions.getPosts(channelId, 0, POSTS_PER_PAGE);
- this.hasScrolledToNewMessageSeparator = true;
- }
-
- if (posts && posts.order.length < POSTS_PER_PAGE) {
- this.setState({atEnd: true});
- }
- }
-
- loadMorePosts = (e) => {
- if (e) {
- e.preventDefault();
- }
-
- this.props.actions.increasePostVisibility(this.props.channel.id, this.props.focusedPostId).then((moreToLoad) => {
- this.setState({atEnd: !moreToLoad && this.props.posts.length < this.props.postVisibility});
- });
- }
-
- handleScroll = () => {
- // Only count as user scroll if we've already performed our first load scroll
- this.hasScrolled = this.hasScrolledToNewMessageSeparator || this.hasScrolledToFocusedPost;
- if (!this.refs.postlist) {
- return;
- }
-
- this.previousScrollTop = this.refs.postlist.scrollTop;
-
- if (this.refs.postlist.scrollHeight === this.previousScrollHeight) {
- this.atBottom = this.checkBottom();
- }
-
- this.updateFloatingTimestamp();
-
- if (!this.state.isScrolling) {
- this.setState({
- isScrolling: true
- });
- }
-
- if (this.checkBottom()) {
- this.setState({
- lastViewed: new Date().getTime(),
- unViewedCount: 0,
- isScrolling: false
- });
- }
-
- this.scrollStopAction.fireAfter(Constants.SCROLL_DELAY);
- }
-
- updateFloatingTimestamp = () => {
- // skip this in non-mobile view since that's when the timestamp is visible
- if (!Utils.isMobile()) {
- return;
- }
-
- if (this.props.posts) {
- // iterate through posts starting at the bottom since users are more likely to be viewing newer posts
- for (let i = 0; i < this.props.posts.length; i++) {
- const post = this.props.posts[i];
- const element = this.refs[post.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 topPost;
-
- if (i > 0) {
- topPost = this.props.posts[i - 1];
- } else {
- // the first post we look at should always be on the screen, but handle that case anyway
- topPost = post;
- }
-
- if (!this.state.topPost || topPost.id !== this.state.topPost.id) {
- this.setState({
- topPost
- });
- }
-
- break;
- }
- }
- }
- }
-
- scrollToBottom = () => {
- if (this.refs.postlist) {
- this.refs.postlist.scrollTop = this.refs.postlist.scrollHeight;
- }
- }
-
- createPosts = (posts) => {
- const postCtls = [];
- let previousPostDay = new Date(0);
- const currentUserId = this.props.currentUserId;
- const lastViewed = this.props.lastViewedAt || 0;
-
- let renderedLastViewed = false;
-
- for (let i = posts.length - 1; i >= 0; i--) {
- const post = posts[i];
-
- if (post == null) {
- continue;
- }
-
- const postCtl = (
- <Post
- ref={post.id}
- key={'post ' + (post.id || post.pending_post_id)}
- post={post}
- lastPostCount={(i >= 0 && i < Constants.TEST_ID_COUNT) ? i : -1}
- 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 (post.user_id !== currentUserId &&
- lastViewed !== 0 &&
- post.create_at > 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;
- }
-
- getPostList = () => {
- return this.refs.postlist;
- }
-
- render() {
- const posts = this.props.posts;
- const channel = this.props.channel;
-
- if (posts == null || channel == null) {
- return (
- <div id='post-list'>
- <LoadingScreen
- position='absolute'
- key='loading'
- />
- </div>
- );
- }
-
- let topRow;
- if (this.state.atEnd) {
- topRow = createChannelIntroMessage(channel, this.props.fullWidth);
- } else if (this.props.postVisibility >= Constants.MAX_POST_VISIBILITY) {
- topRow = (
- <div className='post-list__loading post-list__loading-search'>
- <FormattedMessage
- id='posts_view.maxLoaded'
- defaultMessage='Looking for a specific message? Try searching for it'
- />
- </div>
- );
- } else {
- topRow = (
- <a
- ref='loadmoretop'
- className='more-messages-text theme'
- href='#'
- onClick={this.loadMorePosts}
- >
- <FormattedMessage
- id='posts_view.loadMore'
- defaultMessage='Load more messages'
- />
- </a>
- );
- }
-
- const topPostCreateAt = this.state.topPost ? this.state.topPost.create_at : 0;
-
- let postVisibility = this.props.postVisibility;
-
- // In focus mode there's an extra (Constants.POST_CHUNK_SIZE / 2) posts to show
- if (this.props.focusedPostId) {
- postVisibility += Constants.POST_CHUNK_SIZE / 2;
- }
-
- return (
- <div id='post-list'>
- <FloatingTimestamp
- isScrolling={this.state.isScrolling}
- isMobile={Utils.isMobile()}
- createAt={topPostCreateAt}
- />
- <ScrollToBottomArrows
- isScrolling={this.state.isScrolling}
- atBottom={this.atBottom}
- onClick={this.scrollToBottom}
- />
- <NewMessageIndicator
- newMessages={this.state.unViewedCount}
- onClick={this.scrollToBottom}
- />
- <div
- ref='postlist'
- className='post-list-holder-by-time'
- key={'postlist-' + channel.id}
- onScroll={this.handleScroll}
- >
- <div className='post-list__table'>
- <div
- ref='postlistcontent'
- className='post-list__content'
- >
- {topRow}
- {this.createPosts(posts.slice(0, postVisibility))}
- </div>
- </div>
- </div>
- </div>
- );
- }
-}