// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; import { ScrollView, TouchableOpacity, View, } from 'react-native'; import {intlShape} from 'react-intl'; import Icon from 'react-native-vector-icons/Ionicons'; import {Posts} from 'mattermost-redux/constants'; import CombinedSystemMessage from 'app/components/combined_system_message'; import FormattedText from 'app/components/formatted_text'; import Markdown from 'app/components/markdown'; import MarkdownEmoji from 'app/components/markdown/markdown_emoji'; import ShowMoreButton from 'app/components/show_more_button'; import {emptyFunction} from 'app/utils/general'; import {getMarkdownTextStyles, getMarkdownBlockStyles} from 'app/utils/markdown'; import {preventDoubleTap} from 'app/utils/tap'; import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; let FileAttachmentList; let PostAddChannelMember; let PostBodyAdditionalContent; let Reactions; const SHOW_MORE_HEIGHT = 60; export default class PostBody extends PureComponent { static propTypes = { canDelete: PropTypes.bool, channelIsReadOnly: PropTypes.bool.isRequired, deviceHeight: PropTypes.number.isRequired, fileIds: PropTypes.array, hasBeenDeleted: PropTypes.bool, hasBeenEdited: PropTypes.bool, hasReactions: PropTypes.bool, highlight: PropTypes.bool, isFailed: PropTypes.bool, isFlagged: PropTypes.bool, isPending: PropTypes.bool, isPostAddChannelMember: PropTypes.bool, isPostEphemeral: PropTypes.bool, isReplyPost: PropTypes.bool, isSearchResult: PropTypes.bool, isSystemMessage: PropTypes.bool, metadata: PropTypes.object, managedConfig: PropTypes.object, message: PropTypes.string, navigator: PropTypes.object.isRequired, onFailedPostPress: PropTypes.func, onHashtagPress: PropTypes.func, onPermalinkPress: PropTypes.func, onPress: PropTypes.func, postId: PropTypes.string.isRequired, postProps: PropTypes.object, postType: PropTypes.string, replyBarStyle: PropTypes.array, showAddReaction: PropTypes.bool, showLongPost: PropTypes.bool.isRequired, isEmojiOnly: PropTypes.bool.isRequired, shouldRenderJumboEmoji: PropTypes.bool.isRequired, theme: PropTypes.object, }; static defaultProps = { fileIds: [], onFailedPostPress: emptyFunction, onPress: emptyFunction, replyBarStyle: [], }; static contextTypes = { intl: intlShape.isRequired, }; static getDerivedStateFromProps(nextProps, prevState) { const maxHeight = Math.round((nextProps.deviceHeight * 0.6) + SHOW_MORE_HEIGHT); if (maxHeight !== prevState.maxHeight) { return { maxHeight, }; } return null; } state = { isLongPost: false, }; measurePost = (event) => { const {height} = event.nativeEvent.layout; const {showLongPost} = this.props; if (!showLongPost && height >= this.state.maxHeight) { this.setState({ isLongPost: true, }); } }; openLongPost = preventDoubleTap(() => { const { managedConfig, navigator, onHashtagPress, onPermalinkPress, postId, } = this.props; const options = { screen: 'LongPost', animationType: 'none', backButtonTitle: '', overrideBackPress: true, navigatorStyle: { navBarHidden: true, screenBackgroundColor: changeOpacity('#000', 0.2), modalPresentationStyle: 'overCurrentContext', }, passProps: { postId, managedConfig, onHashtagPress, onPermalinkPress, }, }; navigator.showModal(options); }); showPostOptions = () => { const { canDelete, channelIsReadOnly, hasBeenDeleted, isFailed, isFlagged, isPending, isPostEphemeral, isSystemMessage, managedConfig, navigator, postId, showAddReaction, } = this.props; if (isSystemMessage && (!canDelete || hasBeenDeleted)) { return; } if (isPending || isFailed || isPostEphemeral) { return; } const options = { screen: 'PostOptions', animationType: 'none', backButtonTitle: '', navigatorStyle: { navBarHidden: true, navBarTransparent: true, screenBackgroundColor: 'transparent', modalPresentationStyle: 'overCurrentContext', }, passProps: { canDelete, channelIsReadOnly, hasBeenDeleted, isFlagged, postId, managedConfig, showAddReaction, }, }; navigator.showModal(options); }; renderAddChannelMember = (style, messageStyle, textStyles) => { const {onPress, postProps} = this.props; if (!PostAddChannelMember) { PostAddChannelMember = require('app/components/post_add_channel_member').default; } return ( ); }; renderFileAttachments() { const { fileIds, isFailed, navigator, postId, showLongPost, } = this.props; if (showLongPost) { return null; } let attachments; if (fileIds.length) { if (!FileAttachmentList) { FileAttachmentList = require('app/components/file_attachment_list').default; } attachments = ( ); } return attachments; } renderPostAdditionalContent = (blockStyles, messageStyle, textStyles) => { const { isReplyPost, isSystemMessage, message, metadata, navigator, onHashtagPress, onPermalinkPress, postId, postProps, } = this.props; if (isSystemMessage) { return null; } if (metadata && !metadata.embeds) { return null; } if (!PostBodyAdditionalContent) { PostBodyAdditionalContent = require('app/components/post_body_additional_content').default; } return ( ); }; renderReactions = () => { const { hasReactions, isSearchResult, navigator, postId, showLongPost, } = this.props; if (!hasReactions || isSearchResult || showLongPost) { return null; } if (!Reactions) { Reactions = require('app/components/reactions').default; } return ( ); }; render() { const { hasBeenDeleted, hasBeenEdited, highlight, isEmojiOnly, isFailed, isPending, isPostAddChannelMember, isReplyPost, isSearchResult, isSystemMessage, message, metadata, navigator, onFailedPostPress, onHashtagPress, onPermalinkPress, onPress, postProps, postType, replyBarStyle, shouldRenderJumboEmoji, showLongPost, theme, } = this.props; const {isLongPost, maxHeight} = this.state; const style = getStyleSheet(theme); const blockStyles = getMarkdownBlockStyles(theme); const textStyles = getMarkdownTextStyles(theme); const messageStyle = isSystemMessage ? [style.message, style.systemMessage] : style.message; const isPendingOrFailedPost = isPending || isFailed; let body; let messageComponent; if (hasBeenDeleted) { body = ( ); } else if (isPostAddChannelMember) { messageComponent = this.renderAddChannelMember(style, messageStyle, textStyles); } else if (postType === Posts.POST_TYPES.COMBINED_USER_ACTIVITY) { const {allUserIds, allUsernames, messageData} = postProps.user_activity; messageComponent = ( ); } else if (isEmojiOnly) { messageComponent = ( ); } else if (message.length) { messageComponent = ( ); } if (!hasBeenDeleted) { body = ( {messageComponent} {isLongPost && } {this.renderPostAdditionalContent(blockStyles, messageStyle, textStyles)} {this.renderFileAttachments()} {this.renderReactions()} ); } return ( {body} {isFailed && } ); } } const getStyleSheet = makeStyleSheetFromTheme((theme) => { return { messageBody: { paddingBottom: 2, paddingTop: 2, flex: 1, }, messageContainer: { width: '100%', }, reply: { paddingRight: 10, }, retry: { justifyContent: 'center', marginLeft: 10, }, message: { color: theme.centerChannelColor, fontSize: 15, lineHeight: 20, }, messageContainerWithReplyBar: { flexDirection: 'row', width: '100%', }, pendingPost: { opacity: 0.5, }, systemMessage: { opacity: 0.6, }, }; });