@atlaskit/editor-common
Version:
A package that contains common classes and components for editor and renderer
197 lines (186 loc) • 8.44 kB
JavaScript
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;
};