From b0af0d651c61fbb89c1ebdef3196317aa10c13cb Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Thu, 5 Sep 2024 12:25:28 +1000 Subject: [PATCH] fix: only show "you set disappearing timer" when you actually did it --- .../conversation/TimerNotification.tsx | 42 +-- ts/models/message.ts | 289 +++++++++--------- 2 files changed, 178 insertions(+), 153 deletions(-) diff --git a/ts/components/conversation/TimerNotification.tsx b/ts/components/conversation/TimerNotification.tsx index 43acba8b9..c8f3e894b 100644 --- a/ts/components/conversation/TimerNotification.tsx +++ b/ts/components/conversation/TimerNotification.tsx @@ -10,8 +10,6 @@ import { useSelectedExpireTimer, useSelectedIsGroupOrCommunity, useSelectedIsGroupV2, - useSelectedIsNoteToSelf, - useSelectedIsPrivate, useSelectedIsPrivateFriend, } from '../../state/selectors/selectedConversation'; import { ReleasedFeatures } from '../../util/releaseFeature'; @@ -47,17 +45,17 @@ function useFollowSettingsButtonClick( ? window.i18n('disappearingMessagesTypeRead') : window.i18n('disappearingMessagesTypeSent'); - const i18nMessage = props.disabled - ? ({ + const i18nMessage: LocalizerComponentProps = props.disabled + ? { token: 'disappearingMessagesFollowSettingOff', - } as LocalizerComponentProps<'disappearingMessagesFollowSettingOff'>) - : ({ + } + : { token: 'disappearingMessagesFollowSettingOn', args: { time: props.timespanText, disappearing_messages_type: localizedMode, }, - } as LocalizerComponentProps<'disappearingMessagesFollowSettingOn'>); + }; const okText = props.disabled ? window.i18n('yes') : window.i18n('set'); @@ -142,21 +140,21 @@ const FollowSettingsButton = (props: PropsForExpirationTimer) => { ); }; -function useTextToRenderI18nProps(props: PropsForExpirationTimer) { - const { pubkey, profileName, expirationMode, timespanText: time, type, disabled } = props; +function useTextToRenderI18nProps( + props: PropsForExpirationTimer +): LocalizerComponentProps { + const { pubkey: authorPk, profileName, expirationMode, timespanText: time, disabled } = props; - const isPrivate = useSelectedIsPrivate(); - const isNoteToSelf = useSelectedIsNoteToSelf(); - const isPrivateAndNotNoteToSelf = isPrivate && !isNoteToSelf; + const authorIsUs = authorPk === UserUtils.getOurPubKeyStrFromCache(); - const name = profileName ?? pubkey; + const name = profileName ?? authorPk; // TODO: legacy messages support will be removed in a future release if (isLegacyDisappearingModeEnabled(expirationMode)) { return { token: 'deleteAfterLegacyDisappearingMessagesTheyChangedTimer', args: { - name: type === 'fromOther' ? name : window.i18n('you'), + name: authorIsUs ? window.i18n('you') : name, time, }, }; @@ -168,7 +166,7 @@ function useTextToRenderI18nProps(props: PropsForExpirationTimer) { : window.i18n('disappearingMessagesTypeSent'); if (disabled) { - if (type === 'fromMe' || isPrivateAndNotNoteToSelf) { + if (authorIsUs) { return { token: 'disappearingMessagesTurnedOffYou', }; @@ -180,12 +178,22 @@ function useTextToRenderI18nProps(props: PropsForExpirationTimer) { }, }; } + if (authorIsUs) { + return { + token: 'disappearingMessagesSetYou', + args: { + time, + disappearing_messages_type, + }, + }; + } return { - token: 'disappearingMessagesSetYou', + token: 'disappearingMessagesSet', args: { time, disappearing_messages_type, + name, }, }; } @@ -193,7 +201,7 @@ function useTextToRenderI18nProps(props: PropsForExpirationTimer) { export const TimerNotification = (props: PropsForExpirationTimer) => { const { messageId } = props; - const i18nProps = useTextToRenderI18nProps(props) as LocalizerComponentProps; + const i18nProps = useTextToRenderI18nProps(props); const isGroupOrCommunity = useSelectedIsGroupOrCommunity(); const isGroupV2 = useSelectedIsGroupV2(); // renderOff is true when the update is put to off, or when we have a legacy group control message (as they are not expiring at all) diff --git a/ts/models/message.ts b/ts/models/message.ts index d735072f9..bafb1b922 100644 --- a/ts/models/message.ts +++ b/ts/models/message.ts @@ -246,26 +246,119 @@ export class MessageModel extends Backbone.Model { } public getNotificationText() { - let description = this.getDescription(); - if (description) { - // regex with a 'g' to ignore part groups - const regex = new RegExp(`@${PubKey.regexForPubkeys}`, 'g'); - const pubkeysInDesc = description.match(regex); - (pubkeysInDesc || []).forEach((pubkeyWithAt: string) => { - const pubkey = pubkeyWithAt.slice(1); - const isUS = isUsAnySogsFromCache(pubkey); - const displayName = - getConversationController().getContactProfileNameOrShortenedPubKey(pubkey); - if (isUS) { - description = description?.replace(pubkeyWithAt, `@${window.i18n('you')}`); - } else if (displayName && displayName.length) { - description = description?.replace(pubkeyWithAt, `@${displayName}`); - } + const groupUpdate = this.getGroupUpdateAsArray(); + if (groupUpdate) { + const groupName = + this.getConversation()?.getNicknameOrRealUsernameOrPlaceholder() || window.i18n('unknown'); + + if (groupUpdate.left) { + // @ts-expect-error -- TODO: Fix by using new i18n builder + const { token, args } = getLeftGroupUpdateChangeStr(groupUpdate.left, groupName); + // TODO: clean up this typing + return window.i18n.stripped(...([token, args] as GetMessageArgs)); + } + + if (groupUpdate.name) { + return window.i18n.stripped('groupNameNew', { group_name: groupUpdate.name }); + } + + if (groupUpdate.joined?.length) { + // @ts-expect-error -- TODO: Fix by using new i18n builder + const { token, args } = getJoinedGroupUpdateChangeStr(groupUpdate.joined, groupName); + // TODO: clean up this typing + return window.i18n.stripped(...([token, args] as GetMessageArgs)); + } + + if (groupUpdate.kicked?.length) { + // @ts-expect-error -- TODO: Fix by using new i18n builder + const { token, args } = getKickedGroupUpdateStr(groupUpdate.kicked, groupName); + // TODO: clean up this typing + return window.i18n.stripped(...([token, args] as GetMessageArgs)); + } + window.log.warn('did not build a specific change for getDescription of ', groupUpdate); + + return window.i18n.stripped('groupUpdated'); + } + + if (this.isGroupInvitation()) { + return `😎 ${window.i18n.stripped('communityInvitation')}`; + } + + if (this.isDataExtractionNotification()) { + const dataExtraction = this.get( + 'dataExtractionNotification' + ) as DataExtractionNotificationMsg; + if (dataExtraction.type === SignalService.DataExtractionNotification.Type.SCREENSHOT) { + return window.i18n.stripped('screenshotTaken', { + name: getConversationController().getContactProfileNameOrShortenedPubKey( + dataExtraction.source + ), + }); + } + + return window.i18n.stripped('attachmentsMediaSaved', { + name: getConversationController().getContactProfileNameOrShortenedPubKey( + dataExtraction.source + ), }); - return description; } - if ((this.get('attachments') || []).length > 0) { - return window.i18n.stripped('contentDescriptionMediaMessage'); + if (this.isCallNotification()) { + const name = getConversationController().getContactProfileNameOrShortenedPubKey( + this.get('conversationId') + ); + const callNotificationType = this.get('callNotificationType'); + if (callNotificationType === 'missed-call') { + return window.i18n.stripped('callsMissedCallFrom', { name }); + } + if (callNotificationType === 'started-call') { + return window.i18n.stripped('callsYouCalled', { name }); + } + if (callNotificationType === 'answered-a-call') { + return window.i18n.stripped('callsInProgress'); + } + } + + const interactionNotification = this.getInteractionNotification(); + if (interactionNotification) { + const { interactionType, interactionStatus } = interactionNotification; + + // NOTE For now we only show interaction errors in the message history + if (interactionStatus === ConversationInteractionStatus.Error) { + const convo = getConversationController().get(this.get('conversationId')); + + if (convo) { + const isGroup = !convo.isPrivate(); + const isCommunity = convo.isPublic(); + + switch (interactionType) { + case ConversationInteractionType.Hide: + // there is no text for hiding changes + return ''; + case ConversationInteractionType.Leave: + return isCommunity + ? window.i18n.stripped('communityLeaveError', { + community_name: convo.getNicknameOrRealUsernameOrPlaceholder(), + }) + : isGroup + ? window.i18n.stripped('groupLeaveErrorFailed', { + group_name: convo.getNicknameOrRealUsernameOrPlaceholder(), + }) + : ''; + default: + assertUnreachable( + interactionType, + `Message.getDescription: Missing case error "${interactionType}"` + ); + } + } + } + } + + if (this.get('reaction')) { + const reaction = this.get('reaction'); + if (reaction && reaction.emoji && reaction.emoji !== '') { + return window.i18n.stripped('emojiReactsNotification', { emoji: reaction.emoji }); + } } if (this.isExpirationTimerUpdate()) { const expireTimerUpdate = this.getExpirationTimerUpdate(); @@ -282,12 +375,17 @@ export class MessageModel extends Backbone.Model { ); const source = expireTimerUpdate?.source; + const isUs = UserUtils.isUsFromCache(source); + const authorName = getConversationController() .get(source || '') ?.getNicknameOrRealUsernameOrPlaceholder() || window.i18n.stripped('unknown'); if (!expireTimerUpdate || expirationMode === 'off' || !expireTimer || expireTimer === 0) { + if (isUs) { + return window.i18n.stripped('disappearingMessagesTurnedOffYou'); + } return window.i18n.stripped('disappearingMessagesTurnedOff', { name: authorName, }); @@ -298,12 +396,49 @@ export class MessageModel extends Backbone.Model { ? window.i18n.stripped('disappearingMessagesTypeRead') : window.i18n.stripped('disappearingMessagesTypeSent'); + if (isUs) { + return window.i18n.stripped('disappearingMessagesSetYou', { + time: TimerOptions.getAbbreviated(expireTimerUpdate.expireTimer || 0), + disappearing_messages_type: localizedMode, + }); + } + return window.i18n.stripped('disappearingMessagesSet', { time: TimerOptions.getAbbreviated(expireTimerUpdate.expireTimer || 0), name: authorName, disappearing_messages_type: localizedMode, }); } + const body = this.get('body'); + if (body) { + let bodyMentionsMappedToNames = body; + // regex with a 'g' to ignore part groups + const regex = new RegExp(`@${PubKey.regexForPubkeys}`, 'g'); + const pubkeysInDesc = body.match(regex); + (pubkeysInDesc || []).forEach((pubkeyWithAt: string) => { + const pubkey = pubkeyWithAt.slice(1); + const isUS = isUsAnySogsFromCache(pubkey); + const displayName = + getConversationController().getContactProfileNameOrShortenedPubKey(pubkey); + if (isUS) { + bodyMentionsMappedToNames = bodyMentionsMappedToNames?.replace( + pubkeyWithAt, + `@${window.i18n('you')}` + ); + } else if (displayName && displayName.length) { + bodyMentionsMappedToNames = bodyMentionsMappedToNames?.replace( + pubkeyWithAt, + `@${displayName}` + ); + } + }); + return bodyMentionsMappedToNames; + } + + // Note: we want this after the check for a body as we want to display the body if we have one. + if ((this.get('attachments') || []).length) { + return window.i18n.stripped('contentDescriptionMediaMessage'); + } return ''; } @@ -1265,124 +1400,6 @@ export class MessageModel extends Backbone.Model { return forcedArrayUpdate; } - private getDescription() { - const groupUpdate = this.getGroupUpdateAsArray(); - if (groupUpdate) { - const groupName = - this.getConversation()?.getNicknameOrRealUsernameOrPlaceholder() || window.i18n('unknown'); - - if (groupUpdate.left) { - // @ts-expect-error -- TODO: Fix by using new i18n builder - const { token, args } = getLeftGroupUpdateChangeStr(groupUpdate.left, groupName); - // TODO: clean up this typing - return window.i18n.stripped(...([token, args] as GetMessageArgs)); - } - - if (groupUpdate.name) { - return window.i18n.stripped('groupNameNew', { group_name: groupUpdate.name }); - } - - if (groupUpdate.joined?.length) { - // @ts-expect-error -- TODO: Fix by using new i18n builder - const { token, args } = getJoinedGroupUpdateChangeStr(groupUpdate.joined, groupName); - // TODO: clean up this typing - return window.i18n.stripped(...([token, args] as GetMessageArgs)); - } - - if (groupUpdate.kicked?.length) { - // @ts-expect-error -- TODO: Fix by using new i18n builder - const { token, args } = getKickedGroupUpdateStr(groupUpdate.kicked, groupName); - // TODO: clean up this typing - return window.i18n.stripped(...([token, args] as GetMessageArgs)); - } - window.log.warn('did not build a specific change for getDescription of ', groupUpdate); - - return window.i18n.stripped('groupUpdated'); - } - - if (this.isGroupInvitation()) { - return `😎 ${window.i18n.stripped('communityInvitation')}`; - } - - if (this.isDataExtractionNotification()) { - const dataExtraction = this.get( - 'dataExtractionNotification' - ) as DataExtractionNotificationMsg; - if (dataExtraction.type === SignalService.DataExtractionNotification.Type.SCREENSHOT) { - return window.i18n.stripped('screenshotTaken', { - name: getConversationController().getContactProfileNameOrShortenedPubKey( - dataExtraction.source - ), - }); - } - - return window.i18n.stripped('attachmentsMediaSaved', { - name: getConversationController().getContactProfileNameOrShortenedPubKey( - dataExtraction.source - ), - }); - } - if (this.isCallNotification()) { - const name = getConversationController().getContactProfileNameOrShortenedPubKey( - this.get('conversationId') - ); - const callNotificationType = this.get('callNotificationType'); - if (callNotificationType === 'missed-call') { - return window.i18n.stripped('callsMissedCallFrom', { name }); - } - if (callNotificationType === 'started-call') { - return window.i18n.stripped('callsYouCalled', { name }); - } - if (callNotificationType === 'answered-a-call') { - return window.i18n.stripped('callsInProgress'); - } - } - - const interactionNotification = this.getInteractionNotification(); - if (interactionNotification) { - const { interactionType, interactionStatus } = interactionNotification; - - // NOTE For now we only show interaction errors in the message history - if (interactionStatus === ConversationInteractionStatus.Error) { - const convo = getConversationController().get(this.get('conversationId')); - - if (convo) { - const isGroup = !convo.isPrivate(); - const isCommunity = convo.isPublic(); - - switch (interactionType) { - case ConversationInteractionType.Hide: - // there is no text for hiding changes - return ''; - case ConversationInteractionType.Leave: - return isCommunity - ? window.i18n.stripped('communityLeaveError', { - community_name: convo.getNicknameOrRealUsernameOrPlaceholder(), - }) - : isGroup - ? window.i18n.stripped('groupLeaveErrorFailed', { - group_name: convo.getNicknameOrRealUsernameOrPlaceholder(), - }) - : ''; - default: - assertUnreachable( - interactionType, - `Message.getDescription: Missing case error "${interactionType}"` - ); - } - } - } - } - - if (this.get('reaction')) { - const reaction = this.get('reaction'); - if (reaction && reaction.emoji && reaction.emoji !== '') { - return window.i18n.stripped('emojiReactsNotification', { emoji: reaction.emoji }); - } - } - return this.get('body'); - } - // NOTE We want to replace Backbone .get() calls with these getters as we migrate to Redux completely eventually // #region Start of getters public getExpirationType() {