244 lines
6.5 KiB
TypeScript
244 lines
6.5 KiB
TypeScript
// Copyright 2023 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
import React, { type RefObject } from 'react';
|
|
import { ContextMenu, MenuItem } from 'react-contextmenu';
|
|
import ReactDOM from 'react-dom';
|
|
import type { LocalizerType } from '../../types/I18N';
|
|
|
|
export type ContextMenuTriggerType = {
|
|
handleContextClick: (
|
|
event: React.MouseEvent<HTMLDivElement> | MouseEvent
|
|
) => void;
|
|
};
|
|
|
|
type MessageContextProps = {
|
|
i18n: LocalizerType;
|
|
triggerId: string;
|
|
shouldShowAdditional: boolean;
|
|
|
|
onDownload: (() => void) | undefined;
|
|
onEdit: (() => void) | undefined;
|
|
onReplyToMessage: (() => void) | undefined;
|
|
onReact: (() => void) | undefined;
|
|
onRetryMessageSend: (() => void) | undefined;
|
|
onRetryDeleteForEveryone: (() => void) | undefined;
|
|
onCopy: (() => void) | undefined;
|
|
onForward: (() => void) | undefined;
|
|
onDeleteMessage: () => void;
|
|
onMoreInfo: (() => void) | undefined;
|
|
onSelect: (() => void) | undefined;
|
|
};
|
|
export const MessageContextMenu = ({
|
|
i18n,
|
|
triggerId,
|
|
shouldShowAdditional,
|
|
onDownload,
|
|
onEdit,
|
|
onReplyToMessage,
|
|
onReact,
|
|
onMoreInfo,
|
|
onCopy,
|
|
onSelect,
|
|
onRetryMessageSend,
|
|
onRetryDeleteForEveryone,
|
|
onForward,
|
|
onDeleteMessage,
|
|
}: MessageContextProps): JSX.Element => {
|
|
const menu = (
|
|
<ContextMenu id={triggerId}>
|
|
{shouldShowAdditional && (
|
|
<>
|
|
{onDownload && (
|
|
<MenuItem
|
|
attributes={{
|
|
className:
|
|
'module-message__context--icon module-message__context__download',
|
|
}}
|
|
onClick={onDownload}
|
|
>
|
|
{i18n('icu:MessageContextMenu__download')}
|
|
</MenuItem>
|
|
)}
|
|
{onReplyToMessage && (
|
|
<MenuItem
|
|
attributes={{
|
|
className:
|
|
'module-message__context--icon module-message__context__reply',
|
|
}}
|
|
onClick={(event: React.MouseEvent) => {
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
|
|
onReplyToMessage();
|
|
}}
|
|
>
|
|
{i18n('icu:MessageContextMenu__reply')}
|
|
</MenuItem>
|
|
)}
|
|
{onReact && (
|
|
<MenuItem
|
|
attributes={{
|
|
className:
|
|
'module-message__context--icon module-message__context__react',
|
|
}}
|
|
onClick={(event: React.MouseEvent) => {
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
|
|
onReact();
|
|
}}
|
|
>
|
|
{i18n('icu:MessageContextMenu__react')}
|
|
</MenuItem>
|
|
)}
|
|
</>
|
|
)}
|
|
{onForward && (
|
|
<MenuItem
|
|
attributes={{
|
|
className:
|
|
'module-message__context--icon module-message__context__forward-message',
|
|
}}
|
|
onClick={(event: React.MouseEvent) => {
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
|
|
onForward();
|
|
}}
|
|
>
|
|
{i18n('icu:MessageContextMenu__forward')}
|
|
</MenuItem>
|
|
)}
|
|
{onEdit && (
|
|
<MenuItem
|
|
attributes={{
|
|
className:
|
|
'module-message__context--icon module-message__context__edit-message',
|
|
}}
|
|
onClick={(event: React.MouseEvent) => {
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
|
|
onEdit();
|
|
}}
|
|
>
|
|
{i18n('icu:edit')}
|
|
</MenuItem>
|
|
)}
|
|
{onSelect && (
|
|
<MenuItem
|
|
attributes={{
|
|
className:
|
|
'module-message__context--icon module-message__context__select',
|
|
}}
|
|
onClick={() => {
|
|
onSelect();
|
|
}}
|
|
>
|
|
{i18n('icu:MessageContextMenu__select')}
|
|
</MenuItem>
|
|
)}
|
|
{onCopy && (
|
|
<MenuItem
|
|
attributes={{
|
|
className:
|
|
'module-message__context--icon module-message__context__copy-timestamp',
|
|
}}
|
|
onClick={() => {
|
|
onCopy();
|
|
}}
|
|
>
|
|
{i18n('icu:copy')}
|
|
</MenuItem>
|
|
)}
|
|
{onMoreInfo && (
|
|
<MenuItem
|
|
attributes={{
|
|
className:
|
|
'module-message__context--icon module-message__context__more-info',
|
|
}}
|
|
onClick={(event: React.MouseEvent) => {
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
|
|
onMoreInfo();
|
|
}}
|
|
>
|
|
{i18n('icu:MessageContextMenu__info')}
|
|
</MenuItem>
|
|
)}
|
|
<MenuItem
|
|
attributes={{
|
|
className:
|
|
'module-message__context--icon module-message__context__delete-message',
|
|
}}
|
|
onClick={(event: React.MouseEvent) => {
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
|
|
onDeleteMessage();
|
|
}}
|
|
>
|
|
{i18n('icu:MessageContextMenu__deleteMessage')}
|
|
</MenuItem>
|
|
{onRetryMessageSend && (
|
|
<MenuItem
|
|
attributes={{
|
|
className:
|
|
'module-message__context--icon module-message__context__retry-send',
|
|
}}
|
|
onClick={(event: React.MouseEvent) => {
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
|
|
onRetryMessageSend();
|
|
}}
|
|
>
|
|
{i18n('icu:retrySend')}
|
|
</MenuItem>
|
|
)}
|
|
{onRetryDeleteForEveryone && (
|
|
<MenuItem
|
|
attributes={{
|
|
className:
|
|
'module-message__context--icon module-message__context__delete-message-for-everyone',
|
|
}}
|
|
onClick={(event: React.MouseEvent) => {
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
|
|
onRetryDeleteForEveryone();
|
|
}}
|
|
>
|
|
{i18n('icu:retryDeleteForEveryone')}
|
|
</MenuItem>
|
|
)}
|
|
</ContextMenu>
|
|
);
|
|
|
|
return ReactDOM.createPortal(menu, document.body);
|
|
};
|
|
|
|
export function useHandleMessageContextMenu(
|
|
menuTriggerRef: RefObject<ContextMenuTriggerType>
|
|
): ContextMenuTriggerType['handleContextClick'] {
|
|
return React.useCallback(
|
|
(event: React.MouseEvent<HTMLDivElement> | MouseEvent): void => {
|
|
const selection = window.getSelection();
|
|
|
|
if (selection && !selection.isCollapsed) {
|
|
return;
|
|
}
|
|
if (event && event.target instanceof HTMLAnchorElement) {
|
|
return;
|
|
}
|
|
if (menuTriggerRef.current) {
|
|
menuTriggerRef.current.handleContextClick(
|
|
event ?? new MouseEvent('click')
|
|
);
|
|
}
|
|
},
|
|
[menuTriggerRef]
|
|
);
|
|
}
|