2023-01-03 19:55:46 +00:00
|
|
|
// Copyright 2019 Signal Messenger, LLC
|
2022-11-04 13:22:07 +00:00
|
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
|
|
|
import classNames from 'classnames';
|
2022-12-03 00:40:33 +00:00
|
|
|
import { noop } from 'lodash';
|
2022-12-19 22:33:55 +00:00
|
|
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
2022-11-04 13:22:07 +00:00
|
|
|
import type { Ref } from 'react';
|
2023-12-12 16:11:39 +00:00
|
|
|
import { ContextMenuTrigger } from 'react-contextmenu';
|
|
|
|
import { createPortal } from 'react-dom';
|
2022-11-04 13:22:07 +00:00
|
|
|
import { Manager, Popper, Reference } from 'react-popper';
|
|
|
|
import type { PreventOverflowModifier } from '@popperjs/core/lib/modifiers/preventOverflow';
|
|
|
|
import { isDownloaded } from '../../types/Attachment';
|
|
|
|
import type { LocalizerType } from '../../types/I18N';
|
|
|
|
import { handleOutsideClick } from '../../util/handleOutsideClick';
|
|
|
|
import { offsetDistanceModifier } from '../../util/popperUtil';
|
|
|
|
import { StopPropagation } from '../StopPropagation';
|
|
|
|
import { WidthBreakpoint } from '../_util';
|
|
|
|
import { Message } from './Message';
|
|
|
|
import type { SmartReactionPicker } from '../../state/smart/ReactionPicker';
|
|
|
|
import type {
|
|
|
|
Props as MessageProps,
|
|
|
|
PropsActions as MessagePropsActions,
|
|
|
|
PropsData as MessagePropsData,
|
|
|
|
PropsHousekeeping,
|
|
|
|
} from './Message';
|
2022-12-21 20:44:23 +00:00
|
|
|
import type { PushPanelForConversationActionType } from '../../state/ducks/conversations';
|
2022-11-04 13:22:07 +00:00
|
|
|
import { doesMessageBodyOverflow } from './MessageBodyReadMore';
|
|
|
|
import type { Props as ReactionPickerProps } from './ReactionPicker';
|
2023-06-21 16:54:05 +00:00
|
|
|
import {
|
|
|
|
useKeyboardShortcutsConditionally,
|
|
|
|
useOpenContextMenu,
|
|
|
|
useToggleReactionPicker,
|
|
|
|
} from '../../hooks/useKeyboardShortcuts';
|
2022-12-21 03:25:10 +00:00
|
|
|
import { PanelType } from '../../types/Panels';
|
2023-04-10 21:38:34 +00:00
|
|
|
import type { DeleteMessagesPropsType } from '../../state/ducks/globalModals';
|
2023-09-19 19:01:04 +00:00
|
|
|
import { useScrollerLock } from '../../hooks/useScrollLock';
|
2023-12-12 16:11:39 +00:00
|
|
|
import {
|
|
|
|
type ContextMenuTriggerType,
|
|
|
|
MessageContextMenu,
|
|
|
|
useHandleMessageContextMenu,
|
|
|
|
} from './MessageContextMenu';
|
2022-11-04 13:22:07 +00:00
|
|
|
|
|
|
|
export type PropsData = {
|
|
|
|
canDownload: boolean;
|
2023-04-22 01:52:25 +00:00
|
|
|
canCopy: boolean;
|
2023-04-20 16:31:59 +00:00
|
|
|
canEditMessage: boolean;
|
2022-11-04 13:22:07 +00:00
|
|
|
canRetry: boolean;
|
|
|
|
canRetryDeleteForEveryone: boolean;
|
|
|
|
canReact: boolean;
|
|
|
|
canReply: boolean;
|
|
|
|
selectedReaction?: string;
|
2023-03-20 22:23:53 +00:00
|
|
|
isTargeted?: boolean;
|
2022-11-04 13:22:07 +00:00
|
|
|
} & Omit<MessagePropsData, 'renderingContext' | 'menu'>;
|
|
|
|
|
|
|
|
export type PropsActions = {
|
2022-12-21 03:25:10 +00:00
|
|
|
pushPanelForConversation: PushPanelForConversationActionType;
|
2023-04-10 21:38:34 +00:00
|
|
|
toggleDeleteMessagesModal: (props: DeleteMessagesPropsType) => void;
|
|
|
|
toggleForwardMessagesModal: (messageIds: Array<string>) => void;
|
2022-11-04 13:22:07 +00:00
|
|
|
reactToMessage: (
|
|
|
|
id: string,
|
|
|
|
{ emoji, remove }: { emoji: string; remove: boolean }
|
|
|
|
) => void;
|
2022-12-20 01:04:47 +00:00
|
|
|
retryMessageSend: (id: string) => void;
|
2023-04-22 01:52:25 +00:00
|
|
|
copyMessageText: (id: string) => void;
|
2022-11-04 13:22:07 +00:00
|
|
|
retryDeleteForEveryone: (id: string) => void;
|
2023-04-20 16:31:59 +00:00
|
|
|
setMessageToEdit: (conversationId: string, messageId: string) => unknown;
|
2022-12-09 19:11:14 +00:00
|
|
|
setQuoteByMessageId: (conversationId: string, messageId: string) => void;
|
2023-03-20 22:23:53 +00:00
|
|
|
toggleSelectMessage: (
|
|
|
|
conversationId: string,
|
|
|
|
messageId: string,
|
|
|
|
shift: boolean,
|
|
|
|
selected: boolean
|
|
|
|
) => void;
|
|
|
|
} & Omit<MessagePropsActions, 'onToggleSelect' | 'onReplyToMessage'>;
|
2022-11-04 13:22:07 +00:00
|
|
|
|
|
|
|
export type Props = PropsData &
|
|
|
|
PropsActions &
|
|
|
|
Omit<PropsHousekeeping, 'isAttachmentPending'> &
|
|
|
|
Pick<ReactionPickerProps, 'renderEmojiPicker'> & {
|
|
|
|
renderReactionPicker: (
|
|
|
|
props: React.ComponentProps<typeof SmartReactionPicker>
|
|
|
|
) => JSX.Element;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Message with menu/context-menu (as necessary for rendering in the timeline)
|
|
|
|
*/
|
2022-11-18 00:45:19 +00:00
|
|
|
export function TimelineMessage(props: Props): JSX.Element {
|
2022-11-04 13:22:07 +00:00
|
|
|
const {
|
|
|
|
attachments,
|
2022-12-21 20:44:23 +00:00
|
|
|
author,
|
2022-11-04 13:22:07 +00:00
|
|
|
canDownload,
|
2023-04-22 01:52:25 +00:00
|
|
|
canCopy,
|
2023-04-20 16:31:59 +00:00
|
|
|
canEditMessage,
|
2022-11-04 13:22:07 +00:00
|
|
|
canReact,
|
|
|
|
canReply,
|
|
|
|
canRetry,
|
|
|
|
canRetryDeleteForEveryone,
|
|
|
|
contact,
|
|
|
|
containerElementRef,
|
|
|
|
containerWidthBreakpoint,
|
2022-12-21 20:44:23 +00:00
|
|
|
conversationId,
|
|
|
|
deletedForEveryone,
|
2022-11-04 13:22:07 +00:00
|
|
|
direction,
|
|
|
|
giftBadge,
|
2022-12-21 20:44:23 +00:00
|
|
|
i18n,
|
|
|
|
id,
|
2023-03-20 22:23:53 +00:00
|
|
|
isTargeted,
|
2022-11-04 13:22:07 +00:00
|
|
|
isSticker,
|
|
|
|
isTapToView,
|
2022-12-21 20:44:23 +00:00
|
|
|
kickOffAttachmentDownload,
|
|
|
|
payment,
|
2023-04-22 01:52:25 +00:00
|
|
|
copyMessageText,
|
2022-12-21 03:25:10 +00:00
|
|
|
pushPanelForConversation,
|
2022-11-04 13:22:07 +00:00
|
|
|
reactToMessage,
|
|
|
|
renderEmojiPicker,
|
2022-12-21 20:44:23 +00:00
|
|
|
renderReactionPicker,
|
2022-11-04 13:22:07 +00:00
|
|
|
retryDeleteForEveryone,
|
2022-12-21 20:44:23 +00:00
|
|
|
retryMessageSend,
|
|
|
|
saveAttachment,
|
2022-11-04 13:22:07 +00:00
|
|
|
selectedReaction,
|
2022-12-21 20:44:23 +00:00
|
|
|
setQuoteByMessageId,
|
2023-04-20 16:31:59 +00:00
|
|
|
setMessageToEdit,
|
2022-11-04 13:22:07 +00:00
|
|
|
text,
|
|
|
|
timestamp,
|
2023-04-10 21:38:34 +00:00
|
|
|
toggleDeleteMessagesModal,
|
2023-03-20 22:23:53 +00:00
|
|
|
toggleForwardMessagesModal,
|
|
|
|
toggleSelectMessage,
|
2022-11-04 13:22:07 +00:00
|
|
|
} = props;
|
|
|
|
|
|
|
|
const [reactionPickerRoot, setReactionPickerRoot] = useState<
|
|
|
|
HTMLDivElement | undefined
|
|
|
|
>(undefined);
|
2023-12-12 16:11:39 +00:00
|
|
|
const menuTriggerRef = useRef<ContextMenuTriggerType | null>(null);
|
2022-11-04 13:22:07 +00:00
|
|
|
|
|
|
|
const isWindowWidthNotNarrow =
|
|
|
|
containerWidthBreakpoint !== WidthBreakpoint.Narrow;
|
|
|
|
|
2022-12-19 22:33:55 +00:00
|
|
|
const popperPreventOverflowModifier =
|
|
|
|
useCallback((): Partial<PreventOverflowModifier> => {
|
|
|
|
return {
|
|
|
|
name: 'preventOverflow',
|
|
|
|
options: {
|
|
|
|
altAxis: true,
|
|
|
|
boundary: containerElementRef.current || undefined,
|
|
|
|
padding: {
|
|
|
|
bottom: 16,
|
|
|
|
left: 8,
|
|
|
|
right: 8,
|
|
|
|
top: 16,
|
|
|
|
},
|
2022-11-04 13:22:07 +00:00
|
|
|
},
|
2022-12-19 22:33:55 +00:00
|
|
|
};
|
|
|
|
}, [containerElementRef]);
|
2022-11-04 13:22:07 +00:00
|
|
|
|
|
|
|
// This id is what connects our triple-dot click with our associated pop-up menu.
|
|
|
|
// It needs to be unique.
|
|
|
|
const triggerId = String(id || `${author.id}-${timestamp}`);
|
|
|
|
|
2022-12-19 22:33:55 +00:00
|
|
|
const toggleReactionPicker = useCallback(
|
2022-11-04 13:22:07 +00:00
|
|
|
(onlyRemove = false): void => {
|
|
|
|
if (reactionPickerRoot) {
|
|
|
|
document.body.removeChild(reactionPickerRoot);
|
|
|
|
setReactionPickerRoot(undefined);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!onlyRemove) {
|
|
|
|
const root = document.createElement('div');
|
|
|
|
document.body.appendChild(root);
|
|
|
|
|
|
|
|
setReactionPickerRoot(root);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
[reactionPickerRoot]
|
|
|
|
);
|
|
|
|
|
2023-09-19 19:01:04 +00:00
|
|
|
useScrollerLock({
|
|
|
|
reason: 'TimelineMessage reactionPicker',
|
|
|
|
lockScrollWhen: reactionPickerRoot != null,
|
|
|
|
onUserInterrupt() {
|
|
|
|
toggleReactionPicker(true);
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2022-11-04 13:22:07 +00:00
|
|
|
useEffect(() => {
|
|
|
|
let cleanUpHandler: (() => void) | undefined;
|
|
|
|
if (reactionPickerRoot) {
|
|
|
|
cleanUpHandler = handleOutsideClick(
|
|
|
|
() => {
|
|
|
|
toggleReactionPicker(true);
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
{
|
|
|
|
containerElements: [reactionPickerRoot],
|
|
|
|
name: 'Message.reactionPicker',
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return () => {
|
|
|
|
cleanUpHandler?.();
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
2022-12-19 22:33:55 +00:00
|
|
|
const openGenericAttachment = useCallback(
|
|
|
|
(event?: React.MouseEvent): void => {
|
|
|
|
if (event) {
|
|
|
|
event.preventDefault();
|
|
|
|
event.stopPropagation();
|
|
|
|
}
|
2022-11-04 13:22:07 +00:00
|
|
|
|
2022-12-19 22:33:55 +00:00
|
|
|
if (!attachments || attachments.length !== 1) {
|
|
|
|
return;
|
|
|
|
}
|
2022-11-04 13:22:07 +00:00
|
|
|
|
2022-12-19 22:33:55 +00:00
|
|
|
const attachment = attachments[0];
|
|
|
|
if (!isDownloaded(attachment)) {
|
|
|
|
kickOffAttachmentDownload({
|
|
|
|
attachment,
|
|
|
|
messageId: id,
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
2022-11-04 13:22:07 +00:00
|
|
|
|
2022-12-19 22:33:55 +00:00
|
|
|
saveAttachment(attachment, timestamp);
|
|
|
|
},
|
|
|
|
[kickOffAttachmentDownload, saveAttachment, attachments, id, timestamp]
|
|
|
|
);
|
2022-11-04 13:22:07 +00:00
|
|
|
|
2023-12-12 16:11:39 +00:00
|
|
|
const handleContextMenu = useHandleMessageContextMenu(menuTriggerRef);
|
2022-11-04 13:22:07 +00:00
|
|
|
const canForward =
|
2022-11-30 21:47:54 +00:00
|
|
|
!isTapToView && !deletedForEveryone && !giftBadge && !contact && !payment;
|
2022-11-04 13:22:07 +00:00
|
|
|
|
|
|
|
const shouldShowAdditional =
|
|
|
|
doesMessageBodyOverflow(text || '') || !isWindowWidthNotNarrow;
|
|
|
|
|
|
|
|
const multipleAttachments = attachments && attachments.length > 1;
|
|
|
|
const firstAttachment = attachments && attachments[0];
|
|
|
|
|
|
|
|
const handleDownload =
|
|
|
|
canDownload &&
|
|
|
|
!isSticker &&
|
|
|
|
!multipleAttachments &&
|
|
|
|
!isTapToView &&
|
|
|
|
firstAttachment &&
|
|
|
|
!firstAttachment.pending
|
|
|
|
? openGenericAttachment
|
|
|
|
: undefined;
|
|
|
|
|
2022-12-19 22:33:55 +00:00
|
|
|
const handleReplyToMessage = useCallback(() => {
|
|
|
|
if (!canReply) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
setQuoteByMessageId(conversationId, id);
|
|
|
|
}, [canReply, conversationId, id, setQuoteByMessageId]);
|
2022-11-04 13:22:07 +00:00
|
|
|
|
2022-12-19 22:33:55 +00:00
|
|
|
const handleReact = useCallback(() => {
|
|
|
|
if (canReact) {
|
|
|
|
toggleReactionPicker();
|
|
|
|
}
|
|
|
|
}, [canReact, toggleReactionPicker]);
|
2022-11-04 13:22:07 +00:00
|
|
|
|
2022-12-03 00:40:33 +00:00
|
|
|
const toggleReactionPickerKeyboard = useToggleReactionPicker(
|
|
|
|
handleReact || noop
|
|
|
|
);
|
|
|
|
|
2023-12-12 16:11:39 +00:00
|
|
|
const openContextMenuKeyboard = useOpenContextMenu(handleContextMenu);
|
2023-06-21 16:54:05 +00:00
|
|
|
|
|
|
|
useKeyboardShortcutsConditionally(
|
|
|
|
Boolean(isTargeted),
|
|
|
|
openContextMenuKeyboard,
|
|
|
|
toggleReactionPickerKeyboard
|
|
|
|
);
|
2022-12-03 00:40:33 +00:00
|
|
|
|
2022-12-19 22:33:55 +00:00
|
|
|
const renderMenu = useCallback(() => {
|
|
|
|
return (
|
|
|
|
<Manager>
|
|
|
|
<MessageMenu
|
|
|
|
i18n={i18n}
|
|
|
|
triggerId={triggerId}
|
|
|
|
isWindowWidthNotNarrow={isWindowWidthNotNarrow}
|
|
|
|
direction={direction}
|
|
|
|
menuTriggerRef={menuTriggerRef}
|
|
|
|
showMenu={handleContextMenu}
|
|
|
|
onDownload={handleDownload}
|
2023-08-30 15:38:21 +00:00
|
|
|
onReplyToMessage={canReply ? handleReplyToMessage : undefined}
|
|
|
|
onReact={canReact ? handleReact : undefined}
|
2022-12-19 22:33:55 +00:00
|
|
|
/>
|
|
|
|
{reactionPickerRoot &&
|
|
|
|
createPortal(
|
|
|
|
<Popper
|
|
|
|
placement="top"
|
|
|
|
modifiers={[
|
|
|
|
offsetDistanceModifier(4),
|
|
|
|
popperPreventOverflowModifier(),
|
|
|
|
]}
|
|
|
|
>
|
|
|
|
{({ ref, style }) =>
|
|
|
|
renderReactionPicker({
|
|
|
|
ref,
|
|
|
|
style,
|
|
|
|
selected: selectedReaction,
|
|
|
|
onClose: toggleReactionPicker,
|
|
|
|
onPick: emoji => {
|
|
|
|
toggleReactionPicker(true);
|
|
|
|
reactToMessage(id, {
|
|
|
|
emoji,
|
|
|
|
remove: emoji === selectedReaction,
|
|
|
|
});
|
|
|
|
},
|
|
|
|
renderEmojiPicker,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
</Popper>,
|
|
|
|
reactionPickerRoot
|
|
|
|
)}
|
|
|
|
</Manager>
|
|
|
|
);
|
|
|
|
}, [
|
|
|
|
i18n,
|
|
|
|
triggerId,
|
|
|
|
isWindowWidthNotNarrow,
|
|
|
|
direction,
|
|
|
|
menuTriggerRef,
|
2023-08-30 15:38:21 +00:00
|
|
|
canReply,
|
|
|
|
canReact,
|
2022-12-19 22:33:55 +00:00
|
|
|
handleContextMenu,
|
|
|
|
handleDownload,
|
|
|
|
handleReplyToMessage,
|
|
|
|
handleReact,
|
|
|
|
reactionPickerRoot,
|
|
|
|
popperPreventOverflowModifier,
|
|
|
|
renderReactionPicker,
|
|
|
|
selectedReaction,
|
|
|
|
reactToMessage,
|
|
|
|
renderEmojiPicker,
|
|
|
|
toggleReactionPicker,
|
|
|
|
id,
|
|
|
|
]);
|
|
|
|
|
2022-11-04 13:22:07 +00:00
|
|
|
return (
|
|
|
|
<>
|
2023-03-20 22:23:53 +00:00
|
|
|
<Message
|
|
|
|
{...props}
|
|
|
|
renderingContext="conversation/TimelineItem"
|
|
|
|
onContextMenu={handleContextMenu}
|
|
|
|
renderMenu={renderMenu}
|
|
|
|
onToggleSelect={(selected, shift) => {
|
|
|
|
toggleSelectMessage(conversationId, id, shift, selected);
|
2021-01-11 21:43:58 +00:00
|
|
|
}}
|
2023-03-20 22:23:53 +00:00
|
|
|
onReplyToMessage={handleReplyToMessage}
|
|
|
|
/>
|
2022-11-04 13:22:07 +00:00
|
|
|
|
|
|
|
<MessageContextMenu
|
|
|
|
i18n={i18n}
|
|
|
|
triggerId={triggerId}
|
|
|
|
shouldShowAdditional={shouldShowAdditional}
|
|
|
|
onDownload={handleDownload}
|
2023-04-20 16:31:59 +00:00
|
|
|
onEdit={
|
|
|
|
canEditMessage
|
|
|
|
? () => setMessageToEdit(conversationId, id)
|
|
|
|
: undefined
|
|
|
|
}
|
2022-11-04 13:22:07 +00:00
|
|
|
onReplyToMessage={handleReplyToMessage}
|
|
|
|
onReact={handleReact}
|
2022-12-20 01:04:47 +00:00
|
|
|
onRetryMessageSend={canRetry ? () => retryMessageSend(id) : undefined}
|
2022-11-04 13:22:07 +00:00
|
|
|
onRetryDeleteForEveryone={
|
|
|
|
canRetryDeleteForEveryone
|
|
|
|
? () => retryDeleteForEveryone(id)
|
|
|
|
: undefined
|
|
|
|
}
|
2023-04-22 01:52:25 +00:00
|
|
|
onCopy={canCopy ? () => copyMessageText(id) : undefined}
|
2023-03-20 22:23:53 +00:00
|
|
|
onSelect={() => toggleSelectMessage(conversationId, id, false, true)}
|
|
|
|
onForward={
|
|
|
|
canForward ? () => toggleForwardMessagesModal([id]) : undefined
|
|
|
|
}
|
2023-04-10 21:38:34 +00:00
|
|
|
onDeleteMessage={() => {
|
|
|
|
toggleDeleteMessagesModal({
|
|
|
|
conversationId,
|
|
|
|
messageIds: [id],
|
|
|
|
});
|
|
|
|
}}
|
2022-12-21 03:25:10 +00:00
|
|
|
onMoreInfo={() =>
|
2022-12-21 20:44:23 +00:00
|
|
|
pushPanelForConversation({
|
2022-12-21 03:25:10 +00:00
|
|
|
type: PanelType.MessageDetails,
|
|
|
|
args: { messageId: id },
|
|
|
|
})
|
|
|
|
}
|
2022-11-04 13:22:07 +00:00
|
|
|
/>
|
|
|
|
</>
|
|
|
|
);
|
2022-11-18 00:45:19 +00:00
|
|
|
}
|
2022-11-04 13:22:07 +00:00
|
|
|
|
|
|
|
type MessageMenuProps = {
|
|
|
|
i18n: LocalizerType;
|
|
|
|
triggerId: string;
|
|
|
|
isWindowWidthNotNarrow: boolean;
|
2023-12-12 16:11:39 +00:00
|
|
|
menuTriggerRef: Ref<ContextMenuTriggerType>;
|
2022-11-04 13:22:07 +00:00
|
|
|
showMenu: (event: React.MouseEvent<HTMLDivElement>) => void;
|
|
|
|
onDownload: (() => void) | undefined;
|
|
|
|
onReplyToMessage: (() => void) | undefined;
|
|
|
|
onReact: (() => void) | undefined;
|
|
|
|
} & Pick<MessageProps, 'i18n' | 'direction'>;
|
|
|
|
|
2022-11-18 00:45:19 +00:00
|
|
|
function MessageMenu({
|
2022-11-04 13:22:07 +00:00
|
|
|
i18n,
|
|
|
|
triggerId,
|
|
|
|
direction,
|
|
|
|
isWindowWidthNotNarrow,
|
|
|
|
menuTriggerRef,
|
|
|
|
showMenu,
|
|
|
|
onDownload,
|
|
|
|
onReplyToMessage,
|
|
|
|
onReact,
|
2022-11-18 00:45:19 +00:00
|
|
|
}: MessageMenuProps) {
|
2022-11-04 13:22:07 +00:00
|
|
|
// This a menu meant for mouse use only
|
|
|
|
/* eslint-disable jsx-a11y/interactive-supports-focus */
|
|
|
|
/* eslint-disable jsx-a11y/click-events-have-key-events */
|
|
|
|
const menuButton = (
|
|
|
|
<Reference>
|
|
|
|
{({ ref: popperRef }) => {
|
|
|
|
// Only attach the popper reference to the collapsed menu button if the reaction
|
|
|
|
// button is not visible (it is hidden when the timeline is narrow)
|
|
|
|
const maybePopperRef = !isWindowWidthNotNarrow ? popperRef : undefined;
|
|
|
|
|
|
|
|
return (
|
|
|
|
<StopPropagation className="module-message__buttons__menu--container">
|
|
|
|
<ContextMenuTrigger
|
|
|
|
id={triggerId}
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
|
|
ref={menuTriggerRef as any}
|
|
|
|
>
|
|
|
|
<div
|
|
|
|
ref={maybePopperRef}
|
|
|
|
role="button"
|
|
|
|
onClick={showMenu}
|
2023-03-30 00:03:25 +00:00
|
|
|
aria-label={i18n('icu:messageContextMenuButton')}
|
2022-11-04 13:22:07 +00:00
|
|
|
className={classNames(
|
|
|
|
'module-message__buttons__menu',
|
|
|
|
`module-message__buttons__download--${direction}`
|
|
|
|
)}
|
2021-01-11 21:43:58 +00:00
|
|
|
onDoubleClick={ev => {
|
|
|
|
// Prevent double click from triggering the replyToMessage action
|
|
|
|
ev.stopPropagation();
|
|
|
|
}}
|
2022-11-04 13:22:07 +00:00
|
|
|
/>
|
|
|
|
</ContextMenuTrigger>
|
|
|
|
</StopPropagation>
|
|
|
|
);
|
|
|
|
}}
|
|
|
|
</Reference>
|
|
|
|
);
|
|
|
|
/* eslint-enable jsx-a11y/interactive-supports-focus */
|
|
|
|
/* eslint-enable jsx-a11y/click-events-have-key-events */
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div
|
|
|
|
className={classNames(
|
|
|
|
'module-message__buttons',
|
|
|
|
`module-message__buttons--${direction}`
|
|
|
|
)}
|
|
|
|
>
|
|
|
|
{isWindowWidthNotNarrow && (
|
|
|
|
<>
|
|
|
|
{onReact && (
|
|
|
|
<Reference>
|
|
|
|
{({ ref: popperRef }) => {
|
|
|
|
// Only attach the popper reference to the reaction button if it is
|
|
|
|
// visible (it is hidden when the timeline is narrow)
|
|
|
|
const maybePopperRef = isWindowWidthNotNarrow
|
|
|
|
? popperRef
|
|
|
|
: undefined;
|
|
|
|
|
|
|
|
return (
|
|
|
|
// This a menu meant for mouse use only
|
|
|
|
// eslint-disable-next-line max-len
|
|
|
|
// eslint-disable-next-line jsx-a11y/interactive-supports-focus, jsx-a11y/click-events-have-key-events
|
|
|
|
<div
|
|
|
|
ref={maybePopperRef}
|
|
|
|
onClick={(event: React.MouseEvent) => {
|
|
|
|
event.stopPropagation();
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
|
|
onReact();
|
|
|
|
}}
|
|
|
|
role="button"
|
|
|
|
className="module-message__buttons__react"
|
2023-03-30 00:03:25 +00:00
|
|
|
aria-label={i18n('icu:reactToMessage')}
|
2021-01-11 21:43:58 +00:00
|
|
|
onDoubleClick={ev => {
|
|
|
|
// Prevent double click from triggering the replyToMessage action
|
|
|
|
ev.stopPropagation();
|
|
|
|
}}
|
2022-11-04 13:22:07 +00:00
|
|
|
/>
|
|
|
|
);
|
|
|
|
}}
|
|
|
|
</Reference>
|
|
|
|
)}
|
|
|
|
|
|
|
|
{onDownload && (
|
|
|
|
// This a menu meant for mouse use only
|
|
|
|
// eslint-disable-next-line max-len
|
|
|
|
// eslint-disable-next-line jsx-a11y/interactive-supports-focus, jsx-a11y/click-events-have-key-events
|
|
|
|
<div
|
|
|
|
onClick={onDownload}
|
|
|
|
role="button"
|
2023-03-30 00:03:25 +00:00
|
|
|
aria-label={i18n('icu:downloadAttachment')}
|
2022-11-04 13:22:07 +00:00
|
|
|
className={classNames(
|
|
|
|
'module-message__buttons__download',
|
|
|
|
`module-message__buttons__download--${direction}`
|
|
|
|
)}
|
2021-01-11 21:43:58 +00:00
|
|
|
onDoubleClick={ev => {
|
|
|
|
// Prevent double click from triggering the replyToMessage action
|
|
|
|
ev.stopPropagation();
|
|
|
|
}}
|
2022-11-04 13:22:07 +00:00
|
|
|
/>
|
|
|
|
)}
|
|
|
|
|
|
|
|
{onReplyToMessage && (
|
|
|
|
// This a menu meant for mouse use only
|
|
|
|
// eslint-disable-next-line max-len
|
|
|
|
// eslint-disable-next-line jsx-a11y/interactive-supports-focus, jsx-a11y/click-events-have-key-events
|
|
|
|
<div
|
|
|
|
onClick={(event: React.MouseEvent) => {
|
|
|
|
event.stopPropagation();
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
|
|
onReplyToMessage();
|
|
|
|
}}
|
|
|
|
// This a menu meant for mouse use only
|
|
|
|
role="button"
|
2023-03-30 00:03:25 +00:00
|
|
|
aria-label={i18n('icu:replyToMessage')}
|
2022-11-04 13:22:07 +00:00
|
|
|
className={classNames(
|
|
|
|
'module-message__buttons__reply',
|
|
|
|
`module-message__buttons__download--${direction}`
|
|
|
|
)}
|
2021-01-11 21:43:58 +00:00
|
|
|
onDoubleClick={ev => {
|
|
|
|
// Prevent double click from triggering the replyToMessage action
|
|
|
|
ev.stopPropagation();
|
|
|
|
}}
|
2022-11-04 13:22:07 +00:00
|
|
|
/>
|
|
|
|
)}
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
{menuButton}
|
|
|
|
</div>
|
|
|
|
);
|
2022-11-18 00:45:19 +00:00
|
|
|
}
|