[MM-10285] Merge consecutive messages by same user (#1714)
* MM-10285 show avatar and reply header only on first messages * implemented changes for code review, thanks @koxen * resolved merge conflicts
This commit is contained in:
parent
f6d4d40caa
commit
830022cb82
2 changed files with 146 additions and 94 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<View style={[style.container, this.props.style, highlighted, selected]}>
|
||||
let postHeader;
|
||||
let userProfile;
|
||||
|
||||
if (mergeMessage) {
|
||||
userProfile = <View style={style.consecutivePostContainer}/>;
|
||||
} else {
|
||||
userProfile = (
|
||||
<TouchableHighlight
|
||||
style={[style.profilePictureContainer, (isPostPendingOrFailed(post) && style.pendingPost)]}
|
||||
onPress={this.handlePress}
|
||||
|
|
@ -426,22 +437,31 @@ export default class Post extends PureComponent {
|
|||
postId={post.id}
|
||||
/>
|
||||
</TouchableHighlight>
|
||||
);
|
||||
postHeader = (
|
||||
<PostHeader
|
||||
postId={post.id}
|
||||
commentedOnUserId={commentedOnPost && commentedOnPost.user_id}
|
||||
createAt={post.create_at}
|
||||
isSearchResult={isSearchResult}
|
||||
shouldRenderReplyButton={shouldRenderReplyButton}
|
||||
showFullDate={showFullDate}
|
||||
onPress={this.handleReply}
|
||||
onUsernamePress={onUsernamePress}
|
||||
renderReplies={renderReplies}
|
||||
theme={theme}
|
||||
isFlagged={isFlagged}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={[style.container, this.props.style, highlighted, selected]}>
|
||||
{userProfile}
|
||||
<View style={style.messageContainerWithReplyBar}>
|
||||
{!commentedOnPost && this.renderReplyBar()}
|
||||
<View style={[style.rightColumn, (commentedOnPost && isLastReply && style.rightColumnPadding)]}>
|
||||
<PostHeader
|
||||
postId={post.id}
|
||||
commentedOnUserId={commentedOnPost && commentedOnPost.user_id}
|
||||
createAt={post.create_at}
|
||||
isSearchResult={isSearchResult}
|
||||
shouldRenderReplyButton={shouldRenderReplyButton}
|
||||
showFullDate={showFullDate}
|
||||
onPress={this.handleReply}
|
||||
onUsernamePress={onUsernamePress}
|
||||
renderReplies={renderReplies}
|
||||
theme={theme}
|
||||
isFlagged={isFlagged}
|
||||
/>
|
||||
{postHeader}
|
||||
<View style={{maxWidth: postWidth}}>
|
||||
<PostBody
|
||||
ref={'postBody'}
|
||||
|
|
@ -496,6 +516,12 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
flexDirection: 'row',
|
||||
flex: 1,
|
||||
},
|
||||
consecutivePostContainer: {
|
||||
marginBottom: 10,
|
||||
marginRight: 10,
|
||||
marginLeft: 46,
|
||||
marginTop: 10,
|
||||
},
|
||||
profilePictureContainer: {
|
||||
marginBottom: 10,
|
||||
marginRight: 10,
|
||||
|
|
|
|||
Loading…
Reference in a new issue