Receive support for editing messages

This commit is contained in:
Josh Perez 2023-03-27 19:48:57 -04:00 committed by GitHub
parent 2781e621ad
commit 36e21c0134
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 2053 additions and 405 deletions

View file

@ -0,0 +1,112 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React, { useCallback, 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';
export type PropsType = {
closeEditHistoryModal: () => unknown;
editHistoryMessages: Array<MessagePropsType>;
getPreferredBadge: PreferredBadgeSelectorType;
i18n: LocalizerType;
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: [],
pushPanelForConversation: shouldNeverBeCalled,
renderAudioAttachment: () => <div />,
renderingContext: 'EditHistoryMessagesModal',
saveAttachment: shouldNeverBeCalled,
scrollToQuotedMessage: shouldNeverBeCalled,
shouldCollapseAbove: false,
shouldCollapseBelow: false,
shouldHideMetadata: true,
showContactModal: shouldNeverBeCalled,
showConversation: noop,
showEditHistoryModal: shouldNeverBeCalled,
showExpiredIncomingTapToViewToast: shouldNeverBeCalled,
showExpiredOutgoingTapToViewToast: shouldNeverBeCalled,
showLightboxForViewOnceMedia: shouldNeverBeCalled,
startConversation: shouldNeverBeCalled,
textDirection: TextDirection.Default,
viewStory: shouldNeverBeCalled,
};
export function EditHistoryMessagesModal({
closeEditHistoryModal,
getPreferredBadge,
editHistoryMessages,
i18n,
kickOffAttachmentDownload,
showLightbox,
}: PropsType): JSX.Element {
const containerElementRef = useRef<HTMLDivElement | null>(null);
const theme = useTheme();
const closeAndShowLightbox = useCallback(
(options: { attachment: AttachmentType; messageId: string }) => {
closeEditHistoryModal();
showLightbox(options);
},
[closeEditHistoryModal, showLightbox]
);
return (
<Modal
hasXButton
i18n={i18n}
modalName="EditHistoryMessagesModal"
onClose={closeEditHistoryModal}
title={i18n('icu:EditHistoryMessagesModal__title')}
>
<div ref={containerElementRef}>
{editHistoryMessages.map(messageAttributes => (
<Message
{...MESSAGE_DEFAULT_PROPS}
{...messageAttributes}
containerElementRef={containerElementRef}
getPreferredBadge={getPreferredBadge}
i18n={i18n}
key={messageAttributes.timestamp}
kickOffAttachmentDownload={kickOffAttachmentDownload}
showLightbox={closeAndShowLightbox}
theme={theme}
/>
))}
</div>
</Modal>
);
}

View file

@ -3,11 +3,12 @@
import React from 'react';
import type {
ContactModalStateType,
UserNotFoundModalStateType,
SafetyNumberChangedBlockingDataType,
AuthorizeArtCreatorDataType,
ContactModalStateType,
EditHistoryMessagesType,
ForwardMessagesPropsType,
SafetyNumberChangedBlockingDataType,
UserNotFoundModalStateType,
} from '../state/ducks/globalModals';
import type { LocalizerType, ThemeType } from '../types/Util';
import { missingCaseError } from '../util/missingCaseError';
@ -28,6 +29,9 @@ export type PropsType = {
// ContactModal
contactModalState: ContactModalStateType | undefined;
renderContactModal: () => JSX.Element;
// EditHistoryMessagesModal
editHistoryMessages: EditHistoryMessagesType | undefined;
renderEditHistoryMessagesModal: () => JSX.Element;
// ErrorModal
errorModalProps: { description?: string; title?: string } | undefined;
renderErrorModal: (opts: {
@ -82,6 +86,9 @@ export function GlobalModalContainer({
// ContactModal
contactModalState,
renderContactModal,
// EditHistoryMessages
editHistoryMessages,
renderEditHistoryMessagesModal,
// ErrorModal
errorModalProps,
renderErrorModal,
@ -147,6 +154,10 @@ export function GlobalModalContainer({
return renderContactModal();
}
if (editHistoryMessages) {
return renderEditHistoryMessagesModal();
}
if (forwardMessagesProps) {
return renderForwardMessagesModal();
}

View file

@ -207,6 +207,7 @@ export type PropsData = {
text?: string;
textDirection: TextDirection;
textAttachment?: AttachmentType;
isEditedMessage?: boolean;
isSticker?: boolean;
isTargeted?: boolean;
isTargetedCounter?: number;
@ -338,6 +339,7 @@ export type PropsActions = {
}) => void;
targetMessage?: (messageId: string, conversationId: string) => unknown;
showEditHistoryModal?: (id: string) => unknown;
showExpiredIncomingTapToViewToast: () => unknown;
showExpiredOutgoingTapToViewToast: () => unknown;
viewStory: ViewStoryActionCreatorType;
@ -768,9 +770,11 @@ export class Message extends React.PureComponent<Props, State> {
expirationTimestamp,
i18n,
id,
isEditedMessage,
isSticker,
isTapToViewExpired,
pushPanelForConversation,
showEditHistoryModal,
status,
text,
textAttachment,
@ -788,12 +792,14 @@ export class Message extends React.PureComponent<Props, State> {
hasText={Boolean(text)}
i18n={i18n}
id={id}
isEditedMessage={isEditedMessage}
isInline={isInline}
isShowingImage={this.isShowingImage()}
isSticker={isStickerLike}
isTapToViewExpired={isTapToViewExpired}
onWidthMeasured={isInline ? this.updateMetadataWidth : undefined}
pushPanelForConversation={pushPanelForConversation}
showEditHistoryModal={showEditHistoryModal}
status={status}
textPending={textAttachment?.pending}
timestamp={timestamp}

View file

@ -22,12 +22,14 @@ type PropsType = {
hasText: boolean;
i18n: LocalizerType;
id: string;
isEditedMessage?: boolean;
isInline?: boolean;
isShowingImage: boolean;
isSticker?: boolean;
isTapToViewExpired?: boolean;
onWidthMeasured?: (width: number) => unknown;
pushPanelForConversation: PushPanelForConversationActionType;
showEditHistoryModal?: (id: string) => unknown;
status?: MessageStatusType;
textPending?: boolean;
timestamp: number;
@ -41,12 +43,14 @@ export function MessageMetadata({
hasText,
i18n,
id,
isEditedMessage,
isInline,
isShowingImage,
isSticker,
isTapToViewExpired,
onWidthMeasured,
pushPanelForConversation,
showEditHistoryModal,
status,
textPending,
timestamp,
@ -130,6 +134,15 @@ export function MessageMetadata({
);
const children = (
<>
{isEditedMessage && showEditHistoryModal && (
<button
className="module-message__metadata__edited"
onClick={() => showEditHistoryModal(id)}
type="button"
>
{i18n('icu:MessageMetadata__edited')}
</button>
)}
{timestampNode}
{expirationLength ? (
<ExpireTimer