]> BookStack Code Mirror - bookstack/blob - resources/js/wysiwyg/lexical/core/LexicalNode.ts
c6bc2e642eed20dab0effb19392d264c5d21ac09
[bookstack] / resources / js / wysiwyg / lexical / core / LexicalNode.ts
1 /**
2  * Copyright (c) Meta Platforms, Inc. and affiliates.
3  *
4  * This source code is licensed under the MIT license found in the
5  * LICENSE file in the root directory of this source tree.
6  *
7  */
8
9 /* eslint-disable no-constant-condition */
10 import type {EditorConfig, LexicalEditor} from './LexicalEditor';
11 import type {BaseSelection, RangeSelection} from './LexicalSelection';
12 import type {Klass, KlassConstructor} from 'lexical';
13
14 import invariant from 'lexical/shared/invariant';
15
16 import {
17   $createParagraphNode,
18   $isDecoratorNode,
19   $isElementNode,
20   $isRootNode,
21   $isTextNode,
22   type DecoratorNode,
23   ElementNode,
24 } from '.';
25 import {
26   $getSelection,
27   $isNodeSelection,
28   $isRangeSelection,
29   $moveSelectionPointToEnd,
30   $updateElementSelectionOnCreateDeleteNode,
31   moveSelectionPointToSibling,
32 } from './LexicalSelection';
33 import {
34   errorOnReadOnly,
35   getActiveEditor,
36   getActiveEditorState,
37 } from './LexicalUpdates';
38 import {
39   $cloneWithProperties,
40   $getCompositionKey,
41   $getNodeByKey,
42   $isRootOrShadowRoot,
43   $maybeMoveChildrenSelectionToParent,
44   $setCompositionKey,
45   $setNodeKey,
46   $setSelection,
47   errorOnInsertTextNodeOnRoot,
48   internalMarkNodeAsDirty,
49   removeFromParent,
50 } from './LexicalUtils';
51
52 export type NodeMap = Map<NodeKey, LexicalNode>;
53
54 export type SerializedLexicalNode = {
55   type: string;
56   version: number;
57 };
58
59 export function $removeNode(
60   nodeToRemove: LexicalNode,
61   restoreSelection: boolean,
62   preserveEmptyParent?: boolean,
63 ): void {
64   errorOnReadOnly();
65   const key = nodeToRemove.__key;
66   const parent = nodeToRemove.getParent();
67   if (parent === null) {
68     return;
69   }
70   const selection = $maybeMoveChildrenSelectionToParent(nodeToRemove);
71   let selectionMoved = false;
72   if ($isRangeSelection(selection) && restoreSelection) {
73     const anchor = selection.anchor;
74     const focus = selection.focus;
75     if (anchor.key === key) {
76       moveSelectionPointToSibling(
77         anchor,
78         nodeToRemove,
79         parent,
80         nodeToRemove.getPreviousSibling(),
81         nodeToRemove.getNextSibling(),
82       );
83       selectionMoved = true;
84     }
85     if (focus.key === key) {
86       moveSelectionPointToSibling(
87         focus,
88         nodeToRemove,
89         parent,
90         nodeToRemove.getPreviousSibling(),
91         nodeToRemove.getNextSibling(),
92       );
93       selectionMoved = true;
94     }
95   } else if (
96     $isNodeSelection(selection) &&
97     restoreSelection &&
98     nodeToRemove.isSelected()
99   ) {
100     nodeToRemove.selectPrevious();
101   }
102
103   if ($isRangeSelection(selection) && restoreSelection && !selectionMoved) {
104     // Doing this is O(n) so lets avoid it unless we need to do it
105     const index = nodeToRemove.getIndexWithinParent();
106     removeFromParent(nodeToRemove);
107     $updateElementSelectionOnCreateDeleteNode(selection, parent, index, -1);
108   } else {
109     removeFromParent(nodeToRemove);
110   }
111
112   if (
113     !preserveEmptyParent &&
114     !$isRootOrShadowRoot(parent) &&
115     !parent.canBeEmpty() &&
116     parent.isEmpty()
117   ) {
118     $removeNode(parent, restoreSelection);
119   }
120   if (restoreSelection && $isRootNode(parent) && parent.isEmpty()) {
121     parent.selectEnd();
122   }
123 }
124
125 export type DOMConversion<T extends HTMLElement = HTMLElement> = {
126   conversion: DOMConversionFn<T>;
127   priority?: 0 | 1 | 2 | 3 | 4;
128 };
129
130 export type DOMConversionFn<T extends HTMLElement = HTMLElement> = (
131   element: T,
132 ) => DOMConversionOutput | null;
133
134 export type DOMChildConversion = (
135   lexicalNode: LexicalNode,
136   parentLexicalNode: LexicalNode | null | undefined,
137 ) => LexicalNode | null | undefined;
138
139 export type DOMConversionMap<T extends HTMLElement = HTMLElement> = Record<
140   NodeName,
141   (node: T) => DOMConversion<T> | null
142 >;
143 type NodeName = string;
144
145 export type DOMConversionOutput = {
146   after?: (childLexicalNodes: Array<LexicalNode>) => Array<LexicalNode>;
147   forChild?: DOMChildConversion;
148   node: null | LexicalNode | Array<LexicalNode>;
149 };
150
151 export type DOMExportOutputMap = Map<
152   Klass<LexicalNode>,
153   (editor: LexicalEditor, target: LexicalNode) => DOMExportOutput
154 >;
155
156 export type DOMExportOutput = {
157   after?: (
158     generatedElement: HTMLElement | Text | null | undefined,
159   ) => HTMLElement | Text | null | undefined;
160   element: HTMLElement | Text | null;
161 };
162
163 export type NodeKey = string;
164
165 export class LexicalNode {
166   // Allow us to look up the type including static props
167   ['constructor']!: KlassConstructor<typeof LexicalNode>;
168   /** @internal */
169   __type: string;
170   /** @internal */
171   //@ts-ignore We set the key in the constructor.
172   __key: string;
173   /** @internal */
174   __parent: null | NodeKey;
175   /** @internal */
176   __prev: null | NodeKey;
177   /** @internal */
178   __next: null | NodeKey;
179
180   // Flow doesn't support abstract classes unfortunately, so we can't _force_
181   // subclasses of Node to implement statics. All subclasses of Node should have
182   // a static getType and clone method though. We define getType and clone here so we can call it
183   // on any  Node, and we throw this error by default since the subclass should provide
184   // their own implementation.
185   /**
186    * Returns the string type of this node. Every node must
187    * implement this and it MUST BE UNIQUE amongst nodes registered
188    * on the editor.
189    *
190    */
191   static getType(): string {
192     invariant(
193       false,
194       'LexicalNode: Node %s does not implement .getType().',
195       this.name,
196     );
197   }
198
199   /**
200    * Clones this node, creating a new node with a different key
201    * and adding it to the EditorState (but not attaching it anywhere!). All nodes must
202    * implement this method.
203    *
204    */
205   static clone(_data: unknown): LexicalNode {
206     invariant(
207       false,
208       'LexicalNode: Node %s does not implement .clone().',
209       this.name,
210     );
211   }
212
213   /**
214    * Perform any state updates on the clone of prevNode that are not already
215    * handled by the constructor call in the static clone method. If you have
216    * state to update in your clone that is not handled directly by the
217    * constructor, it is advisable to override this method but it is required
218    * to include a call to `super.afterCloneFrom(prevNode)` in your
219    * implementation. This is only intended to be called by
220    * {@link $cloneWithProperties} function or via a super call.
221    *
222    * @example
223    * ```ts
224    * class ClassesTextNode extends TextNode {
225    *   // Not shown: static getType, static importJSON, exportJSON, createDOM, updateDOM
226    *   __classes = new Set<string>();
227    *   static clone(node: ClassesTextNode): ClassesTextNode {
228    *     // The inherited TextNode constructor is used here, so
229    *     // classes is not set by this method.
230    *     return new ClassesTextNode(node.__text, node.__key);
231    *   }
232    *   afterCloneFrom(node: this): void {
233    *     // This calls TextNode.afterCloneFrom and LexicalNode.afterCloneFrom
234    *     // for necessary state updates
235    *     super.afterCloneFrom(node);
236    *     this.__addClasses(node.__classes);
237    *   }
238    *   // This method is a private implementation detail, it is not
239    *   // suitable for the public API because it does not call getWritable
240    *   __addClasses(classNames: Iterable<string>): this {
241    *     for (const className of classNames) {
242    *       this.__classes.add(className);
243    *     }
244    *     return this;
245    *   }
246    *   addClass(...classNames: string[]): this {
247    *     return this.getWritable().__addClasses(classNames);
248    *   }
249    *   removeClass(...classNames: string[]): this {
250    *     const node = this.getWritable();
251    *     for (const className of classNames) {
252    *       this.__classes.delete(className);
253    *     }
254    *     return this;
255    *   }
256    *   getClasses(): Set<string> {
257    *     return this.getLatest().__classes;
258    *   }
259    * }
260    * ```
261    *
262    */
263   afterCloneFrom(prevNode: this) {
264     this.__parent = prevNode.__parent;
265     this.__next = prevNode.__next;
266     this.__prev = prevNode.__prev;
267   }
268
269   // eslint-disable-next-line @typescript-eslint/no-explicit-any
270   static importDOM?: () => DOMConversionMap<any> | null;
271
272   constructor(key?: NodeKey) {
273     this.__type = this.constructor.getType();
274     this.__parent = null;
275     this.__prev = null;
276     this.__next = null;
277     $setNodeKey(this, key);
278
279     if (__DEV__) {
280       if (this.__type !== 'root') {
281         errorOnReadOnly();
282         errorOnTypeKlassMismatch(this.__type, this.constructor);
283       }
284     }
285   }
286   // Getters and Traversers
287
288   /**
289    * Returns the string type of this node.
290    */
291   getType(): string {
292     return this.__type;
293   }
294
295   isInline(): boolean {
296     invariant(
297       false,
298       'LexicalNode: Node %s does not implement .isInline().',
299       this.constructor.name,
300     );
301   }
302
303   /**
304    * Returns true if there is a path between this node and the RootNode, false otherwise.
305    * This is a way of determining if the node is "attached" EditorState. Unattached nodes
306    * won't be reconciled and will ultimatelt be cleaned up by the Lexical GC.
307    */
308   isAttached(): boolean {
309     let nodeKey: string | null = this.__key;
310     while (nodeKey !== null) {
311       if (nodeKey === 'root') {
312         return true;
313       }
314
315       const node: LexicalNode | null = $getNodeByKey(nodeKey);
316
317       if (node === null) {
318         break;
319       }
320       nodeKey = node.__parent;
321     }
322     return false;
323   }
324
325   /**
326    * Returns true if this node is contained within the provided Selection., false otherwise.
327    * Relies on the algorithms implemented in {@link BaseSelection.getNodes} to determine
328    * what's included.
329    *
330    * @param selection - The selection that we want to determine if the node is in.
331    */
332   isSelected(selection?: null | BaseSelection): boolean {
333     const targetSelection = selection || $getSelection();
334     if (targetSelection == null) {
335       return false;
336     }
337
338     const isSelected = targetSelection
339       .getNodes()
340       .some((n) => n.__key === this.__key);
341
342     if ($isTextNode(this)) {
343       return isSelected;
344     }
345     // For inline images inside of element nodes.
346     // Without this change the image will be selected if the cursor is before or after it.
347     const isElementRangeSelection =
348       $isRangeSelection(targetSelection) &&
349       targetSelection.anchor.type === 'element' &&
350       targetSelection.focus.type === 'element';
351
352     if (isElementRangeSelection) {
353       if (targetSelection.isCollapsed()) {
354         return false;
355       }
356
357       const parentNode = this.getParent();
358       if ($isDecoratorNode(this) && this.isInline() && parentNode) {
359         const firstPoint = targetSelection.isBackward()
360           ? targetSelection.focus
361           : targetSelection.anchor;
362         const firstElement = firstPoint.getNode() as ElementNode;
363         if (
364           firstPoint.offset === firstElement.getChildrenSize() &&
365           firstElement.is(parentNode) &&
366           firstElement.getLastChildOrThrow().is(this)
367         ) {
368           return false;
369         }
370       }
371     }
372     return isSelected;
373   }
374
375   /**
376    * Returns this nodes key.
377    */
378   getKey(): NodeKey {
379     // Key is stable between copies
380     return this.__key;
381   }
382
383   /**
384    * Returns the zero-based index of this node within the parent.
385    */
386   getIndexWithinParent(): number {
387     const parent = this.getParent();
388     if (parent === null) {
389       return -1;
390     }
391     let node = parent.getFirstChild();
392     let index = 0;
393     while (node !== null) {
394       if (this.is(node)) {
395         return index;
396       }
397       index++;
398       node = node.getNextSibling();
399     }
400     return -1;
401   }
402
403   /**
404    * Returns the parent of this node, or null if none is found.
405    */
406   getParent<T extends ElementNode>(): T | null {
407     const parent = this.getLatest().__parent;
408     if (parent === null) {
409       return null;
410     }
411     return $getNodeByKey<T>(parent);
412   }
413
414   /**
415    * Returns the parent of this node, or throws if none is found.
416    */
417   getParentOrThrow<T extends ElementNode>(): T {
418     const parent = this.getParent<T>();
419     if (parent === null) {
420       invariant(false, 'Expected node %s to have a parent.', this.__key);
421     }
422     return parent;
423   }
424
425   /**
426    * Returns the highest (in the EditorState tree)
427    * non-root ancestor of this node, or null if none is found. See {@link lexical!$isRootOrShadowRoot}
428    * for more information on which Elements comprise "roots".
429    */
430   getTopLevelElement(): ElementNode | DecoratorNode<unknown> | null {
431     let node: ElementNode | this | null = this;
432     while (node !== null) {
433       const parent: ElementNode | null = node.getParent();
434       if ($isRootOrShadowRoot(parent)) {
435         invariant(
436           $isElementNode(node) || (node === this && $isDecoratorNode(node)),
437           'Children of root nodes must be elements or decorators',
438         );
439         return node;
440       }
441       node = parent;
442     }
443     return null;
444   }
445
446   /**
447    * Returns the highest (in the EditorState tree)
448    * non-root ancestor of this node, or throws if none is found. See {@link lexical!$isRootOrShadowRoot}
449    * for more information on which Elements comprise "roots".
450    */
451   getTopLevelElementOrThrow(): ElementNode | DecoratorNode<unknown> {
452     const parent = this.getTopLevelElement();
453     if (parent === null) {
454       invariant(
455         false,
456         'Expected node %s to have a top parent element.',
457         this.__key,
458       );
459     }
460     return parent;
461   }
462
463   /**
464    * Returns a list of the every ancestor of this node,
465    * all the way up to the RootNode.
466    *
467    */
468   getParents(): Array<ElementNode> {
469     const parents: Array<ElementNode> = [];
470     let node = this.getParent();
471     while (node !== null) {
472       parents.push(node);
473       node = node.getParent();
474     }
475     return parents;
476   }
477
478   /**
479    * Returns a list of the keys of every ancestor of this node,
480    * all the way up to the RootNode.
481    *
482    */
483   getParentKeys(): Array<NodeKey> {
484     const parents = [];
485     let node = this.getParent();
486     while (node !== null) {
487       parents.push(node.__key);
488       node = node.getParent();
489     }
490     return parents;
491   }
492
493   /**
494    * Returns the "previous" siblings - that is, the node that comes
495    * before this one in the same parent.
496    *
497    */
498   getPreviousSibling<T extends LexicalNode>(): T | null {
499     const self = this.getLatest();
500     const prevKey = self.__prev;
501     return prevKey === null ? null : $getNodeByKey<T>(prevKey);
502   }
503
504   /**
505    * Returns the "previous" siblings - that is, the nodes that come between
506    * this one and the first child of it's parent, inclusive.
507    *
508    */
509   getPreviousSiblings<T extends LexicalNode>(): Array<T> {
510     const siblings: Array<T> = [];
511     const parent = this.getParent();
512     if (parent === null) {
513       return siblings;
514     }
515     let node: null | T = parent.getFirstChild();
516     while (node !== null) {
517       if (node.is(this)) {
518         break;
519       }
520       siblings.push(node);
521       node = node.getNextSibling();
522     }
523     return siblings;
524   }
525
526   /**
527    * Returns the "next" siblings - that is, the node that comes
528    * after this one in the same parent
529    *
530    */
531   getNextSibling<T extends LexicalNode>(): T | null {
532     const self = this.getLatest();
533     const nextKey = self.__next;
534     return nextKey === null ? null : $getNodeByKey<T>(nextKey);
535   }
536
537   /**
538    * Returns all "next" siblings - that is, the nodes that come between this
539    * one and the last child of it's parent, inclusive.
540    *
541    */
542   getNextSiblings<T extends LexicalNode>(): Array<T> {
543     const siblings: Array<T> = [];
544     let node: null | T = this.getNextSibling();
545     while (node !== null) {
546       siblings.push(node);
547       node = node.getNextSibling();
548     }
549     return siblings;
550   }
551
552   /**
553    * Returns the closest common ancestor of this node and the provided one or null
554    * if one cannot be found.
555    *
556    * @param node - the other node to find the common ancestor of.
557    */
558   getCommonAncestor<T extends ElementNode = ElementNode>(
559     node: LexicalNode,
560   ): T | null {
561     const a = this.getParents();
562     const b = node.getParents();
563     if ($isElementNode(this)) {
564       a.unshift(this);
565     }
566     if ($isElementNode(node)) {
567       b.unshift(node);
568     }
569     const aLength = a.length;
570     const bLength = b.length;
571     if (aLength === 0 || bLength === 0 || a[aLength - 1] !== b[bLength - 1]) {
572       return null;
573     }
574     const bSet = new Set(b);
575     for (let i = 0; i < aLength; i++) {
576       const ancestor = a[i] as T;
577       if (bSet.has(ancestor)) {
578         return ancestor;
579       }
580     }
581     return null;
582   }
583
584   /**
585    * Returns true if the provided node is the exact same one as this node, from Lexical's perspective.
586    * Always use this instead of referential equality.
587    *
588    * @param object - the node to perform the equality comparison on.
589    */
590   is(object: LexicalNode | null | undefined): boolean {
591     if (object == null) {
592       return false;
593     }
594     return this.__key === object.__key;
595   }
596
597   /**
598    * Returns true if this node logical precedes the target node in the editor state.
599    *
600    * @param targetNode - the node we're testing to see if it's after this one.
601    */
602   isBefore(targetNode: LexicalNode): boolean {
603     if (this === targetNode) {
604       return false;
605     }
606     if (targetNode.isParentOf(this)) {
607       return true;
608     }
609     if (this.isParentOf(targetNode)) {
610       return false;
611     }
612     const commonAncestor = this.getCommonAncestor(targetNode);
613     let indexA = 0;
614     let indexB = 0;
615     let node: this | ElementNode | LexicalNode = this;
616     while (true) {
617       const parent: ElementNode = node.getParentOrThrow();
618       if (parent === commonAncestor) {
619         indexA = node.getIndexWithinParent();
620         break;
621       }
622       node = parent;
623     }
624     node = targetNode;
625     while (true) {
626       const parent: ElementNode = node.getParentOrThrow();
627       if (parent === commonAncestor) {
628         indexB = node.getIndexWithinParent();
629         break;
630       }
631       node = parent;
632     }
633     return indexA < indexB;
634   }
635
636   /**
637    * Returns true if this node is the parent of the target node, false otherwise.
638    *
639    * @param targetNode - the would-be child node.
640    */
641   isParentOf(targetNode: LexicalNode): boolean {
642     const key = this.__key;
643     if (key === targetNode.__key) {
644       return false;
645     }
646     let node: ElementNode | LexicalNode | null = targetNode;
647     while (node !== null) {
648       if (node.__key === key) {
649         return true;
650       }
651       node = node.getParent();
652     }
653     return false;
654   }
655
656   // TO-DO: this function can be simplified a lot
657   /**
658    * Returns a list of nodes that are between this node and
659    * the target node in the EditorState.
660    *
661    * @param targetNode - the node that marks the other end of the range of nodes to be returned.
662    */
663   getNodesBetween(targetNode: LexicalNode): Array<LexicalNode> {
664     const isBefore = this.isBefore(targetNode);
665     const nodes = [];
666     const visited = new Set();
667     let node: LexicalNode | this | null = this;
668     while (true) {
669       if (node === null) {
670         break;
671       }
672       const key = node.__key;
673       if (!visited.has(key)) {
674         visited.add(key);
675         nodes.push(node);
676       }
677       if (node === targetNode) {
678         break;
679       }
680       const child: LexicalNode | null = $isElementNode(node)
681         ? isBefore
682           ? node.getFirstChild()
683           : node.getLastChild()
684         : null;
685       if (child !== null) {
686         node = child;
687         continue;
688       }
689       const nextSibling: LexicalNode | null = isBefore
690         ? node.getNextSibling()
691         : node.getPreviousSibling();
692       if (nextSibling !== null) {
693         node = nextSibling;
694         continue;
695       }
696       const parent: LexicalNode | null = node.getParentOrThrow();
697       if (!visited.has(parent.__key)) {
698         nodes.push(parent);
699       }
700       if (parent === targetNode) {
701         break;
702       }
703       let parentSibling = null;
704       let ancestor: LexicalNode | null = parent;
705       do {
706         if (ancestor === null) {
707           invariant(false, 'getNodesBetween: ancestor is null');
708         }
709         parentSibling = isBefore
710           ? ancestor.getNextSibling()
711           : ancestor.getPreviousSibling();
712         ancestor = ancestor.getParent();
713         if (ancestor !== null) {
714           if (parentSibling === null && !visited.has(ancestor.__key)) {
715             nodes.push(ancestor);
716           }
717         } else {
718           break;
719         }
720       } while (parentSibling === null);
721       node = parentSibling;
722     }
723     if (!isBefore) {
724       nodes.reverse();
725     }
726     return nodes;
727   }
728
729   /**
730    * Returns true if this node has been marked dirty during this update cycle.
731    *
732    */
733   isDirty(): boolean {
734     const editor = getActiveEditor();
735     const dirtyLeaves = editor._dirtyLeaves;
736     return dirtyLeaves !== null && dirtyLeaves.has(this.__key);
737   }
738
739   /**
740    * Returns the latest version of the node from the active EditorState.
741    * This is used to avoid getting values from stale node references.
742    *
743    */
744   getLatest(): this {
745     const latest = $getNodeByKey<this>(this.__key);
746     if (latest === null) {
747       invariant(
748         false,
749         'Lexical node does not exist in active editor state. Avoid using the same node references between nested closures from editorState.read/editor.update.',
750       );
751     }
752     return latest;
753   }
754
755   /**
756    * Returns a mutable version of the node using {@link $cloneWithProperties}
757    * if necessary. Will throw an error if called outside of a Lexical Editor
758    * {@link LexicalEditor.update} callback.
759    *
760    */
761   getWritable(): this {
762     errorOnReadOnly();
763     const editorState = getActiveEditorState();
764     const editor = getActiveEditor();
765     const nodeMap = editorState._nodeMap;
766     const key = this.__key;
767     // Ensure we get the latest node from pending state
768     const latestNode = this.getLatest();
769     const cloneNotNeeded = editor._cloneNotNeeded;
770     const selection = $getSelection();
771     if (selection !== null) {
772       selection.setCachedNodes(null);
773     }
774     if (cloneNotNeeded.has(key)) {
775       // Transforms clear the dirty node set on each iteration to keep track on newly dirty nodes
776       internalMarkNodeAsDirty(latestNode);
777       return latestNode;
778     }
779     const mutableNode = $cloneWithProperties(latestNode);
780     cloneNotNeeded.add(key);
781     internalMarkNodeAsDirty(mutableNode);
782     // Update reference in node map
783     nodeMap.set(key, mutableNode);
784
785     return mutableNode;
786   }
787
788   /**
789    * Returns the text content of the node. Override this for
790    * custom nodes that should have a representation in plain text
791    * format (for copy + paste, for example)
792    *
793    */
794   getTextContent(): string {
795     return '';
796   }
797
798   /**
799    * Returns the length of the string produced by calling getTextContent on this node.
800    *
801    */
802   getTextContentSize(): number {
803     return this.getTextContent().length;
804   }
805
806   // View
807
808   /**
809    * Called during the reconciliation process to determine which nodes
810    * to insert into the DOM for this Lexical Node.
811    *
812    * This method must return exactly one HTMLElement. Nested elements are not supported.
813    *
814    * Do not attempt to update the Lexical EditorState during this phase of the update lifecyle.
815    *
816    * @param _config - allows access to things like the EditorTheme (to apply classes) during reconciliation.
817    * @param _editor - allows access to the editor for context during reconciliation.
818    *
819    * */
820   createDOM(_config: EditorConfig, _editor: LexicalEditor): HTMLElement {
821     invariant(false, 'createDOM: base method not extended');
822   }
823
824   /**
825    * Called when a node changes and should update the DOM
826    * in whatever way is necessary to make it align with any changes that might
827    * have happened during the update.
828    *
829    * Returning "true" here will cause lexical to unmount and recreate the DOM node
830    * (by calling createDOM). You would need to do this if the element tag changes,
831    * for instance.
832    *
833    * */
834   updateDOM(
835     _prevNode: unknown,
836     _dom: HTMLElement,
837     _config: EditorConfig,
838   ): boolean {
839     invariant(false, 'updateDOM: base method not extended');
840   }
841
842   /**
843    * Controls how the this node is serialized to HTML. This is important for
844    * copy and paste between Lexical and non-Lexical editors, or Lexical editors with different namespaces,
845    * in which case the primary transfer format is HTML. It's also important if you're serializing
846    * to HTML for any other reason via {@link @lexical/html!$generateHtmlFromNodes}. You could
847    * also use this method to build your own HTML renderer.
848    *
849    * */
850   exportDOM(editor: LexicalEditor): DOMExportOutput {
851     const element = this.createDOM(editor._config, editor);
852     return {element};
853   }
854
855   /**
856    * Controls how the this node is serialized to JSON. This is important for
857    * copy and paste between Lexical editors sharing the same namespace. It's also important
858    * if you're serializing to JSON for persistent storage somewhere.
859    * See [Serialization & Deserialization](https://lexical.dev/docs/concepts/serialization#lexical---html).
860    *
861    * */
862   exportJSON(): SerializedLexicalNode {
863     invariant(false, 'exportJSON: base method not extended');
864   }
865
866   /**
867    * Controls how the this node is deserialized from JSON. This is usually boilerplate,
868    * but provides an abstraction between the node implementation and serialized interface that can
869    * be important if you ever make breaking changes to a node schema (by adding or removing properties).
870    * See [Serialization & Deserialization](https://lexical.dev/docs/concepts/serialization#lexical---html).
871    *
872    * */
873   static importJSON(_serializedNode: SerializedLexicalNode): LexicalNode {
874     invariant(
875       false,
876       'LexicalNode: Node %s does not implement .importJSON().',
877       this.name,
878     );
879   }
880   /**
881    * @experimental
882    *
883    * Registers the returned function as a transform on the node during
884    * Editor initialization. Most such use cases should be addressed via
885    * the {@link LexicalEditor.registerNodeTransform} API.
886    *
887    * Experimental - use at your own risk.
888    */
889   static transform(): ((node: LexicalNode) => void) | null {
890     return null;
891   }
892
893   // Setters and mutators
894
895   /**
896    * Removes this LexicalNode from the EditorState. If the node isn't re-inserted
897    * somewhere, the Lexical garbage collector will eventually clean it up.
898    *
899    * @param preserveEmptyParent - If falsy, the node's parent will be removed if
900    * it's empty after the removal operation. This is the default behavior, subject to
901    * other node heuristics such as {@link ElementNode#canBeEmpty}
902    * */
903   remove(preserveEmptyParent?: boolean): void {
904     $removeNode(this, true, preserveEmptyParent);
905   }
906
907   /**
908    * Replaces this LexicalNode with the provided node, optionally transferring the children
909    * of the replaced node to the replacing node.
910    *
911    * @param replaceWith - The node to replace this one with.
912    * @param includeChildren - Whether or not to transfer the children of this node to the replacing node.
913    * */
914   replace<N extends LexicalNode>(replaceWith: N, includeChildren?: boolean): N {
915     errorOnReadOnly();
916     let selection = $getSelection();
917     if (selection !== null) {
918       selection = selection.clone();
919     }
920     errorOnInsertTextNodeOnRoot(this, replaceWith);
921     const self = this.getLatest();
922     const toReplaceKey = this.__key;
923     const key = replaceWith.__key;
924     const writableReplaceWith = replaceWith.getWritable();
925     const writableParent = this.getParentOrThrow().getWritable();
926     const size = writableParent.__size;
927     removeFromParent(writableReplaceWith);
928     const prevSibling = self.getPreviousSibling();
929     const nextSibling = self.getNextSibling();
930     const prevKey = self.__prev;
931     const nextKey = self.__next;
932     const parentKey = self.__parent;
933     $removeNode(self, false, true);
934
935     if (prevSibling === null) {
936       writableParent.__first = key;
937     } else {
938       const writablePrevSibling = prevSibling.getWritable();
939       writablePrevSibling.__next = key;
940     }
941     writableReplaceWith.__prev = prevKey;
942     if (nextSibling === null) {
943       writableParent.__last = key;
944     } else {
945       const writableNextSibling = nextSibling.getWritable();
946       writableNextSibling.__prev = key;
947     }
948     writableReplaceWith.__next = nextKey;
949     writableReplaceWith.__parent = parentKey;
950     writableParent.__size = size;
951     if (includeChildren) {
952       invariant(
953         $isElementNode(this) && $isElementNode(writableReplaceWith),
954         'includeChildren should only be true for ElementNodes',
955       );
956       this.getChildren().forEach((child: LexicalNode) => {
957         writableReplaceWith.append(child);
958       });
959     }
960     if ($isRangeSelection(selection)) {
961       $setSelection(selection);
962       const anchor = selection.anchor;
963       const focus = selection.focus;
964       if (anchor.key === toReplaceKey) {
965         $moveSelectionPointToEnd(anchor, writableReplaceWith);
966       }
967       if (focus.key === toReplaceKey) {
968         $moveSelectionPointToEnd(focus, writableReplaceWith);
969       }
970     }
971     if ($getCompositionKey() === toReplaceKey) {
972       $setCompositionKey(key);
973     }
974     return writableReplaceWith;
975   }
976
977   /**
978    * Inserts a node after this LexicalNode (as the next sibling).
979    *
980    * @param nodeToInsert - The node to insert after this one.
981    * @param restoreSelection - Whether or not to attempt to resolve the
982    * selection to the appropriate place after the operation is complete.
983    * */
984   insertAfter(nodeToInsert: LexicalNode, restoreSelection = true): LexicalNode {
985     errorOnReadOnly();
986     errorOnInsertTextNodeOnRoot(this, nodeToInsert);
987     const writableSelf = this.getWritable();
988     const writableNodeToInsert = nodeToInsert.getWritable();
989     const oldParent = writableNodeToInsert.getParent();
990     const selection = $getSelection();
991     let elementAnchorSelectionOnNode = false;
992     let elementFocusSelectionOnNode = false;
993     if (oldParent !== null) {
994       // TODO: this is O(n), can we improve?
995       const oldIndex = nodeToInsert.getIndexWithinParent();
996       removeFromParent(writableNodeToInsert);
997       if ($isRangeSelection(selection)) {
998         const oldParentKey = oldParent.__key;
999         const anchor = selection.anchor;
1000         const focus = selection.focus;
1001         elementAnchorSelectionOnNode =
1002           anchor.type === 'element' &&
1003           anchor.key === oldParentKey &&
1004           anchor.offset === oldIndex + 1;
1005         elementFocusSelectionOnNode =
1006           focus.type === 'element' &&
1007           focus.key === oldParentKey &&
1008           focus.offset === oldIndex + 1;
1009       }
1010     }
1011     const nextSibling = this.getNextSibling();
1012     const writableParent = this.getParentOrThrow().getWritable();
1013     const insertKey = writableNodeToInsert.__key;
1014     const nextKey = writableSelf.__next;
1015     if (nextSibling === null) {
1016       writableParent.__last = insertKey;
1017     } else {
1018       const writableNextSibling = nextSibling.getWritable();
1019       writableNextSibling.__prev = insertKey;
1020     }
1021     writableParent.__size++;
1022     writableSelf.__next = insertKey;
1023     writableNodeToInsert.__next = nextKey;
1024     writableNodeToInsert.__prev = writableSelf.__key;
1025     writableNodeToInsert.__parent = writableSelf.__parent;
1026     if (restoreSelection && $isRangeSelection(selection)) {
1027       const index = this.getIndexWithinParent();
1028       $updateElementSelectionOnCreateDeleteNode(
1029         selection,
1030         writableParent,
1031         index + 1,
1032       );
1033       const writableParentKey = writableParent.__key;
1034       if (elementAnchorSelectionOnNode) {
1035         selection.anchor.set(writableParentKey, index + 2, 'element');
1036       }
1037       if (elementFocusSelectionOnNode) {
1038         selection.focus.set(writableParentKey, index + 2, 'element');
1039       }
1040     }
1041     return nodeToInsert;
1042   }
1043
1044   /**
1045    * Inserts a node before this LexicalNode (as the previous sibling).
1046    *
1047    * @param nodeToInsert - The node to insert before this one.
1048    * @param restoreSelection - Whether or not to attempt to resolve the
1049    * selection to the appropriate place after the operation is complete.
1050    * */
1051   insertBefore(
1052     nodeToInsert: LexicalNode,
1053     restoreSelection = true,
1054   ): LexicalNode {
1055     errorOnReadOnly();
1056     errorOnInsertTextNodeOnRoot(this, nodeToInsert);
1057     const writableSelf = this.getWritable();
1058     const writableNodeToInsert = nodeToInsert.getWritable();
1059     const insertKey = writableNodeToInsert.__key;
1060     removeFromParent(writableNodeToInsert);
1061     const prevSibling = this.getPreviousSibling();
1062     const writableParent = this.getParentOrThrow().getWritable();
1063     const prevKey = writableSelf.__prev;
1064     // TODO: this is O(n), can we improve?
1065     const index = this.getIndexWithinParent();
1066     if (prevSibling === null) {
1067       writableParent.__first = insertKey;
1068     } else {
1069       const writablePrevSibling = prevSibling.getWritable();
1070       writablePrevSibling.__next = insertKey;
1071     }
1072     writableParent.__size++;
1073     writableSelf.__prev = insertKey;
1074     writableNodeToInsert.__prev = prevKey;
1075     writableNodeToInsert.__next = writableSelf.__key;
1076     writableNodeToInsert.__parent = writableSelf.__parent;
1077     const selection = $getSelection();
1078     if (restoreSelection && $isRangeSelection(selection)) {
1079       const parent = this.getParentOrThrow();
1080       $updateElementSelectionOnCreateDeleteNode(selection, parent, index);
1081     }
1082     return nodeToInsert;
1083   }
1084
1085   /**
1086    * Whether or not this node has a required parent. Used during copy + paste operations
1087    * to normalize nodes that would otherwise be orphaned. For example, ListItemNodes without
1088    * a ListNode parent or TextNodes with a ParagraphNode parent.
1089    *
1090    * */
1091   isParentRequired(): boolean {
1092     return false;
1093   }
1094
1095   /**
1096    * The creation logic for any required parent. Should be implemented if {@link isParentRequired} returns true.
1097    *
1098    * */
1099   createParentElementNode(): ElementNode {
1100     return $createParagraphNode();
1101   }
1102
1103   selectStart(): RangeSelection {
1104     return this.selectPrevious();
1105   }
1106
1107   selectEnd(): RangeSelection {
1108     return this.selectNext(0, 0);
1109   }
1110
1111   /**
1112    * Moves selection to the previous sibling of this node, at the specified offsets.
1113    *
1114    * @param anchorOffset - The anchor offset for selection.
1115    * @param focusOffset -  The focus offset for selection
1116    * */
1117   selectPrevious(anchorOffset?: number, focusOffset?: number): RangeSelection {
1118     errorOnReadOnly();
1119     const prevSibling = this.getPreviousSibling();
1120     const parent = this.getParentOrThrow();
1121     if (prevSibling === null) {
1122       return parent.select(0, 0);
1123     }
1124     if ($isElementNode(prevSibling)) {
1125       return prevSibling.select();
1126     } else if (!$isTextNode(prevSibling)) {
1127       const index = prevSibling.getIndexWithinParent() + 1;
1128       return parent.select(index, index);
1129     }
1130     return prevSibling.select(anchorOffset, focusOffset);
1131   }
1132
1133   /**
1134    * Moves selection to the next sibling of this node, at the specified offsets.
1135    *
1136    * @param anchorOffset - The anchor offset for selection.
1137    * @param focusOffset -  The focus offset for selection
1138    * */
1139   selectNext(anchorOffset?: number, focusOffset?: number): RangeSelection {
1140     errorOnReadOnly();
1141     const nextSibling = this.getNextSibling();
1142     const parent = this.getParentOrThrow();
1143     if (nextSibling === null) {
1144       return parent.select();
1145     }
1146     if ($isElementNode(nextSibling)) {
1147       return nextSibling.select(0, 0);
1148     } else if (!$isTextNode(nextSibling)) {
1149       const index = nextSibling.getIndexWithinParent();
1150       return parent.select(index, index);
1151     }
1152     return nextSibling.select(anchorOffset, focusOffset);
1153   }
1154
1155   /**
1156    * Marks a node dirty, triggering transforms and
1157    * forcing it to be reconciled during the update cycle.
1158    *
1159    * */
1160   markDirty(): void {
1161     this.getWritable();
1162   }
1163 }
1164
1165 function errorOnTypeKlassMismatch(
1166   type: string,
1167   klass: Klass<LexicalNode>,
1168 ): void {
1169   const registeredNode = getActiveEditor()._nodes.get(type);
1170   // Common error - split in its own invariant
1171   if (registeredNode === undefined) {
1172     invariant(
1173       false,
1174       'Create node: Attempted to create node %s that was not configured to be used on the editor.',
1175       klass.name,
1176     );
1177   }
1178   const editorKlass = registeredNode.klass;
1179   if (editorKlass !== klass) {
1180     invariant(
1181       false,
1182       'Create node: Type %s in node %s does not match registered node %s with the same type',
1183       type,
1184       klass.name,
1185       editorKlass.name,
1186     );
1187   }
1188 }
1189
1190 /**
1191  * Insert a series of nodes after this LexicalNode (as next siblings)
1192  *
1193  * @param firstToInsert - The first node to insert after this one.
1194  * @param lastToInsert - The last node to insert after this one. Must be a
1195  * later sibling of FirstNode. If not provided, it will be its last sibling.
1196  */
1197 export function insertRangeAfter(
1198   node: LexicalNode,
1199   firstToInsert: LexicalNode,
1200   lastToInsert?: LexicalNode,
1201 ) {
1202   const lastToInsert2 =
1203     lastToInsert || firstToInsert.getParentOrThrow().getLastChild()!;
1204   let current = firstToInsert;
1205   const nodesToInsert = [firstToInsert];
1206   while (current !== lastToInsert2) {
1207     if (!current.getNextSibling()) {
1208       invariant(
1209         false,
1210         'insertRangeAfter: lastToInsert must be a later sibling of firstToInsert',
1211       );
1212     }
1213     current = current.getNextSibling()!;
1214     nodesToInsert.push(current);
1215   }
1216
1217   let currentNode: LexicalNode = node;
1218   for (const nodeToInsert of nodesToInsert) {
1219     currentNode = currentNode.insertAfter(nodeToInsert);
1220   }
1221 }