Skip to content

Commit 50c1db8

Browse files
authored
Merge pull request #49228 from dominictb/fix/46766
fix: pasting long text freezes app
2 parents bf38806 + 6d245d7 commit 50c1db8

File tree

3 files changed

+53
-31
lines changed

3 files changed

+53
-31
lines changed

src/components/RNMarkdownTextInput.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@ import React from 'react';
55
import type {TextInput} from 'react-native';
66
import Animated from 'react-native-reanimated';
77
import useTheme from '@hooks/useTheme';
8+
import CONST from '@src/CONST';
89

910
// Convert the underlying TextInput into an Animated component so that we can take an animated ref and pass it to a worklet
1011
const AnimatedMarkdownTextInput = Animated.createAnimatedComponent(MarkdownTextInput);
1112

1213
type AnimatedMarkdownTextInputRef = typeof AnimatedMarkdownTextInput & TextInput & HTMLInputElement;
1314

14-
function RNMarkdownTextInputWithRef(props: MarkdownTextInputProps, ref: ForwardedRef<AnimatedMarkdownTextInputRef>) {
15+
function RNMarkdownTextInputWithRef({maxLength, ...props}: MarkdownTextInputProps, ref: ForwardedRef<AnimatedMarkdownTextInputRef>) {
1516
const theme = useTheme();
1617

1718
return (
@@ -27,6 +28,10 @@ function RNMarkdownTextInputWithRef(props: MarkdownTextInputProps, ref: Forwarde
2728
}}
2829
// eslint-disable-next-line
2930
{...props}
31+
/**
32+
* If maxLength is not set, we should set the it to CONST.MAX_COMMENT_LENGTH + 1, to avoid parsing markdown for large text
33+
*/
34+
maxLength={maxLength ?? CONST.MAX_COMMENT_LENGTH + 1}
3035
/>
3136
);
3237
}

src/hooks/useHtmlPaste/index.ts

Lines changed: 46 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,30 @@
11
import {useNavigation} from '@react-navigation/native';
22
import {useCallback, useEffect} from 'react';
33
import Parser from '@libs/Parser';
4+
import CONST from '@src/CONST';
45
import type UseHtmlPaste from './types';
56

6-
const insertByCommand = (text: string) => {
7-
document.execCommand('insertText', false, text);
8-
};
7+
const insertAtCaret = (target: HTMLElement, insertedText: string, maxLength: number) => {
8+
const currentText = target.textContent ?? '';
9+
10+
let availableLength = maxLength - currentText.length;
11+
if (availableLength <= 0) {
12+
return;
13+
}
14+
15+
let text = insertedText;
916

10-
const insertAtCaret = (target: HTMLElement, text: string) => {
1117
const selection = window.getSelection();
1218
if (selection?.rangeCount) {
1319
const range = selection.getRangeAt(0);
20+
const selectedText = range.toString();
21+
availableLength -= selectedText.length;
22+
if (availableLength <= 0) {
23+
return;
24+
}
25+
text = text.slice(0, availableLength);
1426
range.deleteContents();
27+
1528
const node = document.createTextNode(text);
1629
range.insertNode(node);
1730

@@ -22,40 +35,43 @@ const insertAtCaret = (target: HTMLElement, text: string) => {
2235

2336
// Dispatch input event to trigger Markdown Input to parse the new text
2437
target.dispatchEvent(new Event('input', {bubbles: true}));
25-
} else {
26-
insertByCommand(text);
2738
}
2839
};
2940

30-
const useHtmlPaste: UseHtmlPaste = (textInputRef, preHtmlPasteCallback, removeListenerOnScreenBlur = false) => {
41+
const useHtmlPaste: UseHtmlPaste = (textInputRef, preHtmlPasteCallback, removeListenerOnScreenBlur = false, maxLength = CONST.MAX_COMMENT_LENGTH + 1) => {
3142
const navigation = useNavigation();
3243

3344
/**
3445
* Set pasted text to clipboard
3546
* @param {String} text
3647
*/
37-
const paste = useCallback((text: string) => {
38-
try {
39-
const textInputHTMLElement = textInputRef.current as HTMLElement;
40-
if (textInputHTMLElement?.hasAttribute('contenteditable')) {
41-
insertAtCaret(textInputHTMLElement, text);
42-
} else {
43-
insertByCommand(text);
44-
}
48+
const paste = useCallback(
49+
(text: string) => {
50+
try {
51+
const textInputHTMLElement = textInputRef.current as HTMLElement;
52+
if (textInputHTMLElement?.hasAttribute('contenteditable')) {
53+
insertAtCaret(textInputHTMLElement, text, maxLength);
54+
} else {
55+
const htmlInput = textInputRef.current as unknown as HTMLInputElement;
56+
const availableLength = maxLength - (htmlInput.value?.length ?? 0);
57+
htmlInput.setRangeText(text.slice(0, availableLength));
58+
}
4559

46-
// Pointer will go out of sight when a large paragraph is pasted on the web. Refocusing the input keeps the cursor in view.
47-
// To avoid the keyboard toggle issue in mWeb if using blur() and focus() functions, we just need to dispatch the event to trigger the onFocus handler
48-
// We need to trigger the bubbled "focusin" event to make sure the onFocus handler is triggered
49-
textInputHTMLElement.dispatchEvent(
50-
new FocusEvent('focusin', {
51-
bubbles: true,
52-
}),
53-
);
54-
// eslint-disable-next-line no-empty
55-
} catch (e) {}
56-
// We only need to set the callback once.
57-
// eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps
58-
}, []);
60+
// Pointer will go out of sight when a large paragraph is pasted on the web. Refocusing the input keeps the cursor in view.
61+
// To avoid the keyboard toggle issue in mWeb if using blur() and focus() functions, we just need to dispatch the event to trigger the onFocus handler
62+
// We need to trigger the bubbled "focusin" event to make sure the onFocus handler is triggered
63+
textInputHTMLElement.dispatchEvent(
64+
new FocusEvent('focusin', {
65+
bubbles: true,
66+
}),
67+
);
68+
// eslint-disable-next-line no-empty
69+
} catch (e) {}
70+
// We only need to set the callback once.
71+
// eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps
72+
},
73+
[maxLength, textInputRef],
74+
);
5975

6076
/**
6177
* Manually place the pasted HTML into Composer
@@ -64,9 +80,9 @@ const useHtmlPaste: UseHtmlPaste = (textInputRef, preHtmlPasteCallback, removeLi
6480
*/
6581
const handlePastedHTML = useCallback(
6682
(html: string) => {
67-
paste(Parser.htmlToMarkdown(html));
83+
paste(Parser.htmlToMarkdown(html.slice(0, maxLength)));
6884
},
69-
[paste],
85+
[paste, maxLength],
7086
);
7187

7288
/**

src/hooks/useHtmlPaste/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ type UseHtmlPaste = (
55
textInputRef: MutableRefObject<(HTMLTextAreaElement & TextInput) | TextInput | null>,
66
preHtmlPasteCallback?: (event: ClipboardEvent) => boolean,
77
removeListenerOnScreenBlur?: boolean,
8+
maxLength?: number, // Maximum length of the text input value after pasting
89
) => void;
910

1011
export default UseHtmlPaste;

0 commit comments

Comments
 (0)