// Copyright 2023 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import React, { useCallback, useState, useRef } from 'react'; import { noop } from 'lodash'; import type { AttachmentType } from '../types/Attachment'; import type { LocalizerType } from '../types/Util'; import type { MessagePropsType } from '../state/selectors/message'; import type { PreferredBadgeSelectorType } from '../state/selectors/badges'; import { Message, TextDirection } from './conversation/Message'; import { Modal } from './Modal'; import { WidthBreakpoint } from './_util'; import { shouldNeverBeCalled } from '../util/shouldNeverBeCalled'; import { useTheme } from '../hooks/useTheme'; import { isSameDay } from '../util/timestamp'; import { TimelineDateHeader } from './conversation/TimelineDateHeader'; import { drop } from '../util/drop'; export type PropsType = { closeEditHistoryModal: () => unknown; editHistoryMessages: Array; getPreferredBadge: PreferredBadgeSelectorType; i18n: LocalizerType; platform: string; kickOffAttachmentDownload: (options: { attachment: AttachmentType; messageId: string; }) => void; showLightbox: (options: { attachment: AttachmentType; messageId: string; }) => void; }; const MESSAGE_DEFAULT_PROPS = { canDeleteForEveryone: false, checkForAccount: shouldNeverBeCalled, clearSelectedMessage: shouldNeverBeCalled, clearTargetedMessage: shouldNeverBeCalled, containerWidthBreakpoint: WidthBreakpoint.Medium, doubleCheckMissingQuoteReference: shouldNeverBeCalled, interactionMode: 'mouse' as const, isBlocked: false, isMessageRequestAccepted: true, markAttachmentAsCorrupted: shouldNeverBeCalled, messageExpanded: shouldNeverBeCalled, onReplyToMessage: shouldNeverBeCalled, onToggleSelect: shouldNeverBeCalled, openGiftBadge: shouldNeverBeCalled, openLink: shouldNeverBeCalled, previews: [], retryMessageSend: shouldNeverBeCalled, pushPanelForConversation: shouldNeverBeCalled, renderAudioAttachment: () =>
, renderingContext: 'EditHistoryMessagesModal', saveAttachment: shouldNeverBeCalled, scrollToQuotedMessage: shouldNeverBeCalled, shouldCollapseAbove: false, shouldCollapseBelow: false, shouldHideMetadata: false, showContactModal: shouldNeverBeCalled, showConversation: noop, showEditHistoryModal: noop, showExpiredIncomingTapToViewToast: shouldNeverBeCalled, showExpiredOutgoingTapToViewToast: shouldNeverBeCalled, showLightboxForViewOnceMedia: shouldNeverBeCalled, startConversation: shouldNeverBeCalled, textDirection: TextDirection.Default, viewStory: shouldNeverBeCalled, }; export function EditHistoryMessagesModal({ closeEditHistoryModal, getPreferredBadge, editHistoryMessages, i18n, platform, kickOffAttachmentDownload, showLightbox, }: PropsType): JSX.Element { const containerElementRef = useRef(null); const theme = useTheme(); const closeAndShowLightbox = useCallback( (options: { attachment: AttachmentType; messageId: string }) => { closeEditHistoryModal(); showLightbox(options); }, [closeEditHistoryModal, showLightbox] ); // These states aren't in redux; they are meant to last only as long as this dialog. const [revealedSpoilersById, setRevealedSpoilersById] = useState< Record | undefined> >({}); const [displayLimitById, setDisplayLimitById] = useState< Record >({}); const [currentMessage, ...pastEdits] = editHistoryMessages; const currentMessageId = `${currentMessage.id}.${currentMessage.timestamp}`; let previousItem = currentMessage; return (
{ const update = { ...displayLimitById, [messageId]: displayLimit, }; setDisplayLimitById(update); }} onContextMenu={() => { drop( window.navigator.clipboard.writeText( String(currentMessage.timestamp) ) ); }} platform={platform} showLightbox={closeAndShowLightbox} showSpoiler={(messageId, data) => { const update = { ...revealedSpoilersById, [messageId]: data, }; setRevealedSpoilersById(update); }} theme={theme} />

{i18n('icu:EditHistoryMessagesModal__title')}

{pastEdits.map(messageAttributes => { const syntheticId = `${messageAttributes.id}.${messageAttributes.timestamp}`; const shouldShowDateHeader = Boolean( !previousItem || // This comparison avoids strange header behavior for out-of-order messages. (messageAttributes.timestamp > previousItem.timestamp && !isSameDay(previousItem.timestamp, messageAttributes.timestamp)) ); const dateHeaderElement = shouldShowDateHeader ? ( ) : null; previousItem = messageAttributes; return ( {dateHeaderElement} { const update = { ...displayLimitById, [messageId]: displayLimit, }; setDisplayLimitById(update); }} onContextMenu={() => { drop( window.navigator.clipboard.writeText( String(messageAttributes.timestamp) ) ); }} platform={platform} showLightbox={closeAndShowLightbox} showSpoiler={(messageId, data) => { const update = { ...revealedSpoilersById, [messageId]: data, }; setRevealedSpoilersById(update); }} theme={theme} /> ); })}
); }