]> BookStack Code Mirror - bookstack/blob - resources/js/wysiwyg/lexical/core/LexicalNormalization.ts
Merge branch 'development' of github.com:rubentalstra/BookStack into rubentalstra...
[bookstack] / resources / js / wysiwyg / lexical / core / LexicalNormalization.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 import type {RangeSelection, TextNode} from '.';
10 import type {PointType} from './LexicalSelection';
11
12 import {$isElementNode, $isTextNode} from '.';
13 import {getActiveEditor} from './LexicalUpdates';
14
15 function $canSimpleTextNodesBeMerged(
16   node1: TextNode,
17   node2: TextNode,
18 ): boolean {
19   const node1Mode = node1.__mode;
20   const node1Format = node1.__format;
21   const node1Style = node1.__style;
22   const node2Mode = node2.__mode;
23   const node2Format = node2.__format;
24   const node2Style = node2.__style;
25   return (
26     (node1Mode === null || node1Mode === node2Mode) &&
27     (node1Format === null || node1Format === node2Format) &&
28     (node1Style === null || node1Style === node2Style)
29   );
30 }
31
32 function $mergeTextNodes(node1: TextNode, node2: TextNode): TextNode {
33   const writableNode1 = node1.mergeWithSibling(node2);
34
35   const normalizedNodes = getActiveEditor()._normalizedNodes;
36
37   normalizedNodes.add(node1.__key);
38   normalizedNodes.add(node2.__key);
39   return writableNode1;
40 }
41
42 export function $normalizeTextNode(textNode: TextNode): void {
43   let node = textNode;
44
45   if (node.__text === '' && node.isSimpleText() && !node.isUnmergeable()) {
46     node.remove();
47     return;
48   }
49
50   // Backward
51   let previousNode;
52
53   while (
54     (previousNode = node.getPreviousSibling()) !== null &&
55     $isTextNode(previousNode) &&
56     previousNode.isSimpleText() &&
57     !previousNode.isUnmergeable()
58   ) {
59     if (previousNode.__text === '') {
60       previousNode.remove();
61     } else if ($canSimpleTextNodesBeMerged(previousNode, node)) {
62       node = $mergeTextNodes(previousNode, node);
63       break;
64     } else {
65       break;
66     }
67   }
68
69   // Forward
70   let nextNode;
71
72   while (
73     (nextNode = node.getNextSibling()) !== null &&
74     $isTextNode(nextNode) &&
75     nextNode.isSimpleText() &&
76     !nextNode.isUnmergeable()
77   ) {
78     if (nextNode.__text === '') {
79       nextNode.remove();
80     } else if ($canSimpleTextNodesBeMerged(node, nextNode)) {
81       node = $mergeTextNodes(node, nextNode);
82       break;
83     } else {
84       break;
85     }
86   }
87 }
88
89 export function $normalizeSelection(selection: RangeSelection): RangeSelection {
90   $normalizePoint(selection.anchor);
91   $normalizePoint(selection.focus);
92   return selection;
93 }
94
95 function $normalizePoint(point: PointType): void {
96   while (point.type === 'element') {
97     const node = point.getNode();
98     const offset = point.offset;
99     let nextNode;
100     let nextOffsetAtEnd;
101     if (offset === node.getChildrenSize()) {
102       nextNode = node.getChildAtIndex(offset - 1);
103       nextOffsetAtEnd = true;
104     } else {
105       nextNode = node.getChildAtIndex(offset);
106       nextOffsetAtEnd = false;
107     }
108     if ($isTextNode(nextNode)) {
109       point.set(
110         nextNode.__key,
111         nextOffsetAtEnd ? nextNode.getTextContentSize() : 0,
112         'text',
113       );
114       break;
115     } else if (!$isElementNode(nextNode)) {
116       break;
117     }
118     point.set(
119       nextNode.__key,
120       nextOffsetAtEnd ? nextNode.getChildrenSize() : 0,
121       'element',
122     );
123   }
124 }