Clarify behavior of group deletion

This commit is contained in:
automated-signal 2024-10-21 13:25:01 -05:00 committed by GitHub
parent 98c237856d
commit 08e8a7c6e9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 62 additions and 9 deletions

View file

@ -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.", "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" "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": { "icu:ConversationHeader__ContextMenu__LeaveGroupAction__title": {
"messageformat": "Leave group", "messageformat": "Leave group",
"description": "This is a button to leave a group" "description": "This is a button to leave a group"

View file

@ -654,7 +654,7 @@ function HeaderMenu({
)} )}
<MenuItem onClick={onConversationDeleteMessages}> <MenuItem onClick={onConversationDeleteMessages}>
{i18n('icu:deleteMessagesInConversation')} {i18n('icu:deleteConversation')}
</MenuItem> </MenuItem>
</ContextMenu> </ContextMenu>
); );
@ -792,7 +792,7 @@ function HeaderMenu({
</MenuItem> </MenuItem>
)} )}
<MenuItem onClick={onConversationDeleteMessages}> <MenuItem onClick={onConversationDeleteMessages}>
{i18n('icu:deleteMessagesInConversation')} {i18n('icu:deleteConversation')}
</MenuItem> </MenuItem>
{isGroup && ( {isGroup && (
<MenuItem onClick={onConversationLeaveGroup}> <MenuItem onClick={onConversationLeaveGroup}>
@ -1002,17 +1002,17 @@ function DeleteMessagesConfirmationDialog({
const dialogBody = isDeleteSyncSendEnabled const dialogBody = isDeleteSyncSendEnabled
? i18n( ? i18n(
'icu:ConversationHeader__DeleteMessagesInConversationConfirmation__description-with-sync' 'icu:ConversationHeader__DeleteConversationConfirmation__description-with-sync'
) )
: i18n( : i18n(
'icu:ConversationHeader__DeleteMessagesInConversationConfirmation__description' 'icu:ConversationHeader__DeleteConversationConfirmation__description'
); );
return ( return (
<ConfirmationDialog <ConfirmationDialog
dialogName="ConversationHeader.destroyMessages" dialogName="ConversationHeader.destroyMessages"
title={i18n( title={i18n(
'icu:ConversationHeader__DeleteMessagesInConversationConfirmation__title' 'icu:ConversationHeader__DeleteConversationConfirmation__title'
)} )}
actions={[ actions={[
{ {

1
ts/model-types.d.ts vendored
View file

@ -354,6 +354,7 @@ export type ConversationAttributesType = {
draftAttachments?: ReadonlyArray<AttachmentDraftType>; draftAttachments?: ReadonlyArray<AttachmentDraftType>;
draftBodyRanges?: DraftBodyRanges; draftBodyRanges?: DraftBodyRanges;
draftTimestamp?: number | null; draftTimestamp?: number | null;
hiddenFromConversationSearch?: boolean;
hideStory?: boolean; hideStory?: boolean;
inbox_position?: number; inbox_position?: number;
// When contact is removed - it is initially placed into `justNotification` // When contact is removed - it is initially placed into `justNotification`

View file

@ -407,6 +407,8 @@ export class ConversationModel extends window.Backbone
this.unset('tokens'); this.unset('tokens');
this.on('change:members change:membersV2', this.fetchContacts); this.on('change:members change:membersV2', this.fetchContacts);
this.on('change:active_at', this.onActiveAtChange);
this.typingRefreshTimer = null; this.typingRefreshTimer = null;
this.typingPauseTimer = 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<void> { async refreshGroupLink(): Promise<void> {
if (!isGroupV2(this.attributes)) { if (!isGroupV2(this.attributes)) {
return; return;
@ -4846,7 +4854,12 @@ export class ConversationModel extends window.Backbone
ourAci ourAci
); );
const sharedGroups = ourGroups const sharedGroups = ourGroups
.filter(c => c.hasMember(ourAci) && c.hasMember(theirAci)) .filter(
c =>
c.hasMember(ourAci) &&
c.hasMember(theirAci) &&
!c.attributes.hiddenFromConversationSearch
)
.sort( .sort(
(left, right) => (left, right) =>
(right.get('timestamp') || 0) - (left.get('timestamp') || 0) (right.get('timestamp') || 0) - (left.get('timestamp') || 0)
@ -5209,6 +5222,9 @@ export class ConversationModel extends window.Backbone
active_at: null, active_at: null,
pendingUniversalTimer: undefined, pendingUniversalTimer: undefined,
}); });
if (isGroup(this.attributes)) {
this.set('hiddenFromConversationSearch', true);
}
await DataWriter.updateConversation(this.attributes); await DataWriter.updateConversation(this.attributes);
const ourConversation = const ourConversation =

View file

@ -199,6 +199,7 @@ import {
import { MAX_MESSAGE_COUNT } from '../../util/deleteForMe.types'; import { MAX_MESSAGE_COUNT } from '../../util/deleteForMe.types';
import { markCallHistoryReadInConversation } from './callHistory'; import { markCallHistoryReadInConversation } from './callHistory';
import type { CapabilitiesType } from '../../textsecure/WebAPI'; import type { CapabilitiesType } from '../../textsecure/WebAPI';
import type { SearchActionType } from './search';
// State // State
@ -295,6 +296,7 @@ export type ConversationType = ReadonlyDeep<
customColor?: CustomColorType; customColor?: CustomColorType;
customColorId?: string; customColorId?: string;
discoveredUnregisteredAt?: number; discoveredUnregisteredAt?: number;
hiddenFromConversationSearch?: boolean;
hideStory?: boolean; hideStory?: boolean;
isArchived?: boolean; isArchived?: boolean;
isBlocked?: boolean; isBlocked?: boolean;
@ -1850,7 +1852,7 @@ function destroyMessages(
void, void,
RootStateType, RootStateType,
unknown, unknown,
ConversationUnloadedActionType | NoopActionType ConversationUnloadedActionType | NoopActionType | SearchActionType
> { > {
return async (dispatch, getState) => { return async (dispatch, getState) => {
const conversation = window.ConversationController.get(conversationId); const conversation = window.ConversationController.get(conversationId);
@ -1869,6 +1871,20 @@ function destroyMessages(
); );
await conversation.destroyMessages({ source: 'local-delete' }); 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()); drop(conversation.updateLastMessage());
}, },
}); });

View file

@ -589,7 +589,8 @@ export const getAllComposableConversations = createSelector(
// All conversation should have a title except in weird cases where // 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. // they don't, in that case we don't want to show these for Forwarding.
conversation.titleNoDefault && conversation.titleNoDefault &&
hasDisplayInfo(conversation) hasDisplayInfo(conversation) &&
!conversation.hiddenFromConversationSearch
) )
); );

View file

@ -165,7 +165,7 @@ export function filterAndSortConversations(
const withoutUnknown = conversations.filter(item => item.titleNoDefault); const withoutUnknown = conversations.filter(item => item.titleNoDefault);
return searchConversations(withoutUnknown, searchTerm, regionCode) return searchConversations(withoutUnknown, searchTerm, regionCode)
.slice() .filter(({ item }) => !item.hiddenFromConversationSearch)
.sort((a, b) => { .sort((a, b) => {
const { activeAt: aActiveAt = 0, left: aLeft = false } = a.item; const { activeAt: aActiveAt = 0, left: aLeft = false } = a.item;
const { activeAt: bActiveAt = 0, left: bLeft = false } = b.item; const { activeAt: bActiveAt = 0, left: bLeft = false } = b.item;

View file

@ -175,6 +175,9 @@ export function getConversation(model: ConversationModel): ConversationType {
groupVersion, groupVersion,
groupId: attributes.groupId, groupId: attributes.groupId,
groupLink: buildGroupLink(attributes), groupLink: buildGroupLink(attributes),
hiddenFromConversationSearch: Boolean(
attributes.hiddenFromConversationSearch
),
hideStory: Boolean(attributes.hideStory), hideStory: Boolean(attributes.hideStory),
inboxPosition, inboxPosition,
isArchived: attributes.isArchived, isArchived: attributes.isArchived,