From ac5ace5cb5ccab56ab3c4381048c50de3629d118 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Wed, 17 Aug 2022 08:16:50 -1000 Subject: [PATCH] Revert "Fix anchor renderer for links in report messages & clean up" --- .../BaseAnchorForAttachmentsOnly.js | 56 --------- .../anchorForAttachmentsOnlyPropTypes.js | 21 ---- .../AnchorForAttachmentsOnly/index.js | 12 -- .../AnchorForAttachmentsOnly/index.native.js | 13 --- src/components/AnchorForCommentsOnly.js | 107 ------------------ .../BaseAnchorForCommentsOnly/index.js | 104 +++++++++++++++++ .../BaseAnchorForCommentsOnly/index.native.js | 95 ++++++++++++++++ .../anchorForCommentsOnlyPropTypes.js | 43 +++++++ src/components/AnchorForCommentsOnly/index.js | 40 +++++++ .../HTMLRenderers/AnchorRenderer.js | 55 +++++---- src/stories/Banner.stories.js | 7 -- 11 files changed, 308 insertions(+), 245 deletions(-) delete mode 100644 src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.js delete mode 100644 src/components/AnchorForAttachmentsOnly/anchorForAttachmentsOnlyPropTypes.js delete mode 100644 src/components/AnchorForAttachmentsOnly/index.js delete mode 100644 src/components/AnchorForAttachmentsOnly/index.native.js delete mode 100644 src/components/AnchorForCommentsOnly.js create mode 100644 src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly/index.js create mode 100644 src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly/index.native.js create mode 100644 src/components/AnchorForCommentsOnly/anchorForCommentsOnlyPropTypes.js create mode 100644 src/components/AnchorForCommentsOnly/index.js diff --git a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.js b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.js deleted file mode 100644 index dec809e1266be..0000000000000 --- a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.js +++ /dev/null @@ -1,56 +0,0 @@ -import React from 'react'; -import {Pressable} from 'react-native'; -import * as anchorForAttachmentsOnlyPropTypes from './anchorForAttachmentsOnlyPropTypes'; -import AttachmentView from '../AttachmentView'; -import fileDownload from '../../libs/fileDownload'; -import addEncryptedAuthTokenToURL from '../../libs/addEncryptedAuthTokenToURL'; - -class BaseAnchorForAttachmentsOnly extends React.Component { - constructor(props) { - super(props); - - this.state = { - isDownloading: false, - }; - this.processDownload = this.processDownload.bind(this); - } - - /** - * Initiate file downloading and update downloading flags - * - * @param {String} href - * @param {String} fileName - */ - processDownload(href, fileName) { - this.setState({isDownloading: true}); - fileDownload(href, fileName).then(() => this.setState({isDownloading: false})); - } - - render() { - const source = addEncryptedAuthTokenToURL(this.props.source); - - return ( - { - if (this.state.isDownloading) { - return; - } - this.processDownload(source, this.props.displayName); - }} - > - - - ); - } -} - -BaseAnchorForAttachmentsOnly.propTypes = anchorForAttachmentsOnlyPropTypes.propTypes; -BaseAnchorForAttachmentsOnly.defaultProps = anchorForAttachmentsOnlyPropTypes.defaultProps; - -export default BaseAnchorForAttachmentsOnly; diff --git a/src/components/AnchorForAttachmentsOnly/anchorForAttachmentsOnlyPropTypes.js b/src/components/AnchorForAttachmentsOnly/anchorForAttachmentsOnlyPropTypes.js deleted file mode 100644 index a17f0a27ce4d6..0000000000000 --- a/src/components/AnchorForAttachmentsOnly/anchorForAttachmentsOnlyPropTypes.js +++ /dev/null @@ -1,21 +0,0 @@ -import PropTypes from 'prop-types'; -import stylePropTypes from '../../styles/stylePropTypes'; - -const propTypes = { - /** The URL of the attachment */ - source: PropTypes.string, - - /** Filename for attachments. */ - displayName: PropTypes.string, - - /** Any additional styles to apply */ - style: stylePropTypes, -}; - -const defaultProps = { - source: '', - style: {}, - displayName: '', -}; - -export {propTypes, defaultProps}; diff --git a/src/components/AnchorForAttachmentsOnly/index.js b/src/components/AnchorForAttachmentsOnly/index.js deleted file mode 100644 index a71d65e969cd7..0000000000000 --- a/src/components/AnchorForAttachmentsOnly/index.js +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; -import * as anchorForAttachmentsOnlyPropTypes from './anchorForAttachmentsOnlyPropTypes'; -import BaseAnchorForAttachmentsOnly from './BaseAnchorForAttachmentsOnly'; - -// eslint-disable-next-line react/jsx-props-no-spreading -const AnchorForAttachmentsOnly = props => ; - -AnchorForAttachmentsOnly.propTypes = anchorForAttachmentsOnlyPropTypes.propTypes; -AnchorForAttachmentsOnly.defaultProps = anchorForAttachmentsOnlyPropTypes.defaultProps; -AnchorForAttachmentsOnly.displayName = 'AnchorForAttachmentsOnly'; - -export default AnchorForAttachmentsOnly; diff --git a/src/components/AnchorForAttachmentsOnly/index.native.js b/src/components/AnchorForAttachmentsOnly/index.native.js deleted file mode 100644 index 0a98ee0bb4ec8..0000000000000 --- a/src/components/AnchorForAttachmentsOnly/index.native.js +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; -import * as anchorForAttachmentsOnlyPropTypes from './anchorForAttachmentsOnlyPropTypes'; -import BaseAnchorForAttachmentsOnly from './BaseAnchorForAttachmentsOnly'; -import styles from '../../styles/styles'; - -// eslint-disable-next-line react/jsx-props-no-spreading -const AnchorForAttachmentsOnly = props => ; - -AnchorForAttachmentsOnly.propTypes = anchorForAttachmentsOnlyPropTypes.propTypes; -AnchorForAttachmentsOnly.defaultProps = anchorForAttachmentsOnlyPropTypes.defaultProps; -AnchorForAttachmentsOnly.displayName = 'AnchorForAttachmentsOnly'; - -export default AnchorForAttachmentsOnly; diff --git a/src/components/AnchorForCommentsOnly.js b/src/components/AnchorForCommentsOnly.js deleted file mode 100644 index c93ecb416b644..0000000000000 --- a/src/components/AnchorForCommentsOnly.js +++ /dev/null @@ -1,107 +0,0 @@ -import _ from 'underscore'; -import React from 'react'; -import {StyleSheet} from 'react-native'; -import lodashGet from 'lodash/get'; -import Str from 'expensify-common/lib/str'; -import PropTypes from 'prop-types'; -import Text from './Text'; -import PressableWithSecondaryInteraction from './PressableWithSecondaryInteraction'; -import * as ReportActionContextMenu from '../pages/home/report/ContextMenu/ReportActionContextMenu'; -import * as ContextMenuActions from '../pages/home/report/ContextMenu/ContextMenuActions'; -import Tooltip from './Tooltip'; -import canUseTouchScreen from '../libs/canUseTouchscreen'; -import styles from '../styles/styles'; -import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; - -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, - - /** Anchor text of URLs or emails. */ - displayName: PropTypes.string, - - /** Any additional styles to apply */ - // eslint-disable-next-line react/forbid-prop-types - style: PropTypes.any, - - /** Press handler for the link, when not passed, default href is used to create a link like behaviour */ - onPress: PropTypes.func, - - ...windowDimensionsPropTypes, -}; - -const defaultProps = { - href: '', - rel: '', - target: '', - children: null, - style: {}, - displayName: '', - onPress: undefined, -}; - -/* - * This is a default anchor component for regular links. - */ -const BaseAnchorForCommentsOnly = (props) => { - let linkRef; - const rest = _.omit(props, _.keys(propTypes)); - const linkProps = {}; - if (_.isFunction(props.onPress)) { - linkProps.onPress = props.onPress; - } else { - linkProps.href = props.href; - } - const defaultTextStyle = canUseTouchScreen() || props.isSmallScreenWidth ? {} : styles.userSelectText; - - return ( - { - ReportActionContextMenu.showContextMenu( - Str.isValidEmail(props.displayName) ? ContextMenuActions.CONTEXT_MENU_TYPES.EMAIL : ContextMenuActions.CONTEXT_MENU_TYPES.LINK, - event, - props.href, - lodashGet(linkRef, 'current'), - ); - } - } - > - - linkRef = el} - style={StyleSheet.flatten([props.style, defaultTextStyle])} - accessibilityRole="link" - hrefAttrs={{ - rel: props.rel, - target: props.target, - }} - // eslint-disable-next-line react/jsx-props-no-spreading - {...linkProps} - // eslint-disable-next-line react/jsx-props-no-spreading - {...rest} - > - {props.children} - - - - ); -}; - -BaseAnchorForCommentsOnly.propTypes = propTypes; -BaseAnchorForCommentsOnly.defaultProps = defaultProps; -BaseAnchorForCommentsOnly.displayName = 'BaseAnchorForCommentsOnly'; - -export default withWindowDimensions(BaseAnchorForCommentsOnly); diff --git a/src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly/index.js b/src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly/index.js new file mode 100644 index 0000000000000..dab9d2ea718f3 --- /dev/null +++ b/src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly/index.js @@ -0,0 +1,104 @@ +import _ from 'underscore'; +import React from 'react'; +import {Pressable, StyleSheet} from 'react-native'; +import lodashGet from 'lodash/get'; +import Str from 'expensify-common/lib/str'; +import Text from '../../Text'; +import {propTypes, defaultProps} from '../anchorForCommentsOnlyPropTypes'; +import PressableWithSecondaryInteraction from '../../PressableWithSecondaryInteraction'; +import * as ReportActionContextMenu from '../../../pages/home/report/ContextMenu/ReportActionContextMenu'; +import * as ContextMenuActions from '../../../pages/home/report/ContextMenu/ContextMenuActions'; +import AttachmentView from '../../AttachmentView'; +import fileDownload from '../../../libs/fileDownload'; +import Tooltip from '../../Tooltip'; +import canUseTouchScreen from '../../../libs/canUseTouchscreen'; +import styles from '../../../styles/styles'; + +/* + * This is a default anchor component for regular links. + */ +class BaseAnchorForCommentsOnly extends React.Component { + constructor(props) { + super(props); + + this.state = { + isDownloading: false, + }; + this.processDownload = this.processDownload.bind(this); + } + + /** + * Initiate file downloading and update downloading flags + * + * @param {String} href + * @param {String} fileName + */ + processDownload(href, fileName) { + this.setState({isDownloading: true}); + fileDownload(href, fileName).then(() => this.setState({isDownloading: false})); + } + + render() { + let linkRef; + const rest = _.omit(this.props, _.keys(propTypes)); + const defaultTextStyle = canUseTouchScreen() || this.props.isSmallScreenWidth ? {} : styles.userSelectText; + + return ( + this.props.isAttachment + ? ( + { + if (this.state.isDownloading) { + return; + } + this.processDownload(this.props.href, this.props.displayName); + }} + > + + + ) + : ( + { + ReportActionContextMenu.showContextMenu( + Str.isValidEmail(this.props.displayName) ? ContextMenuActions.CONTEXT_MENU_TYPES.EMAIL : ContextMenuActions.CONTEXT_MENU_TYPES.LINK, + event, + this.props.href, + lodashGet(linkRef, 'current'), + ); + } + } + > + + linkRef = el} + style={StyleSheet.flatten([this.props.style, defaultTextStyle])} + accessibilityRole="link" + href={this.props.href} + hrefAttrs={{ + rel: this.props.rel, + target: this.props.target, + }} + // eslint-disable-next-line react/jsx-props-no-spreading + {...rest} + > + {this.props.children} + + + + ) + ); + } +} + +BaseAnchorForCommentsOnly.propTypes = propTypes; +BaseAnchorForCommentsOnly.defaultProps = defaultProps; +BaseAnchorForCommentsOnly.displayName = 'BaseAnchorForCommentsOnly'; + +export default BaseAnchorForCommentsOnly; diff --git a/src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly/index.native.js b/src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly/index.native.js new file mode 100644 index 0000000000000..19e1b28774c2a --- /dev/null +++ b/src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly/index.native.js @@ -0,0 +1,95 @@ +import _ from 'underscore'; +import React from 'react'; +import lodashGet from 'lodash/get'; +import {Linking, StyleSheet, Pressable} from 'react-native'; +import Str from 'expensify-common/lib/str'; +import {propTypes, defaultProps} from '../anchorForCommentsOnlyPropTypes'; +import fileDownload from '../../../libs/fileDownload'; +import Text from '../../Text'; +import PressableWithSecondaryInteraction from '../../PressableWithSecondaryInteraction'; +import * as ReportActionContextMenu from '../../../pages/home/report/ContextMenu/ReportActionContextMenu'; +import * as ContextMenuActions from '../../../pages/home/report/ContextMenu/ContextMenuActions'; +import AttachmentView from '../../AttachmentView'; +import styles from '../../../styles/styles'; + +/* + * This is a default anchor component for regular links. + */ +class BaseAnchorForCommentsOnly extends React.Component { + constructor(props) { + super(props); + + this.state = { + isDownloading: false, + }; + this.processDownload = this.processDownload.bind(this); + } + + /** + * Initiate file downloading and update downloading flags + * + * @param {String} href + * @param {String} fileName + */ + processDownload(href, fileName) { + this.setState({isDownloading: true}); + fileDownload(href, fileName).then(() => this.setState({isDownloading: false})); + } + + render() { + let linkRef; + const rest = _.omit(this.props, _.keys(propTypes)); + return ( + this.props.isAttachment + ? ( + { + if (this.state.isDownloading) { + return; + } + this.processDownload(this.props.href, this.props.displayName); + }} + > + + + ) + : ( + { + ReportActionContextMenu.showContextMenu( + Str.isValidEmail(this.props.displayName) ? ContextMenuActions.CONTEXT_MENU_TYPES.EMAIL : ContextMenuActions.CONTEXT_MENU_TYPES.LINK, + event, + this.props.href, + lodashGet(linkRef, 'current'), + ); + } + } + onPress={() => Linking.openURL(this.props.href)} + > + linkRef = el} + style={StyleSheet.flatten(this.props.style)} + // eslint-disable-next-line react/jsx-props-no-spreading + {...rest} + > + {this.props.children} + + + ) + ); + } +} + +BaseAnchorForCommentsOnly.propTypes = propTypes; +BaseAnchorForCommentsOnly.defaultProps = defaultProps; +BaseAnchorForCommentsOnly.displayName = 'BaseAnchorForCommentsOnly'; + +export default BaseAnchorForCommentsOnly; diff --git a/src/components/AnchorForCommentsOnly/anchorForCommentsOnlyPropTypes.js b/src/components/AnchorForCommentsOnly/anchorForCommentsOnlyPropTypes.js new file mode 100644 index 0000000000000..fd8b2148a4af3 --- /dev/null +++ b/src/components/AnchorForCommentsOnly/anchorForCommentsOnlyPropTypes.js @@ -0,0 +1,43 @@ +import PropTypes from 'prop-types'; + +/** + * 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, + + /** Flag to differentiate attachments and hyperlink. Base on flag link will be treated as a file download or a regular hyperlink */ + isAttachment: PropTypes.bool, + + /** Any children to display */ + children: PropTypes.node, + + /** Filename in case of attachments, anchor text in case of URLs or emails. */ + displayName: PropTypes.string, + + /** Any additional styles to apply */ + // eslint-disable-next-line react/forbid-prop-types + style: PropTypes.any, + +}; + +const defaultProps = { + href: '', + rel: '', + target: '', + isAttachment: false, + children: null, + style: {}, + displayName: '', +}; + +export {propTypes, defaultProps}; diff --git a/src/components/AnchorForCommentsOnly/index.js b/src/components/AnchorForCommentsOnly/index.js new file mode 100644 index 0000000000000..55dc0b3154efb --- /dev/null +++ b/src/components/AnchorForCommentsOnly/index.js @@ -0,0 +1,40 @@ +import _ from 'underscore'; +import React from 'react'; +import PropTypes from 'prop-types'; +import * as anchorForCommentsOnlyPropTypes from './anchorForCommentsOnlyPropTypes'; +import BaseAnchorForCommentsOnly from './BaseAnchorForCommentsOnly'; +import addEncryptedAuthTokenToURL from '../../libs/addEncryptedAuthTokenToURL'; + +const propTypes = { + /** Do we need an auth token to view this link or download the remote resource? */ + isAuthTokenRequired: PropTypes.bool, + + // eslint-disable-next-line react/forbid-foreign-prop-types + ...anchorForCommentsOnlyPropTypes.propTypes, +}; + +const defaultProps = { + isAuthTokenRequired: false, + ...anchorForCommentsOnlyPropTypes.defaultProps, +}; + +/* + * This component acts as a switch between AnchorWithAuthToken and default BaseAnchorForCommentsOnly. + * It is an optimization so that we can attach an auth token to a URL when one is required, + * without using Onyx.connect on links that don't need an authToken. + */ +const AnchorForCommentsOnly = (props) => { + const propsToPass = _.omit(props, 'isAuthTokenRequired'); + if (props.isAuthTokenRequired) { + propsToPass.href = addEncryptedAuthTokenToURL(props.href); + propsToPass.isAttachment = true; + } + // eslint-disable-next-line react/jsx-props-no-spreading + return ; +}; + +AnchorForCommentsOnly.propTypes = propTypes; +AnchorForCommentsOnly.defaultProps = defaultProps; +AnchorForCommentsOnly.displayName = 'AnchorForCommentsOnly'; + +export default AnchorForCommentsOnly; diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js index 9d395a8779b99..e93ba851d9964 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js @@ -13,7 +13,6 @@ import CONST from '../../../CONST'; import styles from '../../../styles/styles'; import Navigation from '../../../libs/Navigation/Navigation'; import AnchorForCommentsOnly from '../../AnchorForCommentsOnly'; -import AnchorForAttachmentsOnly from '../../AnchorForAttachmentsOnly'; const AnchorRenderer = (props) => { const htmlAttribs = props.tnode.attributes; @@ -29,22 +28,31 @@ const AnchorRenderer = (props) => { && !attrHref.startsWith(CONFIG.EXPENSIFY.CONCIERGE_URL) && attrHref.replace(CONFIG.EXPENSIFY.EXPENSIFY_URL, ''); - const navigateToLink = () => { - // 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 (internalNewExpensifyPath) { - Navigation.navigate(internalNewExpensifyPath); - return; - } + // 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 (internalNewExpensifyPath) { + return ( + Navigation.navigate(internalNewExpensifyPath)} + > + + + ); + } - // If we are handling an old dot Expensify link we need to open it with openOldDotLink() so we can navigate to it with the user already logged in. - // As attachments also use expensify.com we don't want it working the same as links. - if (internalExpensifyPath && !isAttachment) { - Link.openOldDotLink(internalExpensifyPath); - return; - } - Linking.openURL(attrHref); - }; + // If we are handling an old dot Expensify link (excluding Concierge) we need to open it with openOldDotLink() so we can navigate to it with the user already logged in. + // As attachments also use expensify.com we don't want it working the same as links. + if (internalExpensifyPath && !isAttachment) { + return ( + Link.openOldDotLink(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. @@ -53,25 +61,17 @@ const AnchorRenderer = (props) => { return ( Linking.openURL(attrHref)} > ); } - if (isAttachment) { - return ( - - ); - } - return ( { style={{...props.style, ...parentStyle}} key={props.key} displayName={displayName} - - // Only pass the press handler for internal links, for public links fallback to default link handling - onPress={internalNewExpensifyPath || internalExpensifyPath ? navigateToLink : undefined} > diff --git a/src/stories/Banner.stories.js b/src/stories/Banner.stories.js index 180d130029dd9..247489d497112 100644 --- a/src/stories/Banner.stories.js +++ b/src/stories/Banner.stories.js @@ -27,15 +27,8 @@ HTMLBanner.args = { shouldRenderHTML: true, }; -const BannerWithLink = Template.bind({}); -BannerWithLink.args = { - text: 'This is a informational banner containing internal Link and public link', - shouldRenderHTML: true, -}; - export default story; export { InfoBanner, HTMLBanner, - BannerWithLink, };