Edit message: Don't allow send unless message contents changed
This commit is contained in:
		
					parent
					
						
							
								5987350dbe
							
						
					
				
			
			
				commit
				
					
						f53e956810
					
				
			
		
					 5 changed files with 70 additions and 8 deletions
				
			
		|  | @ -82,6 +82,9 @@ | |||
|       &::before { | ||||
|         @include color-svg('../images/icons/v3/check/check.svg', $color-white); | ||||
|       } | ||||
|       &:disabled { | ||||
|         opacity: 0.5; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
|  | @ -359,7 +359,15 @@ export const CompositionArea = memo(function CompositionArea({ | |||
|   const editedMessageId = draftEditMessage?.targetMessageId; | ||||
| 
 | ||||
|   const handleSubmit = useCallback( | ||||
|     (message: string, bodyRanges: DraftBodyRanges, timestamp: number) => { | ||||
|     ( | ||||
|       message: string, | ||||
|       bodyRanges: DraftBodyRanges, | ||||
|       timestamp: number | ||||
|     ): boolean => { | ||||
|       if (!dirty) { | ||||
|         return false; | ||||
|       } | ||||
| 
 | ||||
|       emojiButtonRef.current?.close(); | ||||
| 
 | ||||
|       if (editedMessageId) { | ||||
|  | @ -380,9 +388,12 @@ export const CompositionArea = memo(function CompositionArea({ | |||
|         }); | ||||
|       } | ||||
|       setLarge(false); | ||||
| 
 | ||||
|       return true; | ||||
|     }, | ||||
|     [ | ||||
|       conversationId, | ||||
|       dirty, | ||||
|       draftAttachments, | ||||
|       editedMessageId, | ||||
|       quotedMessageSentAt, | ||||
|  | @ -592,6 +603,7 @@ export const CompositionArea = memo(function CompositionArea({ | |||
|         <button | ||||
|           aria-label={i18n('icu:CompositionArea__edit-action--send')} | ||||
|           className="CompositionArea__edit-button CompositionArea__edit-button--accept" | ||||
|           disabled={!dirty} | ||||
|           onClick={() => inputApiRef.current?.submit()} | ||||
|           type="button" | ||||
|         /> | ||||
|  |  | |||
|  | @ -22,7 +22,12 @@ import type { | |||
|   HydratedBodyRangesType, | ||||
|   RangeNode, | ||||
| } from '../types/BodyRange'; | ||||
| import { BodyRange, collapseRangeTree, insertRange } from '../types/BodyRange'; | ||||
| import { | ||||
|   BodyRange, | ||||
|   areBodyRangesEqual, | ||||
|   collapseRangeTree, | ||||
|   insertRange, | ||||
| } from '../types/BodyRange'; | ||||
| import type { LocalizerType, ThemeType } from '../types/Util'; | ||||
| import type { ConversationType } from '../state/ducks/conversations'; | ||||
| import type { PreferredBadgeSelectorType } from '../state/selectors/badges'; | ||||
|  | @ -359,7 +364,11 @@ export function CompositionInput(props: Props): React.ReactElement { | |||
|       `CompositionInput: Submitting message ${timestamp} with ${bodyRanges.length} ranges` | ||||
|     ); | ||||
|     canSendRef.current = false; | ||||
|     onSubmit(text, bodyRanges, timestamp); | ||||
|     const didSend = onSubmit(text, bodyRanges, timestamp); | ||||
| 
 | ||||
|     if (!didSend) { | ||||
|       canSendRef.current = true; | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   if (inputApi) { | ||||
|  | @ -579,7 +588,19 @@ export function CompositionInput(props: Props): React.ReactElement { | |||
|     } | ||||
| 
 | ||||
|     if (propsRef.current.onDirtyChange) { | ||||
|       propsRef.current.onDirtyChange(text.length > 0); | ||||
|       let isDirty: boolean = false; | ||||
| 
 | ||||
|       if (!draftEditMessage) { | ||||
|         isDirty = text.length > 0; | ||||
|       } else if (text.trimEnd() !== draftEditMessage.body.trimEnd()) { | ||||
|         isDirty = true; | ||||
|       } else if (bodyRanges.length !== draftEditMessage.bodyRanges?.length) { | ||||
|         isDirty = true; | ||||
|       } else if (!areBodyRangesEqual(bodyRanges, draftEditMessage.bodyRanges)) { | ||||
|         isDirty = true; | ||||
|       } | ||||
| 
 | ||||
|       propsRef.current.onDirtyChange(isDirty); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -1913,18 +1913,20 @@ function setMessageToEdit( | |||
|         : undefined; | ||||
|     } | ||||
| 
 | ||||
|     const draftBodyRanges = processBodyRanges(message, { | ||||
|       conversationSelector: getConversationSelector(getState()), | ||||
|     }); | ||||
|     conversation.set({ | ||||
|       draftEditMessage: { | ||||
|         body: message.body, | ||||
|         bodyRanges: draftBodyRanges, | ||||
|         editHistoryLength: message.editHistory?.length ?? 0, | ||||
|         attachmentThumbnail, | ||||
|         preview: message.preview ? message.preview[0] : undefined, | ||||
|         targetMessageId: messageId, | ||||
|         quote: message.quote, | ||||
|       }, | ||||
|       draftBodyRanges: processBodyRanges(message, { | ||||
|         conversationSelector: getConversationSelector(getState()), | ||||
|       }), | ||||
|       draftBodyRanges, | ||||
|     }); | ||||
| 
 | ||||
|     dispatch({ | ||||
|  |  | |||
|  | @ -3,7 +3,7 @@ | |||
| 
 | ||||
| /* eslint-disable @typescript-eslint/no-namespace */ | ||||
| 
 | ||||
| import { isNumber, omit, partition } from 'lodash'; | ||||
| import { isEqual, isNumber, omit, orderBy, partition } from 'lodash'; | ||||
| 
 | ||||
| import { SignalService as Proto } from '../protobuf'; | ||||
| import * as log from '../logging/log'; | ||||
|  | @ -849,3 +849,27 @@ export function applyRangesToText( | |||
| 
 | ||||
|   return state; | ||||
| } | ||||
| 
 | ||||
| // For ease of working with draft mentions in Quill, a conversationID field is present.
 | ||||
| function normalizeBodyRanges(bodyRanges: DraftBodyRanges) { | ||||
|   return orderBy(bodyRanges, ['start', 'length']).map(item => { | ||||
|     if (BodyRange.isMention(item)) { | ||||
|       return { ...item, conversationID: undefined }; | ||||
|     } | ||||
|     return item; | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| export function areBodyRangesEqual( | ||||
|   left: DraftBodyRanges, | ||||
|   right: DraftBodyRanges | ||||
| ): boolean { | ||||
|   const normalizedLeft = normalizeBodyRanges(left); | ||||
|   const sortedRight = normalizeBodyRanges(right); | ||||
| 
 | ||||
|   if (normalizedLeft.length !== sortedRight.length) { | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
|   return isEqual(normalizedLeft, sortedRight); | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Scott Nonnenberg
				Scott Nonnenberg