UNPKG

uxcore-mention

Version:

Mention anywhere with this component

380 lines (334 loc) 12.3 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _propTypes = require('prop-types'); var _propTypes2 = _interopRequireDefault(_propTypes); var _reactLifecyclesCompat = require('react-lifecycles-compat'); var _baseEditor = require('./baseEditor'); var _baseEditor2 = _interopRequireDefault(_baseEditor); var _util = require('../utils/util'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } function _defaults(obj, defaults) { var keys = Object.getOwnPropertyNames(defaults); for (var i = 0; i < keys.length; i++) { var key = keys[i]; var value = Object.getOwnPropertyDescriptor(defaults, key); if (value && value.configurable && obj[key] === undefined) { Object.defineProperty(obj, key, value); } } return obj; } 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) : _defaults(subClass, superClass); } var rangy = global.rangy; // webkit browsers support 'plaintext-only' var contentEditableValue = function () { // only this format can solve `document is not defined` in nodejs if (typeof document !== 'undefined') { var div = document.createElement('div'); div.setAttribute('contenteditable', 'PLAINTEXT-ONLY'); return div.contentEditable === 'plaintext-only' ? 'plaintext-only' : true; } return true; }(); var ContentEditableEditor = function (_BaseEditor) { _inherits(ContentEditableEditor, _BaseEditor); ContentEditableEditor.getDerivedStateFromProps = function getDerivedStateFromProps(nextProps, prevState) { if (nextProps.value !== prevState.prveValue) { return { value: nextProps.value, prveValue: nextProps.value }; } return null; }; function ContentEditableEditor(props) { _classCallCheck(this, ContentEditableEditor); var _this = _possibleConstructorReturn(this, _BaseEditor.call(this, props)); var val = props.value || props.defaultValue; _this.state = { focus: false, value: val, readOnly: _this.props.readOnly, prveValue: val }; return _this; } ContentEditableEditor.prototype.componentDidMount = function componentDidMount() { this.STORE = {}; if (this.state.value) { this.editor.innerHTML = this.state.value; } var MutationObserver = window.MutationObserver || window.WebkitMutationObserver || window.MozMutationObserver; if (MutationObserver) { this.observer = new MutationObserver(this.onMutation.bind(this)); this.observer.observe(this.editor, { characterData: true, childList: true, subtree: true }); } }; ContentEditableEditor.prototype.componentDidUpdate = function componentDidUpdate(prevProps) { if (prevProps.value !== this.props.value) { this.editor.innerHTML = this.state.value; } }; ContentEditableEditor.prototype.componentWillUnmount = function componentWillUnmount() { if (this.observer) { this.observer.disconnect(); } }; ContentEditableEditor.prototype.handleEnterPress = function handleEnterPress(e) { e.preventDefault(); var editor = this.editor; var sel = rangy.getSelection(); var range = sel.getRangeAt(0); // make sure the last element of the editor is br // refer to: http://stackoverflow.com/questions/6023307/dealing-with-line-breaks-on-contenteditable-div if (!editor.lastChild || editor.lastChild.nodeName.toLowerCase() !== 'br') { editor.appendChild(document.createElement('br')); } var nodeBr = document.createElement('br'); range.deleteContents(); range.insertNode(nodeBr); range.setStartAfter(nodeBr); sel.setSingleRange(range); }; ContentEditableEditor.prototype.handleDefaultKeyup = function handleDefaultKeyup() { var delimiter = this.props.delimiter; var sel = rangy.getSelection(); var range = sel.getRangeAt(0); if (range.commonAncestorContainer.nodeType === 3) { range.setStart(range.commonAncestorContainer, 0); var originStr = range.toString(); var str = (0, _util.parseStrByDelimiter)(originStr, delimiter); // send str to matcher this.props.matcher(str); if (str !== false) { // set range's start position before delimiter range.setStart(range.commonAncestorContainer, originStr.length - str.length - 1); var pos = range.getEndClientPos(); // FIXME: ie8 will return error position in some case this.props.setCursorPos(pos); // save range position this.STORE.bookmark = range.getBookmark(range.commonAncestorContainer); } } }; ContentEditableEditor.prototype.onInput = function onInput() { if (!this.observer) { this.emitChange(); } }; ContentEditableEditor.prototype.onMutation = function onMutation(mutations) { this.emitChange(mutations); }; ContentEditableEditor.prototype.onBlur = function onBlur() { this.emitChange(); this.setState({ focus: false }); }; ContentEditableEditor.prototype.onFocus = function onFocus() { this.setState({ focus: true }); this.props.onFocus(this); }; ContentEditableEditor.prototype.insert = function insert(mentionContent) { var _this2 = this; var editor = this.editor; var sel = rangy.getSelection(); if (this.STORE.bookmark) { var range = sel.getRangeAt(0); range.moveToBookmark(this.STORE.bookmark); if (Array.isArray(mentionContent)) { mentionContent.map(function (item) { return _this2.addSelectPerson(item, range); }); } else { this.addSelectPerson(mentionContent, range); } setTimeout(function () { return editor.focus(); }, 0); } }; ContentEditableEditor.prototype.addSelectPerson = function addSelectPerson(item, range) { var prefixCls = this.props.prefixCls + '-node'; var mentionNode = document.createElement('input'); mentionNode.setAttribute('type', 'button'); mentionNode.setAttribute('tabindex', '-1'); mentionNode.className = prefixCls; mentionNode.value = item; // delete origin content in range range.deleteContents(); range.insertNode(mentionNode); range.collapseAfter(mentionNode); range.select(); }; ContentEditableEditor.prototype.extractContent = function extractContent() { // console.time('extractContent'); var editor = this.editor; var nodes = editor.childNodes; var content = ''; for (var i = 0, len = nodes.length; i < len; i += 1) { var node = nodes[i]; if (node.nodeType === 1) { var tagName = node.tagName.toLowerCase(); if (tagName === 'input') { content += ' ' + node.value + ' '; } else if (tagName === 'br') { content += '\n'; } } else if (node.nodeType === 3) { content += node.textContent || node.nodeValue; } } // console.timeEnd('extractContent'); return content; }; ContentEditableEditor.prototype.emitChange = function emitChange(e) { if (this.emitFromMaxLength) { this.emitFromMaxLength = false; return; } if (!this.observer) { var editor = this.editor; var lastHtml = this.lastHtml; var currentHtml = editor.innerHTML; if (lastHtml === currentHtml) { // no change made return; } this.lastHtml = currentHtml; } var content = this.extractContent(); var value = this.props.maxLength > 0 ? content.substring(0, this.props.maxLength) : content; this.setState({ value: value }); if (this.props.maxLength > 0 && content.length > this.props.maxLength) { this.editor.innerHTML = value; this.emitFromMaxLength = true; // 焦点问题太难hack,所以通过移除焦点来禁止用户输入 this.editor.blur(); } this.props.onChange(e, value); }; ContentEditableEditor.prototype.render = function render() { var _this3 = this; var placeholder = this.props.placeholder; var readOnly = this.state.readOnly; var style = { width: this.props.width, height: this.props.height }; return _react2["default"].createElement( 'div', { className: this.props.prefixCls }, _react2["default"].createElement('div', { className: this.props.prefixCls + '-editor', ref: function ref(el) { return _this3.editor = el; }, onKeyUp: this.onKeyup.bind(this), onKeyDown: this.onKeydown.bind(this), contentEditable: readOnly ? false : contentEditableValue, onInput: this.onInput.bind(this), onBlur: this.onBlur.bind(this), onFocus: this.onFocus.bind(this), style: style }), !this.state.focus && !this.state.value ? _react2["default"].createElement( 'div', { className: this.props.prefixCls + '-placeholder', onClick: function onClick() { _this3.editor.focus(); _this3.onFocus(); } }, placeholder ) : '' ); }; return ContentEditableEditor; }(_baseEditor2["default"]); ContentEditableEditor.displayName = 'ContentEditableEditor'; ContentEditableEditor.propTypes = { /** * @i18n {zh-CN} class前缀 * @i18n {en-US} class prefix */ prefixCls: _propTypes2["default"].string, /** * @i18n {zh-CN} 编辑区域宽度 * @i18n {en-US} editor's width */ width: _propTypes2["default"].oneOfType([_propTypes2["default"].number, _propTypes2["default"].string]), /** * @i18n {zh-CN} 编辑区域高度 * @i18n {en-US} editor's height */ height: _propTypes2["default"].number, /** * @i18n {zh-CN} placeholder * @i18n {en-US} placeholder */ placeholder: _propTypes2["default"].string, /** * @i18n {zh-CN} 自定义插入的mention内容 * @i18n {en-US} customize the insert content with this function | function */ mentionFormatter: _propTypes2["default"].func, /** * @i18n {zh-CN} 发生变化后的触发 * @i18n {en-US} trigger when editor content change */ onChange: _propTypes2["default"].func, /** * @i18n {zh-CN} 添加mention后触发 * @i18n {en-US} Callback invoked when a mention has been added */ onAdd: _propTypes2["default"].func, /** * @i18n {zh-CN} 默认内容 * @i18n {en-US} default value */ defaultValue: _propTypes2["default"].string, /** * @i18n {zh-CN} 内容 * @i18n {en-US} value */ value: _propTypes2["default"].string, /** * @i18n {zh-CN} 只读 * @i18n {en-US} read only */ readOnly: _propTypes2["default"].bool, /** * @i18n {zh-CN} 触发字符 * @i18n {en-US} Defines the char sequence upon which to trigger querying the data source */ delimiter: _propTypes2["default"].string, /** * @i18n {zh-CN} 最大长度 * @i18n {en-US} max length of content */ maxLength: _propTypes2["default"].number }; ContentEditableEditor.defaultProps = { prefixCls: '', width: 200, height: 100, placeholder: '', mentionFormatter: function mentionFormatter(data) { return '@' + data.text; }, onChange: function onChange() {}, onAdd: function onAdd() {}, defaultValue: '', readOnly: false, delimiter: '@', maxLength: -1 }; (0, _reactLifecyclesCompat.polyfill)(ContentEditableEditor); exports["default"] = ContentEditableEditor; module.exports = exports['default'];