operation.dart 4.7 KB

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