]> BookStack Code Mirror - bookstack/commitdiff
Lexical: Made list selections & intendting more reliable
authorDan Brown <redacted>
Thu, 27 Mar 2025 17:49:48 +0000 (17:49 +0000)
committerDan Brown <redacted>
Thu, 27 Mar 2025 17:49:48 +0000 (17:49 +0000)
- Added handling to not include parent of top-most list range selection
  so that it's not also changed while not visually part of the
  selection range.
- Fixed issue where list items could be left over after unnesting, due
  to empty checks/removals occuring before all child handling.
- Added node sorting, applied to list items during nest operations so
  that selection range remains reliable.

resources/js/wysiwyg/utils/lists.ts
resources/js/wysiwyg/utils/nodes.ts

index 005b05f98167aa97b5998e47263ace9ec14a6382..3deb9dfb6e9b4401ef407d27a4910a2aea221ee9 100644 (file)
@@ -1,6 +1,6 @@
 import {$createTextNode, $getSelection, BaseSelection, LexicalEditor, TextNode} from "lexical";
 import {$getBlockElementNodesInSelection, $selectNodes, $toggleSelection} from "./selection";
-import {nodeHasInset} from "./nodes";
+import {$sortNodes, nodeHasInset} from "./nodes";
 import {$createListItemNode, $createListNode, $isListItemNode, $isListNode, ListItemNode} from "@lexical/list";
 
 
@@ -49,16 +49,11 @@ export function $unnestListItem(node: ListItemNode): ListItemNode {
     }
 
     const laterSiblings = node.getNextSiblings();
-
     parentListItem.insertAfter(node);
     if (list.getChildren().length === 0) {
         list.remove();
     }
 
-    if (parentListItem.getChildren().length === 0) {
-        parentListItem.remove();
-    }
-
     if (laterSiblings.length > 0) {
         const childList = $createListNode(list.getListType());
         childList.append(...laterSiblings);
@@ -69,23 +64,54 @@ export function $unnestListItem(node: ListItemNode): ListItemNode {
         list.remove();
     }
 
+    if (parentListItem.getChildren().length === 0) {
+        parentListItem.remove();
+    }
+
     return node;
 }
 
 function getListItemsForSelection(selection: BaseSelection|null): (ListItemNode|null)[] {
     const nodes = selection?.getNodes() || [];
-    const listItemNodes = [];
+    let [start, end] = selection?.getStartEndPoints() || [null, null];
+
+    // Ensure we ignore parent list items of the top-most list item since,
+    // although technically part of the selection, from a user point of
+    // view the selection does not spread to encompass this outer element.
+    const itemsToIgnore: Set<string> = new Set();
+    if (selection && start) {
+        if (selection.isBackward() && end) {
+            [end, start] = [start, end];
+        }
 
+        const startParents = start.getNode().getParents();
+        let foundList = false;
+        for (const parent of startParents) {
+            if ($isListItemNode(parent)) {
+                if (foundList) {
+                    itemsToIgnore.add(parent.getKey());
+                } else {
+                    foundList = true;
+                }
+            }
+        }
+    }
+
+    const listItemNodes = [];
     outer: for (const node of nodes) {
         if ($isListItemNode(node)) {
-            listItemNodes.push(node);
+            if (!itemsToIgnore.has(node.getKey())) {
+                listItemNodes.push(node);
+            }
             continue;
         }
 
         const parents = node.getParents();
         for (const parent of parents) {
             if ($isListItemNode(parent)) {
-                listItemNodes.push(parent);
+                if (!itemsToIgnore.has(parent.getKey())) {
+                    listItemNodes.push(parent);
+                }
                 continue outer;
             }
         }
@@ -110,7 +136,8 @@ function $reduceDedupeListItems(listItems: (ListItemNode|null)[]): ListItemNode[
         }
     }
 
-    return Object.values(listItemMap);
+    const items = Object.values(listItemMap);
+    return $sortNodes(items) as ListItemNode[];
 }
 
 export function $setInsetForSelection(editor: LexicalEditor, change: number): void {
index b5cc789550cda2a0d82f10a09911aef306a54887..591232ea385b994a9e4c9901ae2027a1a44afedf 100644 (file)
@@ -94,6 +94,30 @@ export function $getNearestNodeBlockParent(node: LexicalNode): LexicalNode|null
     return $findMatchingParent(node, isBlockNode);
 }
 
+export function $sortNodes(nodes: LexicalNode[]): LexicalNode[] {
+    const idChain: string[] = [];
+    const addIds = (n: ElementNode) => {
+        for (const child of n.getChildren()) {
+            idChain.push(child.getKey())
+            if ($isElementNode(child)) {
+                addIds(child)
+            }
+        }
+    };
+
+    const root = $getRoot();
+    addIds(root);
+
+    const sorted = Array.from(nodes);
+    sorted.sort((a, b) => {
+        const aIndex = idChain.indexOf(a.getKey());
+        const bIndex = idChain.indexOf(b.getKey());
+        return aIndex - bIndex;
+    });
+
+    return sorted;
+}
+
 export function nodeHasAlignment(node: object): node is NodeHasAlignment {
     return '__alignment' in node;
 }
Morty Proxy This is a proxified and sanitized view of the page, visit original site.