diff --git a/.gitignore b/.gitignore index 8efffec2c5f8..0c4904335d51 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,9 @@ build/ local.properties *.iml +# Vscode +.vscode + # node.js # node_modules/ diff --git a/config/webpack/webpack.common.js b/config/webpack/webpack.common.js index 28b397f7bc72..334d52593a75 100644 --- a/config/webpack/webpack.common.js +++ b/config/webpack/webpack.common.js @@ -52,7 +52,7 @@ module.exports = { */ exclude: [ // eslint-disable-next-line max-len - /node_modules\/(?!(react-native-render-html|react-native-webview|react-native-onyx)\/).*|\.native\.js$/, + /node_modules\/(?!(react-native-webview|react-native-onyx)\/).*|\.native\.js$/, platformExclude ], }, diff --git a/config/webpack/webpack.dev.js b/config/webpack/webpack.dev.js index 871b46e53e55..4f6e0bd32fe9 100644 --- a/config/webpack/webpack.dev.js +++ b/config/webpack/webpack.dev.js @@ -16,6 +16,11 @@ module.exports = merge(common, { plugins: [ new webpack.DefinePlugin({ __REACT_WEB_CONFIG__: JSON.stringify(env), - }) - ] + + // React Native JavaScript environment requires the global __DEV__ variable to be accessible. + // react-native-render-html uses variable to log exclusively during development. + // See https://reactnative.dev/docs/javascript-environment + __DEV__: true, + }), + ], }); diff --git a/config/webpack/webpack.prod.js b/config/webpack/webpack.prod.js index 3a1604c44c3a..6cccd5242175 100644 --- a/config/webpack/webpack.prod.js +++ b/config/webpack/webpack.prod.js @@ -12,6 +12,11 @@ module.exports = merge(common, { plugins: [ new webpack.DefinePlugin({ __REACT_WEB_CONFIG__: JSON.stringify(env), + + // React Native JavaScript environment requires the global __DEV__ variable to be accessible. + // react-native-render-html uses variable to log exclusively during development. + // See https://reactnative.dev/docs/javascript-environment + __DEV__: false, }) ], }); diff --git a/package-lock.json b/package-lock.json index 04200605df51..4076bdab8592 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2913,6 +2913,81 @@ "chalk": "^3.0.0" } }, + "@native-html/css-processor": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@native-html/css-processor/-/css-processor-1.6.1.tgz", + "integrity": "sha512-3l4SmYU5CIwL7f8GSssypWfFd7W/FcqVrOomhDRbaWYsxKh2T0zNcIjJbkr8ZbpXJk3qKrV1EMoTJ8vt6H8M9Q==", + "requires": { + "css-to-react-native": "^3.0.0" + } + }, + "@native-html/transient-render-engine": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@native-html/transient-render-engine/-/transient-render-engine-3.6.0.tgz", + "integrity": "sha512-fvPIzD+b2xq7+cIcFwItze3IS59eneht7h31VqRQ5CyN7mCTfcuxCmMzLDdM7/1Chn4A79tG0yEWeJQSt2565Q==", + "requires": { + "@native-html/css-processor": "1.6.1", + "@types/ramda": "^0.27.32", + "htmlparser2": "^5.0.1", + "ramda": "^0.27.1" + }, + "dependencies": { + "dom-serializer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.1.0.tgz", + "integrity": "sha512-ox7bvGXt2n+uLWtCRLybYx60IrOlWL/aCebWJk1T0d4m3y2tzf4U3ij9wBMUb6YJZpz06HCCYuyCDveE2xXmzQ==", + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^3.0.0", + "entities": "^2.0.0" + } + }, + "domelementtype": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.2.tgz", + "integrity": "sha512-wFwTwCVebUrMgGeAwRL/NhZtHAUyT9n9yg4IMDwf10+6iCMxSkVq9MGCVEH+QZWo1nNidy8kNvwmv4zWHDTqvA==" + }, + "domhandler": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.3.0.tgz", + "integrity": "sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA==", + "requires": { + "domelementtype": "^2.0.1" + } + }, + "domutils": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.4.2.tgz", + "integrity": "sha512-NKbgaM8ZJOecTZsIzW5gSuplsX2IWW2mIK7xVr8hTQF2v1CJWTmLZ1HOCh5sH+IzVPAGE5IucooOkvwBRAdowA==", + "requires": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.0.1", + "domhandler": "^3.3.0" + } + }, + "entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==" + }, + "htmlparser2": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-5.0.1.tgz", + "integrity": "sha512-vKZZra6CSe9qsJzh0BjBGXo8dvzNsq/oGvsjfRdOrrryfeD9UOBEEQdeoqCRmKZchF5h2zOBMQ6YuQ0uRUmdbQ==", + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^3.3.0", + "domutils": "^2.4.2", + "entities": "^2.0.0" + } + }, + "ramda": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.1.tgz", + "integrity": "sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw==" + } + } + }, "@nodelib/fs.scandir": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", @@ -3471,6 +3546,14 @@ "integrity": "sha512-UEyp8LwZ4Dg30kVU2Q3amHHyTn1jEdhCIE59ANed76GaT1Vp76DD3ZWSAxgCrw6wJ0TqeoBpqmfUHiUDPs//HQ==", "dev": true }, + "@types/ramda": { + "version": "0.27.32", + "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.27.32.tgz", + "integrity": "sha512-vdwZcWC+hlTxB//LZQLS1+VEdArImGI4yVKUpeqB8b9mBXgDFXCuQoOt8spQbi8fTyNLOdqRv6liSm2ckxWLog==", + "requires": { + "ts-toolbelt": "^6.15.1" + } + }, "@types/semver": { "version": "7.3.4", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.4.tgz", @@ -5745,6 +5828,11 @@ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" }, + "camelize": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", + "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=" + }, "caniuse-lite": { "version": "1.0.30001148", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001148.tgz", @@ -6608,6 +6696,11 @@ "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", "dev": true }, + "css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU=" + }, "css-hot-loader": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/css-hot-loader/-/css-hot-loader-1.4.4.tgz", @@ -6758,6 +6851,16 @@ } } }, + "css-to-react-native": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.0.0.tgz", + "integrity": "sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ==", + "requires": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "css-what": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", @@ -7376,6 +7479,7 @@ "version": "0.2.2", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "dev": true, "requires": { "domelementtype": "^2.0.1", "entities": "^2.0.0" @@ -7384,12 +7488,14 @@ "domelementtype": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.2.tgz", - "integrity": "sha512-wFwTwCVebUrMgGeAwRL/NhZtHAUyT9n9yg4IMDwf10+6iCMxSkVq9MGCVEH+QZWo1nNidy8kNvwmv4zWHDTqvA==" + "integrity": "sha512-wFwTwCVebUrMgGeAwRL/NhZtHAUyT9n9yg4IMDwf10+6iCMxSkVq9MGCVEH+QZWo1nNidy8kNvwmv4zWHDTqvA==", + "dev": true }, "entities": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==" + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "dev": true } } }, @@ -7407,7 +7513,8 @@ "domelementtype": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true }, "domexception": { "version": "2.0.1", @@ -7430,6 +7537,7 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "dev": true, "requires": { "domelementtype": "1" } @@ -7438,6 +7546,7 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "dev": true, "requires": { "dom-serializer": "0", "domelementtype": "1" @@ -7999,7 +8108,8 @@ "entities": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true }, "env-paths": { "version": "2.2.0", @@ -9502,11 +9612,6 @@ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==" }, - "events": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", - "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" - }, "eventsource": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.0.7.tgz", @@ -11204,6 +11309,7 @@ "version": "3.10.1", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "dev": true, "requires": { "domelementtype": "^1.3.1", "domhandler": "^2.3.0", @@ -11217,6 +11323,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -18639,8 +18746,7 @@ "postcss-value-parser": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", - "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==", - "dev": true + "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==" }, "prelude-ls": { "version": "1.2.1", @@ -19459,14 +19565,20 @@ } }, "react-native-render-html": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/react-native-render-html/-/react-native-render-html-4.2.4.tgz", - "integrity": "sha512-OiLItEzKgS7dzD9XI5bHhjcUEfpWdzH1FgexzjbBdICPfYjmmcefpcRmLZY1+HMfxJ7wL8iF1PzTF48LchGTBA==", + "version": "6.0.0-alpha.8", + "resolved": "https://registry.npmjs.org/react-native-render-html/-/react-native-render-html-6.0.0-alpha.8.tgz", + "integrity": "sha512-iMXnJ59mB9bV0O/w/GEqcTQ1PnV7/tjmxC8eDZv8vTIbN3wVvpgwvPWS/aTndDtvBC9mHYGG+CazH7ALqJjJMw==", "requires": { - "buffer": "^4.5.1", - "events": "^1.1.0", - "html-entities": "^1.2.0", - "htmlparser2": "3.10.1" + "@native-html/transient-render-engine": "^3.6.0", + "@types/ramda": "^0.27.32", + "ramda": "^0.27.1" + }, + "dependencies": { + "ramda": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.1.tgz", + "integrity": "sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw==" + } } }, "react-native-safe-area-context": { @@ -22181,6 +22293,11 @@ "utf8-byte-length": "^1.0.1" } }, + "ts-toolbelt": { + "version": "6.15.5", + "resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-6.15.5.tgz", + "integrity": "sha512-FZIXf1ksVyLcfr7M317jbB67XFJhOO1YqdTcuGaq9q5jLUoTikukZ+98TPjKiP2jC5CgmYdWWYs0s2nLSU0/1A==" + }, "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", diff --git a/package.json b/package.json index d3566f35131a..903c3be491f1 100644 --- a/package.json +++ b/package.json @@ -60,9 +60,9 @@ "react-native-image-picker": "^2.3.3", "react-native-keyboard-spacer": "^0.4.1", "react-native-modal": "^11.5.6", - "react-native-pdf": "^6.2.2", "react-native-onyx": "git+https://git@github.com:Expensify/react-native-onyx.git#8e29d1807382c8a1325c92858c551f6b19e1aaad", - "react-native-render-html": "^4.2.3", + "react-native-pdf": "^6.2.2", + "react-native-render-html": "^6.0.0-alpha.8", "react-native-safe-area-context": "^3.1.4", "react-native-web": "^0.14.0", "react-native-web-webview": "^1.0.2", diff --git a/src/components/AnchorForCommentsOnly/anchorForCommentsOnlyPropTypes.js b/src/components/AnchorForCommentsOnly/anchorForCommentsOnlyPropTypes.js new file mode 100644 index 000000000000..eb61cdc7d3a1 --- /dev/null +++ b/src/components/AnchorForCommentsOnly/anchorForCommentsOnlyPropTypes.js @@ -0,0 +1,27 @@ +import PropTypes from 'prop-types'; + +/** + * Text based component that is passed a URL to open onPress + */ +const anchorForCommentsOnlyPropTypes = { + // The URL to open + href: PropTypes.string, + + // What headers to send to the linked page (usually noopener and noreferrer) + // This is unused in native, but is here for parity with web + rel: PropTypes.string, + + // Used to determine where to open a link ("_blank" is passed for a new tab) + // This is unused in native, but is here for parity with web + target: PropTypes.string, + + + // Any children to display + children: PropTypes.node, + + // Any additional styles to apply + // eslint-disable-next-line react/forbid-prop-types + style: PropTypes.any, +}; + +export default anchorForCommentsOnlyPropTypes; diff --git a/src/components/AnchorForCommentsOnly/index.js b/src/components/AnchorForCommentsOnly/index.js index 96244d347278..6e70e859cf96 100644 --- a/src/components/AnchorForCommentsOnly/index.js +++ b/src/components/AnchorForCommentsOnly/index.js @@ -1,28 +1,6 @@ import React from 'react'; -import PropTypes from 'prop-types'; import {StyleSheet} from 'react-native'; - -/** - * Text based component that is passed a URL to open onPress - */ - -const propTypes = { - // The URL to open - href: PropTypes.string, - - // What headers to send to the linked page (usually noopener and noreferrer) - rel: PropTypes.string, - - // Used to determine where to open a link ("_blank" is passed for a new tab) - target: PropTypes.string, - - // Any children to display - children: PropTypes.node, - - // Any additional styles to apply - // eslint-disable-next-line react/forbid-prop-types - style: PropTypes.any, -}; +import anchorForCommentsOnlyPropTypes from './anchorForCommentsOnlyPropTypes'; const defaultProps = { href: '', @@ -52,7 +30,7 @@ const AnchorForCommentsOnly = ({ ); -AnchorForCommentsOnly.propTypes = propTypes; +AnchorForCommentsOnly.propTypes = anchorForCommentsOnlyPropTypes; AnchorForCommentsOnly.defaultProps = defaultProps; AnchorForCommentsOnly.displayName = 'AnchorForCommentsOnly'; diff --git a/src/components/AnchorForCommentsOnly/index.native.js b/src/components/AnchorForCommentsOnly/index.native.js index d4ed3dfc944b..a5b184a8a6b3 100644 --- a/src/components/AnchorForCommentsOnly/index.native.js +++ b/src/components/AnchorForCommentsOnly/index.native.js @@ -1,31 +1,6 @@ import React from 'react'; -import PropTypes from 'prop-types'; import {Linking, StyleSheet, Text} from 'react-native'; - -/** - * Text based component that is passed a URL to open onPress - */ - -const propTypes = { - // The URL to open - href: PropTypes.string, - - // What headers to send to the linked page (usually noopener and noreferrer) - // This is unused in native, but is here for parity with web - rel: PropTypes.string, - - // Used to determine where to open a link ("_blank" is passed for a new tab) - // This is unused in native, but is here for parity with web - target: PropTypes.string, - - - // Any children to display - children: PropTypes.node, - - // Any additional styles to apply - // eslint-disable-next-line react/forbid-prop-types - style: PropTypes.any, -}; +import anchorForCommentsOnlyPropTypes from './anchorForCommentsOnlyPropTypes'; const defaultProps = { href: '', @@ -45,7 +20,7 @@ const AnchorForCommentsOnly = ({ Linking.openURL(href)} {...props}>{children} ); -AnchorForCommentsOnly.propTypes = propTypes; +AnchorForCommentsOnly.propTypes = anchorForCommentsOnlyPropTypes; AnchorForCommentsOnly.defaultProps = defaultProps; AnchorForCommentsOnly.displayName = 'AnchorForCommentsOnly'; diff --git a/src/components/InlineCodeBlock/index.android.js b/src/components/InlineCodeBlock/index.android.js index 00f294e3a327..d68ca0031b84 100644 --- a/src/components/InlineCodeBlock/index.android.js +++ b/src/components/InlineCodeBlock/index.android.js @@ -1,18 +1,19 @@ +/* eslint-disable react/jsx-props-no-spreading */ import React from 'react'; -import PropTypes from 'prop-types'; import {View} from 'react-native'; -import {webViewStyles} from '../../styles/StyleSheet'; +import inlineCodeBlockPropTypes from './inlineCodeBlockPropTypes'; -const propTypes = { - children: PropTypes.node.isRequired, -}; - -const InlineCodeBlock = ({children}) => ( - - {children} +const InlineCodeBlock = ({ + TDefaultRenderer, + defaultRendererProps, + boxModelStyle, + textStyle, +}) => ( + + ); -InlineCodeBlock.propTypes = propTypes; +InlineCodeBlock.propTypes = inlineCodeBlockPropTypes; InlineCodeBlock.displayName = 'InlineCodeBlock'; export default InlineCodeBlock; diff --git a/src/components/InlineCodeBlock/index.ios.js b/src/components/InlineCodeBlock/index.ios.js index 40ce39772dc8..65b3d26d1f19 100644 --- a/src/components/InlineCodeBlock/index.ios.js +++ b/src/components/InlineCodeBlock/index.ios.js @@ -1,18 +1,25 @@ +/* eslint-disable react/jsx-props-no-spreading */ import React from 'react'; -import PropTypes from 'prop-types'; import {View} from 'react-native'; -import styles, {webViewStyles} from '../../styles/StyleSheet'; +import styles from '../../styles/StyleSheet'; +import inlineCodeBlockPropTypes from './inlineCodeBlockPropTypes'; -const propTypes = { - children: PropTypes.node.isRequired, -}; - -const InlineCodeBlock = ({children}) => ( - - {children} +const InlineCodeBlock = ({ + TDefaultRenderer, + defaultRendererProps, + boxModelStyle, + textStyle, +}) => ( + + ); -InlineCodeBlock.propTypes = propTypes; +InlineCodeBlock.propTypes = inlineCodeBlockPropTypes; InlineCodeBlock.displayName = 'InlineCodeBlock'; export default InlineCodeBlock; diff --git a/src/components/InlineCodeBlock/index.js b/src/components/InlineCodeBlock/index.js index 6264e55b0264..aa6edab7018e 100644 --- a/src/components/InlineCodeBlock/index.js +++ b/src/components/InlineCodeBlock/index.js @@ -1,18 +1,19 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import {Text} from 'react-native'; -import {webViewStyles} from '../../styles/StyleSheet'; +import inlineCodeBlockPropTypes from './inlineCodeBlockPropTypes'; -const propTypes = { - children: PropTypes.node.isRequired, -}; - -const InlineCodeBlock = ({children}) => ( - - {children} - +const InlineCodeBlock = ({ + TDefaultRenderer, + defaultRendererProps, + boxModelStyle, + textStyle, +}) => ( + ); -InlineCodeBlock.propTypes = propTypes; +InlineCodeBlock.propTypes = inlineCodeBlockPropTypes; InlineCodeBlock.displayName = 'InlineCodeBlock'; export default InlineCodeBlock; diff --git a/src/components/InlineCodeBlock/inlineCodeBlockPropTypes.js b/src/components/InlineCodeBlock/inlineCodeBlockPropTypes.js new file mode 100644 index 000000000000..f880d3a1e4ae --- /dev/null +++ b/src/components/InlineCodeBlock/inlineCodeBlockPropTypes.js @@ -0,0 +1,10 @@ +import PropTypes from 'prop-types'; + +const inlineCodeBlockPropTypes = { + TDefaultRenderer: PropTypes.func.isRequired, + defaultRendererProps: PropTypes.object.isRequired, + boxModelStyle: PropTypes.any.isRequired, + textStyle: PropTypes.any.isRequired +}; + +export default inlineCodeBlockPropTypes; diff --git a/src/components/RenderHTML.js b/src/components/RenderHTML.js new file mode 100644 index 000000000000..3f05c055736c --- /dev/null +++ b/src/components/RenderHTML.js @@ -0,0 +1,156 @@ +/* eslint-disable react/prop-types */ +import React from 'react'; +import PropTypes from 'prop-types'; +import {useWindowDimensions} from 'react-native'; +import HTML, { + defaultHTMLElementModels, + TNodeChildrenRenderer, + splitBoxModelStyle, +} from 'react-native-render-html'; +import Config from '../CONFIG'; +import {getAuthToken} from '../libs/API'; +import {webViewStyles} from '../styles/StyleSheet'; +import fontFamily from '../styles/fontFamily'; +import AnchorForCommentsOnly from './AnchorForCommentsOnly'; +import ImageThumbnailWithModal from './ImageThumbnailWithModal'; +import InlineCodeBlock from './InlineCodeBlock'; + +const MAX_IMG_DIMENSIONS = 512; + +const EXTRA_FONTS = [ + fontFamily.GTA, + fontFamily.GTA_BOLD, + fontFamily.GTA_ITALIC, + fontFamily.MONOSPACE, + 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; + 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); + return ( + + ); +} + +function ImgRenderer({key, tnode}) { + const htmlAttribs = tnode.attributes; + + // Attaches authTokens as a URL parameter to load image attachments + let previewSource = htmlAttribs['data-expensify-source'] + ? `${htmlAttribs.src}?authToken=${getAuthToken()}` + : htmlAttribs.src; + + let source = htmlAttribs['data-expensify-source'] + ? `${htmlAttribs['data-expensify-source']}?authToken=${getAuthToken()}` + : 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 ( + + ); +} + +// Define default element models for these renderers. +AnchorRenderer.model = defaultHTMLElementModels.a; +CodeRenderer.model = defaultHTMLElementModels.code; +ImgRenderer.model = defaultHTMLElementModels.img; + +// Define the custom render methods +const renderers = { + a: AnchorRenderer, + code: CodeRenderer, + img: ImgRenderer, +}; + +const propTypes = { + html: PropTypes.string.isRequired, + debug: PropTypes.bool, +}; + +const RenderHTML = ({html, debug = false}) => { + const {width} = useWindowDimensions(); + const containerWidth = width * 0.8; + return ( + + ); +}; + +RenderHTML.displayName = 'RenderHTML'; +RenderHTML.propTypes = propTypes; +RenderHTML.defaultProps = { + debug: false, +}; + +export default RenderHTML; diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js index 363e9cc4bf73..0ec35ffcf925 100644 --- a/src/pages/home/report/ReportActionItemFragment.js +++ b/src/pages/home/report/ReportActionItemFragment.js @@ -1,18 +1,11 @@ import React from 'react'; -import HTML from 'react-native-render-html'; -import { - Linking, ActivityIndicator, View, Dimensions -} from 'react-native'; +import {ActivityIndicator, View} from 'react-native'; import PropTypes from 'prop-types'; import Str from 'expensify-common/lib/str'; import ReportActionFragmentPropTypes from './ReportActionFragmentPropTypes'; -import styles, {webViewStyles, colors} from '../../../styles/StyleSheet'; +import styles, {colors} from '../../../styles/StyleSheet'; +import RenderHTML from '../../../components/RenderHTML'; import Text from '../../../components/Text'; -import AnchorForCommentsOnly from '../../../components/AnchorForCommentsOnly'; -import InlineCodeBlock from '../../../components/InlineCodeBlock'; -import * as API from '../../../libs/API'; -import ImageThumbnailWithModal from '../../../components/ImageThumbnailWithModal'; -import Config from '../../../CONFIG'; const propTypes = { // The message fragment needing to be displayed @@ -31,85 +24,8 @@ const defaultProps = { }; class ReportActionItemFragment extends React.PureComponent { - constructor(props) { - super(props); - - // Define the custom render methods - // For tags, the attribute is used to be more cross-platform friendly - this.customRenderers = { - a: (htmlAttribs, children, convertedCSSStyles, passProps) => ( - - {children} - - ), - pre: (htmlAttribs, children, convertedCSSStyles, passProps) => ( - - {children} - - ), - code: (htmlAttribs, children, convertedCSSStyles, passProps) => ( - - {children} - - ), - blockquote: (htmlAttribs, children, convertedCSSStyles, passProps) => ( - - {children} - - ), - img: (htmlAttribs, children, convertedCSSStyles, passProps) => { - // Attaches authTokens as a URL parameter to load image attachments - let previewSource = htmlAttribs['data-expensify-source'] - ? `${htmlAttribs.src}?authToken=${API.getAuthToken()}` - : htmlAttribs.src; - - let source = htmlAttribs['data-expensify-source'] - ? `${htmlAttribs['data-expensify-source']}?authToken=${API.getAuthToken()}` - : 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 ( - - ); - }, - }; - } - render() { const {fragment} = this.props; - const maxImageDimensions = 512; - const windowWidth = Dimensions.get('window').width; switch (fragment.type) { case 'COMMENT': // If this is an attachment placeholder, return the placeholder component @@ -126,24 +42,11 @@ class ReportActionItemFragment extends React.PureComponent { } // Only render HTML if we have html in the fragment - return fragment.html !== fragment.text - ? ( - Linking.openURL(href)} - html={fragment.html} - imagesMaxWidth={Math.min(maxImageDimensions, windowWidth * 0.8)} - imagesInitialDimensions={{width: maxImageDimensions, height: maxImageDimensions}} - /> - ) - : ( - - {Str.htmlDecode(fragment.text)} - - ); + return fragment.html !== fragment.text ? ( + + ) : ( + {Str.htmlDecode(fragment.text)} + ); case 'TEXT': return (