]> BookStack Code Mirror - bookstack/blobdiff - resources/js/wysiwyg/utils/selection.ts
respective book and chapter structure added.
[bookstack] / resources / js / wysiwyg / utils / selection.ts
index 74dd94527433c155a2620107a7995f7ead508365..67c2d91b26c0bc086a6c2bec747631c6c587a729 100644 (file)
@@ -1,23 +1,24 @@
 import {
     $createNodeSelection,
-    $createParagraphNode,
+    $createParagraphNode, $createRangeSelection,
     $getRoot,
-    $getSelection,
+    $getSelection, $isBlockElementNode, $isDecoratorNode,
     $isElementNode,
     $isTextNode,
     $setSelection,
-    BaseSelection,
+    BaseSelection, DecoratorNode,
     ElementFormatType,
     ElementNode, LexicalEditor,
     LexicalNode,
-    TextFormatType
+    TextFormatType, TextNode
 } from "lexical";
 import {$findMatchingParent, $getNearestBlockElementAncestorOrThrow} from "@lexical/utils";
 import {LexicalElementNodeCreator, LexicalNodeMatcher} from "../nodes";
 import {$setBlocksType} from "@lexical/selection";
 
-import {$getParentOfType} from "./nodes";
+import {$getNearestNodeBlockParent, $getParentOfType, nodeHasAlignment} from "./nodes";
 import {$createCustomParagraphNode} from "../nodes/custom-paragraph";
+import {CommonBlockAlignment} from "../nodes/_common";
 
 const lastSelectionByEditor = new WeakMap<LexicalEditor, BaseSelection|null>;
 
@@ -81,8 +82,8 @@ export function $insertNewBlockNodeAtSelection(node: LexicalNode, insertAfter: b
 }
 
 export function $insertNewBlockNodesAtSelection(nodes: LexicalNode[], insertAfter: boolean = true) {
-    const selection = $getSelection();
-    const blockElement = selection ? $getNearestBlockElementAncestorOrThrow(selection.getNodes()[0]) : null;
+    const selectionNodes = $getSelection()?.getNodes() || [];
+    const blockElement = selectionNodes.length > 0 ? $getNearestNodeBlockParent(selectionNodes[0]) : null;
 
     if (blockElement) {
         if (insertAfter) {
@@ -105,6 +106,69 @@ export function $selectSingleNode(node: LexicalNode) {
     $setSelection(nodeSelection);
 }
 
+function getFirstTextNodeInNodes(nodes: LexicalNode[]): TextNode|null {
+    for (const node of nodes) {
+        if ($isTextNode(node)) {
+            return node;
+        }
+
+        if ($isElementNode(node)) {
+            const children = node.getChildren();
+            const textNode = getFirstTextNodeInNodes(children);
+            if (textNode !== null) {
+                return textNode;
+            }
+        }
+    }
+
+    return null;
+}
+
+function getLastTextNodeInNodes(nodes: LexicalNode[]): TextNode|null {
+    const revNodes = [...nodes].reverse();
+    for (const node of revNodes) {
+        if ($isTextNode(node)) {
+            return node;
+        }
+
+        if ($isElementNode(node)) {
+            const children = [...node.getChildren()].reverse();
+            const textNode = getLastTextNodeInNodes(children);
+            if (textNode !== null) {
+                return textNode;
+            }
+        }
+    }
+
+    return null;
+}
+
+export function $selectNodes(nodes: LexicalNode[]) {
+    if (nodes.length === 0) {
+        return;
+    }
+
+    const selection = $createRangeSelection();
+    const firstText = getFirstTextNodeInNodes(nodes);
+    const lastText = getLastTextNodeInNodes(nodes);
+    if (firstText && lastText) {
+        selection.setTextNodeRange(firstText, 0, lastText, lastText.getTextContentSize() || 0)
+        $setSelection(selection);
+    }
+}
+
+export function $toggleSelection(editor: LexicalEditor) {
+    const lastSelection = getLastSelection(editor);
+
+    if (lastSelection) {
+        window.requestAnimationFrame(() => {
+            editor.update(() => {
+                $setSelection(lastSelection.clone());
+            })
+        });
+    }
+}
+
 export function $selectionContainsNode(selection: BaseSelection | null, node: LexicalNode): boolean {
     if (!selection) {
         return false;
@@ -120,10 +184,30 @@ export function $selectionContainsNode(selection: BaseSelection | null, node: Le
     return false;
 }
 
-export function $selectionContainsElementFormat(selection: BaseSelection | null, format: ElementFormatType): boolean {
-    const nodes = $getBlockElementNodesInSelection(selection);
+export function $selectionContainsAlignment(selection: BaseSelection | null, alignment: CommonBlockAlignment): boolean {
+
+    const nodes = [
+        ...(selection?.getNodes() || []),
+        ...$getBlockElementNodesInSelection(selection)
+    ];
     for (const node of nodes) {
-        if (node.getFormatType() === format) {
+        if (nodeHasAlignment(node) && node.getAlignment() === alignment) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+export function $selectionContainsDirection(selection: BaseSelection | null, direction: 'rtl'|'ltr'): boolean {
+
+    const nodes = [
+        ...(selection?.getNodes() || []),
+        ...$getBlockElementNodesInSelection(selection)
+    ];
+
+    for (const node of nodes) {
+        if ($isBlockElementNode(node) && node.getDirection() === direction) {
             return true;
         }
     }
@@ -138,14 +222,19 @@ export function $getBlockElementNodesInSelection(selection: BaseSelection | null
 
     const blockNodes: Map<string, ElementNode> = new Map();
     for (const node of selection.getNodes()) {
-        const blockElement = $findMatchingParent(node, (node) => {
-            return $isElementNode(node) && !node.isInline();
-        }) as ElementNode | null;
-
-        if (blockElement) {
+        const blockElement = $getNearestNodeBlockParent(node);
+        if ($isElementNode(blockElement)) {
             blockNodes.set(blockElement.getKey(), blockElement);
         }
     }
 
     return Array.from(blockNodes.values());
+}
+
+export function $getDecoratorNodesInSelection(selection: BaseSelection | null): DecoratorNode<any>[] {
+    if (!selection) {
+        return [];
+    }
+
+    return selection.getNodes().filter(node => $isDecoratorNode(node));
 }
\ No newline at end of file