Forráskód Böngészése

fix: delete the nested bulleted list will lost the children nodes

Lucas.Xu 2 éve
szülő
commit
6bda1fd2ea

+ 46 - 0
frontend/app_flowy/packages/appflowy_editor/lib/src/infra/infra.dart

@@ -0,0 +1,46 @@
+import 'package:appflowy_editor/src/document/node.dart';
+
+class Infra {
+// find the forward nearest text node
+  static TextNode? forwardNearestTextNode(Node node) {
+    var previous = node.previous;
+    while (previous != null) {
+      final lastTextNode = findLastTextNode(previous);
+      if (lastTextNode != null) {
+        return lastTextNode;
+      }
+      if (previous is TextNode) {
+        return previous;
+      }
+      previous = previous.previous;
+    }
+    final parent = node.parent;
+    if (parent != null) {
+      if (parent is TextNode) {
+        return parent;
+      }
+      return forwardNearestTextNode(parent);
+    }
+    return null;
+  }
+
+  // find the last text node
+  static TextNode? findLastTextNode(Node node) {
+    final children = node.children.toList(growable: false).reversed;
+    for (final child in children) {
+      if (child.children.isNotEmpty) {
+        final result = findLastTextNode(child);
+        if (result != null) {
+          return result;
+        }
+      }
+      if (child is TextNode) {
+        return child;
+      }
+    }
+    if (node is TextNode) {
+      return node;
+    }
+    return null;
+  }
+}

+ 17 - 57
frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/backspace_handler.dart

@@ -1,7 +1,9 @@
+import 'package:appflowy_editor/src/infra/infra.dart';
 import 'package:appflowy_editor/src/service/internal_key_event_handlers/number_list_helper.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
 import 'package:appflowy_editor/appflowy_editor.dart';
+import 'package:appflowy_editor/src/extensions/path_extensions.dart';
 
 // Handle delete text.
 ShortcutEventHandler deleteTextHandler = (editorState, event) {
@@ -126,33 +128,34 @@ KeyEventResult _backDeleteToPreviousTextNode(
   List<Node> nonTextNodes,
   Selection selection,
 ) {
-  // Not reach to the root.
-  // if (textNode.parent?.parent != null) {
-  //   transactionBuilder
-  //     ..deleteNode(textNode)
-  //     ..insertNode(textNode.parent!.path.next, textNode)
-  //     ..afterSelection = Selection.collapsed(
-  //       Position(path: textNode.parent!.path.next, offset: 0),
-  //     )
-  //     ..commit();
-  //   return KeyEventResult.handled;
-  // }
+  if (textNode.next == null &&
+      textNode.children.isEmpty &&
+      textNode.parent?.parent != null) {
+    transactionBuilder
+      ..deleteNode(textNode)
+      ..insertNode(textNode.parent!.path.next, textNode)
+      ..afterSelection = Selection.collapsed(
+        Position(path: textNode.parent!.path.next, offset: 0),
+      )
+      ..commit();
+    return KeyEventResult.handled;
+  }
 
   bool prevIsNumberList = false;
-  final previousTextNode = forwardNearestTextNode(textNode);
+  final previousTextNode = Infra.forwardNearestTextNode(textNode);
   if (previousTextNode != null) {
     if (previousTextNode.subtype == BuiltInAttributeKey.numberList) {
       prevIsNumberList = true;
     }
 
     transactionBuilder.mergeText(previousTextNode, textNode);
-    transactionBuilder.deleteNode(textNode);
     if (textNode.children.isNotEmpty) {
       transactionBuilder.insertNodes(
-        previousTextNode.path + [0],
+        previousTextNode.path.next,
         textNode.children.toList(growable: false),
       );
     }
+    transactionBuilder.deleteNode(textNode);
     transactionBuilder.afterSelection = Selection.collapsed(
       Position(
         path: previousTextNode.path,
@@ -273,46 +276,3 @@ void _deleteTextNodes(TransactionBuilder transactionBuilder,
       secondOffset: selection.end.offset,
     );
 }
-
-// TODO: Just a simple solution for textNode, need to be optimized.
-TextNode? findLastTextNode(Node node) {
-  final children = node.children.toList(growable: false).reversed;
-  for (final child in children) {
-    if (child.children.isNotEmpty) {
-      final result = findLastTextNode(child);
-      if (result != null) {
-        return result;
-      }
-    }
-    if (child is TextNode) {
-      return child;
-    }
-  }
-  if (node is TextNode) {
-    return node;
-  }
-  return null;
-}
-
-// find the forward nearest text node
-TextNode? forwardNearestTextNode(Node node) {
-  var previous = node.previous;
-  while (previous != null) {
-    final lastTextNode = findLastTextNode(previous);
-    if (lastTextNode != null) {
-      return lastTextNode;
-    }
-    if (previous is TextNode) {
-      return previous;
-    }
-    previous = previous.previous;
-  }
-  final parent = node.parent;
-  if (parent != null) {
-    if (parent is TextNode) {
-      return parent;
-    }
-    return forwardNearestTextNode(parent);
-  }
-  return null;
-}

+ 51 - 0
frontend/app_flowy/packages/appflowy_editor/test/infra/infra_test.dart

@@ -0,0 +1,51 @@
+import 'package:appflowy_editor/appflowy_editor.dart';
+import 'package:appflowy_editor/src/infra/infra.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+void main() async {
+  group('infra.dart', () {
+    test('find the last text node', () {
+      // * Welcome to Appflowy 😁
+      //  * Welcome to Appflowy 😁
+      //  * Welcome to Appflowy 😁
+      //    * Welcome to Appflowy 😁
+      //    * Welcome to Appflowy 😁
+      //      * Welcome to Appflowy 😁
+      //      * Welcome to Appflowy 😁
+      const text = 'Welcome to Appflowy 😁';
+      TextNode textNode() {
+        return TextNode(
+          type: 'text',
+          delta: Delta()..insert(text),
+        );
+      }
+
+      final node110 = textNode();
+      final node111 = textNode();
+      final node11 = textNode()
+        ..insert(node110)
+        ..insert(node111);
+      final node10 = textNode();
+      final node1 = textNode()
+        ..insert(node10)
+        ..insert(node11);
+      final node0 = textNode();
+      final node = textNode()
+        ..insert(node0)
+        ..insert(node1);
+
+      expect(Infra.findLastTextNode(node)?.path, [1, 1, 1]);
+      expect(Infra.findLastTextNode(node0)?.path, [0]);
+      expect(Infra.findLastTextNode(node1)?.path, [1, 1, 1]);
+      expect(Infra.findLastTextNode(node10)?.path, [1, 0]);
+      expect(Infra.findLastTextNode(node11)?.path, [1, 1, 1]);
+
+      expect(Infra.forwardNearestTextNode(node111)?.path, [1, 1, 0]);
+      expect(Infra.forwardNearestTextNode(node110)?.path, [1, 1]);
+      expect(Infra.forwardNearestTextNode(node11)?.path, [1, 0]);
+      expect(Infra.forwardNearestTextNode(node10)?.path, [1]);
+      expect(Infra.forwardNearestTextNode(node1)?.path, [0]);
+      expect(Infra.forwardNearestTextNode(node0)?.path, []);
+    });
+  });
+}

+ 9 - 57
frontend/app_flowy/packages/appflowy_editor/test/service/internal_key_event_handlers/backspace_handler_test.dart

@@ -3,7 +3,6 @@ import 'dart:collection';
 import 'package:appflowy_editor/appflowy_editor.dart';
 import 'package:appflowy_editor/src/render/image/image_node_widget.dart';
 import 'package:appflowy_editor/src/render/rich_text/flowy_rich_text.dart';
-import 'package:appflowy_editor/src/service/internal_key_event_handlers/backspace_handler.dart';
 import 'package:flutter/services.dart';
 import 'package:flutter_test/flutter_test.dart';
 import 'package:network_image_mock/network_image_mock.dart';
@@ -383,73 +382,26 @@ void main() async {
     //    * Welcome to Appflowy 😁
     // After
     // * Welcome to Appflowy 😁
+    //  * Welcome to Appflowy 😁Welcome to Appflowy 😁
+    //  * Welcome to Appflowy 😁
     //  * Welcome to Appflowy 😁
-    // Welcome to Appflowy 😁
-    //   * Welcome to Appflowy 😁
-    //   * Welcome to Appflowy 😁
     await editor.pressLogicKey(LogicalKeyboardKey.backspace);
     expect(
-      editor.nodeAtPath([1])!.subtype != BuiltInAttributeKey.bulletedList,
+      editor.nodeAtPath([0, 0])!.subtype == BuiltInAttributeKey.bulletedList,
       true,
     );
     expect(
-      editor.nodeAtPath([1, 0])!.subtype == BuiltInAttributeKey.bulletedList,
+      (editor.nodeAtPath([0, 0]) as TextNode).toRawString() == text * 2,
       true,
     );
     expect(
-      editor.nodeAtPath([1, 1])!.subtype == BuiltInAttributeKey.bulletedList,
+      editor.nodeAtPath([0, 1])!.subtype == BuiltInAttributeKey.bulletedList,
+      true,
+    );
+    expect(
+      editor.nodeAtPath([0, 2])!.subtype == BuiltInAttributeKey.bulletedList,
       true,
     );
-
-    // After
-    // * Welcome to Appflowy 😁
-    //  * Welcome to Appflowy 😁Welcome to Appflowy 😁
-    // * Welcome to Appflowy 😁
-    // * Welcome to Appflowy 😁
-  });
-
-  test('find the last text node', () {
-    // * Welcome to Appflowy 😁
-    //  * Welcome to Appflowy 😁
-    //  * Welcome to Appflowy 😁
-    //    * Welcome to Appflowy 😁
-    //    * Welcome to Appflowy 😁
-    //      * Welcome to Appflowy 😁
-    //      * Welcome to Appflowy 😁
-    const text = 'Welcome to Appflowy 😁';
-    TextNode textNode() {
-      return TextNode(
-        type: 'text',
-        delta: Delta()..insert(text),
-      );
-    }
-
-    final node110 = textNode();
-    final node111 = textNode();
-    final node11 = textNode()
-      ..insert(node110)
-      ..insert(node111);
-    final node10 = textNode();
-    final node1 = textNode()
-      ..insert(node10)
-      ..insert(node11);
-    final node0 = textNode();
-    final node = textNode()
-      ..insert(node0)
-      ..insert(node1);
-
-    expect(findLastTextNode(node)?.path, [1, 1, 1]);
-    expect(findLastTextNode(node0)?.path, [0]);
-    expect(findLastTextNode(node1)?.path, [1, 1, 1]);
-    expect(findLastTextNode(node10)?.path, [1, 0]);
-    expect(findLastTextNode(node11)?.path, [1, 1, 1]);
-
-    expect(forwardNearestTextNode(node111)?.path, [1, 1, 0]);
-    expect(forwardNearestTextNode(node110)?.path, [1, 1]);
-    expect(forwardNearestTextNode(node11)?.path, [1, 0]);
-    expect(forwardNearestTextNode(node10)?.path, [1]);
-    expect(forwardNearestTextNode(node1)?.path, [0]);
-    expect(forwardNearestTextNode(node0)?.path, []);
   });
 }