diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 416d56c5908..5e7e3ae46a1 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -3771,6 +3771,14 @@ "messageformat": "Okay", "description": "Button to dismiss popup dialog when user-initiated task has gone wrong" }, + "icu:MessageMaxEditsModal__Title": { + "messageformat": "Can't edit message", + "description": "Title in popup dialog when attempting to edit a message too many times." + }, + "icu:MessageMaxEditsModal__Description": { + "messageformat": "Only {max, number} edits can be applied to this message.", + "description": "Description text in popup dialog when attempting to edit a message too many times." + }, "icu:unknown-sgnl-link": { "messageformat": "Sorry, that sgnl:// link didn't make sense!", "description": "Shown if you click on a sgnl:// link not currently supported by Desktop" diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts index f338b0bb584..f9287787a4a 100644 --- a/ts/state/ducks/conversations.ts +++ b/ts/state/ducks/conversations.ts @@ -29,10 +29,12 @@ import * as Attachment from '../../types/Attachment'; import { isFileDangerous } from '../../util/isFileDangerous'; import type { ShowSendAnywayDialogActionType, + ShowErrorModalActionType, ToggleProfileEditorErrorActionType, } from './globalModals'; import { SHOW_SEND_ANYWAY_DIALOG, + SHOW_ERROR_MODAL, TOGGLE_PROFILE_EDITOR_ERROR, } from './globalModals'; import { @@ -90,6 +92,7 @@ import { getMe, getMessagesByConversation, } from '../selectors/conversations'; +import { getIntl } from '../selectors/user'; import type { AvatarDataType, AvatarUpdateType } from '../../types/Avatar'; import { getDefaultAvatars } from '../../types/Avatar'; import { getAvatarData } from '../../util/getAvatarData'; @@ -164,7 +167,11 @@ import { } from './composer'; import { ReceiptType } from '../../types/Receipt'; import { Sound, SoundType } from '../../util/Sound'; -import { canEditMessage } from '../../util/canEditMessage'; +import { + canEditMessage, + isWithinMaxEdits, + MESSAGE_MAX_EDIT_COUNT, +} from '../../util/canEditMessage'; import type { ChangeNavTabActionType } from './nav'; import { CHANGE_NAV_TAB, NavTab, actions as navActions } from './nav'; import { sortByMessageOrder } from '../../types/ForwardDraft'; @@ -1770,7 +1777,12 @@ function discardEditMessage( function setMessageToEdit( conversationId: string, messageId: string -): ThunkAction { +): ThunkAction< + void, + RootStateType, + unknown, + SetFocusActionType | ShowErrorModalActionType +> { return async (dispatch, getState) => { const conversation = window.ConversationController.get(conversationId); @@ -1787,6 +1799,20 @@ function setMessageToEdit( return; } + if (!isWithinMaxEdits(message)) { + const i18n = getIntl(getState()); + dispatch({ + type: SHOW_ERROR_MODAL, + payload: { + title: i18n('icu:MessageMaxEditsModal__Title'), + description: i18n('icu:MessageMaxEditsModal__Description', { + max: MESSAGE_MAX_EDIT_COUNT, + }), + }, + }); + return; + } + setQuoteByMessageId(conversationId, undefined)( dispatch, getState, diff --git a/ts/state/ducks/globalModals.ts b/ts/state/ducks/globalModals.ts index 79e1f043df3..1a81ec0fac3 100644 --- a/ts/state/ducks/globalModals.ts +++ b/ts/state/ducks/globalModals.ts @@ -135,7 +135,7 @@ const CLOSE_GV2_MIGRATION_DIALOG = 'globalModals/CLOSE_GV2_MIGRATION_DIALOG'; const SHOW_STICKER_PACK_PREVIEW = 'globalModals/SHOW_STICKER_PACK_PREVIEW'; const CLOSE_STICKER_PACK_PREVIEW = 'globalModals/CLOSE_STICKER_PACK_PREVIEW'; const CLOSE_ERROR_MODAL = 'globalModals/CLOSE_ERROR_MODAL'; -const SHOW_ERROR_MODAL = 'globalModals/SHOW_ERROR_MODAL'; +export const SHOW_ERROR_MODAL = 'globalModals/SHOW_ERROR_MODAL'; const SHOW_FORMATTING_WARNING_MODAL = 'globalModals/SHOW_FORMATTING_WARNING_MODAL'; const SHOW_SEND_EDIT_WARNING_MODAL = @@ -286,7 +286,7 @@ type CloseErrorModalActionType = ReadonlyDeep<{ type: typeof CLOSE_ERROR_MODAL; }>; -type ShowErrorModalActionType = ReadonlyDeep<{ +export type ShowErrorModalActionType = ReadonlyDeep<{ type: typeof SHOW_ERROR_MODAL; payload: { description?: string; diff --git a/ts/state/selectors/conversations.ts b/ts/state/selectors/conversations.ts index 69babb997e3..1e311957325 100644 --- a/ts/state/selectors/conversations.ts +++ b/ts/state/selectors/conversations.ts @@ -1215,6 +1215,7 @@ export const getConversationTitle = createSelector( getConversationTitleForPanelType(i18n, panel?.type) ); +// Note that this doesn't take into account max edit count. See canEditMessage. export const getLastEditableMessageId = createSelector( getConversationMessages, getMessages, diff --git a/ts/util/canEditMessage.ts b/ts/util/canEditMessage.ts index 65548c41857..a54dcdc59ec 100644 --- a/ts/util/canEditMessage.ts +++ b/ts/util/canEditMessage.ts @@ -8,7 +8,7 @@ import { isMoreRecentThan } from './timestamp'; import { isOutgoing } from '../messages/helpers'; import { isSent, someSendStatus } from '../messages/MessageSendState'; -const MAX_EDIT_COUNT = 10; +export const MESSAGE_MAX_EDIT_COUNT = 10; export function canEditMessage(message: MessageAttributesType): boolean { const result = @@ -16,7 +16,6 @@ export function canEditMessage(message: MessageAttributesType): boolean { !message.deletedForEveryone && isOutgoing(message) && isMoreRecentThan(message.sent_at, DAY) && - (message.editHistory?.length ?? 0) <= MAX_EDIT_COUNT && someSendStatus(message.sendStateByConversationId, isSent) && Boolean(message.body); @@ -35,3 +34,7 @@ export function canEditMessage(message: MessageAttributesType): boolean { return false; } + +export function isWithinMaxEdits(message: MessageAttributesType): boolean { + return (message.editHistory?.length ?? 0) <= MESSAGE_MAX_EDIT_COUNT; +} diff --git a/ts/util/sendEditedMessage.ts b/ts/util/sendEditedMessage.ts index d4161bd9bd6..36fd0bfdd8d 100644 --- a/ts/util/sendEditedMessage.ts +++ b/ts/util/sendEditedMessage.ts @@ -15,7 +15,7 @@ import { ErrorWithToast } from '../types/ErrorWithToast'; import { SendStatus } from '../messages/MessageSendState'; import { ToastType } from '../types/Toast'; import type { AciString } from '../types/ServiceId'; -import { canEditMessage } from './canEditMessage'; +import { canEditMessage, isWithinMaxEdits } from './canEditMessage'; import { conversationJobQueue, conversationQueueJobEnum, @@ -77,7 +77,10 @@ export async function sendEditedMessage( return; } - if (!canEditMessage(targetMessage.attributes)) { + if ( + !canEditMessage(targetMessage.attributes) || + !isWithinMaxEdits(targetMessage.attributes) + ) { throw new ErrorWithToast( `${idLog}: cannot edit`, ToastType.CannotEditMessage