From 9e8cd937908d5d2e730e94f761d6533eb2d95e28 Mon Sep 17 00:00:00 2001 From: Christopher Speller Date: Wed, 18 Nov 2015 17:29:06 -0500 Subject: Implementing Permalinks and jumping to post from search. Performance improvements. --- web/react/components/center_panel.jsx | 33 +++++-- web/react/components/channel_invite_modal.jsx | 13 ++- web/react/components/channel_loader.jsx | 4 +- web/react/components/channel_members_modal.jsx | 14 ++- web/react/components/create_post.jsx | 11 +-- web/react/components/post.jsx | 34 ++++--- web/react/components/post_focus_view.jsx | 110 +++++++++++++++++++++ web/react/components/post_info.jsx | 69 ++++++++++++- web/react/components/posts_view.jsx | 91 ++++++++++++----- web/react/components/posts_view_container.jsx | 129 ++++++------------------- web/react/components/rhs_thread.jsx | 2 +- web/react/components/search_results_item.jsx | 34 +------ web/react/components/sidebar.jsx | 4 - 13 files changed, 348 insertions(+), 200 deletions(-) create mode 100644 web/react/components/post_focus_view.jsx (limited to 'web/react/components') diff --git a/web/react/components/center_panel.jsx b/web/react/components/center_panel.jsx index c2ecf4fa2..3c6a36ad4 100644 --- a/web/react/components/center_panel.jsx +++ b/web/react/components/center_panel.jsx @@ -4,11 +4,13 @@ import TutorialIntroScreens from './tutorial/tutorial_intro_screens.jsx'; import CreatePost from './create_post.jsx'; import PostsViewContainer from './posts_view_container.jsx'; +import PostFocusView from './post_focus_view.jsx'; import ChannelHeader from './channel_header.jsx'; import Navbar from './navbar.jsx'; import FileUploadOverlay from './file_upload_overlay.jsx'; import PreferenceStore from '../stores/preference_store.jsx'; +import ChannelStore from '../stores/channel_store.jsx'; import UserStore from '../stores/user_store.jsx'; import Constants from '../utils/constants.jsx'; @@ -20,26 +22,48 @@ export default class CenterPanel extends React.Component { super(props); this.onPreferenceChange = this.onPreferenceChange.bind(this); + this.onChannelChange = this.onChannelChange.bind(this); const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '999'}); - this.state = {showTutorialScreens: parseInt(tutorialPref.value, 10) === TutorialSteps.INTRO_SCREENS}; + this.state = { + showTutorialScreens: parseInt(tutorialPref.value, 10) === TutorialSteps.INTRO_SCREENS, + showPostFocus: ChannelStore.getPostMode() === ChannelStore.POST_MODE_FOCUS + }; } componentDidMount() { PreferenceStore.addChangeListener(this.onPreferenceChange); + ChannelStore.addChangeListener(this.onChannelChange); } componentWillUnmount() { PreferenceStore.removeChangeListener(this.onPreferenceChange); + ChannelStore.removeChangeListener(this.onChannelChange); } onPreferenceChange() { const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '999'}); this.setState({showTutorialScreens: parseInt(tutorialPref.value, 10) <= TutorialSteps.INTRO_SCREENS}); } + onChannelChange() { + this.setState({showPostFocus: ChannelStore.getPostMode() === ChannelStore.POST_MODE_FOCUS}); + } render() { let postsContainer; + let createPost; if (this.state.showTutorialScreens) { postsContainer = ; + createPost = null; + } else if (this.state.showPostFocus) { + postsContainer = ; + createPost = null; } else { postsContainer = ; + createPost = ( +
+ +
+ ); } return ( @@ -62,12 +86,7 @@ export default class CenterPanel extends React.Component { {postsContainer} -
- -
+ {createPost} diff --git a/web/react/components/channel_invite_modal.jsx b/web/react/components/channel_invite_modal.jsx index 6d3203ae5..0518ccb86 100644 --- a/web/react/components/channel_invite_modal.jsx +++ b/web/react/components/channel_invite_modal.jsx @@ -22,6 +22,17 @@ export default class ChannelInviteModal extends React.Component { this.state = this.getStateFromStores(); } + shouldComponentUpdate(nextProps, nextState) { + if (!Utils.areObjectsEqual(this.props, nextProps)) { + return true; + } + + if (!Utils.areObjectsEqual(this.state, nextState)) { + return true; + } + + return false; + } getStateFromStores() { function getId(user) { return user.id; @@ -105,7 +116,7 @@ export default class ChannelInviteModal extends React.Component { } this.setState({inviteError: null, memberIds, nonmembers}); - AsyncClient.getChannelExtraInfo(true); + AsyncClient.getChannelExtraInfo(); }, (err) => { this.setState({inviteError: err.message}); diff --git a/web/react/components/channel_loader.jsx b/web/react/components/channel_loader.jsx index e29c659c7..c8f1196a8 100644 --- a/web/react/components/channel_loader.jsx +++ b/web/react/components/channel_loader.jsx @@ -27,8 +27,8 @@ export default class ChannelLoader extends React.Component { componentDidMount() { /* Initial aysnc loads */ AsyncClient.getPosts(ChannelStore.getCurrentId()); - AsyncClient.getChannels(true, true); - AsyncClient.getChannelExtraInfo(true); + AsyncClient.getChannels(); + AsyncClient.getChannelExtraInfo(); AsyncClient.findTeams(); AsyncClient.getMyTeam(); setTimeout(() => AsyncClient.getStatuses(), 3000); // temporary until statuses are reworked a bit diff --git a/web/react/components/channel_members_modal.jsx b/web/react/components/channel_members_modal.jsx index 08ad95091..f07fc166a 100644 --- a/web/react/components/channel_members_modal.jsx +++ b/web/react/components/channel_members_modal.jsx @@ -25,6 +25,17 @@ export default class ChannelMembersModal extends React.Component { state.showInviteModal = false; this.state = state; } + shouldComponentUpdate(nextProps, nextState) { + if (!Utils.areObjectsEqual(this.props, nextProps)) { + return true; + } + + if (!Utils.areObjectsEqual(this.state, nextState)) { + return true; + } + + return false; + } getStateFromStores() { const users = UserStore.getActiveOnlyProfiles(); const memberList = ChannelStore.getCurrentExtraInfo().members; @@ -74,6 +85,7 @@ export default class ChannelMembersModal extends React.Component { if ($(window).width() > 768) { $(ReactDOM.findDOMNode(this.refs.modalBody)).perfectScrollbar(); } + this.onChange(); } componentDidUpdate(prevProps) { if (this.props.show && !prevProps.show) { @@ -130,7 +142,7 @@ export default class ChannelMembersModal extends React.Component { } this.setState({memberList, nonmemberList}); - AsyncClient.getChannelExtraInfo(true); + AsyncClient.getChannelExtraInfo(); }, (err) => { this.setState({inviteError: err.message}); diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx index 0a2979e21..f7f63fb92 100644 --- a/web/react/components/create_post.jsx +++ b/web/react/components/create_post.jsx @@ -8,6 +8,7 @@ import FilePreview from './file_preview.jsx'; import TutorialTip from './tutorial/tutorial_tip.jsx'; import AppDispatcher from '../dispatcher/app_dispatcher.jsx'; +import * as EventHelpers from '../dispatcher/event_helpers.jsx'; import * as Client from '../utils/client.jsx'; import * as AsyncClient from '../utils/async_client.jsx'; import * as Utils from '../utils/utils.jsx'; @@ -19,6 +20,7 @@ import PreferenceStore from '../stores/preference_store.jsx'; import SocketStore from '../stores/socket_store.jsx'; import Constants from '../utils/constants.jsx'; + const Preferences = Constants.Preferences; const TutorialSteps = Constants.TutorialSteps; const ActionTypes = Constants.ActionTypes; @@ -176,9 +178,7 @@ export default class CreatePost extends React.Component { const channel = ChannelStore.get(this.state.channelId); - PostStore.storePendingPost(post); - PostStore.storeDraft(channel.id, null); - PostStore.jumpPostsViewToBottom(); + EventHelpers.emitUserPostedEvent(post); this.setState({messageText: '', submitting: false, postError: null, previews: [], serverError: null}); Client.createPost(post, channel, @@ -190,10 +190,7 @@ export default class CreatePost extends React.Component { member.last_viewed_at = Date.now(); ChannelStore.setChannelMember(member); - AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_POST, - post: data - }); + EventHelpers.emitPostRecievedEvent(data); }, (err) => { const state = {}; diff --git a/web/react/components/post.jsx b/web/react/components/post.jsx index 5b61c711c..278261e22 100644 --- a/web/react/components/post.jsx +++ b/web/react/components/post.jsx @@ -105,7 +105,7 @@ export default class Post extends React.Component { } else { commentRootId = post.id; } - for (let postId in posts) { + for (const postId in posts) { if (posts[postId].root_id === commentRootId) { commentCount += 1; } @@ -114,53 +114,58 @@ export default class Post extends React.Component { return commentCount; } render() { - var post = this.props.post; - var parentPost = this.props.parentPost; - var posts = this.props.posts; + const post = this.props.post; + const parentPost = this.props.parentPost; + const posts = this.props.posts; if (!post.props) { post.props = {}; } - var type = 'Post'; + let type = 'Post'; if (post.root_id && post.root_id.length > 0) { type = 'Comment'; } const commentCount = this.getCommentCount(this.props); - var rootUser; + let rootUser; if (this.props.sameRoot) { rootUser = 'same--root'; } else { rootUser = 'other--root'; } - var postType = ''; + let postType = ''; if (type !== 'Post') { postType = 'post--comment'; } else if (commentCount > 0) { postType = 'post--root'; } - var currentUserCss = ''; + let currentUserCss = ''; if (UserStore.getCurrentId() === post.user_id && !post.props.from_webhook) { currentUserCss = 'current--user'; } - var userProfile = UserStore.getProfile(post.user_id); + const userProfile = UserStore.getProfile(post.user_id); - var timestamp = UserStore.getCurrentUser().update_at; + let timestamp = UserStore.getCurrentUser().update_at; if (userProfile) { timestamp = userProfile.update_at; } - var sameUserClass = ''; + let sameUserClass = ''; if (this.props.sameUser) { sameUserClass = 'same--user'; } - var profilePic = null; + let shouldHighlightClass = ''; + if (this.props.shouldHighlight) { + shouldHighlightClass = 'post--highlight'; + } + + let profilePic = null; if (!this.props.hideProfilePic) { let src = '/api/v1/users/' + post.user_id + '/image?time=' + timestamp + '&' + utils.getSessionIndex(); if (post.props && post.props.from_webhook && global.window.mm_config.EnablePostIconOverride === 'true') { @@ -182,7 +187,7 @@ export default class Post extends React.Component {
{profilePic}
@@ -218,5 +223,6 @@ Post.propTypes = { sameUser: React.PropTypes.bool, sameRoot: React.PropTypes.bool, hideProfilePic: React.PropTypes.bool, - isLastComment: React.PropTypes.bool + isLastComment: React.PropTypes.bool, + shouldHighlight: React.PropTypes.bool }; diff --git a/web/react/components/post_focus_view.jsx b/web/react/components/post_focus_view.jsx new file mode 100644 index 000000000..5c6ad6c28 --- /dev/null +++ b/web/react/components/post_focus_view.jsx @@ -0,0 +1,110 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import PostsView from './posts_view.jsx'; + +import PostStore from '../stores/post_store.jsx'; +import ChannelStore from '../stores/channel_store.jsx'; +import * as EventHelpers from '../dispatcher/event_helpers.jsx'; + +export default class PostFocusView extends React.Component { + constructor(props) { + super(props); + + this.onChannelChange = this.onChannelChange.bind(this); + this.onPostsChange = this.onPostsChange.bind(this); + this.handlePostsViewScroll = this.handlePostsViewScroll.bind(this); + this.loadMorePostsTop = this.loadMorePostsTop.bind(this); + this.loadMorePostsBottom = this.loadMorePostsBottom.bind(this); + + const focusedPostId = PostStore.getFocusedPostId(); + + this.state = { + scrollType: PostsView.SCROLL_TYPE_POST, + scrollPostId: focusedPostId, + postList: PostStore.getVisiblePosts(focusedPostId), + atTop: PostStore.getVisibilityAtTop(focusedPostId), + atBottom: PostStore.getVisibilityAtBottom(focusedPostId) + }; + } + + componentDidMount() { + ChannelStore.addChangeListener(this.onChannelChange); + PostStore.addChangeListener(this.onPostsChange); + } + + componentWillUnmount() { + ChannelStore.removeChangeListener(this.onChannelChange); + PostStore.removeChangeListener(this.onPostsChange); + } + + onChannelChange() { + this.setState({ + scrollType: PostsView.SCROLL_TYPE_POST + }); + } + + onPostsChange() { + const focusedPostId = PostStore.getFocusedPostId(); + if (focusedPostId == null) { + return; + } + + this.setState({ + scrollPostId: focusedPostId, + postList: PostStore.getVisiblePosts(focusedPostId), + atTop: PostStore.getVisibilityAtTop(focusedPostId), + atBottom: PostStore.getVisibilityAtBottom(focusedPostId) + }); + } + + handlePostsViewScroll() { + this.setState({scrollType: PostsView.SCROLL_TYPE_FREE}); + } + + loadMorePostsTop() { + EventHelpers.emitLoadMorePostsFocusedTopEvent(); + } + + loadMorePostsBottom() { + EventHelpers.emitLoadMorePostsFocusedBottomEvent(); + } + + getIntroMessage() { + return ( +
+

{'Beginning of Channel'}

+
+ ); + } + + render() { + const postsToHighlight = {}; + postsToHighlight[this.state.scrollPostId] = true; + + return ( +
+ +
+ ); + } +} +PostFocusView.defaultProps = { +}; + +PostFocusView.propTypes = { +}; diff --git a/web/react/components/post_info.jsx b/web/react/components/post_info.jsx index edd63decd..bc6e8470d 100644 --- a/web/react/components/post_info.jsx +++ b/web/react/components/post_info.jsx @@ -3,20 +3,28 @@ import DeletePostModal from './delete_post_modal.jsx'; import UserStore from '../stores/user_store.jsx'; -import * as utils from '../utils/utils.jsx'; +import TeamStore from '../stores/team_store.jsx'; +import * as Utils from '../utils/utils.jsx'; import TimeSince from './time_since.jsx'; import Constants from '../utils/constants.jsx'; +const OverlayTrigger = ReactBootstrap.OverlayTrigger; +const Popover = ReactBootstrap.Popover; + export default class PostInfo extends React.Component { constructor(props) { super(props); - this.state = {}; + this.state = { + copiedLink: false + }; + + this.handlePermalinkCopy = this.handlePermalinkCopy.bind(this); } createDropdown() { var post = this.props.post; var isOwner = UserStore.getCurrentId() === post.user_id; - var isAdmin = utils.isAdmin(UserStore.getCurrentUser().roles); + var isAdmin = Utils.isAdmin(UserStore.getCurrentUser().roles); if (post.state === Constants.POST_FAILED || post.state === Constants.POST_LOADING || post.state === Constants.POST_DELETED) { return ''; @@ -113,6 +121,21 @@ export default class PostInfo extends React.Component {
); } + handlePermalinkCopy() { + const textBox = $(ReactDOM.findDOMNode(this.refs.permalinkbox)); + textBox.select(); + + try { + const successful = document.execCommand('copy'); + if (successful) { + this.setState({copiedLink: true}); + } else { + this.setState({copiedLink: false}); + } + } catch (err) { + this.setState({copiedLink: false}); + } + } render() { var post = this.props.post; var comments = ''; @@ -143,6 +166,37 @@ export default class PostInfo extends React.Component { var dropdown = this.createDropdown(); + const permalink = TeamStore.getCurrentTeamUrl() + '/pl/' + post.id; + const copyButtonText = this.state.copiedLink ? (
{'Copy '}
) : 'Copy'; + const permalinkOverlay = ( + +
+ + +
+
+ ); + return (
  • @@ -152,6 +206,15 @@ export default class PostInfo extends React.Component {
  • {comments} + + + +
    {dropdown}
    diff --git a/web/react/components/posts_view.jsx b/web/react/components/posts_view.jsx index 5b36ecbc5..5e374b877 100644 --- a/web/react/components/posts_view.jsx +++ b/web/react/components/posts_view.jsx @@ -2,6 +2,7 @@ // See License.txt for license information. import UserStore from '../stores/user_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'; @@ -13,6 +14,7 @@ export default class PostsView extends React.Component { 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); @@ -27,12 +29,15 @@ 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; + } isAtBottom() { return ((this.refs.postlist.scrollHeight - this.refs.postlist.scrollTop) === this.refs.postlist.clientHeight); } @@ -47,15 +52,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,12 +75,7 @@ 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]]; @@ -113,6 +120,8 @@ export default class PostsView extends React.Component { const keyPrefix = post.id ? post.id : i; + const shouldHighlight = this.props.postsToHighlight && this.props.postsToHighlight.hasOwnProperty(post.id); + const postCtl = ( EventHelpers.emitPostFocusEvent(post.id)} //eslint-disable-line no-loop-func /> ); @@ -185,9 +196,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); @@ -195,7 +209,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; @@ -211,7 +225,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); + } }); } } @@ -219,14 +236,18 @@ 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(); + } } shouldComponentUpdate(nextProps) { if (this.props.isActive !== nextProps.isActive) { @@ -235,15 +256,12 @@ export default class PostsView extends React.Component { 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) { - return true; - } if (this.props.messageSeparatorTime !== nextProps.messageSeparatorTime) { return true; } @@ -256,7 +274,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) { @@ -264,10 +283,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 = ( ); } else { - moreMessages = this.props.introText; + moreMessagesTop = this.props.introText; + } + + // Give option to load more posts at bottom if nessisary + if (this.props.showMoreMessagesBottom) { + moreMessagesBottom = ( + + {'Load more messages'} + + ); + } else { + moreMessagesBottom = null; } // Create post elements @@ -299,8 +334,9 @@ export default class PostsView extends React.Component { ref='postlistcontent' className='post-list__content' > - {moreMessages} + {moreMessagesTop} {postElements} + {moreMessagesBottom}
@@ -313,11 +349,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 }; diff --git a/web/react/components/posts_view_container.jsx b/web/react/components/posts_view_container.jsx index c71ef401e..367d3687e 100644 --- a/web/react/components/posts_view_container.jsx +++ b/web/react/components/posts_view_container.jsx @@ -9,12 +9,9 @@ import ChannelStore from '../stores/channel_store.jsx'; import PostStore from '../stores/post_store.jsx'; import * as Utils from '../utils/utils.jsx'; -import * as Client from '../utils/client.jsx'; -import AppDispatcher from '../dispatcher/app_dispatcher.jsx'; -import * as AsyncClient from '../utils/async_client.jsx'; +import * as EventHelpers from '../dispatcher/event_helpers.jsx'; import Constants from '../utils/constants.jsx'; -const ActionTypes = Constants.ActionTypes; import {createChannelIntroMessage} from '../utils/channel_intro_mssages.jsx'; @@ -27,27 +24,26 @@ export default class PostsViewContainer extends React.Component { this.onPostsChange = this.onPostsChange.bind(this); this.handlePostsViewScroll = this.handlePostsViewScroll.bind(this); this.loadMorePostsTop = this.loadMorePostsTop.bind(this); - this.postsLoaded = this.postsLoaded.bind(this); - this.postsLoadedFailure = this.postsLoadedFailure.bind(this); this.handlePostsViewJumpRequest = this.handlePostsViewJumpRequest.bind(this); const currentChannelId = ChannelStore.getCurrentId(); const state = { scrollType: PostsView.SCROLL_TYPE_BOTTOM, - scrollPost: null, - numPostsToDisplay: Constants.POST_CHUNK_SIZE + scrollPost: null }; if (currentChannelId) { Object.assign(state, { currentChannelIndex: 0, channels: [currentChannelId], - postLists: [this.getChannelPosts(currentChannelId)] + postLists: [this.getChannelPosts(currentChannelId)], + atTop: [PostStore.getVisibilityAtTop(currentChannelId)] }); } else { Object.assign(state, { currentChannelIndex: null, channels: [], - postLists: [] + postLists: [], + atTop: [] }); } @@ -78,24 +74,21 @@ export default class PostsViewContainer extends React.Component { }); break; case Constants.PostsViewJumpTypes.SIDEBAR_OPEN: - this.setState({scrollType: PostsView.SIDEBAR_OPEN}); + this.setState({scrollType: PostsView.SCROLL_TYPE_SIDEBAR_OPEN}); break; } } onChannelChange() { const postLists = this.state.postLists.slice(); + const atTop = this.state.atTop.slice(); const channels = this.state.channels.slice(); const channelId = ChannelStore.getCurrentId(); // Has the channel really changed? if (channelId === channels[this.state.currentChannelIndex]) { - // Dirty hack - this.forceUpdate(); return; } - PostStore.clearUnseenDeletedPosts(channelId); - let lastViewed = Number.MAX_VALUE; const member = ChannelStore.getMember(channelId); if (member != null) { @@ -107,115 +100,45 @@ export default class PostsViewContainer extends React.Component { newIndex = channels.length; channels.push(channelId); postLists[newIndex] = this.getChannelPosts(channelId); + atTop[newIndex] = PostStore.getVisibilityAtTop(channelId); } + this.setState({ currentChannelIndex: newIndex, currentLastViewed: lastViewed, scrollType: PostsView.SCROLL_TYPE_NEW_MESSAGE, channels, - postLists}); + postLists, + atTop}); } onChannelLeave(id) { const postLists = this.state.postLists.slice(); const channels = this.state.channels.slice(); + const atTop = this.state.atTop.slice(); const index = channels.indexOf(id); if (index !== -1) { postLists.splice(index, 1); channels.splice(index, 1); + atTop.splice(index, 1); } - this.setState({channels, postLists}); + this.setState({channels, postLists, atTop}); } onPostsChange() { const channels = this.state.channels; const postLists = this.state.postLists.slice(); - const newPostsView = this.getChannelPosts(channels[this.state.currentChannelIndex]); + const atTop = this.state.atTop.slice(); + const currentChannelId = channels[this.state.currentChannelIndex]; + const newPostsView = this.getChannelPosts(currentChannelId); postLists[this.state.currentChannelIndex] = newPostsView; - this.setState({postLists}); + atTop[this.state.currentChannelIndex] = PostStore.getVisibilityAtTop(currentChannelId); + this.setState({postLists, atTop}); } getChannelPosts(id) { - const postList = PostStore.getPosts(id); - - if (postList != null) { - const deletedPosts = PostStore.getUnseenDeletedPosts(id); - - if (deletedPosts && Object.keys(deletedPosts).length > 0) { - for (const pid in deletedPosts) { - if (deletedPosts.hasOwnProperty(pid)) { - postList.posts[pid] = deletedPosts[pid]; - postList.order.unshift(pid); - } - } - - postList.order.sort((a, b) => { - if (postList.posts[a].create_at > postList.posts[b].create_at) { - return -1; - } - if (postList.posts[a].create_at < postList.posts[b].create_at) { - return 1; - } - return 0; - }); - } - - const pendingPostList = PostStore.getPendingPosts(id); - - if (pendingPostList) { - postList.order = pendingPostList.order.concat(postList.order); - for (const ppid in pendingPostList.posts) { - if (pendingPostList.posts.hasOwnProperty(ppid)) { - postList.posts[ppid] = pendingPostList.posts[ppid]; - } - } - } - } - - return postList; + return PostStore.getVisiblePosts(id); } loadMorePostsTop() { - const postLists = this.state.postLists; - const channels = this.state.channels; - const currentChannelId = channels[this.state.currentChannelIndex]; - const currentPostList = postLists[this.state.currentChannelIndex]; - - this.setState({numPostsToDisplay: this.state.numPostsToDisplay + Constants.POST_CHUNK_SIZE}); - - Client.getPostsPage( - currentChannelId, - currentPostList.order.length, - Constants.POST_CHUNK_SIZE, - this.postsLoaded, - this.postsLoadedFailure - ); - } - postsLoaded(data) { - if (!data) { - return; - } - - if (data.order.length === 0) { - return; - } - - const postLists = this.state.postLists; - const currentPostList = postLists[this.state.currentChannelIndex]; - const channels = this.state.channels; - const currentChannelId = channels[this.state.currentChannelIndex]; - - var newPostList = {}; - newPostList.posts = Object.assign(currentPostList.posts, data.posts); - newPostList.order = currentPostList.order.concat(data.order); - - AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_POSTS, - id: currentChannelId, - post_list: newPostList - }); - - Client.getProfiles(); - } - postsLoadedFailure(err) { - AsyncClient.dispatchError(err, 'getPosts'); + EventHelpers.emitLoadMorePostsEvent(); } handlePostsViewScroll(atBottom) { if (atBottom) { @@ -246,15 +169,17 @@ export default class PostsViewContainer extends React.Component { isActive={isActive} postList={postLists[i]} scrollType={this.state.scrollType} - scrollPost={this.state.scrollPost} + scrollPostId={this.state.scrollPost} postViewScrolled={this.handlePostsViewScroll} loadMorePostsTopClicked={this.loadMorePostsTop} - numPostsToDisplay={this.state.numPostsToDisplay} + loadMorePostsBottomClicked={() => {}} + showMoreMessagesTop={!this.state.atTop[this.state.currentChannelIndex]} + showMoreMessagesBottom={false} introText={channel ? createChannelIntroMessage(channel, () => this.setState({showInviteModal: true})) : null} messageSeparatorTime={this.state.currentLastViewed} /> ); - if ((!postLists[i] || !channel) && isActive) { + if (!postLists[i] && isActive) { postListCtls.push( 768) { - $('.nav-pills__container').perfectScrollbar(); - $('.nav-pills__container').perfectScrollbar('update'); - } } onChange() { this.setState(this.getStateFromStores()); -- cgit v1.2.3-1-g7c22