Prevent >64k text in composition box; truncate too-large drafts
This commit is contained in:
		
					parent
					
						
							
								87ae65c852
							
						
					
				
			
			
				commit
				
					
						095cd884a2
					
				
			
		
					 5 changed files with 116 additions and 7 deletions
				
			
		| 
						 | 
				
			
			@ -29,6 +29,7 @@ import {
 | 
			
		|||
} from './emoji/lib';
 | 
			
		||||
import { LocalizerType } from '../types/Util';
 | 
			
		||||
 | 
			
		||||
const MAX_LENGTH = 64 * 1024;
 | 
			
		||||
const colonsRegex = /(?:^|\s):[a-z0-9-_+]+:?/gi;
 | 
			
		||||
const triggerEmojiRegex = /^(?:[-+]\d|[a-z]{2})/i;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -43,6 +44,7 @@ export type Props = {
 | 
			
		|||
  onDirtyChange?(dirty: boolean): unknown;
 | 
			
		||||
  onEditorStateChange?(messageText: string, caretLocation: number): unknown;
 | 
			
		||||
  onEditorSizeChange?(rect: ContentRect): unknown;
 | 
			
		||||
  onTextTooLong(): unknown;
 | 
			
		||||
  onPickEmoji(o: EmojiPickDataType): unknown;
 | 
			
		||||
  onSubmit(message: string): unknown;
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -78,6 +80,43 @@ function getTrimmedMatchAtIndex(str: string, index: number, pattern: RegExp) {
 | 
			
		|||
  return null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getLengthOfSelectedText(state: EditorState): number {
 | 
			
		||||
  const currentSelection = state.getSelection();
 | 
			
		||||
  let length = 0;
 | 
			
		||||
 | 
			
		||||
  const currentContent = state.getCurrentContent();
 | 
			
		||||
  const startKey = currentSelection.getStartKey();
 | 
			
		||||
  const endKey = currentSelection.getEndKey();
 | 
			
		||||
  const startBlock = currentContent.getBlockForKey(startKey);
 | 
			
		||||
  const isStartAndEndBlockAreTheSame = startKey === endKey;
 | 
			
		||||
  const startBlockTextLength = startBlock.getLength();
 | 
			
		||||
  const startSelectedTextLength =
 | 
			
		||||
    startBlockTextLength - currentSelection.getStartOffset();
 | 
			
		||||
  const endSelectedTextLength = currentSelection.getEndOffset();
 | 
			
		||||
  const keyAfterEnd = currentContent.getKeyAfter(endKey);
 | 
			
		||||
 | 
			
		||||
  if (isStartAndEndBlockAreTheSame) {
 | 
			
		||||
    length +=
 | 
			
		||||
      currentSelection.getEndOffset() - currentSelection.getStartOffset();
 | 
			
		||||
  } else {
 | 
			
		||||
    let currentKey = startKey;
 | 
			
		||||
 | 
			
		||||
    while (currentKey && currentKey !== keyAfterEnd) {
 | 
			
		||||
      if (currentKey === startKey) {
 | 
			
		||||
        length += startSelectedTextLength + 1;
 | 
			
		||||
      } else if (currentKey === endKey) {
 | 
			
		||||
        length += endSelectedTextLength;
 | 
			
		||||
      } else {
 | 
			
		||||
        length += currentContent.getBlockForKey(currentKey).getLength() + 1;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      currentKey = currentContent.getKeyAfter(currentKey);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return length;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getWordAtIndex(str: string, index: number) {
 | 
			
		||||
  const start = str
 | 
			
		||||
    .slice(0, index + 1)
 | 
			
		||||
| 
						 | 
				
			
			@ -172,6 +211,7 @@ export const CompositionInput = ({
 | 
			
		|||
  onDirtyChange,
 | 
			
		||||
  onEditorStateChange,
 | 
			
		||||
  onEditorSizeChange,
 | 
			
		||||
  onTextTooLong,
 | 
			
		||||
  onPickEmoji,
 | 
			
		||||
  onSubmit,
 | 
			
		||||
  skinTone,
 | 
			
		||||
| 
						 | 
				
			
			@ -298,6 +338,51 @@ export const CompositionInput = ({
 | 
			
		|||
    ]
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const handleBeforeInput = React.useCallback(
 | 
			
		||||
    (): DraftHandleValue => {
 | 
			
		||||
      if (!editorStateRef.current) {
 | 
			
		||||
        return 'not-handled';
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const editorState = editorStateRef.current;
 | 
			
		||||
      const plainText = editorState.getCurrentContent().getPlainText();
 | 
			
		||||
      const selectedTextLength = getLengthOfSelectedText(editorState);
 | 
			
		||||
 | 
			
		||||
      if (plainText.length - selectedTextLength > MAX_LENGTH - 1) {
 | 
			
		||||
        onTextTooLong();
 | 
			
		||||
 | 
			
		||||
        return 'handled';
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return 'not-handled';
 | 
			
		||||
    },
 | 
			
		||||
    [onTextTooLong, editorStateRef]
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const handlePastedText = React.useCallback(
 | 
			
		||||
    (pastedText: string): DraftHandleValue => {
 | 
			
		||||
      if (!editorStateRef.current) {
 | 
			
		||||
        return 'not-handled';
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const editorState = editorStateRef.current;
 | 
			
		||||
      const plainText = editorState.getCurrentContent().getPlainText();
 | 
			
		||||
      const selectedTextLength = getLengthOfSelectedText(editorState);
 | 
			
		||||
 | 
			
		||||
      if (
 | 
			
		||||
        plainText.length + pastedText.length - selectedTextLength >
 | 
			
		||||
        MAX_LENGTH
 | 
			
		||||
      ) {
 | 
			
		||||
        onTextTooLong();
 | 
			
		||||
 | 
			
		||||
        return 'handled';
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return 'not-handled';
 | 
			
		||||
    },
 | 
			
		||||
    [onTextTooLong, editorStateRef]
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const resetEditorState = React.useCallback(
 | 
			
		||||
    () => {
 | 
			
		||||
      const newEmptyState = EditorState.createEmpty(compositeDecorator);
 | 
			
		||||
| 
						 | 
				
			
			@ -694,6 +779,8 @@ export const CompositionInput = ({
 | 
			
		|||
                    onEscape={handleEscapeKey}
 | 
			
		||||
                    onTab={onTab}
 | 
			
		||||
                    handleKeyCommand={handleEditorCommand}
 | 
			
		||||
                    handleBeforeInput={handleBeforeInput}
 | 
			
		||||
                    handlePastedText={handlePastedText}
 | 
			
		||||
                    keyBindingFn={editorKeybindingFn}
 | 
			
		||||
                    spellCheck={true}
 | 
			
		||||
                    stripPastedStyles={true}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue