diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js index ab813db2f2e9..bdef759d0ed6 100644 --- a/src/pages/home/report/ReportActionItemFragment.js +++ b/src/pages/home/report/ReportActionItemFragment.js @@ -1,4 +1,3 @@ -import Str from 'expensify-common/lib/str'; import PropTypes from 'prop-types'; import React, {memo} from 'react'; import avatarPropTypes from '@components/avatarPropTypes'; @@ -8,16 +7,13 @@ import Text from '@components/Text'; import UserDetailsTooltip from '@components/UserDetailsTooltip'; import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withWindowDimensions'; -import ZeroWidthView from '@components/ZeroWidthView'; import compose from '@libs/compose'; import convertToLTR from '@libs/convertToLTR'; -import * as DeviceCapabilities from '@libs/DeviceCapabilities'; -import * as EmojiUtils from '@libs/EmojiUtils'; -import editedLabelStyles from '@styles/editedLabelStyles'; +import * as ReportUtils from '@libs/ReportUtils'; import styles from '@styles/styles'; -import themeColors from '@styles/themes/default'; -import variables from '@styles/variables'; import CONST from '@src/CONST'; +import AttachmentCommentFragment from './comment/AttachmentCommentFragment'; +import TextCommentFragment from './comment/TextCommentFragment'; import reportActionFragmentPropTypes from './reportActionFragmentPropTypes'; const propTypes = { @@ -63,6 +59,9 @@ const propTypes = { /** Whether the comment is a thread parent message/the first message in a thread */ isThreadParentMessage: PropTypes.bool, + /** Should the comment have the appearance of being grouped with the previous comment? */ + displayAsGroup: PropTypes.bool, + /** Whether the report action type is 'APPROVED' or 'SUBMITTED'. Used to style system messages from Old Dot */ isApprovedOrSubmittedReportAction: PropTypes.bool, @@ -73,9 +72,6 @@ const propTypes = { /** localization props */ ...withLocalizePropTypes, - - /** Should the comment have the appearance of being grouped with the previous comment? */ - displayAsGroup: PropTypes.bool, }; const defaultProps = { @@ -98,70 +94,39 @@ const defaultProps = { }; function ReportActionItemFragment(props) { - switch (props.fragment.type) { + const fragment = props.fragment; + + switch (fragment.type) { case 'COMMENT': { - const {html, text} = props.fragment; - const isPendingDelete = props.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && props.network.isOffline; + const isPendingDelete = props.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; // Threaded messages display "[Deleted message]" instead of being hidden altogether. // While offline we display the previous message with a strikethrough style. Once online we want to // immediately display "[Deleted message]" while the delete action is pending. - if ((!props.network.isOffline && props.isThreadParentMessage && props.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) || props.fragment.isDeletedParentAction) { + if ((!props.network.isOffline && props.isThreadParentMessage && isPendingDelete) || props.fragment.isDeletedParentAction) { return ${props.translate('parentReportAction.deletedMessage')}`} />; } - // If the only difference between fragment.text and fragment.html is
tags - // we render it as text, not as html. - // This is done to render emojis with line breaks between them as text. - const differByLineBreaksOnly = Str.replaceAll(html, '
', '\n') === text; - - // Only render HTML if we have html in the fragment - if (!differByLineBreaksOnly) { - const editedTag = props.fragment.isEdited ? `` : ''; - const htmlContent = isPendingDelete ? `${html}` : html; - - const htmlWithTag = editedTag ? `${htmlContent}${editedTag}` : htmlContent; - - return ${htmlWithTag}` : `${htmlWithTag}`} />; + if (ReportUtils.isReportMessageAttachment(fragment)) { + return ( + + ); } - const containsOnlyEmojis = EmojiUtils.containsOnlyEmojis(text); return ( - - - - {convertToLTR(props.iouMessage || text)} - - {Boolean(props.fragment.isEdited) && ( - <> - - {' '} - - - {props.translate('reportActionCompose.edited')} - - - )} - + ); } case 'TEXT': { @@ -182,7 +147,7 @@ function ReportActionItemFragment(props) { numberOfLines={props.isSingleLine ? 1 : undefined} style={[styles.chatItemMessageHeaderSender, props.isSingleLine ? styles.pre : styles.preWrap]} > - {props.fragment.text} + {fragment.text} ); diff --git a/src/pages/home/report/ReportActionItemMessage.js b/src/pages/home/report/ReportActionItemMessage.js index 4c6603c052a3..75e316342165 100644 --- a/src/pages/home/report/ReportActionItemMessage.js +++ b/src/pages/home/report/ReportActionItemMessage.js @@ -37,8 +37,7 @@ const defaultProps = { }; function ReportActionItemMessage(props) { - const messages = _.compact(props.action.previousMessage || props.action.message); - const isAttachment = ReportUtils.isReportMessageAttachment(_.last(messages)); + const fragments = _.compact(props.action.previousMessage || props.action.message); const isIOUReport = ReportActionsUtils.isMoneyRequestAction(props.action); let iouMessage; if (isIOUReport) { @@ -56,7 +55,7 @@ function ReportActionItemMessage(props) { * @returns {Object} report action item fragments */ const renderReportActionItemFragments = (shouldWrapInText) => { - const reportActionItemFragments = _.map(messages, (fragment, index) => ( + const reportActionItemFragments = _.map(fragments, (fragment, index) => ( + {!props.isHidden ? ( renderReportActionItemFragments(isApprovedOrSubmittedReportAction) ) : ( diff --git a/src/pages/home/report/comment/AttachmentCommentFragment.js b/src/pages/home/report/comment/AttachmentCommentFragment.js new file mode 100644 index 000000000000..8ee161600aee --- /dev/null +++ b/src/pages/home/report/comment/AttachmentCommentFragment.js @@ -0,0 +1,33 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import {View} from 'react-native'; +import reportActionSourcePropType from '@pages/home/report/reportActionSourcePropType'; +import styles from '@styles/styles'; +import RenderCommentHTML from './RenderCommentHTML'; + +const propTypes = { + /** The reportAction's source */ + source: reportActionSourcePropType.isRequired, + + /** The message fragment's HTML */ + html: PropTypes.string.isRequired, + + /** Should extra margin be added on top of the component? */ + addExtraMargin: PropTypes.bool.isRequired, +}; + +function AttachmentCommentFragment({addExtraMargin, html, source}) { + return ( + + + + ); +} + +AttachmentCommentFragment.propTypes = propTypes; +AttachmentCommentFragment.displayName = 'AttachmentCommentFragment'; + +export default AttachmentCommentFragment; diff --git a/src/pages/home/report/comment/RenderCommentHTML.js b/src/pages/home/report/comment/RenderCommentHTML.js new file mode 100644 index 000000000000..14039af21189 --- /dev/null +++ b/src/pages/home/report/comment/RenderCommentHTML.js @@ -0,0 +1,23 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import RenderHTML from '@components/RenderHTML'; +import reportActionSourcePropType from '@pages/home/report/reportActionSourcePropType'; + +const propTypes = { + /** The reportAction's source */ + source: reportActionSourcePropType.isRequired, + + /** The comment's HTML */ + html: PropTypes.string.isRequired, +}; + +function RenderCommentHTML({html, source}) { + const commentHtml = source === 'email' ? `${html}` : `${html}`; + + return ; +} + +RenderCommentHTML.propTypes = propTypes; +RenderCommentHTML.displayName = 'RenderCommentHTML'; + +export default RenderCommentHTML; diff --git a/src/pages/home/report/comment/TextCommentFragment.js b/src/pages/home/report/comment/TextCommentFragment.js new file mode 100644 index 000000000000..9dccf8de7f9d --- /dev/null +++ b/src/pages/home/report/comment/TextCommentFragment.js @@ -0,0 +1,118 @@ +import Str from 'expensify-common/lib/str'; +import PropTypes from 'prop-types'; +import React, {memo} from 'react'; +import Text from '@components/Text'; +import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withWindowDimensions'; +import ZeroWidthView from '@components/ZeroWidthView'; +import compose from '@libs/compose'; +import convertToLTR from '@libs/convertToLTR'; +import * as DeviceCapabilities from '@libs/DeviceCapabilities'; +import * as EmojiUtils from '@libs/EmojiUtils'; +import reportActionFragmentPropTypes from '@pages/home/report/reportActionFragmentPropTypes'; +import reportActionSourcePropType from '@pages/home/report/reportActionSourcePropType'; +import editedLabelStyles from '@styles/editedLabelStyles'; +import styles from '@styles/styles'; +import themeColors from '@styles/themes/default'; +import variables from '@styles/variables'; +import CONST from '@src/CONST'; +import RenderCommentHTML from './RenderCommentHTML'; + +const propTypes = { + /** The reportAction's source */ + source: reportActionSourcePropType.isRequired, + + /** The message fragment needing to be displayed */ + fragment: reportActionFragmentPropTypes.isRequired, + + /** Should this message fragment be styled as deleted? */ + styleAsDeleted: PropTypes.bool.isRequired, + + /** Text of an IOU report action */ + iouMessage: PropTypes.string, + + /** Should the comment have the appearance of being grouped with the previous comment? */ + displayAsGroup: PropTypes.bool.isRequired, + + /** Additional styles to add after local styles. */ + style: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]).isRequired, + + ...windowDimensionsPropTypes, + + /** localization props */ + ...withLocalizePropTypes, +}; + +const defaultProps = { + iouMessage: undefined, +}; + +function TextCommentFragment(props) { + const {fragment, styleAsDeleted} = props; + const {html, text} = fragment; + + // If the only difference between fragment.text and fragment.html is
tags + // we render it as text, not as html. + // This is done to render emojis with line breaks between them as text. + const differByLineBreaksOnly = Str.replaceAll(html, '
', '\n') === text; + + // Only render HTML if we have html in the fragment + if (!differByLineBreaksOnly) { + const editedTag = fragment.isEdited ? `` : ''; + const htmlContent = styleAsDeleted ? `${html}` : html; + + const htmlWithTag = editedTag ? `${htmlContent}${editedTag}` : htmlContent; + + return ( + + ); + } + + const containsOnlyEmojis = EmojiUtils.containsOnlyEmojis(text); + + return ( + + + + {convertToLTR(props.iouMessage || text)} + + {Boolean(fragment.isEdited) && ( + <> + + {' '} + + + {props.translate('reportActionCompose.edited')} + + + )} + + ); +} + +TextCommentFragment.propTypes = propTypes; +TextCommentFragment.defaultProps = defaultProps; +TextCommentFragment.displayName = 'TextCommentFragment'; + +export default compose(withWindowDimensions, withLocalize)(memo(TextCommentFragment)); diff --git a/src/pages/home/report/reportActionSourcePropType.js b/src/pages/home/report/reportActionSourcePropType.js new file mode 100644 index 000000000000..0ad9662eb693 --- /dev/null +++ b/src/pages/home/report/reportActionSourcePropType.js @@ -0,0 +1,3 @@ +import PropTypes from 'prop-types'; + +export default PropTypes.oneOf(['Chronos', 'email', 'ios', 'android', 'web', 'email', '']);