Browse Source

refactor: text-delta

Vincent Chan 2 years ago
parent
commit
737e374a54

+ 1 - 3
frontend/app_flowy/packages/flowy_editor/lib/src/document/node.dart

@@ -219,7 +219,5 @@ class TextNode extends Node {
         delta: delta ?? this.delta,
       );
 
-  // TODO: It's unneccesry to compute everytime.
-  String toRawString() =>
-      _delta.operations.whereType<TextInsert>().map((op) => op.content).join();
+  String toRawString() => _delta.toRawString();
 }

+ 34 - 27
frontend/app_flowy/packages/flowy_editor/lib/src/document/text_delta.dart

@@ -257,8 +257,8 @@ TextOperation? _textOperationFromJson(Map<String, dynamic> json) {
 }
 
 // basically copy from: https://github.com/quilljs/delta
-class Delta {
-  final List<TextOperation> operations;
+class Delta extends Iterable<TextOperation> {
+  final List<TextOperation> _operations;
 
   factory Delta.fromJson(List<dynamic> list) {
     final operations = <TextOperation>[];
@@ -273,9 +273,9 @@ class Delta {
     return Delta(operations);
   }
 
-  Delta([List<TextOperation>? ops]) : operations = ops ?? <TextOperation>[];
+  Delta([List<TextOperation>? ops]) : _operations = ops ?? <TextOperation>[];
 
-  Delta addAll(List<TextOperation> textOps) {
+  Delta addAll(Iterable<TextOperation> textOps) {
     textOps.forEach(add);
     return this;
   }
@@ -285,8 +285,8 @@ class Delta {
       return this;
     }
 
-    if (operations.isNotEmpty) {
-      final lastOp = operations.last;
+    if (_operations.isNotEmpty) {
+      final lastOp = _operations.last;
       if (lastOp is TextDelete && textOp is TextDelete) {
         lastOp.length += textOp.length;
         return this;
@@ -299,9 +299,9 @@ class Delta {
         // if there is an delete before the insert
         // swap the order
         if (lastOp is TextDelete && textOp is TextInsert) {
-          operations.removeLast();
-          operations.add(textOp);
-          operations.add(lastOp);
+          _operations.removeLast();
+          _operations.add(textOp);
+          _operations.add(lastOp);
           return this;
         }
         if (lastOp is TextRetain && textOp is TextRetain) {
@@ -311,13 +311,13 @@ class Delta {
       }
     }
 
-    operations.add(textOp);
+    _operations.add(textOp);
     return this;
   }
 
   Delta slice(int start, [int? end]) {
     final result = Delta();
-    final iterator = _OpIterator(operations);
+    final iterator = _OpIterator(_operations);
     int index = 0;
 
     while ((end == null || index < end) && iterator.hasNext) {
@@ -351,13 +351,13 @@ class Delta {
   }
 
   int get length {
-    return operations.fold(
+    return _operations.fold(
         0, (previousValue, element) => previousValue + element.length);
   }
 
   Delta compose(Delta other) {
-    final thisIter = _OpIterator(operations);
-    final otherIter = _OpIterator(other.operations);
+    final thisIter = _OpIterator(_operations);
+    final otherIter = _OpIterator(other._operations);
     final ops = <TextOperation>[];
 
     final firstOther = otherIter.peek();
@@ -405,7 +405,7 @@ class Delta {
 
           // Optimization if rest of other is just retain
           if (!otherIter.hasNext &&
-              delta.operations[delta.operations.length - 1] == newOp) {
+              delta._operations[delta._operations.length - 1] == newOp) {
             final rest = Delta(thisIter.rest());
             return delta.concat(rest).chop();
           }
@@ -419,21 +419,21 @@ class Delta {
   }
 
   Delta concat(Delta other) {
-    var ops = [...operations];
-    if (other.operations.isNotEmpty) {
-      ops.add(other.operations[0]);
-      ops.addAll(other.operations.sublist(1));
+    var ops = [..._operations];
+    if (other._operations.isNotEmpty) {
+      ops.add(other._operations[0]);
+      ops.addAll(other._operations.sublist(1));
     }
     return Delta(ops);
   }
 
   Delta chop() {
-    if (operations.isEmpty) {
+    if (_operations.isEmpty) {
       return this;
     }
-    final lastOp = operations.last;
+    final lastOp = _operations.last;
     if (lastOp is TextRetain && (lastOp.attributes?.length ?? 0) == 0) {
-      operations.removeLast();
+      _operations.removeLast();
     }
     return this;
   }
@@ -443,17 +443,17 @@ class Delta {
     if (other is! Delta) {
       return false;
     }
-    return listEquals(operations, other.operations);
+    return listEquals(_operations, other._operations);
   }
 
   @override
   int get hashCode {
-    return hashList(operations);
+    return hashList(_operations);
   }
 
   Delta invert(Delta base) {
     final inverted = Delta();
-    operations.fold(0, (int previousValue, op) {
+    _operations.fold(0, (int previousValue, op) {
       if (op is TextInsert) {
         inverted.delete(op.length);
       } else if (op is TextRetain && op.attributes == null) {
@@ -462,7 +462,7 @@ class Delta {
       } else if (op is TextDelete || op is TextRetain) {
         final length = op.length;
         final slice = base.slice(previousValue, previousValue + length);
-        for (final baseOp in slice.operations) {
+        for (final baseOp in slice._operations) {
           if (op is TextDelete) {
             inverted.add(baseOp);
           } else if (op is TextRetain && op.attributes != null) {
@@ -478,6 +478,13 @@ class Delta {
   }
 
   List<dynamic> toJson() {
-    return operations.map((e) => e.toJson()).toList();
+    return _operations.map((e) => e.toJson()).toList();
   }
+
+  // TODO: It's unneccesry to compute everytime.
+  String toRawString() =>
+      _operations.whereType<TextInsert>().map((op) => op.content).join();
+
+  @override
+  Iterator<TextOperation> get iterator => _operations.iterator;
 }

+ 1 - 1
frontend/app_flowy/packages/flowy_editor/lib/src/extensions/text_node_extensions.dart

@@ -19,7 +19,7 @@ extension TextNodeExtension on TextNode {
       allSatisfyInSelection(StyleKey.strikethrough, selection);
 
   bool allSatisfyInSelection(String styleKey, Selection selection) {
-    final ops = delta.operations.whereType<TextInsert>();
+    final ops = delta.whereType<TextInsert>();
     var start = 0;
     for (final op in ops) {
       if (start >= selection.end.offset) {

+ 3 - 3
frontend/app_flowy/packages/flowy_editor/lib/src/infra/html_converter.dart

@@ -71,7 +71,7 @@ class HTMLToNodesConverter {
         delta.insert(child.text ?? "");
       }
     }
-    if (delta.operations.isNotEmpty) {
+    if (delta.isNotEmpty) {
       result.add(TextNode(type: "text", delta: delta));
     }
     return result;
@@ -101,7 +101,7 @@ class HTMLToNodesConverter {
     } else {
       final delta = Delta();
       delta.insert(element.text);
-      if (delta.operations.isNotEmpty) {
+      if (delta.isNotEmpty) {
         return [TextNode(type: "text", delta: delta)];
       }
     }
@@ -446,7 +446,7 @@ class NodesToHTMLConverter {
       childNodes.add(node);
     }
 
-    for (final op in delta.operations) {
+    for (final op in delta) {
       if (op is TextInsert) {
         final attributes = op.attributes;
         if (attributes != null) {

+ 4 - 7
frontend/app_flowy/packages/flowy_editor/lib/src/operation/transaction_builder.dart

@@ -94,7 +94,7 @@ class TransactionBuilder {
       () => Delta()
         ..retain(firstOffset ?? firstLength)
         ..delete(firstLength - (firstOffset ?? firstLength))
-        ..addAll(secondNode.delta.slice(secondOffset, secondLength).operations),
+        ..addAll(secondNode.delta.slice(secondOffset, secondLength)),
     );
     afterSelection = Selection.collapsed(
       Position(
@@ -108,11 +108,8 @@ class TransactionBuilder {
       [Attributes? attributes]) {
     var newAttributes = attributes;
     if (index != 0 && attributes == null) {
-      newAttributes = node.delta
-          .slice(max(index - 1, 0), index)
-          .operations
-          .first
-          .attributes;
+      newAttributes =
+          node.delta.slice(max(index - 1, 0), index).first.attributes;
     }
     textEdit(
       node,
@@ -140,7 +137,7 @@ class TransactionBuilder {
       [Attributes? attributes]) {
     var newAttributes = attributes;
     if (attributes == null) {
-      final ops = node.delta.slice(index, index + length).operations;
+      final ops = node.delta.slice(index, index + length);
       if (ops.isNotEmpty) {
         newAttributes = ops.first.attributes;
       }

+ 1 - 1
frontend/app_flowy/packages/flowy_editor/lib/src/render/rich_text/flowy_rich_text.dart

@@ -198,7 +198,7 @@ class _FlowyRichTextState extends State<FlowyRichText> with Selectable {
   }
 
   TextSpan get _textSpan => TextSpan(
-        children: widget.textNode.delta.operations
+        children: widget.textNode.delta
             .whereType<TextInsert>()
             .map((insert) => RichTextStyle(
                   attributes: insert.attributes ?? {},

+ 2 - 3
frontend/app_flowy/packages/flowy_editor/lib/src/service/internal_key_event_handlers/copy_paste_handler.dart

@@ -175,8 +175,7 @@ _handlePastePlainText(EditorState editorState, String plainText) {
     final nodes = remains.map((e) {
       if (index++ == remains.length - 1) {
         return TextNode(
-            type: "text",
-            delta: Delta().insert(e).addAll(insertedLineSuffix.operations));
+            type: "text", delta: Delta().insert(e).addAll(insertedLineSuffix));
       }
       return TextNode(type: "text", delta: Delta().insert(e));
     }).toList();
@@ -246,7 +245,7 @@ _deleteSelectedContent(EditorState editorState) {
 
         if (endNode is TextNode) {
           final remain = endNode.delta.slice(selection.end.offset);
-          delta.addAll(remain.operations);
+          delta.addAll(remain);
         }
 
         return delta;

+ 1 - 1
frontend/app_flowy/packages/flowy_editor/test/delta_test.dart

@@ -19,7 +19,7 @@ void main() {
       }).delete(4);
 
       final restores = delta.compose(death);
-      expect(restores.operations, <TextOperation>[
+      expect(restores.toList(), <TextOperation>[
         TextInsert('Gandalf', {'bold': true}),
         TextInsert(' the '),
         TextInsert('White', {'color': '#fff'}),