UNPKG

@atlaskit/editor-plugin-show-diff

Version:

ShowDiff plugin for @atlaskit/editor-core

172 lines (160 loc) 6.11 kB
import { DOMSerializer } from '@atlaskit/editor-prosemirror/model'; /** * Utilities for working with ProseMirror node views and DOM serialization within the * Show Diff editor plugin. * * This module centralizes: * - Access to the editor's `nodeViews` registry (when available on `EditorView`) * - Safe attempts to instantiate a node view for a given node, with a blocklist to * avoid node types that are known to be problematic in this context (e.g. tables) * - Schema-driven serialization of nodes and fragments to DOM via `DOMSerializer` * * The Show Diff decorations leverage this to either render nodes using their * corresponding node view implementation, or fall back to DOM serialization. */ /** * Narrowed `EditorView` that exposes the internal `nodeViews` registry. * Many editor instances provide this, but it's not part of the base type. */ /** * Type guard to detect whether an `EditorView` exposes a `nodeViews` map. */ export function isEditorViewWithNodeViews(view) { // eslint-disable-next-line @typescript-eslint/no-explicit-any return view.nodeViews !== undefined; } /** * Encapsulates DOM serialization and node view access/creation. * * Responsible for: * - Creating a `DOMSerializer` from the provided schema * - Reading `nodeViews` from an `EditorView` (if present) or using an explicit mapping * - Preventing node view creation for blocklisted node types */ export class NodeViewSerializer { constructor(params) { var _params$blocklist; if (params !== null && params !== void 0 && params.editorView) { this.init({ editorView: params.editorView }); } this.nodeViewBlocklist = new Set((_params$blocklist = params.blocklist) !== null && _params$blocklist !== void 0 ? _params$blocklist : ['paragraph']); } /** * Initializes or reinitializes the NodeViewSerializer with a new EditorView. * This allows the same serializer instance to be reused across different editor states. */ init(params) { var _params$editorView, _ref, _this$editorView; this.serializer = DOMSerializer.fromSchema(params.editorView.state.schema); if (isEditorViewWithNodeViews(params.editorView)) { this.editorView = params.editorView; } const nodeViews = // eslint-disable-next-line @typescript-eslint/no-explicit-any ((_params$editorView = params.editorView) === null || _params$editorView === void 0 ? void 0 : _params$editorView.nodeViews) || {}; this.nodeViews = (_ref = nodeViews !== null && nodeViews !== void 0 ? nodeViews : (_this$editorView = this.editorView) === null || _this$editorView === void 0 ? void 0 : _this$editorView.nodeViews) !== null && _ref !== void 0 ? _ref : {}; } /** * Appends serialized child nodes to the given contentDOM element. */ appendChildNodes(children, contentDOM) { children.forEach(child => { const childNode = this.tryCreateNodeView(child) || this.serializeNode(child); if (childNode) { contentDOM === null || contentDOM === void 0 ? void 0 : contentDOM.append(childNode); } }); } /** * Attempts to create a node view for the given node. * * Returns `null` when there is no `EditorView`, no constructor for the node type, * or the node type is blocklisted. Otherwise returns the constructed node view instance. */ tryCreateNodeView(targetNode) { var _this$nodeViews; if (!this.editorView) { return null; } const constructor = (_this$nodeViews = this.nodeViews) === null || _this$nodeViews === void 0 ? void 0 : _this$nodeViews[targetNode.type.name]; if (this.nodeViewBlocklist.has(targetNode.type.name)) { return null; } try { if (!constructor) { var _targetNode$type$spec, _targetNode$type$spec2; if (targetNode.isInline) { return null; } const toDOMResult = (_targetNode$type$spec = (_targetNode$type$spec2 = targetNode.type.spec).toDOM) === null || _targetNode$type$spec === void 0 ? void 0 : _targetNode$type$spec.call(_targetNode$type$spec2, targetNode); if (!toDOMResult) { return null; } const { dom, contentDOM } = DOMSerializer.renderSpec(document, toDOMResult); if (dom instanceof HTMLElement) { if (targetNode.type.name === 'paragraph' && targetNode.children.length === 1) { return this.serializeFragment(targetNode.content); } // Iteratively populate children this.appendChildNodes(targetNode.children, contentDOM); } return dom; } const { dom, contentDOM } = constructor(targetNode, this.editorView, () => 0, [], {}); // Iteratively populate children this.appendChildNodes(targetNode.children, contentDOM); return dom; } catch (e) { return null; } } /** * Serializes a node to a DOM `Node` using the schema's `DOMSerializer`. */ serializeNode(node) { if (!this.serializer) { throw new Error('NodeViewSerializer must be initialized with init() before use'); } try { return this.serializer.serializeNode(node); } catch (e) { return null; } } /** * Serializes a fragment to a `DocumentFragment` using the schema's `DOMSerializer`. */ serializeFragment(fragment) { if (!this.serializer) { throw new Error('NodeViewSerializer must be initialized with init() before use'); } try { return this.serializer.serializeFragment(fragment); } catch (e) { return null; } } /** * Returns a copy of the current node view blocklist. */ getNodeViewBlocklist() { return new Set(this.nodeViewBlocklist); } /** * Returns a filtered copy of the node view blocklist, excluding specified node types. * @param excludeTypes - Array of node type names to exclude from the blocklist */ getFilteredNodeViewBlocklist(excludeTypes) { const filtered = new Set(this.nodeViewBlocklist); excludeTypes.forEach(type => filtered.delete(type)); return filtered; } }