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

3 лет назад
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  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 { BlockNodeRecord } from "./BlockNodeRecord";
  13. import type ContentState from "./ContentState";
  14. import type { DraftDecoratorComponentProps } from "./DraftDecorator";
  15. import type { DraftDecoratorType } from "./DraftDecoratorType";
  16. import type { DraftInlineStyle } from "./DraftInlineStyle";
  17. import type SelectionState from "./SelectionState";
  18. import type { BidiDirection } from "fbjs/lib/UnicodeBidiDirection";
  19. import type { List } from "immutable";
  20. const DraftEditorLeaf = require("./DraftEditorLeaf.react");
  21. const DraftOffsetKey = require("./DraftOffsetKey");
  22. const React = require("react");
  23. const Scroll = require("fbjs/lib/Scroll");
  24. const Style = require("fbjs/lib/Style");
  25. const UnicodeBidi = require("fbjs/lib/UnicodeBidi");
  26. const UnicodeBidiDirection = require("fbjs/lib/UnicodeBidiDirection");
  27. const cx = require("fbjs/lib/cx");
  28. const getElementPosition = require("fbjs/lib/getElementPosition");
  29. const getScrollPosition = require("fbjs/lib/getScrollPosition");
  30. const getViewportDimensions = require("fbjs/lib/getViewportDimensions");
  31. const invariant = require("fbjs/lib/invariant");
  32. const isHTMLElement = require("./isHTMLElement");
  33. const nullthrows = require("fbjs/lib/nullthrows");
  34. const SCROLL_BUFFER = 10;
  35. type Props = {
  36. block: BlockNodeRecord,
  37. blockProps?: Object,
  38. blockStyleFn: (block: BlockNodeRecord) => string,
  39. contentState: ContentState,
  40. customStyleFn: (style: DraftInlineStyle, block: BlockNodeRecord) => ?Object,
  41. customStyleMap: Object,
  42. decorator: ?DraftDecoratorType,
  43. direction: BidiDirection,
  44. forceSelection: boolean,
  45. offsetKey: string,
  46. preventScroll?: boolean,
  47. selection: SelectionState,
  48. startIndent?: boolean,
  49. tree: List<any>,
  50. ...
  51. };
  52. /**
  53. * Return whether a block overlaps with either edge of the `SelectionState`.
  54. */
  55. const isBlockOnSelectionEdge = (selection: SelectionState, key: string): boolean => {
  56. return selection.getAnchorKey() === key || selection.getFocusKey() === key;
  57. };
  58. /**
  59. * The default block renderer for a `DraftEditor` component.
  60. *
  61. * A `DraftEditorBlock` is able to render a given `ContentBlock` to its
  62. * appropriate decorator and inline style components.
  63. */
  64. class DraftEditorBlock extends React.Component<Props> {
  65. _node: ?HTMLDivElement;
  66. shouldComponentUpdate(nextProps: Props): boolean {
  67. return this.props.block !== nextProps.block || this.props.tree !== nextProps.tree || this.props.direction !== nextProps.direction || isBlockOnSelectionEdge(nextProps.selection, nextProps.block.getKey()) && nextProps.forceSelection;
  68. }
  69. /**
  70. * When a block is mounted and overlaps the selection state, we need to make
  71. * sure that the cursor is visible to match native behavior. This may not
  72. * be the case if the user has pressed `RETURN` or pasted some content, since
  73. * programmatically creating these new blocks and setting the DOM selection
  74. * will miss out on the browser natively scrolling to that position.
  75. *
  76. * To replicate native behavior, if the block overlaps the selection state
  77. * on mount, force the scroll position. Check the scroll state of the scroll
  78. * parent, and adjust it to align the entire block to the bottom of the
  79. * scroll parent.
  80. */
  81. componentDidMount(): void {
  82. if (this.props.preventScroll) {
  83. return;
  84. }
  85. const selection = this.props.selection;
  86. const endKey = selection.getEndKey();
  87. if (!selection.getHasFocus() || endKey !== this.props.block.getKey()) {
  88. return;
  89. }
  90. const blockNode = this._node;
  91. if (blockNode == null) {
  92. return;
  93. }
  94. const scrollParent = Style.getScrollParent(blockNode);
  95. const scrollPosition = getScrollPosition(scrollParent);
  96. let scrollDelta;
  97. if (scrollParent === window) {
  98. const nodePosition = getElementPosition(blockNode);
  99. const nodeBottom = nodePosition.y + nodePosition.height;
  100. const viewportHeight = getViewportDimensions().height;
  101. scrollDelta = nodeBottom - viewportHeight;
  102. if (scrollDelta > 0) {
  103. window.scrollTo(scrollPosition.x, scrollPosition.y + scrollDelta + SCROLL_BUFFER);
  104. }
  105. } else {
  106. invariant(isHTMLElement(blockNode), 'blockNode is not an HTMLElement');
  107. const blockBottom = blockNode.offsetHeight + blockNode.offsetTop;
  108. const pOffset = scrollParent.offsetTop + scrollParent.offsetHeight;
  109. const scrollBottom = pOffset + scrollPosition.y;
  110. scrollDelta = blockBottom - scrollBottom;
  111. if (scrollDelta > 0) {
  112. Scroll.setTop(scrollParent, Scroll.getTop(scrollParent) + scrollDelta + SCROLL_BUFFER);
  113. }
  114. }
  115. }
  116. _renderChildren(): Array<React.Node> {
  117. const block = this.props.block;
  118. const blockKey = block.getKey();
  119. const text = block.getText();
  120. const lastLeafSet = this.props.tree.size - 1;
  121. const hasSelection = isBlockOnSelectionEdge(this.props.selection, blockKey);
  122. return this.props.tree.map((leafSet, ii) => {
  123. const leavesForLeafSet = leafSet.get('leaves'); // T44088704
  124. if (leavesForLeafSet.size === 0) {
  125. return null;
  126. }
  127. const lastLeaf = leavesForLeafSet.size - 1;
  128. const leaves = leavesForLeafSet.map((leaf, jj) => {
  129. const offsetKey = DraftOffsetKey.encode(blockKey, ii, jj);
  130. const start = leaf.get('start');
  131. const end = leaf.get('end');
  132. return <DraftEditorLeaf key={offsetKey} offsetKey={offsetKey} block={block} start={start} selection={hasSelection ? this.props.selection : null} forceSelection={this.props.forceSelection} text={text.slice(start, end)} styleSet={block.getInlineStyleAt(start)} customStyleMap={this.props.customStyleMap} customStyleFn={this.props.customStyleFn} isLast={ii === lastLeafSet && jj === lastLeaf} />;
  133. }).toArray();
  134. const decoratorKey = leafSet.get('decoratorKey');
  135. if (decoratorKey == null) {
  136. return leaves;
  137. }
  138. if (!this.props.decorator) {
  139. return leaves;
  140. }
  141. const decorator = nullthrows(this.props.decorator);
  142. const DecoratorComponent = decorator.getComponentForKey(decoratorKey);
  143. if (!DecoratorComponent) {
  144. return leaves;
  145. }
  146. const decoratorProps = decorator.getPropsForKey(decoratorKey);
  147. const decoratorOffsetKey = DraftOffsetKey.encode(blockKey, ii, 0);
  148. const start = leavesForLeafSet.first().get('start');
  149. const end = leavesForLeafSet.last().get('end');
  150. const decoratedText = text.slice(start, end);
  151. const entityKey = block.getEntityAt(leafSet.get('start')); // Resetting dir to the same value on a child node makes Chrome/Firefox
  152. // confused on cursor movement. See http://jsfiddle.net/d157kLck/3/
  153. const dir = UnicodeBidiDirection.getHTMLDirIfDifferent(UnicodeBidi.getDirection(decoratedText), this.props.direction);
  154. const commonProps: DraftDecoratorComponentProps = {
  155. contentState: this.props.contentState,
  156. decoratedText,
  157. dir: dir,
  158. start,
  159. end,
  160. blockKey,
  161. entityKey,
  162. offsetKey: decoratorOffsetKey
  163. };
  164. return <DecoratorComponent {...decoratorProps} {...commonProps} key={decoratorOffsetKey}>
  165. {leaves}
  166. </DecoratorComponent>;
  167. }).toArray();
  168. }
  169. render(): React.Node {
  170. const {
  171. direction,
  172. offsetKey
  173. } = this.props;
  174. const className = cx({
  175. 'public/DraftStyleDefault/block': true,
  176. 'public/DraftStyleDefault/ltr': direction === 'LTR',
  177. 'public/DraftStyleDefault/rtl': direction === 'RTL'
  178. });
  179. return <div data-offset-key={offsetKey} className={className} ref={ref => this._node = ref}>
  180. {this._renderChildren()}
  181. </div>;
  182. }
  183. }
  184. module.exports = DraftEditorBlock;