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