forked from Ivasoft/mattermost-mobile
* RN-223 Emoji picker for adding new emoji reactions * Remove redundant setServerVersion and fetch custom emojis on reset * Review feedback * Review feedback 2 * Review feedback 3 * Review feedback 4 * Change aliases to array and add header to image source * Remove aliases from custom emojis
403 lines
14 KiB
JavaScript
403 lines
14 KiB
JavaScript
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
|
// See License.txt for license information.
|
|
|
|
import React, {PureComponent} from 'react';
|
|
import PropTypes from 'prop-types';
|
|
import {
|
|
Alert,
|
|
View,
|
|
ViewPropTypes
|
|
} from 'react-native';
|
|
import {injectIntl, intlShape} from 'react-intl';
|
|
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
|
|
|
|
import PostBody from 'app/components/post_body';
|
|
import PostHeader from 'app/components/post_header';
|
|
import PostProfilePicture from 'app/components/post_profile_picture';
|
|
import {NavigationTypes} from 'app/constants';
|
|
import {emptyFunction} from 'app/utils/general';
|
|
import {preventDoubleTap} from 'app/utils/tap';
|
|
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
|
|
|
import {Posts} from 'mattermost-redux/constants';
|
|
import DelayedAction from 'mattermost-redux/utils/delayed_action';
|
|
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
|
import {canDeletePost, canEditPost, isPostEphemeral, isPostPendingOrFailed, isSystemMessage} from 'mattermost-redux/utils/post_utils';
|
|
import {isAdmin, isSystemAdmin} from 'mattermost-redux/utils/user_utils';
|
|
|
|
class Post extends PureComponent {
|
|
static propTypes = {
|
|
actions: PropTypes.shape({
|
|
addReaction: PropTypes.func.isRequired,
|
|
createPost: PropTypes.func.isRequired,
|
|
deletePost: PropTypes.func.isRequired,
|
|
removePost: PropTypes.func.isRequired,
|
|
setPostTooltipVisible: PropTypes.func.isRequired
|
|
}).isRequired,
|
|
config: PropTypes.object.isRequired,
|
|
currentUserId: PropTypes.string.isRequired,
|
|
highlight: PropTypes.bool,
|
|
intl: intlShape.isRequired,
|
|
style: ViewPropTypes.style,
|
|
post: PropTypes.object.isRequired,
|
|
renderReplies: PropTypes.bool,
|
|
isFirstReply: PropTypes.bool,
|
|
isLastReply: PropTypes.bool,
|
|
isSearchResult: PropTypes.bool,
|
|
commentedOnPost: PropTypes.object,
|
|
license: PropTypes.object.isRequired,
|
|
navigator: PropTypes.object,
|
|
roles: PropTypes.string,
|
|
shouldRenderReplyButton: PropTypes.bool,
|
|
tooltipVisible: PropTypes.bool,
|
|
theme: PropTypes.object.isRequired,
|
|
onPress: PropTypes.func,
|
|
onReply: PropTypes.func
|
|
};
|
|
|
|
static defaultProps = {
|
|
isSearchResult: false
|
|
};
|
|
|
|
constructor(props) {
|
|
super(props);
|
|
|
|
const {config, license, currentUserId, roles, post} = props;
|
|
this.editDisableAction = new DelayedAction(this.handleEditDisable);
|
|
this.state = {
|
|
canEdit: canEditPost(config, license, currentUserId, post, this.editDisableAction),
|
|
canDelete: canDeletePost(config, license, currentUserId, post, isAdmin(roles), isSystemAdmin(roles))
|
|
};
|
|
}
|
|
|
|
componentWillReceiveProps(nextProps) {
|
|
const {config, license, currentUserId, roles, post} = nextProps;
|
|
this.setState({
|
|
canEdit: canEditPost(config, license, currentUserId, post, this.editDisableAction),
|
|
canDelete: canDeletePost(config, license, currentUserId, post, isAdmin(roles), isSystemAdmin(roles))
|
|
});
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
this.editDisableAction.cancel();
|
|
}
|
|
|
|
goToUserProfile = () => {
|
|
const {intl, navigator, post, theme} = this.props;
|
|
navigator.push({
|
|
screen: 'UserProfile',
|
|
title: intl.formatMessage({id: 'mobile.routes.user_profile', defaultMessage: 'Profile'}),
|
|
animated: true,
|
|
backButtonTitle: '',
|
|
passProps: {
|
|
userId: post.user_id
|
|
},
|
|
navigatorStyle: {
|
|
navBarTextColor: theme.sidebarHeaderTextColor,
|
|
navBarBackgroundColor: theme.sidebarHeaderBg,
|
|
navBarButtonColor: theme.sidebarHeaderTextColor,
|
|
screenBackgroundColor: theme.centerChannelBg
|
|
}
|
|
});
|
|
};
|
|
|
|
handleEditDisable = () => {
|
|
this.setState({canEdit: false});
|
|
};
|
|
|
|
handlePostDelete = () => {
|
|
const {formatMessage} = this.props.intl;
|
|
const {post, actions} = this.props;
|
|
|
|
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?'}),
|
|
[{
|
|
text: formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'}),
|
|
style: 'cancel'
|
|
}, {
|
|
text: formatMessage({id: 'post_info.del', defaultMessage: 'Delete'}),
|
|
style: 'destructive',
|
|
onPress: () => {
|
|
this.editDisableAction.cancel();
|
|
actions.deletePost(post);
|
|
}
|
|
}]
|
|
);
|
|
};
|
|
|
|
handlePostEdit = () => {
|
|
const {intl, navigator, post, theme} = this.props;
|
|
MaterialIcon.getImageSource('close', 20, theme.sidebarHeaderTextColor).then((source) => {
|
|
navigator.showModal({
|
|
screen: 'EditPost',
|
|
title: intl.formatMessage({id: 'mobile.edit_post.title', defaultMessage: 'Editing Message'}),
|
|
animated: true,
|
|
navigatorStyle: {
|
|
navBarTextColor: theme.sidebarHeaderTextColor,
|
|
navBarBackgroundColor: theme.sidebarHeaderBg,
|
|
navBarButtonColor: theme.sidebarHeaderTextColor,
|
|
screenBackgroundColor: theme.centerChannelBg
|
|
},
|
|
passProps: {
|
|
post,
|
|
closeButton: source
|
|
}
|
|
});
|
|
});
|
|
};
|
|
|
|
handleAddReactionToPost = (emoji) => {
|
|
const {post} = this.props;
|
|
this.props.actions.addReaction(post.id, emoji);
|
|
}
|
|
|
|
handleAddReaction = () => {
|
|
const {intl, 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
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
handleFailedPostPress = () => {
|
|
const options = {
|
|
title: {
|
|
id: 'mobile.post.failed_title',
|
|
defaultMessage: 'Unable to send your message:'
|
|
},
|
|
items: [{
|
|
action: () => {
|
|
const {failed, id, ...post} = this.props.post; // eslint-disable-line
|
|
|
|
EventEmitter.emit(NavigationTypes.NAVIGATION_CLOSE_MODAL);
|
|
this.props.actions.createPost(post);
|
|
},
|
|
text: {
|
|
id: 'mobile.post.failed_retry',
|
|
defaultMessage: 'Try Again'
|
|
}
|
|
}, {
|
|
action: () => {
|
|
EventEmitter.emit(NavigationTypes.NAVIGATION_CLOSE_MODAL);
|
|
this.onRemovePost(this.props.post);
|
|
},
|
|
text: {
|
|
id: 'mobile.post.failed_delete',
|
|
defaultMessage: 'Delete Message'
|
|
},
|
|
textStyle: {
|
|
color: '#CC3239'
|
|
}
|
|
}]
|
|
};
|
|
|
|
this.props.navigator.showModal({
|
|
screen: 'OptionsModal',
|
|
title: '',
|
|
animationType: 'none',
|
|
passProps: {
|
|
items: options.items,
|
|
title: options.title
|
|
},
|
|
navigatorStyle: {
|
|
navBarHidden: true,
|
|
statusBarHidden: false,
|
|
statusBarHideWithNavBar: false,
|
|
screenBackgroundColor: 'transparent',
|
|
modalPresentationStyle: 'overCurrentContext'
|
|
}
|
|
});
|
|
};
|
|
|
|
handlePress = () => {
|
|
const {post, onPress, tooltipVisible} = this.props;
|
|
if (!tooltipVisible) {
|
|
if (onPress && post.state !== Posts.POST_DELETED && !isSystemMessage(post) && !isPostPendingOrFailed(post)) {
|
|
preventDoubleTap(onPress, null, post);
|
|
} else if (isPostEphemeral(post)) {
|
|
preventDoubleTap(this.onRemovePost, this, post);
|
|
}
|
|
}
|
|
};
|
|
|
|
handleReply = () => {
|
|
const {post, onReply, tooltipVisible} = this.props;
|
|
if (!tooltipVisible && onReply) {
|
|
return preventDoubleTap(onReply, null, post);
|
|
}
|
|
|
|
return this.handlePress();
|
|
};
|
|
|
|
onRemovePost = (post) => {
|
|
const {removePost} = this.props.actions;
|
|
removePost(post);
|
|
};
|
|
|
|
renderReplyBar = () => {
|
|
const {
|
|
commentedOnPost,
|
|
isFirstReply,
|
|
isLastReply,
|
|
post,
|
|
renderReplies,
|
|
theme
|
|
} = this.props;
|
|
|
|
if (!renderReplies || !post.root_id) {
|
|
return null;
|
|
}
|
|
|
|
const style = getStyleSheet(theme);
|
|
const replyBarStyle = [style.replyBar];
|
|
|
|
if (isFirstReply || commentedOnPost) {
|
|
replyBarStyle.push(style.replyBarFirst);
|
|
}
|
|
|
|
if (isLastReply) {
|
|
replyBarStyle.push(style.replyBarLast);
|
|
}
|
|
|
|
return <View style={replyBarStyle}/>;
|
|
};
|
|
|
|
viewUserProfile = () => {
|
|
const {isSearchResult} = this.props;
|
|
|
|
if (!isSearchResult) {
|
|
preventDoubleTap(this.goToUserProfile, this);
|
|
}
|
|
};
|
|
|
|
toggleSelected = (selected) => {
|
|
this.props.actions.setPostTooltipVisible(selected);
|
|
this.setState({selected});
|
|
};
|
|
|
|
render() {
|
|
const {
|
|
commentedOnPost,
|
|
highlight,
|
|
isLastReply,
|
|
isSearchResult,
|
|
post,
|
|
renderReplies,
|
|
shouldRenderReplyButton,
|
|
theme
|
|
} = this.props;
|
|
const style = getStyleSheet(theme);
|
|
const selected = this.state && this.state.selected ? style.selected : null;
|
|
const highlighted = highlight ? style.highlight : null;
|
|
|
|
return (
|
|
<View style={[style.container, this.props.style, highlighted, selected]}>
|
|
<View style={[style.profilePictureContainer, (isPostPendingOrFailed(post) && style.pendingPost)]}>
|
|
<PostProfilePicture
|
|
onViewUserProfile={this.viewUserProfile}
|
|
postId={post.id}
|
|
/>
|
|
</View>
|
|
<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}
|
|
onPress={this.handleReply}
|
|
onViewUserProfile={this.viewUserProfile}
|
|
renderReplies={renderReplies}
|
|
theme={theme}
|
|
/>
|
|
<PostBody
|
|
canDelete={this.state.canDelete}
|
|
canEdit={this.state.canEdit}
|
|
isSearchResult={isSearchResult}
|
|
navigator={this.props.navigator}
|
|
onAddReaction={this.handleAddReaction}
|
|
onFailedPostPress={this.handleFailedPostPress}
|
|
onPostDelete={this.handlePostDelete}
|
|
onPostEdit={this.handlePostEdit}
|
|
onPress={this.handlePress}
|
|
postId={post.id}
|
|
renderReplyBar={commentedOnPost ? this.renderReplyBar : emptyFunction}
|
|
toggleSelected={this.toggleSelected}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
}
|
|
|
|
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|
return {
|
|
container: {
|
|
backgroundColor: theme.centerChannelBg,
|
|
flexDirection: 'row'
|
|
},
|
|
pendingPost: {
|
|
opacity: 0.5
|
|
},
|
|
rightColumn: {
|
|
flex: 1,
|
|
flexDirection: 'column',
|
|
marginRight: 12
|
|
},
|
|
rightColumnPadding: {
|
|
paddingBottom: 3
|
|
},
|
|
messageContainerWithReplyBar: {
|
|
flexDirection: 'row',
|
|
flex: 1
|
|
},
|
|
profilePictureContainer: {
|
|
marginBottom: 10,
|
|
marginRight: 10,
|
|
marginLeft: 12,
|
|
marginTop: 10
|
|
},
|
|
replyBar: {
|
|
backgroundColor: theme.centerChannelColor,
|
|
opacity: 0.1,
|
|
marginRight: 10,
|
|
width: 3,
|
|
flexBasis: 3
|
|
},
|
|
replyBarFirst: {
|
|
paddingTop: 10
|
|
},
|
|
replyBarLast: {
|
|
paddingBottom: 10
|
|
},
|
|
selected: {
|
|
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1)
|
|
},
|
|
highlight: {
|
|
backgroundColor: changeOpacity(theme.mentionHighlightBg, 0.5)
|
|
}
|
|
};
|
|
});
|
|
|
|
export default injectIntl(Post);
|