Pārlūkot izejas kodu

refactor: move operation.dart to core/transform

Lucas.Xu 2 gadi atpakaļ
vecāks
revīzija
319875529f

+ 1 - 1
frontend/app_flowy/packages/appflowy_editor/lib/appflowy_editor.dart

@@ -12,7 +12,7 @@ export 'src/core/document/text_delta.dart';
 export 'src/core/document/attributes.dart';
 export 'src/core/legacy/built_in_attribute_keys.dart';
 export 'src/editor_state.dart';
-export 'src/operation/operation.dart';
+export 'src/core/transform/operation.dart';
 export 'src/operation/transaction.dart';
 export 'src/operation/transaction_builder.dart';
 export 'src/render/selection/selectable.dart';

+ 1 - 1
frontend/app_flowy/packages/appflowy_editor/lib/src/core/document/document.dart

@@ -42,7 +42,7 @@ class Document {
   }
 
   /// Inserts a [Node]s at the given [Path].
-  bool insert(Path path, List<Node> nodes) {
+  bool insert(Path path, Iterable<Node> nodes) {
     if (path.isEmpty || nodes.isEmpty) {
       return false;
     }

+ 216 - 0
frontend/app_flowy/packages/appflowy_editor/lib/src/core/transform/operation.dart

@@ -0,0 +1,216 @@
+import 'package:appflowy_editor/src/core/document/attributes.dart';
+import 'package:appflowy_editor/src/core/document/node.dart';
+import 'package:appflowy_editor/src/core/document/path.dart';
+import 'package:appflowy_editor/src/core/document/text_delta.dart';
+
+/// [Operation] represents a change to a [Document].
+abstract class Operation {
+  Operation(
+    this.path,
+  );
+
+  factory Operation.fromJson() => throw UnimplementedError();
+
+  final Path path;
+
+  /// Inverts the operation.
+  ///
+  /// Returns the inverted operation.
+  Operation invert();
+
+  /// Returns the JSON representation of the operation.
+  Map<String, dynamic> toJson();
+
+  Operation copyWith({Path? path});
+}
+
+/// [InsertOperation] represents an insert operation.
+class InsertOperation extends Operation {
+  InsertOperation(
+    super.path,
+    this.nodes,
+  );
+
+  factory InsertOperation.fromJson(Map<String, dynamic> json) {
+    final path = json['path'] as Path;
+    final nodes = (json['nodes'] as List).map((n) => Node.fromJson(n));
+    return InsertOperation(path, nodes);
+  }
+
+  final Iterable<Node> nodes;
+
+  @override
+  Operation invert() => DeleteOperation(path, nodes);
+
+  @override
+  Map<String, dynamic> toJson() {
+    return {
+      'op': 'insert',
+      'path': path,
+      'nodes': nodes.map((n) => n.toJson()),
+    };
+  }
+
+  @override
+  Operation copyWith({Path? path}) {
+    return InsertOperation(path ?? this.path, nodes);
+  }
+}
+
+/// [DeleteOperation] represents a delete operation.
+class DeleteOperation extends Operation {
+  DeleteOperation(
+    super.path,
+    this.nodes,
+  );
+
+  factory DeleteOperation.fromJson(Map<String, dynamic> json) {
+    final path = json['path'] as Path;
+    final nodes = (json['nodes'] as List).map((n) => Node.fromJson(n));
+    return DeleteOperation(path, nodes);
+  }
+
+  final Iterable<Node> nodes;
+
+  @override
+  Operation invert() => InsertOperation(path, nodes);
+
+  @override
+  Map<String, dynamic> toJson() {
+    return {
+      'op': 'delete',
+      'path': path,
+      'nodes': nodes.map((n) => n.toJson()),
+    };
+  }
+
+  @override
+  Operation copyWith({Path? path}) {
+    return DeleteOperation(path ?? this.path, nodes);
+  }
+}
+
+/// [UpdateOperation] represents an attributes update operation.
+class UpdateOperation extends Operation {
+  UpdateOperation(
+    super.path,
+    this.attributes,
+    this.oldAttributes,
+  );
+
+  factory UpdateOperation.fromJson(Map<String, dynamic> json) {
+    final path = json['path'] as Path;
+    final oldAttributes = json['oldAttributes'] as Attributes;
+    final attributes = json['attributes'] as Attributes;
+    return UpdateOperation(
+      path,
+      attributes,
+      oldAttributes,
+    );
+  }
+
+  final Attributes attributes;
+  final Attributes oldAttributes;
+
+  @override
+  Operation invert() => UpdateOperation(
+        path,
+        oldAttributes,
+        attributes,
+      );
+
+  @override
+  Map<String, dynamic> toJson() {
+    return {
+      'op': 'update',
+      'path': path,
+      'attributes': {...attributes},
+      'oldAttributes': {...oldAttributes},
+    };
+  }
+
+  @override
+  Operation copyWith({Path? path}) {
+    return UpdateOperation(
+      path ?? this.path,
+      {...attributes},
+      {...oldAttributes},
+    );
+  }
+}
+
+/// [UpdateTextOperation] represents a text update operation.
+class UpdateTextOperation extends Operation {
+  UpdateTextOperation(
+    super.path,
+    this.delta,
+    this.inverted,
+  );
+
+  factory UpdateTextOperation.fromJson(Map<String, dynamic> json) {
+    final path = json['path'] as Path;
+    final delta = Delta.fromJson(json['delta']);
+    final inverted = Delta.fromJson(json['invert']);
+    return UpdateTextOperation(path, delta, inverted);
+  }
+
+  final Delta delta;
+  final Delta inverted;
+
+  @override
+  Operation invert() => UpdateTextOperation(path, inverted, delta);
+
+  @override
+  Map<String, dynamic> toJson() {
+    return {
+      'op': 'update_text',
+      'path': path,
+      'delta': delta.toJson(),
+      'inverted': inverted.toJson(),
+    };
+  }
+
+  @override
+  Operation copyWith({Path? path}) {
+    return UpdateTextOperation(path ?? this.path, delta, inverted);
+  }
+}
+
+// TODO(Lucas.Xu): refactor this part
+Path transformPath(Path preInsertPath, Path b, [int delta = 1]) {
+  if (preInsertPath.length > b.length) {
+    return b;
+  }
+  if (preInsertPath.isEmpty || b.isEmpty) {
+    return b;
+  }
+  // check the prefix
+  for (var i = 0; i < preInsertPath.length - 1; i++) {
+    if (preInsertPath[i] != b[i]) {
+      return b;
+    }
+  }
+  final prefix = preInsertPath.sublist(0, preInsertPath.length - 1);
+  final suffix = b.sublist(preInsertPath.length);
+  final preInsertLast = preInsertPath.last;
+  final bAtIndex = b[preInsertPath.length - 1];
+  if (preInsertLast <= bAtIndex) {
+    prefix.add(bAtIndex + delta);
+  } else {
+    prefix.add(bAtIndex);
+  }
+  prefix.addAll(suffix);
+  return prefix;
+}
+
+Operation transformOperation(Operation a, Operation b) {
+  if (a is InsertOperation) {
+    final newPath = transformPath(a.path, b.path, a.nodes.length);
+    return b.copyWith(path: newPath);
+  } else if (a is DeleteOperation) {
+    final newPath = transformPath(a.path, b.path, -1 * a.nodes.length);
+    return b.copyWith(path: newPath);
+  }
+  // TODO: transform update and textedit
+  return b;
+}

+ 2 - 2
frontend/app_flowy/packages/appflowy_editor/lib/src/editor_state.dart

@@ -7,7 +7,7 @@ import 'package:flutter/material.dart';
 
 import 'package:appflowy_editor/src/core/location/selection.dart';
 import 'package:appflowy_editor/src/core/document/document.dart';
-import 'package:appflowy_editor/src/operation/operation.dart';
+import 'package:appflowy_editor/src/core/transform/operation.dart';
 import 'package:appflowy_editor/src/operation/transaction.dart';
 import 'package:appflowy_editor/src/undo_manager.dart';
 
@@ -166,7 +166,7 @@ class EditorState {
       document.update(op.path, op.attributes);
     } else if (op is DeleteOperation) {
       document.delete(op.path, op.nodes.length);
-    } else if (op is TextEditOperation) {
+    } else if (op is UpdateTextOperation) {
       document.updateText(op.path, op.delta);
     }
     _observer.add(op);

+ 0 - 218
frontend/app_flowy/packages/appflowy_editor/lib/src/operation/operation.dart

@@ -1,218 +0,0 @@
-import 'package:appflowy_editor/appflowy_editor.dart';
-
-abstract class Operation {
-  factory Operation.fromJson(Map<String, dynamic> map) {
-    String t = map["op"] as String;
-    if (t == "insert") {
-      return InsertOperation.fromJson(map);
-    } else if (t == "update") {
-      return UpdateOperation.fromJson(map);
-    } else if (t == "delete") {
-      return DeleteOperation.fromJson(map);
-    } else if (t == "text-edit") {
-      return TextEditOperation.fromJson(map);
-    }
-
-    throw ArgumentError('unexpected type $t');
-  }
-  final Path path;
-  Operation(this.path);
-  Operation copyWithPath(Path path);
-  Operation invert();
-  Map<String, dynamic> toJson();
-}
-
-class InsertOperation extends Operation {
-  final List<Node> nodes;
-
-  factory InsertOperation.fromJson(Map<String, dynamic> map) {
-    final path = map["path"] as List<int>;
-    final value =
-        (map["nodes"] as List<dynamic>).map((n) => Node.fromJson(n)).toList();
-    return InsertOperation(path, value);
-  }
-
-  InsertOperation(Path path, this.nodes) : super(path);
-
-  InsertOperation copyWith({Path? path, List<Node>? nodes}) =>
-      InsertOperation(path ?? this.path, nodes ?? this.nodes);
-
-  @override
-  Operation copyWithPath(Path path) => copyWith(path: path);
-
-  @override
-  Operation invert() {
-    return DeleteOperation(
-      path,
-      nodes,
-    );
-  }
-
-  @override
-  Map<String, dynamic> toJson() {
-    return {
-      "op": "insert",
-      "path": path.toList(),
-      "nodes": nodes.map((n) => n.toJson()),
-    };
-  }
-}
-
-class UpdateOperation extends Operation {
-  final Attributes attributes;
-  final Attributes oldAttributes;
-
-  factory UpdateOperation.fromJson(Map<String, dynamic> map) {
-    final path = map["path"] as List<int>;
-    final attributes = map["attributes"] as Map<String, dynamic>;
-    final oldAttributes = map["oldAttributes"] as Map<String, dynamic>;
-    return UpdateOperation(path, attributes, oldAttributes);
-  }
-
-  UpdateOperation(
-    Path path,
-    this.attributes,
-    this.oldAttributes,
-  ) : super(path);
-
-  UpdateOperation copyWith(
-          {Path? path, Attributes? attributes, Attributes? oldAttributes}) =>
-      UpdateOperation(path ?? this.path, attributes ?? this.attributes,
-          oldAttributes ?? this.oldAttributes);
-
-  @override
-  Operation copyWithPath(Path path) => copyWith(path: path);
-
-  @override
-  Operation invert() {
-    return UpdateOperation(
-      path,
-      oldAttributes,
-      attributes,
-    );
-  }
-
-  @override
-  Map<String, dynamic> toJson() {
-    return {
-      "op": "update",
-      "path": path.toList(),
-      "attributes": {...attributes},
-      "oldAttributes": {...oldAttributes},
-    };
-  }
-}
-
-class DeleteOperation extends Operation {
-  final List<Node> nodes;
-
-  factory DeleteOperation.fromJson(Map<String, dynamic> map) {
-    final path = map["path"] as List<int>;
-    final List<Node> nodes =
-        (map["nodes"] as List<dynamic>).map((e) => Node.fromJson(e)).toList();
-    return DeleteOperation(path, nodes);
-  }
-
-  DeleteOperation(
-    Path path,
-    this.nodes,
-  ) : super(path);
-
-  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, nodes);
-  }
-
-  @override
-  Map<String, dynamic> toJson() {
-    return {
-      "op": "delete",
-      "path": path.toList(),
-      "nodes": nodes.map((n) => n.toJson()),
-    };
-  }
-}
-
-class TextEditOperation extends Operation {
-  final Delta delta;
-  final Delta inverted;
-
-  factory TextEditOperation.fromJson(Map<String, dynamic> map) {
-    final path = map["path"] as List<int>;
-    final delta = Delta.fromJson(map["delta"]);
-    final invert = Delta.fromJson(map["invert"]);
-    return TextEditOperation(path, delta, invert);
-  }
-
-  TextEditOperation(
-    Path path,
-    this.delta,
-    this.inverted,
-  ) : super(path);
-
-  TextEditOperation copyWith({Path? path, Delta? delta, Delta? inverted}) =>
-      TextEditOperation(
-          path ?? this.path, delta ?? this.delta, inverted ?? this.inverted);
-
-  @override
-  Operation copyWithPath(Path path) => copyWith(path: path);
-
-  @override
-  Operation invert() {
-    return TextEditOperation(path, inverted, delta);
-  }
-
-  @override
-  Map<String, dynamic> toJson() {
-    return {
-      "op": "text-edit",
-      "path": path.toList(),
-      "delta": delta.toJson(),
-      "invert": inverted.toJson(),
-    };
-  }
-}
-
-Path transformPath(Path preInsertPath, Path b, [int delta = 1]) {
-  if (preInsertPath.length > b.length) {
-    return b;
-  }
-  if (preInsertPath.isEmpty || b.isEmpty) {
-    return b;
-  }
-  // check the prefix
-  for (var i = 0; i < preInsertPath.length - 1; i++) {
-    if (preInsertPath[i] != b[i]) {
-      return b;
-    }
-  }
-  final prefix = preInsertPath.sublist(0, preInsertPath.length - 1);
-  final suffix = b.sublist(preInsertPath.length);
-  final preInsertLast = preInsertPath.last;
-  final bAtIndex = b[preInsertPath.length - 1];
-  if (preInsertLast <= bAtIndex) {
-    prefix.add(bAtIndex + delta);
-  } else {
-    prefix.add(bAtIndex);
-  }
-  prefix.addAll(suffix);
-  return prefix;
-}
-
-Operation transformOperation(Operation a, Operation b) {
-  if (a is InsertOperation) {
-    final newPath = transformPath(a.path, b.path, a.nodes.length);
-    return b.copyWithPath(newPath);
-  } else if (a is DeleteOperation) {
-    final newPath = transformPath(a.path, b.path, -1 * a.nodes.length);
-    return b.copyWithPath(newPath);
-  }
-  // TODO: transform update and textedit
-  return b;
-}

