diff --git a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.js b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.js
new file mode 100644
index 0000000000000..dec809e1266be
--- /dev/null
+++ b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.js
@@ -0,0 +1,56 @@
+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
new file mode 100644
index 0000000000000..a17f0a27ce4d6
--- /dev/null
+++ b/src/components/AnchorForAttachmentsOnly/anchorForAttachmentsOnlyPropTypes.js
@@ -0,0 +1,21 @@
+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
new file mode 100644
index 0000000000000..a71d65e969cd7
--- /dev/null
+++ b/src/components/AnchorForAttachmentsOnly/index.js
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 0000000000000..0a98ee0bb4ec8
--- /dev/null
+++ b/src/components/AnchorForAttachmentsOnly/index.native.js
@@ -0,0 +1,13 @@
+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
new file mode 100644
index 0000000000000..c93ecb416b644
--- /dev/null
+++ b/src/components/AnchorForCommentsOnly.js
@@ -0,0 +1,107 @@
+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
deleted file mode 100644
index dab9d2ea718f3..0000000000000
--- a/src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly/index.js
+++ /dev/null
@@ -1,104 +0,0 @@
-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
deleted file mode 100644
index 19e1b28774c2a..0000000000000
--- a/src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly/index.native.js
+++ /dev/null
@@ -1,95 +0,0 @@
-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
deleted file mode 100644
index fd8b2148a4af3..0000000000000
--- a/src/components/AnchorForCommentsOnly/anchorForCommentsOnlyPropTypes.js
+++ /dev/null
@@ -1,43 +0,0 @@
-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
deleted file mode 100644
index 55dc0b3154efb..0000000000000
--- a/src/components/AnchorForCommentsOnly/index.js
+++ /dev/null
@@ -1,40 +0,0 @@
-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 e93ba851d9964..9d395a8779b99 100644
--- a/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js
+++ b/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js
@@ -13,6 +13,7 @@ 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;
@@ -28,31 +29,22 @@ const AnchorRenderer = (props) => {
&& !attrHref.startsWith(CONFIG.EXPENSIFY.CONCIERGE_URL)
&& attrHref.replace(CONFIG.EXPENSIFY.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 (internalNewExpensifyPath) {
- return (
- Navigation.navigate(internalNewExpensifyPath)}
- >
-
-
- );
- }
+ 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 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 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 (!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.
@@ -61,17 +53,25 @@ const AnchorRenderer = (props) => {
return (
Linking.openURL(attrHref)}
+ onPress={navigateToLink}
>
);
}
+ 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 247489d497112..180d130029dd9 100644
--- a/src/stories/Banner.stories.js
+++ b/src/stories/Banner.stories.js
@@ -27,8 +27,15 @@ 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,
};