UNPKG

@atlaskit/editor-common

Version:

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

278 lines (277 loc) • 12.9 kB
import _defineProperty from "@babel/runtime/helpers/defineProperty"; import _extends from "@babel/runtime/helpers/extends"; /* eslint-disable @repo/internal/react/no-class-components */ import React, { Component, useEffect, useLayoutEffect, useRef, useState } from 'react'; import memoizeOne from 'memoize-one'; import { NodeSelection } from '@atlaskit/editor-prosemirror/state'; import { fg } from '@atlaskit/platform-feature-flags'; import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals'; import { getExtensionModuleNodePrivateProps, getNodeRenderer } from '../extensions'; import { getExtensionRenderer, nodeToJSON, toJSON } from '../utils'; import Extension from './Extension/Extension'; import { isEmptyBodiedMacro } from './Extension/Extension/extension-utils'; import InlineExtension from './Extension/InlineExtension'; import MultiBodiedExtension from './MultiBodiedExtension'; const getBodiedExtensionContent = node => { const bodiedExtensionContent = []; node.content.forEach(childNode => { bodiedExtensionContent.push(nodeToJSON(childNode)); }); return !!bodiedExtensionContent.length ? bodiedExtensionContent : node.attrs.text; }; export const ExtensionComponent = props => { const { extensionProvider: extensionProviderResolver, showLivePagesBodiedMacrosRendererView, node, ...restProps } = props; const [extensionProvider, setExtensionProvider] = useState(undefined); const [showBodiedExtensionRendererView, setShowBodiedExtensionRendererView] = useState(!!(showLivePagesBodiedMacrosRendererView !== null && showLivePagesBodiedMacrosRendererView !== void 0 && showLivePagesBodiedMacrosRendererView(nodeToJSON(node))) && !isEmptyBodiedMacro(node)); const mountedRef = useRef(true); useLayoutEffect(() => { mountedRef.current = true; return () => { mountedRef.current = false; }; }, []); useEffect(() => { extensionProviderResolver === null || extensionProviderResolver === void 0 ? void 0 : extensionProviderResolver.then(provider => { if (mountedRef.current) { setExtensionProvider(provider); } }); }, [extensionProviderResolver]); return /*#__PURE__*/React.createElement(ExtensionComponentInner // Ignored via go/ees005 // eslint-disable-next-line react/jsx-props-no-spreading , _extends({}, restProps, { extensionProvider: extensionProvider, node: node, showLivePagesBodiedMacrosRendererView: showLivePagesBodiedMacrosRendererView, showBodiedExtensionRendererView: showBodiedExtensionRendererView, setShowBodiedExtensionRendererView: setShowBodiedExtensionRendererView })); }; class ExtensionComponentInner extends Component { constructor(...args) { super(...args); _defineProperty(this, "privatePropsParsed", false); _defineProperty(this, "state", {}); // memoized to avoid rerender on extension state changes _defineProperty(this, "getNodeRenderer", memoizeOne(getNodeRenderer)); _defineProperty(this, "getExtensionModuleNodePrivateProps", memoizeOne(getExtensionModuleNodePrivateProps)); _defineProperty(this, "setIsNodeHovered", isHovered => { // Don't want to show hover interactions for live page view mode if (!this.props.isLivePageViewMode) { this.setState({ isNodeHovered: isHovered }); } }); /** * 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.props.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.props.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, showBodiedExtensionRendererView, rendererExtensionHandlers } = this.props; const { extensionType, extensionKey, parameters, text } = pmNode.attrs; const isBodiedExtension = pmNode.type.name === 'bodiedExtension'; const { selection } = editorView.state; const isSelected = selection instanceof NodeSelection && selection.node === pmNode; if (isBodiedExtension && !showBodiedExtensionRendererView) { 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 content = isBodiedExtension ? getBodiedExtensionContent(pmNode) : text; const node = { type: pmNode.type.name, extensionType, extensionKey, parameters, content, localId: pmNode.attrs.localId, fragmentLocalId }; if (isBodiedExtension) { const rendererExtensionHandler = rendererExtensionHandlers === null || rendererExtensionHandlers === void 0 ? void 0 : rendererExtensionHandlers[extensionType]; // Forge bodied extensions don't get rendererExtensionHandlers passed in and use extensionHandlerFromProvider from the below logic instead if (rendererExtensionHandler) { return getExtensionRenderer(rendererExtensionHandler)(node, toJSON(editorView.state.doc)); } } let result; if (extensionHandlers && extensionHandlers[extensionType]) { const render = getExtensionRenderer(extensionHandlers[extensionType]); result = render(node, editorView.state.doc, actions); } if (!result) { const extensionHandlerFromProvider = this.props.extensionProvider && this.getNodeRenderer(this.props.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 }); } return /*#__PURE__*/React.createElement(NodeRenderer, { node: node, references: this.props.references, isSelected: isSelected, showUnknownMacroPlaceholder: fg('tinymce_display_unknown_macro_body_content') }); } } return result; }); } componentDidUpdate() { this.parsePrivateNodePropsIfNeeded(); } render() { var _this$state$_privateP2; const { node, handleContentDOMRef, editorView, references, editorAppearance, pluginInjectionApi, getPos, eventDispatcher, macroInteractionDesignFeatureFlags, extensionProvider, showLivePagesBodiedMacrosRendererView, showUpdatedLivePages1PBodiedExtensionUI, showBodiedExtensionRendererView, setShowBodiedExtensionRendererView, isLivePageViewMode } = this.props; const { selection } = editorView.state; const selectedNode = selection instanceof NodeSelection && selection.node; const position = typeof getPos === 'function' && getPos(); const resolvedPosition = position && editorView.state.doc.resolve(position); const isNodeNested = !!(resolvedPosition && resolvedPosition.depth > 0); if (node.type.name === 'multiBodiedExtension') { var _this$state$_privateP; const allowBodiedOverride = (_this$state$_privateP = this.state._privateProps) === null || _this$state$_privateP === void 0 ? void 0 : _this$state$_privateP.__allowBodiedOverride; return /*#__PURE__*/React.createElement(MultiBodiedExtension, { node: node, editorView: editorView, getPos: getPos, handleContentDOMRef: handleContentDOMRef, tryExtensionHandler: this.tryExtensionHandler.bind(this), eventDispatcher: eventDispatcher, pluginInjectionApi: pluginInjectionApi, editorAppearance: editorAppearance, macroInteractionDesignFeatureFlags: macroInteractionDesignFeatureFlags, isNodeSelected: selectedNode === node, isNodeNested: isNodeNested, isNodeHovered: expValEquals('cc_editor_ttvc_release_bundle_one', 'extensionHoverRefactor', true) ? undefined : this.state.isNodeHovered, setIsNodeHovered: expValEquals('cc_editor_ttvc_release_bundle_one', 'extensionHoverRefactor', true) ? undefined : this.setIsNodeHovered, isLivePageViewMode: isLivePageViewMode, allowBodiedOverride: allowBodiedOverride }); } 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: extensionProvider, handleContentDOMRef: handleContentDOMRef, view: editorView, editorAppearance: editorAppearance, hideFrame: (_this$state$_privateP2 = this.state._privateProps) === null || _this$state$_privateP2 === void 0 ? void 0 : _this$state$_privateP2.__hideFrame, pluginInjectionApi: pluginInjectionApi, macroInteractionDesignFeatureFlags: macroInteractionDesignFeatureFlags, isNodeSelected: selectedNode === node, isNodeHovered: expValEquals('cc_editor_ttvc_release_bundle_one', 'extensionHoverRefactor', true) ? undefined : this.state.isNodeHovered, isNodeNested: isNodeNested, setIsNodeHovered: expValEquals('cc_editor_ttvc_release_bundle_one', 'extensionHoverRefactor', true) ? undefined : this.setIsNodeHovered, showLivePagesBodiedMacrosRendererView: !!(showLivePagesBodiedMacrosRendererView !== null && showLivePagesBodiedMacrosRendererView !== void 0 && showLivePagesBodiedMacrosRendererView(nodeToJSON(node))), showUpdatedLivePages1PBodiedExtensionUI: !!(showUpdatedLivePages1PBodiedExtensionUI !== null && showUpdatedLivePages1PBodiedExtensionUI !== void 0 && showUpdatedLivePages1PBodiedExtensionUI(nodeToJSON(node))), showBodiedExtensionRendererView: showBodiedExtensionRendererView, setShowBodiedExtensionRendererView: setShowBodiedExtensionRendererView, isLivePageViewMode: isLivePageViewMode }, extensionHandlerResult); case 'inlineExtension': return /*#__PURE__*/React.createElement(InlineExtension, { node: node, macroInteractionDesignFeatureFlags: macroInteractionDesignFeatureFlags, isNodeSelected: selectedNode === node, pluginInjectionApi: pluginInjectionApi, isNodeHovered: expValEquals('cc_editor_ttvc_release_bundle_one', 'extensionHoverRefactor', true) ? undefined : this.state.isNodeHovered, setIsNodeHovered: expValEquals('cc_editor_ttvc_release_bundle_one', 'extensionHoverRefactor', true) ? undefined : this.setIsNodeHovered, isLivePageViewMode: isLivePageViewMode }, 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; } }