]> BookStack Code Mirror - bookstack/blobdiff - resources/js/wysiwyg/helpers.ts
Lexical: Added code block selection & edit features
[bookstack] / resources / js / wysiwyg / helpers.ts
index 720f3c6d5dab9b820eee5974c2f405c6cc3c5142..3708c2b25917a375e6b8c98fc60e3d1a8f5cd154 100644 (file)
@@ -1,25 +1,70 @@
-import {$createParagraphNode, $getSelection, BaseSelection, LexicalEditor} from "lexical";
-import {LexicalElementNodeCreator, LexicalNodeMatcher} from "./nodes";
+import {
+    $createNodeSelection,
+    $createParagraphNode, $getRoot,
+    $getSelection,
+    $isTextNode, $setSelection,
+    BaseSelection,
+    LexicalEditor, LexicalNode, TextFormatType
+} from "lexical";
+import {getNodesForPageEditor, LexicalElementNodeCreator, LexicalNodeMatcher} from "./nodes";
 import {$getNearestBlockElementAncestorOrThrow} from "@lexical/utils";
 import {$setBlocksType} from "@lexical/selection";
 
+export function el(tag: string, attrs: Record<string, string|null> = {}, children: (string|HTMLElement)[] = []): HTMLElement {
+    const el = document.createElement(tag);
+    const attrKeys = Object.keys(attrs);
+    for (const attr of attrKeys) {
+        if (attrs[attr] !== null) {
+            el.setAttribute(attr, attrs[attr] as string);
+        }
+    }
+
+    for (const child of children) {
+        if (typeof child === 'string') {
+            el.append(document.createTextNode(child));
+        } else {
+            el.append(child);
+        }
+    }
+
+    return el;
+}
+
 export function selectionContainsNodeType(selection: BaseSelection|null, matcher: LexicalNodeMatcher): boolean {
+    return getNodeFromSelection(selection, matcher) !== null;
+}
+
+export function getNodeFromSelection(selection: BaseSelection|null, matcher: LexicalNodeMatcher): LexicalNode|null {
     if (!selection) {
-        return false;
+        return null;
     }
 
     for (const node of selection.getNodes()) {
         if (matcher(node)) {
-            return true;
+            return node;
         }
 
         for (const parent of node.getParents()) {
             if (matcher(parent)) {
-                return true;
+                return parent;
             }
         }
     }
 
+    return null;
+}
+
+export function selectionContainsTextFormat(selection: BaseSelection|null, format: TextFormatType): boolean {
+    if (!selection) {
+        return false;
+    }
+
+    for (const node of selection.getNodes()) {
+        if ($isTextNode(node) && node.hasFormat(format)) {
+            return true;
+        }
+    }
+
     return false;
 }
 
@@ -33,4 +78,40 @@ export function toggleSelectionBlockNodeType(editor: LexicalEditor, matcher: Lex
             $setBlocksType(selection, creator);
         }
     });
+}
+
+export function insertNewBlockNodeAtSelection(node: LexicalNode, insertAfter: boolean = true) {
+    const selection = $getSelection();
+    const blockElement = selection ? $getNearestBlockElementAncestorOrThrow(selection.getNodes()[0]) : null;
+
+    if (blockElement) {
+        if (insertAfter) {
+            blockElement.insertAfter(node);
+        } else {
+            blockElement.insertBefore(node);
+        }
+    } else {
+        $getRoot().append(node);
+    }
+}
+
+export function selectSingleNode(node: LexicalNode) {
+    const nodeSelection = $createNodeSelection();
+    nodeSelection.add(node.getKey());
+    $setSelection(nodeSelection);
+}
+
+export function selectionContainsNode(selection: BaseSelection|null, node: LexicalNode): boolean {
+    if (!selection) {
+        return false;
+    }
+
+    const key = node.getKey();
+    for (const node of selection.getNodes()) {
+        if (node.getKey() === key) {
+            return true;
+        }
+    }
+
+    return false;
 }
\ No newline at end of file