You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

175 regels
6.9 KiB

  1. /**
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. *
  7. * @format
  8. * @flow
  9. * @emails oncall+draft_js
  10. */
  11. 'use strict';
  12. import type { BlockMap } from "./BlockMap";
  13. import type DraftEditor from "./DraftEditor.react";
  14. import type { EntityMap } from "./EntityMap";
  15. const BlockMapBuilder = require("./BlockMapBuilder");
  16. const CharacterMetadata = require("./CharacterMetadata");
  17. const DataTransfer = require("fbjs/lib/DataTransfer");
  18. const DraftModifier = require("./DraftModifier");
  19. const DraftPasteProcessor = require("./DraftPasteProcessor");
  20. const EditorState = require("./EditorState");
  21. const RichTextEditorUtil = require("./RichTextEditorUtil");
  22. const getEntityKeyForSelection = require("./getEntityKeyForSelection");
  23. const getTextContentFromFiles = require("./getTextContentFromFiles");
  24. const isEventHandled = require("./isEventHandled");
  25. const splitTextIntoTextBlocks = require("./splitTextIntoTextBlocks");
  26. /**
  27. * Paste content.
  28. */
  29. function editOnPaste(editor: DraftEditor, e: SyntheticClipboardEvent<>): void {
  30. e.preventDefault();
  31. const data = new DataTransfer(e.clipboardData); // Get files, unless this is likely to be a string the user wants inline.
  32. if (!data.isRichText()) {
  33. const files: Array<Blob> = (data.getFiles(): any);
  34. const defaultFileText = data.getText();
  35. if (files.length > 0) {
  36. // Allow customized paste handling for images, etc. Otherwise, fall
  37. // through to insert text contents into the editor.
  38. if (editor.props.handlePastedFiles && isEventHandled(editor.props.handlePastedFiles(files))) {
  39. return;
  40. }
  41. /* $FlowFixMe This comment suppresses an error found DataTransfer was
  42. * typed. getFiles() returns an array of <Files extends Blob>, not Blob
  43. */
  44. getTextContentFromFiles(files,
  45. /*string*/
  46. fileText => {
  47. fileText = fileText || defaultFileText;
  48. if (!fileText) {
  49. return;
  50. }
  51. const editorState = editor._latestEditorState;
  52. const blocks = splitTextIntoTextBlocks(fileText);
  53. const character = CharacterMetadata.create({
  54. style: editorState.getCurrentInlineStyle(),
  55. entity: getEntityKeyForSelection(editorState.getCurrentContent(), editorState.getSelection())
  56. });
  57. const currentBlockType = RichTextEditorUtil.getCurrentBlockType(editorState);
  58. const text = DraftPasteProcessor.processText(blocks, character, currentBlockType);
  59. const fragment = BlockMapBuilder.createFromArray(text);
  60. const withInsertedText = DraftModifier.replaceWithFragment(editorState.getCurrentContent(), editorState.getSelection(), fragment);
  61. editor.update(EditorState.push(editorState, withInsertedText, 'insert-fragment'));
  62. });
  63. return;
  64. }
  65. }
  66. let textBlocks: Array<string> = [];
  67. const text: string = (data.getText(): any);
  68. const html: string = (data.getHTML(): any);
  69. const editorState = editor._latestEditorState;
  70. if (editor.props.handlePastedText && isEventHandled(editor.props.handlePastedText(text, html, editorState))) {
  71. return;
  72. }
  73. if (text) {
  74. textBlocks = splitTextIntoTextBlocks(text);
  75. }
  76. if (!editor.props.stripPastedStyles) {
  77. // If the text from the paste event is rich content that matches what we
  78. // already have on the internal clipboard, assume that we should just use
  79. // the clipboard fragment for the paste. This will allow us to preserve
  80. // styling and entities, if any are present. Note that newlines are
  81. // stripped during comparison -- this is because copy/paste within the
  82. // editor in Firefox and IE will not include empty lines. The resulting
  83. // paste will preserve the newlines correctly.
  84. const internalClipboard = editor.getClipboard();
  85. if (data.isRichText() && internalClipboard) {
  86. if ( // If the editorKey is present in the pasted HTML, it should be safe to
  87. // assume this is an internal paste.
  88. html.indexOf(editor.getEditorKey()) !== -1 || // The copy may have been made within a single block, in which case the
  89. // editor key won't be part of the paste. In this case, just check
  90. // whether the pasted text matches the internal clipboard.
  91. textBlocks.length === 1 && internalClipboard.size === 1 && internalClipboard.first().getText() === text) {
  92. editor.update(insertFragment(editor._latestEditorState, internalClipboard));
  93. return;
  94. }
  95. } else if (internalClipboard && data.types.includes('com.apple.webarchive') && !data.types.includes('text/html') && areTextBlocksAndClipboardEqual(textBlocks, internalClipboard)) {
  96. // Safari does not properly store text/html in some cases.
  97. // Use the internalClipboard if present and equal to what is on
  98. // the clipboard. See https://bugs.webkit.org/show_bug.cgi?id=19893.
  99. editor.update(insertFragment(editor._latestEditorState, internalClipboard));
  100. return;
  101. } // If there is html paste data, try to parse that.
  102. if (html) {
  103. const htmlFragment = DraftPasteProcessor.processHTML(html, editor.props.blockRenderMap);
  104. if (htmlFragment) {
  105. const {
  106. contentBlocks,
  107. entityMap
  108. } = htmlFragment;
  109. if (contentBlocks) {
  110. const htmlMap = BlockMapBuilder.createFromArray(contentBlocks);
  111. editor.update(insertFragment(editor._latestEditorState, htmlMap, entityMap));
  112. return;
  113. }
  114. }
  115. } // Otherwise, create a new fragment from our pasted text. Also
  116. // empty the internal clipboard, since it's no longer valid.
  117. editor.setClipboard(null);
  118. }
  119. if (textBlocks.length) {
  120. const character = CharacterMetadata.create({
  121. style: editorState.getCurrentInlineStyle(),
  122. entity: getEntityKeyForSelection(editorState.getCurrentContent(), editorState.getSelection())
  123. });
  124. const currentBlockType = RichTextEditorUtil.getCurrentBlockType(editorState);
  125. const textFragment = DraftPasteProcessor.processText(textBlocks, character, currentBlockType);
  126. const textMap = BlockMapBuilder.createFromArray(textFragment);
  127. editor.update(insertFragment(editor._latestEditorState, textMap));
  128. }
  129. }
  130. function insertFragment(editorState: EditorState, fragment: BlockMap, entityMap: ?EntityMap): EditorState {
  131. const newContent = DraftModifier.replaceWithFragment(editorState.getCurrentContent(), editorState.getSelection(), fragment); // TODO: merge the entity map once we stop using DraftEntity
  132. // like this:
  133. // const mergedEntityMap = newContent.getEntityMap().merge(entityMap);
  134. return EditorState.push(editorState, newContent.set('entityMap', entityMap), 'insert-fragment');
  135. }
  136. function areTextBlocksAndClipboardEqual(textBlocks: Array<string>, blockMap: BlockMap): boolean {
  137. return textBlocks.length === blockMap.size && blockMap.valueSeq().every((block, ii) => block.getText() === textBlocks[ii]);
  138. }
  139. module.exports = editOnPaste;