UNPKG

shengnian-editor

Version:

Shengnian React Rich Text Editor

601 lines (481 loc) 20.7 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.Dropdown = exports.Button = exports.ButtonGroup = exports.createValueFromString = exports.createEmptyValue = exports.decorator = exports.EditorValue = undefined; var _assign = require('babel-runtime/core-js/object/assign'); var _assign2 = _interopRequireDefault(_assign); var _defineProperty2 = require('babel-runtime/helpers/defineProperty'); var _defineProperty3 = _interopRequireDefault(_defineProperty2); var _extends2 = require('babel-runtime/helpers/extends'); var _extends3 = _interopRequireDefault(_extends2); var _objectWithoutProperties2 = require('babel-runtime/helpers/objectWithoutProperties'); var _objectWithoutProperties3 = _interopRequireDefault(_objectWithoutProperties2); var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); var _createClass2 = require('babel-runtime/helpers/createClass'); var _createClass3 = _interopRequireDefault(_createClass2); var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); var _inherits2 = require('babel-runtime/helpers/inherits'); var _inherits3 = _interopRequireDefault(_inherits2); var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _reactDom = require('react-dom'); var _reactDom2 = _interopRequireDefault(_reactDom); var _prism = require('./lib/prism'); var _prism2 = _interopRequireDefault(_prism); var _prismjs = require('prismjs'); var _prismjs2 = _interopRequireDefault(_prismjs); require('./lib/prism/prismjs'); var _draftJs = require('draft-js'); var _getDefaultKeyBinding = require('draft-js/lib/getDefaultKeyBinding'); var _getDefaultKeyBinding2 = _interopRequireDefault(_getDefaultKeyBinding); var _changeBlockDepth = require('./lib/changeBlockDepth'); var _changeBlockDepth2 = _interopRequireDefault(_changeBlockDepth); var _changeBlockType = require('./lib/changeBlockType'); var _changeBlockType2 = _interopRequireDefault(_changeBlockType); var _getBlocksInSelection = require('./lib/getBlocksInSelection'); var _getBlocksInSelection2 = _interopRequireDefault(_getBlocksInSelection); var _insertBlockAfter = require('./lib/insertBlockAfter'); var _insertBlockAfter2 = _interopRequireDefault(_insertBlockAfter); var _isListItem = require('./lib/isListItem'); var _isListItem2 = _interopRequireDefault(_isListItem); var _isSoftNewlineEvent = require('draft-js/lib/isSoftNewlineEvent'); var _isSoftNewlineEvent2 = _interopRequireDefault(_isSoftNewlineEvent); var _EditorToolbar = require('./lib/EditorToolbar'); var _EditorToolbar2 = _interopRequireDefault(_EditorToolbar); var _EditorValue = require('./lib/EditorValue'); var _EditorValue2 = _interopRequireDefault(_EditorValue); var _LinkDecorator = require('./lib/LinkDecorator'); var _LinkDecorator2 = _interopRequireDefault(_LinkDecorator); var _ImageDecorator = require('./lib/ImageDecorator'); var _ImageDecorator2 = _interopRequireDefault(_ImageDecorator); var _composite = require('./lib/composite'); var _composite2 = _interopRequireDefault(_composite); var _getBlockRendererFn = require('./lib/getBlockRendererFn'); var _getBlockRendererFn2 = _interopRequireDefault(_getBlockRendererFn); var _MultiDecorator = require('./lib/MultiDecorator'); var _MultiDecorator2 = _interopRequireDefault(_MultiDecorator); var _classnames = require('classnames'); var _classnames2 = _interopRequireDefault(_classnames); var _classAutobind = require('class-autobind'); var _classAutobind2 = _interopRequireDefault(_classAutobind); var _events = require('events'); var _events2 = _interopRequireDefault(_events); var _draftJsUtils = require('draft-js-utils'); require('./Draft.global.css'); var _RichTextEditor = require('./RichTextEditor.css'); var _RichTextEditor2 = _interopRequireDefault(_RichTextEditor); require('prismjs/themes/prism.css'); var _ButtonGroup = require('./ui/ButtonGroup'); var _ButtonGroup2 = _interopRequireDefault(_ButtonGroup); var _Button = require('./ui/Button'); var _Button2 = _interopRequireDefault(_Button); var _Dropdown = require('./ui/Dropdown'); var _Dropdown2 = _interopRequireDefault(_Dropdown); var _SupportLanguagePopover = require('./ui/SupportLanguagePopover'); var _SupportLanguagePopover2 = _interopRequireDefault(_SupportLanguagePopover); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } // import var MAX_LIST_DEPTH = 2; // Custom overrides for "code" style. var styleMap = { CODE: { backgroundColor: 'rgba(0, 0, 0, 0.05)', fontFamily: '"Inconsolata", "Menlo", "Consolas", monospace', fontSize: 16, padding: 2 } }; var RichTextEditor = function (_Component) { (0, _inherits3.default)(RichTextEditor, _Component); function RichTextEditor(props) { (0, _classCallCheck3.default)(this, RichTextEditor); var _this = (0, _possibleConstructorReturn3.default)(this, (RichTextEditor.__proto__ || (0, _getPrototypeOf2.default)(RichTextEditor)).call(this, props)); _this.renderSuppLanguages = function () { var editorState = _this.props.value.getEditorState(); var selection = editorState.getSelection(); var b = _this.state.block; if (selection.isCollapsed()) { var contentState = editorState.getCurrentContent(); var blockKey = selection.getStartKey(); var newBlock = contentState.getBlockForKey(blockKey); b = newBlock; } _reactDom2.default.render(_react2.default.createElement(_SupportLanguagePopover2.default, { getEditorState: _this.props.value.getEditorState(), onChange: _this._onChange, block: b, position: _this.state.position // hidePopover={this._hidePopover} }), _this.suppLanguagesPopoverWrapper); }; _this._keyEmitter = new _events2.default(); (0, _classAutobind2.default)(_this); _this.state = { position: { left: -1180, top: -999 }, block: null // this.blockRenderMap = Immutable.Map({ // 'code-block': { // element: 'pre', // wrapper: <CodeBlockRender />, // }, // }).merge(DefaultDraftBlockRenderMap); };return _this; } (0, _createClass3.default)(RichTextEditor, [{ key: 'componentDidMount', value: function componentDidMount() { var _this2 = this; var _props = this.props, autoFocus = _props.autoFocus, topOffset = _props.topOffset, leftOffset = _props.leftOffset; this.suppLanguagesPopoverWrapper = document.createElement('div'); document.body.appendChild(this.suppLanguagesPopoverWrapper); this.getEditorState = function () { return _this2.props.value.getEditorState(); }; this.blockRendererFn = (0, _getBlockRendererFn2.default)(this.getEditorState(), this._onChange, topOffset ? topOffset : 0, leftOffset ? leftOffset : 0, this._setSupportedLang); if (!autoFocus) { return; } this._focus(); } }, { key: 'componentDidUpdate', value: function componentDidUpdate() { this.renderSuppLanguages(); } }, { key: 'componentWillUnmount', value: function componentWillUnmount() { _reactDom2.default.unmountComponentAtNode(this.suppLanguagesPopoverWrapper); document.body.removeChild(this.suppLanguagesPopoverWrapper); } }, { key: 'render', value: function render() { var _cx, _this3 = this; var _props2 = this.props, value = _props2.value, className = _props2.className, toolbarClassName = _props2.toolbarClassName, editorClassName = _props2.editorClassName, placeholder = _props2.placeholder, customStyleMap = _props2.customStyleMap, readOnly = _props2.readOnly, disabled = _props2.disabled, toolbarConfig = _props2.toolbarConfig, blockStyleFn = _props2.blockStyleFn, customControls = _props2.customControls, keyBindingFn = _props2.keyBindingFn, rootStyle = _props2.rootStyle, toolbarStyle = _props2.toolbarStyle, editorStyle = _props2.editorStyle, onImageChange = _props2.onImageChange, onImageRejected = _props2.onImageRejected, onImageAccepted = _props2.onImageAccepted, otherProps = (0, _objectWithoutProperties3.default)(_props2, ['value', 'className', 'toolbarClassName', 'editorClassName', 'placeholder', 'customStyleMap', 'readOnly', 'disabled', 'toolbarConfig', 'blockStyleFn', 'customControls', 'keyBindingFn', 'rootStyle', 'toolbarStyle', 'editorStyle', 'onImageChange', 'onImageRejected', 'onImageAccepted']); var editorState = value.getEditorState(); customStyleMap = customStyleMap ? (0, _extends3.default)({}, styleMap, customStyleMap) : styleMap; // If the user changes block type before entering any text, we can either // style the placeholder or hide it. Let's just hide it for now. var combinedEditorClassName = (0, _classnames2.default)((_cx = {}, (0, _defineProperty3.default)(_cx, _RichTextEditor2.default.editor, true), (0, _defineProperty3.default)(_cx, _RichTextEditor2.default.hidePlaceholder, this._shouldHidePlaceholder()), _cx), editorClassName); if (readOnly == null) { readOnly = disabled; } var editorToolbar = void 0; if (!readOnly) { editorToolbar = _react2.default.createElement(_EditorToolbar2.default, { rootStyle: toolbarStyle, className: toolbarClassName, keyEmitter: this._keyEmitter, editorState: editorState, onChange: this._onChange, focusEditor: this._focus, toolbarConfig: toolbarConfig, customControls: customControls, onImageChange: onImageChange, onImageRejected: onImageRejected, onImageAccepted: onImageAccepted }); } return _react2.default.createElement( 'div', { className: (0, _classnames2.default)(_RichTextEditor2.default.root, className), style: rootStyle }, editorToolbar, _react2.default.createElement( 'div', { onClick: function onClick() { _this3._focus(); }, className: combinedEditorClassName, style: editorStyle }, _react2.default.createElement(_draftJs.Editor, (0, _extends3.default)({}, otherProps, { blockStyleFn: (0, _composite2.default)(defaultBlockStyleFn, blockStyleFn), customStyleMap: customStyleMap, editorState: editorState, handleReturn: this._handleReturn, keyBindingFn: keyBindingFn || this._customKeyHandler, handleKeyCommand: this._handleKeyCommand, onTab: this._onTab, onChange: this._onChange, placeholder: placeholder, ref: 'editor', spellCheck: true, readOnly: readOnly, blockRendererFn: this.blockRendererFn, handlePastedText: function handlePastedText(value) { return console.log('paste', value); }, handleDroppedFiles: function handleDroppedFiles(files) { return console.log('drop', files); }, handlePastedFiles: function handlePastedFiles(files) { return console.log('file', files); } // blockRenderMap={this.blockRenderMap} })) ) ); } }, { key: '_hidePopover', value: function _hidePopover() { this.setState({ position: { top: -999, left: -1180 } }); } }, { key: '_setSupportedLang', value: function _setSupportedLang(suppLang) { this.setState({ position: suppLang.position, block: suppLang.block }); } }, { key: '_shouldHidePlaceholder', value: function _shouldHidePlaceholder() { var editorState = this.props.value.getEditorState(); var contentState = editorState.getCurrentContent(); if (!contentState.hasText()) { if (contentState.getBlockMap().first().getType() !== 'unstyled') { return true; } } return false; } }, { key: '_handleReturn', value: function _handleReturn(event) { var handleReturn = this.props.handleReturn; if (handleReturn != null && handleReturn(event)) { return true; } if (this._handleReturnSoftNewline(event)) { return true; } if (this._handleReturnEmptyListItem()) { return true; } if (this._handleReturnSpecialBlock()) { return true; } return false; } // `shift + return` should insert a soft newline. }, { key: '_handleReturnSoftNewline', value: function _handleReturnSoftNewline(event) { var editorState = this.props.value.getEditorState(); if ((0, _isSoftNewlineEvent2.default)(event)) { var selection = editorState.getSelection(); if (selection.isCollapsed()) { this._onChange(_draftJs.RichUtils.insertSoftNewline(editorState)); } else { var content = editorState.getCurrentContent(); var newContent = _draftJs.Modifier.removeRange(content, selection, 'forward'); var newSelection = newContent.getSelectionAfter(); var _block = newContent.getBlockForKey(newSelection.getStartKey()); newContent = _draftJs.Modifier.insertText(newContent, newSelection, '\n', _block.getInlineStyleAt(newSelection.getStartOffset()), null); this._onChange(_draftJs.EditorState.push(editorState, newContent, 'insert-fragment')); } return true; } return false; } // If the cursor is in an empty list item when return is pressed, then the // block type should change to normal (end the list). }, { key: '_handleReturnEmptyListItem', value: function _handleReturnEmptyListItem() { var editorState = this.props.value.getEditorState(); var selection = editorState.getSelection(); if (selection.isCollapsed()) { var contentState = editorState.getCurrentContent(); var blockKey = selection.getStartKey(); var _block2 = contentState.getBlockForKey(blockKey); if ((0, _isListItem2.default)(_block2) && _block2.getLength() === 0) { var depth = _block2.getDepth(); var newState = depth === 0 ? (0, _changeBlockType2.default)(editorState, blockKey, _draftJsUtils.BLOCK_TYPE.UNSTYLED) : (0, _changeBlockDepth2.default)(editorState, blockKey, depth - 1); this._onChange(newState); return true; } } return false; } // If the cursor is at the end of a special block (any block type other than // normal or list item) when return is pressed, new block should be normal. }, { key: '_handleReturnSpecialBlock', value: function _handleReturnSpecialBlock() { var editorState = this.props.value.getEditorState(); var selection = editorState.getSelection(); if (selection.isCollapsed()) { var contentState = editorState.getCurrentContent(); var blockKey = selection.getStartKey(); var _block3 = contentState.getBlockForKey(blockKey); if (!(0, _isListItem2.default)(_block3) && _block3.getType() !== _draftJsUtils.BLOCK_TYPE.UNSTYLED) { // If cursor is at end. if (_block3.getLength() === selection.getStartOffset()) { var newEditorState = (0, _insertBlockAfter2.default)(editorState, blockKey, _draftJsUtils.BLOCK_TYPE.UNSTYLED); this._onChange(newEditorState); return true; } } } return false; } }, { key: '_onTab', value: function _onTab(event) { var editorState = this.props.value.getEditorState(); var newEditorState = _draftJs.RichUtils.onTab(event, editorState, MAX_LIST_DEPTH); if (newEditorState !== editorState) { this._onChange(newEditorState); } } }, { key: '_customKeyHandler', value: function _customKeyHandler(event) { // Allow toolbar to catch key combinations. var eventFlags = {}; this._keyEmitter.emit('keypress', event, eventFlags); if (eventFlags.wasHandled) { return null; } else { return (0, _getDefaultKeyBinding2.default)(event); } } }, { key: '_handleKeyCommand', value: function _handleKeyCommand(command) { var editorState = this.props.value.getEditorState(); var newEditorState = _draftJs.RichUtils.handleKeyCommand(editorState, command); if (newEditorState) { this._onChange(newEditorState); return true; } else { return false; } } }, { key: '_onChange', value: function _onChange(editorState) { var _props3 = this.props, onChange = _props3.onChange, value = _props3.value; if (onChange == null) { return; } var newValue = value.setEditorState(editorState); var newEditorState = newValue.getEditorState(); this._handleInlineImageSelection(newEditorState); onChange(newValue); } }, { key: '_handleInlineImageSelection', value: function _handleInlineImageSelection(editorState) { var selection = editorState.getSelection(); var contentState = editorState.getCurrentContent(); var blocks = (0, _getBlocksInSelection2.default)(editorState); var selectImage = function selectImage(block, offset) { var imageKey = block.getEntityAt(offset); contentState.mergeEntityData(imageKey, { selected: true }); }; var isInMiddleBlock = function isInMiddleBlock(index) { return index > 0 && index < blocks.size - 1; }; var isWithinStartBlockSelection = function isWithinStartBlockSelection(offset, index) { return index === 0 && offset > selection.getStartOffset(); }; var isWithinEndBlockSelection = function isWithinEndBlockSelection(offset, index) { return index === blocks.size - 1 && offset < selection.getEndOffset(); }; blocks.toIndexedSeq().forEach(function (block, index) { _ImageDecorator2.default.strategy(block, function (offset) { if (isWithinStartBlockSelection(offset, index) || isInMiddleBlock(index) || isWithinEndBlockSelection(offset, index)) { selectImage(block, offset); } }); }); } }, { key: '_focus', value: function _focus() { this.refs.editor.focus(); } }]); return RichTextEditor; }(_react.Component); exports.default = RichTextEditor; function defaultBlockStyleFn(block) { var result = _RichTextEditor2.default.block; switch (block.getType()) { case 'unstyled': return (0, _classnames2.default)('editable-unstyled', result, _RichTextEditor2.default.paragraph); case 'blockquote': return (0, _classnames2.default)('editable-styled', result, _RichTextEditor2.default.blockquote); case 'code-block': return (0, _classnames2.default)('editable-styled', result, _RichTextEditor2.default.codeBlock); default: return result; } } var decorator = new _MultiDecorator2.default([new _prism2.default({ // Provide your own instance of PrismJS prism: _prismjs2.default }), new _draftJs.CompositeDecorator([_LinkDecorator2.default, _ImageDecorator2.default])]); function createEmptyValue() { return _EditorValue2.default.createEmpty(decorator); } function createValueFromString(markup, format, options) { return _EditorValue2.default.createFromString(markup, format, decorator, options); } // $FlowIssue - This should probably not be done this way. (0, _assign2.default)(RichTextEditor, { EditorValue: _EditorValue2.default, decorator: decorator, createEmptyValue: createEmptyValue, createValueFromString: createValueFromString, ButtonGroup: _ButtonGroup2.default, Button: _Button2.default, Dropdown: _Dropdown2.default }); exports.EditorValue = _EditorValue2.default; exports.decorator = decorator; exports.createEmptyValue = createEmptyValue; exports.createValueFromString = createValueFromString; exports.ButtonGroup = _ButtonGroup2.default; exports.Button = _Button2.default; exports.Dropdown = _Dropdown2.default;