UNPKG

@atlaskit/editor-common

Version:

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

197 lines (186 loc) • 8.44 kB
import _slicedToArray from "@babel/runtime/helpers/slicedToArray"; // Disable no-re-export rule for entry point files /* eslint-disable @atlaskit/editor/no-re-export */ import { PluginKey } from '@atlaskit/editor-prosemirror/state'; import { cancelCallback, scheduleCallback } from './lazy-scheduler'; import { LazyNodeView, makeNodePlaceholderId } from './node-view'; export { convertToInlineCss } from './css-helper'; /** * 📢 Public Plugin Key * * Communication channel between LazyNodeView loader and LazyNodeViewDecorationPlugin. */ export var lazyNodeViewDecorationPluginKey = new PluginKey('lazyNodeViewDecoration'); /** * 📢 Public Type * * @see {withLazyLoading} */ /** * 📢 Public Type * * @see {withLazyLoading} */ /** * 🧱 Internal: Editor FE Platform * * Caches loaded node view factory functions */ /** * 🧱 Internal: Editor FE Platform * * Controls which node was scheduled to load the original node view code */ var requestedNodesPerEditorView = new WeakMap(); /** * 🧱 Internal: Editor FE Platform * * Caches loaded node view factory functions for each editor view */ var resolvedNodesPerEditorView = new WeakMap(); /** * 🧱 Internal: Editor FE Platform * * Stores editorView -> raf to debounce NodeView updates. */ var debounceToEditorViewMap = new WeakMap(); var testOnlyIgnoreLazyNodeViewSet = new WeakSet(); // eslint-disable-next-line @repo/internal/deprecations/deprecation-ticket-required -- Ignored via go/ED-25883 /** * 🧱 Internal: Editor FE Platform * * Used in tests to prevent lazy node view being replaced by a real node view. * * This needs to be replaced with proper implementation once LazyNodeView is converted to a plugin. * * @deprecated DO NOT USE THIS OUTSIDE TESTS. */ export function testOnlyIgnoreLazyNodeView(view) { testOnlyIgnoreLazyNodeViewSet.add(view); } /** * 📢 Public: Any EditorPlugin can use this function * * Wraps a NodeView constructor with laziness, allowing the NodeView to be loaded only when required. * * This higher-order function is designed to optimize the loading and rendering performance * of ProseMirror editor nodes by deferring the loading of their associated NodeViews until they are actually needed. * This is particularly useful for complex or heavy NodeViews, such as tables, table cells, rows, and headers within * the ProseMirror editor. By using dynamic imports (with promises), the initial load time of the editor can be significantly * reduced, leading to a smoother and faster user experience. * * The function accepts configuration parameters including the node name, a loader function that dynamically imports * the NodeView, and a function to retrieve NodeView options. It returns a NodeViewConstructor that ProseMirror * can use when rendering nodes of the specified type. * * @template NodeViewOptions - The type parameter that describes the shape of the options object for the NodeView. * @param {LazyLoadingProps<NodeViewOptions>} params - Configuration parameters for lazy loading. * @param {string} params.nodeName - The name of the node (e.g., 'table', 'tableCell', 'tableHeader', 'tableRow') for which the lazy-loaded NodeView is intended. * @param {() => Promise<CreateReactNodeViewProps<NodeViewOptions>>} params.loader - A function that, when called, returns a promise that resolves to the actual NodeView constructor. This function typically uses dynamic `import()` to load the NodeView code. * @param {() => NodeViewOptions} params.getNodeViewOptions - A function that returns the options to be passed to the NodeView constructor. These options can include dependencies like `portalProviderAPI`, `eventDispatcher`, and others, which are necessary for the NodeView's operation. * @param {DispatchAnalyticsEvent} [params.dispatchAnalyticsEvent] - An optional function for dispatching analytics events, which can be used to monitor the performance and usage of the lazy-loaded NodeViews. * @returns {NodeViewConstructor} A constructor function for creating a NodeView that ProseMirror can instantiate when it encounters a node of the specified type. This constructor is a lightweight placeholder until the actual NodeView is loaded. * * @example * // Lazy load a table NodeView with specific options * const lazyTableView = withLazyLoading({ * nodeName: 'table', * loader: () => import('./table').then(module => module.createTableView), * getNodeViewOptions: () => ({ * portalProviderAPI, * eventDispatcher, * getEditorContainerWidth, * getEditorFeatureFlags, * dispatchAnalyticsEvent, * pluginInjectionApi, * }), * }); * * // Then, use `lazyTableView` in ProseMirror editor setup to enhance 'table' nodes with lazy loading */ export var withLazyLoading = function withLazyLoading(_ref) { var nodeName = _ref.nodeName, loader = _ref.loader, getNodeViewOptions = _ref.getNodeViewOptions; var createLazyNodeView = function createLazyNodeView(node, view, getPos, decorations // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/max-params ) { var _node$type; var requestedNodes = requestedNodesPerEditorView.get(view); if (!requestedNodes) { requestedNodes = new Map(); requestedNodesPerEditorView.set(view, requestedNodes); } var resolvedNodeViews = resolvedNodesPerEditorView.get(view); if (!resolvedNodeViews) { resolvedNodesPerEditorView.set(view, new Map()); } var wasAlreadyRequested = requestedNodes.has(nodeName); if (wasAlreadyRequested) { var resolvedNodeView = resolvedNodeViews === null || resolvedNodeViews === void 0 ? void 0 : resolvedNodeViews.get(nodeName); if (resolvedNodeView && !testOnlyIgnoreLazyNodeViewSet.has(view)) { return resolvedNodeView(node, view, getPos, decorations); } return new LazyNodeView(node, view, getPos, decorations); } var loaderPromise = loader().then(function (nodeViewFuncModule) { var _resolvedNodesPerEdit; // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/max-params var nodeViewFunc = function nodeViewFunc(node, view, getPos, decorations) { var nodeView = nodeViewFuncModule(node, view, getPos, decorations, getNodeViewOptions); // eslint-disable-next-line @atlaskit/editor/no-as-casting var dom = nodeView.dom; dom.setAttribute('data-vc', "editor-lnv-loaded--".concat(node.type.name)); var pos = getPos(); if (pos !== undefined) { dom.setAttribute('data-editor-lnv-placeholder-replace', makeNodePlaceholderId(node.type.name, pos)); } return nodeView; }; (_resolvedNodesPerEdit = resolvedNodesPerEditorView.get(view)) === null || _resolvedNodesPerEdit === void 0 || _resolvedNodesPerEdit.set(nodeName, nodeViewFunc); /** * Triggering lazyNodeViewDecoration plugin to apply decorations * to nodes with newly loaded NodeViews. */ var _ref2 = debounceToEditorViewMap.get(view) || [null, new Set()], _ref3 = _slicedToArray(_ref2, 2), callbackId = _ref3[0], nodeTypes = _ref3[1]; if (callbackId) { cancelCallback(callbackId); } nodeTypes.add(node.type.name); var nextCallbackId = scheduleCallback(function () { debounceToEditorViewMap.set(view, [null, new Set()]); var tr = view.state.tr; tr.setMeta(lazyNodeViewDecorationPluginKey, { type: 'add', nodeTypes: nodeTypes }); view.dispatch(tr); }); debounceToEditorViewMap.set(view, [nextCallbackId, nodeTypes]); /** * END triggering LazyNodeViewDecoration plugin */ return nodeViewFunc; }); requestedNodes.set(nodeName, loaderPromise); if (typeof ((_node$type = node.type) === null || _node$type === void 0 || (_node$type = _node$type.spec) === null || _node$type === void 0 ? void 0 : _node$type.toDOM) !== 'function') { // TODO: Analytics ED-23982 // dispatchAnalyticsEvent({ // action: ACTION.LAZY_NODE_VIEW_ERROR, // actionSubject: ACTION_SUBJECT.LAZY_NODE_VIEW, // eventType: EVENT_TYPE.OPERATIONAL, // attributes: { // nodeName, // error: 'No spec found', // }, // }); } return new LazyNodeView(node, view, getPos, decorations); }; return createLazyNodeView; };