Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix context menu when long press on url and email link #12987

Merged
merged 12 commits into from
Jan 12, 2023
Merged
Original file line number Diff line number Diff line change
@@ -1,9 +1,30 @@
import React from 'react';
import {Pressable} from 'react-native';
import * as anchorForAttachmentsOnlyPropTypes from './anchorForAttachmentsOnlyPropTypes';
import PropTypes from 'prop-types';
import {
propTypes as anchorForAttachmentsOnlyPropTypes,
defaultProps as anchorForAttachmentsOnlyDefaultProps,
} from './anchorForAttachmentsOnlyPropTypes';
import AttachmentView from '../AttachmentView';
import fileDownload from '../../libs/fileDownload';
import addEncryptedAuthTokenToURL from '../../libs/addEncryptedAuthTokenToURL';
import {ShowContextMenuContext, showContextMenuForReport} from '../ShowContextMenuContext';

const propTypes = {
/** Press in handler for the link */
onPressIn: PropTypes.func,

/** Press out handler for the link */
onPressOut: PropTypes.func,

...anchorForAttachmentsOnlyPropTypes,
};

const defaultProps = {
onPressIn: undefined,
onPressOut: undefined,
...anchorForAttachmentsOnlyDefaultProps,
};

class BaseAnchorForAttachmentsOnly extends React.Component {
constructor(props) {
@@ -30,27 +51,45 @@ class BaseAnchorForAttachmentsOnly extends React.Component {
const source = addEncryptedAuthTokenToURL(this.props.source);

return (
<Pressable
style={this.props.style}
onPress={() => {
if (this.state.isDownloading) {
return;
}
this.processDownload(source, this.props.displayName);
}}
>
<AttachmentView
sourceURL={source}
file={{name: this.props.displayName}}
shouldShowDownloadIcon
shouldShowLoadingSpinnerIcon={this.state.isDownloading}
/>
</Pressable>
<ShowContextMenuContext.Consumer>
{({
anchor,
reportID,
action,
checkIfContextMenuActive,
}) => (
<Pressable
style={this.props.style}
onPress={() => {
if (this.state.isDownloading) {
return;
}
this.processDownload(source, this.props.displayName);
}}
onPressIn={this.props.onPressIn}
onPressOut={this.props.onPressOut}
onLongPress={event => showContextMenuForReport(
event,
anchor,
reportID,
action,
checkIfContextMenuActive,
)}
>
<AttachmentView
sourceURL={source}
file={{name: this.props.displayName}}
shouldShowDownloadIcon
shouldShowLoadingSpinnerIcon={this.state.isDownloading}
/>
</Pressable>
)}
</ShowContextMenuContext.Consumer>
);
}
}

BaseAnchorForAttachmentsOnly.propTypes = anchorForAttachmentsOnlyPropTypes.propTypes;
BaseAnchorForAttachmentsOnly.defaultProps = anchorForAttachmentsOnlyPropTypes.defaultProps;
BaseAnchorForAttachmentsOnly.propTypes = propTypes;
BaseAnchorForAttachmentsOnly.defaultProps = defaultProps;

export default BaseAnchorForAttachmentsOnly;
12 changes: 10 additions & 2 deletions src/components/AnchorForAttachmentsOnly/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import React from 'react';
import * as anchorForAttachmentsOnlyPropTypes from './anchorForAttachmentsOnlyPropTypes';
import BaseAnchorForAttachmentsOnly from './BaseAnchorForAttachmentsOnly';
import * as DeviceCapabilities from '../../libs/DeviceCapabilities';
import ControlSelection from '../../libs/ControlSelection';

// eslint-disable-next-line react/jsx-props-no-spreading
const AnchorForAttachmentsOnly = props => <BaseAnchorForAttachmentsOnly {...props} />;
const AnchorForAttachmentsOnly = props => (
<BaseAnchorForAttachmentsOnly
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
onPressIn={() => DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()}
onPressOut={() => ControlSelection.unblock()}
/>
);

AnchorForAttachmentsOnly.propTypes = anchorForAttachmentsOnlyPropTypes.propTypes;
AnchorForAttachmentsOnly.defaultProps = anchorForAttachmentsOnlyPropTypes.defaultProps;
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import _ from 'underscore';
import React from 'react';
import {StyleSheet} from 'react-native';
import PropTypes from 'prop-types';
import lodashGet from 'lodash/get';
import Str from 'expensify-common/lib/str';
import Text from '../Text';
@@ -11,13 +12,28 @@ import Tooltip from '../Tooltip';
import * as DeviceCapabilities from '../../libs/DeviceCapabilities';
import styles from '../../styles/styles';
import withWindowDimensions, {windowDimensionsPropTypes} from '../withWindowDimensions';
import {propTypes as anchorForCommentsOnlyPropTypes, defaultProps} from './anchorForCommentsOnlyPropTypes';
import {
propTypes as anchorForCommentsOnlyPropTypes,
defaultProps as anchorForCommentsOnlyDefaultProps,
} from './anchorForCommentsOnlyPropTypes';

const propTypes = {
/** Press in handler for the link */
onPressIn: PropTypes.func,

/** Press out handler for the link */
onPressOut: PropTypes.func,

...anchorForCommentsOnlyPropTypes,
...windowDimensionsPropTypes,
};

const defaultProps = {
onPressIn: undefined,
onPressOut: undefined,
...anchorForCommentsOnlyDefaultProps,
};

/*
* This is a default anchor component for regular links.
*/
@@ -45,6 +61,9 @@ const BaseAnchorForCommentsOnly = (props) => {
);
}
}
onPress={linkProps.onPress}
onPressIn={props.onPressIn}
onPressOut={props.onPressOut}
>
<Tooltip containerStyles={[styles.dInline]} text={Str.isValidEmail(props.displayName) ? '' : props.href}>
<Text
@@ -55,8 +74,7 @@ const BaseAnchorForCommentsOnly = (props) => {
rel: props.rel,
target: props.target,
}}
// eslint-disable-next-line react/jsx-props-no-spreading
{...linkProps}
href={linkProps.href}
// eslint-disable-next-line react/jsx-props-no-spreading
{...rest}
>
13 changes: 11 additions & 2 deletions src/components/AnchorForCommentsOnly/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import React from 'react';
import * as anchorForCommentsOnlyPropTypes from './anchorForCommentsOnlyPropTypes';
import BaseAnchorForCommentsOnly from './BaseAnchorForCommentsOnly';
import * as DeviceCapabilities from '../../libs/DeviceCapabilities';
import ControlSelection from '../../libs/ControlSelection';

