forked from Gitlink/forgeplus-react
170 lines
6.2 KiB
JavaScript
170 lines
6.2 KiB
JavaScript
|
|
/**
|
||
|
|
* Copyright (c) 2013-present, Facebook, Inc.
|
||
|
|
* All rights reserved.
|
||
|
|
*
|
||
|
|
* This source code is licensed under the BSD-style license found in the
|
||
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||
|
|
*
|
||
|
|
* @providesModule DraftEditorLeaf.react
|
||
|
|
* @format
|
||
|
|
*
|
||
|
|
*/
|
||
|
|
|
||
|
|
'use strict';
|
||
|
|
|
||
|
|
var _assign = require('object-assign');
|
||
|
|
|
||
|
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
||
|
|
|
||
|
|
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
|
||
|
|
|
||
|
|
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
|
||
|
|
|
||
|
|
var DraftEditorTextNode = require('./DraftEditorTextNode.react');
|
||
|
|
var React = require('react');
|
||
|
|
var ReactDOM = require('react-dom');
|
||
|
|
|
||
|
|
var invariant = require('fbjs/lib/invariant');
|
||
|
|
var setDraftEditorSelection = require('./setDraftEditorSelection');
|
||
|
|
|
||
|
|
/**
|
||
|
|
* All leaf nodes in the editor are spans with single text nodes. Leaf
|
||
|
|
* elements are styled based on the merging of an optional custom style map
|
||
|
|
* and a default style map.
|
||
|
|
*
|
||
|
|
* `DraftEditorLeaf` also provides a wrapper for calling into the imperative
|
||
|
|
* DOM Selection API. In this way, top-level components can declaratively
|
||
|
|
* maintain the selection state.
|
||
|
|
*/
|
||
|
|
var DraftEditorLeaf = function (_React$Component) {
|
||
|
|
_inherits(DraftEditorLeaf, _React$Component);
|
||
|
|
|
||
|
|
function DraftEditorLeaf() {
|
||
|
|
_classCallCheck(this, DraftEditorLeaf);
|
||
|
|
|
||
|
|
return _possibleConstructorReturn(this, _React$Component.apply(this, arguments));
|
||
|
|
}
|
||
|
|
|
||
|
|
DraftEditorLeaf.prototype._setSelection = function _setSelection() {
|
||
|
|
var selection = this.props.selection;
|
||
|
|
|
||
|
|
// If selection state is irrelevant to the parent block, no-op.
|
||
|
|
|
||
|
|
if (selection == null || !selection.getHasFocus()) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
var _props = this.props,
|
||
|
|
block = _props.block,
|
||
|
|
start = _props.start,
|
||
|
|
text = _props.text;
|
||
|
|
|
||
|
|
var blockKey = block.getKey();
|
||
|
|
var end = start + text.length;
|
||
|
|
if (!selection.hasEdgeWithin(blockKey, start, end)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Determine the appropriate target node for selection. If the child
|
||
|
|
// is not a text node, it is a <br /> spacer. In this case, use the
|
||
|
|
// <span> itself as the selection target.
|
||
|
|
var node = ReactDOM.findDOMNode(this);
|
||
|
|
!node ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Missing node') : invariant(false) : void 0;
|
||
|
|
var child = node.firstChild;
|
||
|
|
!child ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Missing child') : invariant(false) : void 0;
|
||
|
|
var targetNode = void 0;
|
||
|
|
|
||
|
|
if (child.nodeType === Node.TEXT_NODE) {
|
||
|
|
targetNode = child;
|
||
|
|
} else if (child.tagName === 'BR') {
|
||
|
|
targetNode = node;
|
||
|
|
} else {
|
||
|
|
targetNode = child.firstChild;
|
||
|
|
!targetNode ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Missing targetNode') : invariant(false) : void 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
setDraftEditorSelection(selection, targetNode, blockKey, start, end);
|
||
|
|
};
|
||
|
|
/**
|
||
|
|
* By making individual leaf instances aware of their context within
|
||
|
|
* the text of the editor, we can set our selection range more
|
||
|
|
* easily than we could in the non-React world.
|
||
|
|
*
|
||
|
|
* Note that this depends on our maintaining tight control over the
|
||
|
|
* DOM structure of the DraftEditor component. If leaves had multiple
|
||
|
|
* text nodes, this would be harder.
|
||
|
|
*/
|
||
|
|
|
||
|
|
DraftEditorLeaf.prototype.shouldComponentUpdate = function shouldComponentUpdate(nextProps) {
|
||
|
|
var leafNode = ReactDOM.findDOMNode(this.leaf);
|
||
|
|
!leafNode ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Missing leafNode') : invariant(false) : void 0;
|
||
|
|
return leafNode.textContent !== nextProps.text || nextProps.styleSet !== this.props.styleSet || nextProps.forceSelection;
|
||
|
|
};
|
||
|
|
|
||
|
|
DraftEditorLeaf.prototype.componentDidUpdate = function componentDidUpdate() {
|
||
|
|
this._setSelection();
|
||
|
|
};
|
||
|
|
|
||
|
|
DraftEditorLeaf.prototype.componentDidMount = function componentDidMount() {
|
||
|
|
this._setSelection();
|
||
|
|
};
|
||
|
|
|
||
|
|
DraftEditorLeaf.prototype.render = function render() {
|
||
|
|
var _this2 = this;
|
||
|
|
|
||
|
|
var block = this.props.block;
|
||
|
|
var text = this.props.text;
|
||
|
|
|
||
|
|
// If the leaf is at the end of its block and ends in a soft newline, append
|
||
|
|
// an extra line feed character. Browsers collapse trailing newline
|
||
|
|
// characters, which leaves the cursor in the wrong place after a
|
||
|
|
// shift+enter. The extra character repairs this.
|
||
|
|
|
||
|
|
if (text.endsWith('\n') && this.props.isLast) {
|
||
|
|
text += '\n';
|
||
|
|
}
|
||
|
|
|
||
|
|
var _props2 = this.props,
|
||
|
|
customStyleMap = _props2.customStyleMap,
|
||
|
|
customStyleFn = _props2.customStyleFn,
|
||
|
|
offsetKey = _props2.offsetKey,
|
||
|
|
styleSet = _props2.styleSet;
|
||
|
|
|
||
|
|
var styleObj = styleSet.reduce(function (map, styleName) {
|
||
|
|
var mergedStyles = {};
|
||
|
|
var style = customStyleMap[styleName];
|
||
|
|
|
||
|
|
if (style !== undefined && map.textDecoration !== style.textDecoration) {
|
||
|
|
// .trim() is necessary for IE9/10/11 and Edge
|
||
|
|
mergedStyles.textDecoration = [map.textDecoration, style.textDecoration].join(' ').trim();
|
||
|
|
}
|
||
|
|
|
||
|
|
return _assign(map, style, mergedStyles);
|
||
|
|
}, {});
|
||
|
|
|
||
|
|
if (customStyleFn) {
|
||
|
|
var newStyles = customStyleFn(styleSet, block);
|
||
|
|
styleObj = _assign(styleObj, newStyles);
|
||
|
|
}
|
||
|
|
|
||
|
|
return React.createElement(
|
||
|
|
'span',
|
||
|
|
{
|
||
|
|
'data-offset-key': offsetKey,
|
||
|
|
ref: function ref(_ref) {
|
||
|
|
return _this2.leaf = _ref;
|
||
|
|
},
|
||
|
|
style: styleObj },
|
||
|
|
React.createElement(
|
||
|
|
DraftEditorTextNode,
|
||
|
|
null,
|
||
|
|
text
|
||
|
|
)
|
||
|
|
);
|
||
|
|
};
|
||
|
|
|
||
|
|
return DraftEditorLeaf;
|
||
|
|
}(React.Component);
|
||
|
|
|
||
|
|
module.exports = DraftEditorLeaf;
|