]> BookStack Code Mirror - bookstack/blob - resources/js/wysiwyg/helpers.ts
Lexical: Added table creator UI
[bookstack] / resources / js / wysiwyg / helpers.ts
1 import {
2     $createParagraphNode, $getRoot,
3     $getSelection,
4     $isTextNode,
5     BaseSelection, ElementNode,
6     LexicalEditor, LexicalNode, TextFormatType
7 } from "lexical";
8 import {LexicalElementNodeCreator, LexicalNodeMatcher} from "./nodes";
9 import {$getNearestBlockElementAncestorOrThrow} from "@lexical/utils";
10 import {$setBlocksType} from "@lexical/selection";
11 import {$createDetailsNode} from "./nodes/details";
12
13 export function el(tag: string, attrs: Record<string, string|null> = {}, children: (string|HTMLElement)[] = []): HTMLElement {
14     const el = document.createElement(tag);
15     const attrKeys = Object.keys(attrs);
16     for (const attr of attrKeys) {
17         if (attrs[attr] !== null) {
18             el.setAttribute(attr, attrs[attr] as string);
19         }
20     }
21
22     for (const child of children) {
23         if (typeof child === 'string') {
24             el.append(document.createTextNode(child));
25         } else {
26             el.append(child);
27         }
28     }
29
30     return el;
31 }
32
33 export function selectionContainsNodeType(selection: BaseSelection|null, matcher: LexicalNodeMatcher): boolean {
34     return getNodeFromSelection(selection, matcher) !== null;
35 }
36
37 export function getNodeFromSelection(selection: BaseSelection|null, matcher: LexicalNodeMatcher): LexicalNode|null {
38     if (!selection) {
39         return null;
40     }
41
42     for (const node of selection.getNodes()) {
43         if (matcher(node)) {
44             return node;
45         }
46
47         for (const parent of node.getParents()) {
48             if (matcher(parent)) {
49                 return parent;
50             }
51         }
52     }
53
54     return null;
55 }
56
57 export function selectionContainsTextFormat(selection: BaseSelection|null, format: TextFormatType): boolean {
58     if (!selection) {
59         return false;
60     }
61
62     for (const node of selection.getNodes()) {
63         if ($isTextNode(node) && node.hasFormat(format)) {
64             return true;
65         }
66     }
67
68     return false;
69 }
70
71 export function toggleSelectionBlockNodeType(editor: LexicalEditor, matcher: LexicalNodeMatcher, creator: LexicalElementNodeCreator) {
72     editor.update(() => {
73         const selection = $getSelection();
74         const blockElement = selection ? $getNearestBlockElementAncestorOrThrow(selection.getNodes()[0]) : null;
75         if (selection && matcher(blockElement)) {
76             $setBlocksType(selection, $createParagraphNode);
77         } else {
78             $setBlocksType(selection, creator);
79         }
80     });
81 }
82
83 export function insertNewBlockNodeAtSelection(node: LexicalNode) {
84     const selection = $getSelection();
85     const blockElement = selection ? $getNearestBlockElementAncestorOrThrow(selection.getNodes()[0]) : null;
86
87     if (blockElement) {
88         blockElement.insertAfter(node);
89     } else {
90         $getRoot().append(node);
91     }
92 }