From 101070bf4223cc9c12a942f16bbd8c554e3ca33f Mon Sep 17 00:00:00 2001 From: Ken Powers Date: Fri, 7 Feb 2020 18:13:46 -0500 Subject: [PATCH] Prevent replies/reactions on messages with errors --- js/models/messages.js | 19 ++++++ js/views/conversation_view.js | 4 ++ .../conversation/Message.stories.tsx | 2 + ts/components/conversation/Message.tsx | 66 +++++++++++-------- ts/util/lint/exceptions.json | 8 +-- 5 files changed, 68 insertions(+), 31 deletions(-) diff --git a/js/models/messages.js b/js/models/messages.js index 866c5e04c..bb5223274 100644 --- a/js/models/messages.js +++ b/js/models/messages.js @@ -534,6 +534,7 @@ timestamp: this.get('sent_at'), status: this.getMessagePropStatus(), contact: this.getPropsForEmbeddedContact(), + canReply: this.canReply(), authorColor, authorName: contact.name, authorProfileName: contact.profileName, @@ -1305,6 +1306,24 @@ e.name === 'OutgoingIdentityKeyError' ); }, + canReply() { + const errors = this.get('errors'); + const isOutgoing = this.get('type') === 'outgoing'; + const numDelivered = this.get('delivered'); + + // Case 1: We can reply if this is outgoing and delievered to at least one recipient + if (isOutgoing && numDelivered > 0) { + return true; + } + + // Case 2: We can reply if there are no errors + if (errors && errors.length === 0) { + return true; + } + + // Otherwise we cannot reply + return false; + }, // Called when the user ran into an error with a specific user, wants to send to them // One caller today: ConversationView.forceSend() diff --git a/js/views/conversation_view.js b/js/views/conversation_view.js index df1d398ca..25bd680d2 100644 --- a/js/views/conversation_view.js +++ b/js/views/conversation_view.js @@ -2528,6 +2528,10 @@ }) : null; + if (model && !model.canReply()) { + return; + } + if (model && !model.isNormalBubble()) { return; } diff --git a/ts/components/conversation/Message.stories.tsx b/ts/components/conversation/Message.stories.tsx index f94e33abf..676aeb14d 100644 --- a/ts/components/conversation/Message.stories.tsx +++ b/ts/components/conversation/Message.stories.tsx @@ -15,6 +15,7 @@ const book = storiesOf('Components/Conversation/Message', module); const baseDataProps: Pick< PropsData, | 'id' + | 'canReply' | 'conversationId' | 'interactionMode' | 'conversationType' @@ -23,6 +24,7 @@ const baseDataProps: Pick< | 'authorPhoneNumber' > = { id: 'asdf', + canReply: true, conversationId: 'asdf', interactionMode: 'mouse', conversationType: 'direct', diff --git a/ts/components/conversation/Message.tsx b/ts/components/conversation/Message.tsx index 702848010..a7ff41577 100644 --- a/ts/components/conversation/Message.tsx +++ b/ts/components/conversation/Message.tsx @@ -105,6 +105,8 @@ export type PropsData = { reactions?: ReactionViewerProps['reactions']; selectedReaction?: string; + + canReply: boolean; }; export type PropsHousekeeping = { @@ -986,6 +988,7 @@ export class Message extends React.PureComponent { const { attachments, // tslint:disable-next-line max-func-body-length + canReply, direction, disableMenu, id, @@ -1098,9 +1101,9 @@ export class Message extends React.PureComponent { `module-message__buttons--${direction}` )} > - {reactButton} + {canReply ? reactButton : null} {downloadButton} - {replyButton} + {canReply ? replyButton : null} {menuButton} {reactionPickerRoot && @@ -1132,6 +1135,7 @@ export class Message extends React.PureComponent { public renderContextMenu(triggerId: string) { const { attachments, + canReply, deleteMessage, direction, i18n, @@ -1163,32 +1167,36 @@ export class Message extends React.PureComponent { {i18n('downloadAttachment')} ) : null} - { - event.stopPropagation(); - event.preventDefault(); + {canReply ? ( + <> + { + event.stopPropagation(); + event.preventDefault(); - this.toggleReactionPicker(); - }} - > - {i18n('reactToMessage')} - - { - event.stopPropagation(); - event.preventDefault(); + this.toggleReactionPicker(); + }} + > + {i18n('reactToMessage')} + + { + event.stopPropagation(); + event.preventDefault(); - replyToMessage(id); - }} - > - {i18n('replyToMessage')} - + replyToMessage(id); + }} + > + {i18n('replyToMessage')} + + + ) : null} { }; public handleKeyDown = (event: React.KeyboardEvent) => { + // Do not allow reactions to error messages + const { canReply } = this.props; + if ( (event.key === 'E' || event.key === 'e') && (event.metaKey || event.ctrlKey) && - event.shiftKey + event.shiftKey && + canReply ) { this.toggleReactionPicker(); } diff --git a/ts/util/lint/exceptions.json b/ts/util/lint/exceptions.json index 57629b2d4..e9cbda680 100644 --- a/ts/util/lint/exceptions.json +++ b/ts/util/lint/exceptions.json @@ -9250,17 +9250,17 @@ "rule": "React-createRef", "path": "ts/components/conversation/Message.tsx", "line": " public audioRef: React.RefObject = React.createRef();", - "lineNumber": 178, + "lineNumber": 180, "reasonCategory": "usageTrusted", - "updated": "2020-02-03T17:18:39.600Z" + "updated": "2020-02-07T22:17:41.885Z" }, { "rule": "React-createRef", "path": "ts/components/conversation/Message.tsx", "line": " > = React.createRef();", - "lineNumber": 182, + "lineNumber": 184, "reasonCategory": "usageTrusted", - "updated": "2020-02-03T17:18:39.600Z" + "updated": "2020-02-07T22:17:41.885Z" }, { "rule": "React-createRef",