diff --git a/app/components/post/index.js b/app/components/post/index.js index 6aafa6274..97a864e77 100644 --- a/app/components/post/index.js +++ b/app/components/post/index.js @@ -6,83 +6,109 @@ import {bindActionCreators} from 'redux'; import {createPost, deletePost, removePost} from 'mattermost-redux/actions/posts'; import {getCurrentChannelId, isCurrentChannelReadOnly} from 'mattermost-redux/selectors/entities/channels'; -import {getPost} from 'mattermost-redux/selectors/entities/posts'; +import {getPost, makeGetCommentCountForPost} from 'mattermost-redux/selectors/entities/posts'; import {getCurrentUserId, getCurrentUserRoles} from 'mattermost-redux/selectors/entities/users'; import {getMyPreferences, getTheme} from 'mattermost-redux/selectors/entities/preferences'; import {getCurrentTeamUrl, getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams'; -import {canDeletePost, canEditPost, isPostFlagged} from 'mattermost-redux/utils/post_utils'; +import {canDeletePost, canEditPost, isPostFlagged, isSystemMessage} from 'mattermost-redux/utils/post_utils'; import {isAdmin as checkIsAdmin, isSystemAdmin as checkIsSystemAdmin} from 'mattermost-redux/utils/user_utils'; import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general'; import {insertToDraft, setPostTooltipVisible} from 'app/actions/views/channel'; import {addReaction} from 'app/actions/views/emoji'; import {getDimensions} from 'app/selectors/device'; +import {Posts} from 'mattermost-redux/constants'; import Post from './post'; -function mapStateToProps(state, ownProps) { +function isConsecutivePost(state, ownProps) { const post = getPost(state, ownProps.postId); + const previousPost = ownProps.previousPostId && getPost(state, ownProps.previousPostId); - const config = getConfig(state); - const license = getLicense(state); - const roles = getCurrentUserId(state) ? getCurrentUserRoles(state) : ''; - const myPreferences = getMyPreferences(state); - const currentUserId = getCurrentUserId(state); - const currentTeamId = getCurrentTeamId(state); - const currentChannelId = getCurrentChannelId(state); + let consecutivePost = false; - let isFirstReply = true; - let isLastReply = true; - let commentedOnPost = null; - if (ownProps.renderReplies && post && post.root_id) { - if (ownProps.previousPostId) { - const previousPost = getPost(state, ownProps.previousPostId); + if (previousPost) { + const postFromWebhook = Boolean(post.props && post.props.from_webhook); + const prevPostFromWebhook = Boolean(previousPost.props && previousPost.props.from_webhook); - if (previousPost && (previousPost.id === post.root_id || previousPost.root_id === post.root_id)) { - // Previous post is root post or previous post is in same thread - isFirstReply = false; - } else { - // Last post is not a comment on the same message - commentedOnPost = getPost(state, post.root_id); - } - } - - if (ownProps.nextPostId) { - const nextPost = getPost(state, ownProps.nextPostId); - - if (nextPost && nextPost.root_id === post.root_id) { - isLastReply = false; - } + if (previousPost && previousPost.user_id === post.user_id && + post.create_at - previousPost.create_at <= Posts.POST_COLLAPSE_TIMEOUT && + !postFromWebhook && !prevPostFromWebhook && + !isSystemMessage(post) && !isSystemMessage(previousPost) && + previousPost.root_id === post.root_id) { + // The last post and this post were made by the same user within some time + consecutivePost = true; } } + return consecutivePost; +} - const {deviceWidth} = getDimensions(state); +function makeMapStateToProps() { + const getCommentCountForPost = makeGetCommentCountForPost(); + return function mapStateToProps(state, ownProps) { + const post = getPost(state, ownProps.postId); + const config = getConfig(state); + const license = getLicense(state); + const roles = getCurrentUserId(state) ? getCurrentUserRoles(state) : ''; + const myPreferences = getMyPreferences(state); + const currentUserId = getCurrentUserId(state); + const currentTeamId = getCurrentTeamId(state); + const currentChannelId = getCurrentChannelId(state); - const isAdmin = checkIsAdmin(roles); - const isSystemAdmin = checkIsSystemAdmin(roles); + let isFirstReply = true; + let isLastReply = true; + let commentedOnPost = null; - let canDelete = false; - let canEdit = false; - if (post) { - canDelete = canDeletePost(state, config, license, currentTeamId, currentChannelId, currentUserId, post, isAdmin, isSystemAdmin); - canEdit = canEditPost(state, config, license, currentTeamId, currentChannelId, currentUserId, post); - } + if (ownProps.renderReplies && post && post.root_id) { + if (ownProps.previousPostId) { + const previousPost = getPost(state, ownProps.previousPostId); + if (previousPost && (previousPost.id === post.root_id || previousPost.root_id === post.root_id)) { + // Previous post is root post or previous post is in same thread + isFirstReply = false; + } else { + // Last post is not a comment on the same message + commentedOnPost = getPost(state, post.root_id); + } + } - return { - channelIsReadOnly: isCurrentChannelReadOnly(state), - config, - canDelete, - canEdit, - currentTeamUrl: getCurrentTeamUrl(state), - currentUserId, - deviceWidth, - post, - isFirstReply, - isLastReply, - commentedOnPost, - license, - theme: getTheme(state), - isFlagged: isPostFlagged(post.id, myPreferences), + if (ownProps.nextPostId) { + const nextPost = getPost(state, ownProps.nextPostId); + + if (nextPost && nextPost.root_id === post.root_id) { + isLastReply = false; + } + } + } + const {deviceWidth} = getDimensions(state); + + const isAdmin = checkIsAdmin(roles); + const isSystemAdmin = checkIsSystemAdmin(roles); + + let canDelete = false; + let canEdit = false; + if (post) { + canDelete = canDeletePost(state, config, license, currentTeamId, currentChannelId, currentUserId, post, isAdmin, isSystemAdmin); + canEdit = canEditPost(state, config, license, currentTeamId, currentChannelId, currentUserId, post); + } + + return { + channelIsReadOnly: isCurrentChannelReadOnly(state), + config, + canDelete, + canEdit, + currentTeamUrl: getCurrentTeamUrl(state), + currentUserId, + deviceWidth, + post, + isFirstReply, + isLastReply, + consecutivePost: isConsecutivePost(state, ownProps), + hasComments: getCommentCountForPost(state, {post}) > 0, + commentedOnPost, + license, + theme: getTheme(state), + isFlagged: isPostFlagged(post.id, myPreferences), + }; }; } @@ -99,4 +125,4 @@ function mapDispatchToProps(dispatch) { }; } -export default connect(mapStateToProps, mapDispatchToProps)(Post); +export default connect(makeMapStateToProps, mapDispatchToProps)(Post); diff --git a/app/components/post/post.js b/app/components/post/post.js index acbc82694..5616e3e36 100644 --- a/app/components/post/post.js +++ b/app/components/post/post.js @@ -51,6 +51,8 @@ export default class Post extends PureComponent { renderReplies: PropTypes.bool, isFirstReply: PropTypes.bool, isLastReply: PropTypes.bool, + consecutivePost: PropTypes.bool, + hasComments: PropTypes.bool, isSearchResult: PropTypes.bool, commentedOnPost: PropTypes.object, license: PropTypes.object.isRequired, @@ -139,7 +141,7 @@ export default class Post extends PureComponent { autofillUserMention = (username) => { this.props.actions.insertToDraft(`@${username} `); - } + }; handleEditDisable = () => { this.setState({canEdit: false}); @@ -151,7 +153,10 @@ export default class Post extends PureComponent { Alert.alert( formatMessage({id: 'mobile.post.delete_title', defaultMessage: 'Delete Post'}), - formatMessage({id: 'mobile.post.delete_question', defaultMessage: 'Are you sure you want to delete this post?'}), + formatMessage({ + id: 'mobile.post.delete_question', + defaultMessage: 'Are you sure you want to delete this post?', + }), [{ text: formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'}), style: 'cancel', @@ -194,31 +199,30 @@ export default class Post extends PureComponent { handleAddReactionToPost = (emoji) => { const {post} = this.props; this.props.actions.addReaction(post.id, emoji); - } + }; handleAddReaction = preventDoubleTap(() => { const {intl} = this.context; const {navigator, post, theme} = this.props; - MaterialIcon.getImageSource('close', 20, theme.sidebarHeaderTextColor). - then((source) => { - navigator.showModal({ - screen: 'AddReaction', - title: intl.formatMessage({id: 'mobile.post_info.add_reaction', defaultMessage: 'Add Reaction'}), - animated: true, - navigatorStyle: { - navBarTextColor: theme.sidebarHeaderTextColor, - navBarBackgroundColor: theme.sidebarHeaderBg, - navBarButtonColor: theme.sidebarHeaderTextColor, - screenBackgroundColor: theme.centerChannelBg, - }, - passProps: { - post, - closeButton: source, - onEmojiPress: this.handleAddReactionToPost, - }, - }); + MaterialIcon.getImageSource('close', 20, theme.sidebarHeaderTextColor).then((source) => { + navigator.showModal({ + screen: 'AddReaction', + title: intl.formatMessage({id: 'mobile.post_info.add_reaction', defaultMessage: 'Add Reaction'}), + animated: true, + navigatorStyle: { + navBarTextColor: theme.sidebarHeaderTextColor, + navBarBackgroundColor: theme.sidebarHeaderBg, + navBarButtonColor: theme.sidebarHeaderTextColor, + screenBackgroundColor: theme.centerChannelBg, + }, + passProps: { + post, + closeButton: source, + onEmojiPress: this.handleAddReactionToPost, + }, }); + }); }); handleFailedPostPress = () => { @@ -355,7 +359,7 @@ export default class Post extends PureComponent { } Clipboard.setString(textToCopy); - } + }; handleCopyPermalink = () => { const {currentTeamUrl, postId} = this.props; @@ -394,6 +398,8 @@ export default class Post extends PureComponent { showLongPost, theme, managedConfig, + consecutivePost, + hasComments, isFlagged, } = this.props; @@ -405,14 +411,19 @@ export default class Post extends PureComponent { const selected = this.state && this.state.selected ? style.selected : null; const highlighted = highlight ? style.highlight : null; const isReplyPost = this.isReplyPost(); - const onUsernamePress = Config.ExperimentalUsernamePressIsMention ? this.autofillUserMention : this.viewUserProfile; + const mergeMessage = consecutivePost && !hasComments; // postWidth = deviceWidth - profilePic width - profilePictureContainer margins - right column margin const postWidth = this.props.deviceWidth - 66; - return ( - + let postHeader; + let userProfile; + + if (mergeMessage) { + userProfile = ; + } else { + userProfile = ( + ); + postHeader = ( + + ); + } + + return ( + + {userProfile} {!commentedOnPost && this.renderReplyBar()} - + {postHeader} { flexDirection: 'row', flex: 1, }, + consecutivePostContainer: { + marginBottom: 10, + marginRight: 10, + marginLeft: 46, + marginTop: 10, + }, profilePictureContainer: { marginBottom: 10, marginRight: 10,