diff --git a/ts/background.ts b/ts/background.ts index 990fd48cb7..1e2e52464c 100644 --- a/ts/background.ts +++ b/ts/background.ts @@ -1631,7 +1631,16 @@ export async function startApp(): Promise { ) { const { selectedMessage } = state.conversations; - conversation.trigger('toggle-reply', selectedMessage); + const composerState = window.reduxStore + ? window.reduxStore.getState().composer + : undefined; + const quote = composerState?.quotedMessage?.quote; + + window.reduxActions.composer.setQuoteByMessageId( + conversation.id, + quote ? undefined : selectedMessage + ); + event.preventDefault(); event.stopPropagation(); return; diff --git a/ts/components/App.tsx b/ts/components/App.tsx index 73f0cb79b3..cf2d893c71 100644 --- a/ts/components/App.tsx +++ b/ts/components/App.tsx @@ -45,12 +45,13 @@ type PropsType = { executeMenuRole: ExecuteMenuRoleType; executeMenuAction: (action: MenuActionType) => void; + hideToast: () => unknown; titleBarDoubleClick: () => void; toast?: { toastType: ToastType; parameters?: ReplacementValuesType; }; - hideToast: () => unknown; + scrollToMessage: (conversationId: string, messageId: string) => unknown; toggleStoriesView: () => unknown; viewStory: ViewStoryActionCreatorType; } & ComponentProps; @@ -79,6 +80,7 @@ export function App({ renderStories, renderStoryViewer, requestVerification, + scrollToMessage, selectedConversationId, selectedMessage, selectedMessageSource, @@ -116,6 +118,7 @@ export function App({ renderCustomizingPreferredReactionsModal } renderLeftPane={renderLeftPane} + scrollToMessage={scrollToMessage} selectedConversationId={selectedConversationId} selectedMessage={selectedMessage} selectedMessageSource={selectedMessageSource} diff --git a/ts/components/CompositionArea.stories.tsx b/ts/components/CompositionArea.stories.tsx index 68700ccd5c..83cf3e9c75 100644 --- a/ts/components/CompositionArea.stories.tsx +++ b/ts/components/CompositionArea.stories.tsx @@ -43,6 +43,7 @@ const useProps = (overrideProps: Partial = {}): Props => ({ removeAttachment: action('removeAttachment'), theme: React.useContext(StorybookThemeContext), setComposerFocus: action('setComposerFocus'), + setQuoteByMessageId: action('setQuoteByMessageId'), // AttachmentList draftAttachments: overrideProps.draftAttachments || [], @@ -63,8 +64,7 @@ const useProps = (overrideProps: Partial = {}): Props => ({ onCloseLinkPreview: action('onCloseLinkPreview'), // Quote quotedMessageProps: overrideProps.quotedMessageProps, - onClickQuotedMessage: action('onClickQuotedMessage'), - setQuotedMessage: action('setQuotedMessage'), + scrollToMessage: action('scrollToMessage'), // MediaEditor imageToBlurHash: async () => 'LDA,FDBnm+I=p{tkIUI;~UkpELV]', // MediaQualitySelector diff --git a/ts/components/CompositionArea.tsx b/ts/components/CompositionArea.tsx index 1404983992..4c0b2747cc 100644 --- a/ts/components/CompositionArea.tsx +++ b/ts/components/CompositionArea.tsx @@ -97,7 +97,6 @@ export type OwnProps = Readonly<{ linkPreviewResult?: LinkPreviewType; messageRequestsEnabled?: boolean; onClearAttachments(): unknown; - onClickQuotedMessage(): unknown; onCloseLinkPreview(): unknown; processAttachments: (options: { conversationId: string; @@ -119,13 +118,18 @@ export type OwnProps = Readonly<{ } ): unknown; openConversation(conversationId: string): unknown; + quotedMessageId?: string; quotedMessageProps?: Omit< QuoteProps, 'i18n' | 'onClick' | 'onClose' | 'withContentAbove' >; removeAttachment: (conversationId: string, filePath: string) => unknown; + scrollToMessage: (conversationId: string, messageId: string) => unknown; setComposerFocus: (conversationId: string) => unknown; - setQuotedMessage(message: undefined): unknown; + setQuoteByMessageId( + conversationId: string, + messageId: string | undefined + ): unknown; shouldSendHighQualityAttachments: boolean; startRecording: () => unknown; theme: ThemeType; @@ -179,6 +183,7 @@ export function CompositionArea({ removeAttachment, sendMultiMediaMessage, setComposerFocus, + setQuoteByMessageId, theme, // AttachmentList @@ -196,9 +201,9 @@ export function CompositionArea({ linkPreviewResult, onCloseLinkPreview, // Quote + quotedMessageId, quotedMessageProps, - onClickQuotedMessage, - setQuotedMessage, + scrollToMessage, // MediaQualitySelector onSelectMediaQuality, shouldSendHighQualityAttachments, @@ -639,18 +644,15 @@ export function CompositionArea({ 'CompositionArea__row--column' )} > - {quotedMessageProps && ( + {quotedMessageId && quotedMessageProps && (
scrollToMessage(conversationId, quotedMessageId)} onClose={() => { - // This one is for redux... - setQuotedMessage(undefined); - // and this is for conversation_view. - clearQuotedMessage?.(); + setQuoteByMessageId(conversationId, undefined); }} />
diff --git a/ts/components/Inbox.tsx b/ts/components/Inbox.tsx index effc600b46..0e21c3e5ac 100644 --- a/ts/components/Inbox.tsx +++ b/ts/components/Inbox.tsx @@ -23,6 +23,7 @@ export type PropsType = { isCustomizingPreferredReactions: boolean; renderCustomizingPreferredReactionsModal: () => JSX.Element; renderLeftPane: () => JSX.Element; + scrollToMessage: (conversationId: string, messageId: string) => unknown; selectedConversationId?: string; selectedMessage?: string; selectedMessageSource?: SelectedMessageSource; @@ -36,6 +37,7 @@ export function Inbox({ isCustomizingPreferredReactions, renderCustomizingPreferredReactionsModal, renderLeftPane, + scrollToMessage, selectedConversationId, selectedMessage, selectedMessageSource, @@ -93,10 +95,11 @@ export function Inbox({ selectedMessage && selectedMessageSource !== SelectedMessageSource.Focus ) { - conversation.trigger('scroll-to-message', selectedMessage); + scrollToMessage(conversation.id, selectedMessage); } }, [ prevConversation, + scrollToMessage, selectedConversationId, selectedMessage, selectedMessageSource, diff --git a/ts/components/conversation/Quote.stories.tsx b/ts/components/conversation/Quote.stories.tsx index b5d4ca3ba5..f145b756ff 100644 --- a/ts/components/conversation/Quote.stories.tsx +++ b/ts/components/conversation/Quote.stories.tsx @@ -122,7 +122,7 @@ const defaultMessageProps: TimelineMessagesProps = { renderEmojiPicker: () =>
, renderReactionPicker: () =>
, renderAudioAttachment: () =>
*AudioAttachment*
, - replyToMessage: action('default--replyToMessage'), + setQuoteByMessageId: action('default--setQuoteByMessageId'), retrySend: action('default--retrySend'), retryDeleteForEveryone: action('default--retryDeleteForEveryone'), scrollToQuotedMessage: action('default--scrollToQuotedMessage'), diff --git a/ts/components/conversation/Timeline.stories.tsx b/ts/components/conversation/Timeline.stories.tsx index d711dfff85..8690eb7aac 100644 --- a/ts/components/conversation/Timeline.stories.tsx +++ b/ts/components/conversation/Timeline.stories.tsx @@ -276,7 +276,7 @@ const actions = () => ({ updateSharedGroups: action('updateSharedGroups'), reactToMessage: action('reactToMessage'), - replyToMessage: action('replyToMessage'), + setQuoteByMessageId: action('setQuoteByMessageId'), retryDeleteForEveryone: action('retryDeleteForEveryone'), retrySend: action('retrySend'), deleteMessage: action('deleteMessage'), diff --git a/ts/components/conversation/Timeline.tsx b/ts/components/conversation/Timeline.tsx index a05ef0baef..184df0404f 100644 --- a/ts/components/conversation/Timeline.tsx +++ b/ts/components/conversation/Timeline.tsx @@ -239,7 +239,6 @@ const getActions = createSelector( 'doubleCheckMissingQuoteReference', 'checkForAccount', 'reactToMessage', - 'replyToMessage', 'retryDeleteForEveryone', 'retrySend', 'toggleForwardMessageModal', @@ -248,6 +247,7 @@ const getActions = createSelector( 'showMessageDetail', 'openConversation', 'openGiftBadge', + 'setQuoteByMessageId', 'showContactDetail', 'showContactModal', 'kickOffAttachmentDownload', diff --git a/ts/components/conversation/TimelineItem.stories.tsx b/ts/components/conversation/TimelineItem.stories.tsx index 847a5686b7..84a12692e5 100644 --- a/ts/components/conversation/TimelineItem.stories.tsx +++ b/ts/components/conversation/TimelineItem.stories.tsx @@ -66,7 +66,7 @@ const getDefaultProps = () => ({ checkForAccount: action('checkForAccount'), clearSelectedMessage: action('clearSelectedMessage'), contactSupport: action('contactSupport'), - replyToMessage: action('replyToMessage'), + setQuoteByMessageId: action('setQuoteByMessageId'), retryDeleteForEveryone: action('retryDeleteForEveryone'), retrySend: action('retrySend'), blockGroupLinkRequests: action('blockGroupLinkRequests'), diff --git a/ts/components/conversation/TimelineMessage.stories.tsx b/ts/components/conversation/TimelineMessage.stories.tsx index c7cec46f55..ac70ab48c7 100644 --- a/ts/components/conversation/TimelineMessage.stories.tsx +++ b/ts/components/conversation/TimelineMessage.stories.tsx @@ -294,7 +294,7 @@ const createProps = (overrideProps: Partial = {}): Props => ({ renderEmojiPicker, renderReactionPicker, renderAudioAttachment, - replyToMessage: action('replyToMessage'), + setQuoteByMessageId: action('setQuoteByMessageId'), retrySend: action('retrySend'), retryDeleteForEveryone: action('retryDeleteForEveryone'), scrollToQuotedMessage: action('scrollToQuotedMessage'), diff --git a/ts/components/conversation/TimelineMessage.tsx b/ts/components/conversation/TimelineMessage.tsx index 0ffa8f8f62..6522f32d34 100644 --- a/ts/components/conversation/TimelineMessage.tsx +++ b/ts/components/conversation/TimelineMessage.tsx @@ -49,8 +49,7 @@ export type PropsActions = { ) => void; retrySend: (id: string) => void; retryDeleteForEveryone: (id: string) => void; - - replyToMessage: (id: string) => void; + setQuoteByMessageId: (conversationId: string, messageId: string) => void; } & MessagePropsActions; export type Props = PropsData & @@ -83,6 +82,7 @@ export function TimelineMessage(props: Props): JSX.Element { canRetryDeleteForEveryone, contact, payment, + conversationId, containerElementRef, containerWidthBreakpoint, deletedForEveryone, @@ -94,7 +94,7 @@ export function TimelineMessage(props: Props): JSX.Element { isSticker, isTapToView, reactToMessage, - replyToMessage, + setQuoteByMessageId, renderReactionPicker, renderEmojiPicker, retrySend, @@ -234,7 +234,9 @@ export function TimelineMessage(props: Props): JSX.Element { ? openGenericAttachment : undefined; - const handleReplyToMessage = canReply ? () => replyToMessage(id) : undefined; + const handleReplyToMessage = canReply + ? () => setQuoteByMessageId(conversationId, id) + : undefined; const handleReact = canReact ? () => toggleReactionPicker() : undefined; diff --git a/ts/state/ducks/composer.ts b/ts/state/ducks/composer.ts index 92f4a6b3f1..57292027f0 100644 --- a/ts/state/ducks/composer.ts +++ b/ts/state/ducks/composer.ts @@ -61,6 +61,9 @@ import { resolveAttachmentDraftData } from '../../util/resolveAttachmentDraftDat import { resolveDraftAttachmentOnDisk } from '../../util/resolveDraftAttachmentOnDisk'; import { shouldShowInvalidMessageToast } from '../../util/shouldShowInvalidMessageToast'; import { writeDraftAttachment } from '../../util/writeDraftAttachment'; +import { getMessageById } from '../../messages/getMessageById'; +import { canReply } from '../selectors/message'; +import { getConversationSelector } from '../selectors/conversations'; // State @@ -143,6 +146,7 @@ export const actions = { sendStickerMessage, setComposerDisabledState, setComposerFocus, + setQuoteByMessageId, setMediaQualitySetting, setQuotedMessage, }; @@ -267,7 +271,11 @@ function sendMultiMediaMessage( conversation.setMarkedUnread(false); resetLinkPreview(); clearConversationDraftAttachments(conversationId, draftAttachments); - dispatch(setQuotedMessage(undefined)); + setQuoteByMessageId(conversationId, undefined)( + dispatch, + getState, + undefined + ); dispatch(resetComposer()); }, } @@ -349,6 +357,77 @@ function getAttachmentsFromConversationModel( return conversation?.get('draftAttachments') || []; } +function setQuoteByMessageId( + conversationId: string, + messageId: string | undefined +): ThunkAction< + void, + RootStateType, + unknown, + SetComposerDisabledStateActionType | SetQuotedMessageActionType +> { + return async (dispatch, getState) => { + const conversation = window.ConversationController.get(conversationId); + if (!conversation) { + throw new Error('sendStickerMessage: No conversation found'); + } + + const message = messageId ? await getMessageById(messageId) : undefined; + const state = getState(); + + if ( + message && + !canReply( + message.attributes, + window.ConversationController.getOurConversationIdOrThrow(), + getConversationSelector(state) + ) + ) { + return; + } + + if (message && !message.isNormalBubble()) { + return; + } + + const existing = conversation.get('quotedMessageId'); + if (existing !== messageId) { + const now = Date.now(); + let activeAt = conversation.get('active_at'); + let timestamp = conversation.get('timestamp'); + + if (!activeAt && messageId) { + activeAt = now; + timestamp = now; + } + + conversation.set({ + active_at: activeAt, + draftChanged: true, + quotedMessageId: messageId, + timestamp, + }); + + window.Signal.Data.updateConversation(conversation.attributes); + } + + if (message) { + const quote = await conversation.makeQuote(message); + dispatch( + setQuotedMessage({ + conversationId, + quote, + }) + ); + + dispatch(setComposerFocus(conversation.id)); + dispatch(setComposerDisabledState(false)); + } else { + dispatch(setQuotedMessage(undefined)); + } + }; +} + function addAttachment( conversationId: string, attachment: InMemoryAttachmentDraftType diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts index 8cbcb7a7ad..7757cd62de 100644 --- a/ts/state/ducks/conversations.ts +++ b/ts/state/ducks/conversations.ts @@ -70,6 +70,7 @@ import { getConversationUuidsStoppingSend, getConversationIdsStoppedForVerification, getMe, + getMessagesByConversation, } from '../selectors/conversations'; import type { AvatarDataType, AvatarUpdateType } from '../../types/Avatar'; import { getDefaultAvatars } from '../../types/Avatar'; @@ -113,6 +114,7 @@ import { buildPromotePendingAdminApprovalMemberChange, initiateMigrationToGroupV2 as doInitiateMigrationToGroupV2, } from '../../groups'; +import { getMessageById } from '../../messages/getMessageById'; // State @@ -2480,16 +2482,55 @@ function closeMaximumGroupSizeModal(): CloseMaximumGroupSizeModalActionType { function closeRecommendedGroupSizeModal(): CloseRecommendedGroupSizeModalActionType { return { type: 'CLOSE_RECOMMENDED_GROUP_SIZE_MODAL' }; } + function scrollToMessage( conversationId: string, messageId: string -): ScrollToMessageActionType { - return { - type: 'SCROLL_TO_MESSAGE', - payload: { - conversationId, - messageId, - }, +): ThunkAction { + return async (dispatch, getState) => { + const conversation = window.ConversationController.get(conversationId); + if (!conversation) { + throw new Error('scrollToMessage: No conversation found'); + } + + const message = await getMessageById(messageId); + if (!message) { + throw new Error(`scrollToMessage: failed to load message ${messageId}`); + } + if (message.get('conversationId') !== conversationId) { + throw new Error( + `scrollToMessage: ${messageId} didn't have conversationId ${conversationId}` + ); + } + + const state = getState(); + + let isInMemory = true; + + if (!window.MessageController.getById(messageId)) { + isInMemory = false; + } + + // Message might be in memory, but not in the redux anymore because + // we call `messageReset()` in `loadAndScroll()`. + const messagesByConversation = + getMessagesByConversation(state)[conversationId]; + if (!messagesByConversation?.messageIds.includes(messageId)) { + isInMemory = false; + } + + if (isInMemory) { + dispatch({ + type: 'SCROLL_TO_MESSAGE', + payload: { + conversationId, + messageId, + }, + }); + return; + } + + conversation.loadAndScroll(messageId); }; } diff --git a/ts/state/smart/CompositionArea.tsx b/ts/state/smart/CompositionArea.tsx index dd36ae7129..2cd9a972f4 100644 --- a/ts/state/smart/CompositionArea.tsx +++ b/ts/state/smart/CompositionArea.tsx @@ -33,13 +33,12 @@ import { isSignalConversation } from '../../util/isSignalConversation'; type ExternalProps = { id: string; - handleClickQuotedMessage: (id: string) => unknown; }; export type CompositionAreaPropsType = ExternalProps & ComponentPropsType; const mapStateToProps = (state: StateType, props: ExternalProps) => { - const { id, handleClickQuotedMessage } = props; + const { id } = props; const conversationSelector = getConversationSelector(state); const conversation = conversationSelector(id); @@ -108,18 +107,13 @@ const mapStateToProps = (state: StateType, props: ExternalProps) => { linkPreviewLoading, linkPreviewResult, // Quote + quotedMessageId: quotedMessage?.quote?.messageId, quotedMessageProps: quotedMessage ? getPropsForQuote(quotedMessage, { conversationSelector, ourConversationId: getUserConversationId(state), }) : undefined, - onClickQuotedMessage: () => { - const messageId = quotedMessage?.quote?.messageId; - if (messageId) { - handleClickQuotedMessage(messageId); - } - }, // Emojis recentEmojis, skinTone: getEmojiSkinTone(state), diff --git a/ts/state/smart/ConversationView.tsx b/ts/state/smart/ConversationView.tsx index fcb8ceb043..dd3f361ee3 100644 --- a/ts/state/smart/ConversationView.tsx +++ b/ts/state/smart/ConversationView.tsx @@ -17,9 +17,6 @@ export type PropsType = { conversationId: string; compositionAreaProps: Pick< CompositionAreaPropsType, - | 'clearQuotedMessage' - | 'getQuotedMessage' - | 'handleClickQuotedMessage' | 'id' | 'onCancelJoinRequest' | 'onClearAttachments' diff --git a/ts/state/smart/Timeline.tsx b/ts/state/smart/Timeline.tsx index 15372b05ea..323e2b64ff 100644 --- a/ts/state/smart/Timeline.tsx +++ b/ts/state/smart/Timeline.tsx @@ -81,7 +81,6 @@ export type TimelinePropsType = ExternalProps & | 'openLink' | 'reactToMessage' | 'removeMember' - | 'replyToMessage' | 'retryDeleteForEveryone' | 'retrySend' | 'scrollToQuotedMessage' diff --git a/ts/test-both/state/ducks/composer_test.ts b/ts/test-both/state/ducks/composer_test.ts index 221dd09f6f..450561dd93 100644 --- a/ts/test-both/state/ducks/composer_test.ts +++ b/ts/test-both/state/ducks/composer_test.ts @@ -15,6 +15,7 @@ import { fakeDraftAttachment } from '../../helpers/fakeAttachment'; describe('both/state/ducks/composer', () => { const QUOTED_MESSAGE = { conversationId: '123', + id: 'quoted-message-id', quote: { attachments: [], id: 456, diff --git a/ts/views/conversation_view.tsx b/ts/views/conversation_view.tsx index 127b057c0b..8c47293e42 100644 --- a/ts/views/conversation_view.tsx +++ b/ts/views/conversation_view.tsx @@ -21,18 +21,13 @@ import { strictAssert } from '../util/assert'; import { enqueueReactionForSend } from '../reactions/enqueueReactionForSend'; import type { GroupNameCollisionsWithIdsByTitle } from '../util/groupMemberNameCollisions'; import { isGroup } from '../util/whatTypeOfConversation'; -import { findAndFormatContact } from '../util/findAndFormatContact'; import { getPreferredBadgeSelector } from '../state/selectors/badges'; import { - canReply, isIncoming, isOutgoing, isTapToView, } from '../state/selectors/message'; -import { - getConversationSelector, - getMessagesByConversation, -} from '../state/selectors/conversations'; +import { getConversationSelector } from '../state/selectors/conversations'; import { getActiveCallState } from '../state/selectors/calling'; import { getTheme } from '../state/selectors/user'; import { ReactWrapperView } from './ReactWrapperView'; @@ -113,7 +108,6 @@ type MessageActionsType = { messageId: string, reaction: { emoji: string; remove: boolean } ) => unknown; - replyToMessage: (messageId: string) => unknown; retrySend: (messageId: string) => unknown; retryDeleteForEveryone: (messageId: string) => unknown; showContactDetail: (options: { @@ -171,7 +165,6 @@ export class ConversationView extends window.Backbone.View { // These are triggered by InboxView this.listenTo(this.model, 'opened', this.onOpened); - this.listenTo(this.model, 'scroll-to-message', this.scrollToMessage); this.listenTo(this.model, 'unload', (reason: string) => this.unload(`model trigger - ${reason}`) ); @@ -180,18 +173,6 @@ export class ConversationView extends window.Backbone.View { this.listenTo(this.model, 'open-all-media', this.showAllMedia); this.listenTo(this.model, 'escape-pressed', this.resetPanel); this.listenTo(this.model, 'show-message-details', this.showMessageDetail); - this.listenTo( - this.model, - 'toggle-reply', - (messageId: string | undefined) => { - const composerState = window.reduxStore - ? window.reduxStore.getState().composer - : undefined; - const quote = composerState?.quotedMessage?.quote; - - this.setQuoteMessage(quote ? undefined : messageId); - } - ); this.listenTo( this.model, 'save-attachment', @@ -332,7 +313,10 @@ export class ConversationView extends window.Backbone.View { return; } - this.scrollToMessage(message.id); + window.reduxActions.conversations.scrollToMessage( + conversationId, + message.id + ); }; const markMessageRead = async (messageId: string) => { @@ -396,8 +380,6 @@ export class ConversationView extends window.Backbone.View { id: this.model.id, onClickAddPack: () => this.showStickerManager(), onTextTooLong: () => showToast(ToastMessageBodyTooLong), - getQuotedMessage: () => this.model.get('quotedMessageId'), - clearQuotedMessage: () => this.setQuoteMessage(undefined), onCancelJoinRequest: async () => { await window.showConfirmationDialog({ dialogName: 'GroupV2CancelRequestToJoin', @@ -421,8 +403,6 @@ export class ConversationView extends window.Backbone.View { window.reduxActions.composer.setMediaQualitySetting(isHQ); }, - handleClickQuotedMessage: (id: string) => this.scrollToMessage(id), - onCloseLinkPreview: () => { suspendLinkPreviews(); removeLinkPreview(); @@ -461,9 +441,6 @@ export class ConversationView extends window.Backbone.View { showToast(ToastReactionFailed); } }; - const replyToMessage = (messageId: string) => { - this.setQuoteMessage(messageId); - }; const retrySend = retryMessageSend; const deleteMessage = (messageId: string) => { this.deleteMessage(messageId); @@ -555,7 +532,6 @@ export class ConversationView extends window.Backbone.View { openGiftBadge, openLink, reactToMessage, - replyToMessage, retrySend, retryDeleteForEveryone, showContactDetail, @@ -567,37 +543,6 @@ export class ConversationView extends window.Backbone.View { }; } - async scrollToMessage(messageId: string): Promise { - const message = await getMessageById(messageId); - if (!message) { - throw new Error(`scrollToMessage: failed to load message ${messageId}`); - } - - const state = window.reduxStore.getState(); - - let isInMemory = true; - - if (!window.MessageController.getById(messageId)) { - isInMemory = false; - } - - // Message might be in memory, but not in the redux anymore because - // we call `messageReset()` in `loadAndScroll()`. - const messagesByConversation = - getMessagesByConversation(state)[this.model.id]; - if (!messagesByConversation?.messageIds.includes(messageId)) { - isInMemory = false; - } - - if (isInMemory) { - const { scrollToMessage } = window.reduxActions.conversations; - scrollToMessage(this.model.id, messageId); - return; - } - - this.model.loadAndScroll(messageId); - } - unload(reason: string): void { log.info( 'unloading conversation', @@ -697,7 +642,10 @@ export class ConversationView extends window.Backbone.View { const quotedMessageId = this.model.get('quotedMessageId'); if (quotedMessageId) { - this.setQuoteMessage(quotedMessageId); + window.reduxActions.composer.setQuoteByMessageId( + this.model.id, + quotedMessageId + ); } this.model.fetchLatestGroupV2Data(); @@ -1532,60 +1480,6 @@ export class ConversationView extends window.Backbone.View { ); } - async setQuoteMessage(messageId: string | undefined): Promise { - const { model } = this; - const message = messageId ? await getMessageById(messageId) : undefined; - - if ( - message && - !canReply( - message.attributes, - window.ConversationController.getOurConversationIdOrThrow(), - findAndFormatContact - ) - ) { - return; - } - - if (message && !message.isNormalBubble()) { - return; - } - - const existing = model.get('quotedMessageId'); - if (existing !== messageId) { - const now = Date.now(); - let active_at = this.model.get('active_at'); - let timestamp = this.model.get('timestamp'); - - if (!active_at && messageId) { - active_at = now; - timestamp = now; - } - - this.model.set({ - active_at, - draftChanged: true, - quotedMessageId: messageId, - timestamp, - }); - - await this.saveModel(); - } - - if (message) { - const quote = await model.makeQuote(message); - window.reduxActions.composer.setQuotedMessage({ - conversationId: model.id, - quote, - }); - - window.reduxActions.composer.setComposerFocus(this.model.id); - window.reduxActions.composer.setComposerDisabledState(false); - } else { - window.reduxActions.composer.setQuotedMessage(undefined); - } - } - async clearAttachments(): Promise { const draftAttachments = this.model.get('draftAttachments') || []; this.model.set({