// Copyright 2025 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import type { FC, ReactNode } from 'react'; import React, { memo, useCallback, useMemo, useState } from 'react'; import { AxoContextMenu } from '../../axo/AxoContextMenu.js'; import type { LocalizerType } from '../../types/I18N.js'; import type { ConversationType } from '../../state/ducks/conversations.js'; import { isConversationUnread } from '../../util/isConversationUnread.js'; import { Environment, getEnvironment, isMockEnvironment, } from '../../environment.js'; import { isAlpha } from '../../util/version.js'; import { drop } from '../../util/drop.js'; import { DeleteMessagesConfirmationDialog } from '../DeleteMessagesConfirmationDialog.js'; import { getMuteOptions } from '../../util/getMuteOptions.js'; function isEnabled() { const env = getEnvironment(); if ( env === Environment.Development || env === Environment.Test || isMockEnvironment() ) { return true; } const version = window.getVersion?.(); if (version != null) { if (isAlpha(version)) { return true; } } return false; } export type LeftPaneConversationListItemContextMenuProps = Readonly<{ i18n: LocalizerType; conversation: ConversationType; onMarkUnread: (conversationId: string) => void; onMarkRead: (conversationId: string) => void; onPin: (conversationId: string) => void; onUnpin: (conversationId: string) => void; onUpdateMute: (conversationId: string, muteExpiresAt: number) => void; onArchive: (conversationId: string) => void; onUnarchive: (conversationId: string) => void; onDelete: (conversationId: string) => void; localDeleteWarningShown: boolean; setLocalDeleteWarningShown: () => void; children: ReactNode; }>; export const LeftPaneConversationListItemContextMenu: FC = memo(function ConversationListItemContextMenu(props) { const { i18n, conversation, onMarkUnread, onMarkRead, onPin, onUnpin, onUpdateMute, onArchive, onUnarchive, onDelete, } = props; const { id: conversationId, muteExpiresAt } = conversation; const muteOptions = useMemo(() => { return getMuteOptions(muteExpiresAt, i18n); }, [muteExpiresAt, i18n]); const [showConfirmDeleteDialog, setShowConfirmDeleteDialog] = useState(false); const handleOpenConfirmDeleteDialog = useCallback(() => { setShowConfirmDeleteDialog(true); }, []); const handleCloseConfirmDeleteDialog = useCallback(() => { setShowConfirmDeleteDialog(false); }, []); const isUnread = useMemo(() => { return isConversationUnread(conversation); }, [conversation]); const handleMarkUnread = useCallback(() => { onMarkUnread(conversationId); }, [onMarkUnread, conversationId]); const handleMarkRead = useCallback(() => { onMarkRead(conversationId); }, [onMarkRead, conversationId]); const handlePin = useCallback(() => { onPin(conversationId); }, [onPin, conversationId]); const handleUnpin = useCallback(() => { onUnpin(conversationId); }, [onUnpin, conversationId]); const handleUpdateMute = useCallback( (value: number) => { onUpdateMute(conversationId, value); }, [onUpdateMute, conversationId] ); const handleArchive = useCallback(() => { onArchive(conversationId); }, [onArchive, conversationId]); const handleUnarchive = useCallback(() => { onUnarchive(conversationId); }, [onUnarchive, conversationId]); const handleDelete = useCallback(() => { onDelete(conversationId); }, [onDelete, conversationId]); return ( <> {props.children} {isUnread && ( {i18n('icu:markRead')} )} {!isUnread && !conversation.markedUnread && ( {i18n('icu:markUnread')} )} {!conversation.isPinned && ( {i18n('icu:pinConversation')} )} {conversation.isPinned && ( {i18n('icu:unpinConversation')} )} {i18n('icu:muteNotificationsTitle')} {muteOptions.map(muteOption => { return ( {muteOption.name} ); })} {!conversation.isArchived && ( {i18n('icu:archiveConversation')} )} {conversation.isArchived && ( {i18n('icu:moveConversationToInbox')} )} {i18n('icu:deleteConversation')} {isEnabled() && ( <> Internal Copy Conversation ID {conversation.serviceId != null && ( Copy Service ID )} {conversation.pni != null && ( Copy PNI )} {conversation.groupId != null && ( Copy Group ID )} {conversation.e164 != null && ( Copy E164 )} )} {showConfirmDeleteDialog && ( )} ); }); function ContextMenuMuteNotificationsItem(props: { disabled?: boolean; value: number; onSelect: (value: number) => void; children: ReactNode; }): JSX.Element { const { value, onSelect } = props; const handleSelect = useCallback(() => { onSelect(value); }, [onSelect, value]); return ( {props.children} ); } function ContextMenuCopyTextItem(props: { value: string; children: ReactNode; }): JSX.Element { const { value } = props; const handleSelect = useCallback((): void => { drop(window.navigator.clipboard.writeText(value)); }, [value]); return ( {props.children} ); }