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 a851eb370577..7a8cf4bed42a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2913,6 +2913,91 @@
"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.1",
+ "resolved": "https://registry.npmjs.org/@native-html/transient-render-engine/-/transient-render-engine-3.6.1.tgz",
+ "integrity": "sha512-SkII7uJt399xn9W7ciPzEWZBnRYGKb2zlo3VLMkIEL9V8O1yQPxEfHERI4D7w8zU7W8H9+7S8IYpPEhBzIlzFQ==",
+ "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.1.0",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.1.0.tgz",
+ "integrity": "sha512-LsTgx/L5VpD+Q8lmsXSHW2WpA+eBlZ9HPf3erD1IoPF00/3JKHZ3BknUVA2QGDNu69ZNmyFmCWBSO45XjYKC5w=="
+ },
+ "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.3",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.4.3.tgz",
+ "integrity": "sha512-MDMfEjgtzHvRX7i21XQfkk/vfZbLOe0VJk8dDETkTTo3BTeH3NXz3Xvs94UQ+GzTw/GjRYKsfVKIIOheYX63fw==",
+ "requires": {
+ "dom-serializer": "^1.0.1",
+ "domelementtype": "^2.0.1",
+ "domhandler": "^4.0.0"
+ },
+ "dependencies": {
+ "domhandler": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.0.0.tgz",
+ "integrity": "sha512-KPTbnGQ1JeEMQyO1iYXoagsI6so/C96HZiFyByU3T6iAzpXn8EGEvct6unm1ZGoed8ByO2oirxgwxBmqKF9haA==",
+ "requires": {
+ "domelementtype": "^2.1.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 +3556,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 +5838,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 +6706,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 +6861,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 +7489,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 +7498,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 +7523,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 +7547,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 +7556,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 +8118,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 +9622,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 +11319,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 +11333,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 +18756,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 +19575,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.10",
+ "resolved": "https://registry.npmjs.org/react-native-render-html/-/react-native-render-html-6.0.0-alpha.10.tgz",
+ "integrity": "sha512-qXc8Osb8QuEFztfGqWTXltxRi8Pg84brqZSLYRpmC3ERfCXDCE8KQpGq6SEx8zTKk5uaurkMo/QBa07sRcQB2g==",
"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.1",
+ "@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 +22303,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 dc4db7019f92..02cd7ba68696 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-render-html": "^6.0.0-alpha.10",
+ "react-native-pdf": "^6.2.2",
"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..183f9404bd46
--- /dev/null
+++ b/src/components/RenderHTML.js
@@ -0,0 +1,170 @@
+/* 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 {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;
+
+ // 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 (
+
+ );
+}
+
+// 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 90365daa20cf..0ec35ffcf925 100644
--- a/src/pages/home/report/ReportActionItemFragment.js
+++ b/src/pages/home/report/ReportActionItemFragment.js
@@ -1,17 +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 ImageThumbnailWithModal from '../../../components/ImageThumbnailWithModal';
-import Config from '../../../CONFIG';
const propTypes = {
// The message fragment needing to be displayed
@@ -30,100 +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) => {
- // 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 (
-
- );
- },
- };
- }
-
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
@@ -140,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 (