const AnchorForCommentsOnly = props => (
<BaseAnchorForCommentsOnly
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
onPressIn={() => DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()}
onPressOut={() => ControlSelection.unblock()}
/>
);

// eslint-disable-next-line react/jsx-props-no-spreading
const AnchorForCommentsOnly = props => <BaseAnchorForCommentsOnly {...props} />;
AnchorForCommentsOnly.propTypes = anchorForCommentsOnlyPropTypes.propTypes;
AnchorForCommentsOnly.defaultProps = anchorForCommentsOnlyPropTypes.defaultProps;
AnchorForCommentsOnly.displayName = 'AnchorForCommentsOnly';
49 changes: 30 additions & 19 deletions src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ import styles from '../../../styles/styles';
import ThumbnailImage from '../../ThumbnailImage';
import PressableWithoutFocus from '../../PressableWithoutFocus';
import CONST from '../../../CONST';
import {ShowContextMenuContext, showContextMenuForReport} from '../../ShowContextMenuContext';

const ImageRenderer = (props) => {
const htmlAttribs = props.tnode.attributes;
@@ -57,27 +58,37 @@ const ImageRenderer = (props) => {
imageHeight={imageHeight}
/>
) : (
<AttachmentModal
allowDownload
sourceURL={source}
isAuthTokenRequired={isAttachment}
originalFileName={originalFileName}
>
{({show}) => (
<PressableWithoutFocus
style={styles.noOutline}
onPress={show}
<ShowContextMenuContext.Consumer>
{({
anchor,
reportID,
action,
checkIfContextMenuActive,
}) => (
<AttachmentModal
allowDownload
sourceURL={source}
isAuthTokenRequired={isAttachment}
originalFileName={originalFileName}
>
<ThumbnailImage
previewSourceURL={previewSource}
style={styles.webViewStyles.tagStyles.img}
isAuthTokenRequired={isAttachment}
imageWidth={imageWidth}
imageHeight={imageHeight}
/>
</PressableWithoutFocus>
{({show}) => (
<PressableWithoutFocus
style={styles.noOutline}
onPress={show}
onLongPress={event => showContextMenuForReport(event, anchor, reportID, action, checkIfContextMenuActive)}
>
<ThumbnailImage
previewSourceURL={previewSource}
style={styles.webViewStyles.tagStyles.img}
isAuthTokenRequired={isAttachment}
imageWidth={imageWidth}
imageHeight={imageHeight}
/>
</PressableWithoutFocus>
)}
</AttachmentModal>
)}
</AttachmentModal>
</ShowContextMenuContext.Consumer>
);
};

Original file line number Diff line number Diff line change
@@ -1,28 +1,59 @@
import React, {forwardRef} from 'react';
import {ScrollView} from 'react-native-gesture-handler';
import {View} from 'react-native';
import {Pressable} from 'react-native';
import PropTypes from 'prop-types';
import _ from 'underscore';
import htmlRendererPropTypes from '../htmlRendererPropTypes';
import withLocalize from '../../../withLocalize';
import {ShowContextMenuContext, showContextMenuForReport} from '../../../ShowContextMenuContext';

const propTypes = {
/** Press in handler for the code block */
onPressIn: PropTypes.func,

/** Press out handler for the code block */
onPressOut: PropTypes.func,

...htmlRendererPropTypes,
};

const defaultProps = {
onPressIn: undefined,
onPressOut: undefined,
};

const BasePreRenderer = forwardRef((props, ref) => {
const TDefaultRenderer = props.TDefaultRenderer;
const defaultRendererProps = _.omit(props, ['TDefaultRenderer']);
const defaultRendererProps = _.omit(props, ['TDefaultRenderer', 'onPressIn', 'onPressOut', 'onLongPress']);

return (
<ScrollView
ref={ref}
horizontal
>
<View onStartShouldSetResponder={() => true}>
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
<TDefaultRenderer {...defaultRendererProps} />
</View>
<ShowContextMenuContext.Consumer>
{({
anchor,
reportID,
action,
checkIfContextMenuActive,
}) => (
<Pressable
onPressIn={props.onPressIn}
onPressOut={props.onPressOut}
onLongPress={event => showContextMenuForReport(event, anchor, reportID, action, checkIfContextMenuActive)}
>
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
<TDefaultRenderer {...defaultRendererProps} />
</Pressable>
)}
</ShowContextMenuContext.Consumer>
</ScrollView>
);
});

BasePreRenderer.displayName = 'BasePreRenderer';
BasePreRenderer.propTypes = htmlRendererPropTypes;
BasePreRenderer.propTypes = propTypes;
BasePreRenderer.defaultProps = defaultProps;

export default withLocalize(BasePreRenderer);
Original file line number Diff line number Diff line change
@@ -3,6 +3,8 @@ import _ from 'underscore';
import withLocalize from '../../../withLocalize';
import htmlRendererPropTypes from '../htmlRendererPropTypes';
import BasePreRenderer from './BasePreRenderer';
import * as DeviceCapabilities from '../../../../libs/DeviceCapabilities';
import ControlSelection from '../../../../libs/ControlSelection';

class PreRenderer extends React.Component {
constructor(props) {
@@ -58,6 +60,8 @@ class PreRenderer extends React.Component {
// eslint-disable-next-line react/jsx-props-no-spreading
{...this.props}
ref={el => this.ref = el}
onPressIn={() => DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()}
onPressOut={() => ControlSelection.unblock()}
/>
);
}
Loading