UNPKG

@atlaskit/editor-common

Version:

A package that contains common classes and components for editor and renderer

226 lines (225 loc) • 8.46 kB
import _defineProperty from "@babel/runtime/helpers/defineProperty"; import React, { Component } from 'react'; import memoizeOne from 'memoize-one'; import { NodeSelection } from '@atlaskit/editor-prosemirror/state'; import { getExtensionModuleNodePrivateProps, getNodeRenderer } from '../extensions'; import { getExtensionRenderer } from '../utils'; import Extension from './Extension/Extension'; import InlineExtension from './Extension/InlineExtension'; import MultiBodiedExtension from './MultiBodiedExtension'; export class ExtensionComponent extends Component { constructor(...args) { super(...args); _defineProperty(this, "privatePropsParsed", false); _defineProperty(this, "state", {}); _defineProperty(this, "mounted", false); // memoized to avoid rerender on extension state changes _defineProperty(this, "getNodeRenderer", memoizeOne(getNodeRenderer)); _defineProperty(this, "getExtensionModuleNodePrivateProps", memoizeOne(getExtensionModuleNodePrivateProps)); _defineProperty(this, "setIsNodeHovered", isHovered => { this.setState({ isNodeHovered: isHovered }); }); _defineProperty(this, "setStateFromPromise", (stateKey, promise) => { promise && promise.then(p => { if (!this.mounted) { return; } this.setState({ [stateKey]: p }); }); }); /** * Parses any private nodes once an extension provider is available. * * We do this separately from resolving a node renderer component since the * private props come from extension provider, rather than an extension * handler which only handles `render`/component concerns. */ _defineProperty(this, "parsePrivateNodePropsIfNeeded", async () => { if (this.privatePropsParsed || !this.state.extensionProvider) { return; } this.privatePropsParsed = true; const { extensionType, extensionKey } = this.props.node.attrs; /** * getExtensionModuleNodePrivateProps can throw if there are issues in the * manifest */ try { const privateProps = await this.getExtensionModuleNodePrivateProps(this.state.extensionProvider, extensionType, extensionKey); this.setState({ _privateProps: privateProps }); } catch (e) { // eslint-disable-next-line no-console console.error('Provided extension handler has thrown an error\n', e); /** We don't want this error to block renderer */ /** We keep rendering the default content */ } }); _defineProperty(this, "handleExtension", (pmNode, actions) => { var _pmNode$marks, _pmNode$marks$find, _pmNode$marks$find$at; const { extensionHandlers, editorView } = this.props; const { extensionType, extensionKey, parameters, text } = pmNode.attrs; const isBodiedExtension = pmNode.type.name === 'bodiedExtension'; if (isBodiedExtension) { return; } const fragmentLocalId = pmNode === null || pmNode === void 0 ? void 0 : (_pmNode$marks = pmNode.marks) === null || _pmNode$marks === void 0 ? void 0 : (_pmNode$marks$find = _pmNode$marks.find(m => m.type.name === 'fragment')) === null || _pmNode$marks$find === void 0 ? void 0 : (_pmNode$marks$find$at = _pmNode$marks$find.attrs) === null || _pmNode$marks$find$at === void 0 ? void 0 : _pmNode$marks$find$at.localId; const node = { type: pmNode.type.name, extensionType, extensionKey, parameters, content: text, localId: pmNode.attrs.localId, fragmentLocalId }; let result; if (extensionHandlers && extensionHandlers[extensionType]) { const render = getExtensionRenderer(extensionHandlers[extensionType]); result = render(node, editorView.state.doc, actions); } if (!result) { const extensionHandlerFromProvider = this.state.extensionProvider && this.getNodeRenderer(this.state.extensionProvider, extensionType, extensionKey); if (extensionHandlerFromProvider) { const NodeRenderer = extensionHandlerFromProvider; if (node.type === 'multiBodiedExtension') { return /*#__PURE__*/React.createElement(NodeRenderer, { node: node, references: this.props.references, actions: actions }); } else { return /*#__PURE__*/React.createElement(NodeRenderer, { node: node, references: this.props.references }); } } } return result; }); } UNSAFE_componentWillMount() { this.mounted = true; } componentDidMount() { const { extensionProvider } = this.props; if (extensionProvider) { this.setStateFromPromise('extensionProvider', extensionProvider); } } componentDidUpdate() { this.parsePrivateNodePropsIfNeeded(); } componentWillUnmount() { this.mounted = false; } UNSAFE_componentWillReceiveProps(nextProps) { const { extensionProvider } = nextProps; if (extensionProvider && this.props.extensionProvider !== extensionProvider) { this.setStateFromPromise('extensionProvider', extensionProvider); } } render() { var _this$state$_privateP; const { node, handleContentDOMRef, editorView, references, editorAppearance, pluginInjectionApi, getPos, eventDispatcher, showMacroInteractionDesignUpdates } = this.props; const { selection } = editorView.state; const selectedNode = selection instanceof NodeSelection && selection.node; if (node.type.name === 'multiBodiedExtension') { return /*#__PURE__*/React.createElement(MultiBodiedExtension, { node: node, editorView: editorView, getPos: getPos, handleContentDOMRef: handleContentDOMRef, tryExtensionHandler: this.tryExtensionHandler.bind(this), eventDispatcher: eventDispatcher, pluginInjectionApi: pluginInjectionApi, editorAppearance: editorAppearance, showMacroInteractionDesignUpdates: showMacroInteractionDesignUpdates, isNodeSelected: selectedNode === node, isNodeHovered: this.state.isNodeHovered, setIsNodeHovered: this.setIsNodeHovered }); } const extensionHandlerResult = this.tryExtensionHandler(undefined); switch (node.type.name) { case 'extension': case 'bodiedExtension': return /*#__PURE__*/React.createElement(Extension, { node: node, getPos: this.props.getPos, references: references, extensionProvider: this.state.extensionProvider, handleContentDOMRef: handleContentDOMRef, view: editorView, editorAppearance: editorAppearance, hideFrame: (_this$state$_privateP = this.state._privateProps) === null || _this$state$_privateP === void 0 ? void 0 : _this$state$_privateP.__hideFrame, pluginInjectionApi: pluginInjectionApi, showMacroInteractionDesignUpdates: showMacroInteractionDesignUpdates, isNodeSelected: selectedNode === node, isNodeHovered: this.state.isNodeHovered, setIsNodeHovered: this.setIsNodeHovered }, extensionHandlerResult); case 'inlineExtension': return /*#__PURE__*/React.createElement(InlineExtension, { node: node, showMacroInteractionDesignUpdates: showMacroInteractionDesignUpdates, isNodeSelected: selectedNode === node, pluginInjectionApi: pluginInjectionApi, isNodeHovered: this.state.isNodeHovered, setIsNodeHovered: this.setIsNodeHovered }, extensionHandlerResult); default: return null; } } tryExtensionHandler(actions) { const { node } = this.props; try { const extensionContent = this.handleExtension(node, actions); if (extensionContent && /*#__PURE__*/React.isValidElement(extensionContent)) { return extensionContent; } } catch (e) { // eslint-disable-next-line no-console console.error('Provided extension handler has thrown an error\n', e); /** We don't want this error to block renderer */ /** We keep rendering the default content */ } return null; } }