From a6799096a3328bc4e03e5f56e885b23acc704c69 Mon Sep 17 00:00:00 2001 From: Viacheslav Surzhikov Date: Thu, 17 Jun 2021 16:41:02 +0300 Subject: [PATCH 1/9] fix contextMenu on Mobile --- .../{RenderHTML.js => RenderHTML/index.js} | 39 ++- src/components/RenderHTML/index.native.js | 226 ++++++++++++++++++ .../RenderHTML/renderHTMLPropTypes.js | 9 + .../home/report/ReportActionItemFragment.js | 2 +- 4 files changed, 253 insertions(+), 23 deletions(-) rename src/components/{RenderHTML.js => RenderHTML/index.js} (91%) create mode 100755 src/components/RenderHTML/index.native.js create mode 100644 src/components/RenderHTML/renderHTMLPropTypes.js diff --git a/src/components/RenderHTML.js b/src/components/RenderHTML/index.js similarity index 91% rename from src/components/RenderHTML.js rename to src/components/RenderHTML/index.js index 7cceffca2031..b25cfe1e6560 100755 --- a/src/components/RenderHTML.js +++ b/src/components/RenderHTML/index.js @@ -1,23 +1,27 @@ /* eslint-disable react/prop-types */ import _ from 'underscore'; import React from 'react'; -import PropTypes from 'prop-types'; import {useWindowDimensions, TouchableOpacity} from 'react-native'; import HTML, { defaultHTMLElementModels, TNodeChildrenRenderer, splitBoxModelStyle, } from 'react-native-render-html'; -import Config from '../CONFIG'; -import styles, {webViewStyles, getFontFamilyMonospace} from '../styles/styles'; -import fontFamily from '../styles/fontFamily'; -import AnchorForCommentsOnly from './AnchorForCommentsOnly'; -import InlineCodeBlock from './InlineCodeBlock'; -import AttachmentModal from './AttachmentModal'; -import ThumbnailImage from './ThumbnailImage'; -import variables from '../styles/variables'; -import themeColors from '../styles/themes/default'; -import Text from './Text'; +import Config from '../../CONFIG'; +import styles, {webViewStyles, getFontFamilyMonospace} from '../../styles/styles'; +import fontFamily from '../../styles/fontFamily'; +import AnchorForCommentsOnly from '../AnchorForCommentsOnly'; +import InlineCodeBlock from '../InlineCodeBlock'; +import AttachmentModal from '../AttachmentModal'; +import ThumbnailImage from '../ThumbnailImage'; +import variables from '../../styles/variables'; +import themeColors from '../../styles/themes/default'; +import Text from '../Text'; +import renderHTMLpropTypes from './renderHTMLPropTypes'; + +const defaultProps = { + debug: false, +}; const MAX_IMG_DIMENSIONS = 512; @@ -192,14 +196,6 @@ const renderers = { edited: EditedRenderer, }; -const propTypes = { - /** HTML string to render */ - html: PropTypes.string.isRequired, - - /** Optional debug flag */ - debug: PropTypes.bool, -}; - const RenderHTML = ({html, debug = false}) => { const {width} = useWindowDimensions(); const containerWidth = width * 0.8; @@ -224,9 +220,8 @@ const RenderHTML = ({html, debug = false}) => { }; RenderHTML.displayName = 'RenderHTML'; -RenderHTML.propTypes = propTypes; -RenderHTML.defaultProps = { - debug: false, +RenderHTML.propTypes = renderHTMLpropTypes; +RenderHTML.defaultProps = defaultProps; }; export default RenderHTML; diff --git a/src/components/RenderHTML/index.native.js b/src/components/RenderHTML/index.native.js new file mode 100755 index 000000000000..adc6e46bc4d5 --- /dev/null +++ b/src/components/RenderHTML/index.native.js @@ -0,0 +1,226 @@ +/* eslint-disable react/prop-types */ +import _ from 'underscore'; +import React from 'react'; +import {useWindowDimensions, TouchableOpacity} from 'react-native'; +import HTML, { + defaultHTMLElementModels, + TNodeChildrenRenderer, + splitBoxModelStyle, +} from 'react-native-render-html'; +import Config from '../../CONFIG'; +import styles, {webViewStyles, getFontFamilyMonospace} from '../../styles/styles'; +import fontFamily from '../../styles/fontFamily'; +import AnchorForCommentsOnly from '../AnchorForCommentsOnly'; +import InlineCodeBlock from '../InlineCodeBlock'; +import AttachmentModal from '../AttachmentModal'; +import ThumbnailImage from '../ThumbnailImage'; +import variables from '../../styles/variables'; +import themeColors from '../../styles/themes/default'; +import Text from '../Text'; +import renderHTMLpropTypes from './renderHTMLPropTypes'; + +const defaultProps = { + debug: false, +}; + +const MAX_IMG_DIMENSIONS = 512; + +const EXTRA_FONTS = [ + fontFamily.GTA, + fontFamily.GTA_BOLD, + fontFamily.GTA_ITALIC, + fontFamily.MONOSPACE, + fontFamily.MONOSPACE_ITALIC, + fontFamily.MONOSPACE_BOLD, + fontFamily.MONOSPACE_BOLD_ITALIC, + fontFamily.SYSTEM, +]; + +/** + * Compute images maximum width from the available screen width. This function + * is used by the HTML component in the default renderer for img tags to scale + * down images that would otherwise overflow horizontally. + * + * @param {number} contentWidth - The content width provided to the HTML + * component. + * @returns {number} The minimum between contentWidth and MAX_IMG_DIMENSIONS + */ +function computeImagesMaxWidth(contentWidth) { + return Math.min(MAX_IMG_DIMENSIONS, contentWidth); +} + +function AnchorRenderer({tnode, key, style}) { + const htmlAttribs = tnode.attributes; + + // An auth token is needed to download Expensify chat attachments + const isAttachment = Boolean(htmlAttribs['data-expensify-source']); + return ( + + + + ); +} + +function CodeRenderer({ + key, style, TDefaultRenderer, ...defaultRendererProps +}) { + // We split wrapper and inner styles + // "boxModelStyle" corresponds to border, margin, padding and backgroundColor + const {boxModelStyle, otherStyle: textStyle} = splitBoxModelStyle(style); + + // Get the correct fontFamily variant based in the fontStyle and fontWeight + const font = getFontFamilyMonospace({ + fontStyle: textStyle.fontStyle, + fontWeight: textStyle.fontWeight, + }); + + const textStyleOverride = { + fontFamily: font, + + // We need to override this properties bellow that was defined in `textStyle` + // Because by default the `react-native-render-html` add a style in the elements, + // for example the tag has a fontWeight: "bold" and in the android it break the font + fontWeight: undefined, + fontStyle: undefined, + }; + + return ( + + ); +} + +function EditedRenderer(props) { + const defaultRendererProps = _.omit(props, ['TDefaultRenderer', 'style', 'tnode']); + return ( + + {/* Native devices do not support margin between nested text */} + {' '} + (edited) + + ); +} + +function ImgRenderer({tnode}) { + const htmlAttribs = tnode.attributes; + + // There are two kinds of images that need to be displayed: + // + // - Chat Attachment images + // + // Images uploaded by the user via the app or email. + // These have a full-sized image `htmlAttribs['data-expensify-source']` + // and a thumbnail `htmlAttribs.src`. Both of these URLs need to have + // an authToken added to them in order to control who + // can see the images. + // + // - Non-Attachment Images + // + // These could be hosted from anywhere (Expensify or another source) + // and are not protected by any kind of access control e.g. certain + // Concierge responder attachments are uploaded to S3 without any access + // control and thus require no authToken to verify access. + // + const isAttachment = Boolean(htmlAttribs['data-expensify-source']); + let previewSource = htmlAttribs.src; + let source = isAttachment + ? htmlAttribs['data-expensify-source'] + : htmlAttribs.src; + + // Update the image URL so the images can be accessed depending on the config environment + previewSource = previewSource.replace( + Config.EXPENSIFY.URL_EXPENSIFY_COM, + Config.EXPENSIFY.URL_API_ROOT, + ); + source = source.replace( + Config.EXPENSIFY.URL_EXPENSIFY_COM, + Config.EXPENSIFY.URL_API_ROOT, + ); + + return ( + + {({show}) => ( + show()} + > + + + )} + + ); +} + +// Define default element models for these renderers. +AnchorRenderer.model = defaultHTMLElementModels.a; +CodeRenderer.model = defaultHTMLElementModels.code; +ImgRenderer.model = defaultHTMLElementModels.img; +EditedRenderer.model = defaultHTMLElementModels.span; + +// Define the custom render methods +const renderers = { + a: AnchorRenderer, + code: CodeRenderer, + img: ImgRenderer, + edited: EditedRenderer, +}; + +const RenderHTML = ({html, debug = false}) => { + const {width} = useWindowDimensions(); + const containerWidth = width * 0.8; + return ( + + ); +}; + +RenderHTML.displayName = 'RenderHTML'; +RenderHTML.propTypes = renderHTMLpropTypes; +RenderHTML.defaultProps = defaultProps; + + +export default RenderHTML; diff --git a/src/components/RenderHTML/renderHTMLPropTypes.js b/src/components/RenderHTML/renderHTMLPropTypes.js new file mode 100644 index 000000000000..2d792445b3c4 --- /dev/null +++ b/src/components/RenderHTML/renderHTMLPropTypes.js @@ -0,0 +1,9 @@ +import PropTypes from 'prop-types'; + +export default { + /** HTML string to render */ + html: PropTypes.string.isRequired, + + /** Optional debug flag */ + debug: PropTypes.bool, +}; diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js index 165b4b649e61..cddbcb7684cf 100644 --- a/src/pages/home/report/ReportActionItemFragment.js +++ b/src/pages/home/report/ReportActionItemFragment.js @@ -56,7 +56,7 @@ class ReportActionItemFragment extends React.PureComponent { debug={false} /> ) : ( - + {Str.htmlDecode(fragment.text)} {fragment.isEdited && ( Date: Thu, 17 Jun 2021 16:49:30 +0300 Subject: [PATCH 2/9] fix eslint --- src/components/RenderHTML/index.js | 3 +-- src/components/RenderHTML/index.native.js | 2 +- src/pages/home/report/ReportActionItemFragment.js | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/components/RenderHTML/index.js b/src/components/RenderHTML/index.js index b25cfe1e6560..5db051e11680 100755 --- a/src/components/RenderHTML/index.js +++ b/src/components/RenderHTML/index.js @@ -17,7 +17,7 @@ import ThumbnailImage from '../ThumbnailImage'; import variables from '../../styles/variables'; import themeColors from '../../styles/themes/default'; import Text from '../Text'; -import renderHTMLpropTypes from './renderHTMLPropTypes'; +import renderHTMLpropTypes from './renderHTMLPropTypes'; const defaultProps = { debug: false, @@ -222,6 +222,5 @@ const RenderHTML = ({html, debug = false}) => { RenderHTML.displayName = 'RenderHTML'; RenderHTML.propTypes = renderHTMLpropTypes; RenderHTML.defaultProps = defaultProps; -}; export default RenderHTML; diff --git a/src/components/RenderHTML/index.native.js b/src/components/RenderHTML/index.native.js index adc6e46bc4d5..aa9e03bb1acd 100755 --- a/src/components/RenderHTML/index.native.js +++ b/src/components/RenderHTML/index.native.js @@ -17,7 +17,7 @@ import ThumbnailImage from '../ThumbnailImage'; import variables from '../../styles/variables'; import themeColors from '../../styles/themes/default'; import Text from '../Text'; -import renderHTMLpropTypes from './renderHTMLPropTypes'; +import renderHTMLpropTypes from './renderHTMLPropTypes'; const defaultProps = { debug: false, diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js index cddbcb7684cf..42bd1f2f28bb 100644 --- a/src/pages/home/report/ReportActionItemFragment.js +++ b/src/pages/home/report/ReportActionItemFragment.js @@ -56,7 +56,7 @@ class ReportActionItemFragment extends React.PureComponent { debug={false} /> ) : ( - + {Str.htmlDecode(fragment.text)} {fragment.isEdited && ( Date: Tue, 22 Jun 2021 23:04:51 +0300 Subject: [PATCH 3/9] add BaseRenderHTML --- src/components/RenderHTML/BaseRenderHTML.js | 226 +++++++++++++++++++ src/components/RenderHTML/index.js | 228 +------------------- src/components/RenderHTML/index.native.js | 228 +------------------- 3 files changed, 241 insertions(+), 441 deletions(-) create mode 100755 src/components/RenderHTML/BaseRenderHTML.js diff --git a/src/components/RenderHTML/BaseRenderHTML.js b/src/components/RenderHTML/BaseRenderHTML.js new file mode 100755 index 000000000000..2dadfd26bd90 --- /dev/null +++ b/src/components/RenderHTML/BaseRenderHTML.js @@ -0,0 +1,226 @@ +/* eslint-disable react/prop-types */ +import _ from 'underscore'; +import React from 'react'; +import {useWindowDimensions, TouchableOpacity} from 'react-native'; +import HTML, { + defaultHTMLElementModels, + TNodeChildrenRenderer, + splitBoxModelStyle, +} from 'react-native-render-html'; +import Config from '../../CONFIG'; +import styles, {webViewStyles, getFontFamilyMonospace} from '../../styles/styles'; +import fontFamily from '../../styles/fontFamily'; +import AnchorForCommentsOnly from '../AnchorForCommentsOnly'; +import InlineCodeBlock from '../InlineCodeBlock'; +import AttachmentModal from '../AttachmentModal'; +import ThumbnailImage from '../ThumbnailImage'; +import variables from '../../styles/variables'; +import themeColors from '../../styles/themes/default'; +import Text from '../Text'; +import renderHTMLpropTypes from './renderHTMLPropTypes'; + +const defaultProps = { + debug: false, +}; + +const MAX_IMG_DIMENSIONS = 512; + +const EXTRA_FONTS = [ + fontFamily.GTA, + fontFamily.GTA_BOLD, + fontFamily.GTA_ITALIC, + fontFamily.MONOSPACE, + fontFamily.MONOSPACE_ITALIC, + fontFamily.MONOSPACE_BOLD, + fontFamily.MONOSPACE_BOLD_ITALIC, + fontFamily.SYSTEM, +]; + +/** + * Compute images maximum width from the available screen width. This function + * is used by the HTML component in the default renderer for img tags to scale + * down images that would otherwise overflow horizontally. + * + * @param {number} contentWidth - The content width provided to the HTML + * component. + * @returns {number} The minimum between contentWidth and MAX_IMG_DIMENSIONS + */ +function computeImagesMaxWidth(contentWidth) { + return Math.min(MAX_IMG_DIMENSIONS, contentWidth); +} + +function AnchorRenderer({tnode, key, style}) { + const htmlAttribs = tnode.attributes; + + // An auth token is needed to download Expensify chat attachments + const isAttachment = Boolean(htmlAttribs['data-expensify-source']); + return ( + + + + ); +} + +function CodeRenderer({ + key, style, TDefaultRenderer, ...defaultRendererProps +}) { + // We split wrapper and inner styles + // "boxModelStyle" corresponds to border, margin, padding and backgroundColor + const {boxModelStyle, otherStyle: textStyle} = splitBoxModelStyle(style); + + // Get the correct fontFamily variant based in the fontStyle and fontWeight + const font = getFontFamilyMonospace({ + fontStyle: textStyle.fontStyle, + fontWeight: textStyle.fontWeight, + }); + + const textStyleOverride = { + fontFamily: font, + + // We need to override this properties bellow that was defined in `textStyle` + // Because by default the `react-native-render-html` add a style in the elements, + // for example the tag has a fontWeight: "bold" and in the android it break the font + fontWeight: undefined, + fontStyle: undefined, + }; + + return ( + + ); +} + +function EditedRenderer(props) { + const defaultRendererProps = _.omit(props, ['TDefaultRenderer', 'style', 'tnode']); + return ( + + {/* Native devices do not support margin between nested text */} + {' '} + (edited) + + ); +} + +function ImgRenderer({tnode}) { + const htmlAttribs = tnode.attributes; + + // There are two kinds of images that need to be displayed: + // + // - Chat Attachment images + // + // Images uploaded by the user via the app or email. + // These have a full-sized image `htmlAttribs['data-expensify-source']` + // and a thumbnail `htmlAttribs.src`. Both of these URLs need to have + // an authToken added to them in order to control who + // can see the images. + // + // - Non-Attachment Images + // + // These could be hosted from anywhere (Expensify or another source) + // and are not protected by any kind of access control e.g. certain + // Concierge responder attachments are uploaded to S3 without any access + // control and thus require no authToken to verify access. + // + const isAttachment = Boolean(htmlAttribs['data-expensify-source']); + let previewSource = htmlAttribs.src; + let source = isAttachment + ? htmlAttribs['data-expensify-source'] + : htmlAttribs.src; + + // Update the image URL so the images can be accessed depending on the config environment + previewSource = previewSource.replace( + Config.EXPENSIFY.URL_EXPENSIFY_COM, + Config.EXPENSIFY.URL_API_ROOT, + ); + source = source.replace( + Config.EXPENSIFY.URL_EXPENSIFY_COM, + Config.EXPENSIFY.URL_API_ROOT, + ); + + return ( + + {({show}) => ( + show()} + > + + + )} + + ); +} + +// Define default element models for these renderers. +AnchorRenderer.model = defaultHTMLElementModels.a; +CodeRenderer.model = defaultHTMLElementModels.code; +ImgRenderer.model = defaultHTMLElementModels.img; +EditedRenderer.model = defaultHTMLElementModels.span; + +// Define the custom render methods +const renderers = { + a: AnchorRenderer, + code: CodeRenderer, + img: ImgRenderer, + edited: EditedRenderer, +}; + +const RenderHTML = ({html, debug = false, selectable = false}) => { + const {width} = useWindowDimensions(); + const containerWidth = width * 0.8; + return ( + + ); +}; + +RenderHTML.displayName = 'RenderHTML'; +RenderHTML.propTypes = renderHTMLpropTypes; +RenderHTML.defaultProps = defaultProps; + +export default RenderHTML; diff --git a/src/components/RenderHTML/index.js b/src/components/RenderHTML/index.js index 5db051e11680..16902fcf6ed5 100755 --- a/src/components/RenderHTML/index.js +++ b/src/components/RenderHTML/index.js @@ -1,226 +1,14 @@ /* eslint-disable react/prop-types */ -import _ from 'underscore'; +/* eslint-disable react/jsx-props-no-spreading */ import React from 'react'; -import {useWindowDimensions, TouchableOpacity} from 'react-native'; -import HTML, { - defaultHTMLElementModels, - TNodeChildrenRenderer, - splitBoxModelStyle, -} from 'react-native-render-html'; -import Config from '../../CONFIG'; -import styles, {webViewStyles, getFontFamilyMonospace} from '../../styles/styles'; -import fontFamily from '../../styles/fontFamily'; -import AnchorForCommentsOnly from '../AnchorForCommentsOnly'; -import InlineCodeBlock from '../InlineCodeBlock'; -import AttachmentModal from '../AttachmentModal'; -import ThumbnailImage from '../ThumbnailImage'; -import variables from '../../styles/variables'; -import themeColors from '../../styles/themes/default'; -import Text from '../Text'; -import renderHTMLpropTypes from './renderHTMLPropTypes'; +import HTML from './BaseRenderHTML'; -const defaultProps = { - debug: false, -}; +const RenderHTML = props => ( + +); -const MAX_IMG_DIMENSIONS = 512; - -const EXTRA_FONTS = [ - fontFamily.GTA, - fontFamily.GTA_BOLD, - fontFamily.GTA_ITALIC, - fontFamily.MONOSPACE, - fontFamily.MONOSPACE_ITALIC, - fontFamily.MONOSPACE_BOLD, - fontFamily.MONOSPACE_BOLD_ITALIC, - fontFamily.SYSTEM, -]; - -/** - * Compute images maximum width from the available screen width. This function - * is used by the HTML component in the default renderer for img tags to scale - * down images that would otherwise overflow horizontally. - * - * @param {number} contentWidth - The content width provided to the HTML - * component. - * @returns {number} The minimum between contentWidth and MAX_IMG_DIMENSIONS - */ -function computeImagesMaxWidth(contentWidth) { - return Math.min(MAX_IMG_DIMENSIONS, contentWidth); -} - -function AnchorRenderer({tnode, key, style}) { - const htmlAttribs = tnode.attributes; - - // An auth token is needed to download Expensify chat attachments - const isAttachment = Boolean(htmlAttribs['data-expensify-source']); - return ( - - - - ); -} - -function CodeRenderer({ - key, style, TDefaultRenderer, ...defaultRendererProps -}) { - // We split wrapper and inner styles - // "boxModelStyle" corresponds to border, margin, padding and backgroundColor - const {boxModelStyle, otherStyle: textStyle} = splitBoxModelStyle(style); - - // Get the correct fontFamily variant based in the fontStyle and fontWeight - const font = getFontFamilyMonospace({ - fontStyle: textStyle.fontStyle, - fontWeight: textStyle.fontWeight, - }); - - const textStyleOverride = { - fontFamily: font, - - // We need to override this properties bellow that was defined in `textStyle` - // Because by default the `react-native-render-html` add a style in the elements, - // for example the tag has a fontWeight: "bold" and in the android it break the font - fontWeight: undefined, - fontStyle: undefined, - }; - - return ( - - ); -} - -function EditedRenderer(props) { - const defaultRendererProps = _.omit(props, ['TDefaultRenderer', 'style', 'tnode']); - return ( - - {/* Native devices do not support margin between nested text */} - {' '} - (edited) - - ); -} - -function ImgRenderer({tnode}) { - const htmlAttribs = tnode.attributes; - - // There are two kinds of images that need to be displayed: - // - // - Chat Attachment images - // - // Images uploaded by the user via the app or email. - // These have a full-sized image `htmlAttribs['data-expensify-source']` - // and a thumbnail `htmlAttribs.src`. Both of these URLs need to have - // an authToken added to them in order to control who - // can see the images. - // - // - Non-Attachment Images - // - // These could be hosted from anywhere (Expensify or another source) - // and are not protected by any kind of access control e.g. certain - // Concierge responder attachments are uploaded to S3 without any access - // control and thus require no authToken to verify access. - // - const isAttachment = Boolean(htmlAttribs['data-expensify-source']); - let previewSource = htmlAttribs.src; - let source = isAttachment - ? htmlAttribs['data-expensify-source'] - : htmlAttribs.src; - - // Update the image URL so the images can be accessed depending on the config environment - previewSource = previewSource.replace( - Config.EXPENSIFY.URL_EXPENSIFY_COM, - Config.EXPENSIFY.URL_API_ROOT, - ); - source = source.replace( - Config.EXPENSIFY.URL_EXPENSIFY_COM, - Config.EXPENSIFY.URL_API_ROOT, - ); - - return ( - - {({show}) => ( - show()} - > - - - )} - - ); -} - -// Define default element models for these renderers. -AnchorRenderer.model = defaultHTMLElementModels.a; -CodeRenderer.model = defaultHTMLElementModels.code; -ImgRenderer.model = defaultHTMLElementModels.img; -EditedRenderer.model = defaultHTMLElementModels.span; - -// Define the custom render methods -const renderers = { - a: AnchorRenderer, - code: CodeRenderer, - img: ImgRenderer, - edited: EditedRenderer, -}; - -const RenderHTML = ({html, debug = false}) => { - const {width} = useWindowDimensions(); - const containerWidth = width * 0.8; - return ( - - ); -}; - -RenderHTML.displayName = 'RenderHTML'; -RenderHTML.propTypes = renderHTMLpropTypes; -RenderHTML.defaultProps = defaultProps; export default RenderHTML; diff --git a/src/components/RenderHTML/index.native.js b/src/components/RenderHTML/index.native.js index aa9e03bb1acd..796f828cdffb 100755 --- a/src/components/RenderHTML/index.native.js +++ b/src/components/RenderHTML/index.native.js @@ -1,226 +1,12 @@ /* eslint-disable react/prop-types */ -import _ from 'underscore'; +/* eslint-disable react/jsx-props-no-spreading */ import React from 'react'; -import {useWindowDimensions, TouchableOpacity} from 'react-native'; -import HTML, { - defaultHTMLElementModels, - TNodeChildrenRenderer, - splitBoxModelStyle, -} from 'react-native-render-html'; -import Config from '../../CONFIG'; -import styles, {webViewStyles, getFontFamilyMonospace} from '../../styles/styles'; -import fontFamily from '../../styles/fontFamily'; -import AnchorForCommentsOnly from '../AnchorForCommentsOnly'; -import InlineCodeBlock from '../InlineCodeBlock'; -import AttachmentModal from '../AttachmentModal'; -import ThumbnailImage from '../ThumbnailImage'; -import variables from '../../styles/variables'; -import themeColors from '../../styles/themes/default'; -import Text from '../Text'; -import renderHTMLpropTypes from './renderHTMLPropTypes'; - -const defaultProps = { - debug: false, -}; - -const MAX_IMG_DIMENSIONS = 512; - -const EXTRA_FONTS = [ - fontFamily.GTA, - fontFamily.GTA_BOLD, - fontFamily.GTA_ITALIC, - fontFamily.MONOSPACE, - fontFamily.MONOSPACE_ITALIC, - fontFamily.MONOSPACE_BOLD, - fontFamily.MONOSPACE_BOLD_ITALIC, - fontFamily.SYSTEM, -]; - -/** - * Compute images maximum width from the available screen width. This function - * is used by the HTML component in the default renderer for img tags to scale - * down images that would otherwise overflow horizontally. - * - * @param {number} contentWidth - The content width provided to the HTML - * component. - * @returns {number} The minimum between contentWidth and MAX_IMG_DIMENSIONS - */ -function computeImagesMaxWidth(contentWidth) { - return Math.min(MAX_IMG_DIMENSIONS, contentWidth); -} - -function AnchorRenderer({tnode, key, style}) { - const htmlAttribs = tnode.attributes; - - // An auth token is needed to download Expensify chat attachments - const isAttachment = Boolean(htmlAttribs['data-expensify-source']); - return ( - - - - ); -} - -function CodeRenderer({ - key, style, TDefaultRenderer, ...defaultRendererProps -}) { - // We split wrapper and inner styles - // "boxModelStyle" corresponds to border, margin, padding and backgroundColor - const {boxModelStyle, otherStyle: textStyle} = splitBoxModelStyle(style); - - // Get the correct fontFamily variant based in the fontStyle and fontWeight - const font = getFontFamilyMonospace({ - fontStyle: textStyle.fontStyle, - fontWeight: textStyle.fontWeight, - }); - - const textStyleOverride = { - fontFamily: font, - - // We need to override this properties bellow that was defined in `textStyle` - // Because by default the `react-native-render-html` add a style in the elements, - // for example the tag has a fontWeight: "bold" and in the android it break the font - fontWeight: undefined, - fontStyle: undefined, - }; - - return ( - - ); -} - -function EditedRenderer(props) { - const defaultRendererProps = _.omit(props, ['TDefaultRenderer', 'style', 'tnode']); - return ( - - {/* Native devices do not support margin between nested text */} - {' '} - (edited) - - ); -} - -function ImgRenderer({tnode}) { - const htmlAttribs = tnode.attributes; - - // There are two kinds of images that need to be displayed: - // - // - Chat Attachment images - // - // Images uploaded by the user via the app or email. - // These have a full-sized image `htmlAttribs['data-expensify-source']` - // and a thumbnail `htmlAttribs.src`. Both of these URLs need to have - // an authToken added to them in order to control who - // can see the images. - // - // - Non-Attachment Images - // - // These could be hosted from anywhere (Expensify or another source) - // and are not protected by any kind of access control e.g. certain - // Concierge responder attachments are uploaded to S3 without any access - // control and thus require no authToken to verify access. - // - const isAttachment = Boolean(htmlAttribs['data-expensify-source']); - let previewSource = htmlAttribs.src; - let source = isAttachment - ? htmlAttribs['data-expensify-source'] - : htmlAttribs.src; - - // Update the image URL so the images can be accessed depending on the config environment - previewSource = previewSource.replace( - Config.EXPENSIFY.URL_EXPENSIFY_COM, - Config.EXPENSIFY.URL_API_ROOT, - ); - source = source.replace( - Config.EXPENSIFY.URL_EXPENSIFY_COM, - Config.EXPENSIFY.URL_API_ROOT, - ); - - return ( - - {({show}) => ( - show()} - > - - - )} - - ); -} - -// Define default element models for these renderers. -AnchorRenderer.model = defaultHTMLElementModels.a; -CodeRenderer.model = defaultHTMLElementModels.code; -ImgRenderer.model = defaultHTMLElementModels.img; -EditedRenderer.model = defaultHTMLElementModels.span; - -// Define the custom render methods -const renderers = { - a: AnchorRenderer, - code: CodeRenderer, - img: ImgRenderer, - edited: EditedRenderer, -}; - -const RenderHTML = ({html, debug = false}) => { - const {width} = useWindowDimensions(); - const containerWidth = width * 0.8; - return ( - - ); -}; - -RenderHTML.displayName = 'RenderHTML'; -RenderHTML.propTypes = renderHTMLpropTypes; -RenderHTML.defaultProps = defaultProps; +import HTML from './BaseRenderHTML'; +const RenderHTML = props => ( + +); export default RenderHTML; From f0cca6c4075cfd56c2dda13d1f513cd410e9f96b Mon Sep 17 00:00:00 2001 From: Viacheslav Surzhikov Date: Thu, 24 Jun 2021 11:46:38 +0300 Subject: [PATCH 4/9] defaultProp textSelectable --- src/components/RenderHTML/BaseRenderHTML.js | 5 +++-- src/components/RenderHTML/index.js | 2 +- src/components/RenderHTML/renderHTMLPropTypes.js | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/RenderHTML/BaseRenderHTML.js b/src/components/RenderHTML/BaseRenderHTML.js index 2dadfd26bd90..6c4cfeada0aa 100755 --- a/src/components/RenderHTML/BaseRenderHTML.js +++ b/src/components/RenderHTML/BaseRenderHTML.js @@ -21,6 +21,7 @@ import renderHTMLpropTypes from './renderHTMLPropTypes'; const defaultProps = { debug: false, + textSelectable: false, }; const MAX_IMG_DIMENSIONS = 512; @@ -196,12 +197,12 @@ const renderers = { edited: EditedRenderer, }; -const RenderHTML = ({html, debug = false, selectable = false}) => { +const RenderHTML = ({html, debug, textSelectable}) => { const {width} = useWindowDimensions(); const containerWidth = width * 0.8; return ( ( ); diff --git a/src/components/RenderHTML/renderHTMLPropTypes.js b/src/components/RenderHTML/renderHTMLPropTypes.js index 2d792445b3c4..cd7a794197d7 100644 --- a/src/components/RenderHTML/renderHTMLPropTypes.js +++ b/src/components/RenderHTML/renderHTMLPropTypes.js @@ -6,4 +6,5 @@ export default { /** Optional debug flag */ debug: PropTypes.bool, + textSelectable: PropTypes.bool, }; From e2fc7ec8eab854f078e34536c7e73554fe3fd52f Mon Sep 17 00:00:00 2001 From: Viacheslav Surzhikov Date: Thu, 24 Jun 2021 13:41:57 +0300 Subject: [PATCH 5/9] fix errors --- src/components/RenderHTML/BaseRenderHTML.js | 24 +++++++++++++------ src/components/RenderHTML/index.js | 19 ++++++++++----- src/components/RenderHTML/index.native.js | 19 ++++++++++----- .../RenderHTML/renderHTMLPropTypes.js | 9 +++++-- 4 files changed, 50 insertions(+), 21 deletions(-) diff --git a/src/components/RenderHTML/BaseRenderHTML.js b/src/components/RenderHTML/BaseRenderHTML.js index 6c4cfeada0aa..2b17d6b16c82 100755 --- a/src/components/RenderHTML/BaseRenderHTML.js +++ b/src/components/RenderHTML/BaseRenderHTML.js @@ -7,6 +7,7 @@ import HTML, { TNodeChildrenRenderer, splitBoxModelStyle, } from 'react-native-render-html'; +import PropTypes from 'prop-types'; import Config from '../../CONFIG'; import styles, {webViewStyles, getFontFamilyMonospace} from '../../styles/styles'; import fontFamily from '../../styles/fontFamily'; @@ -17,11 +18,20 @@ import ThumbnailImage from '../ThumbnailImage'; import variables from '../../styles/variables'; import themeColors from '../../styles/themes/default'; import Text from '../Text'; -import renderHTMLpropTypes from './renderHTMLPropTypes'; +import { + propTypes as renderHTMLPropTypes, + defaultProps as renderHTMLDefaultProps, +} from './renderHTMLPropTypes'; + +const propTypes = { + // Whether text elements should be selectable + textSelectable: PropTypes.bool, + ...renderHTMLPropTypes, +}; const defaultProps = { - debug: false, textSelectable: false, + ...renderHTMLDefaultProps, }; const MAX_IMG_DIMENSIONS = 512; @@ -197,7 +207,7 @@ const renderers = { edited: EditedRenderer, }; -const RenderHTML = ({html, debug, textSelectable}) => { +const BaseRenderHTML = ({html, debug, textSelectable}) => { const {width} = useWindowDimensions(); const containerWidth = width * 0.8; return ( @@ -220,8 +230,8 @@ const RenderHTML = ({html, debug, textSelectable}) => { ); }; -RenderHTML.displayName = 'RenderHTML'; -RenderHTML.propTypes = renderHTMLpropTypes; -RenderHTML.defaultProps = defaultProps; +BaseRenderHTML.displayName = 'BaseRenderHTML'; +BaseRenderHTML.propTypes = propTypes; +BaseRenderHTML.defaultProps = defaultProps; -export default RenderHTML; +export default BaseRenderHTML; diff --git a/src/components/RenderHTML/index.js b/src/components/RenderHTML/index.js index 61dc63ccc1b9..e060db8cfb50 100755 --- a/src/components/RenderHTML/index.js +++ b/src/components/RenderHTML/index.js @@ -1,14 +1,21 @@ -/* eslint-disable react/prop-types */ /* eslint-disable react/jsx-props-no-spreading */ import React from 'react'; -import HTML from './BaseRenderHTML'; +import BaseRenderHTML from './BaseRenderHTML'; +import { + propTypes, + defaultProps, +} from './renderHTMLPropTypes'; -const RenderHTML = props => ( - ( + ); +RenderHTML.displayName = 'RenderHTML'; +RenderHTML.propTypes = propTypes; +RenderHTML.defaultProps = defaultProps; export default RenderHTML; diff --git a/src/components/RenderHTML/index.native.js b/src/components/RenderHTML/index.native.js index 796f828cdffb..4fada63451aa 100755 --- a/src/components/RenderHTML/index.native.js +++ b/src/components/RenderHTML/index.native.js @@ -1,12 +1,19 @@ -/* eslint-disable react/prop-types */ -/* eslint-disable react/jsx-props-no-spreading */ import React from 'react'; -import HTML from './BaseRenderHTML'; +import BaseRenderHTML from './BaseRenderHTML'; +import { + propTypes, + defaultProps, +} from './renderHTMLPropTypes'; -const RenderHTML = props => ( - ( + ); +RenderHTML.displayName = 'RenderHTML'; +RenderHTML.propTypes = propTypes; +RenderHTML.defaultProps = defaultProps; + export default RenderHTML; diff --git a/src/components/RenderHTML/renderHTMLPropTypes.js b/src/components/RenderHTML/renderHTMLPropTypes.js index cd7a794197d7..58032059e4b9 100644 --- a/src/components/RenderHTML/renderHTMLPropTypes.js +++ b/src/components/RenderHTML/renderHTMLPropTypes.js @@ -1,10 +1,15 @@ import PropTypes from 'prop-types'; -export default { +const propTypes = { /** HTML string to render */ html: PropTypes.string.isRequired, /** Optional debug flag */ debug: PropTypes.bool, - textSelectable: PropTypes.bool, }; + +const defaultProps = { + debug: false, +}; + +export {propTypes, defaultProps}; From 69722febd5feb99564dc3c79f311da8387bf7cfa Mon Sep 17 00:00:00 2001 From: Viacheslav Surzhikov Date: Fri, 25 Jun 2021 08:40:00 +0300 Subject: [PATCH 6/9] fix comments --- src/components/RenderHTML/BaseRenderHTML.js | 2 +- src/components/RenderHTML/index.js | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/RenderHTML/BaseRenderHTML.js b/src/components/RenderHTML/BaseRenderHTML.js index 2b17d6b16c82..584dedf60d03 100755 --- a/src/components/RenderHTML/BaseRenderHTML.js +++ b/src/components/RenderHTML/BaseRenderHTML.js @@ -24,7 +24,7 @@ import { } from './renderHTMLPropTypes'; const propTypes = { - // Whether text elements should be selectable + /** Whether text elements should be selectable */ textSelectable: PropTypes.bool, ...renderHTMLPropTypes, }; diff --git a/src/components/RenderHTML/index.js b/src/components/RenderHTML/index.js index e060db8cfb50..0f115512eba7 100755 --- a/src/components/RenderHTML/index.js +++ b/src/components/RenderHTML/index.js @@ -1,4 +1,3 @@ -/* eslint-disable react/jsx-props-no-spreading */ import React from 'react'; import BaseRenderHTML from './BaseRenderHTML'; import { From 1111fa33a68c02ebcd0f891b0da669ea11c1071c Mon Sep 17 00:00:00 2001 From: Viacheslav Surzhikov Date: Sun, 27 Jun 2021 17:14:28 +0300 Subject: [PATCH 7/9] add isSmallScreenWidth in RenderHTML + ReportActionItemFragment --- src/components/RenderHTML/index.js | 7 ++++--- src/pages/home/report/ReportActionItemFragment.js | 6 +++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/components/RenderHTML/index.js b/src/components/RenderHTML/index.js index 0f115512eba7..54bc144c77c7 100755 --- a/src/components/RenderHTML/index.js +++ b/src/components/RenderHTML/index.js @@ -1,13 +1,14 @@ import React from 'react'; import BaseRenderHTML from './BaseRenderHTML'; +import withWindowDimensions from '../withWindowDimensions'; import { propTypes, defaultProps, } from './renderHTMLPropTypes'; -const RenderHTML = ({html, debag}) => ( +const RenderHTML = ({html, debag, isSmallScreenWidth}) => ( @@ -17,4 +18,4 @@ RenderHTML.displayName = 'RenderHTML'; RenderHTML.propTypes = propTypes; RenderHTML.defaultProps = defaultProps; -export default RenderHTML; +export default withWindowDimensions(RenderHTML); diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js index 7ff53227d4d5..71b474527150 100644 --- a/src/pages/home/report/ReportActionItemFragment.js +++ b/src/pages/home/report/ReportActionItemFragment.js @@ -10,6 +10,7 @@ import RenderHTML from '../../../components/RenderHTML'; import Text from '../../../components/Text'; import Tooltip from '../../../components/Tooltip'; import {isSingleEmoji} from '../../../libs/ValidationUtils'; +import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions'; const propTypes = { /** The message fragment needing to be displayed */ @@ -23,6 +24,8 @@ const propTypes = { /** Does this fragment belong to a reportAction that has not yet loaded? */ loading: PropTypes.bool, + + ...windowDimensionsPropTypes, }; const defaultProps = { @@ -58,6 +61,7 @@ class ReportActionItemFragment extends React.PureComponent { /> ) : ( {Str.htmlDecode(fragment.text)} @@ -108,4 +112,4 @@ ReportActionItemFragment.propTypes = propTypes; ReportActionItemFragment.defaultProps = defaultProps; ReportActionItemFragment.displayName = 'ReportActionItemFragment'; -export default ReportActionItemFragment; +export default withWindowDimensions(ReportActionItemFragment); From 44cf52afa457f7e8897d25ce57728964d391511b Mon Sep 17 00:00:00 2001 From: Viacheslav Surzhikov Date: Mon, 28 Jun 2021 13:30:44 +0300 Subject: [PATCH 8/9] fix debug prop --- src/components/RenderHTML/index.js | 4 ++-- src/components/RenderHTML/index.native.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/RenderHTML/index.js b/src/components/RenderHTML/index.js index 54bc144c77c7..4e03dae203a7 100755 --- a/src/components/RenderHTML/index.js +++ b/src/components/RenderHTML/index.js @@ -6,11 +6,11 @@ import { defaultProps, } from './renderHTMLPropTypes'; -const RenderHTML = ({html, debag, isSmallScreenWidth}) => ( +const RenderHTML = ({html, debug, isSmallScreenWidth}) => ( ); diff --git a/src/components/RenderHTML/index.native.js b/src/components/RenderHTML/index.native.js index 4fada63451aa..99048ea86c37 100755 --- a/src/components/RenderHTML/index.native.js +++ b/src/components/RenderHTML/index.native.js @@ -5,10 +5,10 @@ import { defaultProps, } from './renderHTMLPropTypes'; -const RenderHTML = ({html, debag}) => ( +const RenderHTML = ({html, debug}) => ( ); From a1f2c94cec417a14fac29c2327cac5928cee3196 Mon Sep 17 00:00:00 2001 From: Viacheslav Surzhikov Date: Tue, 29 Jun 2021 17:44:23 +0300 Subject: [PATCH 9/9] fix selection on longPress --- .../PressableWithSecondaryInteraction/index.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/components/PressableWithSecondaryInteraction/index.js b/src/components/PressableWithSecondaryInteraction/index.js index 30480e0143ba..79543027bfb3 100644 --- a/src/components/PressableWithSecondaryInteraction/index.js +++ b/src/components/PressableWithSecondaryInteraction/index.js @@ -22,10 +22,20 @@ class PressableWithSecondaryInteraction extends Component { this.props.forwardedRef(this.pressableRef); } this.pressableRef.addEventListener('contextmenu', this.executeSecondaryInteractionOnContextMenu); + this.pressableRef.addEventListener('touchstart', this.preventDefault); } componentWillUnmount() { this.pressableRef.removeEventListener('contextmenu', this.executeSecondaryInteractionOnContextMenu); + this.pressableRef.removeEventListener('touchstart', this.preventDefault); + } + + /** + * @param {touchstart} e - TouchEvent. + * https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent + */ + preventDefault = (e) => { + e.preventDefault(); } /**