Use sentCounter in CompositionInput to drop old draft updates
This commit is contained in:
parent
0e606c45b0
commit
4a18667ddf
8 changed files with 121 additions and 63 deletions
|
@ -35,6 +35,7 @@ const useProps = (overrideProps: Partial<Props> = {}): Props => ({
|
||||||
addAttachment: action('addAttachment'),
|
addAttachment: action('addAttachment'),
|
||||||
conversationId: '123',
|
conversationId: '123',
|
||||||
focusCounter: 0,
|
focusCounter: 0,
|
||||||
|
sendCounter: 0,
|
||||||
i18n,
|
i18n,
|
||||||
isDisabled: false,
|
isDisabled: false,
|
||||||
messageCompositionId: '456',
|
messageCompositionId: '456',
|
||||||
|
|
|
@ -162,14 +162,15 @@ export type OwnProps = Readonly<{
|
||||||
|
|
||||||
export type Props = Pick<
|
export type Props = Pick<
|
||||||
CompositionInputProps,
|
CompositionInputProps,
|
||||||
| 'sortedGroupMembers'
|
| 'clearQuotedMessage'
|
||||||
| 'onEditorStateChange'
|
|
||||||
| 'onTextTooLong'
|
|
||||||
| 'draftText'
|
| 'draftText'
|
||||||
| 'draftBodyRanges'
|
| 'draftBodyRanges'
|
||||||
| 'clearQuotedMessage'
|
|
||||||
| 'getPreferredBadge'
|
| 'getPreferredBadge'
|
||||||
| 'getQuotedMessage'
|
| 'getQuotedMessage'
|
||||||
|
| 'onEditorStateChange'
|
||||||
|
| 'onTextTooLong'
|
||||||
|
| 'sendCounter'
|
||||||
|
| 'sortedGroupMembers'
|
||||||
> &
|
> &
|
||||||
Pick<
|
Pick<
|
||||||
EmojiButtonProps,
|
EmojiButtonProps,
|
||||||
|
@ -231,13 +232,14 @@ export function CompositionArea({
|
||||||
setMediaQualitySetting,
|
setMediaQualitySetting,
|
||||||
shouldSendHighQualityAttachments,
|
shouldSendHighQualityAttachments,
|
||||||
// CompositionInput
|
// CompositionInput
|
||||||
onEditorStateChange,
|
clearQuotedMessage,
|
||||||
onTextTooLong,
|
|
||||||
draftText,
|
draftText,
|
||||||
draftBodyRanges,
|
draftBodyRanges,
|
||||||
clearQuotedMessage,
|
|
||||||
getPreferredBadge,
|
getPreferredBadge,
|
||||||
getQuotedMessage,
|
getQuotedMessage,
|
||||||
|
onEditorStateChange,
|
||||||
|
onTextTooLong,
|
||||||
|
sendCounter,
|
||||||
sortedGroupMembers,
|
sortedGroupMembers,
|
||||||
// EmojiButton
|
// EmojiButton
|
||||||
onPickEmoji,
|
onPickEmoji,
|
||||||
|
@ -805,6 +807,7 @@ export function CompositionArea({
|
||||||
onPickEmoji={onPickEmoji}
|
onPickEmoji={onPickEmoji}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
onTextTooLong={onTextTooLong}
|
onTextTooLong={onTextTooLong}
|
||||||
|
sendCounter={sendCounter}
|
||||||
skinTone={skinTone}
|
skinTone={skinTone}
|
||||||
sortedGroupMembers={sortedGroupMembers}
|
sortedGroupMembers={sortedGroupMembers}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
|
|
|
@ -33,6 +33,7 @@ const useProps = (overrideProps: Partial<Props> = {}): Props => ({
|
||||||
getQuotedMessage: action('getQuotedMessage'),
|
getQuotedMessage: action('getQuotedMessage'),
|
||||||
onPickEmoji: action('onPickEmoji'),
|
onPickEmoji: action('onPickEmoji'),
|
||||||
large: boolean('large', overrideProps.large || false),
|
large: boolean('large', overrideProps.large || false),
|
||||||
|
sendCounter: 0,
|
||||||
sortedGroupMembers: overrideProps.sortedGroupMembers || [],
|
sortedGroupMembers: overrideProps.sortedGroupMembers || [],
|
||||||
skinTone: select(
|
skinTone: select(
|
||||||
'skinTone',
|
'skinTone',
|
||||||
|
|
|
@ -79,6 +79,7 @@ export type Props = Readonly<{
|
||||||
getPreferredBadge: PreferredBadgeSelectorType;
|
getPreferredBadge: PreferredBadgeSelectorType;
|
||||||
large?: boolean;
|
large?: boolean;
|
||||||
inputApi?: React.MutableRefObject<InputApi | undefined>;
|
inputApi?: React.MutableRefObject<InputApi | undefined>;
|
||||||
|
sendCounter: number;
|
||||||
skinTone?: EmojiPickDataType['skinTone'];
|
skinTone?: EmojiPickDataType['skinTone'];
|
||||||
draftText?: string;
|
draftText?: string;
|
||||||
draftBodyRanges?: DraftBodyRangesType;
|
draftBodyRanges?: DraftBodyRangesType;
|
||||||
|
@ -88,12 +89,13 @@ export type Props = Readonly<{
|
||||||
sortedGroupMembers?: ReadonlyArray<ConversationType>;
|
sortedGroupMembers?: ReadonlyArray<ConversationType>;
|
||||||
scrollerRef?: React.RefObject<HTMLDivElement>;
|
scrollerRef?: React.RefObject<HTMLDivElement>;
|
||||||
onDirtyChange?(dirty: boolean): unknown;
|
onDirtyChange?(dirty: boolean): unknown;
|
||||||
onEditorStateChange?(
|
onEditorStateChange?(options: {
|
||||||
conversationId: string | undefined,
|
bodyRanges: DraftBodyRangesType;
|
||||||
messageText: string,
|
caretLocation?: number;
|
||||||
bodyRanges: DraftBodyRangesType,
|
conversationId: string | undefined;
|
||||||
caretLocation?: number
|
messageText: string;
|
||||||
): unknown;
|
sendCounter: number;
|
||||||
|
}): unknown;
|
||||||
onTextTooLong(): unknown;
|
onTextTooLong(): unknown;
|
||||||
onPickEmoji(o: EmojiPickDataType): unknown;
|
onPickEmoji(o: EmojiPickDataType): unknown;
|
||||||
onSubmit(
|
onSubmit(
|
||||||
|
@ -134,6 +136,7 @@ export function CompositionInput(props: Props): React.ReactElement {
|
||||||
onSubmit,
|
onSubmit,
|
||||||
placeholder,
|
placeholder,
|
||||||
skinTone,
|
skinTone,
|
||||||
|
sendCounter,
|
||||||
sortedGroupMembers,
|
sortedGroupMembers,
|
||||||
theme,
|
theme,
|
||||||
} = props;
|
} = props;
|
||||||
|
@ -458,12 +461,13 @@ export function CompositionInput(props: Props): React.ReactElement {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const selection = quill.getSelection();
|
const selection = quill.getSelection();
|
||||||
|
|
||||||
onEditorStateChange(
|
onEditorStateChange({
|
||||||
|
bodyRanges: mentions,
|
||||||
|
caretLocation: selection ? selection.index : undefined,
|
||||||
conversationId,
|
conversationId,
|
||||||
text,
|
messageText: text,
|
||||||
mentions,
|
sendCounter,
|
||||||
selection ? selection.index : undefined
|
});
|
||||||
);
|
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,12 +86,7 @@ export function CompositionTextArea({
|
||||||
}, [inputApiRef]);
|
}, [inputApiRef]);
|
||||||
|
|
||||||
const handleChange = React.useCallback(
|
const handleChange = React.useCallback(
|
||||||
(
|
({ bodyRanges, caretLocation, messageText: newValue }) => {
|
||||||
_conversationId: string | undefined,
|
|
||||||
newValue: string,
|
|
||||||
bodyRanges: DraftBodyRangesType,
|
|
||||||
caretLocation?: number | undefined
|
|
||||||
) => {
|
|
||||||
const inputEl = inputApiRef.current;
|
const inputEl = inputApiRef.current;
|
||||||
if (!inputEl) {
|
if (!inputEl) {
|
||||||
return;
|
return;
|
||||||
|
@ -124,21 +119,22 @@ export function CompositionTextArea({
|
||||||
return (
|
return (
|
||||||
<div className="CompositionTextArea">
|
<div className="CompositionTextArea">
|
||||||
<CompositionInput
|
<CompositionInput
|
||||||
placeholder={placeholder}
|
|
||||||
clearQuotedMessage={shouldNeverBeCalled}
|
clearQuotedMessage={shouldNeverBeCalled}
|
||||||
scrollerRef={scrollerRef}
|
draftText={draftText}
|
||||||
getPreferredBadge={getPreferredBadge}
|
getPreferredBadge={getPreferredBadge}
|
||||||
getQuotedMessage={noop}
|
getQuotedMessage={noop}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
inputApi={inputApiRef}
|
inputApi={inputApiRef}
|
||||||
large
|
large
|
||||||
moduleClassName="CompositionTextArea__input"
|
moduleClassName="CompositionTextArea__input"
|
||||||
onScroll={onScroll}
|
|
||||||
onEditorStateChange={handleChange}
|
onEditorStateChange={handleChange}
|
||||||
onPickEmoji={onPickEmoji}
|
onPickEmoji={onPickEmoji}
|
||||||
|
onScroll={onScroll}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
onTextTooLong={onTextTooLong}
|
onTextTooLong={onTextTooLong}
|
||||||
draftText={draftText}
|
placeholder={placeholder}
|
||||||
|
scrollerRef={scrollerRef}
|
||||||
|
sendCounter={0}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
/>
|
/>
|
||||||
<div className="CompositionTextArea__emoji">
|
<div className="CompositionTextArea__emoji">
|
||||||
|
|
|
@ -225,7 +225,7 @@ export function StoryViewsNRepliesModal({
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
inputApi={inputApiRef}
|
inputApi={inputApiRef}
|
||||||
moduleClassName="StoryViewsNRepliesModal__input"
|
moduleClassName="StoryViewsNRepliesModal__input"
|
||||||
onEditorStateChange={(_conversationId, messageText) => {
|
onEditorStateChange={({ messageText }) => {
|
||||||
setMessageBodyText(messageText);
|
setMessageBodyText(messageText);
|
||||||
}}
|
}}
|
||||||
onPickEmoji={onUseEmoji}
|
onPickEmoji={onUseEmoji}
|
||||||
|
@ -242,6 +242,7 @@ export function StoryViewsNRepliesModal({
|
||||||
firstName: authorTitle,
|
firstName: authorTitle,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
sendCounter={0}
|
||||||
sortedGroupMembers={sortedGroupMembers}
|
sortedGroupMembers={sortedGroupMembers}
|
||||||
theme={ThemeType.dark}
|
theme={ThemeType.dark}
|
||||||
>
|
>
|
||||||
|
|
|
@ -100,6 +100,7 @@ type ComposerStateByConversationType = {
|
||||||
linkPreviewResult?: LinkPreviewType;
|
linkPreviewResult?: LinkPreviewType;
|
||||||
messageCompositionId: UUIDStringType;
|
messageCompositionId: UUIDStringType;
|
||||||
quotedMessage?: Pick<MessageAttributesType, 'conversationId' | 'quote'>;
|
quotedMessage?: Pick<MessageAttributesType, 'conversationId' | 'quote'>;
|
||||||
|
sendCounter: number;
|
||||||
shouldSendHighQualityAttachments?: boolean;
|
shouldSendHighQualityAttachments?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -121,6 +122,7 @@ function getEmptyComposerState(): ComposerStateByConversationType {
|
||||||
isDisabled: false,
|
isDisabled: false,
|
||||||
linkPreviewLoading: false,
|
linkPreviewLoading: false,
|
||||||
messageCompositionId: UUID.generate().toString(),
|
messageCompositionId: UUID.generate().toString(),
|
||||||
|
sendCounter: 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,6 +136,7 @@ export function getComposerStateForConversation(
|
||||||
// Actions
|
// Actions
|
||||||
|
|
||||||
const ADD_PENDING_ATTACHMENT = 'composer/ADD_PENDING_ATTACHMENT';
|
const ADD_PENDING_ATTACHMENT = 'composer/ADD_PENDING_ATTACHMENT';
|
||||||
|
const INCREMENT_SEND_COUNTER = 'composer/INCREMENT_SEND_COUNTER';
|
||||||
const REPLACE_ATTACHMENTS = 'composer/REPLACE_ATTACHMENTS';
|
const REPLACE_ATTACHMENTS = 'composer/REPLACE_ATTACHMENTS';
|
||||||
const RESET_COMPOSER = 'composer/RESET_COMPOSER';
|
const RESET_COMPOSER = 'composer/RESET_COMPOSER';
|
||||||
const SET_FOCUS = 'composer/SET_FOCUS';
|
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
|
// eslint-disable-next-line local-rules/type-alias-readonlydeep
|
||||||
export type ReplaceAttachmentsActionType = {
|
export type ReplaceAttachmentsActionType = {
|
||||||
type: typeof REPLACE_ATTACHMENTS;
|
type: typeof REPLACE_ATTACHMENTS;
|
||||||
|
@ -202,6 +212,7 @@ type ComposerActionType =
|
||||||
| AddLinkPreviewActionType
|
| AddLinkPreviewActionType
|
||||||
| AddPendingAttachmentActionType
|
| AddPendingAttachmentActionType
|
||||||
| ConversationUnloadedActionType
|
| ConversationUnloadedActionType
|
||||||
|
| IncrementSendActionType
|
||||||
| RemoveLinkPreviewActionType
|
| RemoveLinkPreviewActionType
|
||||||
| ReplaceAttachmentsActionType
|
| ReplaceAttachmentsActionType
|
||||||
| ResetComposerActionType
|
| ResetComposerActionType
|
||||||
|
@ -217,6 +228,7 @@ export const actions = {
|
||||||
addAttachment,
|
addAttachment,
|
||||||
addPendingAttachment,
|
addPendingAttachment,
|
||||||
cancelJoinRequest,
|
cancelJoinRequest,
|
||||||
|
incrementSendCounter,
|
||||||
onClearAttachments,
|
onClearAttachments,
|
||||||
onCloseLinkPreview,
|
onCloseLinkPreview,
|
||||||
onEditorStateChange,
|
onEditorStateChange,
|
||||||
|
@ -236,6 +248,15 @@ export const actions = {
|
||||||
setQuotedMessage,
|
setQuotedMessage,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function incrementSendCounter(conversationId: string): IncrementSendActionType {
|
||||||
|
return {
|
||||||
|
type: INCREMENT_SEND_COUNTER,
|
||||||
|
payload: {
|
||||||
|
conversationId,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export const useComposerActions = (): BoundActionCreatorsMapObject<
|
export const useComposerActions = (): BoundActionCreatorsMapObject<
|
||||||
typeof actions
|
typeof actions
|
||||||
> => useBoundActions(actions);
|
> => useBoundActions(actions);
|
||||||
|
@ -370,6 +391,7 @@ function sendMultiMediaMessage(
|
||||||
void,
|
void,
|
||||||
RootStateType,
|
RootStateType,
|
||||||
unknown,
|
unknown,
|
||||||
|
| IncrementSendActionType
|
||||||
| NoopActionType
|
| NoopActionType
|
||||||
| ResetComposerActionType
|
| ResetComposerActionType
|
||||||
| SetComposerDisabledStateActionType
|
| SetComposerDisabledStateActionType
|
||||||
|
@ -495,7 +517,7 @@ function sendMultiMediaMessage(
|
||||||
getState,
|
getState,
|
||||||
undefined
|
undefined
|
||||||
);
|
);
|
||||||
dispatch(resetComposer(conversationId));
|
dispatch(incrementSendCounter(conversationId));
|
||||||
dispatch(setComposerDisabledState(conversationId, false));
|
dispatch(setComposerDisabledState(conversationId, false));
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -787,44 +809,66 @@ export function setComposerFocus(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function onEditorStateChange(
|
function onEditorStateChange({
|
||||||
conversationId: string | undefined,
|
bodyRanges,
|
||||||
messageText: string,
|
caretLocation,
|
||||||
bodyRanges: DraftBodyRangesType,
|
conversationId,
|
||||||
caretLocation?: number
|
messageText,
|
||||||
): NoopActionType {
|
sendCounter,
|
||||||
if (!conversationId) {
|
}: {
|
||||||
throw new Error(
|
bodyRanges: DraftBodyRangesType;
|
||||||
'onEditorStateChange: Got falsey conversationId, needs local override'
|
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);
|
const conversation = window.ConversationController.get(conversationId);
|
||||||
if (!conversation) {
|
if (!conversation) {
|
||||||
throw new Error('processAttachments: Unable to find conversation');
|
throw new Error('processAttachments: Unable to find conversation');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (messageText.length && conversation.throttledBumpTyping) {
|
const state = getState().composer.conversations[conversationId];
|
||||||
conversation.throttledBumpTyping();
|
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 (messageText.length && conversation.throttledBumpTyping) {
|
||||||
if (
|
conversation.throttledBumpTyping();
|
||||||
!hasDraftAttachments(conversation.attributes.draftAttachments, {
|
}
|
||||||
includePending: true,
|
|
||||||
})
|
debouncedSaveDraft(conversationId, messageText, bodyRanges);
|
||||||
) {
|
|
||||||
maybeGrabLinkPreview(messageText, LinkPreviewSourceType.Composer, {
|
// If we have attachments, don't add link preview
|
||||||
caretLocation,
|
if (
|
||||||
conversationId,
|
!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) {
|
if (action.type === SET_FOCUS) {
|
||||||
return updateComposerState(state, action, prevState => ({
|
return updateComposerState(state, action, prevState => ({
|
||||||
focusCounter: prevState.focusCounter + 1,
|
focusCounter: prevState.focusCounter + 1,
|
||||||
|
|
|
@ -86,6 +86,7 @@ const mapStateToProps = (state: StateType, props: ExternalProps) => {
|
||||||
linkPreviewResult,
|
linkPreviewResult,
|
||||||
messageCompositionId,
|
messageCompositionId,
|
||||||
quotedMessage,
|
quotedMessage,
|
||||||
|
sendCounter,
|
||||||
shouldSendHighQualityAttachments,
|
shouldSendHighQualityAttachments,
|
||||||
} = composerStateForConversationIdSelector(id);
|
} = composerStateForConversationIdSelector(id);
|
||||||
|
|
||||||
|
@ -102,6 +103,7 @@ const mapStateToProps = (state: StateType, props: ExternalProps) => {
|
||||||
i18n: getIntl(state),
|
i18n: getIntl(state),
|
||||||
isDisabled,
|
isDisabled,
|
||||||
messageCompositionId,
|
messageCompositionId,
|
||||||
|
sendCounter,
|
||||||
theme: getTheme(state),
|
theme: getTheme(state),
|
||||||
|
|
||||||
// AudioCapture
|
// AudioCapture
|
||||||
|
|
Loading…
Add table
Reference in a new issue