Receive support for editing messages
This commit is contained in:
parent
2781e621ad
commit
36e21c0134
46 changed files with 2053 additions and 405 deletions
|
@ -88,6 +88,7 @@ import type {
|
|||
import { longRunningTaskWrapper } from '../../util/longRunningTaskWrapper';
|
||||
import { drop } from '../../util/drop';
|
||||
import { strictAssert } from '../../util/assert';
|
||||
import { makeQuote } from '../../util/makeQuote';
|
||||
|
||||
// State
|
||||
// eslint-disable-next-line local-rules/type-alias-readonlydeep
|
||||
|
@ -630,7 +631,7 @@ export function setQuoteByMessageId(
|
|||
}
|
||||
|
||||
if (message) {
|
||||
const quote = await conversation.makeQuote(message);
|
||||
const quote = await makeQuote(message.attributes);
|
||||
|
||||
// In case the conversation changed while we were about to set the quote
|
||||
if (getState().conversations.selectedConversationId !== conversationId) {
|
||||
|
|
|
@ -1991,7 +1991,15 @@ function kickOffAttachmentDownload(
|
|||
`kickOffAttachmentDownload: Message ${options.messageId} missing!`
|
||||
);
|
||||
}
|
||||
await message.queueAttachmentDownloads();
|
||||
const didUpdateValues = await message.queueAttachmentDownloads();
|
||||
|
||||
if (didUpdateValues) {
|
||||
drop(
|
||||
window.Signal.Data.saveMessage(message.attributes, {
|
||||
ourUuid: window.textsecure.storage.user.getCheckedUuid().toString(),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: 'NOOP',
|
||||
|
|
|
@ -4,15 +4,24 @@
|
|||
import type { ThunkAction } from 'redux-thunk';
|
||||
import type { ReadonlyDeep } from 'type-fest';
|
||||
import type { ExplodePromiseResultType } from '../../util/explodePromise';
|
||||
import type { GroupV2PendingMemberType } from '../../model-types.d';
|
||||
import type { PropsForMessage } from '../selectors/message';
|
||||
import type {
|
||||
GroupV2PendingMemberType,
|
||||
MessageAttributesType,
|
||||
} from '../../model-types.d';
|
||||
import type {
|
||||
MessageChangedActionType,
|
||||
MessageDeletedActionType,
|
||||
MessageExpiredActionType,
|
||||
} from './conversations';
|
||||
import type { MessagePropsType } from '../selectors/message';
|
||||
import type { RecipientsByConversation } from './stories';
|
||||
import type { SafetyNumberChangeSource } from '../../components/SafetyNumberChangeDialog';
|
||||
import type { StateType as RootStateType } from '../reducer';
|
||||
import type { UUIDStringType } from '../../types/UUID';
|
||||
import * as Errors from '../../types/errors';
|
||||
import * as SingleServePromise from '../../services/singleServePromise';
|
||||
import * as Stickers from '../../types/Stickers';
|
||||
import * as Errors from '../../types/errors';
|
||||
import * as log from '../../logging/log';
|
||||
import { getMessageById } from '../../messages/getMessageById';
|
||||
import { getMessagePropsSelector } from '../selectors/message';
|
||||
import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions';
|
||||
|
@ -22,19 +31,24 @@ import { isGroupV1 } from '../../util/whatTypeOfConversation';
|
|||
import { authorizeArtCreator } from '../../textsecure/authorizeArtCreator';
|
||||
import type { AuthorizeArtCreatorOptionsType } from '../../textsecure/authorizeArtCreator';
|
||||
import { getGroupMigrationMembers } from '../../groups';
|
||||
import * as log from '../../logging/log';
|
||||
import { ToastType } from '../../types/Toast';
|
||||
import {
|
||||
MESSAGE_CHANGED,
|
||||
MESSAGE_DELETED,
|
||||
MESSAGE_EXPIRED,
|
||||
} from './conversations';
|
||||
import { SHOW_TOAST } from './toast';
|
||||
import type { ShowToastActionType } from './toast';
|
||||
|
||||
// State
|
||||
|
||||
export type EditHistoryMessagesType = ReadonlyDeep<
|
||||
Array<MessageAttributesType>
|
||||
>;
|
||||
export type ConfirmDeleteForMeModalProps = ReadonlyDeep<{
|
||||
count: number;
|
||||
}>;
|
||||
export type ForwardMessagePropsType = ReadonlyDeep<
|
||||
Omit<PropsForMessage, 'renderingContext' | 'menu' | 'contextMenu'>
|
||||
>;
|
||||
export type ForwardMessagePropsType = ReadonlyDeep<MessagePropsType>;
|
||||
export type ForwardMessagesPropsType = ReadonlyDeep<{
|
||||
messages: Array<ForwardMessagePropsType>;
|
||||
onForward?: () => void;
|
||||
|
@ -57,6 +71,7 @@ type MigrateToGV2PropsType = ReadonlyDeep<{
|
|||
export type GlobalModalsStateType = ReadonlyDeep<{
|
||||
addUserToAnotherGroupModalContactId?: string;
|
||||
contactModalState?: ContactModalStateType;
|
||||
editHistoryMessages?: EditHistoryMessagesType;
|
||||
errorModalProps?: {
|
||||
description?: string;
|
||||
title?: string;
|
||||
|
@ -115,6 +130,8 @@ const CONFIRM_AUTH_ART_CREATOR_PENDING =
|
|||
'globalModals/CONFIRM_AUTH_ART_CREATOR_PENDING';
|
||||
const CONFIRM_AUTH_ART_CREATOR_FULFILLED =
|
||||
'globalModals/CONFIRM_AUTH_ART_CREATOR_FULFILLED';
|
||||
const SHOW_EDIT_HISTORY_MODAL = 'globalModals/SHOW_EDIT_HISTORY_MODAL';
|
||||
const CLOSE_EDIT_HISTORY_MODAL = 'globalModals/CLOSE_EDIT_HISTORY_MODAL';
|
||||
|
||||
export type ContactModalStateType = ReadonlyDeep<{
|
||||
contactId: string;
|
||||
|
@ -264,34 +281,50 @@ type ConfirmAuthArtCreatorFulfilledActionType = ReadonlyDeep<{
|
|||
type: typeof CONFIRM_AUTH_ART_CREATOR_FULFILLED;
|
||||
}>;
|
||||
|
||||
type ShowEditHistoryModalActionType = ReadonlyDeep<{
|
||||
type: typeof SHOW_EDIT_HISTORY_MODAL;
|
||||
payload: {
|
||||
messages: EditHistoryMessagesType;
|
||||
};
|
||||
}>;
|
||||
|
||||
type CloseEditHistoryModalActionType = ReadonlyDeep<{
|
||||
type: typeof CLOSE_EDIT_HISTORY_MODAL;
|
||||
}>;
|
||||
|
||||
export type GlobalModalsActionType = ReadonlyDeep<
|
||||
| StartMigrationToGV2ActionType
|
||||
| CloseGV2MigrationDialogActionType
|
||||
| HideContactModalActionType
|
||||
| ShowContactModalActionType
|
||||
| HideWhatsNewModalActionType
|
||||
| ShowWhatsNewModalActionType
|
||||
| HideUserNotFoundModalActionType
|
||||
| ShowUserNotFoundModalActionType
|
||||
| HideStoriesSettingsActionType
|
||||
| ShowStoriesSettingsActionType
|
||||
| HideSendAnywayDialogActiontype
|
||||
| ShowSendAnywayDialogActionType
|
||||
| CloseStickerPackPreviewActionType
|
||||
| ShowStickerPackPreviewActionType
|
||||
| CloseErrorModalActionType
|
||||
| ShowErrorModalActionType
|
||||
| CloseShortcutGuideModalActionType
|
||||
| ShowShortcutGuideModalActionType
|
||||
| CancelAuthArtCreatorActionType
|
||||
| ConfirmAuthArtCreatorPendingActionType
|
||||
| CloseEditHistoryModalActionType
|
||||
| CloseErrorModalActionType
|
||||
| CloseGV2MigrationDialogActionType
|
||||
| CloseShortcutGuideModalActionType
|
||||
| CloseStickerPackPreviewActionType
|
||||
| ConfirmAuthArtCreatorFulfilledActionType
|
||||
| ConfirmAuthArtCreatorPendingActionType
|
||||
| HideContactModalActionType
|
||||
| HideSendAnywayDialogActiontype
|
||||
| HideStoriesSettingsActionType
|
||||
| HideUserNotFoundModalActionType
|
||||
| HideWhatsNewModalActionType
|
||||
| MessageChangedActionType
|
||||
| MessageDeletedActionType
|
||||
| MessageExpiredActionType
|
||||
| ShowAuthArtCreatorActionType
|
||||
| ShowContactModalActionType
|
||||
| ShowEditHistoryModalActionType
|
||||
| ShowErrorModalActionType
|
||||
| ShowSendAnywayDialogActionType
|
||||
| ShowShortcutGuideModalActionType
|
||||
| ShowStickerPackPreviewActionType
|
||||
| ShowStoriesSettingsActionType
|
||||
| ShowUserNotFoundModalActionType
|
||||
| ShowWhatsNewModalActionType
|
||||
| StartMigrationToGV2ActionType
|
||||
| ToggleAddUserToAnotherGroupModalActionType
|
||||
| ToggleForwardMessagesModalActionType
|
||||
| ToggleProfileEditorActionType
|
||||
| ToggleProfileEditorErrorActionType
|
||||
| ToggleSafetyNumberModalActionType
|
||||
| ToggleAddUserToAnotherGroupModalActionType
|
||||
| ToggleSignalConnectionsModalActionType
|
||||
| ToggleConfirmationModalActionType
|
||||
>;
|
||||
|
@ -299,34 +332,36 @@ export type GlobalModalsActionType = ReadonlyDeep<
|
|||
// Action Creators
|
||||
|
||||
export const actions = {
|
||||
hideContactModal,
|
||||
showContactModal,
|
||||
hideWhatsNewModal,
|
||||
showWhatsNewModal,
|
||||
hideUserNotFoundModal,
|
||||
showUserNotFoundModal,
|
||||
hideStoriesSettings,
|
||||
showStoriesSettings,
|
||||
cancelAuthorizeArtCreator,
|
||||
closeEditHistoryModal,
|
||||
closeErrorModal,
|
||||
closeGV2MigrationDialog,
|
||||
closeShortcutGuideModal,
|
||||
closeStickerPackPreview,
|
||||
confirmAuthorizeArtCreator,
|
||||
hideBlockingSafetyNumberChangeDialog,
|
||||
hideContactModal,
|
||||
hideStoriesSettings,
|
||||
hideUserNotFoundModal,
|
||||
hideWhatsNewModal,
|
||||
showAuthorizeArtCreator,
|
||||
showBlockingSafetyNumberChangeDialog,
|
||||
showContactModal,
|
||||
showEditHistoryModal,
|
||||
showErrorModal,
|
||||
showGV2MigrationDialog,
|
||||
showShortcutGuideModal,
|
||||
showStickerPackPreview,
|
||||
showStoriesSettings,
|
||||
showUserNotFoundModal,
|
||||
showWhatsNewModal,
|
||||
toggleAddUserToAnotherGroupModal,
|
||||
toggleConfirmationModal,
|
||||
toggleForwardMessagesModal,
|
||||
toggleProfileEditor,
|
||||
toggleProfileEditorHasError,
|
||||
toggleSafetyNumberModal,
|
||||
toggleAddUserToAnotherGroupModal,
|
||||
toggleSignalConnectionsModal,
|
||||
toggleConfirmationModal,
|
||||
showGV2MigrationDialog,
|
||||
closeGV2MigrationDialog,
|
||||
showStickerPackPreview,
|
||||
closeStickerPackPreview,
|
||||
closeErrorModal,
|
||||
showErrorModal,
|
||||
closeShortcutGuideModal,
|
||||
showShortcutGuideModal,
|
||||
showAuthorizeArtCreator,
|
||||
cancelAuthorizeArtCreator,
|
||||
confirmAuthorizeArtCreator,
|
||||
};
|
||||
|
||||
export const useGlobalModalActions = (): BoundActionCreatorsMapObject<
|
||||
|
@ -632,6 +667,56 @@ function cancelAuthorizeArtCreator(): ThunkAction<
|
|||
};
|
||||
}
|
||||
|
||||
function copyOverMessageAttributesIntoEditHistory(
|
||||
messageAttributes: ReadonlyDeep<MessageAttributesType>
|
||||
): EditHistoryMessagesType | undefined {
|
||||
if (!messageAttributes.editHistory) {
|
||||
return;
|
||||
}
|
||||
|
||||
return messageAttributes.editHistory.map(editedMessageAttributes => ({
|
||||
...messageAttributes,
|
||||
...editedMessageAttributes,
|
||||
// For timestamp uniqueness of messages
|
||||
sent_at: editedMessageAttributes.timestamp,
|
||||
}));
|
||||
}
|
||||
|
||||
function showEditHistoryModal(
|
||||
messageId: string
|
||||
): ThunkAction<void, RootStateType, unknown, ShowEditHistoryModalActionType> {
|
||||
return async dispatch => {
|
||||
const message = await getMessageById(messageId);
|
||||
|
||||
if (!message) {
|
||||
log.warn('showEditHistoryModal: no message found');
|
||||
return;
|
||||
}
|
||||
|
||||
const messageAttributes = message.attributes;
|
||||
const nextEditHistoryMessages =
|
||||
copyOverMessageAttributesIntoEditHistory(messageAttributes);
|
||||
|
||||
if (!nextEditHistoryMessages) {
|
||||
log.warn('showEditHistoryModal: no edit history for message');
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: SHOW_EDIT_HISTORY_MODAL,
|
||||
payload: {
|
||||
messages: nextEditHistoryMessages,
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function closeEditHistoryModal(): CloseEditHistoryModalActionType {
|
||||
return {
|
||||
type: CLOSE_EDIT_HISTORY_MODAL,
|
||||
};
|
||||
}
|
||||
|
||||
export function showAuthorizeArtCreator(
|
||||
data: AuthorizeArtCreatorDataType
|
||||
): ShowAuthArtCreatorActionType {
|
||||
|
@ -896,5 +981,71 @@ export function reducer(
|
|||
};
|
||||
}
|
||||
|
||||
if (action.type === SHOW_EDIT_HISTORY_MODAL) {
|
||||
return {
|
||||
...state,
|
||||
editHistoryMessages: action.payload.messages,
|
||||
};
|
||||
}
|
||||
|
||||
if (action.type === CLOSE_EDIT_HISTORY_MODAL) {
|
||||
return {
|
||||
...state,
|
||||
editHistoryMessages: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
action.type === MESSAGE_CHANGED ||
|
||||
action.type === MESSAGE_DELETED ||
|
||||
action.type === MESSAGE_EXPIRED
|
||||
) {
|
||||
if (!state.editHistoryMessages) {
|
||||
return state;
|
||||
}
|
||||
|
||||
if (action.type === MESSAGE_DELETED || action.type === MESSAGE_EXPIRED) {
|
||||
const hasMessageId = state.editHistoryMessages.some(
|
||||
edit => edit.id === action.payload.id
|
||||
);
|
||||
|
||||
if (!hasMessageId) {
|
||||
return state;
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
editHistoryMessages: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
if (action.type === MESSAGE_CHANGED) {
|
||||
if (!action.payload.data.editHistory) {
|
||||
return state;
|
||||
}
|
||||
|
||||
const hasMessageId = state.editHistoryMessages.some(
|
||||
edit => edit.id === action.payload.id
|
||||
);
|
||||
|
||||
if (!hasMessageId) {
|
||||
return state;
|
||||
}
|
||||
|
||||
const nextEditHistoryMessages = copyOverMessageAttributesIntoEditHistory(
|
||||
action.payload.data
|
||||
);
|
||||
|
||||
if (!nextEditHistoryMessages) {
|
||||
return state;
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
editHistoryMessages: nextEditHistoryMessages,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -136,6 +136,10 @@ type FormattedContact = Partial<ConversationType> &
|
|||
| 'unblurredAvatarPath'
|
||||
>;
|
||||
export type PropsForMessage = Omit<TimelineMessagePropsData, 'interactionMode'>;
|
||||
export type MessagePropsType = Omit<
|
||||
PropsForMessage,
|
||||
'renderingContext' | 'menu' | 'contextMenu'
|
||||
>;
|
||||
type PropsForUnsupportedMessage = {
|
||||
canProcessNow: boolean;
|
||||
contact: FormattedContact;
|
||||
|
@ -718,6 +722,7 @@ export const getPropsForMessage = (
|
|||
giftBadge: message.giftBadge,
|
||||
id: message.id,
|
||||
isBlocked: conversation.isBlocked || false,
|
||||
isEditedMessage: Boolean(message.editHistory),
|
||||
isMessageRequestAccepted: conversation?.acceptedMessageRequest ?? true,
|
||||
isTargeted,
|
||||
isTargetedCounter: isTargeted ? targetedMessageCounter : undefined,
|
||||
|
|
57
ts/state/smart/EditHistoryMessagesModal.tsx
Normal file
57
ts/state/smart/EditHistoryMessagesModal.tsx
Normal file
|
@ -0,0 +1,57 @@
|
|||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import type { GlobalModalsStateType } from '../ducks/globalModals';
|
||||
import type { MessageAttributesType } from '../../model-types.d';
|
||||
import type { StateType } from '../reducer';
|
||||
import { EditHistoryMessagesModal } from '../../components/EditHistoryMessagesModal';
|
||||
import { getIntl } from '../selectors/user';
|
||||
import { getMessagePropsSelector } from '../selectors/message';
|
||||
import { getPreferredBadgeSelector } from '../selectors/badges';
|
||||
import { useConversationsActions } from '../ducks/conversations';
|
||||
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||
import { useLightboxActions } from '../ducks/lightbox';
|
||||
import { strictAssert } from '../../util/assert';
|
||||
|
||||
export function SmartEditHistoryMessagesModal(): JSX.Element {
|
||||
const i18n = useSelector(getIntl);
|
||||
|
||||
const { closeEditHistoryModal } = useGlobalModalActions();
|
||||
|
||||
const { kickOffAttachmentDownload } = useConversationsActions();
|
||||
const { showLightbox } = useLightboxActions();
|
||||
|
||||
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
||||
|
||||
const { editHistoryMessages: messagesAttributes } = useSelector<
|
||||
StateType,
|
||||
GlobalModalsStateType
|
||||
>(state => state.globalModals);
|
||||
|
||||
const messagePropsSelector = useSelector(getMessagePropsSelector);
|
||||
|
||||
strictAssert(messagesAttributes, 'messages not provided');
|
||||
|
||||
const editHistoryMessages = useMemo(() => {
|
||||
return messagesAttributes.map(messageAttributes => ({
|
||||
...messagePropsSelector(messageAttributes as MessageAttributesType),
|
||||
// Make sure the messages don't get an "edited" badge
|
||||
editHistory: undefined,
|
||||
// Do not show the same reactions in the message history UI
|
||||
reactions: undefined,
|
||||
}));
|
||||
}, [messagesAttributes, messagePropsSelector]);
|
||||
|
||||
return (
|
||||
<EditHistoryMessagesModal
|
||||
closeEditHistoryModal={closeEditHistoryModal}
|
||||
editHistoryMessages={editHistoryMessages}
|
||||
getPreferredBadge={getPreferredBadge}
|
||||
i18n={i18n}
|
||||
kickOffAttachmentDownload={kickOffAttachmentDownload}
|
||||
showLightbox={showLightbox}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -10,6 +10,7 @@ import { ErrorModal } from '../../components/ErrorModal';
|
|||
import { GlobalModalContainer } from '../../components/GlobalModalContainer';
|
||||
import { SmartAddUserToAnotherGroupModal } from './AddUserToAnotherGroupModal';
|
||||
import { SmartContactModal } from './ContactModal';
|
||||
import { SmartEditHistoryMessagesModal } from './EditHistoryMessagesModal';
|
||||
import { SmartForwardMessagesModal } from './ForwardMessagesModal';
|
||||
import { SmartProfileEditorModal } from './ProfileEditorModal';
|
||||
import { SmartSafetyNumberModal } from './SafetyNumberModal';
|
||||
|
@ -21,6 +22,10 @@ import { getConversationsStoppingSend } from '../selectors/conversations';
|
|||
import { getIntl, getTheme } from '../selectors/user';
|
||||
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||
|
||||
function renderEditHistoryMessagesModal(): JSX.Element {
|
||||
return <SmartEditHistoryMessagesModal />;
|
||||
}
|
||||
|
||||
function renderProfileEditor(): JSX.Element {
|
||||
return <SmartProfileEditorModal />;
|
||||
}
|
||||
|
@ -55,6 +60,7 @@ export function SmartGlobalModalContainer(): JSX.Element {
|
|||
const {
|
||||
addUserToAnotherGroupModalContactId,
|
||||
contactModalState,
|
||||
editHistoryMessages,
|
||||
errorModalProps,
|
||||
forwardMessagesProps,
|
||||
isProfileEditorVisible,
|
||||
|
@ -120,6 +126,7 @@ export function SmartGlobalModalContainer(): JSX.Element {
|
|||
<GlobalModalContainer
|
||||
addUserToAnotherGroupModalContactId={addUserToAnotherGroupModalContactId}
|
||||
contactModalState={contactModalState}
|
||||
editHistoryMessages={editHistoryMessages}
|
||||
errorModalProps={errorModalProps}
|
||||
forwardMessagesProps={forwardMessagesProps}
|
||||
hasSafetyNumberChangeModal={hasSafetyNumberChangeModal}
|
||||
|
@ -133,6 +140,7 @@ export function SmartGlobalModalContainer(): JSX.Element {
|
|||
isWhatsNewVisible={isWhatsNewVisible}
|
||||
renderAddUserToAnotherGroup={renderAddUserToAnotherGroup}
|
||||
renderContactModal={renderContactModal}
|
||||
renderEditHistoryMessagesModal={renderEditHistoryMessagesModal}
|
||||
renderErrorModal={renderErrorModal}
|
||||
renderForwardMessagesModal={renderForwardMessagesModal}
|
||||
renderProfileEditor={renderProfileEditor}
|
||||
|
|
|
@ -130,6 +130,7 @@ export function SmartTimelineItem(props: ExternalProps): JSX.Element {
|
|||
|
||||
const {
|
||||
showContactModal,
|
||||
showEditHistoryModal,
|
||||
toggleForwardMessagesModal,
|
||||
toggleSafetyNumberModal,
|
||||
} = useGlobalModalActions();
|
||||
|
@ -161,6 +162,7 @@ export function SmartTimelineItem(props: ExternalProps): JSX.Element {
|
|||
shouldCollapseBelow={shouldCollapseBelow}
|
||||
shouldHideMetadata={shouldHideMetadata}
|
||||
shouldRenderDateHeader={shouldRenderDateHeader}
|
||||
showEditHistoryModal={showEditHistoryModal}
|
||||
i18n={i18n}
|
||||
interactionMode={interactionMode}
|
||||
theme={theme}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue