]> BookStack Code Mirror - bookstack/blob - resources/js/wysiwyg/utils/selection.ts
e34afbe36e86f89ac517f7a40307943d9c7f6b7e
[bookstack] / resources / js / wysiwyg / utils / selection.ts
1 import {
2     $createNodeSelection,
3     $createParagraphNode,
4     $getRoot,
5     $getSelection,
6     $isElementNode,
7     $isTextNode,
8     $setSelection,
9     BaseSelection,
10     ElementFormatType,
11     ElementNode,
12     LexicalNode,
13     TextFormatType
14 } from "lexical";
15 import {$findMatchingParent, $getNearestBlockElementAncestorOrThrow} from "@lexical/utils";
16 import {LexicalElementNodeCreator, LexicalNodeMatcher} from "../nodes";
17 import {$setBlocksType} from "@lexical/selection";
18
19 import {$getParentOfType} from "./nodes";
20
21 export function $selectionContainsNodeType(selection: BaseSelection | null, matcher: LexicalNodeMatcher): boolean {
22     return $getNodeFromSelection(selection, matcher) !== null;
23 }
24
25 export function $getNodeFromSelection(selection: BaseSelection | null, matcher: LexicalNodeMatcher): LexicalNode | null {
26     if (!selection) {
27         return null;
28     }
29
30     for (const node of selection.getNodes()) {
31         if (matcher(node)) {
32             return node;
33         }
34
35         const matchedParent = $getParentOfType(node, matcher);
36         if (matchedParent) {
37             return matchedParent;
38         }
39     }
40
41     return null;
42 }
43
44 export function $selectionContainsTextFormat(selection: BaseSelection | null, format: TextFormatType): boolean {
45     if (!selection) {
46         return false;
47     }
48
49     for (const node of selection.getNodes()) {
50         if ($isTextNode(node) && node.hasFormat(format)) {
51             return true;
52         }
53     }
54
55     return false;
56 }
57
58 export function $toggleSelectionBlockNodeType(matcher: LexicalNodeMatcher, creator: LexicalElementNodeCreator) {
59     const selection = $getSelection();
60     const blockElement = selection ? $getNearestBlockElementAncestorOrThrow(selection.getNodes()[0]) : null;
61     if (selection && matcher(blockElement)) {
62         $setBlocksType(selection, $createParagraphNode);
63     } else {
64         $setBlocksType(selection, creator);
65     }
66 }
67
68 export function $insertNewBlockNodeAtSelection(node: LexicalNode, insertAfter: boolean = true) {
69     $insertNewBlockNodesAtSelection([node], insertAfter);
70 }
71
72 export function $insertNewBlockNodesAtSelection(nodes: LexicalNode[], insertAfter: boolean = true) {
73     const selection = $getSelection();
74     const blockElement = selection ? $getNearestBlockElementAncestorOrThrow(selection.getNodes()[0]) : null;
75
76     if (blockElement) {
77         if (insertAfter) {
78             for (let i = nodes.length - 1; i >= 0; i--) {
79                 blockElement.insertAfter(nodes[i]);
80             }
81         } else {
82             for (const node of nodes) {
83                 blockElement.insertBefore(node);
84             }
85         }
86     } else {
87         $getRoot().append(...nodes);
88     }
89 }
90
91 export function $selectSingleNode(node: LexicalNode) {
92     const nodeSelection = $createNodeSelection();
93     nodeSelection.add(node.getKey());
94     $setSelection(nodeSelection);
95 }
96
97 export function $selectionContainsNode(selection: BaseSelection | null, node: LexicalNode): boolean {
98     if (!selection) {
99         return false;
100     }
101
102     const key = node.getKey();
103     for (const node of selection.getNodes()) {
104         if (node.getKey() === key) {
105             return true;
106         }
107     }
108
109     return false;
110 }
111
112 export function $selectionContainsElementFormat(selection: BaseSelection | null, format: ElementFormatType): boolean {
113     const nodes = $getBlockElementNodesInSelection(selection);
114     for (const node of nodes) {
115         if (node.getFormatType() === format) {
116             return true;
117         }
118     }
119
120     return false;
121 }
122
123 export function $getBlockElementNodesInSelection(selection: BaseSelection | null): ElementNode[] {
124     if (!selection) {
125         return [];
126     }
127
128     const blockNodes: Map<string, ElementNode> = new Map();
129     for (const node of selection.getNodes()) {
130         const blockElement = $findMatchingParent(node, (node) => {
131             return $isElementNode(node) && !node.isInline();
132         }) as ElementNode | null;
133
134         if (blockElement) {
135             blockNodes.set(blockElement.getKey(), blockElement);
136         }
137     }
138
139     return Array.from(blockNodes.values());
140 }