Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 

203 строки
7.3 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 { DOMDerivedSelection } from "./DOMDerivedSelection";
  13. import type EditorState from "./EditorState";
  14. const findAncestorOffsetKey = require("./findAncestorOffsetKey");
  15. const getSelectionOffsetKeyForNode = require("./getSelectionOffsetKeyForNode");
  16. const getUpdatedSelectionState = require("./getUpdatedSelectionState");
  17. const invariant = require("fbjs/lib/invariant");
  18. const isElement = require("./isElement");
  19. const nullthrows = require("fbjs/lib/nullthrows");
  20. type SelectionPoint = {
  21. key: string,
  22. offset: number,
  23. ...
  24. };
  25. /**
  26. * Convert the current selection range to an anchor/focus pair of offset keys
  27. * and values that can be interpreted by components.
  28. */
  29. function getDraftEditorSelectionWithNodes(editorState: EditorState, root: ?HTMLElement, anchorNode: Node, anchorOffset: number, focusNode: Node, focusOffset: number): DOMDerivedSelection {
  30. const anchorIsTextNode = anchorNode.nodeType === Node.TEXT_NODE;
  31. const focusIsTextNode = focusNode.nodeType === Node.TEXT_NODE; // If the selection range lies only on text nodes, the task is simple.
  32. // Find the nearest offset-aware elements and use the
  33. // offset values supplied by the selection range.
  34. if (anchorIsTextNode && focusIsTextNode) {
  35. return {
  36. selectionState: getUpdatedSelectionState(editorState, nullthrows(findAncestorOffsetKey(anchorNode)), anchorOffset, nullthrows(findAncestorOffsetKey(focusNode)), focusOffset),
  37. needsRecovery: false
  38. };
  39. }
  40. let anchorPoint = null;
  41. let focusPoint = null;
  42. let needsRecovery = true; // An element is selected. Convert this selection range into leaf offset
  43. // keys and offset values for consumption at the component level. This
  44. // is common in Firefox, where select-all and triple click behavior leads
  45. // to entire elements being selected.
  46. //
  47. // Note that we use the `needsRecovery` parameter in the callback here. This
  48. // is because when certain elements are selected, the behavior for subsequent
  49. // cursor movement (e.g. via arrow keys) is uncertain and may not match
  50. // expectations at the component level. For example, if an entire <div> is
  51. // selected and the user presses the right arrow, Firefox keeps the selection
  52. // on the <div>. If we allow subsequent keypresses to insert characters
  53. // natively, they will be inserted into a browser-created text node to the
  54. // right of that <div>. This is obviously undesirable.
  55. //
  56. // With the `needsRecovery` flag, we inform the caller that it is responsible
  57. // for manually setting the selection state on the rendered document to
  58. // ensure proper selection state maintenance.
  59. if (anchorIsTextNode) {
  60. anchorPoint = {
  61. key: nullthrows(findAncestorOffsetKey(anchorNode)),
  62. offset: anchorOffset
  63. };
  64. focusPoint = getPointForNonTextNode(root, focusNode, focusOffset);
  65. } else if (focusIsTextNode) {
  66. focusPoint = {
  67. key: nullthrows(findAncestorOffsetKey(focusNode)),
  68. offset: focusOffset
  69. };
  70. anchorPoint = getPointForNonTextNode(root, anchorNode, anchorOffset);
  71. } else {
  72. anchorPoint = getPointForNonTextNode(root, anchorNode, anchorOffset);
  73. focusPoint = getPointForNonTextNode(root, focusNode, focusOffset); // If the selection is collapsed on an empty block, don't force recovery.
  74. // This way, on arrow key selection changes, the browser can move the
  75. // cursor from a non-zero offset on one block, through empty blocks,
  76. // to a matching non-zero offset on other text blocks.
  77. if (anchorNode === focusNode && anchorOffset === focusOffset) {
  78. needsRecovery = !!anchorNode.firstChild && anchorNode.firstChild.nodeName !== 'BR';
  79. }
  80. }
  81. return {
  82. selectionState: getUpdatedSelectionState(editorState, anchorPoint.key, anchorPoint.offset, focusPoint.key, focusPoint.offset),
  83. needsRecovery
  84. };
  85. }
  86. /**
  87. * Identify the first leaf descendant for the given node.
  88. */
  89. function getFirstLeaf(node: any): Node {
  90. while (node.firstChild && ( // data-blocks has no offset
  91. isElement(node.firstChild) && (node.firstChild: Element).getAttribute('data-blocks') === 'true' || getSelectionOffsetKeyForNode(node.firstChild))) {
  92. node = node.firstChild;
  93. }
  94. return node;
  95. }
  96. /**
  97. * Identify the last leaf descendant for the given node.
  98. */
  99. function getLastLeaf(node: any): Node {
  100. while (node.lastChild && ( // data-blocks has no offset
  101. isElement(node.lastChild) && node.lastChild.getAttribute('data-blocks') === 'true' || getSelectionOffsetKeyForNode(node.lastChild))) {
  102. node = node.lastChild;
  103. }
  104. return node;
  105. }
  106. function getPointForNonTextNode(editorRoot: ?HTMLElement, startNode: Node, childOffset: number): SelectionPoint {
  107. let node = startNode;
  108. const offsetKey: ?string = findAncestorOffsetKey(node);
  109. invariant(offsetKey != null || editorRoot && (editorRoot === node || editorRoot.firstChild === node), 'Unknown node in selection range.'); // If the editorRoot is the selection, step downward into the content
  110. // wrapper.
  111. if (editorRoot === node) {
  112. node = node.firstChild;
  113. invariant(isElement(node), 'Invalid DraftEditorContents node.');
  114. const castedNode: Element = (node: any); // assignment only added for flow :/
  115. // otherwise it throws in line 200 saying that node can be null or undefined
  116. node = castedNode;
  117. invariant(node.getAttribute('data-contents') === 'true', 'Invalid DraftEditorContents structure.');
  118. if (childOffset > 0) {
  119. childOffset = node.childNodes.length;
  120. }
  121. } // If the child offset is zero and we have an offset key, we're done.
  122. // If there's no offset key because the entire editor is selected,
  123. // find the leftmost ("first") leaf in the tree and use that as the offset
  124. // key.
  125. if (childOffset === 0) {
  126. let key: ?string = null;
  127. if (offsetKey != null) {
  128. key = offsetKey;
  129. } else {
  130. const firstLeaf = getFirstLeaf(node);
  131. key = nullthrows(getSelectionOffsetKeyForNode(firstLeaf));
  132. }
  133. return {
  134. key,
  135. offset: 0
  136. };
  137. }
  138. const nodeBeforeCursor = node.childNodes[childOffset - 1];
  139. let leafKey: ?string = null;
  140. let textLength: ?number = null;
  141. if (!getSelectionOffsetKeyForNode(nodeBeforeCursor)) {
  142. // Our target node may be a leaf or a text node, in which case we're
  143. // already where we want to be and can just use the child's length as
  144. // our offset.
  145. leafKey = nullthrows(offsetKey);
  146. textLength = getTextContentLength(nodeBeforeCursor);
  147. } else {
  148. // Otherwise, we'll look at the child to the left of the cursor and find
  149. // the last leaf node in its subtree.
  150. const lastLeaf = getLastLeaf(nodeBeforeCursor);
  151. leafKey = nullthrows(getSelectionOffsetKeyForNode(lastLeaf));
  152. textLength = getTextContentLength(lastLeaf);
  153. }
  154. return {
  155. key: leafKey,
  156. offset: textLength
  157. };
  158. }
  159. /**
  160. * Return the length of a node's textContent, regarding single newline
  161. * characters as zero-length. This allows us to avoid problems with identifying
  162. * the correct selection offset for empty blocks in IE, in which we
  163. * render newlines instead of break tags.
  164. */
  165. function getTextContentLength(node: Node): number {
  166. const textContent = node.textContent;
  167. return textContent === '\n' ? 0 : textContent.length;
  168. }
  169. module.exports = getDraftEditorSelectionWithNodes;