operation.dart 4.3 KB

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