소스 검색

refactor: batch insert and delete nodes

Vincent Chan 2 년 전
부모
커밋
2f58c54b81

+ 15 - 7
frontend/app_flowy/packages/flowy_editor/lib/document/state_tree.dart

@@ -22,17 +22,21 @@ class StateTree {
     return root.childAtPath(path);
   }
 
-  bool insert(Path path, Node node) {
+  bool insert(Path path, List<Node> nodes) {
     if (path.isEmpty) {
       return false;
     }
-    final insertedNode = root.childAtPath(
+    Node? insertedNode = root.childAtPath(
       path.sublist(0, path.length - 1) + [path.last - 1],
     );
     if (insertedNode == null) {
       return false;
     }
-    insertedNode.insertAfter(node);
+    for (var i = 0; i < nodes.length; i++) {
+      final node = nodes[i];
+      insertedNode!.insertAfter(node);
+      insertedNode = node;
+    }
     return true;
   }
 
@@ -48,13 +52,17 @@ class StateTree {
     return false;
   }
 
-  Node? delete(Path path) {
+  delete(Path path, [int length = 1]) {
     if (path.isEmpty) {
       return null;
     }
-    final deletedNode = root.childAtPath(path);
-    deletedNode?.unlink();
-    return deletedNode;
+    var deletedNode = root.childAtPath(path);
+    while (deletedNode != null && length > 0) {
+      final next = deletedNode.next;
+      deletedNode.unlink();
+      length--;
+      deletedNode = next;
+    }
   }
 
   Attributes? update(Path path, Attributes attributes) {

+ 2 - 2
frontend/app_flowy/packages/flowy_editor/lib/editor_state.dart

@@ -94,11 +94,11 @@ class EditorState {
 
   _applyOperation(Operation op) {
     if (op is InsertOperation) {
-      document.insert(op.path, op.value);
+      document.insert(op.path, op.nodes);
     } else if (op is UpdateOperation) {
       document.update(op.path, op.attributes);
     } else if (op is DeleteOperation) {
-      document.delete(op.path);
+      document.delete(op.path, op.nodes.length);
     } else if (op is TextEditOperation) {
       document.textEdit(op.path, op.delta);
     }

+ 17 - 15
frontend/app_flowy/packages/flowy_editor/lib/operation/operation.dart

@@ -24,18 +24,19 @@ abstract class Operation {
 }
 
 class InsertOperation extends Operation {
-  final Node value;
+  final List<Node> nodes;
 
   factory InsertOperation.fromJson(Map<String, dynamic> map) {
     final path = map["path"] as List<int>;
-    final value = Node.fromJson(map["value"]);
+    final value =
+        (map["nodes"] as List<dynamic>).map((n) => Node.fromJson(n)).toList();
     return InsertOperation(path, value);
   }
 
-  InsertOperation(Path path, this.value) : super(path);
+  InsertOperation(Path path, this.nodes) : super(path);
 
-  InsertOperation copyWith({Path? path, Node? value}) =>
-      InsertOperation(path ?? this.path, value ?? this.value);
+  InsertOperation copyWith({Path? path, List<Node>? nodes}) =>
+      InsertOperation(path ?? this.path, nodes ?? this.nodes);
 
   @override
   Operation copyWithPath(Path path) => copyWith(path: path);
@@ -44,7 +45,7 @@ class InsertOperation extends Operation {
   Operation invert() {
     return DeleteOperation(
       path,
-      value,
+      nodes,
     );
   }
 
@@ -53,7 +54,7 @@ class InsertOperation extends Operation {
     return {
       "type": "insert-operation",
       "path": path.toList(),
-      "value": value.toJson(),
+      "nodes": nodes.map((n) => n.toJson()),
     };
   }
 }
@@ -104,28 +105,29 @@ class UpdateOperation extends Operation {
 }
 
 class DeleteOperation extends Operation {
-  final Node removedValue;
+  final List<Node> nodes;
 
   factory DeleteOperation.fromJson(Map<String, dynamic> map) {
     final path = map["path"] as List<int>;
-    final removedValue = Node.fromJson(map["removedValue"]);
-    return DeleteOperation(path, removedValue);
+    final List<Node> nodes =
+        (map["nodes"] as List<dynamic>).map((e) => Node.fromJson(e)).toList();
+    return DeleteOperation(path, nodes);
   }
 
   DeleteOperation(
     Path path,
-    this.removedValue,
+    this.nodes,
   ) : super(path);
 
-  DeleteOperation copyWith({Path? path, Node? removedValue}) =>
-      DeleteOperation(path ?? this.path, removedValue ?? this.removedValue);
+  DeleteOperation copyWith({Path? path, List<Node>? nodes}) =>
+      DeleteOperation(path ?? this.path, nodes ?? this.nodes);
 
   @override
   Operation copyWithPath(Path path) => copyWith(path: path);
 
   @override
   Operation invert() {
-    return InsertOperation(path, removedValue);
+    return InsertOperation(path, nodes);
   }
 
   @override
@@ -133,7 +135,7 @@ class DeleteOperation extends Operation {
     return {
       "type": "delete-operation",
       "path": path.toList(),
-      "removedValue": removedValue.toJson(),
+      "nodes": nodes.map((n) => n.toJson()),
     };
   }
 }

+ 21 - 3
frontend/app_flowy/packages/flowy_editor/lib/operation/transaction_builder.dart

@@ -29,8 +29,12 @@ class TransactionBuilder {
   }
 
   insertNode(Path path, Node node) {
+    insertNodes(path, [node]);
+  }
+
+  insertNodes(Path path, List<Node> nodes) {
     beforeSelection = state.cursorSelection;
-    add(InsertOperation(path, node));
+    add(InsertOperation(path, nodes));
   }
 
   updateNode(Node node, Attributes attributes) {
@@ -43,14 +47,28 @@ class TransactionBuilder {
   }
 
   deleteNode(Node node) {
-    beforeSelection = state.cursorSelection;
-    add(DeleteOperation(node.path, node));
+    deleteNodesAtPath(node.path);
   }
 
   deleteNodes(List<Node> nodes) {
     nodes.forEach(deleteNode);
   }
 
+  deleteNodesAtPath(Path path, [int length = 1]) {
+    if (path.isEmpty) {
+      return;
+    }
+    final nodes = <Node>[];
+    final prefix = path.sublist(0, path.length - 1);
+    final last = path.last;
+    for (var i = 0; i < length; i++) {
+      final node = state.document.nodeAtPath(prefix + [last + i])!;
+      nodes.add(node);
+    }
+
+    add(DeleteOperation(path, nodes));
+  }
+
   textEdit(TextNode node, Delta Function() f) {
     beforeSelection = state.cursorSelection;
     final path = node.path;

+ 2 - 5
frontend/app_flowy/packages/flowy_editor/test/flowy_editor_test.dart

@@ -1,5 +1,4 @@
 import 'dart:convert';
-import 'dart:math';
 
 import 'package:flowy_editor/document/node.dart';
 import 'package:flowy_editor/document/state_tree.dart';
@@ -50,7 +49,7 @@ void main() {
     final insertNode = Node.fromJson({
       'type': 'text',
     });
-    bool result = stateTree.insert([1, 1], insertNode);
+    bool result = stateTree.insert([1, 1], [insertNode]);
     expect(result, true);
     expect(identical(insertNode, stateTree.nodeAtPath([1, 1])), true);
   });
@@ -59,9 +58,7 @@ void main() {
     final String response = await rootBundle.loadString('assets/document.json');
     final data = Map<String, Object>.from(json.decode(response));
     final stateTree = StateTree.fromJson(data);
-    final deletedNode = stateTree.delete([1, 1]);
-    expect(deletedNode != null, true);
-    expect(deletedNode!.attributes['text-type'], 'checkbox');
+    stateTree.delete([1, 1], 1);
     final node = stateTree.nodeAtPath([1, 1]);
     expect(node != null, true);
     expect(node!.attributes['tag'], '**');

+ 6 - 6
frontend/app_flowy/packages/flowy_editor/test/operation_test.dart

@@ -28,17 +28,17 @@ void main() {
     test('insert + insert', () {
       final t = transformOperation(
           InsertOperation([0, 1],
-              Node(type: "node", attributes: {}, children: LinkedList())),
+              [Node(type: "node", attributes: {}, children: LinkedList())]),
           InsertOperation([0, 1],
-              Node(type: "node", attributes: {}, children: LinkedList())));
+              [Node(type: "node", attributes: {}, children: LinkedList())]));
       expect(t.path, [0, 2]);
     });
     test('delete + delete', () {
       final t = transformOperation(
           DeleteOperation([0, 1],
-              Node(type: "node", attributes: {}, children: LinkedList())),
+              [Node(type: "node", attributes: {}, children: LinkedList())]),
           DeleteOperation([0, 2],
-              Node(type: "node", attributes: {}, children: LinkedList())));
+              [Node(type: "node", attributes: {}, children: LinkedList())]));
       expect(t.path, [0, 1]);
     });
   });
@@ -85,7 +85,7 @@ void main() {
           {
             "type": "insert-operation",
             "path": [0],
-            "value": item1.toJson(),
+            "nodes": [item1.toJson()],
           }
         ],
       });
@@ -108,7 +108,7 @@ void main() {
           {
             "type": "delete-operation",
             "path": [0],
-            "removedValue": item1.toJson(),
+            "nodes": [item1.toJson()],
           }
         ],
       });