From 08e8a7c6e9e47e42ec693c59d279c7020f929558 Mon Sep 17 00:00:00 2001 From: automated-signal <37887102+automated-signal@users.noreply.github.com> Date: Mon, 21 Oct 2024 13:25:01 -0500 Subject: [PATCH] Clarify behavior of group deletion --- _locales/en/messages.json | 16 ++++++++++++++++ .../conversation/ConversationHeader.tsx | 10 +++++----- ts/model-types.d.ts | 1 + ts/models/conversations.ts | 18 +++++++++++++++++- ts/state/ducks/conversations.ts | 18 +++++++++++++++++- ts/state/selectors/conversations.ts | 3 ++- ts/util/filterAndSortConversations.ts | 2 +- ts/util/getConversation.ts | 3 +++ 8 files changed, 62 insertions(+), 9 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 50464b2edd3d..8802b83b3183 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1338,6 +1338,22 @@ "messageformat": "All messages in this chat will be deleted from all your devices. You can still search for this chat after you delete messages.", "description": "Description for confirmation modal to delete all messages in a conversation" }, + "icu:deleteConversation": { + "messageformat": "Delete", + "description": "Menu item for deleting a conversation (including messages), title case." + }, + "icu:ConversationHeader__DeleteConversationConfirmation__title": { + "messageformat": "Delete chat?", + "description": "Conversation Header > Delete Action > Delete Messages Confirmation Modal > Title. Previously known as icu:ConversationHeader__DeleteMessagesConfirmation__title" + }, + "icu:ConversationHeader__DeleteConversationConfirmation__description": { + "messageformat": "All messages in this chat will be deleted from this device.", + "description": "Description for confirmation modal to delete a conversation" + }, + "icu:ConversationHeader__DeleteConversationConfirmation__description-with-sync": { + "messageformat": "All messages in this chat will be deleted from all your devices.", + "description": "Description for confirmation modal to delete a conversation" + }, "icu:ConversationHeader__ContextMenu__LeaveGroupAction__title": { "messageformat": "Leave group", "description": "This is a button to leave a group" diff --git a/ts/components/conversation/ConversationHeader.tsx b/ts/components/conversation/ConversationHeader.tsx index 24a86d645613..62a1a7909df5 100644 --- a/ts/components/conversation/ConversationHeader.tsx +++ b/ts/components/conversation/ConversationHeader.tsx @@ -654,7 +654,7 @@ function HeaderMenu({ )} - {i18n('icu:deleteMessagesInConversation')} + {i18n('icu:deleteConversation')} ); @@ -792,7 +792,7 @@ function HeaderMenu({ )} - {i18n('icu:deleteMessagesInConversation')} + {i18n('icu:deleteConversation')} {isGroup && ( @@ -1002,17 +1002,17 @@ function DeleteMessagesConfirmationDialog({ const dialogBody = isDeleteSyncSendEnabled ? i18n( - 'icu:ConversationHeader__DeleteMessagesInConversationConfirmation__description-with-sync' + 'icu:ConversationHeader__DeleteConversationConfirmation__description-with-sync' ) : i18n( - 'icu:ConversationHeader__DeleteMessagesInConversationConfirmation__description' + 'icu:ConversationHeader__DeleteConversationConfirmation__description' ); return ( ; draftBodyRanges?: DraftBodyRanges; draftTimestamp?: number | null; + hiddenFromConversationSearch?: boolean; hideStory?: boolean; inbox_position?: number; // When contact is removed - it is initially placed into `justNotification` diff --git a/ts/models/conversations.ts b/ts/models/conversations.ts index bfefc8ce8a04..62a238820f6f 100644 --- a/ts/models/conversations.ts +++ b/ts/models/conversations.ts @@ -407,6 +407,8 @@ export class ConversationModel extends window.Backbone this.unset('tokens'); this.on('change:members change:membersV2', this.fetchContacts); + this.on('change:active_at', this.onActiveAtChange); + this.typingRefreshTimer = null; this.typingPauseTimer = null; @@ -4416,6 +4418,12 @@ export class ConversationModel extends window.Backbone } } + private onActiveAtChange(): void { + if (this.get('active_at') && this.get('hiddenFromConversationSearch')) { + this.set('hiddenFromConversationSearch', false); + } + } + async refreshGroupLink(): Promise { if (!isGroupV2(this.attributes)) { return; @@ -4846,7 +4854,12 @@ export class ConversationModel extends window.Backbone ourAci ); const sharedGroups = ourGroups - .filter(c => c.hasMember(ourAci) && c.hasMember(theirAci)) + .filter( + c => + c.hasMember(ourAci) && + c.hasMember(theirAci) && + !c.attributes.hiddenFromConversationSearch + ) .sort( (left, right) => (right.get('timestamp') || 0) - (left.get('timestamp') || 0) @@ -5209,6 +5222,9 @@ export class ConversationModel extends window.Backbone active_at: null, pendingUniversalTimer: undefined, }); + if (isGroup(this.attributes)) { + this.set('hiddenFromConversationSearch', true); + } await DataWriter.updateConversation(this.attributes); const ourConversation = diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts index 3c6381cd69ce..34509d7a2d47 100644 --- a/ts/state/ducks/conversations.ts +++ b/ts/state/ducks/conversations.ts @@ -199,6 +199,7 @@ import { import { MAX_MESSAGE_COUNT } from '../../util/deleteForMe.types'; import { markCallHistoryReadInConversation } from './callHistory'; import type { CapabilitiesType } from '../../textsecure/WebAPI'; +import type { SearchActionType } from './search'; // State @@ -295,6 +296,7 @@ export type ConversationType = ReadonlyDeep< customColor?: CustomColorType; customColorId?: string; discoveredUnregisteredAt?: number; + hiddenFromConversationSearch?: boolean; hideStory?: boolean; isArchived?: boolean; isBlocked?: boolean; @@ -1850,7 +1852,7 @@ function destroyMessages( void, RootStateType, unknown, - ConversationUnloadedActionType | NoopActionType + ConversationUnloadedActionType | NoopActionType | SearchActionType > { return async (dispatch, getState) => { const conversation = window.ConversationController.get(conversationId); @@ -1869,6 +1871,20 @@ function destroyMessages( ); await conversation.destroyMessages({ source: 'local-delete' }); + + // Deselect the conversation + if ( + getState().conversations.selectedConversationId === conversationId + ) { + showConversation({ conversationId: undefined }); + } + + // Clear search state, in case it's showing in search + dispatch({ + type: 'CLEAR_CONVERSATION_SEARCH', + payload: null, + }); + drop(conversation.updateLastMessage()); }, }); diff --git a/ts/state/selectors/conversations.ts b/ts/state/selectors/conversations.ts index 03367eeaea78..02af48ebce3c 100644 --- a/ts/state/selectors/conversations.ts +++ b/ts/state/selectors/conversations.ts @@ -589,7 +589,8 @@ export const getAllComposableConversations = createSelector( // All conversation should have a title except in weird cases where // they don't, in that case we don't want to show these for Forwarding. conversation.titleNoDefault && - hasDisplayInfo(conversation) + hasDisplayInfo(conversation) && + !conversation.hiddenFromConversationSearch ) ); diff --git a/ts/util/filterAndSortConversations.ts b/ts/util/filterAndSortConversations.ts index d85abd7c82e8..2890b94d83fc 100644 --- a/ts/util/filterAndSortConversations.ts +++ b/ts/util/filterAndSortConversations.ts @@ -165,7 +165,7 @@ export function filterAndSortConversations( const withoutUnknown = conversations.filter(item => item.titleNoDefault); return searchConversations(withoutUnknown, searchTerm, regionCode) - .slice() + .filter(({ item }) => !item.hiddenFromConversationSearch) .sort((a, b) => { const { activeAt: aActiveAt = 0, left: aLeft = false } = a.item; const { activeAt: bActiveAt = 0, left: bLeft = false } = b.item; diff --git a/ts/util/getConversation.ts b/ts/util/getConversation.ts index f331458b3775..b05d274dfa47 100644 --- a/ts/util/getConversation.ts +++ b/ts/util/getConversation.ts @@ -175,6 +175,9 @@ export function getConversation(model: ConversationModel): ConversationType { groupVersion, groupId: attributes.groupId, groupLink: buildGroupLink(attributes), + hiddenFromConversationSearch: Boolean( + attributes.hiddenFromConversationSearch + ), hideStory: Boolean(attributes.hideStory), inboxPosition, isArchived: attributes.isArchived,