From ab67f6e257f6e8f08145a02a7b93550f99641be4 Mon Sep 17 00:00:00 2001 From: Joram Wilander Date: Sun, 18 Jun 2017 14:42:32 -0400 Subject: PLT-6215 Major post list refactor (#6501) * Major post list refactor * Fix post and thread deletion * Fix preferences not selecting correctly * Fix military time displaying * Fix UP key for editing posts * Fix ESLint error * Various fixes and updates per feedback * Fix for permalink view * Revert to old scrolling method and various fixes * Add floating timestamp, new message indicator, scroll arrows * Update post loading for focus mode and add visibility limit * Fix pinning posts and a react warning * Add loading UI updates from Asaad * Fix refreshing loop * Temporarily bump post visibility limit * Update infinite scrolling * Remove infinite scrolling --- webapp/components/channel_view.jsx | 6 +- webapp/components/common/post_flag_icon.jsx | 89 --- webapp/components/create_comment.jsx | 3 +- webapp/components/create_post.jsx | 35 +- webapp/components/dot_menu/dot_menu.jsx | 33 +- webapp/components/dot_menu/dot_menu_flag.jsx | 21 +- webapp/components/dot_menu/dot_menu_item.jsx | 20 +- webapp/components/dot_menu/index.js | 26 + webapp/components/edit_post_modal.jsx | 12 +- webapp/components/file_attachment.jsx | 8 +- webapp/components/file_attachment_list.jsx | 73 --- .../file_attachment_list/file_attachment_list.jsx | 104 ++++ webapp/components/file_attachment_list/index.js | 39 ++ .../components/file_attachment_list_container.jsx | 92 --- webapp/components/file_preview.jsx | 4 +- webapp/components/needs_team/needs_team.jsx | 11 +- webapp/components/permalink_view.jsx | 24 +- .../commented_on_files_message.jsx | 51 ++ .../post_view/commented_on_files_message/index.js | 36 ++ .../commented_on_files_message_container.jsx | 90 --- .../post_view/components/date_separator.jsx | 26 - .../post_view/components/floating_timestamp.jsx | 61 -- .../post_view/components/new_message_indicator.jsx | 66 -- .../post_view/components/pending_post_options.jsx | 89 --- webapp/components/post_view/components/post.jsx | 332 ---------- .../post_view/components/post_attachment.jsx | 323 ---------- .../post_view/components/post_attachment_list.jsx | 30 - .../components/post_attachment_opengraph.jsx | 287 --------- .../components/post_view/components/post_body.jsx | 223 ------- .../components/post_body_additional_content.jsx | 240 ------- .../post_view/components/post_header.jsx | 125 ---- .../components/post_view/components/post_image.jsx | 93 --- .../components/post_view/components/post_info.jsx | 214 ------- .../components/post_view/components/post_list.jsx | 690 --------------------- .../components/post_message_container.jsx | 106 ---- .../post_view/components/post_message_view.jsx | 137 ---- .../components/post_view/components/post_time.jsx | 84 --- .../components/post_view/components/reaction.jsx | 205 ------ .../post_view/components/reaction_container.jsx | 90 --- .../components/reaction_list_container.jsx | 94 --- .../post_view/components/reaction_list_view.jsx | 56 -- .../components/scroll_to_bottom_arrows.jsx | 37 -- .../components/system_message_helpers.jsx | 232 ------- webapp/components/post_view/date_separator.jsx | 32 + .../failed_post_options/failed_post_options.jsx | 94 +++ .../post_view/failed_post_options/index.js | 24 + webapp/components/post_view/floating_timestamp.jsx | 53 ++ webapp/components/post_view/index.js | 44 +- .../components/post_view/new_message_indicator.jsx | 69 +++ webapp/components/post_view/post/index.js | 33 + webapp/components/post_view/post/post.jsx | 306 +++++++++ webapp/components/post_view/post_attachment.jsx | 311 ++++++++++ .../components/post_view/post_attachment_list.jsx | 35 ++ .../post_view/post_attachment_opengraph/index.js | 26 + .../post_attachment_opengraph.jsx | 259 ++++++++ webapp/components/post_view/post_body/index.js | 30 + .../components/post_view/post_body/post_body.jsx | 195 ++++++ .../post_view/post_body_additional_content.jsx | 229 +++++++ webapp/components/post_view/post_flag_icon.jsx | 88 +++ .../post_view/post_focus_view_controller.jsx | 212 ------- webapp/components/post_view/post_header/index.js | 18 + .../post_view/post_header/post_header.jsx | 158 +++++ webapp/components/post_view/post_image.jsx | 100 +++ webapp/components/post_view/post_info/index.js | 31 + .../components/post_view/post_info/post_info.jsx | 243 ++++++++ webapp/components/post_view/post_list.jsx | 523 ++++++++++++++++ .../post_view/post_message_view/index.js | 41 ++ .../post_message_view/post_message_view.jsx | 139 +++++ .../post_message_view/system_message_helpers.jsx | 232 +++++++ webapp/components/post_view/post_time.jsx | 91 +++ webapp/components/post_view/post_view_cache.jsx | 98 --- .../components/post_view/post_view_controller.jsx | 404 ------------ webapp/components/post_view/reaction/index.js | 47 ++ webapp/components/post_view/reaction/reaction.jsx | 239 +++++++ webapp/components/post_view/reaction_list/index.js | 33 + .../post_view/reaction_list/reaction_list.jsx | 80 +++ .../post_view/scroll_to_bottom_arrows.jsx | 37 ++ webapp/components/rhs_comment.jsx | 71 +-- webapp/components/rhs_root_post.jsx | 63 +- webapp/components/rhs_thread.jsx | 509 --------------- webapp/components/rhs_thread/index.js | 27 + webapp/components/rhs_thread/rhs_thread.jsx | 470 ++++++++++++++ webapp/components/search_results_item.jsx | 12 +- webapp/components/sidebar_right.jsx | 271 -------- webapp/components/sidebar_right/index.js | 17 + webapp/components/sidebar_right/sidebar_right.jsx | 257 ++++++++ webapp/components/view_image.jsx | 11 +- webapp/components/youtube_video.jsx | 251 -------- webapp/components/youtube_video/index.js | 16 + webapp/components/youtube_video/youtube_video.jsx | 243 ++++++++ 90 files changed, 5281 insertions(+), 6108 deletions(-) delete mode 100644 webapp/components/common/post_flag_icon.jsx create mode 100644 webapp/components/dot_menu/index.js delete mode 100644 webapp/components/file_attachment_list.jsx create mode 100644 webapp/components/file_attachment_list/file_attachment_list.jsx create mode 100644 webapp/components/file_attachment_list/index.js delete mode 100644 webapp/components/file_attachment_list_container.jsx create mode 100644 webapp/components/post_view/commented_on_files_message/commented_on_files_message.jsx create mode 100644 webapp/components/post_view/commented_on_files_message/index.js delete mode 100644 webapp/components/post_view/components/commented_on_files_message_container.jsx delete mode 100644 webapp/components/post_view/components/date_separator.jsx delete mode 100644 webapp/components/post_view/components/floating_timestamp.jsx delete mode 100644 webapp/components/post_view/components/new_message_indicator.jsx delete mode 100644 webapp/components/post_view/components/pending_post_options.jsx delete mode 100644 webapp/components/post_view/components/post.jsx delete mode 100644 webapp/components/post_view/components/post_attachment.jsx delete mode 100644 webapp/components/post_view/components/post_attachment_list.jsx delete mode 100644 webapp/components/post_view/components/post_attachment_opengraph.jsx delete mode 100644 webapp/components/post_view/components/post_body.jsx delete mode 100644 webapp/components/post_view/components/post_body_additional_content.jsx delete mode 100644 webapp/components/post_view/components/post_header.jsx delete mode 100644 webapp/components/post_view/components/post_image.jsx delete mode 100644 webapp/components/post_view/components/post_info.jsx delete mode 100644 webapp/components/post_view/components/post_list.jsx delete mode 100644 webapp/components/post_view/components/post_message_container.jsx delete mode 100644 webapp/components/post_view/components/post_message_view.jsx delete mode 100644 webapp/components/post_view/components/post_time.jsx delete mode 100644 webapp/components/post_view/components/reaction.jsx delete mode 100644 webapp/components/post_view/components/reaction_container.jsx delete mode 100644 webapp/components/post_view/components/reaction_list_container.jsx delete mode 100644 webapp/components/post_view/components/reaction_list_view.jsx delete mode 100644 webapp/components/post_view/components/scroll_to_bottom_arrows.jsx delete mode 100644 webapp/components/post_view/components/system_message_helpers.jsx create mode 100644 webapp/components/post_view/date_separator.jsx create mode 100644 webapp/components/post_view/failed_post_options/failed_post_options.jsx create mode 100644 webapp/components/post_view/failed_post_options/index.js create mode 100644 webapp/components/post_view/floating_timestamp.jsx create mode 100644 webapp/components/post_view/new_message_indicator.jsx create mode 100644 webapp/components/post_view/post/index.js create mode 100644 webapp/components/post_view/post/post.jsx create mode 100644 webapp/components/post_view/post_attachment.jsx create mode 100644 webapp/components/post_view/post_attachment_list.jsx create mode 100644 webapp/components/post_view/post_attachment_opengraph/index.js create mode 100644 webapp/components/post_view/post_attachment_opengraph/post_attachment_opengraph.jsx create mode 100644 webapp/components/post_view/post_body/index.js create mode 100644 webapp/components/post_view/post_body/post_body.jsx create mode 100644 webapp/components/post_view/post_body_additional_content.jsx create mode 100644 webapp/components/post_view/post_flag_icon.jsx delete mode 100644 webapp/components/post_view/post_focus_view_controller.jsx create mode 100644 webapp/components/post_view/post_header/index.js create mode 100644 webapp/components/post_view/post_header/post_header.jsx create mode 100644 webapp/components/post_view/post_image.jsx create mode 100644 webapp/components/post_view/post_info/index.js create mode 100644 webapp/components/post_view/post_info/post_info.jsx create mode 100644 webapp/components/post_view/post_list.jsx create mode 100644 webapp/components/post_view/post_message_view/index.js create mode 100644 webapp/components/post_view/post_message_view/post_message_view.jsx create mode 100644 webapp/components/post_view/post_message_view/system_message_helpers.jsx create mode 100644 webapp/components/post_view/post_time.jsx delete mode 100644 webapp/components/post_view/post_view_cache.jsx delete mode 100644 webapp/components/post_view/post_view_controller.jsx create mode 100644 webapp/components/post_view/reaction/index.js create mode 100644 webapp/components/post_view/reaction/reaction.jsx create mode 100644 webapp/components/post_view/reaction_list/index.js create mode 100644 webapp/components/post_view/reaction_list/reaction_list.jsx create mode 100644 webapp/components/post_view/scroll_to_bottom_arrows.jsx delete mode 100644 webapp/components/rhs_thread.jsx create mode 100644 webapp/components/rhs_thread/index.js create mode 100644 webapp/components/rhs_thread/rhs_thread.jsx delete mode 100644 webapp/components/sidebar_right.jsx create mode 100644 webapp/components/sidebar_right/index.js create mode 100644 webapp/components/sidebar_right/sidebar_right.jsx delete mode 100644 webapp/components/youtube_video.jsx create mode 100644 webapp/components/youtube_video/index.js create mode 100644 webapp/components/youtube_video/youtube_video.jsx (limited to 'webapp/components') diff --git a/webapp/components/channel_view.jsx b/webapp/components/channel_view.jsx index 97275d37d..3f6edbd2e 100644 --- a/webapp/components/channel_view.jsx +++ b/webapp/components/channel_view.jsx @@ -9,7 +9,7 @@ import * as UserAgent from 'utils/user_agent.jsx'; import ChannelHeader from 'components/channel_header.jsx'; import FileUploadOverlay from 'components/file_upload_overlay.jsx'; import CreatePost from 'components/create_post.jsx'; -import PostViewCache from 'components/post_view'; +import PostView from 'components/post_view'; import ChannelStore from 'stores/channel_store.jsx'; @@ -77,7 +77,9 @@ export default class ChannelView extends React.Component { - +
- - - ); -} - -function flagIcon() { - return ( - - ); -} - -export default function PostFlagIcon(props) { - function onFlagPost(e) { - e.preventDefault(); - flagPost(props.postId); - } - - function onUnflagPost(e) { - e.preventDefault(); - unflagPost(props.postId); - } - - const flagFunc = props.isFlagged ? onUnflagPost : onFlagPost; - const flagVisible = props.isFlagged ? 'visible' : ''; - - let flagIconId = null; - if (props.idCount > -1) { - flagIconId = Utils.createSafeId(props.idPrefix + props.idCount); - } - - if (!props.isEphemeral) { - return ( - - - {flagIcon()} - - - ); - } - - return null; -} - -PostFlagIcon.propTypes = { - idPrefix: PropTypes.string.isRequired, - idCount: PropTypes.number, - postId: PropTypes.string.isRequired, - isFlagged: PropTypes.bool.isRequired, - isEphemeral: PropTypes.bool -}; - -PostFlagIcon.defaultProps = { - idCount: -1, - postId: '', - isFlagged: false, - isEphemeral: false -}; diff --git a/webapp/components/create_comment.jsx b/webapp/components/create_comment.jsx index 175eb03be..56e9eb88f 100644 --- a/webapp/components/create_comment.jsx +++ b/webapp/components/create_comment.jsx @@ -229,7 +229,6 @@ export default class CreateComment extends React.Component { post.channel_id = this.props.channelId; post.root_id = this.props.rootId; post.parent_id = this.props.rootId; - post.file_ids = this.state.fileInfos.map((info) => info.id); post.pending_post_id = `${userId}:${time}`; post.user_id = userId; post.create_at = time; @@ -244,7 +243,7 @@ export default class CreateComment extends React.Component { }); } - PostActions.queuePost(post, false, null, + PostActions.createPost(post, this.state.fileInfos, null, (err) => { if (err.id === 'api.post.create_post.root_id.app_error') { this.showPostDeletedModal(); diff --git a/webapp/components/create_post.jsx b/webapp/components/create_post.jsx index d2f64a266..124728c3d 100644 --- a/webapp/components/create_post.jsx +++ b/webapp/components/create_post.jsx @@ -77,7 +77,7 @@ export default class CreatePost extends React.Component { PostStore.clearDraftUploads(); const channelId = ChannelStore.getCurrentId(); - const draft = PostStore.getPostDraft(channelId); + const draft = PostStore.getDraft(channelId); const stats = ChannelStore.getCurrentStats(); const members = stats.member_count - 1; @@ -141,7 +141,7 @@ export default class CreatePost extends React.Component { const isReaction = REACTION_PATTERN.exec(post.message); if (post.message.indexOf('/') === 0) { - PostActions.storePostDraft(this.state.channelId, null); + PostStore.storeDraft(this.state.channelId, null); this.setState({message: '', postError: null, fileInfos: [], enableSendButton: false}); const args = {}; @@ -228,7 +228,6 @@ export default class CreatePost extends React.Component { sendMessage(post) { post.channel_id = this.state.channelId; - post.file_ids = this.state.fileInfos.map((info) => info.id); const time = Utils.getTimestamp(); const userId = UserStore.getCurrentId(); @@ -247,7 +246,7 @@ export default class CreatePost extends React.Component { }); } - PostActions.queuePost(post, false, null, + PostActions.createPost(post, this.state.fileInfos, null, (err) => { if (err.id === 'api.post.create_post.root_id.app_error') { // this should never actually happen since you can't reply from this textbox @@ -267,7 +266,7 @@ export default class CreatePost extends React.Component { const action = isReaction[1]; const emojiName = isReaction[2]; - const postId = PostStore.getLatestNonEphemeralPost(this.state.channelId).id; + const postId = PostStore.getLatestPostId(this.state.channelId); if (postId && action === '+') { PostActions.addReaction(this.state.channelId, postId, emojiName); @@ -275,7 +274,7 @@ export default class CreatePost extends React.Component { PostActions.removeReaction(this.state.channelId, postId, emojiName); } - PostActions.storePostDraft(this.state.channelId, null); + PostStore.storeDraft(this.state.channelId, null); } focusTextbox(keepFocus = false) { @@ -305,9 +304,9 @@ export default class CreatePost extends React.Component { enableSendButton }); - const draft = PostStore.getPostDraft(this.state.channelId); + const draft = PostStore.getDraft(this.state.channelId); draft.message = message; - PostActions.storePostDraft(this.state.channelId, draft); + PostStore.storeDraft(this.state.channelId, draft); } handleFileUploadChange() { @@ -315,10 +314,10 @@ export default class CreatePost extends React.Component { } handleUploadStart(clientIds, channelId) { - const draft = PostStore.getPostDraft(channelId); + const draft = PostStore.getDraft(channelId); draft.uploadsInProgress = draft.uploadsInProgress.concat(clientIds); - PostActions.storePostDraft(channelId, draft); + PostStore.storeDraft(channelId, draft); this.setState({uploadsInProgress: draft.uploadsInProgress}); @@ -328,7 +327,7 @@ export default class CreatePost extends React.Component { } handleFileUploadComplete(fileInfos, clientIds, channelId) { - const draft = PostStore.getPostDraft(channelId); + const draft = PostStore.getDraft(channelId); // remove each finished file from uploads for (let i = 0; i < clientIds.length; i++) { @@ -340,7 +339,7 @@ export default class CreatePost extends React.Component { } draft.fileInfos = draft.fileInfos.concat(fileInfos); - PostActions.storePostDraft(channelId, draft); + PostStore.storeDraft(channelId, draft); if (channelId === this.state.channelId) { this.setState({ @@ -359,14 +358,14 @@ export default class CreatePost extends React.Component { } if (clientId !== -1) { - const draft = PostStore.getPostDraft(channelId); + const draft = PostStore.getDraft(channelId); const index = draft.uploadsInProgress.indexOf(clientId); if (index !== -1) { draft.uploadsInProgress.splice(index, 1); } - PostActions.storePostDraft(channelId, draft); + PostStore.storeDraft(channelId, draft); if (channelId === this.state.channelId) { this.setState({uploadsInProgress: draft.uploadsInProgress}); @@ -396,10 +395,10 @@ export default class CreatePost extends React.Component { fileInfos.splice(index, 1); } - const draft = PostStore.getPostDraft(this.state.channelId); + const draft = PostStore.getDraft(this.state.channelId); draft.fileInfos = fileInfos; draft.uploadsInProgress = uploadsInProgress; - PostActions.storePostDraft(this.state.channelId, draft); + PostStore.storeDraft(this.state.channelId, draft); const enableSendButton = this.handleEnableSendButton(this.state.message, fileInfos); this.setState({fileInfos, uploadsInProgress, enableSendButton}); @@ -462,7 +461,7 @@ export default class CreatePost extends React.Component { onChange() { const channelId = ChannelStore.getCurrentId(); if (this.state.channelId !== channelId) { - const draft = PostStore.getPostDraft(channelId); + const draft = PostStore.getDraft(channelId); this.setState({channelId, message: draft.message, submitting: false, serverError: null, postError: null, fileInfos: draft.fileInfos, uploadsInProgress: draft.uploadsInProgress}); } @@ -483,7 +482,7 @@ export default class CreatePost extends React.Component { return this.state.fileInfos.length + this.state.uploadsInProgress.length; } - const draft = PostStore.getPostDraft(channelId); + const draft = PostStore.getDraft(channelId); return draft.fileInfos.length + draft.uploadsInProgress.length; } diff --git a/webapp/components/dot_menu/dot_menu.jsx b/webapp/components/dot_menu/dot_menu.jsx index b5f9fde45..eb6a6c005 100644 --- a/webapp/components/dot_menu/dot_menu.jsx +++ b/webapp/components/dot_menu/dot_menu.jsx @@ -22,7 +22,30 @@ export default class DotMenu extends Component { commentCount: PropTypes.number, isFlagged: PropTypes.bool, handleCommentClick: PropTypes.func, - handleDropdownOpened: PropTypes.func + handleDropdownOpened: PropTypes.func, + + actions: PropTypes.shape({ + + /* + * Function flag the post + */ + flagPost: PropTypes.func.isRequired, + + /* + * Function to unflag the post + */ + unflagPost: PropTypes.func.isRequired, + + /* + * Function to pin the post + */ + pinPost: PropTypes.func.isRequired, + + /* + * Function to unpin the post + */ + unpinPost: PropTypes.func.isRequired + }).isRequired } static defaultProps = { @@ -90,6 +113,10 @@ export default class DotMenu extends Component { idCount={this.props.idCount} postId={this.props.post.id} isFlagged={this.props.isFlagged} + actions={{ + flagPost: this.props.actions.flagPost, + unflagPost: this.props.actions.unflagPost + }} /> ); } @@ -121,6 +148,10 @@ export default class DotMenu extends Component { idPrefix={idPrefix + 'Pin'} idCount={this.props.idCount} post={this.props.post} + actions={{ + pinPost: this.props.actions.pinPost, + unpinPost: this.props.actions.unpinPost + }} /> ); } diff --git a/webapp/components/dot_menu/dot_menu_flag.jsx b/webapp/components/dot_menu/dot_menu_flag.jsx index 105363211..11546ee79 100644 --- a/webapp/components/dot_menu/dot_menu_flag.jsx +++ b/webapp/components/dot_menu/dot_menu_flag.jsx @@ -5,7 +5,6 @@ import React from 'react'; import {FormattedMessage} from 'react-intl'; import PropTypes from 'prop-types'; -import {flagPost, unflagPost} from 'actions/post_actions.jsx'; import * as Utils from 'utils/utils.jsx'; import Constants from 'utils/constants.jsx'; @@ -21,12 +20,12 @@ function formatMessage(isFlagged) { export default function DotMenuFlag(props) { function onFlagPost(e) { e.preventDefault(); - flagPost(props.postId); + props.actions.flagPost(props.postId); } function onUnflagPost(e) { e.preventDefault(); - unflagPost(props.postId); + props.actions.unflagPost(props.postId); } const flagFunc = props.isFlagged ? onUnflagPost : onFlagPost; @@ -60,7 +59,21 @@ DotMenuFlag.propTypes = { idCount: PropTypes.number, idPrefix: PropTypes.string.isRequired, postId: PropTypes.string.isRequired, - isFlagged: PropTypes.bool.isRequired + isFlagged: PropTypes.bool.isRequired, + + actions: PropTypes.shape({ + + /* + * Function flag the post + */ + flagPost: PropTypes.func.isRequired, + + /* + * Function to unflag the post + */ + unflagPost: PropTypes.func.isRequired + + }).isRequired }; DotMenuFlag.defaultProps = { diff --git a/webapp/components/dot_menu/dot_menu_item.jsx b/webapp/components/dot_menu/dot_menu_item.jsx index ceda0a1a4..6411beafb 100644 --- a/webapp/components/dot_menu/dot_menu_item.jsx +++ b/webapp/components/dot_menu/dot_menu_item.jsx @@ -5,7 +5,6 @@ import React from 'react'; import {FormattedMessage} from 'react-intl'; import PropTypes from 'prop-types'; -import {unpinPost, pinPost} from 'actions/post_actions.jsx'; import {showGetPostLinkModal, showDeletePostModal} from 'actions/global_actions.jsx'; import * as Utils from 'utils/utils.jsx'; import Constants from 'utils/constants.jsx'; @@ -18,12 +17,12 @@ export default function DotMenuItem(props) { function handleUnpinPost(e) { e.preventDefault(); - unpinPost(props.post.channel_id, props.post.id); + props.actions.unpinPost(props.post.id); } function handlePinPost(e) { e.preventDefault(); - pinPost(props.post.channel_id, props.post.id); + props.actions.pinPost(props.post.id); } function handleDeletePost(e) { @@ -98,7 +97,20 @@ DotMenuItem.propTypes = { post: PropTypes.object, handleOnClick: PropTypes.func, type: PropTypes.string, - commentCount: PropTypes.number + commentCount: PropTypes.number, + + actions: PropTypes.shape({ + + /* + * Function to pin the post + */ + pinPost: PropTypes.func, + + /* + * Function to unpin the post + */ + unpinPost: PropTypes.func + }) }; DotMenuItem.defaultProps = { diff --git a/webapp/components/dot_menu/index.js b/webapp/components/dot_menu/index.js new file mode 100644 index 000000000..eaa1e8d2c --- /dev/null +++ b/webapp/components/dot_menu/index.js @@ -0,0 +1,26 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import {connect} from 'react-redux'; +import {bindActionCreators} from 'redux'; +import {flagPost, unflagPost, pinPost, unpinPost} from 'mattermost-redux/actions/posts'; + +import DotMenu from './dot_menu.jsx'; + +function mapStateToProps(state, ownProps) { + return ownProps; +} + +function mapDispatchToProps(dispatch) { + return { + actions: bindActionCreators({ + flagPost, + unflagPost, + pinPost, + unpinPost + }, dispatch) + }; +} + +export default connect(mapStateToProps, mapDispatchToProps)(DotMenu); + diff --git a/webapp/components/edit_post_modal.jsx b/webapp/components/edit_post_modal.jsx index 3ec7fedcc..683371d23 100644 --- a/webapp/components/edit_post_modal.jsx +++ b/webapp/components/edit_post_modal.jsx @@ -21,6 +21,11 @@ import React from 'react'; import ReactDOM from 'react-dom'; import {FormattedMessage} from 'react-intl'; +import store from 'stores/redux_store.jsx'; +const getState = store.getState; + +import * as Selectors from 'mattermost-redux/selectors/entities/posts'; + export default class EditPostModal extends React.Component { constructor(props) { super(props); @@ -85,7 +90,7 @@ export default class EditPostModal extends React.Component { Reflect.deleteProperty(tempState, 'editText'); BrowserStore.setItem('edit_state_transfer', tempState); $('#edit_post').modal('hide'); - GlobalActions.showDeletePostModal(PostStore.getPost(this.state.channel_id, this.state.post_id), this.state.comments); + GlobalActions.showDeletePostModal(Selectors.getPost(getState(), this.state.post_id), this.state.comments); return; } @@ -93,8 +98,7 @@ export default class EditPostModal extends React.Component { updatedPost, () => { window.scrollTo(0, 0); - }, - Boolean(PostStore.getFocusedPostId()) // If there is focused post we need to update that post's store too. + } ); $('#edit_post').modal('hide'); @@ -120,7 +124,7 @@ export default class EditPostModal extends React.Component { } handleEditPostEvent(options) { - var post = PostStore.getPost(options.channelId, options.postId); + const post = Selectors.getPost(getState(), options.postId); if (global.window.mm_license.IsLicensed === 'true') { if (global.window.mm_config.AllowEditPost === Constants.ALLOW_EDIT_POST_NEVER) { return; diff --git a/webapp/components/file_attachment.jsx b/webapp/components/file_attachment.jsx index 0b8bd1042..f14718e64 100644 --- a/webapp/components/file_attachment.jsx +++ b/webapp/components/file_attachment.jsx @@ -2,7 +2,7 @@ // See License.txt for license information. import Constants from 'utils/constants.jsx'; -import FileStore from 'stores/file_store.jsx'; +import {getFileUrl, getFileThumbnailUrl} from 'mattermost-redux/utils/file_utils'; import * as Utils from 'utils/utils.jsx'; import {Tooltip, OverlayTrigger} from 'react-bootstrap'; @@ -46,7 +46,7 @@ export default class FileAttachment extends React.Component { const fileType = Utils.getFileType(fileInfo.extension); if (fileType === 'image') { - const thumbnailUrl = FileStore.getFileThumbnailUrl(fileInfo.id); + const thumbnailUrl = getFileThumbnailUrl(fileInfo.id); const img = new Image(); img.onload = () => { @@ -64,7 +64,7 @@ export default class FileAttachment extends React.Component { render() { const fileInfo = this.props.fileInfo; const fileName = fileInfo.name; - const fileUrl = FileStore.getFileUrl(fileInfo.id); + const fileUrl = getFileUrl(fileInfo.id); let thumbnail; if (this.state.loaded) { @@ -83,7 +83,7 @@ export default class FileAttachment extends React.Component {
); diff --git a/webapp/components/file_attachment_list.jsx b/webapp/components/file_attachment_list.jsx deleted file mode 100644 index 9beacf94c..000000000 --- a/webapp/components/file_attachment_list.jsx +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import ViewImageModal from './view_image.jsx'; -import FileAttachment from './file_attachment.jsx'; -import Constants from 'utils/constants.jsx'; - -import PropTypes from 'prop-types'; - -import React from 'react'; - -export default class FileAttachmentList extends React.Component { - constructor(props) { - super(props); - - this.handleImageClick = this.handleImageClick.bind(this); - - this.state = {showPreviewModal: false, startImgIndex: 0}; - } - - handleImageClick(indexClicked) { - this.setState({showPreviewModal: true, startImgIndex: indexClicked}); - } - - render() { - const postFiles = []; - if (this.props.fileInfos && this.props.fileInfos.length > 0) { - for (let i = 0; i < Math.min(this.props.fileInfos.length, Constants.MAX_DISPLAY_FILES); i++) { - const fileInfo = this.props.fileInfos[i]; - - postFiles.push( - - ); - } - } else if (this.props.fileCount > 0) { - for (let i = 0; i < Math.min(this.props.fileCount, Constants.MAX_DISPLAY_FILES); i++) { - // Add a placeholder to avoid pop-in once we get the file infos for this post - postFiles.push( -
- ); - } - } - - return ( -
-
- {postFiles} -
- this.setState({showPreviewModal: false})} - startId={this.state.startImgIndex} - fileInfos={this.props.fileInfos} - /> -
- ); - } -} - -FileAttachmentList.propTypes = { - fileCount: PropTypes.number.isRequired, - fileInfos: PropTypes.arrayOf(PropTypes.object), - compactDisplay: PropTypes.bool -}; diff --git a/webapp/components/file_attachment_list/file_attachment_list.jsx b/webapp/components/file_attachment_list/file_attachment_list.jsx new file mode 100644 index 000000000..31b1ac424 --- /dev/null +++ b/webapp/components/file_attachment_list/file_attachment_list.jsx @@ -0,0 +1,104 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import ViewImageModal from 'components/view_image.jsx'; +import FileAttachment from 'components/file_attachment.jsx'; +import Constants from 'utils/constants.jsx'; + +import PropTypes from 'prop-types'; + +import React from 'react'; + +export default class FileAttachmentList extends React.Component { + static propTypes = { + + /* + * The post the files are attached to + */ + post: PropTypes.object.isRequired, + + /* + * The number of files attached to the post + */ + fileCount: PropTypes.number.isRequired, + + /* + * Array of metadata for each file attached to the post + */ + fileInfos: PropTypes.arrayOf(PropTypes.object), + + /* + * Set to render compactly + */ + compactDisplay: PropTypes.bool, + + actions: PropTypes.shape({ + + /* + * Function to get file metadata for a post + */ + getMissingFilesForPost: PropTypes.func.isRequired + }).isRequired + } + + constructor(props) { + super(props); + + this.handleImageClick = this.handleImageClick.bind(this); + + this.state = {showPreviewModal: false, startImgIndex: 0}; + } + + componentDidMount() { + if (this.props.post.file_ids || this.props.post.filenames) { + this.props.actions.getMissingFilesForPost(this.props.post.id); + } + } + + handleImageClick(indexClicked) { + this.setState({showPreviewModal: true, startImgIndex: indexClicked}); + } + + render() { + const postFiles = []; + if (this.props.fileInfos && this.props.fileInfos.length > 0) { + for (let i = 0; i < Math.min(this.props.fileInfos.length, Constants.MAX_DISPLAY_FILES); i++) { + const fileInfo = this.props.fileInfos[i]; + + postFiles.push( + + ); + } + } else if (this.props.fileCount > 0) { + for (let i = 0; i < Math.min(this.props.fileCount, Constants.MAX_DISPLAY_FILES); i++) { + // Add a placeholder to avoid pop-in once we get the file infos for this post + postFiles.push( +
+ ); + } + } + + return ( +
+
+ {postFiles} +
+ this.setState({showPreviewModal: false})} + startId={this.state.startImgIndex} + fileInfos={this.props.fileInfos} + /> +
+ ); + } +} diff --git a/webapp/components/file_attachment_list/index.js b/webapp/components/file_attachment_list/index.js new file mode 100644 index 000000000..4081e4220 --- /dev/null +++ b/webapp/components/file_attachment_list/index.js @@ -0,0 +1,39 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import {connect} from 'react-redux'; +import {bindActionCreators} from 'redux'; +import {getMissingFilesForPost} from 'mattermost-redux/actions/files'; +import {makeGetFilesForPost} from 'mattermost-redux/selectors/entities/files'; + +import FileAttachmentList from './file_attachment_list.jsx'; + +function makeMapStateToProps() { + const selectFilesForPost = makeGetFilesForPost(); + return function mapStateToProps(state, ownProps) { + const fileInfos = selectFilesForPost(state, ownProps.post); + + let fileCount = 0; + if (ownProps.post.file_ids) { + fileCount = ownProps.post.file_ids.length; + } else if (this.props.post.filenames) { + fileCount = ownProps.post.filenames.length; + } + + return { + ...ownProps, + fileInfos, + fileCount + }; + }; +} + +function mapDispatchToProps(dispatch) { + return { + actions: bindActionCreators({ + getMissingFilesForPost + }, dispatch) + }; +} + +export default connect(makeMapStateToProps, mapDispatchToProps)(FileAttachmentList); diff --git a/webapp/components/file_attachment_list_container.jsx b/webapp/components/file_attachment_list_container.jsx deleted file mode 100644 index 4b05e392c..000000000 --- a/webapp/components/file_attachment_list_container.jsx +++ /dev/null @@ -1,92 +0,0 @@ -import PropTypes from 'prop-types'; - -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import React from 'react'; - -import * as AsyncClient from 'utils/async_client.jsx'; -import FileStore from 'stores/file_store.jsx'; - -import FileAttachmentList from './file_attachment_list.jsx'; - -export default class FileAttachmentListContainer extends React.Component { - static propTypes = { - post: PropTypes.object.isRequired, - compactDisplay: PropTypes.bool.isRequired - } - - constructor(props) { - super(props); - - this.handleFileChange = this.handleFileChange.bind(this); - - this.state = { - fileInfos: FileStore.getInfosForPost(props.post.id) - }; - } - - componentDidMount() { - FileStore.addChangeListener(this.handleFileChange); - - if (this.props.post.id && !FileStore.hasInfosForPost(this.props.post.id)) { - AsyncClient.getFileInfosForPost(this.props.post.channel_id, this.props.post.id); - } - } - - componentWillReceiveProps(nextProps) { - if (nextProps.post.id !== this.props.post.id) { - this.setState({ - fileInfos: FileStore.getInfosForPost(nextProps.post.id) - }); - - if (nextProps.post.id && !FileStore.hasInfosForPost(nextProps.post.id)) { - AsyncClient.getFileInfosForPost(nextProps.post.channel_id, nextProps.post.id); - } - } - } - - shouldComponentUpdate(nextProps, nextState) { - if (this.props.post.id !== nextProps.post.id) { - return true; - } - - if (this.props.compactDisplay !== nextProps.compactDisplay) { - return true; - } - - // fileInfos are treated as immutable by the FileStore - if (nextState.fileInfos !== this.state.fileInfos) { - return true; - } - - return false; - } - - handleFileChange() { - this.setState({ - fileInfos: FileStore.getInfosForPost(this.props.post.id) - }); - } - - componentWillUnmount() { - FileStore.removeChangeListener(this.handleFileChange); - } - - render() { - let fileCount = 0; - if (this.props.post.file_ids) { - fileCount = this.props.post.file_ids.length; - } else if (this.props.post.filenames) { - fileCount = this.props.post.filenames.length; - } - - return ( - - ); - } -} diff --git a/webapp/components/file_preview.jsx b/webapp/components/file_preview.jsx index 3bf05744f..65a71c047 100644 --- a/webapp/components/file_preview.jsx +++ b/webapp/components/file_preview.jsx @@ -1,9 +1,9 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import FileStore from 'stores/file_store.jsx'; import ReactDOM from 'react-dom'; import * as Utils from 'utils/utils.jsx'; +import {getFileUrl} from 'mattermost-redux/utils/file_utils'; import PropTypes from 'prop-types'; @@ -39,7 +39,7 @@ export default class FilePreview extends React.Component { previewImage = ( ); } else { diff --git a/webapp/components/needs_team/needs_team.jsx b/webapp/components/needs_team/needs_team.jsx index 4f5188a47..6fd2d3208 100644 --- a/webapp/components/needs_team/needs_team.jsx +++ b/webapp/components/needs_team/needs_team.jsx @@ -13,7 +13,6 @@ import UserStore from 'stores/user_store.jsx'; import PreferenceStore from 'stores/preference_store.jsx'; import ChannelStore from 'stores/channel_store.jsx'; import PostStore from 'stores/post_store.jsx'; -import * as GlobalActions from 'actions/global_actions.jsx'; import {startPeriodicStatusUpdates, stopPeriodicStatusUpdates} from 'actions/status_actions.jsx'; import {startPeriodicSync, stopPeriodicSync} from 'actions/websocket_actions.jsx'; import {loadProfilesForSidebar} from 'actions/user_actions.jsx'; @@ -23,13 +22,16 @@ const TutorialSteps = Constants.TutorialSteps; const Preferences = Constants.Preferences; import AnnouncementBar from 'components/announcement_bar'; -import SidebarRight from 'components/sidebar_right.jsx'; +import SidebarRight from 'components/sidebar_right'; import SidebarRightMenu from 'components/sidebar_right_menu.jsx'; import Navbar from 'components/navbar.jsx'; import WebrtcSidebar from 'components/webrtc/components/webrtc_sidebar.jsx'; import WebrtcNotification from 'components/webrtc/components/webrtc_notification.jsx'; +import store from 'stores/redux_store.jsx'; +import {getPost} from 'mattermost-redux/selectors/entities/posts'; + // Modals import GetPostLinkModal from 'components/get_post_link_modal.jsx'; import GetPublicLinkModal from 'components/get_public_link_modal.jsx'; @@ -111,9 +113,6 @@ export default class NeedsTeam extends React.Component { TeamStore.addChangeListener(this.onTeamChanged); PreferenceStore.addChangeListener(this.onPreferencesChanged); - // Emit view action - GlobalActions.viewLoggedIn(); - startPeriodicStatusUpdates(); startPeriodicSync(); @@ -201,7 +200,7 @@ export default class NeedsTeam extends React.Component { if (channel == null) { // the permalink view is not really tied to a particular channel but still needs it const postId = PostStore.getFocusedPostId(); - const post = PostStore.getEarliestPostFromPage(postId); + const post = getPost(store.getState(), postId); // the post take some time before being available on page load if (post != null) { diff --git a/webapp/components/permalink_view.jsx b/webapp/components/permalink_view.jsx index ebcd83916..237ad8f44 100644 --- a/webapp/components/permalink_view.jsx +++ b/webapp/components/permalink_view.jsx @@ -6,7 +6,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import ChannelHeader from 'components/channel_header.jsx'; -import PostFocusViewController from 'components/post_view/post_focus_view_controller.jsx'; +import PostView from 'components/post_view'; import ChannelStore from 'stores/channel_store.jsx'; import TeamStore from 'stores/team_store.jsx'; @@ -14,7 +14,11 @@ import TeamStore from 'stores/team_store.jsx'; import {Link} from 'react-router/es6'; import {FormattedMessage} from 'react-intl'; -export default class PermalinkView extends React.Component { +export default class PermalinkView extends React.PureComponent { + static propTypes = { + params: PropTypes.object.isRequired + } + constructor(props) { super(props); @@ -24,6 +28,7 @@ export default class PermalinkView extends React.Component { this.state = this.getStateFromStores(props); } + getStateFromStores(props) { const postId = props.params.postid; const channel = ChannelStore.getCurrent(); @@ -38,27 +43,33 @@ export default class PermalinkView extends React.Component { postId }; } + isStateValid() { return this.state.channelId !== '' && this.state.teamName; } + updateState() { this.setState(this.getStateFromStores(this.props)); } + componentDidMount() { ChannelStore.addChangeListener(this.updateState); TeamStore.addChangeListener(this.updateState); $('body').addClass('app__body'); } + componentWillUnmount() { ChannelStore.removeChangeListener(this.updateState); TeamStore.removeChangeListener(this.updateState); $('body').removeClass('app__body'); } + componentWillReceiveProps(nextProps) { this.setState(this.getStateFromStores(nextProps)); } + render() { if (!this.isStateValid()) { return null; @@ -71,7 +82,10 @@ export default class PermalinkView extends React.Component { - +