From 2c0363b5b4b3591dcc2dc10133ce10b3e987b637 Mon Sep 17 00:00:00 2001 From: Rory Abraham Date: Tue, 21 Dec 2021 18:00:05 -0500 Subject: [PATCH 01/10] Move AnchorRenderer --- .../BaseHTMLEngineProvider.js | 87 +------------------ .../HTMLEngineProvider/HTMLEngineUtils.js | 21 +++++ .../HTMLRenderers/AnchorRenderer.js | 79 +++++++++++++++++ .../HTMLRenderers/htmlRendererPropTypes.js | 5 ++ 4 files changed, 107 insertions(+), 85 deletions(-) create mode 100644 src/components/HTMLEngineProvider/HTMLEngineUtils.js create mode 100644 src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js create mode 100644 src/components/HTMLEngineProvider/HTMLRenderers/htmlRendererPropTypes.js diff --git a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js index 76733bfb63c9e..1bfd737783b2e 100755 --- a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js +++ b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js @@ -1,21 +1,18 @@ -/* eslint-disable react/prop-types */ import _ from 'underscore'; import React, {useMemo} from 'react'; -import {TouchableOpacity, Linking} from 'react-native'; +import {TouchableOpacity} from 'react-native'; import { TRenderEngineProvider, RenderHTMLConfigProvider, defaultHTMLElementModels, - TNodeChildrenRenderer, splitBoxModelStyle, } from 'react-native-render-html'; import PropTypes from 'prop-types'; -import lodashGet from 'lodash/get'; +import AnchorRenderer from './HTMLRenderers/AnchorRenderer'; import Config from '../../CONFIG'; import styles from '../../styles/styles'; import * as StyleUtils from '../../styles/StyleUtils'; import fontFamily from '../../styles/fontFamily'; -import AnchorForCommentsOnly from '../AnchorForCommentsOnly'; import InlineCodeBlock from '../InlineCodeBlock'; import AttachmentModal from '../AttachmentModal'; import ThumbnailImage from '../ThumbnailImage'; @@ -23,8 +20,6 @@ import variables from '../../styles/variables'; import themeColors from '../../styles/themes/default'; import ExpensifyText from '../ExpensifyText'; import withLocalize from '../withLocalize'; -import Navigation from '../../libs/Navigation/Navigation'; -import CONST from '../../CONST'; const propTypes = { /** Whether text elements should be selectable */ @@ -67,84 +62,6 @@ function computeEmbeddedMaxWidth(tagName, contentWidth) { return contentWidth; } -/** - * Check if there is an ancestor node with name 'comment'. - * Finding node with name 'comment' flags that we are rendering a comment. - * @param {TNode} tnode - * @returns {Boolean} - */ -function isInsideComment(tnode) { - let currentNode = tnode; - while (currentNode.parent) { - if (currentNode.domNode.name === 'comment') { - return true; - } - currentNode = currentNode.parent; - } - return false; -} - -function AnchorRenderer(props) { - const htmlAttribs = props.tnode.attributes; - - // An auth token is needed to download Expensify chat attachments - const isAttachment = Boolean(htmlAttribs['data-expensify-source']); - const fileName = lodashGet(props.tnode, 'domNode.children[0].data', ''); - const parentStyle = lodashGet(props.tnode, 'parent.styles.nativeTextRet', {}); - const attrHref = htmlAttribs.href || ''; - const internalExpensifyPath = (attrHref.startsWith(CONST.NEW_EXPENSIFY_URL) && attrHref.replace(CONST.NEW_EXPENSIFY_URL, '')) - || (attrHref.startsWith(CONST.STAGING_NEW_EXPENSIFY_URL) && attrHref.replace(CONST.STAGING_NEW_EXPENSIFY_URL, '')); - - // If we are handling a New Expensify link then we will assume this should be opened by the app internally. This ensures that the links are opened internally via react-navigation - // instead of in a new tab or with a page refresh (which is the default behavior of an anchor tag) - if (internalExpensifyPath) { - return ( - Navigation.navigate(internalExpensifyPath)} - > - - - ); - } - - if (!isInsideComment(props.tnode)) { - // This is not a comment from a chat, the AnchorForCommentsOnly uses a Pressable to create a context menu on right click. - // We don't have this behaviour in other links in NewDot - // TODO: We should use TextLink, but I'm leaving it as ExpensifyText for now because TextLink breaks the alignment in Android. - return ( - { - Linking.openURL(attrHref); - }} - > - - - ); - } - - return ( - - - - ); -} - function CodeRenderer(props) { // We split wrapper and inner styles // "boxModelStyle" corresponds to border, margin, padding and backgroundColor diff --git a/src/components/HTMLEngineProvider/HTMLEngineUtils.js b/src/components/HTMLEngineProvider/HTMLEngineUtils.js new file mode 100644 index 0000000000000..0290c8476a519 --- /dev/null +++ b/src/components/HTMLEngineProvider/HTMLEngineUtils.js @@ -0,0 +1,21 @@ +/** + * Check if there is an ancestor node with name 'comment'. + * Finding node with name 'comment' flags that we are rendering a comment. + * @param {TNode} tnode + * @returns {Boolean} + */ +function isInsideComment(tnode) { + let currentNode = tnode; + while (currentNode.parent) { + if (currentNode.domNode.name === 'comment') { + return true; + } + currentNode = currentNode.parent; + } + return false; +} + +export { + // eslint-disable-next-line import/prefer-default-export + isInsideComment, +}; diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js new file mode 100644 index 0000000000000..488de12f25127 --- /dev/null +++ b/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js @@ -0,0 +1,79 @@ +import React from 'react'; +import {Linking} from 'react-native'; +import { + TNodeChildrenRenderer, +} from 'react-native-render-html'; +import lodashGet from 'lodash/get'; +import htmlRendererPropTypes from './htmlRendererPropTypes'; +import * as HTMLEngineUtils from '../HTMLEngineUtils'; +import ExpensifyText from '../../ExpensifyText'; +import CONST from '../../../CONST'; +import styles from '../../../styles/styles'; +import Navigation from '../../../libs/Navigation/Navigation'; +import AnchorForCommentsOnly from '../../AnchorForCommentsOnly'; + +const AnchorRenderer = (props) => { + const htmlAttribs = props.tnode.attributes; + + // An auth token is needed to download Expensify chat attachments + const isAttachment = Boolean(htmlAttribs['data-expensify-source']); + const fileName = lodashGet(props.tnode, 'domNode.children[0].data', ''); + const parentStyle = lodashGet(props.tnode, 'parent.styles.nativeTextRet', {}); + const attrHref = htmlAttribs.href || ''; + const internalExpensifyPath = (attrHref.startsWith(CONST.NEW_EXPENSIFY_URL) && attrHref.replace(CONST.NEW_EXPENSIFY_URL, '')) + || (attrHref.startsWith(CONST.STAGING_NEW_EXPENSIFY_URL) && attrHref.replace(CONST.STAGING_NEW_EXPENSIFY_URL, '')); + + // If we are handling a New Expensify link then we will assume this should be opened by the app internally. This ensures that the links are opened internally via react-navigation + // instead of in a new tab or with a page refresh (which is the default behavior of an anchor tag) + if (internalExpensifyPath) { + return ( + Navigation.navigate(internalExpensifyPath)} + > + + + ); + } + + if (!HTMLEngineUtils.isInsideComment(props.tnode)) { + // This is not a comment from a chat, the AnchorForCommentsOnly uses a Pressable to create a context menu on right click. + // We don't have this behaviour in other links in NewDot + // TODO: We should use TextLink, but I'm leaving it as ExpensifyText for now because TextLink breaks the alignment in Android. + return ( + { + Linking.openURL(attrHref); + }} + > + + + ); + } + + return ( + + + + ); +}; + +AnchorRenderer.propTypes = htmlRendererPropTypes; +AnchorRenderer.displayName = 'AnchorRenderer'; + +export default AnchorRenderer; diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/htmlRendererPropTypes.js b/src/components/HTMLEngineProvider/HTMLRenderers/htmlRendererPropTypes.js new file mode 100644 index 0000000000000..faf55c8286080 --- /dev/null +++ b/src/components/HTMLEngineProvider/HTMLRenderers/htmlRendererPropTypes.js @@ -0,0 +1,5 @@ +import PropTypes from 'prop-types'; + +export default { + tnode: PropTypes.object, +}; From 8ba24905c8ebed64228bd26f8badc0b8b4b4156e Mon Sep 17 00:00:00 2001 From: Rory Abraham Date: Tue, 21 Dec 2021 18:05:49 -0500 Subject: [PATCH 02/10] Move CodeRenderer --- .../BaseHTMLEngineProvider.js | 38 +--------------- .../HTMLRenderers/CodeRenderer.js | 45 +++++++++++++++++++ .../HTMLRenderers/htmlRendererPropTypes.js | 3 ++ 3 files changed, 49 insertions(+), 37 deletions(-) create mode 100644 src/components/HTMLEngineProvider/HTMLRenderers/CodeRenderer.js diff --git a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js index 1bfd737783b2e..5bbf66f42ef83 100755 --- a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js +++ b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js @@ -5,15 +5,13 @@ import { TRenderEngineProvider, RenderHTMLConfigProvider, defaultHTMLElementModels, - splitBoxModelStyle, } from 'react-native-render-html'; import PropTypes from 'prop-types'; import AnchorRenderer from './HTMLRenderers/AnchorRenderer'; +import CodeRenderer from './HTMLRenderers/CodeRenderer'; import Config from '../../CONFIG'; import styles from '../../styles/styles'; -import * as StyleUtils from '../../styles/StyleUtils'; import fontFamily from '../../styles/fontFamily'; -import InlineCodeBlock from '../InlineCodeBlock'; import AttachmentModal from '../AttachmentModal'; import ThumbnailImage from '../ThumbnailImage'; import variables from '../../styles/variables'; @@ -62,40 +60,6 @@ function computeEmbeddedMaxWidth(tagName, contentWidth) { return contentWidth; } -function CodeRenderer(props) { - // We split wrapper and inner styles - // "boxModelStyle" corresponds to border, margin, padding and backgroundColor - const {boxModelStyle, otherStyle: textStyle} = splitBoxModelStyle(props.style); - - // Get the correct fontFamily variant based in the fontStyle and fontWeight - const font = StyleUtils.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, - }; - - const defaultRendererProps = _.omit(props, ['TDefaultRenderer', 'style']); - - return ( - - ); -} - function EditedRenderer(props) { const defaultRendererProps = _.omit(props, ['TDefaultRenderer', 'style', 'tnode']); return ( diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/CodeRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/CodeRenderer.js new file mode 100644 index 0000000000000..1a351447d4eca --- /dev/null +++ b/src/components/HTMLEngineProvider/HTMLRenderers/CodeRenderer.js @@ -0,0 +1,45 @@ +import _ from 'underscore'; +import React from 'react'; +import {splitBoxModelStyle} from 'react-native-render-html'; +import htmlRendererPropTypes from './htmlRendererPropTypes'; +import * as StyleUtils from '../../../styles/StyleUtils'; +import InlineCodeBlock from '../../InlineCodeBlock'; + +const CodeRenderer = (props) => { + // We split wrapper and inner styles + // "boxModelStyle" corresponds to border, margin, padding and backgroundColor + const {boxModelStyle, otherStyle: textStyle} = splitBoxModelStyle(props.style); + + // Get the correct fontFamily variant based in the fontStyle and fontWeight + const font = StyleUtils.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, + }; + + const defaultRendererProps = _.omit(props, ['TDefaultRenderer', 'style']); + + return ( + + ); +}; + +CodeRenderer.propTypes = htmlRendererPropTypes; +CodeRenderer.displayName = 'CodeRenderer'; + +export default CodeRenderer; diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/htmlRendererPropTypes.js b/src/components/HTMLEngineProvider/HTMLRenderers/htmlRendererPropTypes.js index faf55c8286080..a37c9b446c1c0 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/htmlRendererPropTypes.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/htmlRendererPropTypes.js @@ -2,4 +2,7 @@ import PropTypes from 'prop-types'; export default { tnode: PropTypes.object, + TDefaultRenderer: PropTypes.object, + key: PropTypes.string, + style: PropTypes.object, }; From cb6f543533756a68aa788602c9cfd40e4a29d846 Mon Sep 17 00:00:00 2001 From: Rory Abraham Date: Tue, 21 Dec 2021 18:11:34 -0500 Subject: [PATCH 03/10] Move EditedRenderer --- .../BaseHTMLEngineProvider.js | 24 ++----------- .../HTMLRenderers/EditedRenderer.js | 34 +++++++++++++++++++ 2 files changed, 36 insertions(+), 22 deletions(-) create mode 100644 src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.js diff --git a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js index 5bbf66f42ef83..c4458c0e8ec2a 100755 --- a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js +++ b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js @@ -1,4 +1,3 @@ -import _ from 'underscore'; import React, {useMemo} from 'react'; import {TouchableOpacity} from 'react-native'; import { @@ -9,15 +8,12 @@ import { import PropTypes from 'prop-types'; import AnchorRenderer from './HTMLRenderers/AnchorRenderer'; import CodeRenderer from './HTMLRenderers/CodeRenderer'; +import EditedRenderer from './HTMLRenderers/EditedRenderer'; import Config from '../../CONFIG'; import styles from '../../styles/styles'; import fontFamily from '../../styles/fontFamily'; import AttachmentModal from '../AttachmentModal'; import ThumbnailImage from '../ThumbnailImage'; -import variables from '../../styles/variables'; -import themeColors from '../../styles/themes/default'; -import ExpensifyText from '../ExpensifyText'; -import withLocalize from '../withLocalize'; const propTypes = { /** Whether text elements should be selectable */ @@ -60,22 +56,6 @@ function computeEmbeddedMaxWidth(tagName, contentWidth) { return contentWidth; } -function EditedRenderer(props) { - const defaultRendererProps = _.omit(props, ['TDefaultRenderer', 'style', 'tnode']); - return ( - - {/* Native devices do not support margin between nested text */} - {' '} - {props.translate('reportActionCompose.edited')} - - ); -} - function ImgRenderer(props) { const htmlAttribs = props.tnode.attributes; @@ -154,7 +134,7 @@ const renderers = { a: AnchorRenderer, code: CodeRenderer, img: ImgRenderer, - edited: withLocalize(EditedRenderer), + edited: EditedRenderer, }; const renderersProps = { diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.js new file mode 100644 index 0000000000000..7cf0bec9adbb9 --- /dev/null +++ b/src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.js @@ -0,0 +1,34 @@ +import _ from 'underscore'; +import React from 'react'; +import htmlRendererPropTypes from './htmlRendererPropTypes'; +import withLocalize, {withLocalizePropTypes} from '../../withLocalize'; +import ExpensifyText from '../../ExpensifyText'; +import variables from '../../../styles/variables'; +import themeColors from '../../../styles/themes/default'; +import styles from '../../../styles/styles'; + +const propTypes = { + ...htmlRendererPropTypes, + ...withLocalizePropTypes, +}; + +const EditedRenderer = (props) => { + const defaultRendererProps = _.omit(props, ['TDefaultRenderer', 'style', 'tnode']); + return ( + + {/* Native devices do not support margin between nested text */} + {' '} + {props.translate('reportActionCompose.edited')} + + ); +}; + +EditedRenderer.propTypes = propTypes; +EditedRenderer.displayName = 'EditedRenderer'; + +export default withLocalize(EditedRenderer); From d0f3400a28326ff6bbd64c6112644357367552c9 Mon Sep 17 00:00:00 2001 From: Rory Abraham Date: Tue, 21 Dec 2021 18:18:25 -0500 Subject: [PATCH 04/10] Move ImageRenderer --- .../BaseHTMLEngineProvider.js | 66 +---------------- .../HTMLRenderers/ImageRenderer.js | 71 +++++++++++++++++++ 2 files changed, 73 insertions(+), 64 deletions(-) create mode 100644 src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js diff --git a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js index c4458c0e8ec2a..ca69564572ddf 100755 --- a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js +++ b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js @@ -1,5 +1,4 @@ import React, {useMemo} from 'react'; -import {TouchableOpacity} from 'react-native'; import { TRenderEngineProvider, RenderHTMLConfigProvider, @@ -9,11 +8,9 @@ import PropTypes from 'prop-types'; import AnchorRenderer from './HTMLRenderers/AnchorRenderer'; import CodeRenderer from './HTMLRenderers/CodeRenderer'; import EditedRenderer from './HTMLRenderers/EditedRenderer'; -import Config from '../../CONFIG'; +import ImageRenderer from './HTMLRenderers/ImageRenderer'; import styles from '../../styles/styles'; import fontFamily from '../../styles/fontFamily'; -import AttachmentModal from '../AttachmentModal'; -import ThumbnailImage from '../ThumbnailImage'; const propTypes = { /** Whether text elements should be selectable */ @@ -56,65 +53,6 @@ function computeEmbeddedMaxWidth(tagName, contentWidth) { return contentWidth; } -function ImgRenderer(props) { - const htmlAttribs = props.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']); - const originalFileName = htmlAttribs['data-name']; - 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()} - > - - - )} - - ); -} - // Declare nonstandard tags and their content model here const customHTMLElementModels = { edited: defaultHTMLElementModels.span.extend({ @@ -133,7 +71,7 @@ const customHTMLElementModels = { const renderers = { a: AnchorRenderer, code: CodeRenderer, - img: ImgRenderer, + img: ImageRenderer, edited: EditedRenderer, }; diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js new file mode 100644 index 0000000000000..e86bdedc87d1a --- /dev/null +++ b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js @@ -0,0 +1,71 @@ +import React from 'react'; +import {TouchableOpacity} from 'react-native'; +import htmlRendererPropTypes from './htmlRendererPropTypes'; +import Config from '../../../CONFIG'; +import AttachmentModal from '../../AttachmentModal'; +import styles from '../../../styles/styles'; +import ThumbnailImage from '../../ThumbnailImage'; + +const ImageRenderer = (props) => { + const htmlAttribs = props.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']); + const originalFileName = htmlAttribs['data-name']; + 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}) => ( + + + + )} + + ); +}; + +ImageRenderer.propTypes = htmlRendererPropTypes; +ImageRenderer.displayName = 'ImageRenderer'; + +export default ImageRenderer; From 320a54366a30dc377f1e07213b34dc42383ca93e Mon Sep 17 00:00:00 2001 From: Rory Abraham Date: Tue, 21 Dec 2021 18:32:18 -0500 Subject: [PATCH 05/10] Aggregate collection of renderers --- .../BaseHTMLEngineProvider.js | 25 ++----------------- .../HTMLEngineProvider/HTMLRenderers/index.js | 17 +++++++++++++ 2 files changed, 19 insertions(+), 23 deletions(-) create mode 100644 src/components/HTMLEngineProvider/HTMLRenderers/index.js diff --git a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js index ca69564572ddf..95e9bb9de4dec 100755 --- a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js +++ b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js @@ -5,10 +5,7 @@ import { defaultHTMLElementModels, } from 'react-native-render-html'; import PropTypes from 'prop-types'; -import AnchorRenderer from './HTMLRenderers/AnchorRenderer'; -import CodeRenderer from './HTMLRenderers/CodeRenderer'; -import EditedRenderer from './HTMLRenderers/EditedRenderer'; -import ImageRenderer from './HTMLRenderers/ImageRenderer'; +import htmlRenderers from './HTMLRenderers'; import styles from '../../styles/styles'; import fontFamily from '../../styles/fontFamily'; @@ -67,23 +64,6 @@ const customHTMLElementModels = { }), }; -// Define the custom renderer components -const renderers = { - a: AnchorRenderer, - code: CodeRenderer, - img: ImageRenderer, - edited: EditedRenderer, -}; - -const renderersProps = { - img: { - initialDimensions: { - width: MAX_IMG_DIMENSIONS, - height: MAX_IMG_DIMENSIONS, - }, - }, -}; - const defaultViewProps = {style: {alignItems: 'flex-start'}}; // We are using the explicit composite architecture for performance gains. @@ -107,8 +87,7 @@ const BaseHTMLEngineProvider = (props) => { {props.children} diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/index.js b/src/components/HTMLEngineProvider/HTMLRenderers/index.js new file mode 100644 index 0000000000000..1c86a20b713c3 --- /dev/null +++ b/src/components/HTMLEngineProvider/HTMLRenderers/index.js @@ -0,0 +1,17 @@ +import AnchorRenderer from './AnchorRenderer'; +import CodeRenderer from './CodeRenderer'; +import EditedRenderer from './EditedRenderer'; +import ImageRenderer from './ImageRenderer'; + +/** + * This collection defines our custom renderers. It is a mapping from HTML tag type to the corresponding component. + */ +export default { + // Standard HTML tag renderers + a: AnchorRenderer, + code: CodeRenderer, + img: ImageRenderer, + + // Custom tag renderers + edited: EditedRenderer, +} From b7089c27267e35b91ef9f4b95f6b8c020cbf4148 Mon Sep 17 00:00:00 2001 From: Rory Abraham Date: Wed, 22 Dec 2021 18:00:57 -0500 Subject: [PATCH 06/10] Move more non-component code out of BaseHTMLEngineProvider --- .../BaseHTMLEngineProvider.js | 36 ++--------------- .../HTMLEngineProvider/HTMLEngineUtils.js | 21 ---------- .../HTMLRenderers/AnchorRenderer.js | 2 +- .../HTMLEngineProvider/htmlEngineUtils.js | 40 +++++++++++++++++++ 4 files changed, 45 insertions(+), 54 deletions(-) delete mode 100644 src/components/HTMLEngineProvider/HTMLEngineUtils.js create mode 100644 src/components/HTMLEngineProvider/htmlEngineUtils.js diff --git a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js index 95e9bb9de4dec..d40f7df14bdbe 100755 --- a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js +++ b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js @@ -1,3 +1,4 @@ +import _ from 'underscore'; import React, {useMemo} from 'react'; import { TRenderEngineProvider, @@ -6,6 +7,7 @@ import { } from 'react-native-render-html'; import PropTypes from 'prop-types'; import htmlRenderers from './HTMLRenderers'; +import * as HTMLEngineUtils from './htmlEngineUtils'; import styles from '../../styles/styles'; import fontFamily from '../../styles/fontFamily'; @@ -20,36 +22,6 @@ const defaultProps = { children: null, }; -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 embedded 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 {string} tagName - The name of the tag for which max width should be constrained. - * @param {number} contentWidth - The content width provided to the HTML - * component. - * @returns {number} The minimum between contentWidth and MAX_IMG_DIMENSIONS - */ -function computeEmbeddedMaxWidth(tagName, contentWidth) { - if (tagName === 'img') { - return Math.min(MAX_IMG_DIMENSIONS, contentWidth); - } - return contentWidth; -} - // Declare nonstandard tags and their content model here const customHTMLElementModels = { edited: defaultHTMLElementModels.span.extend({ @@ -82,13 +54,13 @@ const BaseHTMLEngineProvider = (props) => { tagsStyles={styles.webViewStyles.tagStyles} enableCSSInlineProcessing={false} dangerouslyDisableWhitespaceCollapsing={false} - systemFonts={EXTRA_FONTS} + systemFonts={_.values(fontFamily)} > {props.children} diff --git a/src/components/HTMLEngineProvider/HTMLEngineUtils.js b/src/components/HTMLEngineProvider/HTMLEngineUtils.js deleted file mode 100644 index 0290c8476a519..0000000000000 --- a/src/components/HTMLEngineProvider/HTMLEngineUtils.js +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Check if there is an ancestor node with name 'comment'. - * Finding node with name 'comment' flags that we are rendering a comment. - * @param {TNode} tnode - * @returns {Boolean} - */ -function isInsideComment(tnode) { - let currentNode = tnode; - while (currentNode.parent) { - if (currentNode.domNode.name === 'comment') { - return true; - } - currentNode = currentNode.parent; - } - return false; -} - -export { - // eslint-disable-next-line import/prefer-default-export - isInsideComment, -}; diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js index 488de12f25127..d1635dfbd8d71 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js @@ -5,7 +5,7 @@ import { } from 'react-native-render-html'; import lodashGet from 'lodash/get'; import htmlRendererPropTypes from './htmlRendererPropTypes'; -import * as HTMLEngineUtils from '../HTMLEngineUtils'; +import * as HTMLEngineUtils from '../htmlEngineUtils'; import ExpensifyText from '../../ExpensifyText'; import CONST from '../../../CONST'; import styles from '../../../styles/styles'; diff --git a/src/components/HTMLEngineProvider/htmlEngineUtils.js b/src/components/HTMLEngineProvider/htmlEngineUtils.js new file mode 100644 index 0000000000000..dc678b99d6017 --- /dev/null +++ b/src/components/HTMLEngineProvider/htmlEngineUtils.js @@ -0,0 +1,40 @@ +const MAX_IMG_DIMENSIONS = 512; + +/** + * Compute embedded 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 {string} tagName - The name of the tag for which max width should be constrained. + * @param {number} contentWidth - The content width provided to the HTML + * component. + * @returns {number} The minimum between contentWidth and MAX_IMG_DIMENSIONS + */ +function computeEmbeddedMaxWidth(tagName, contentWidth) { + if (tagName === 'img') { + return Math.min(MAX_IMG_DIMENSIONS, contentWidth); + } + return contentWidth; +} + +/** + * Check if there is an ancestor node with name 'comment'. + * Finding node with name 'comment' flags that we are rendering a comment. + * @param {TNode} tnode + * @returns {Boolean} + */ +function isInsideComment(tnode) { + let currentNode = tnode; + while (currentNode.parent) { + if (currentNode.domNode.name === 'comment') { + return true; + } + currentNode = currentNode.parent; + } + return false; +} + +export { + computeEmbeddedMaxWidth, + isInsideComment, +}; From 8dfa15ebcc74ade057a7db2cef048073bd6b76aa Mon Sep 17 00:00:00 2001 From: Rory Abraham Date: Tue, 28 Dec 2021 13:01:20 -0500 Subject: [PATCH 07/10] Fix JS style --- src/components/HTMLEngineProvider/HTMLRenderers/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/index.js b/src/components/HTMLEngineProvider/HTMLRenderers/index.js index 1c86a20b713c3..8186dd57841b3 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/index.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/index.js @@ -14,4 +14,4 @@ export default { // Custom tag renderers edited: EditedRenderer, -} +}; From 503ec01d4f658587268ee81829f9dce614139a55 Mon Sep 17 00:00:00 2001 From: Rory Abraham Date: Tue, 28 Dec 2021 13:29:14 -0500 Subject: [PATCH 08/10] Simplify arrow function callback --- .../HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js index d1635dfbd8d71..78341dcccc526 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js @@ -43,9 +43,7 @@ const AnchorRenderer = (props) => { return ( { - Linking.openURL(attrHref); - }} + onPress={() => Linking.openURL(attrHref)} > From 7de067de3860472c29c858304734b633e62f3dac Mon Sep 17 00:00:00 2001 From: Rory Abraham Date: Tue, 28 Dec 2021 13:35:23 -0500 Subject: [PATCH 09/10] Rename ExpensifyText after merge --- .../HTMLRenderers/AnchorRenderer.js | 12 ++++++------ .../HTMLRenderers/EditedRenderer.js | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js index 78341dcccc526..62362d41b77dd 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js @@ -6,7 +6,7 @@ import { import lodashGet from 'lodash/get'; import htmlRendererPropTypes from './htmlRendererPropTypes'; import * as HTMLEngineUtils from '../htmlEngineUtils'; -import ExpensifyText from '../../ExpensifyText'; +import Text from '../../Text'; import CONST from '../../../CONST'; import styles from '../../../styles/styles'; import Navigation from '../../../libs/Navigation/Navigation'; @@ -27,26 +27,26 @@ const AnchorRenderer = (props) => { // instead of in a new tab or with a page refresh (which is the default behavior of an anchor tag) if (internalExpensifyPath) { return ( - Navigation.navigate(internalExpensifyPath)} > - + ); } if (!HTMLEngineUtils.isInsideComment(props.tnode)) { // This is not a comment from a chat, the AnchorForCommentsOnly uses a Pressable to create a context menu on right click. // We don't have this behaviour in other links in NewDot - // TODO: We should use TextLink, but I'm leaving it as ExpensifyText for now because TextLink breaks the alignment in Android. + // TODO: We should use TextLink, but I'm leaving it as Text for now because TextLink breaks the alignment in Android. return ( - Linking.openURL(attrHref)} > - + ); } diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.js index 7cf0bec9adbb9..0b04c3a885beb 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.js @@ -2,7 +2,7 @@ import _ from 'underscore'; import React from 'react'; import htmlRendererPropTypes from './htmlRendererPropTypes'; import withLocalize, {withLocalizePropTypes} from '../../withLocalize'; -import ExpensifyText from '../../ExpensifyText'; +import Text from '../../Text'; import variables from '../../../styles/variables'; import themeColors from '../../../styles/themes/default'; import styles from '../../../styles/styles'; @@ -15,16 +15,16 @@ const propTypes = { const EditedRenderer = (props) => { const defaultRendererProps = _.omit(props, ['TDefaultRenderer', 'style', 'tnode']); return ( - {/* Native devices do not support margin between nested text */} - {' '} + {' '} {props.translate('reportActionCompose.edited')} - + ); }; From c38d79e2a4fe24dbca8909166e323142597f94b9 Mon Sep 17 00:00:00 2001 From: Rory Abraham Date: Tue, 4 Jan 2022 16:32:46 -0500 Subject: [PATCH 10/10] Fix TDefaultRenderer propType --- .../HTMLEngineProvider/HTMLRenderers/htmlRendererPropTypes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/htmlRendererPropTypes.js b/src/components/HTMLEngineProvider/HTMLRenderers/htmlRendererPropTypes.js index a37c9b446c1c0..f26806482e484 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/htmlRendererPropTypes.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/htmlRendererPropTypes.js @@ -2,7 +2,7 @@ import PropTypes from 'prop-types'; export default { tnode: PropTypes.object, - TDefaultRenderer: PropTypes.object, + TDefaultRenderer: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), key: PropTypes.string, style: PropTypes.object, };