| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219 | import 'package:flowy_editor/document/attributes.dart';import 'package:flowy_editor/flowy_editor.dart';abstract class Operation {  factory Operation.fromJson(Map<String, dynamic> map) {    String t = map["type"] as String;    if (t == "insert-operation") {      return InsertOperation.fromJson(map);    } else if (t == "update-operation") {      return UpdateOperation.fromJson(map);    } else if (t == "delete-operation") {      return DeleteOperation.fromJson(map);    } else if (t == "text-edit-operation") {      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 {      "type": "insert-operation",      "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 {      "type": "update-operation",      "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 {      "type": "delete-operation",      "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 {      "type": "text-edit-operation",      "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);    return b.copyWithPath(newPath);  } else if (a is DeleteOperation) {    final newPath = transformPath(a.path, b.path, -1);    return b.copyWithPath(newPath);  }  // TODO: transform update and textedit  return b;}
 |