UNPKG

@lyra/form-builder

Version:
449 lines (364 loc) 14.2 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 _reactDom = require('react-dom'); var _reactDom2 = _interopRequireDefault(_reactDom); var _slateBase64Serializer = require('slate-base64-serializer'); var _slateBase64Serializer2 = _interopRequireDefault(_slateBase64Serializer); var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _slate = require('slate'); var _slateReact = require('slate-react'); var _router = require('part:@lyra/base/router'); var _linkIcon = require('part:@lyra/base/link-icon'); var _linkIcon2 = _interopRequireDefault(_linkIcon); var _stacked = require('part:@lyra/components/utilities/stacked'); var _stacked2 = _interopRequireDefault(_stacked); var _escapable = require('part:@lyra/components/utilities/escapable'); var _escapable2 = _interopRequireDefault(_escapable); var _reactTippy = require('react-tippy'); var _classnames = require('classnames'); var _classnames2 = _interopRequireDefault(_classnames); var _resolveTypeName = require('../../../utils/resolveTypeName'); var _PatchEvent = require('../../../PatchEvent'); var _pathUtils = require('../../../utils/pathUtils'); var _DeleteButton = require('../DeleteButton'); var _DeleteButton2 = _interopRequireDefault(_DeleteButton); var _EditButton = require('../EditButton'); var _EditButton2 = _interopRequireDefault(_EditButton); var _InvalidValueInput = require('../../InvalidValueInput'); var _InvalidValueInput2 = _interopRequireDefault(_InvalidValueInput); var _Preview = require('../../../Preview'); var _Preview2 = _interopRequireDefault(_Preview); var _InlineObject = require('./styles/InlineObject.css'); var _InlineObject2 = _interopRequireDefault(_InlineObject); var _ViewButton = require('../ViewButton'); var _ViewButton2 = _interopRequireDefault(_ViewButton); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /*:: import type { BlockContentFeatures, SlateValue, Type, SlateChange, Marker } from '../typeDefs'*/ /*:: import type {Node} from 'react'*/ /*:: type Props = { attributes: {}, blockContentFeatures: BlockContentFeatures, children: Node, editor: Editor, editorIsFocused: boolean, editorValue: SlateValue, hasFormBuilderFocus: boolean, isSelected: boolean, markers: Marker[], node: Block, onChange: (change: SlateChange) => void, onFocus: (nextPath: []) => void, onPatch: (event: PatchEvent) => void, readOnly?: boolean, type: ?Type }*/ /*:: type State = { isDragging: boolean, menuOpen: boolean }*/ const NOOP = () => {}; function shouldUpdateDropTarget(range, dropTarget) { if (!dropTarget) { return true; } return range.focusOffset !== dropTarget.range.focusOffset; } class InlineObject extends _react2.default.Component /*:: <Props, State>*/ { constructor(...args) { var _temp; return _temp = super(...args), this.rootElement = null, this.state = { isDragging: false, menuOpen: false }, this.handleDragStart = event => { const node = this.props.node; this.setState({ isDragging: true }); this.addDragHandlers(); const element = _reactDom2.default.findDOMNode(this.previewContainer); const encoded = _slateBase64Serializer2.default.serializeNode(node, { preserveKeys: true }); (0, _slateReact.setEventTransfer)(event, 'node', encoded); event.dataTransfer.effectAllowed = 'move'; event.dataTransfer.setDragImage(element, element.clientWidth / 2, -10); }, this.handleDragLeave = event => { if (event.target === this._editorNode) { this.resetDropTarget(); } }, this.handleDragOverOtherNode = event => { if (!this.state.isDragging) { return; } const targetDOMNode = event.target; // As the event is registered on the editor parent node // ignore the event if it is coming from from the editor node itself if (targetDOMNode === this._editorNode) { return; } var _props = this.props; const editorValue = _props.editorValue, onChange = _props.onChange; const range = (0, _slateReact.getEventRange)(event, editorValue); if (range === null || typeof range.focusOffset === undefined) { return; } const targetNode = editorValue.document.getDescendant(range.focusKey); // If we are dragging over another inline return if (editorValue.document.getClosestInline(targetNode.key)) { return; } // If we are dragging over a custom type block return const block = editorValue.document.getClosestBlock(range.focusKey); if (block && block.type !== 'contentBlock') { return; } const moveCursorChange = this.moveCursor(range, targetNode); const finalRange = moveCursorChange.value.selection; if (shouldUpdateDropTarget(finalRange, this._dropTarget)) { this._dropTarget = { node: targetNode, range: finalRange }; onChange(moveCursorChange); } }, this.handleDragEnd = event => { this.setState({ isDragging: false }); var _props2 = this.props; const onChange = _props2.onChange, node = _props2.node, editorValue = _props2.editorValue; const target = this._dropTarget; // Return if this is our node if (!target || target.node === node) { this.resetDropTarget(); return; } const change = editorValue.change().select(target.range).removeNodeByKey(node.key).insertInline(node).collapseToEndOf(node).focus(); onChange(change); this.resetDropTarget(); }, this.handleInvalidValue = (event /*: PatchEvent*/) => { let _event = event; var _props3 = this.props; const editorValue = _props3.editorValue, onPatch = _props3.onPatch; const focusBlock = editorValue.focusBlock; const value = this.getValue(); const path = [{ _key: focusBlock.key }, 'children', { _key: value._key }]; path.reverse().forEach(part => { _event = _event.prefixAll(part); }); onPatch(_event, value); }, this.handleRemoveValue = event => { event.preventDefault(); event.stopPropagation(); var _props4 = this.props; const editorValue = _props4.editorValue, node = _props4.node, onChange = _props4.onChange; const change = editorValue.change(); onChange(change.removeNodeByKey(node.key).focus()); }, this.handleCancelEvent = event => { event.stopPropagation(); event.preventDefault(); }, this.handleEditStart = event => { event.stopPropagation(); var _props5 = this.props; const node = _props5.node, onFocus = _props5.onFocus, onChange = _props5.onChange, editorValue = _props5.editorValue; const focusBlock = editorValue.focusBlock; const change = editorValue.change().collapseToEndOf(node).focus().blur(); onChange(change, () => onFocus([{ _key: focusBlock.key }, 'children', { _key: node.key }, _pathUtils.FOCUS_TERMINATOR])); }, this.handleView = event => { event.stopPropagation(); var _props6 = this.props; const node = _props6.node, onFocus = _props6.onFocus, editorValue = _props6.editorValue; const focusBlock = editorValue.focusBlock; onFocus([{ _key: focusBlock.key }, 'children', { _key: node.key }, _pathUtils.FOCUS_TERMINATOR]); }, this.handleCloseMenu = () => { this.setState({ menuOpen: false }); }, this.refFormBuilderBlock = formBuilderBlock => { this.formBuilderBlock = formBuilderBlock; }, this.refPreview = previewContainer => { this.previewContainer = previewContainer; }, this.handleShowMenu = () => { this.setState({ menuOpen: true }); }, _temp; } componentDidMount() { const editor = this.props.editor; this._editorNode = _reactDom2.default.findDOMNode(editor); } componentWillUnmount() { this.removeDragHandlers(); } addDragHandlers() { this._editorNode.addEventListener('dragover', this.handleDragOverOtherNode); this._editorNode.addEventListener('dragleave', this.handleDragLeave); } removeDragHandlers() { this._editorNode.removeEventListener('dragover', this.handleDragOverOtherNode); this._editorNode.removeEventListener('dragleave', this.handleDragLeave); } // Remove the drop target if we leave the editors nodes resetDropTarget() { this._dropTarget = null; } moveCursor(range, node) { const editorValue = this.props.editorValue; let theOffset = range.focusOffset; // Check if it is acceptable to move the cursor here const nextChars = editorValue.document.getCharactersAtRange(_slate.Range.create({ anchorKey: node.key, focusKey: node.key, anchorOffset: theOffset - 1, focusOffset: theOffset, isFocused: true, isBackward: false })); if (!nextChars.size) { theOffset = 0; } const change = editorValue.change().collapseToStartOf(node).move(theOffset).focus(); return change; } getValue() { return this.props.node.data.get('value'); } renderMenu(value) { const readOnly = this.props.readOnly; return _react2.default.createElement( _stacked2.default, null, isActive => { return _react2.default.createElement( _escapable2.default, { onEscape: isActive ? this.handleCloseMenu : NOOP }, _react2.default.createElement( 'div', { className: _InlineObject2.default.functions }, value._ref && _react2.default.createElement( _router.IntentLink, { className: _InlineObject2.default.linkToReference, intent: 'edit', params: { id: value._ref } }, _react2.default.createElement(_linkIcon2.default, null) ), readOnly && _react2.default.createElement( _ViewButton2.default, { title: 'View this object', onClick: this.handleView }, 'View' ), !readOnly && _react2.default.createElement( _EditButton2.default, { title: 'Edit this object', onClick: this.handleEditStart }, 'Edit' ), !readOnly && _react2.default.createElement( _DeleteButton2.default, { title: 'Remove this object', onClick: this.handleRemoveValue }, 'Delete' ) ) ); } ); } renderPreview(value) { const type = this.props.type; const menuOpen = this.state.menuOpen; const valueKeys = value ? Object.keys(value) : []; const isEmpty = !value || (0, _isEqual3.default)(valueKeys.sort(), ['_key', '_type'].sort()); return _react2.default.createElement( _reactTippy.Tooltip, { arrow: true, theme: 'light', trigger: 'manual', open: menuOpen, position: 'bottom', interactive: true, useContext: true, duration: 100, style: { padding: 0, display: 'inline-block', minWidth: '1em' }, unmountHTMLWhenHide: true, onRequestClose: menuOpen && this.handleCloseMenu, html: this.renderMenu(value) }, !isEmpty && _react2.default.createElement(_Preview2.default, { type: type, value: value, layout: 'inline' }), isEmpty && _react2.default.createElement( 'span', null, 'Click to edit' ) ); } render() { var _props7 = this.props; const attributes = _props7.attributes, node = _props7.node, editorValue = _props7.editorValue, isSelected = _props7.isSelected, readOnly = _props7.readOnly, markers = _props7.markers, blockContentFeatures = _props7.blockContentFeatures; const value = this.getValue(); const valueType = (0, _resolveTypeName.resolveTypeName)(value); const validTypes = blockContentFeatures.types.inlineObjects.map(objType => objType.name); if (!validTypes.includes(valueType)) { return _react2.default.createElement( 'div', { onClick: this.handleCancelEvent }, _react2.default.createElement(_InvalidValueInput2.default, { validTypes: validTypes, actualType: valueType, value: value, onChange: this.handleInvalidValue }) ); } const validation = markers.filter(marker => marker.type === 'validation'); const errors = validation.filter(marker => marker.level === 'error'); const classname = (0, _classnames2.default)([_InlineObject2.default.root, editorValue.selection.hasFocusIn(node) && _InlineObject2.default.focused, isSelected && _InlineObject2.default.selected, errors.length > 0 && _InlineObject2.default.hasErrors]); return _react2.default.createElement( 'span', _extends({}, attributes, { onDragStart: this.handleDragStart, onDragEnd: this.handleDragEnd, onDragEnter: this.handleCancelEvent, onDragLeave: this.handleCancelEvent, onDrop: this.handleCancelEvent, draggable: !readOnly, ref: this.refFormBuilderBlock, className: classname, onClick: this.handleShowMenu }), _react2.default.createElement( 'span', { ref: this.refPreview, className: _InlineObject2.default.previewContainer }, this.renderPreview(value) ) ); } } exports.default = InlineObject;