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 {
|
&::before {
|
||||||
@include color-svg('../images/icons/v3/check/check.svg', $color-white);
|
@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 editedMessageId = draftEditMessage?.targetMessageId;
|
||||||
|
|
||||||
const handleSubmit = useCallback(
|
const handleSubmit = useCallback(
|
||||||
(message: string, bodyRanges: DraftBodyRanges, timestamp: number) => {
|
(
|
||||||
|
message: string,
|
||||||
|
bodyRanges: DraftBodyRanges,
|
||||||
|
timestamp: number
|
||||||
|
): boolean => {
|
||||||
|
if (!dirty) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
emojiButtonRef.current?.close();
|
emojiButtonRef.current?.close();
|
||||||
|
|
||||||
if (editedMessageId) {
|
if (editedMessageId) {
|
||||||
|
@ -380,9 +388,12 @@ export const CompositionArea = memo(function CompositionArea({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
setLarge(false);
|
setLarge(false);
|
||||||
|
|
||||||
|
return true;
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
conversationId,
|
conversationId,
|
||||||
|
dirty,
|
||||||
draftAttachments,
|
draftAttachments,
|
||||||
editedMessageId,
|
editedMessageId,
|
||||||
quotedMessageSentAt,
|
quotedMessageSentAt,
|
||||||
|
@ -592,6 +603,7 @@ export const CompositionArea = memo(function CompositionArea({
|
||||||
<button
|
<button
|
||||||
aria-label={i18n('icu:CompositionArea__edit-action--send')}
|
aria-label={i18n('icu:CompositionArea__edit-action--send')}
|
||||||
className="CompositionArea__edit-button CompositionArea__edit-button--accept"
|
className="CompositionArea__edit-button CompositionArea__edit-button--accept"
|
||||||
|
disabled={!dirty}
|
||||||
onClick={() => inputApiRef.current?.submit()}
|
onClick={() => inputApiRef.current?.submit()}
|
||||||
type="button"
|
type="button"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -22,7 +22,12 @@ import type {
|
||||||
HydratedBodyRangesType,
|
HydratedBodyRangesType,
|
||||||
RangeNode,
|
RangeNode,
|
||||||
} from '../types/BodyRange';
|
} 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 { LocalizerType, ThemeType } from '../types/Util';
|
||||||
import type { ConversationType } from '../state/ducks/conversations';
|
import type { ConversationType } from '../state/ducks/conversations';
|
||||||
import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
|
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`
|
`CompositionInput: Submitting message ${timestamp} with ${bodyRanges.length} ranges`
|
||||||
);
|
);
|
||||||
canSendRef.current = false;
|
canSendRef.current = false;
|
||||||
onSubmit(text, bodyRanges, timestamp);
|
const didSend = onSubmit(text, bodyRanges, timestamp);
|
||||||
|
|
||||||
|
if (!didSend) {
|
||||||
|
canSendRef.current = true;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (inputApi) {
|
if (inputApi) {
|
||||||
|
@ -579,7 +588,19 @@ export function CompositionInput(props: Props): React.ReactElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (propsRef.current.onDirtyChange) {
|
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;
|
: undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const draftBodyRanges = processBodyRanges(message, {
|
||||||
|
conversationSelector: getConversationSelector(getState()),
|
||||||
|
});
|
||||||
conversation.set({
|
conversation.set({
|
||||||
draftEditMessage: {
|
draftEditMessage: {
|
||||||
body: message.body,
|
body: message.body,
|
||||||
|
bodyRanges: draftBodyRanges,
|
||||||
editHistoryLength: message.editHistory?.length ?? 0,
|
editHistoryLength: message.editHistory?.length ?? 0,
|
||||||
attachmentThumbnail,
|
attachmentThumbnail,
|
||||||
preview: message.preview ? message.preview[0] : undefined,
|
preview: message.preview ? message.preview[0] : undefined,
|
||||||
targetMessageId: messageId,
|
targetMessageId: messageId,
|
||||||
quote: message.quote,
|
quote: message.quote,
|
||||||
},
|
},
|
||||||
draftBodyRanges: processBodyRanges(message, {
|
draftBodyRanges,
|
||||||
conversationSelector: getConversationSelector(getState()),
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-namespace */
|
/* 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 { SignalService as Proto } from '../protobuf';
|
||||||
import * as log from '../logging/log';
|
import * as log from '../logging/log';
|
||||||
|
@ -849,3 +849,27 @@ export function applyRangesToText(
|
||||||
|
|
||||||
return state;
|
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