UNPKG

@lyra/form-builder

Version:
542 lines (441 loc) 18.7 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _isEqual2 = require('lodash/isEqual'); var _isEqual3 = _interopRequireDefault(_isEqual2); var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _reactDom = require('react-dom'); var _reactDom2 = _interopRequireDefault(_reactDom); var _slateSoftBreak = require('slate-soft-break'); var _slateSoftBreak2 = _interopRequireDefault(_slateSoftBreak); var _slateReact = require('slate-react'); var _isHotkey = require('is-hotkey'); var _blockTools = require('@lyra/block-tools'); var _slateInsertBlockOnEnter = require('slate-insert-block-on-enter'); var _slateInsertBlockOnEnter2 = _interopRequireDefault(_slateInsertBlockOnEnter); var _onPaste = require('part:@lyra/form-builder/input/block-editor/on-paste?'); var _onPaste2 = _interopRequireDefault(_onPaste); var _onCopy = require('part:@lyra/form-builder/input/block-editor/on-copy?'); var _onCopy2 = _interopRequireDefault(_onCopy); var _changeToPatches = require('./utils/changeToPatches'); var _pathUtils = require('../../utils/pathUtils'); var _createNodeValidator = require('./utils/createNodeValidator'); var _createNodeValidator2 = _interopRequireDefault(_createNodeValidator); var _findInlineByAnnotationKey = require('./utils/findInlineByAnnotationKey'); var _findInlineByAnnotationKey2 = _interopRequireDefault(_findInlineByAnnotationKey); var _PatchEvent = require('../../../PatchEvent'); var _PatchEvent2 = _interopRequireDefault(_PatchEvent); var _ListItemOnEnterKeyPlugin = require('./plugins/ListItemOnEnterKeyPlugin'); var _ListItemOnEnterKeyPlugin2 = _interopRequireDefault(_ListItemOnEnterKeyPlugin); var _ListItemOnTabKeyPlugin = require('./plugins/ListItemOnTabKeyPlugin'); var _ListItemOnTabKeyPlugin2 = _interopRequireDefault(_ListItemOnTabKeyPlugin); var _OnDropPlugin = require('./plugins/OnDropPlugin'); var _OnDropPlugin2 = _interopRequireDefault(_OnDropPlugin); var _PastePlugin = require('./plugins/PastePlugin'); var _PastePlugin2 = _interopRequireDefault(_PastePlugin); var _SetMarksOnKeyComboPlugin = require('./plugins/SetMarksOnKeyComboPlugin'); var _SetMarksOnKeyComboPlugin2 = _interopRequireDefault(_SetMarksOnKeyComboPlugin); var _TextBlockOnEnterKeyPlugin = require('./plugins/TextBlockOnEnterKeyPlugin'); var _TextBlockOnEnterKeyPlugin2 = _interopRequireDefault(_TextBlockOnEnterKeyPlugin); var _UpdateCustomNodesPlugin = require('./plugins/UpdateCustomNodesPlugin'); var _UpdateCustomNodesPlugin2 = _interopRequireDefault(_UpdateCustomNodesPlugin); var _UndoRedoPlugin = require('./plugins/UndoRedoPlugin'); var _UndoRedoPlugin2 = _interopRequireDefault(_UndoRedoPlugin); var _BlockObject = require('./nodes/BlockObject'); var _BlockObject2 = _interopRequireDefault(_BlockObject); var _ContentBlock = require('./nodes/ContentBlock'); var _ContentBlock2 = _interopRequireDefault(_ContentBlock); var _Decorator = require('./nodes/Decorator'); var _Decorator2 = _interopRequireDefault(_Decorator); var _InlineObject = require('./nodes/InlineObject'); var _InlineObject2 = _interopRequireDefault(_InlineObject); var _Span = require('./nodes/Span'); var _Span2 = _interopRequireDefault(_Span); var _Editor = require('./styles/Editor.css'); var _Editor2 = _interopRequireDefault(_Editor); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /*:: import type { Block, BlockContentFeatures, Marker, SlateChange, SlateComponentProps, SlateMarkProps, SlateValue, Type } from './typeDefs'*/ /*:: import type {Node} from 'react'*/ /*:: type Props = { blockContentFeatures: BlockContentFeatures, editorValue: SlateValue, fullscreen: boolean, markers: Marker[], onBlur: (nextPath: []) => void, onChange: (change: SlateChange) => void, onEditorBlur: void => void, onEditorFocus: void => void, onFocus: (nextPath: []) => void, onFormBuilderInputBlur: (nextPath: []) => void, onFormBuilderInputFocus: (nextPath: []) => void, onPaste?: ( event: SyntheticEvent, path: [], type: Type, value: ?Value ) => {insert?: Value, path?: []}, onPatch: (event: PatchEvent) => void, onToggleFullScreen: void => void, readOnly?: boolean, renderBlockActions?: (block: Block) => React.Node, renderCustomMarkers?: (Marker[]) => React.Node, setFocus: void => void, type: Type, undoRedoStack: {undo: [], redo: []}, value: Block[] }*/ class Editor extends _react2.default.Component /*:: <Props>*/ { constructor(props /*: Props*/) { super(props); _initialiseProps.call(this); this._plugins = [(0, _ListItemOnEnterKeyPlugin2.default)({ defaultBlock: _blockTools.EDITOR_DEFAULT_BLOCK_TYPE }), (0, _ListItemOnTabKeyPlugin2.default)(), (0, _TextBlockOnEnterKeyPlugin2.default)({ defaultBlock: _blockTools.EDITOR_DEFAULT_BLOCK_TYPE }), (0, _SetMarksOnKeyComboPlugin2.default)({ decorators: props.blockContentFeatures.decorators.map(item => item.value) }), (0, _slateSoftBreak2.default)({ onlyIn: [_blockTools.EDITOR_DEFAULT_BLOCK_TYPE.type], shift: true }), (0, _PastePlugin2.default)({ blockContentType: props.type }), (0, _slateInsertBlockOnEnter2.default)(_blockTools.EDITOR_DEFAULT_BLOCK_TYPE), (0, _UpdateCustomNodesPlugin2.default)(), (0, _OnDropPlugin2.default)(), (0, _UndoRedoPlugin2.default)({ stack: props.undoRedoStack, onChange: props.onChange, editorValue: props.editorValue, blockContentType: props.type })]; this._validateNode = (0, _createNodeValidator2.default)(props.type, this.getValue); } componentDidMount() { var _props = this.props; const editorValue = _props.editorValue, focusPath = _props.focusPath; if ((focusPath || []).length) { const block = editorValue.document.getDescendant(focusPath[0]._key); if (!block) { return; } // Wait for things to get finshed rendered before scrolling there setTimeout(() => window.requestAnimationFrame(() => { const element = (0, _slateReact.findDOMNode)(block); element.scrollIntoView({ behavior: 'instant', block: 'center', inline: 'nearest' }); }), 0); } } // When focusPath has changed, but the editorValue has another focusBlock, // select the block according to the focusPath and scroll there // eslint-disable-next-line complexity componentWillReceiveProps(nextProps /*: Props*/) { const focusPath = nextProps.focusPath, editorValue = nextProps.editorValue, onChange = nextProps.onChange, readOnly = nextProps.readOnly; if (!focusPath || focusPath.length === 0) { return; } const focusPathIsSingleBlock = editorValue.focusBlock && (0, _isEqual3.default)(focusPath, [{ _key: editorValue.focusBlock.key }]); const focusPathChanged = !(0, _isEqual3.default)(this.props.focusPath, nextProps.focusPath); const change = editorValue.change(); const block = editorValue.document.getDescendant(focusPath[0]._key); let inline; if (focusPathChanged && !focusPathIsSingleBlock) { if (focusPath[1] && focusPath[1] === 'children' && focusPath[2]) { // Inline object inline = editorValue.document.getDescendant(focusPath[2]._key); this.scrollIntoView(change, inline); } else if ( // Annotation focusPath[1] && focusPath[1] === 'markDefs' && focusPath[2] && (inline = (0, _findInlineByAnnotationKey2.default)(focusPath[2]._key, block))) { this.scrollIntoView(change, inline); } else if (block) { // Regular block this.scrollIntoView(change, block); } } else if (focusPathChanged && !readOnly) { // Must be here to set focus after editing interfaces are closed change.focus(); onChange(change); } } scrollIntoView(change, node) { var _props2 = this.props; const onChange = _props2.onChange, readOnly = _props2.readOnly; const element = (0, _slateReact.findDOMNode)(node); element.scrollIntoView({ behavior: 'instant', block: 'center', inline: 'nearest' }); if (!readOnly) { change.collapseToEndOf(node); onChange(change); } } // When user changes the selection in the editor, update focusPath accordingly. getEditor() { return this._editor; } render() { var _props3 = this.props; const editorValue = _props3.editorValue, fullscreen = _props3.fullscreen, readOnly = _props3.readOnly, markers = _props3.markers, renderBlockActions = _props3.renderBlockActions, value = _props3.value; const hasMarkers = markers.filter(marker => marker.path.length > 0).length > 0; // Figure out if we have any block actions let hasBlockActions = false; if (renderBlockActions && value) { this._blockActionsMap = {}; value.forEach(block => { const actions = renderBlockActions(block); if (actions) { this._blockActionsMap[block._key] = actions; } }); hasBlockActions = Object.keys(this._blockActionsMap).length > 0; } const classNames = [_Editor2.default.root, (hasBlockActions || hasMarkers) && _Editor2.default.hasBlockExtras, fullscreen ? _Editor2.default.fullscreen : null].filter(Boolean); return _react2.default.createElement( 'div', { className: classNames.join(' ') }, _react2.default.createElement(_slateReact.Editor, { spellCheck: false, className: _Editor2.default.editor, ref: this.refEditor, value: editorValue, onChange: this.handleChange, onFocus: this.handleEditorFocus, onCopy: this.handleCopy, onPaste: this.handlePaste, onKeyDown: this.handleOnKeyDown, validateNode: this._validateNode, plugins: this._plugins, readOnly: readOnly, renderNode: this.renderNode, renderMark: this.renderMark }), _react2.default.createElement('div', { className: _Editor2.default.blockDragMarker, ref: this.refBlockDragMarker, style: { display: 'none' } }) ); } } exports.default = Editor; var _initialiseProps = function _initialiseProps() { this._blockActionsMap = {}; this._editor = null; this._plugins = []; this.handleChange = (change /*: slateChange*/) => { var _props4 = this.props; const onChange = _props4.onChange, onFocus = _props4.onFocus, focusPath = _props4.focusPath; const focusBlock = change.value.focusBlock; const path = []; if (focusBlock) { path.push({ _key: focusBlock.key }); } if (path.length && focusPath && focusPath.length === 1) { return onChange(change, () => onFocus(path)); } return onChange(change); }; this.handleEditorFocus = () => { const setFocus = this.props.setFocus; setFocus(); }; this.getValue = () => { return this.props.value; }; this.refEditor = (editor /*: ?SlateEditor*/) => { this._editor = editor; }; this.handleShowBlockDragMarker = (pos /*: string*/, node /*: HTMLDivElement*/) => { const editorDOMNode = _reactDom2.default.findDOMNode(this._editor); if (editorDOMNode instanceof HTMLElement) { const editorRect = editorDOMNode.getBoundingClientRect(); const elemRect = node.getBoundingClientRect(); const topPos = elemRect.top - editorRect.top; const bottomPos = topPos + (elemRect.bottom - elemRect.top); const top = pos === 'after' ? `${parseInt(bottomPos, 10)}px` : `${parseInt(topPos, 10)}px`; if (this._blockDragMarker) { this._blockDragMarker.style.display = 'block'; this._blockDragMarker.style.top = top; } } }; this.handleHideBlockDragMarker = () => { if (this._blockDragMarker) { this._blockDragMarker.style.display = 'none'; } }; this.handlePaste = event => { var _props5 = this.props; const focusPath = _props5.focusPath, onPatch = _props5.onPatch, value = _props5.value, type = _props5.type; const onPaste = this.props.onPaste || _onPaste2.default; if (onPaste) { const editorValue = this.props.editorValue; const change = editorValue.change(); var _change$value = change.value; const focusBlock = _change$value.focusBlock, focusKey = _change$value.focusKey, focusText = _change$value.focusText, focusInline = _change$value.focusInline; const path = []; if (focusBlock) { path.push({ _key: focusBlock.key }); } if (focusInline || focusText) { path.push('children'); path.push({ _key: focusKey }); } const result = onPaste({ event, value, path, type }); if (result && result.insert) { onPatch(_PatchEvent2.default.from([(0, _PatchEvent.insert)([result.insert], 'after', result.path || focusPath)])); return result.insert; } } return undefined; }; this.handleCopy = event => { if (_onCopy2.default) { return (0, _onCopy2.default)({ event }); } return undefined; }; this.handleOnKeyDown = event => { const isFullscreenKey = (0, _isHotkey.isKeyHotkey)('mod+enter'); const onToggleFullScreen = this.props.onToggleFullScreen; if (isFullscreenKey(event)) { event.preventDefault(); event.stopPropagation(); onToggleFullScreen(); return true; } return undefined; }; this.refBlockDragMarker = (blockDragMarker /*: ?HTMLDivElement*/) => { this._blockDragMarker = blockDragMarker; }; this.renderNode = (props /*: SlateComponentProps*/) => { var _props6 = this.props; const blockContentFeatures = _props6.blockContentFeatures, editorValue = _props6.editorValue, focusPath = _props6.focusPath, markers = _props6.markers, onChange = _props6.onChange, onFocus = _props6.onFocus, onPatch = _props6.onPatch, readOnly = _props6.readOnly, value = _props6.value, renderCustomMarkers = _props6.renderCustomMarkers, type = _props6.type; const node = props.node; let childMarkers = markers.filter(marker => marker.path.length > 0).filter(marker => marker.path[0]._key === node.data.get('_key')); let ObjectClass = _BlockObject2.default; let ObjectType = blockContentFeatures.types.blockObjects.find(memberType => memberType.name === node.type); if (node.object === 'inline') { ObjectClass = _InlineObject2.default; ObjectType = blockContentFeatures.types.inlineObjects.find(memberType => memberType.name === node.type); childMarkers = markers.filter(marker => marker.path[2] && marker.path[2]._key === node.data.get('_key')); } if (node.type === 'span') { childMarkers = markers.filter(marker => marker.path[2] && marker.path[2]._key === node.data.get('_key')); // Add any markers for related markDefs here as well let annotations; if (annotations = node.data.get('annotations')) { const block = editorValue.document.getParent(node.key); Object.keys(annotations).forEach(key => { childMarkers = childMarkers.concat(markers.filter(marker => marker.path[0]._key === block.key && marker.path[1] === 'markDefs' && marker.path[2]._key === annotations[key]._key)); }); } } // Set prop on blocks that are included in focusPath let hasFormBuilderFocus = false; if (node.object === 'block') { hasFormBuilderFocus = focusPath ? (0, _pathUtils.hasItemFocus)(focusPath, { _key: node.key }) : false; } switch (node.type) { case 'contentBlock': return _react2.default.createElement(_ContentBlock2.default, _extends({}, props, { editorValue: editorValue, onChange: onChange, onFocus: onFocus, blockContentFeatures: blockContentFeatures, hasFormBuilderFocus: hasFormBuilderFocus, markers: childMarkers, readOnly: readOnly, blockActions: this._blockActionsMap[node.key], renderCustomMarkers: renderCustomMarkers, block: value ? value.find(blk => blk._key === node.key) : (0, _blockTools.editorValueToBlocks)({ document: { nodes: [node.toJSON(_changeToPatches.VALUE_TO_JSON_OPTS)] } }, type)[0] })); case 'span': return _react2.default.createElement( _Span2.default, { attributes: props.attributes, blockContentFeatures: blockContentFeatures, editorValue: editorValue, markers: childMarkers, node: props.node, onChange: onChange, onFocus: onFocus, onPatch: onPatch, readOnly: readOnly, type: blockContentFeatures.types.span }, props.children ); default: return _react2.default.createElement(ObjectClass, { attributes: props.attributes, blockContentFeatures: blockContentFeatures, editor: props.editor, editorValue: editorValue, hasFormBuilderFocus: hasFormBuilderFocus, isSelected: props.isSelected, markers: childMarkers, node: props.node, onChange: onChange, onDrag: this.handleDrag, onFocus: onFocus, onHideBlockDragMarker: this.handleHideBlockDragMarker, onPatch: onPatch, onShowBlockDragMarker: this.handleShowBlockDragMarker, readOnly: readOnly, blockActions: this._blockActionsMap[node.key], renderCustomMarkers: renderCustomMarkers, type: ObjectType }); } }; this.renderMark = (props /*: SlateMarkProps*/) => { const blockContentFeatures = this.props.blockContentFeatures; const type = props.mark.type; const decorator = blockContentFeatures.decorators.find(item => item.value === type); const CustomComponent = decorator && decorator.blockEditor && decorator.blockEditor.render ? decorator.blockEditor.render : null; if (CustomComponent) { return _react2.default.createElement(CustomComponent, props); } return decorator ? _react2.default.createElement(_Decorator2.default, props) : null; }; };