operation.dart 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. import 'package:flowy_editor/document/attributes.dart';
  2. import 'package:flowy_editor/flowy_editor.dart';
  3. abstract class Operation {
  4. factory Operation.fromJson(Map<String, dynamic> map) {
  5. String t = map["type"] as String;
  6. if (t == "insert-operation") {
  7. return InsertOperation.fromJson(map);
  8. } else if (t == "update-operation") {
  9. return UpdateOperation.fromJson(map);
  10. } else if (t == "delete-operation") {
  11. return DeleteOperation.fromJson(map);
  12. } else if (t == "text-edit-operation") {
  13. return TextEditOperation.fromJson(map);
  14. }
  15. throw ArgumentError('unexpected type $t');
  16. }
  17. final Path path;
  18. Operation(this.path);
  19. Operation copyWithPath(Path path);
  20. Operation invert();
  21. Map<String, dynamic> toJson();
  22. }
  23. class InsertOperation extends Operation {
  24. final Node value;
  25. factory InsertOperation.fromJson(Map<String, dynamic> map) {
  26. final path = map["path"] as List<int>;
  27. final value = Node.fromJson(map["value"]);
  28. return InsertOperation(path, value);
  29. }
  30. InsertOperation(Path path, this.value) : super(path);
  31. InsertOperation copyWith({Path? path, Node? value}) =>
  32. InsertOperation(path ?? this.path, value ?? this.value);
  33. @override
  34. Operation copyWithPath(Path path) => copyWith(path: path);
  35. @override
  36. Operation invert() {
  37. return DeleteOperation(
  38. path,
  39. value,
  40. );
  41. }
  42. @override
  43. Map<String, dynamic> toJson() {
  44. return {
  45. "type": "insert-operation",
  46. "path": path.toList(),
  47. "value": value.toJson(),
  48. };
  49. }
  50. }
  51. class UpdateOperation extends Operation {
  52. final Attributes attributes;
  53. final Attributes oldAttributes;
  54. factory UpdateOperation.fromJson(Map<String, dynamic> map) {
  55. final path = map["path"] as List<int>;
  56. final attributes = map["attributes"] as Map<String, dynamic>;
  57. final oldAttributes = map["oldAttributes"] as Map<String, dynamic>;
  58. return UpdateOperation(path, attributes, oldAttributes);
  59. }
  60. UpdateOperation(
  61. Path path,
  62. this.attributes,
  63. this.oldAttributes,
  64. ) : super(path);
  65. UpdateOperation copyWith(
  66. {Path? path, Attributes? attributes, Attributes? oldAttributes}) =>
  67. UpdateOperation(path ?? this.path, attributes ?? this.attributes,
  68. oldAttributes ?? this.oldAttributes);
  69. @override
  70. Operation copyWithPath(Path path) => copyWith(path: path);
  71. @override
  72. Operation invert() {
  73. return UpdateOperation(
  74. path,
  75. oldAttributes,
  76. attributes,
  77. );
  78. }
  79. @override
  80. Map<String, dynamic> toJson() {
  81. return {
  82. "type": "update-operation",
  83. "path": path.toList(),
  84. "attributes": {...attributes},
  85. "oldAttributes": {...oldAttributes},
  86. };
  87. }
  88. }
  89. class DeleteOperation extends Operation {
  90. final Node removedValue;
  91. factory DeleteOperation.fromJson(Map<String, dynamic> map) {
  92. final path = map["path"] as List<int>;
  93. final removedValue = Node.fromJson(map["removedValue"]);
  94. return DeleteOperation(path, removedValue);
  95. }
  96. DeleteOperation(
  97. Path path,
  98. this.removedValue,
  99. ) : super(path);
  100. DeleteOperation copyWith({Path? path, Node? removedValue}) =>
  101. DeleteOperation(path ?? this.path, removedValue ?? this.removedValue);
  102. @override
  103. Operation copyWithPath(Path path) => copyWith(path: path);
  104. @override
  105. Operation invert() {
  106. return InsertOperation(path, removedValue);
  107. }
  108. @override
  109. Map<String, dynamic> toJson() {
  110. return {
  111. "type": "delete-operation",
  112. "path": path.toList(),
  113. "removedValue": removedValue.toJson(),
  114. };
  115. }
  116. }
  117. class TextEditOperation extends Operation {
  118. final Delta delta;
  119. final Delta inverted;
  120. factory TextEditOperation.fromJson(Map<String, dynamic> map) {
  121. final path = map["path"] as List<int>;
  122. final delta = Delta.fromJson(map["delta"]);
  123. final invert = Delta.fromJson(map["invert"]);
  124. return TextEditOperation(path, delta, invert);
  125. }
  126. TextEditOperation(
  127. Path path,
  128. this.delta,
  129. this.inverted,
  130. ) : super(path);
  131. TextEditOperation copyWith({Path? path, Delta? delta, Delta? inverted}) =>
  132. TextEditOperation(
  133. path ?? this.path, delta ?? this.delta, inverted ?? this.inverted);
  134. @override
  135. Operation copyWithPath(Path path) => copyWith(path: path);
  136. @override
  137. Operation invert() {
  138. return TextEditOperation(path, inverted, delta);
  139. }
  140. @override
  141. Map<String, dynamic> toJson() {
  142. return {
  143. "type": "text-edit-operation",
  144. "path": path.toList(),
  145. "delta": delta.toJson(),
  146. "invert": inverted.toJson(),
  147. };
  148. }
  149. }
  150. Path transformPath(Path preInsertPath, Path b, [int delta = 1]) {
  151. if (preInsertPath.length > b.length) {
  152. return b;
  153. }
  154. if (preInsertPath.isEmpty || b.isEmpty) {
  155. return b;
  156. }
  157. // check the prefix
  158. for (var i = 0; i < preInsertPath.length - 1; i++) {
  159. if (preInsertPath[i] != b[i]) {
  160. return b;
  161. }
  162. }
  163. final prefix = preInsertPath.sublist(0, preInsertPath.length - 1);
  164. final suffix = b.sublist(preInsertPath.length);
  165. final preInsertLast = preInsertPath.last;
  166. final bAtIndex = b[preInsertPath.length - 1];
  167. if (preInsertLast <= bAtIndex) {
  168. prefix.add(bAtIndex + delta);
  169. } else {
  170. prefix.add(bAtIndex);
  171. }
  172. prefix.addAll(suffix);
  173. return prefix;
  174. }
  175. Operation transformOperation(Operation a, Operation b) {
  176. if (a is InsertOperation) {
  177. final newPath = transformPath(a.path, b.path);
  178. return b.copyWithPath(newPath);
  179. } else if (b is DeleteOperation) {
  180. final newPath = transformPath(a.path, b.path, -1);
  181. return b.copyWithPath(newPath);
  182. }
  183. // TODO: transform update and textedit
  184. return b;
  185. }