Use sentCounter in CompositionInput to drop old draft updates

This commit is contained in:
Scott Nonnenberg 2023-04-05 15:06:16 -07:00 committed by GitHub
parent 0e606c45b0
commit 4a18667ddf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 121 additions and 63 deletions

View file

@ -35,6 +35,7 @@ const useProps = (overrideProps: Partial<Props> = {}): Props => ({
addAttachment: action('addAttachment'),
conversationId: '123',
focusCounter: 0,
sendCounter: 0,
i18n,
isDisabled: false,
messageCompositionId: '456',

View file

@ -162,14 +162,15 @@ export type OwnProps = Readonly<{
export type Props = Pick<
CompositionInputProps,
| 'sortedGroupMembers'
| 'onEditorStateChange'
| 'onTextTooLong'
| 'clearQuotedMessage'
| 'draftText'
| 'draftBodyRanges'
| 'clearQuotedMessage'
| 'getPreferredBadge'
| 'getQuotedMessage'
| 'onEditorStateChange'
| 'onTextTooLong'
| 'sendCounter'
| 'sortedGroupMembers'
> &
Pick<
EmojiButtonProps,
@ -231,13 +232,14 @@ export function CompositionArea({
setMediaQualitySetting,
shouldSendHighQualityAttachments,
// CompositionInput
onEditorStateChange,
onTextTooLong,
clearQuotedMessage,
draftText,
draftBodyRanges,
clearQuotedMessage,
getPreferredBadge,
getQuotedMessage,
onEditorStateChange,
onTextTooLong,
sendCounter,
sortedGroupMembers,
// EmojiButton
onPickEmoji,
@ -805,6 +807,7 @@ export function CompositionArea({
onPickEmoji={onPickEmoji}
onSubmit={handleSubmit}
onTextTooLong={onTextTooLong}
sendCounter={sendCounter}
skinTone={skinTone}
sortedGroupMembers={sortedGroupMembers}
theme={theme}

View file

@ -33,6 +33,7 @@ const useProps = (overrideProps: Partial<Props> = {}): Props => ({
getQuotedMessage: action('getQuotedMessage'),
onPickEmoji: action('onPickEmoji'),
large: boolean('large', overrideProps.large || false),
sendCounter: 0,
sortedGroupMembers: overrideProps.sortedGroupMembers || [],
skinTone: select(
'skinTone',

View file

@ -79,6 +79,7 @@ export type Props = Readonly<{
getPreferredBadge: PreferredBadgeSelectorType;
large?: boolean;
inputApi?: React.MutableRefObject<InputApi | undefined>;
sendCounter: number;
skinTone?: EmojiPickDataType['skinTone'];
draftText?: string;
draftBodyRanges?: DraftBodyRangesType;
@ -88,12 +89,13 @@ export type Props = Readonly<{
sortedGroupMembers?: ReadonlyArray<ConversationType>;
scrollerRef?: React.RefObject<HTMLDivElement>;
onDirtyChange?(dirty: boolean): unknown;
onEditorStateChange?(
conversationId: string | undefined,
messageText: string,
bodyRanges: DraftBodyRangesType,
caretLocation?: number
): unknown;
onEditorStateChange?(options: {
bodyRanges: DraftBodyRangesType;
caretLocation?: number;
conversationId: string | undefined;
messageText: string;
sendCounter: number;
}): unknown;
onTextTooLong(): unknown;
onPickEmoji(o: EmojiPickDataType): unknown;
onSubmit(
@ -134,6 +136,7 @@ export function CompositionInput(props: Props): React.ReactElement {
onSubmit,
placeholder,
skinTone,
sendCounter,
sortedGroupMembers,
theme,
} = props;
@ -458,12 +461,13 @@ export function CompositionInput(props: Props): React.ReactElement {
setTimeout(() => {
const selection = quill.getSelection();
onEditorStateChange(
onEditorStateChange({
bodyRanges: mentions,
caretLocation: selection ? selection.index : undefined,
conversationId,
text,
mentions,
selection ? selection.index : undefined
);
messageText: text,
sendCounter,
});
}, 0);
}
}

View file

@ -86,12 +86,7 @@ export function CompositionTextArea({
}, [inputApiRef]);
const handleChange = React.useCallback(
(
_conversationId: string | undefined,
newValue: string,
bodyRanges: DraftBodyRangesType,
caretLocation?: number | undefined
) => {
({ bodyRanges, caretLocation, messageText: newValue }) => {
const inputEl = inputApiRef.current;
if (!inputEl) {
return;
@ -124,21 +119,22 @@ export function CompositionTextArea({
return (
<div className="CompositionTextArea">
<CompositionInput
placeholder={placeholder}
clearQuotedMessage={shouldNeverBeCalled}
scrollerRef={scrollerRef}
draftText={draftText}
getPreferredBadge={getPreferredBadge}
getQuotedMessage={noop}
i18n={i18n}
inputApi={inputApiRef}
large
moduleClassName="CompositionTextArea__input"
onScroll={onScroll}
onEditorStateChange={handleChange}
onPickEmoji={onPickEmoji}
onScroll={onScroll}
onSubmit={onSubmit}
onTextTooLong={onTextTooLong}
draftText={draftText}
placeholder={placeholder}
scrollerRef={scrollerRef}
sendCounter={0}
theme={theme}
/>
<div className="CompositionTextArea__emoji">

View file

@ -225,7 +225,7 @@ export function StoryViewsNRepliesModal({
i18n={i18n}
inputApi={inputApiRef}
moduleClassName="StoryViewsNRepliesModal__input"
onEditorStateChange={(_conversationId, messageText) => {
onEditorStateChange={({ messageText }) => {
setMessageBodyText(messageText);
}}
onPickEmoji={onUseEmoji}
@ -242,6 +242,7 @@ export function StoryViewsNRepliesModal({
firstName: authorTitle,
})
}
sendCounter={0}
sortedGroupMembers={sortedGroupMembers}
theme={ThemeType.dark}
>

View file

@ -100,6 +100,7 @@ type ComposerStateByConversationType = {
linkPreviewResult?: LinkPreviewType;
messageCompositionId: UUIDStringType;
quotedMessage?: Pick<MessageAttributesType, 'conversationId' | 'quote'>;
sendCounter: number;
shouldSendHighQualityAttachments?: boolean;
};
@ -121,6 +122,7 @@ function getEmptyComposerState(): ComposerStateByConversationType {
isDisabled: false,
linkPreviewLoading: false,
messageCompositionId: UUID.generate().toString(),
sendCounter: 0,
};
}
@ -134,6 +136,7 @@ export function getComposerStateForConversation(
// Actions
const ADD_PENDING_ATTACHMENT = 'composer/ADD_PENDING_ATTACHMENT';
const INCREMENT_SEND_COUNTER = 'composer/INCREMENT_SEND_COUNTER';
const REPLACE_ATTACHMENTS = 'composer/REPLACE_ATTACHMENTS';
const RESET_COMPOSER = 'composer/RESET_COMPOSER';
const SET_FOCUS = 'composer/SET_FOCUS';
@ -149,6 +152,13 @@ type AddPendingAttachmentActionType = ReadonlyDeep<{
};
}>;
export type IncrementSendActionType = ReadonlyDeep<{
type: typeof INCREMENT_SEND_COUNTER;
payload: {
conversationId: string;
};
}>;
// eslint-disable-next-line local-rules/type-alias-readonlydeep
export type ReplaceAttachmentsActionType = {
type: typeof REPLACE_ATTACHMENTS;
@ -202,6 +212,7 @@ type ComposerActionType =
| AddLinkPreviewActionType
| AddPendingAttachmentActionType
| ConversationUnloadedActionType
| IncrementSendActionType
| RemoveLinkPreviewActionType
| ReplaceAttachmentsActionType
| ResetComposerActionType
@ -217,6 +228,7 @@ export const actions = {
addAttachment,
addPendingAttachment,
cancelJoinRequest,
incrementSendCounter,
onClearAttachments,
onCloseLinkPreview,
onEditorStateChange,
@ -236,6 +248,15 @@ export const actions = {
setQuotedMessage,
};
function incrementSendCounter(conversationId: string): IncrementSendActionType {
return {
type: INCREMENT_SEND_COUNTER,
payload: {
conversationId,
},
};
}
export const useComposerActions = (): BoundActionCreatorsMapObject<
typeof actions
> => useBoundActions(actions);
@ -370,6 +391,7 @@ function sendMultiMediaMessage(
void,
RootStateType,
unknown,
| IncrementSendActionType
| NoopActionType
| ResetComposerActionType
| SetComposerDisabledStateActionType
@ -495,7 +517,7 @@ function sendMultiMediaMessage(
getState,
undefined
);
dispatch(resetComposer(conversationId));
dispatch(incrementSendCounter(conversationId));
dispatch(setComposerDisabledState(conversationId, false));
},
}
@ -787,44 +809,66 @@ export function setComposerFocus(
};
}
function onEditorStateChange(
conversationId: string | undefined,
messageText: string,
bodyRanges: DraftBodyRangesType,
caretLocation?: number
): NoopActionType {
if (!conversationId) {
throw new Error(
'onEditorStateChange: Got falsey conversationId, needs local override'
);
}
function onEditorStateChange({
bodyRanges,
caretLocation,
conversationId,
messageText,
sendCounter,
}: {
bodyRanges: DraftBodyRangesType;
caretLocation?: number;
conversationId: string | undefined;
messageText: string;
sendCounter: number;
}): ThunkAction<void, RootStateType, unknown, NoopActionType> {
return (dispatch, getState) => {
if (!conversationId) {
throw new Error(
'onEditorStateChange: Got falsey conversationId, needs local override'
);
}
const conversation = window.ConversationController.get(conversationId);
if (!conversation) {
throw new Error('processAttachments: Unable to find conversation');
}
const conversation = window.ConversationController.get(conversationId);
if (!conversation) {
throw new Error('processAttachments: Unable to find conversation');
}
if (messageText.length && conversation.throttledBumpTyping) {
conversation.throttledBumpTyping();
}
const state = getState().composer.conversations[conversationId];
if (!state) {
return;
}
debouncedSaveDraft(conversationId, messageText, bodyRanges);
if (state.sendCounter !== sendCounter) {
log.warn(
`onEditorStateChange: Got update for conversation ${conversation.idForLogging()}`,
`but sendCounter doesnt match (old: ${state.sendCounter}, new: ${sendCounter})`
);
return;
}
// If we have attachments, don't add link preview
if (
!hasDraftAttachments(conversation.attributes.draftAttachments, {
includePending: true,
})
) {
maybeGrabLinkPreview(messageText, LinkPreviewSourceType.Composer, {
caretLocation,
conversationId,
if (messageText.length && conversation.throttledBumpTyping) {
conversation.throttledBumpTyping();
}
debouncedSaveDraft(conversationId, messageText, bodyRanges);
// If we have attachments, don't add link preview
if (
!hasDraftAttachments(conversation.attributes.draftAttachments, {
includePending: true,
})
) {
maybeGrabLinkPreview(messageText, LinkPreviewSourceType.Composer, {
caretLocation,
conversationId,
});
}
dispatch({
type: 'NOOP',
payload: null,
});
}
return {
type: 'NOOP',
payload: null,
};
}
@ -1296,6 +1340,12 @@ export function reducer(
}));
}
if (action.type === INCREMENT_SEND_COUNTER) {
return updateComposerState(state, action, prevState => ({
sendCounter: prevState.sendCounter + 1,
}));
}
if (action.type === SET_FOCUS) {
return updateComposerState(state, action, prevState => ({
focusCounter: prevState.focusCounter + 1,

View file

@ -86,6 +86,7 @@ const mapStateToProps = (state: StateType, props: ExternalProps) => {
linkPreviewResult,
messageCompositionId,
quotedMessage,
sendCounter,
shouldSendHighQualityAttachments,
} = composerStateForConversationIdSelector(id);
@ -102,6 +103,7 @@ const mapStateToProps = (state: StateType, props: ExternalProps) => {
i18n: getIntl(state),
isDisabled,
messageCompositionId,
sendCounter,
theme: getTheme(state),
// AudioCapture