UNPKG

@atlaskit/editor-common

Version:

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

314 lines (300 loc) • 13 kB
import _extends from "@babel/runtime/helpers/extends"; import _classCallCheck from "@babel/runtime/helpers/classCallCheck"; import _createClass from "@babel/runtime/helpers/createClass"; import _defineProperty from "@babel/runtime/helpers/defineProperty"; // Disable no-re-export rule for entry point files /* eslint-disable @atlaskit/editor/no-re-export */ import React from 'react'; import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals'; import { ACTION_SUBJECT, ACTION_SUBJECT_ID } from '../analytics'; import { isSSR } from '../core-utils'; import { createDispatch } from '../event-dispatcher'; import { ErrorBoundary } from '../ui/ErrorBoundary'; import { getPerformanceOptions, startMeasureReactNodeViewRendered, stopMeasureReactNodeViewRendered } from '../utils'; import { analyticsEventKey } from '../utils/analytics'; import { generateUniqueNodeKey } from './generateUniqueNodeKey'; export { getInlineNodeViewProducer, inlineNodeViewClassname } from './getInlineNodeViewProducer'; export { NodeViewContentHole } from './NodeViewContentHole'; var ReactNodeView = /*#__PURE__*/function () { function ReactNodeView(_node, view, getPos, portalProviderAPI, eventDispatcher, reactComponentProps, // Spreading props to pass through dynamic component props // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any reactComponent, viewShouldUpdate, shouldRenderImmediatelyInPortal) { var _this = this; _classCallCheck(this, ReactNodeView); // Spreading props to pass through dynamic component props // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any _defineProperty(this, "decorations", []); _defineProperty(this, "handleRef", function (node) { return _this._handleRef(node); }); _defineProperty(this, "dispatchAnalyticsEvent", function (payload) { if (_this.eventDispatcher) { var dispatch = createDispatch(_this.eventDispatcher); dispatch(analyticsEventKey, { payload: payload }); } }); this.node = _node; this.view = view; this.getPos = getPos; this.portalProviderAPI = portalProviderAPI; this.reactComponentProps = reactComponentProps || {}; this.reactComponent = reactComponent; this._viewShouldUpdate = viewShouldUpdate; this.eventDispatcher = eventDispatcher; this.key = generateUniqueNodeKey(); this.shouldRenderImmediatelyInPortal = shouldRenderImmediatelyInPortal || false; } /** * This method exists to move initialization logic out of the constructor, * so object can be initialized properly before calling render first time. * * Example: * Instance properties get added to an object only after super call in * constructor, which leads to some methods being undefined during the * first render. */ return _createClass(ReactNodeView, [{ key: "init", value: function init() { var _this2 = this; var shouldSkipInitRender = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; this.domRef = this.createDomRef(); this.setDomAttrs(this.node, this.domRef); var _ref = this.getContentDOM() || { dom: undefined, contentDOM: undefined }, contentDOMWrapper = _ref.dom, contentDOM = _ref.contentDOM; if (this.domRef && contentDOMWrapper) { this.domRef.appendChild(contentDOMWrapper); this.contentDOM = contentDOM ? contentDOM : contentDOMWrapper; this.contentDOMWrapper = contentDOMWrapper || contentDOM; } // @see ED-3790 // something gets messed up during mutation processing inside of a // nodeView if DOM structure has nested plain "div"s, it doesn't see the // difference between them and it kills the nodeView this.domRef.classList.add("".concat(this.node.type.name, "View-content-wrap")); var _getPerformanceOption = getPerformanceOptions(this.view), samplingRate = _getPerformanceOption.samplingRate, slowThreshold = _getPerformanceOption.slowThreshold, trackingEnabled = _getPerformanceOption.trackingEnabled; trackingEnabled && startMeasureReactNodeViewRendered({ nodeTypeName: this.node.type.name }); if (!shouldSkipInitRender) { this.renderReactComponent(function () { return _this2.render(_this2.reactComponentProps, _this2.handleRef); }); // During SSR, renderToStaticMarkup + container.innerHTML (in portal) clobbers the // contentDOMWrapper that was appended above. The React ref callback // (forwardRef) never fires in renderToStaticMarkup, so contentDOM is // left detached. Re-attach it by finding the marked SSR ref target. if (isSSR() && this.domRef && expValEquals('platform_editor_editor_ssr_streaming', 'isEnabled', true)) { var refTarget = this.domRef.querySelector('[data-ssr-content-dom-ref]'); if (refTarget) { this.handleRef(refTarget); } } } trackingEnabled && stopMeasureReactNodeViewRendered({ nodeTypeName: this.node.type.name, dispatchAnalyticsEvent: this.dispatchAnalyticsEvent, samplingRate: samplingRate, slowThreshold: slowThreshold }); return this; } // Spreading props to pass through dynamic component props // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any }, { key: "renderReactComponent", value: function renderReactComponent(component) { var _this3 = this; if (!this.domRef || !component) { return; } var componentWithErrorBoundary = function componentWithErrorBoundary() { var _this3$node$type$name, _this3$node; return /*#__PURE__*/React.createElement(ErrorBoundary, { component: ACTION_SUBJECT.REACT_NODE_VIEW, componentId: (_this3$node$type$name = _this3 === null || _this3 === void 0 || (_this3$node = _this3.node) === null || _this3$node === void 0 || (_this3$node = _this3$node.type) === null || _this3$node === void 0 ? void 0 : _this3$node.name) !== null && _this3$node$type$name !== void 0 ? _this3$node$type$name : ACTION_SUBJECT_ID.UNKNOWN_NODE, dispatchAnalyticsEvent: _this3.dispatchAnalyticsEvent }, component()); }; this.portalProviderAPI.render(componentWithErrorBoundary, // Spreading props to pass through dynamic component props // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion this.domRef, this.key, undefined, this.shouldRenderImmediatelyInPortal); } }, { key: "createDomRef", value: function createDomRef() { if (!this.node.isInline) { return document.createElement('div'); } var htmlElement = document.createElement('span'); return htmlElement; } }, { key: "getContentDOM", value: function getContentDOM() { return undefined; } }, { key: "_handleRef", value: function _handleRef(node) { var _this4 = this; var contentDOM = this.contentDOMWrapper || this.contentDOM; // @ts-ignore // Spreading props to pass through dynamic component props // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any var oldIgnoreMutation; var selectionBookmark; var mutationsIgnored = false; // move the contentDOM node inside the inner reference after rendering if (node && contentDOM && !node.contains(contentDOM)) { // @ts-ignore - ignoreMutation may not be declared oldIgnoreMutation = this.ignoreMutation; // store ref to previous ignoreMutation // ignore all mutations caused by ProseMirror's MutationObserver triggering // after DOM change, except selection changes // @ts-ignore ProseMirror adds selection type to MutationRecord this.ignoreMutation = function (m) { var isSelectionMutation = m.type === 'selection'; if (!isSelectionMutation) { mutationsIgnored = true; } return !isSelectionMutation; }; // capture document selection state before React DOM changes triggers ProseMirror selection change transaction if (this.view.state.selection.visible) { selectionBookmark = this.view.state.selection.getBookmark(); } node.appendChild(contentDOM); // After the next frame: requestAnimationFrame(function () { // Restore the original mutation handler // @ts-ignore - this may not have been declared by implementing class _this4.ignoreMutation = oldIgnoreMutation; // Restore the selection only if: // - We have a selection bookmark // - Mutations were ignored during the table move // - The bookmarked selection is different from the current selection. if (selectionBookmark && mutationsIgnored) { var resolvedSelection = selectionBookmark.resolve(_this4.view.state.tr.doc); // Don't set the selection if it's the same as the current selection. if (!resolvedSelection.eq(_this4.view.state.selection)) { var tr = _this4.view.state.tr.setSelection(resolvedSelection); tr.setMeta('source', 'ReactNodeView:_handleRef:selection-resync'); _this4.view.dispatch(tr); } } }); } } // Spreading props to pass through dynamic component props // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any }, { key: "render", value: function render(props, forwardRef) { return this.reactComponent ? /*#__PURE__*/React.createElement(this.reactComponent, _extends({ view: this.view, getPos: this.getPos, node: this.node, forwardRef: forwardRef // eslint-disable-next-line react/jsx-props-no-spreading -- Spreading props to pass through dynamic component props }, props)) : null; } }, { key: "update", value: function update(node, decorations, _innerDecorations) { var _this5 = this; var validUpdate = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : function () { return true; }; // @see https://github.com/ProseMirror/prosemirror/issues/648 var isValidUpdate = this.node.type === node.type && validUpdate(this.node, node); this.decorations = decorations; if (!isValidUpdate) { return false; } if (this.domRef && !this.node.sameMarkup(node)) { this.setDomAttrs(node, this.domRef); } // View should not process a re-render if this is false. // We dont want to destroy the view, so we return true. // TODO: ED-13910 - Fix viewShouldUpdate readonly decoration array if (!this.viewShouldUpdate(node, decorations)) { this.node = node; return true; } this.node = node; this.renderReactComponent(function () { return _this5.render(_this5.reactComponentProps, _this5.handleRef); }); return true; } }, { key: "viewShouldUpdate", value: function viewShouldUpdate(nextNode, _decorations) { if (this._viewShouldUpdate) { return this._viewShouldUpdate(nextNode); } return true; } /** * Copies the attributes from a ProseMirror Node to a DOM node. * @param node The Prosemirror Node from which to source the attributes */ }, { key: "setDomAttrs", value: function setDomAttrs(node, element) { Object.keys(node.attrs || {}).forEach(function (attr) { element.setAttribute(attr, node.attrs[attr]); }); } }, { key: "dom", get: function get() { // Only return reference if domRef is defined if (this.domRef === undefined) { //raise an error throw new Error('domRef is not defined or may have been destroyed'); } // Spreading props to pass through dynamic component props return this.domRef; } }, { key: "destroy", value: function destroy() { if (!this.domRef) { return; } this.portalProviderAPI.remove(this.key); this.domRef = undefined; this.contentDOM = undefined; } }], [{ key: "fromComponent", value: function fromComponent( // Spreading props to pass through dynamic component props // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any component, portalProviderAPI, eventDispatcher, props, viewShouldUpdate) { return function (node, view, getPos) { return new ReactNodeView(node, view, getPos, portalProviderAPI, eventDispatcher, props, component, viewShouldUpdate).init(); }; } }]); }(); export { ReactNodeView as default };