+ 1 - 1
frontend/app_flowy/packages/appflowy_editor/lib/src/operation/transaction.dart

@@ -1,7 +1,7 @@
 import 'dart:collection';
 import 'package:flutter/material.dart';
 import 'package:appflowy_editor/src/core/location/selection.dart';
-import './operation.dart';
+import '../core/transform/operation.dart';
 
 /// A [Transaction] has a list of [Operation] objects that will be applied
 /// to the editor. It is an immutable class and used to store and transmit.

+ 6 - 6
frontend/app_flowy/packages/appflowy_editor/lib/src/operation/transaction_builder.dart

@@ -8,7 +8,7 @@ import 'package:appflowy_editor/src/core/location/position.dart';
 import 'package:appflowy_editor/src/core/location/selection.dart';
 import 'package:appflowy_editor/src/core/document/text_delta.dart';
 import 'package:appflowy_editor/src/editor_state.dart';
-import 'package:appflowy_editor/src/operation/operation.dart';
+import 'package:appflowy_editor/src/core/transform/operation.dart';
 import 'package:appflowy_editor/src/operation/transaction.dart';
 
 /// A [TransactionBuilder] is used to build the transaction from the state.
@@ -85,7 +85,7 @@ class TransactionBuilder {
 
     final inverted = delta.invert(node.delta);
 
-    add(TextEditOperation(path, delta, inverted));
+    add(UpdateTextOperation(path, delta, inverted));
   }
 
   setAfterSelection(Selection sel) {
@@ -195,10 +195,10 @@ class TransactionBuilder {
   add(Operation op, {bool transform = true}) {
     final Operation? last = operations.isEmpty ? null : operations.last;
     if (last != null) {
-      if (op is TextEditOperation &&
-          last is TextEditOperation &&
+      if (op is UpdateTextOperation &&
+          last is UpdateTextOperation &&
           op.path.equals(last.path)) {
-        final newOp = TextEditOperation(
+        final newOp = UpdateTextOperation(
           op.path,
           last.delta.compose(op.delta),
           op.inverted.compose(last.inverted),
@@ -212,7 +212,7 @@ class TransactionBuilder {
         op = transformOperation(operations[i], op);
       }
     }
-    if (op is TextEditOperation && op.delta.isEmpty) {
+    if (op is UpdateTextOperation && op.delta.isEmpty) {
       return;
     }
     operations.add(op);

+ 1 - 1
frontend/app_flowy/packages/appflowy_editor/lib/src/undo_manager.dart

@@ -2,7 +2,7 @@ import 'dart:collection';
 
 import 'package:appflowy_editor/src/core/location/selection.dart';
 import 'package:appflowy_editor/src/infra/log.dart';
-import 'package:appflowy_editor/src/operation/operation.dart';
+import 'package:appflowy_editor/src/core/transform/operation.dart';
 import 'package:appflowy_editor/src/operation/transaction_builder.dart';
 import 'package:appflowy_editor/src/operation/transaction.dart';
 import 'package:appflowy_editor/src/editor_state.dart';

+ 1 - 1
frontend/app_flowy/packages/appflowy_editor/test/legacy/operation_test.dart

@@ -2,7 +2,7 @@ import 'dart:collection';
 
 import 'package:appflowy_editor/src/core/document/node.dart';
 import 'package:flutter_test/flutter_test.dart';
-import 'package:appflowy_editor/src/operation/operation.dart';
+import 'package:appflowy_editor/src/core/transform/operation.dart';
 import 'package:appflowy_editor/src/operation/transaction_builder.dart';
 import 'package:appflowy_editor/src/editor_state.dart';
 import 'package:appflowy_editor/src/core/document/document.dart';

+ 1 - 1
frontend/app_flowy/packages/appflowy_editor/test/legacy/undo_manager_test.dart

@@ -63,7 +63,7 @@ bool isInsertAndPathEqual(Operation operation, Path path, [String? content]) {
     return false;
   }
 
-  final firstNode = operation.nodes[0];
+  final firstNode = operation.nodes.first;
   if (firstNode is! TextNode) {
     return false;
   }