Composer: Don't insert images from clipboard if text is present (#3455)
This commit is contained in:
parent
f774cf5e43
commit
eb7840c251
@ -370,6 +370,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const [attachments, setAttachments] = useState<ApiAttachment[]>([]);
|
||||
const hasAttachments = Boolean(attachments.length);
|
||||
const [nextText, setNextText] = useState<ApiFormattedText | undefined>(undefined);
|
||||
|
||||
const {
|
||||
canSendStickers, canSendGifs, canAttachMedia, canAttachPolls, canAttachEmbedLinks,
|
||||
@ -378,6 +379,57 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const isComposerBlocked = !canSendPlainText && !editingMessage;
|
||||
|
||||
const insertHtmlAndUpdateCursor = useLastCallback((newHtml: string, inputId: string = EDITABLE_INPUT_ID) => {
|
||||
if (inputId === EDITABLE_INPUT_ID && isComposerBlocked) return;
|
||||
const selection = window.getSelection()!;
|
||||
let messageInput: HTMLDivElement;
|
||||
if (inputId === EDITABLE_INPUT_ID) {
|
||||
messageInput = document.querySelector<HTMLDivElement>(EDITABLE_INPUT_CSS_SELECTOR)!;
|
||||
} else {
|
||||
messageInput = document.getElementById(inputId) as HTMLDivElement;
|
||||
}
|
||||
|
||||
if (selection.rangeCount) {
|
||||
const selectionRange = selection.getRangeAt(0);
|
||||
if (isSelectionInsideInput(selectionRange, inputId)) {
|
||||
insertHtmlInSelection(newHtml);
|
||||
messageInput.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setHtml(`${getHtml()}${newHtml}`);
|
||||
|
||||
// If selection is outside of input, set cursor at the end of input
|
||||
requestNextMutation(() => {
|
||||
focusEditableElement(messageInput);
|
||||
});
|
||||
});
|
||||
|
||||
const insertTextAndUpdateCursor = useLastCallback((text: string, inputId: string = EDITABLE_INPUT_ID) => {
|
||||
const newHtml = renderText(text, ['escape_html', 'emoji_html', 'br_html'])
|
||||
.join('')
|
||||
.replace(/\u200b+/g, '\u200b');
|
||||
insertHtmlAndUpdateCursor(newHtml, inputId);
|
||||
});
|
||||
|
||||
const insertFormattedTextAndUpdateCursor = useLastCallback((
|
||||
text: ApiFormattedText, inputId: string = EDITABLE_INPUT_ID,
|
||||
) => {
|
||||
const newHtml = getTextWithEntitiesAsHtml(text);
|
||||
insertHtmlAndUpdateCursor(newHtml, inputId);
|
||||
});
|
||||
|
||||
const insertCustomEmojiAndUpdateCursor = useLastCallback((emoji: ApiSticker, inputId: string = EDITABLE_INPUT_ID) => {
|
||||
insertHtmlAndUpdateCursor(buildCustomEmojiHtml(emoji), inputId);
|
||||
});
|
||||
|
||||
const insertNextText = useLastCallback(() => {
|
||||
if (!nextText) return;
|
||||
insertFormattedTextAndUpdateCursor(nextText, EDITABLE_INPUT_ID);
|
||||
setNextText(undefined);
|
||||
});
|
||||
|
||||
const {
|
||||
shouldSuggestCompression,
|
||||
shouldForceCompression,
|
||||
@ -397,6 +449,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
canSendVideos,
|
||||
canSendPhotos,
|
||||
canSendDocuments,
|
||||
insertNextText,
|
||||
});
|
||||
|
||||
const [isBotKeyboardOpen, openBotKeyboard, closeBotKeyboard] = useFlag();
|
||||
@ -522,44 +575,6 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
chatBotCommands,
|
||||
);
|
||||
|
||||
const insertHtmlAndUpdateCursor = useLastCallback((newHtml: string, inputId: string = EDITABLE_INPUT_ID) => {
|
||||
if (inputId === EDITABLE_INPUT_ID && isComposerBlocked) return;
|
||||
const selection = window.getSelection()!;
|
||||
let messageInput: HTMLDivElement;
|
||||
if (inputId === EDITABLE_INPUT_ID) {
|
||||
messageInput = document.querySelector<HTMLDivElement>(EDITABLE_INPUT_CSS_SELECTOR)!;
|
||||
} else {
|
||||
messageInput = document.getElementById(inputId) as HTMLDivElement;
|
||||
}
|
||||
|
||||
if (selection.rangeCount) {
|
||||
const selectionRange = selection.getRangeAt(0);
|
||||
if (isSelectionInsideInput(selectionRange, inputId)) {
|
||||
insertHtmlInSelection(newHtml);
|
||||
messageInput.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setHtml(`${getHtml()}${newHtml}`);
|
||||
|
||||
// If selection is outside of input, set cursor at the end of input
|
||||
requestNextMutation(() => {
|
||||
focusEditableElement(messageInput);
|
||||
});
|
||||
});
|
||||
|
||||
const insertFormattedTextAndUpdateCursor = useLastCallback((
|
||||
text: ApiFormattedText, inputId: string = EDITABLE_INPUT_ID,
|
||||
) => {
|
||||
const newHtml = getTextWithEntitiesAsHtml(text);
|
||||
insertHtmlAndUpdateCursor(newHtml, inputId);
|
||||
});
|
||||
|
||||
const insertCustomEmojiAndUpdateCursor = useLastCallback((emoji: ApiSticker, inputId: string = EDITABLE_INPUT_ID) => {
|
||||
insertHtmlAndUpdateCursor(buildCustomEmojiHtml(emoji), inputId);
|
||||
});
|
||||
|
||||
useDraft(draft, chatId, threadId, getHtml, setHtml, editingMessage);
|
||||
|
||||
const resetComposer = useLastCallback((shouldPreserveInput = false) => {
|
||||
@ -568,6 +583,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
}
|
||||
|
||||
setAttachments(MEMO_EMPTY_ARRAY);
|
||||
setNextText(undefined);
|
||||
|
||||
closeEmojiTooltip();
|
||||
closeCustomEmojiTooltip();
|
||||
@ -662,6 +678,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
isForCurrentMessageList,
|
||||
insertFormattedTextAndUpdateCursor,
|
||||
handleSetAttachments,
|
||||
setNextText,
|
||||
editingMessage,
|
||||
!isCurrentUserPremium && !isChatWithSelf,
|
||||
showCustomEmojiPremiumNotification,
|
||||
@ -1090,13 +1107,6 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
}, MOBILE_KEYBOARD_HIDE_DELAY_MS);
|
||||
});
|
||||
|
||||
const insertTextAndUpdateCursor = useLastCallback((text: string, inputId: string = EDITABLE_INPUT_ID) => {
|
||||
const newHtml = renderText(text, ['escape_html', 'emoji_html', 'br_html'])
|
||||
.join('')
|
||||
.replace(/\u200b+/g, '\u200b');
|
||||
insertHtmlAndUpdateCursor(newHtml, inputId);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!isComposerBlocked) return;
|
||||
|
||||
|
||||
@ -23,6 +23,7 @@ export default function useAttachmentModal({
|
||||
canSendVideos,
|
||||
canSendPhotos,
|
||||
canSendDocuments,
|
||||
insertNextText,
|
||||
}: {
|
||||
attachments: ApiAttachment[];
|
||||
fileSizeLimit: number;
|
||||
@ -33,6 +34,7 @@ export default function useAttachmentModal({
|
||||
canSendVideos?: boolean;
|
||||
canSendPhotos?: boolean;
|
||||
canSendDocuments?: boolean;
|
||||
insertNextText: VoidFunction;
|
||||
}) {
|
||||
const { openLimitReachedModal, showAllowedMessageTypesNotification } = getActions();
|
||||
const [shouldForceAsFile, setShouldForceAsFile] = useState<boolean>(false);
|
||||
@ -41,6 +43,7 @@ export default function useAttachmentModal({
|
||||
|
||||
const handleClearAttachments = useLastCallback(() => {
|
||||
setAttachments(MEMO_EMPTY_ARRAY);
|
||||
insertNextText();
|
||||
});
|
||||
|
||||
const handleSetAttachments = useLastCallback(
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { StateHookSetter } from '../../../../lib/teact/teact';
|
||||
import { useEffect } from '../../../../lib/teact/teact';
|
||||
|
||||
import type { StateHookSetter } from '../../../../lib/teact/teact';
|
||||
import type { ApiAttachment, ApiFormattedText, ApiMessage } from '../../../../api/types';
|
||||
import { ApiMessageEntityTypes } from '../../../../api/types';
|
||||
|
||||
@ -14,6 +14,9 @@ import { containsCustomEmoji, stripCustomEmoji } from '../../../../global/helper
|
||||
const MAX_MESSAGE_LENGTH = 4096;
|
||||
|
||||
const STYLE_TAG_REGEX = /<style>(.*?)<\/style>/gs;
|
||||
const TYPE_HTML = 'text/html';
|
||||
const DOCUMENT_TYPE_WORD = 'urn:schemas-microsoft-com:office:word';
|
||||
const NAMESPACE_PREFIX_WORD = 'xmlns:w';
|
||||
|
||||
function preparePastedHtml(html: string) {
|
||||
let fragment = document.createElement('div');
|
||||
@ -67,6 +70,7 @@ const useClipboardPaste = (
|
||||
isActive: boolean,
|
||||
insertTextAndUpdateCursor: (text: ApiFormattedText, inputId?: string) => void,
|
||||
setAttachments: StateHookSetter<ApiAttachment[]>,
|
||||
setNextText: StateHookSetter<ApiFormattedText | undefined>,
|
||||
editedMessage: ApiMessage | undefined,
|
||||
shouldStripCustomEmoji?: boolean,
|
||||
onCustomEmojiStripped?: VoidFunction,
|
||||
@ -88,6 +92,7 @@ const useClipboardPaste = (
|
||||
|
||||
const pastedText = e.clipboardData.getData('text').substring(0, MAX_MESSAGE_LENGTH);
|
||||
const html = e.clipboardData.getData('text/html');
|
||||
|
||||
let pastedFormattedText = html ? parseMessageInput(
|
||||
preparePastedHtml(html), undefined, true,
|
||||
) : undefined;
|
||||
@ -109,17 +114,34 @@ const useClipboardPaste = (
|
||||
return;
|
||||
}
|
||||
|
||||
if (files?.length && !editedMessage) {
|
||||
const newAttachments = await Promise.all(files.map((file) => {
|
||||
const textToPaste = pastedFormattedText?.entities?.length ? pastedFormattedText : { text: pastedText };
|
||||
|
||||
let isWordDocument = false;
|
||||
try {
|
||||
const parser = new DOMParser();
|
||||
const parsedDocument = parser.parseFromString(html, TYPE_HTML);
|
||||
isWordDocument = parsedDocument.documentElement
|
||||
.getAttribute(NAMESPACE_PREFIX_WORD) === DOCUMENT_TYPE_WORD;
|
||||
} catch (err: any) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
const hasText = textToPaste && textToPaste.text;
|
||||
const shouldSetAttachments = files?.length && !editedMessage && !isWordDocument;
|
||||
|
||||
if (shouldSetAttachments) {
|
||||
const newAttachments = await Promise.all(files!.map((file) => {
|
||||
return buildAttachment(file.name, file);
|
||||
}));
|
||||
setAttachments((attachments) => attachments.concat(newAttachments));
|
||||
}
|
||||
|
||||
const textToPaste = pastedFormattedText?.entities?.length ? pastedFormattedText : { text: pastedText };
|
||||
|
||||
if (textToPaste) {
|
||||
insertTextAndUpdateCursor(textToPaste, input?.id);
|
||||
if (hasText) {
|
||||
if (shouldSetAttachments) {
|
||||
setNextText(textToPaste);
|
||||
} else {
|
||||
insertTextAndUpdateCursor(textToPaste, input?.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -130,6 +152,7 @@ const useClipboardPaste = (
|
||||
};
|
||||
}, [
|
||||
insertTextAndUpdateCursor, editedMessage, setAttachments, isActive, shouldStripCustomEmoji, onCustomEmojiStripped,
|
||||
setNextText,
|
||||
]);
|
||||
};
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user