146 lines
4.5 KiB
TypeScript
146 lines
4.5 KiB
TypeScript
import { useCallback, useEffect } from '../../../../lib/teact/teact';
|
|
import { requestMeasure, requestNextMutation } from '../../../../lib/fasterdom/fasterdom';
|
|
import { getActions } from '../../../../global';
|
|
|
|
import type { ApiDraft } from '../../../../global/types';
|
|
import type { ApiMessage } from '../../../../api/types';
|
|
import type { Signal } from '../../../../util/signals';
|
|
|
|
import { ApiMessageEntityTypes } from '../../../../api/types';
|
|
import { DRAFT_DEBOUNCE, EDITABLE_INPUT_CSS_SELECTOR } from '../../../../config';
|
|
import { IS_TOUCH_ENV } from '../../../../util/windowEnvironment';
|
|
import focusEditableElement from '../../../../util/focusEditableElement';
|
|
import parseMessageInput from '../../../../util/parseMessageInput';
|
|
import { getTextWithEntitiesAsHtml } from '../../../common/helpers/renderTextWithEntities';
|
|
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';
|
|
|
|
let isFrozen = false;
|
|
|
|
function freeze() {
|
|
isFrozen = true;
|
|
|
|
requestMeasure(() => {
|
|
isFrozen = false;
|
|
});
|
|
}
|
|
|
|
const useDraft = (
|
|
draft: ApiDraft | undefined,
|
|
chatId: string,
|
|
threadId: number,
|
|
getHtml: Signal<string>,
|
|
setHtml: (html: string) => void,
|
|
editedMessage: ApiMessage | undefined,
|
|
lastSyncTime?: number,
|
|
) => {
|
|
const { saveDraft, clearDraft, loadCustomEmojis } = getActions();
|
|
|
|
const isEditing = Boolean(editedMessage);
|
|
|
|
const updateDraft = useCallback((prevState: { chatId?: string; threadId?: number } = {}, shouldForce = false) => {
|
|
if (isEditing || !lastSyncTime) return;
|
|
|
|
const html = getHtml();
|
|
|
|
if (html) {
|
|
saveDraft({
|
|
chatId: prevState.chatId ?? chatId,
|
|
threadId: prevState.threadId ?? threadId,
|
|
draft: parseMessageInput(html),
|
|
shouldForce,
|
|
});
|
|
} else {
|
|
clearDraft({
|
|
chatId: prevState.chatId ?? chatId,
|
|
threadId: prevState.threadId ?? threadId,
|
|
shouldForce,
|
|
});
|
|
}
|
|
}, [chatId, threadId, isEditing, lastSyncTime, getHtml, saveDraft, clearDraft]);
|
|
|
|
const forceUpdateDraft = useCallback(() => {
|
|
updateDraft(undefined, true);
|
|
}, [updateDraft]);
|
|
|
|
const updateDraftRef = useStateRef(updateDraft);
|
|
const runDebouncedForSaveDraft = useRunDebounced(DRAFT_DEBOUNCE, true, undefined, [chatId, threadId]);
|
|
|
|
// Restore draft on chat change
|
|
useEffectWithPrevDeps(([prevChatId, prevThreadId, prevDraft]) => {
|
|
if (chatId === prevChatId && threadId === prevThreadId) {
|
|
if (!draft && prevDraft) {
|
|
setHtml('');
|
|
}
|
|
|
|
if (!draft?.shouldForce) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (editedMessage || !draft) {
|
|
return;
|
|
}
|
|
|
|
setHtml(getTextWithEntitiesAsHtml(draft));
|
|
|
|
const customEmojiIds = draft.entities
|
|
?.map((entity) => entity.type === ApiMessageEntityTypes.CustomEmoji && entity.documentId)
|
|
.filter(Boolean) || [];
|
|
if (customEmojiIds.length) loadCustomEmojis({ ids: customEmojiIds });
|
|
|
|
if (!IS_TOUCH_ENV) {
|
|
requestNextMutation(() => {
|
|
const messageInput = document.querySelector<HTMLDivElement>(EDITABLE_INPUT_CSS_SELECTOR);
|
|
if (messageInput) {
|
|
focusEditableElement(messageInput, true);
|
|
}
|
|
});
|
|
}
|
|
}, [chatId, threadId, draft, setHtml, editedMessage, loadCustomEmojis]);
|
|
|
|
// Save draft on chat change
|
|
useEffect(() => {
|
|
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 });
|
|
}
|
|
|
|
freeze();
|
|
};
|
|
}, [chatId, threadId, isEditing, updateDraftRef]);
|
|
|
|
const chatIdRef = useStateRef(chatId);
|
|
const threadIdRef = useStateRef(threadId);
|
|
useEffect(() => {
|
|
if (isFrozen) {
|
|
return;
|
|
}
|
|
|
|
if (!getHtml()) {
|
|
updateDraftRef.current();
|
|
|
|
return;
|
|
}
|
|
|
|
const scopedShatId = chatIdRef.current;
|
|
const scopedThreadId = threadIdRef.current;
|
|
|
|
runDebouncedForSaveDraft(() => {
|
|
if (chatIdRef.current === scopedShatId && threadIdRef.current === scopedThreadId) {
|
|
updateDraftRef.current();
|
|
}
|
|
});
|
|
}, [chatIdRef, getHtml, runDebouncedForSaveDraft, threadIdRef, updateDraftRef]);
|
|
|
|
useBackgroundMode(forceUpdateDraft);
|
|
useBeforeUnload(forceUpdateDraft);
|
|
};
|
|
|
|
export default useDraft;
|