Add copy option to triple-dot menu of messages
This commit is contained in:
parent
f1624705a7
commit
f004e714f0
9 changed files with 61 additions and 0 deletions
|
@ -2352,6 +2352,10 @@
|
|||
"messageformat": "More Info",
|
||||
"description": "Shown on the drop-down menu for an individual message, takes you to message detail screen"
|
||||
},
|
||||
"icu:copy": {
|
||||
"messageformat": "Copy text",
|
||||
"description": "Shown on the drop-down menu for an individual message, copies the message text to the clipboard"
|
||||
},
|
||||
"icu:MessageContextMenu__select": {
|
||||
"messageformat": "Select",
|
||||
"description": "Shown on the drop-down menu for an individual message, opens the conversation in select mode with the current message selected"
|
||||
|
|
|
@ -83,6 +83,7 @@ const defaultMessageProps: TimelineMessagesProps = {
|
|||
id: 'some-id',
|
||||
title: 'Person X',
|
||||
}),
|
||||
canCopy: true,
|
||||
canEditMessage: true,
|
||||
canReact: true,
|
||||
canReply: true,
|
||||
|
@ -129,6 +130,7 @@ const defaultMessageProps: TimelineMessagesProps = {
|
|||
setMessageToEdit: action('setMessageToEdit'),
|
||||
setQuoteByMessageId: action('default--setQuoteByMessageId'),
|
||||
retryMessageSend: action('default--retryMessageSend'),
|
||||
copyMessageText: action('copyMessageText'),
|
||||
retryDeleteForEveryone: action('default--retryDeleteForEveryone'),
|
||||
saveAttachment: action('saveAttachment'),
|
||||
scrollToQuotedMessage: action('default--scrollToQuotedMessage'),
|
||||
|
|
|
@ -47,6 +47,7 @@ function mockMessageTimelineItem(
|
|||
data: {
|
||||
id,
|
||||
author: getDefaultConversation({}),
|
||||
canCopy: true,
|
||||
canDeleteForEveryone: false,
|
||||
canDownload: true,
|
||||
canEditMessage: true,
|
||||
|
@ -282,6 +283,7 @@ const actions = () => ({
|
|||
reactToMessage: action('reactToMessage'),
|
||||
setMessageToEdit: action('setMessageToEdit'),
|
||||
setQuoteByMessageId: action('setQuoteByMessageId'),
|
||||
copyMessageText: action('copyMessageText'),
|
||||
retryDeleteForEveryone: action('retryDeleteForEveryone'),
|
||||
retryMessageSend: action('retryMessageSend'),
|
||||
saveAttachment: action('saveAttachment'),
|
||||
|
|
|
@ -69,6 +69,7 @@ const getDefaultProps = () => ({
|
|||
clearTargetedMessage: action('clearTargetedMessage'),
|
||||
setMessageToEdit: action('setMessageToEdit'),
|
||||
setQuoteByMessageId: action('setQuoteByMessageId'),
|
||||
copyMessageText: action('copyMessageText'),
|
||||
retryDeleteForEveryone: action('retryDeleteForEveryone'),
|
||||
retryMessageSend: action('retryMessageSend'),
|
||||
blockGroupLinkRequests: action('blockGroupLinkRequests'),
|
||||
|
|
|
@ -245,6 +245,7 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
|
|||
attachments: overrideProps.attachments,
|
||||
author: overrideProps.author || getDefaultConversation(),
|
||||
bodyRanges: overrideProps.bodyRanges,
|
||||
canCopy: true,
|
||||
canEditMessage: true,
|
||||
canReact: true,
|
||||
canReply: true,
|
||||
|
@ -324,6 +325,7 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
|
|||
saveAttachment: action('saveAttachment'),
|
||||
setQuoteByMessageId: action('setQuoteByMessageId'),
|
||||
retryMessageSend: action('retryMessageSend'),
|
||||
copyMessageText: action('copyMessageText'),
|
||||
retryDeleteForEveryone: action('retryDeleteForEveryone'),
|
||||
scrollToQuotedMessage: action('scrollToQuotedMessage'),
|
||||
targetMessage: action('targetMessage'),
|
||||
|
|
|
@ -32,6 +32,7 @@ import type { DeleteMessagesPropsType } from '../../state/ducks/globalModals';
|
|||
|
||||
export type PropsData = {
|
||||
canDownload: boolean;
|
||||
canCopy: boolean;
|
||||
canEditMessage: boolean;
|
||||
canRetry: boolean;
|
||||
canRetryDeleteForEveryone: boolean;
|
||||
|
@ -50,6 +51,7 @@ export type PropsActions = {
|
|||
{ emoji, remove }: { emoji: string; remove: boolean }
|
||||
) => void;
|
||||
retryMessageSend: (id: string) => void;
|
||||
copyMessageText: (id: string) => void;
|
||||
retryDeleteForEveryone: (id: string) => void;
|
||||
setMessageToEdit: (conversationId: string, messageId: string) => unknown;
|
||||
setQuoteByMessageId: (conversationId: string, messageId: string) => void;
|
||||
|
@ -82,6 +84,7 @@ export function TimelineMessage(props: Props): JSX.Element {
|
|||
attachments,
|
||||
author,
|
||||
canDownload,
|
||||
canCopy,
|
||||
canEditMessage,
|
||||
canReact,
|
||||
canReply,
|
||||
|
@ -101,6 +104,7 @@ export function TimelineMessage(props: Props): JSX.Element {
|
|||
isTapToView,
|
||||
kickOffAttachmentDownload,
|
||||
payment,
|
||||
copyMessageText,
|
||||
pushPanelForConversation,
|
||||
reactToMessage,
|
||||
renderEmojiPicker,
|
||||
|
@ -367,6 +371,7 @@ export function TimelineMessage(props: Props): JSX.Element {
|
|||
? () => retryDeleteForEveryone(id)
|
||||
: undefined
|
||||
}
|
||||
onCopy={canCopy ? () => copyMessageText(id) : undefined}
|
||||
onSelect={() => toggleSelectMessage(conversationId, id, false, true)}
|
||||
onForward={
|
||||
canForward ? () => toggleForwardMessagesModal([id]) : undefined
|
||||
|
@ -554,6 +559,7 @@ type MessageContextProps = {
|
|||
onReact: (() => void) | undefined;
|
||||
onRetryMessageSend: (() => void) | undefined;
|
||||
onRetryDeleteForEveryone: (() => void) | undefined;
|
||||
onCopy: (() => void) | undefined;
|
||||
onForward: (() => void) | undefined;
|
||||
onDeleteMessage: () => void;
|
||||
onMoreInfo: () => void;
|
||||
|
@ -569,6 +575,7 @@ const MessageContextMenu = ({
|
|||
onReplyToMessage,
|
||||
onReact,
|
||||
onMoreInfo,
|
||||
onCopy,
|
||||
onSelect,
|
||||
onRetryMessageSend,
|
||||
onRetryDeleteForEveryone,
|
||||
|
@ -667,6 +674,19 @@ const MessageContextMenu = ({
|
|||
>
|
||||
{i18n('icu:MessageContextMenu__select')}
|
||||
</MenuItem>
|
||||
{onCopy && (
|
||||
<MenuItem
|
||||
attributes={{
|
||||
className:
|
||||
'module-message__context--icon module-message__context__copy-timestamp',
|
||||
}}
|
||||
onClick={() => {
|
||||
onCopy();
|
||||
}}
|
||||
>
|
||||
{i18n('icu:copy')}
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuItem
|
||||
attributes={{
|
||||
className:
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
without,
|
||||
} from 'lodash';
|
||||
|
||||
import { clipboard } from 'electron';
|
||||
import type { ReadonlyDeep } from 'type-fest';
|
||||
import type { AttachmentType } from '../../types/Attachment';
|
||||
import type { StateType as RootStateType } from '../reducer';
|
||||
|
@ -1048,6 +1049,7 @@ export const actions = {
|
|||
repairOldestMessage,
|
||||
replaceAvatar,
|
||||
resetAllChatColors,
|
||||
copyMessageText,
|
||||
retryDeleteForEveryone,
|
||||
retryMessageSend,
|
||||
reviewGroupMemberNameCollision,
|
||||
|
@ -2170,6 +2172,25 @@ function retryMessageSend(
|
|||
};
|
||||
}
|
||||
|
||||
export function copyMessageText(
|
||||
messageId: string
|
||||
): ThunkAction<void, RootStateType, unknown, NoopActionType> {
|
||||
return async dispatch => {
|
||||
const message = await getMessageById(messageId);
|
||||
if (!message) {
|
||||
throw new Error(`copy: Message ${messageId} missing!`);
|
||||
}
|
||||
|
||||
const body = message.getNotificationText();
|
||||
clipboard.writeText(body);
|
||||
|
||||
dispatch({
|
||||
type: 'NOOP',
|
||||
payload: null,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function retryDeleteForEveryone(
|
||||
messageId: string
|
||||
): ThunkAction<void, RootStateType, unknown, NoopActionType> {
|
||||
|
|
|
@ -720,6 +720,7 @@ export const getPropsForMessage = (
|
|||
storyReplyContext,
|
||||
textAttachment,
|
||||
payment,
|
||||
canCopy: canCopy(message),
|
||||
canEditMessage: canEditMessage(message),
|
||||
canDeleteForEveryone: canDeleteForEveryone(message),
|
||||
canDownload: canDownload(message, conversationSelector),
|
||||
|
@ -1773,6 +1774,12 @@ export function canReact(
|
|||
return canReplyOrReact(message, ourConversationId, conversation);
|
||||
}
|
||||
|
||||
export function canCopy(
|
||||
message: Pick<MessageWithUIFieldsType, 'body' | 'deletedForEveryone'>
|
||||
): boolean {
|
||||
return !message.deletedForEveryone && Boolean(message.body);
|
||||
}
|
||||
|
||||
export function canDeleteForEveryone(
|
||||
message: Pick<
|
||||
MessageWithUIFieldsType,
|
||||
|
|
|
@ -118,6 +118,7 @@ export function SmartTimelineItem(props: ExternalProps): JSX.Element {
|
|||
messageExpanded,
|
||||
openGiftBadge,
|
||||
pushPanelForConversation,
|
||||
copyMessageText,
|
||||
retryDeleteForEveryone,
|
||||
retryMessageSend,
|
||||
saveAttachment,
|
||||
|
@ -184,6 +185,7 @@ export function SmartTimelineItem(props: ExternalProps): JSX.Element {
|
|||
openGiftBadge={openGiftBadge}
|
||||
pushPanelForConversation={pushPanelForConversation}
|
||||
reactToMessage={reactToMessage}
|
||||
copyMessageText={copyMessageText}
|
||||
retryDeleteForEveryone={retryDeleteForEveryone}
|
||||
retryMessageSend={retryMessageSend}
|
||||
returnToActiveCall={returnToActiveCall}
|
||||
|
|
Loading…
Add table
Reference in a new issue