Draft: Fix conflict between with two active clients (#3817)
This commit is contained in:
parent
369b0ac9ce
commit
c43c95d3f1
@ -655,7 +655,15 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
chatBotCommands,
|
||||
);
|
||||
|
||||
useDraft(draft, chatId, threadId, getHtml, setHtml, editingMessage, isInStoryViewer);
|
||||
useDraft({
|
||||
draft,
|
||||
chatId,
|
||||
threadId,
|
||||
getHtml,
|
||||
setHtml,
|
||||
editedMessage: editingMessage,
|
||||
isDisabled: isInStoryViewer,
|
||||
});
|
||||
|
||||
const resetComposer = useLastCallback((shouldPreserveInput = false) => {
|
||||
if (!shouldPreserveInput) {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { useEffect } from '../../../../lib/teact/teact';
|
||||
import { useEffect, useRef } from '../../../../lib/teact/teact';
|
||||
import { requestMeasure, requestNextMutation } from '../../../../lib/fasterdom/fasterdom';
|
||||
import { getActions } from '../../../../global';
|
||||
|
||||
@ -17,8 +17,8 @@ import useLastCallback from '../../../../hooks/useLastCallback';
|
||||
import useBackgroundMode from '../../../../hooks/useBackgroundMode';
|
||||
import useBeforeUnload from '../../../../hooks/useBeforeUnload';
|
||||
import { useStateRef } from '../../../../hooks/useStateRef';
|
||||
import useEffectWithPrevDeps from '../../../../hooks/useEffectWithPrevDeps';
|
||||
import useRunDebounced from '../../../../hooks/useRunDebounced';
|
||||
import useLayoutEffectWithPrevDeps from '../../../../hooks/useLayoutEffectWithPrevDeps';
|
||||
|
||||
let isFrozen = false;
|
||||
|
||||
@ -30,21 +30,44 @@ function freeze() {
|
||||
});
|
||||
}
|
||||
|
||||
const useDraft = (
|
||||
draft: ApiDraft | undefined,
|
||||
chatId: string,
|
||||
threadId: number,
|
||||
getHtml: Signal<string>,
|
||||
setHtml: (html: string) => void,
|
||||
editedMessage: ApiMessage | undefined,
|
||||
isDisabled: boolean | undefined,
|
||||
) => {
|
||||
const useDraft = ({
|
||||
draft,
|
||||
chatId,
|
||||
threadId,
|
||||
getHtml,
|
||||
setHtml,
|
||||
editedMessage,
|
||||
isDisabled,
|
||||
} : {
|
||||
draft?: ApiDraft;
|
||||
chatId: string;
|
||||
threadId: number;
|
||||
getHtml: Signal<string>;
|
||||
setHtml: (html: string) => void;
|
||||
editedMessage?: ApiMessage;
|
||||
isDisabled?: boolean;
|
||||
}) => {
|
||||
const { saveDraft, clearDraft, loadCustomEmojis } = getActions();
|
||||
|
||||
const isTouchedRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
const html = getHtml();
|
||||
const isLocalDraft = draft?.isLocal !== undefined;
|
||||
if (getTextWithEntitiesAsHtml(draft) === html && !isLocalDraft) {
|
||||
isTouchedRef.current = false;
|
||||
} else {
|
||||
isTouchedRef.current = true;
|
||||
}
|
||||
}, [draft, getHtml]);
|
||||
useEffect(() => {
|
||||
isTouchedRef.current = false;
|
||||
}, [chatId, threadId]);
|
||||
|
||||
const isEditing = Boolean(editedMessage);
|
||||
|
||||
const updateDraft = useLastCallback((prevState: { chatId?: string; threadId?: number } = {}, shouldForce = false) => {
|
||||
if (isDisabled || isEditing) return;
|
||||
const updateDraft = useLastCallback((prevState: { chatId?: string; threadId?: number } = {}) => {
|
||||
if (isDisabled || isEditing || !isTouchedRef.current) return;
|
||||
|
||||
const html = getHtml();
|
||||
|
||||
@ -53,34 +76,31 @@ const useDraft = (
|
||||
chatId: prevState.chatId ?? chatId,
|
||||
threadId: prevState.threadId ?? threadId,
|
||||
draft: parseMessageInput(html),
|
||||
shouldForce,
|
||||
});
|
||||
} else {
|
||||
clearDraft({
|
||||
chatId: prevState.chatId ?? chatId,
|
||||
threadId: prevState.threadId ?? threadId,
|
||||
shouldForce,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const updateDraftRef = useStateRef(updateDraft);
|
||||
const runDebouncedForSaveDraft = useRunDebounced(DRAFT_DEBOUNCE, true, undefined, [chatId, threadId]);
|
||||
|
||||
// Restore draft on chat change
|
||||
useEffectWithPrevDeps(([prevChatId, prevThreadId, prevDraft]) => {
|
||||
useLayoutEffectWithPrevDeps(([prevChatId, prevThreadId, prevDraft]) => {
|
||||
if (isDisabled) {
|
||||
return;
|
||||
}
|
||||
const isTouched = isTouchedRef.current;
|
||||
|
||||
if (chatId === prevChatId && threadId === prevThreadId) {
|
||||
if (isTouched && !draft) return; // Prevent reset from other client if we have local edits
|
||||
if (!draft && prevDraft) {
|
||||
setHtml('');
|
||||
}
|
||||
|
||||
if (!draft?.shouldForce) {
|
||||
return;
|
||||
}
|
||||
if (isTouched) return;
|
||||
}
|
||||
|
||||
if (editedMessage || !draft) {
|
||||
@ -102,7 +122,7 @@ const useDraft = (
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [chatId, threadId, draft, setHtml, editedMessage, loadCustomEmojis, isDisabled]);
|
||||
}, [chatId, threadId, draft, getHtml, setHtml, editedMessage, isDisabled]);
|
||||
|
||||
// Save draft on chat change
|
||||
useEffect(() => {
|
||||
@ -111,15 +131,13 @@ const useDraft = (
|
||||
}
|
||||
|
||||
return () => {
|
||||
// eslint-disable-next-line react-hooks-static-deps/exhaustive-deps
|
||||
if (!isEditing) {
|
||||
// eslint-disable-next-line react-hooks-static-deps/exhaustive-deps
|
||||
updateDraftRef.current({ chatId, threadId });
|
||||
updateDraft({ chatId, threadId });
|
||||
}
|
||||
|
||||
freeze();
|
||||
};
|
||||
}, [chatId, threadId, isEditing, updateDraftRef, isDisabled]);
|
||||
}, [chatId, threadId, isEditing, updateDraft, isDisabled]);
|
||||
|
||||
const chatIdRef = useStateRef(chatId);
|
||||
const threadIdRef = useStateRef(threadId);
|
||||
@ -129,27 +147,23 @@ const useDraft = (
|
||||
}
|
||||
|
||||
if (!getHtml()) {
|
||||
updateDraftRef.current();
|
||||
updateDraft();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const scopedShatId = chatIdRef.current;
|
||||
const scopedСhatId = chatIdRef.current;
|
||||
const scopedThreadId = threadIdRef.current;
|
||||
|
||||
runDebouncedForSaveDraft(() => {
|
||||
if (chatIdRef.current === scopedShatId && threadIdRef.current === scopedThreadId) {
|
||||
updateDraftRef.current();
|
||||
if (chatIdRef.current === scopedСhatId && threadIdRef.current === scopedThreadId) {
|
||||
updateDraft();
|
||||
}
|
||||
});
|
||||
}, [chatIdRef, getHtml, isDisabled, runDebouncedForSaveDraft, threadIdRef, updateDraftRef]);
|
||||
}, [chatIdRef, getHtml, isDisabled, runDebouncedForSaveDraft, threadIdRef, updateDraft]);
|
||||
|
||||
function forceUpdateDraft() {
|
||||
updateDraft(undefined, true);
|
||||
}
|
||||
|
||||
useBackgroundMode(forceUpdateDraft);
|
||||
useBeforeUnload(forceUpdateDraft);
|
||||
useBackgroundMode(updateDraft);
|
||||
useBeforeUnload(updateDraft);
|
||||
};
|
||||
|
||||
export default useDraft;
|
||||
|
||||
@ -4,7 +4,7 @@ import {
|
||||
} from '../../index';
|
||||
|
||||
import type {
|
||||
ActionReturnType, ApiDraft, GlobalState, TabArgs,
|
||||
ActionReturnType, GlobalState, TabArgs,
|
||||
} from '../../types';
|
||||
import type {
|
||||
ApiAttachment,
|
||||
@ -396,7 +396,7 @@ addActionHandler('cancelSendingMessage', (global, actions, payload): ActionRetur
|
||||
|
||||
addActionHandler('saveDraft', async (global, actions, payload): Promise<void> => {
|
||||
const {
|
||||
chatId, threadId, draft, shouldForce,
|
||||
chatId, threadId, draft,
|
||||
} = payload;
|
||||
if (!draft) {
|
||||
return;
|
||||
@ -408,7 +408,6 @@ addActionHandler('saveDraft', async (global, actions, payload): Promise<void> =>
|
||||
if (user && isDeletedUser(user)) return;
|
||||
|
||||
draft.isLocal = true;
|
||||
draft.shouldForce = shouldForce;
|
||||
global = replaceThreadParam(global, chatId, threadId, 'draft', draft);
|
||||
global = updateChat(global, chatId, { draftDate: Math.round(Date.now() / 1000) });
|
||||
|
||||
@ -435,7 +434,7 @@ addActionHandler('saveDraft', async (global, actions, payload): Promise<void> =>
|
||||
|
||||
addActionHandler('clearDraft', (global, actions, payload): ActionReturnType => {
|
||||
const {
|
||||
chatId, threadId = MAIN_THREAD_ID, localOnly, shouldForce,
|
||||
chatId, threadId = MAIN_THREAD_ID, localOnly,
|
||||
} = payload;
|
||||
if (!selectDraft(global, chatId, threadId)) {
|
||||
return undefined;
|
||||
@ -447,8 +446,7 @@ addActionHandler('clearDraft', (global, actions, payload): ActionReturnType => {
|
||||
void callApi('clearDraft', chat, selectThreadTopMessageId(global, chatId, threadId));
|
||||
}
|
||||
|
||||
const newDraft: ApiDraft | undefined = shouldForce ? { shouldForce, text: '' } : undefined;
|
||||
global = replaceThreadParam(global, chatId, threadId, 'draft', newDraft);
|
||||
global = replaceThreadParam(global, chatId, threadId, 'draft', undefined);
|
||||
global = updateChat(global, chatId, { draftDate: undefined });
|
||||
|
||||
return global;
|
||||
|
||||
@ -931,7 +931,7 @@ export type CallbackAction = Values<{
|
||||
}
|
||||
}>;
|
||||
|
||||
export type ApiDraft = ApiFormattedText & { isLocal?: boolean; shouldForce?: boolean };
|
||||
export type ApiDraft = ApiFormattedText & { isLocal?: boolean };
|
||||
|
||||
type WithTabId = { tabId?: number };
|
||||
|
||||
@ -1383,13 +1383,11 @@ export interface ActionPayloads {
|
||||
chatId: string;
|
||||
threadId: number;
|
||||
draft: ApiDraft;
|
||||
shouldForce?: boolean;
|
||||
};
|
||||
clearDraft: {
|
||||
chatId: string;
|
||||
threadId?: number;
|
||||
localOnly?: boolean;
|
||||
shouldForce?: boolean;
|
||||
};
|
||||
loadPinnedMessages: {
|
||||
chatId: string;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user