diff --git a/ts/state/ducks/globalModals.ts b/ts/state/ducks/globalModals.ts index aeed4330ad..df8f10a83b 100644 --- a/ts/state/ducks/globalModals.ts +++ b/ts/state/ducks/globalModals.ts @@ -36,9 +36,11 @@ import { MESSAGE_CHANGED, MESSAGE_DELETED, MESSAGE_EXPIRED, + actions as conversationsActions, } from './conversations'; import { SHOW_TOAST } from './toast'; import type { ShowToastActionType } from './toast'; +import { isDownloaded } from '../../types/Attachment'; // State @@ -564,6 +566,14 @@ function toggleForwardMessagesModal( ); } + const attachments = message.get('attachments') ?? []; + + if (!attachments.every(isDownloaded)) { + dispatch( + conversationsActions.kickOffAttachmentDownload({ messageId }) + ); + } + const messagePropsSelector = getMessagePropsSelector(getState()); const messageProps = messagePropsSelector(message.attributes); @@ -832,6 +842,21 @@ export function confirmAuthorizeArtCreator(): ThunkAction< }; } +function copyOverMessageAttributesIntoForwardMessages( + messagesProps: ReadonlyArray, + attributes: ReadonlyDeep +): ReadonlyArray { + return messagesProps.map(messageProps => { + if (messageProps.id !== attributes.id) { + return messageProps; + } + return { + ...messageProps, + attachments: attributes.attachments, + }; + }); +} + // Reducer export function getEmptyState(): GlobalModalsStateType { @@ -1099,57 +1124,77 @@ export function reducer( }; } - 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 (state.forwardMessagesProps != null) { 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) { + if ( + !state.forwardMessagesProps.messages.some(message => { + return message.id === action.payload.id; + }) + ) { return state; } return { ...state, - editHistoryMessages: nextEditHistoryMessages, + forwardMessagesProps: { + ...state.forwardMessagesProps, + messages: copyOverMessageAttributesIntoForwardMessages( + state.forwardMessagesProps.messages, + action.payload.data + ), + }, }; } } + if (state.editHistoryMessages != null) { + if ( + action.type === MESSAGE_CHANGED || + action.type === MESSAGE_DELETED || + action.type === MESSAGE_EXPIRED + ) { + 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; } diff --git a/ts/state/smart/ForwardMessagesModal.tsx b/ts/state/smart/ForwardMessagesModal.tsx index 96d322919a..a3e10352d4 100644 --- a/ts/state/smart/ForwardMessagesModal.tsx +++ b/ts/state/smart/ForwardMessagesModal.tsx @@ -3,12 +3,16 @@ import React, { useState } from 'react'; import { useSelector } from 'react-redux'; -import type { ForwardMessagesPropsType } from '../ducks/globalModals'; +import type { + ForwardMessagePropsType, + ForwardMessagesPropsType, +} from '../ducks/globalModals'; import type { StateType } from '../reducer'; import * as log from '../../logging/log'; import { ForwardMessagesModal } from '../../components/ForwardMessagesModal'; import { LinkPreviewSourceType } from '../../types/LinkPreview'; import * as Errors from '../../types/errors'; +import type { GetConversationByIdType } from '../selectors/conversations'; import { getAllComposableConversations, getConversationSelector, @@ -31,12 +35,53 @@ import { useLinkPreviewActions } from '../ducks/linkPreviews'; import { SmartCompositionTextArea } from './CompositionTextArea'; import { useToastActions } from '../ducks/toast'; import { hydrateRanges } from '../../types/BodyRange'; +import { isDownloaded } from '../../types/Attachment'; + +function toMessageForwardDraft( + props: ForwardMessagePropsType, + getConversation: GetConversationByIdType +): MessageForwardDraft { + return { + attachments: props.attachments ?? [], + bodyRanges: hydrateRanges(props.bodyRanges, getConversation), + hasContact: Boolean(props.contact), + isSticker: Boolean(props.isSticker), + messageBody: props.text, + originalMessageId: props.id, + previews: props.previews ?? [], + }; +} export function SmartForwardMessagesModal(): JSX.Element | null { const forwardMessagesProps = useSelector< StateType, ForwardMessagesPropsType | undefined >(state => state.globalModals.forwardMessagesProps); + + if (forwardMessagesProps == null) { + return null; + } + + if ( + !forwardMessagesProps.messages.every(message => { + return message.attachments?.every(isDownloaded) ?? true; + }) + ) { + return null; + } + + return ( + + ); +} + +function SmartForwardMessagesModalInner({ + forwardMessagesProps, +}: { + forwardMessagesProps: ForwardMessagesPropsType; +}): JSX.Element | null { const candidateConversations = useSelector(getAllComposableConversations); const getPreferredBadge = useSelector(getPreferredBadgeSelector); const getConversation = useSelector(getConversationSelector); @@ -51,19 +96,9 @@ export function SmartForwardMessagesModal(): JSX.Element | null { const [drafts, setDrafts] = useState>( () => { - return ( - forwardMessagesProps?.messages.map((props): MessageForwardDraft => { - return { - attachments: props.attachments ?? [], - bodyRanges: hydrateRanges(props.bodyRanges, getConversation), - hasContact: Boolean(props.contact), - isSticker: Boolean(props.isSticker), - messageBody: props.text, - originalMessageId: props.id, - previews: props.previews ?? [], - }; - }) ?? [] - ); + return forwardMessagesProps.messages.map((props): MessageForwardDraft => { + return toMessageForwardDraft(props, getConversation); + }); } ); diff --git a/ts/util/maybeForwardMessages.ts b/ts/util/maybeForwardMessages.ts index b95c7f281d..9d0c3ac158 100644 --- a/ts/util/maybeForwardMessages.ts +++ b/ts/util/maybeForwardMessages.ts @@ -3,7 +3,7 @@ import { orderBy } from 'lodash'; import type { AttachmentType } from '../types/Attachment'; -import { isVoiceMessage } from '../types/Attachment'; +import { isVoiceMessage, isDownloaded } from '../types/Attachment'; import type { LinkPreviewType, LinkPreviewWithHydratedData, @@ -57,41 +57,31 @@ export function isDraftEditable(draft: MessageForwardDraft): boolean { return true; } -export function isDraftForwardable(draft: MessageForwardDraft): boolean { - const messageLength = draft.messageBody?.length ?? 0; - if (messageLength > 0) { - return true; +function isDraftEmpty(draft: MessageForwardDraft) { + const { messageBody, attachments, isSticker, hasContact } = draft; + if (isSticker || hasContact) { + return false; } - if (draft.isSticker) { - return true; + if (attachments != null && attachments.length > 0) { + return false; } - if (draft.hasContact) { - return true; + if (messageBody != null && messageBody.length > 0) { + return false; } - const attachmentsLength = draft.attachments?.length ?? 0; - if (attachmentsLength > 0) { - return true; - } - return false; + return true; } -export function isMessageForwardable(message: MessageAttributesType): boolean { - const { body, attachments, sticker, contact } = message; - const messageLength = body?.length ?? 0; - if (messageLength > 0) { - return true; +export function isDraftForwardable(draft: MessageForwardDraft): boolean { + const { attachments } = draft; + if (isDraftEmpty(draft)) { + return false; } - if (sticker) { - return true; + if (attachments != null && attachments.length > 0) { + if (!attachments.every(isDownloaded)) { + return false; + } } - if (contact?.length) { - return true; - } - const attachmentsLength = attachments?.length ?? 0; - if (attachmentsLength > 0) { - return true; - } - return false; + return true; } export function sortByMessageOrder(