uxcore-mention
Version:
Mention anywhere with this component
380 lines (334 loc) • 12.3 kB
JavaScript
'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'];