diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts index 4feca6907a..3b240f9b41 100644 --- a/ts/state/ducks/conversations.ts +++ b/ts/state/ducks/conversations.ts @@ -331,7 +331,7 @@ export type ConversationsStateType = { messageInfoId: string | undefined; showRightPanel: boolean; selectedMessageIds: Array; - quotedMessage?: ReplyingToMessageProps; + quotedMessageByConversation: Partial>; areMoreMessagesBeingFetched: boolean; /** @@ -545,6 +545,7 @@ export function getEmptyConversationState(): ConversationsStateType { messageInfoId: undefined, showRightPanel: false, selectedMessageIds: [], + quotedMessageByConversation: {}, areMoreMessagesBeingFetched: false, // top or bottom showScrollButton: false, mentionMembers: [], @@ -943,7 +944,7 @@ const conversationsSlice = createSlice({ selectedMessageIds: [], messageInfoId: undefined, - quotedMessage: undefined, + quotedMessageByConversation: state.quotedMessageByConversation, nextMessageToPlay: undefined, showScrollButton, @@ -1022,7 +1023,17 @@ const conversationsSlice = createSlice({ state: ConversationsStateType, action: PayloadAction ) { - state.quotedMessage = action.payload; + const conversationId = action.payload?.convoId || state.selectedConversation; + if (!conversationId) { + return state; + } + + if (action.payload) { + state.quotedMessageByConversation[conversationId] = action.payload; + return state; + } + + delete state.quotedMessageByConversation[conversationId]; return state; }, quotedMessageToAnimate( diff --git a/ts/state/selectors/conversations.ts b/ts/state/selectors/conversations.ts index 6d38b1a1a3..8627b7596c 100644 --- a/ts/state/selectors/conversations.ts +++ b/ts/state/selectors/conversations.ts @@ -507,8 +507,19 @@ export const getReactionBarTriggerPosition = (state: StateType): PopoverTriggerP export const getIsCompositionTextAreaFocused = (state: StateType): boolean => state.conversations.isCompositionTextAreaFocused; -export const getQuotedMessage = (state: StateType): ReplyingToMessageProps | undefined => - state.conversations.quotedMessage; +export const getQuotedMessage = createSelector( + [getSelectedConversationKey, getConversations], + ( + selectedConversationKey: string | undefined, + state: ConversationsStateType + ): ReplyingToMessageProps | undefined => { + if (!selectedConversationKey) { + return undefined; + } + + return state.quotedMessageByConversation[selectedConversationKey]; + } +); export const areMoreMessagesBeingFetched = (state: StateType): boolean => state.conversations.areMoreMessagesBeingFetched || false; diff --git a/ts/test/session/unit/selectors/conversations_test.ts b/ts/test/session/unit/selectors/conversations_test.ts index f1739d493b..4529de6ebd 100644 --- a/ts/test/session/unit/selectors/conversations_test.ts +++ b/ts/test/session/unit/selectors/conversations_test.ts @@ -2,10 +2,19 @@ import { assert } from 'chai'; import Sinon from 'sinon'; import { CONVERSATION_PRIORITIES, ConversationTypeEnum } from '../../../../models/types'; -import { ConversationLookupType } from '../../../../state/ducks/conversations'; +import type { ReplyingToMessageProps } from '../../../../components/conversation/composition/CompositionBox'; +import { + actions, + ConversationLookupType, + getEmptyConversationState, + quoteMessage, + reducer as conversationsReducer, +} from '../../../../state/ducks/conversations'; +import type { StateType } from '../../../../state/reducer'; import { _getConversationComparator, _getSortedConversations, + getQuotedMessage, } from '../../../../state/selectors/conversations'; import { TestUtils } from '../../../test-utils'; @@ -17,6 +26,69 @@ describe('state/selectors/conversations', () => { afterEach(() => { Sinon.restore(); }); + + describe('#getQuotedMessage', () => { + const openConversation = ( + state: ReturnType, + conversationKey: string + ) => + conversationsReducer( + state, + actions.openConversationExternal({ + conversationKey, + initialMessages: [], + initialQuotes: [], + firstUnreadMessageId: null, + mostRecentMessageId: null, + oldestMessageId: null, + }) + ); + + const buildState = (conversations: ReturnType) => + ({ conversations }) as StateType; + + const firstQuote: ReplyingToMessageProps = { + convoId: 'conversation-1', + id: 'message-1', + author: 'author-1', + referencedMessageSentAt: 111, + quotedAt: 1, + text: 'first quote', + }; + + const secondQuote: ReplyingToMessageProps = { + convoId: 'conversation-2', + id: 'message-2', + author: 'author-2', + referencedMessageSentAt: 222, + quotedAt: 2, + text: 'second quote', + }; + + it('keeps draft reply quotes scoped to their conversation', () => { + let state = getEmptyConversationState(); + + state = openConversation(state, 'conversation-1'); + state = conversationsReducer(state, quoteMessage(firstQuote)); + assert.deepEqual(getQuotedMessage(buildState(state)), firstQuote); + + state = openConversation(state, 'conversation-2'); + assert.isUndefined(getQuotedMessage(buildState(state))); + + state = conversationsReducer(state, quoteMessage(secondQuote)); + assert.deepEqual(getQuotedMessage(buildState(state)), secondQuote); + + state = openConversation(state, 'conversation-1'); + assert.deepEqual(getQuotedMessage(buildState(state)), firstQuote); + + state = conversationsReducer(state, quoteMessage(undefined)); + assert.isUndefined(getQuotedMessage(buildState(state))); + + state = openConversation(state, 'conversation-2'); + assert.deepEqual(getQuotedMessage(buildState(state)), secondQuote); + }); + }); + describe('#getSortedConversationsList', () => { it('sorts conversations based on timestamp then by intl-friendly title', () => { const data: ConversationLookupType = {