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

Andrew 3313 link context menu #3931

Merged
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import React from 'react';
import {StyleSheet} from 'react-native';
import Text from '../../Text';
import {propTypes, defaultProps} from '../anchorForCommentsOnlyPropTypes';
import PressableWithSecondaryInteraction from '../../PressableWithSecondaryInteraction';
import {showContextMenu} from '../../../pages/home/report/ContextMenu/ReportActionContextMenu';
import {contextMenuTypes} from '../../../pages/home/report/ContextMenu/ContextMenuActions';

const linkRef = React.createRef();

/*
* This is a default anchor component for regular links.
Expand All @@ -14,16 +19,30 @@ const BaseAnchorForCommentsOnly = ({
style,
...props
}) => (
<Text
style={StyleSheet.flatten(style)}
accessibilityRole="link"
href={href}
hrefAttrs={{rel, target}}
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
<PressableWithSecondaryInteraction
onSecondaryInteraction={
(event) => {
showContextMenu(
contextMenuTypes.link,
event,
href,
linkRef.current,
);
}
}
>
{children}
</Text>
<Text
ref={linkRef}
style={StyleSheet.flatten(style)}
accessibilityRole="link"
href={href}
hrefAttrs={{rel, target}}
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
>
{children}
</Text>
</PressableWithSecondaryInteraction>
);

BaseAnchorForCommentsOnly.propTypes = propTypes;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ import {Linking, StyleSheet} from 'react-native';
import {propTypes, defaultProps} from '../anchorForCommentsOnlyPropTypes';
import fileDownload from '../../../libs/fileDownload';
import Text from '../../Text';
import PressableWithSecondaryInteraction from '../../PressableWithSecondaryInteraction';
import {showContextMenu} from '../../../pages/home/report/ContextMenu/ReportActionContextMenu';
import {contextMenuTypes} from '../../../pages/home/report/ContextMenu/ContextMenuActions';

const linkRef = React.createRef();
roryabraham marked this conversation as resolved.
Show resolved Hide resolved


/*
* This is a default anchor component for regular links.
Expand All @@ -14,14 +20,28 @@ const BaseAnchorForCommentsOnly = ({
shouldDownloadFile,
...props
}) => (
<Text
style={StyleSheet.flatten(style)}
<PressableWithSecondaryInteraction
onSecondaryInteraction={
(event) => {
showContextMenu(
contextMenuTypes.link,
event,
href,
linkRef.current,
);
}
}
onPress={() => (shouldDownloadFile ? fileDownload(href) : Linking.openURL(href))}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, It should have an action on the contextMenu as well. Open Link which can follow what you are doing now.

Not sure if the click to open feature is yet planned. cc: @roryabraham.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@roryabraham can you clarify @parasharrajat 's comment here? Doesn't seem like a bad idea to add Open Link to the context menu however in my testing a single/normal press opens the link. Not sure the exact scenario where they would get a download instead of going to the link.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I missed this. Yeah, I don't think we really need Open Link in the context menu, but since you've already added it we can leave it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's ask in #expensify-open-source

// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
>
{children}
</Text>
<Text
ref={linkRef}
style={StyleSheet.flatten(style)}
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
>
{children}
</Text>
</PressableWithSecondaryInteraction>
);

BaseAnchorForCommentsOnly.propTypes = propTypes;
Expand Down
4 changes: 2 additions & 2 deletions src/components/CommunicationsLink.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ const CommunicationsLink = props => (
{props.children}
<ContextMenuItem
icon={ClipboardIcon}
text={props.translate('contextMenuItem.copyToClipboard')}
text={props.translate('reportActionContextMenu.copyToClipboard')}
successIcon={Checkmark}
successText={props.translate('contextMenuItem.copied')}
successText={props.translate('reportActionContextMenu.copied')}
isMini
autoReset
onPress={() => Clipboard.setString(props.value)}
Expand Down
2 changes: 2 additions & 0 deletions src/components/PressableWithSecondaryInteraction/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class PressableWithSecondaryInteraction extends Component {
*/
executeSecondaryInteractionOnContextMenu(e) {
const selection = window.getSelection().toString();
e.stopPropagation();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, why do we need this line? It seems to be unnecessary. cc @roryabraham @parasharrajat @puneetlath @Drewfergusson

Copy link
Member

@parasharrajat parasharrajat Sep 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This prevents event bubbling when this component is nested. We do not want nested and parent component both to trigger the action.

This happens for nested links in the message. When link context menu is triggered, message context menu should not override it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, so you mean by this case?
image

if (this.props.preventDefaultContentMenu) {
e.preventDefault();
}
Expand All @@ -44,6 +45,7 @@ class PressableWithSecondaryInteraction extends Component {
delayLongPress={200}
onLongPress={this.props.onSecondaryInteraction}
onPressOut={this.props.onPressOut}
onPress={this.props.onPress}
roryabraham marked this conversation as resolved.
Show resolved Hide resolved
ref={el => this.pressableRef = el}
// eslint-disable-next-line react/jsx-props-no-spreading
{...defaultPressableProps}
Expand Down
5 changes: 2 additions & 3 deletions src/languages/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,12 +119,11 @@ export default {
roomIsArchived: 'This chat room has been deleted',
localTime: ({user, time}) => `It's ${time} for ${user}`,
},
contextMenuItem: {
reportActionContextMenu: {
copyToClipboard: 'Copy to Clipboard',
copied: 'Copied!',
},
reportActionContextMenu: {
copyLink: 'Copy Link',
copyURLToClipboard: 'Copy URL to Clipboard',
markAsUnread: 'Mark as Unread',
editComment: 'Edit Comment',
deleteComment: 'Delete Comment',
Expand Down
5 changes: 2 additions & 3 deletions src/languages/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,12 +119,11 @@ export default {
roomIsArchived: 'Esta sala de chat ha sido eliminada',
localTime: ({user, time}) => `Son las ${time} para ${user}`,
},
contextMenuItem: {
reportActionContextMenu: {
copyToClipboard: 'Copiar al Portapapeles',
copied: '¡Copiado!',
},
reportActionContextMenu: {
copyLink: 'Copiar Enlace',
copyURLToClipboard: 'Copiar URL al Portapapeles',
markAsUnread: 'Marcar como no leído',
editComment: 'Editar Commentario',
deleteComment: 'Eliminar Comentario',
Expand Down
47 changes: 27 additions & 20 deletions src/pages/home/report/ContextMenu/BaseReportActionContextMenu.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import {View} from 'react-native';
import _ from 'underscore';
import PropTypes from 'prop-types';
import getReportActionContextMenuStyles from '../../../../styles/getReportActionContextMenuStyles';
import ContextMenuItem from '../../../../components/ContextMenuItem';
import {
Expand All @@ -11,38 +12,44 @@ import withLocalize, {withLocalizePropTypes} from '../../../../components/withLo
import ContextMenuActions from './ContextMenuActions';

const propTypes = {
type: PropTypes.string,
roryabraham marked this conversation as resolved.
Show resolved Hide resolved
...GenericReportActionContextMenuPropTypes,
...withLocalizePropTypes,
};

class BaseReportActionContextMenu extends React.Component {
constructor(props) {
super(props);

this.wrapperStyle = getReportActionContextMenuStyles(this.props.isMini);
}

render() {
return this.props.isVisible && (
<View style={this.wrapperStyle}>
{_.map(ContextMenuActions, contextAction => contextAction.shouldShow(this.props.reportAction) && (
<ContextMenuItem
icon={contextAction.icon}
text={this.props.translate(contextAction.textTranslateKey)}
successIcon={contextAction.successIcon}
successText={contextAction.successTextTranslateKey
? this.props.translate(contextAction.successTextTranslateKey)
: undefined}
isMini={this.props.isMini}
key={contextAction.textTranslateKey}
onPress={() => contextAction.onPress(!this.props.isMini, {
reportAction: this.props.reportAction,
reportID: this.props.reportID,
draftMessage: this.props.draftMessage,
selection: this.props.selection,
})}
/>
))}
{_.map(
ContextMenuActions, (contextAction) => {
if (!contextAction.shouldShow(this.props.type, this.props.reportAction)) {
return;
roryabraham marked this conversation as resolved.
Show resolved Hide resolved
}
return (
<ContextMenuItem
icon={contextAction.icon}
text={this.props.translate(contextAction.textTranslateKey)}
successIcon={contextAction.successIcon}
successText={contextAction.successTextTranslateKey
? this.props.translate(contextAction.successTextTranslateKey)
: undefined}
isMini={this.props.isMini}
key={contextAction.textTranslateKey}
onPress={() => contextAction.onPress(!this.props.isMini, {
reportAction: this.props.reportAction,
reportID: this.props.reportID,
draftMessage: this.props.draftMessage,
selection: this.props.selection,
})}
/>
);
},
)}
</View>
);
}
Expand Down
31 changes: 24 additions & 7 deletions src/pages/home/report/ContextMenu/ContextMenuActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,31 @@ function getActionText(reportAction) {
return lodashGet(message, 'html', '');
}

export const contextMenuTypes = {
roryabraham marked this conversation as resolved.
Show resolved Hide resolved
link: 'LINK',
roryabraham marked this conversation as resolved.
Show resolved Hide resolved
image: 'IMAGE',
roryabraham marked this conversation as resolved.
Show resolved Hide resolved
reportAction: 'REPORT_ACTION',
roryabraham marked this conversation as resolved.
Show resolved Hide resolved
};

// A list of all the context actions in this menu.
export default [
// Copy to clipboard
{
textTranslateKey: 'contextMenuItem.copyToClipboard',
textTranslateKey: 'reportActionContextMenu.copyURLToClipboard',
icon: ClipboardIcon,
successTextTranslateKey: 'reportActionContextMenu.copied',
successIcon: Checkmark,
shouldShow: type => type === contextMenuTypes.link,
onPress: (closePopover, {selection}) => {
Clipboard.setString(selection);
hideContextMenu(true, ReportActionComposeFocusManager.focus);
},
},
{
textTranslateKey: 'reportActionContextMenu.copyToClipboard',
icon: ClipboardIcon,
successTextTranslateKey: 'contextMenuItem.copied',
successTextTranslateKey: 'reportActionContextMenu.copied',
successIcon: Checkmark,
shouldShow: () => true,
shouldShow: type => type === contextMenuTypes.reportAction,

// If return value is true, we switch the `text` and `icon` on
// `ContextMenuItem` with `successText` and `successIcon` which will fallback to
Expand Down Expand Up @@ -64,7 +80,7 @@ export default [
textTranslateKey: 'reportActionContextMenu.markAsUnread',
icon: Mail,
successIcon: Checkmark,
shouldShow: () => true,
shouldShow: type => type === contextMenuTypes.reportAction,
onPress: (closePopover, {reportAction, reportID}) => {
updateLastReadActionID(reportID, reportAction.sequenceNumber);
setNewMarkerPosition(reportID, reportAction.sequenceNumber);
Expand All @@ -77,7 +93,7 @@ export default [
{
textTranslateKey: 'reportActionContextMenu.editComment',
icon: Pencil,
shouldShow: reportAction => canEditReportAction(reportAction),
shouldShow: (type, reportAction) => type === contextMenuTypes.reportAction && canEditReportAction(reportAction),
onPress: (closePopover, {reportID, reportAction, draftMessage}) => {
const editAction = () => saveReportActionDraft(
reportID,
Expand All @@ -98,7 +114,8 @@ export default [
{
textTranslateKey: 'reportActionContextMenu.deleteComment',
icon: Trashcan,
shouldShow: reportAction => canDeleteReportAction(reportAction),
shouldShow: (type, reportAction) => type === contextMenuTypes.reportAction
&& canDeleteReportAction(reportAction),
onPress: (closePopover, {reportID, reportAction}) => {
if (closePopover) {
// Hide popover, then call showDeleteConfirmModal
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ class PopoverReportActionContextMenu extends React.Component {
/**
* Show the ReportActionContextMenu modal popover.
*
* @param {string} type - context menu type [LINK, IMAGE, REPORT_ACTION]
* @param {Object} [event] - A press event.
* @param {string} [selection] - A copy text.
* @param {Element} contextMenuAnchor - popoverAnchor
Expand All @@ -104,6 +105,7 @@ class PopoverReportActionContextMenu extends React.Component {
* @param {Function} [onHide] - Run a callback when Menu is hidden
*/
showContextMenu(
type,
event,
selection,
contextMenuAnchor,
Expand All @@ -126,6 +128,7 @@ class PopoverReportActionContextMenu extends React.Component {
horizontal: nativeEvent.pageX,
vertical: nativeEvent.pageY,
},
type,
reportID,
reportAction,
selection,
Expand Down Expand Up @@ -190,6 +193,7 @@ class PopoverReportActionContextMenu extends React.Component {
measureContent() {
return (
<BaseReportActionContextMenu
type={this.state.type}
isVisible
selection={this.state.selection}
reportID={this.state.reportID}
Expand Down Expand Up @@ -236,6 +240,7 @@ class PopoverReportActionContextMenu extends React.Component {
>
<BaseReportActionContextMenu
isVisible
type={this.state.type}
reportID={this.state.reportID}
reportAction={this.state.reportAction}
draftMessage={this.state.reportActionDraftMessage}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const contextMenuRef = React.createRef();
/**
* Show the ReportActionContextMenu modal popover.
*
* @param {string} type - the context menu type to who [LINK, IMAGE, REPORT_ACTION]
roryabraham marked this conversation as resolved.
Show resolved Hide resolved
* @param {Object} [event] - A press event.
* @param {string} [selection] - A copy text.
* @param {Element} contextMenuAnchor - popoverAnchor
Expand All @@ -15,19 +16,21 @@ const contextMenuRef = React.createRef();
* @param {Function} [onHide=() => {}] - Run a callback when Menu is hidden
*/
function showContextMenu(
type,
event,
selection,
contextMenuAnchor,
reportID,
reportAction,
draftMessage,
reportID = 0,
reportAction = {},
draftMessage = '',
onShow = () => {},
onHide = () => {},
) {
if (!contextMenuRef.current) {
return;
}
contextMenuRef.current.showContextMenu(
type,
event,
selection,
contextMenuAnchor,
Expand Down
2 changes: 2 additions & 0 deletions src/pages/home/report/ReportActionItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import ControlSelection from '../../../libs/ControlSelection';
import canUseTouchScreen from '../../../libs/canUseTouchscreen';
import MiniReportActionContextMenu from './ContextMenu/MiniReportActionContextMenu';
import {isActiveReportAction, showContextMenu} from './ContextMenu/ReportActionContextMenu';
import {contextMenuTypes} from './ContextMenu/ContextMenuActions';

const propTypes = {
/** The ID of the report this action is on. */
Expand Down Expand Up @@ -90,6 +91,7 @@ class ReportActionItem extends Component {
return;
}
showContextMenu(
contextMenuTypes.reportAction,
event,
selection,
this.popoverAnchor,
Expand Down