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) {
Expand All @@ -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;
Expand Down
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';
Expand All @@ -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.
*/
Expand Down Expand Up @@ -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
Expand All @@ -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}
>
Expand Down
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';
Expand Down
49 changes: 30 additions & 19 deletions src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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>
);
};

Expand Down
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
Expand Up @@ -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) {
Expand Down Expand Up @@ -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()}
/>
);
}
Expand Down
Loading