Browse Source

refactor: move text_delta to core/document

Lucas.Xu 2 years ago
parent
commit
e095fd4181

+ 1 - 1
frontend/app_flowy/packages/appflowy_editor/lib/appflowy_editor.dart

@@ -8,7 +8,7 @@ export 'src/core/document/path.dart';
 export 'src/document/position.dart';
 export 'src/document/selection.dart';
 export 'src/document/state_tree.dart';
-export 'src/document/text_delta.dart';
+export 'src/core/document/text_delta.dart';
 export 'src/core/document/attributes.dart';
 export 'src/document/built_in_attribute_keys.dart';
 export 'src/editor_state.dart';

+ 2 - 2
frontend/app_flowy/packages/appflowy_editor/lib/src/core/document/node.dart

@@ -5,7 +5,7 @@ import 'package:flutter/material.dart';
 import 'package:appflowy_editor/src/core/document/attributes.dart';
 import 'package:appflowy_editor/src/core/document/path.dart';
 import 'package:appflowy_editor/src/document/built_in_attribute_keys.dart';
-import 'package:appflowy_editor/src/document/text_delta.dart';
+import 'package:appflowy_editor/src/core/document/text_delta.dart';
 
 class Node extends ChangeNotifier with LinkedListEntry<Node> {
   Node({
@@ -232,7 +232,7 @@ class TextNode extends Node {
         );
 
   TextNode.empty({Attributes? attributes})
-      : _delta = Delta([TextInsert('')]),
+      : _delta = Delta(operations: [TextInsert('')]),
         super(
           type: 'text',
           attributes: attributes ?? {},

+ 219 - 248
frontend/app_flowy/packages/appflowy_editor/lib/src/document/text_delta.dart → frontend/app_flowy/packages/appflowy_editor/lib/src/core/document/text_delta.dart

@@ -1,262 +1,138 @@
 import 'dart:collection';
 import 'dart:math';
 
-import 'package:appflowy_editor/src/core/document/attributes.dart';
 import 'package:flutter/foundation.dart';
 
+import 'package:appflowy_editor/src/core/document/attributes.dart';
+
 // constant number: 2^53 - 1
 const int _maxInt = 9007199254740991;
 
-abstract class TextOperation {
-  bool get isEmpty => length == 0;
+List<int> stringIndexes(String text) {
+  final indexes = List<int>.filled(text.length, 0);
+  final iterator = text.runes.iterator;
 
+  while (iterator.moveNext()) {
+    for (var i = 0; i < iterator.currentSize; i++) {
+      indexes[iterator.rawIndex + i] = iterator.rawIndex;
+    }
+  }
+
+  return indexes;
+}
+
+abstract class TextOperation {
+  Attributes? get attributes;
   int get length;
 
-  Attributes? get attributes => null;
+  bool get isEmpty => length == 0;
 
   Map<String, dynamic> toJson();
 }
 
 class TextInsert extends TextOperation {
-  String content;
-  final Attributes? _attributes;
-
-  TextInsert(this.content, [Attributes? attrs]) : _attributes = attrs;
-
-  @override
-  int get length {
-    return content.length;
-  }
+  TextInsert(
+    this.text, {
+    Attributes? attributes,
+  }) : _attributes = attributes;
 
-  @override
-  Attributes? get attributes {
-    return _attributes;
-  }
+  String text;
+  final Attributes? _attributes;
 
   @override
-  bool operator ==(Object other) {
-    if (other is! TextInsert) {
-      return false;
-    }
-    return content == other.content &&
-        mapEquals(_attributes, other._attributes);
-  }
+  int get length => text.length;
 
   @override
-  int get hashCode {
-    final contentHash = content.hashCode;
-    final attrs = _attributes;
-    return Object.hash(
-      contentHash,
-      attrs != null ? hashAttributes(attrs) : null,
-    );
-  }
+  Attributes? get attributes => _attributes != null ? {..._attributes!} : null;
 
   @override
   Map<String, dynamic> toJson() {
     final result = <String, dynamic>{
-      'insert': content,
+      'insert': text,
     };
-    final attrs = _attributes;
-    if (attrs != null) {
-      result['attributes'] = {...attrs};
+    if (_attributes != null) {
+      result['attributes'] = attributes;
     }
     return result;
   }
-}
-
-class TextRetain extends TextOperation {
-  int _length;
-  final Attributes? _attributes;
-
-  TextRetain(length, [Attributes? attributes])
-      : _length = length,
-        _attributes = attributes;
 
   @override
-  bool get isEmpty {
-    return length == 0;
-  }
-
-  @override
-  int get length {
-    return _length;
-  }
+  bool operator ==(Object other) {
+    if (identical(this, other)) return true;
 
-  set length(int v) {
-    _length = v;
+    return other is TextInsert &&
+        other.text == text &&
+        mapEquals(_attributes, other._attributes);
   }
 
   @override
-  Attributes? get attributes {
-    return _attributes;
-  }
+  int get hashCode => text.hashCode ^ _attributes.hashCode;
+}
+
+class TextRetain extends TextOperation {
+  TextRetain(
+    this.length, {
+    Attributes? attributes,
+  }) : _attributes = attributes;
 
   @override
-  bool operator ==(Object other) {
-    if (other is! TextRetain) {
-      return false;
-    }
-    return _length == other.length && mapEquals(_attributes, other._attributes);
-  }
+  int length;
+  final Attributes? _attributes;
 
   @override
-  int get hashCode {
-    final attrs = _attributes;
-    return Object.hash(
-      _length,
-      attrs != null ? hashAttributes(attrs) : null,
-    );
-  }
+  Attributes? get attributes => _attributes != null ? {..._attributes!} : null;
 
   @override
   Map<String, dynamic> toJson() {
     final result = <String, dynamic>{
-      'retain': _length,
+      'retain': length,
     };
-    final attrs = _attributes;
-    if (attrs != null) {
-      result['attributes'] = {...attrs};
+    if (_attributes != null) {
+      result['attributes'] = attributes;
     }
     return result;
   }
-}
 
-class TextDelete extends TextOperation {
-  int _length;
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other)) return true;
 
-  TextDelete(int length) : _length = length;
+    return other is TextRetain &&
+        other.length == length &&
+        mapEquals(_attributes, other._attributes);
+  }
 
   @override
-  int get length {
-    return _length;
-  }
+  int get hashCode => length.hashCode ^ _attributes.hashCode;
+}
 
-  set length(int v) {
-    _length = v;
-  }
+class TextDelete extends TextOperation {
+  TextDelete({
+    required this.length,
+  });
 
   @override
-  bool operator ==(Object other) {
-    if (other is! TextDelete) {
-      return false;
-    }
-    return _length == other.length;
-  }
+  int length;
 
   @override
-  int get hashCode {
-    return _length.hashCode;
-  }
+  Attributes? get attributes => null;
 
   @override
   Map<String, dynamic> toJson() {
     return {
-      'delete': _length,
+      'delete': length,
     };
   }
-}
-
-class _OpIterator {
-  final UnmodifiableListView<TextOperation> _operations;
-  int _index = 0;
-  int _offset = 0;
-
-  _OpIterator(List<TextOperation> operations)
-      : _operations = UnmodifiableListView(operations);
-
-  bool get hasNext {
-    return peekLength() < _maxInt;
-  }
-
-  TextOperation? peek() {
-    if (_index >= _operations.length) {
-      return null;
-    }
-
-    return _operations[_index];
-  }
-
-  int peekLength() {
-    if (_index < _operations.length) {
-      final op = _operations[_index];
-      return op.length - _offset;
-    }
-    return _maxInt;
-  }
-
-  TextOperation _next([int? length]) {
-    length ??= _maxInt;
-
-    if (_index >= _operations.length) {
-      return TextRetain(_maxInt);
-    }
-
-    final nextOp = _operations[_index];
 
-    final offset = _offset;
-    final opLength = nextOp.length;
-    if (length >= opLength - offset) {
-      length = opLength - offset;
-      _index += 1;
-      _offset = 0;
-    } else {
-      _offset += length;
-    }
-    if (nextOp is TextDelete) {
-      return TextDelete(length);
-    }
-
-    if (nextOp is TextRetain) {
-      return TextRetain(
-        length,
-        nextOp.attributes,
-      );
-    }
-
-    if (nextOp is TextInsert) {
-      return TextInsert(
-        nextOp.content.substring(offset, offset + length),
-        nextOp.attributes,
-      );
-    }
-
-    return TextRetain(_maxInt);
-  }
-
-  List<TextOperation> rest() {
-    if (!hasNext) {
-      return [];
-    } else if (_offset == 0) {
-      return _operations.sublist(_index);
-    } else {
-      final offset = _offset;
-      final index = _index;
-      final next = _next();
-      final rest = _operations.sublist(_index);
-      _offset = offset;
-      _index = index;
-      return [next] + rest;
-    }
-  }
-}
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other)) return true;
 
-TextOperation? _textOperationFromJson(Map<String, dynamic> json) {
-  TextOperation? result;
-
-  if (json['insert'] is String) {
-    final attrs = json['attributes'] as Map<String, dynamic>?;
-    result =
-        TextInsert(json['insert'] as String, attrs == null ? null : {...attrs});
-  } else if (json['retain'] is int) {
-    final attrs = json['attributes'] as Map<String, dynamic>?;
-    result =
-        TextRetain(json['retain'] as int, attrs == null ? null : {...attrs});
-  } else if (json['delete'] is int) {
-    result = TextDelete(json['delete'] as int);
+    return other is TextDelete && other.length == length;
   }
 
-  return result;
+  @override
+  int get hashCode => length.hashCode;
 }
 
 /// Deltas are a simple, yet expressive format that can be used to describe contents and changes.
@@ -266,62 +142,66 @@ TextOperation? _textOperationFromJson(Map<String, dynamic> json) {
 
 /// Basically borrowed from: https://github.com/quilljs/delta
 class Delta extends Iterable<TextOperation> {
-  final List<TextOperation> _operations;
-  String? _rawString;
-  List<int>? _runeIndexes;
+  Delta({
+    List<TextOperation>? operations,
+  }) : _operations = operations ?? <TextOperation>[];
 
   factory Delta.fromJson(List<dynamic> list) {
     final operations = <TextOperation>[];
 
-    for (final obj in list) {
-      final op = _textOperationFromJson(obj as Map<String, dynamic>);
-      if (op != null) {
-        operations.add(op);
+    for (final value in list) {
+      if (value is Map<String, dynamic>) {
+        final op = _textOperationFromJson(value);
+        if (op != null) {
+          operations.add(op);
+        }
       }
     }
 
-    return Delta(operations);
+    return Delta(operations: operations);
   }
 
-  Delta([List<TextOperation>? ops]) : _operations = ops ?? <TextOperation>[];
+  final List<TextOperation> _operations;
+  String? _plainText;
+  List<int>? _runeIndexes;
 
-  void addAll(Iterable<TextOperation> textOps) {
-    textOps.forEach(add);
+  void addAll(Iterable<TextOperation> textOperations) {
+    textOperations.forEach(add);
   }
 
-  void add(TextOperation textOp) {
-    if (textOp.isEmpty) {
+  void add(TextOperation textOperation) {
+    if (textOperation.isEmpty) {
       return;
     }
-    _rawString = null;
+    _plainText = null;
 
     if (_operations.isNotEmpty) {
       final lastOp = _operations.last;
-      if (lastOp is TextDelete && textOp is TextDelete) {
-        lastOp.length += textOp.length;
+      if (lastOp is TextDelete && textOperation is TextDelete) {
+        lastOp.length += textOperation.length;
         return;
       }
-      if (mapEquals(lastOp.attributes, textOp.attributes)) {
-        if (lastOp is TextInsert && textOp is TextInsert) {
-          lastOp.content += textOp.content;
+      if (mapEquals(lastOp.attributes, textOperation.attributes)) {
+        if (lastOp is TextInsert && textOperation is TextInsert) {
+          lastOp.text += textOperation.text;
           return;
         }
         // if there is an delete before the insert
         // swap the order
-        if (lastOp is TextDelete && textOp is TextInsert) {
+        if (lastOp is TextDelete && textOperation is TextInsert) {
           _operations.removeLast();
-          _operations.add(textOp);
+          _operations.add(textOperation);
           _operations.add(lastOp);
           return;
         }
-        if (lastOp is TextRetain && textOp is TextRetain) {
-          lastOp.length += textOp.length;
+        if (lastOp is TextRetain && textOperation is TextRetain) {
+          lastOp.length += textOperation.length;
           return;
         }
       }
     }
 
-    _operations.add(textOp);
+    _operations.add(textOperation);
   }
 
   /// The slice() method does not change the original string.
@@ -349,19 +229,19 @@ class Delta extends Iterable<TextOperation> {
 
   /// Insert operations have an `insert` key defined.
   /// A String value represents inserting text.
-  void insert(String content, [Attributes? attributes]) =>
-      add(TextInsert(content, attributes));
+  void insert(String text, {Attributes? attributes}) =>
+      add(TextInsert(text, attributes: attributes));
 
   /// Retain operations have a Number `retain` key defined representing the number of characters to keep (other libraries might use the name keep or skip).
   /// An optional `attributes` key can be defined with an Object to describe formatting changes to the character range.
   /// A value of `null` in the `attributes` Object represents removal of that key.
   ///
   /// *Note: It is not necessary to retain the last characters of a document as this is implied.*
-  void retain(int length, [Attributes? attributes]) =>
-      add(TextRetain(length, attributes));
+  void retain(int length, {Attributes? attributes}) =>
+      add(TextRetain(length, attributes: attributes));
 
   /// Delete operations have a Number `delete` key defined representing the number of characters to delete.
-  void delete(int length) => add(TextDelete(length));
+  void delete(int length) => add(TextDelete(length: length));
 
   /// The length of the string fo the [Delta].
   @override
@@ -374,7 +254,7 @@ class Delta extends Iterable<TextOperation> {
   Delta compose(Delta other) {
     final thisIter = _OpIterator(_operations);
     final otherIter = _OpIterator(other._operations);
-    final ops = <TextOperation>[];
+    final operations = <TextOperation>[];
 
     final firstOther = otherIter.peek();
     if (firstOther != null &&
@@ -385,14 +265,14 @@ class Delta extends Iterable<TextOperation> {
           thisIter.peek() is TextInsert && thisIter.peekLength() <= firstLeft) {
         firstLeft -= thisIter.peekLength();
         final next = thisIter._next();
-        ops.add(next);
+        operations.add(next);
       }
       if (firstOther.length - firstLeft > 0) {
         otherIter._next(firstOther.length - firstLeft);
       }
     }
 
-    final delta = Delta(ops);
+    final delta = Delta(operations: operations);
     while (thisIter.hasNext || otherIter.hasNext) {
       if (otherIter.peek() is TextInsert) {
         final next = otherIter._next();
@@ -414,9 +294,9 @@ class Delta extends Iterable<TextOperation> {
         if (otherOp is TextRetain && otherOp.length > 0) {
           TextOperation? newOp;
           if (thisOp is TextRetain) {
-            newOp = TextRetain(length, attributes);
+            newOp = TextRetain(length, attributes: attributes);
           } else if (thisOp is TextInsert) {
-            newOp = TextInsert(thisOp.content, attributes);
+            newOp = TextInsert(thisOp.text, attributes: attributes);
           }
 
           if (newOp != null) {
@@ -427,7 +307,7 @@ class Delta extends Iterable<TextOperation> {
           if (!otherIter.hasNext &&
               delta._operations.isNotEmpty &&
               delta._operations.last == newOp) {
-            final rest = Delta(thisIter.rest());
+            final rest = Delta(operations: thisIter.rest());
             return (delta + rest)..chop();
           }
         } else if (otherOp is TextDelete && (thisOp is TextRetain)) {
@@ -441,19 +321,19 @@ class Delta extends Iterable<TextOperation> {
 
   /// This method joins two Delta together.
   Delta operator +(Delta other) {
-    var ops = [..._operations];
+    var operations = [..._operations];
     if (other._operations.isNotEmpty) {
-      ops.add(other._operations[0]);
-      ops.addAll(other._operations.sublist(1));
+      operations.add(other._operations[0]);
+      operations.addAll(other._operations.sublist(1));
     }
-    return Delta(ops);
+    return Delta(operations: operations);
   }
 
   void chop() {
     if (_operations.isEmpty) {
       return;
     }
-    _rawString = null;
+    _plainText = null;
     final lastOp = _operations.last;
     if (lastOp is TextRetain && (lastOp.attributes?.length ?? 0) == 0) {
       _operations.removeLast();
@@ -491,7 +371,7 @@ class Delta extends Iterable<TextOperation> {
           } else if (op is TextRetain && op.attributes != null) {
             inverted.retain(
               baseOp.length,
-              invertAttributes(baseOp.attributes, op.attributes),
+              attributes: invertAttributes(baseOp.attributes, op.attributes),
             );
           }
         }
@@ -517,9 +397,9 @@ class Delta extends Iterable<TextOperation> {
     if (pos == 0) {
       return pos - 1;
     }
-    _rawString ??=
-        _operations.whereType<TextInsert>().map((op) => op.content).join();
-    _runeIndexes ??= stringIndexes(_rawString!);
+    _plainText ??=
+        _operations.whereType<TextInsert>().map((op) => op.text).join();
+    _runeIndexes ??= stringIndexes(_plainText!);
     return _runeIndexes![pos - 1];
   }
 
@@ -535,7 +415,7 @@ class Delta extends Iterable<TextOperation> {
     if (pos >= stringContent.length - 1) {
       return stringContent.length;
     }
-    _runeIndexes ??= stringIndexes(_rawString!);
+    _runeIndexes ??= stringIndexes(_plainText!);
 
     for (var i = pos + 1; i < _runeIndexes!.length; i++) {
       if (_runeIndexes![i] != pos) {
@@ -547,24 +427,115 @@ class Delta extends Iterable<TextOperation> {
   }
 
   String toPlainText() {
-    _rawString ??=
-        _operations.whereType<TextInsert>().map((op) => op.content).join();
-    return _rawString!;
+    _plainText ??=
+        _operations.whereType<TextInsert>().map((op) => op.text).join();
+    return _plainText!;
   }
 
   @override
   Iterator<TextOperation> get iterator => _operations.iterator;
+
+  static TextOperation? _textOperationFromJson(Map<String, dynamic> json) {
+    TextOperation? operation;
+
+    if (json['insert'] is String) {
+      final attributes = json['attributes'] as Map<String, dynamic>?;
+      operation = TextInsert(
+        json['insert'] as String,
+        attributes: attributes != null ? {...attributes} : null,
+      );
+    } else if (json['retain'] is int) {
+      final attrs = json['attributes'] as Map<String, dynamic>?;
+      operation = TextRetain(
+        json['retain'] as int,
+        attributes: attrs != null ? {...attrs} : null,
+      );
+    } else if (json['delete'] is int) {
+      operation = TextDelete(length: json['delete'] as int);
+    }
+
+    return operation;
+  }
 }
 
-List<int> stringIndexes(String content) {
-  final indexes = List<int>.filled(content.length, 0);
-  final iterator = content.runes.iterator;
+class _OpIterator {
+  _OpIterator(
+    Iterable<TextOperation> operations,
+  ) : _operations = UnmodifiableListView(operations);
 
-  while (iterator.moveNext()) {
-    for (var i = 0; i < iterator.currentSize; i++) {
-      indexes[iterator.rawIndex + i] = iterator.rawIndex;
+  final UnmodifiableListView<TextOperation> _operations;
+  int _index = 0;
+  int _offset = 0;
+
+  bool get hasNext {
+    return peekLength() < _maxInt;
+  }
+
+  TextOperation? peek() {
+    if (_index >= _operations.length) {
+      return null;
     }
+
+    return _operations[_index];
   }
 
-  return indexes;
+  int peekLength() {
+    if (_index < _operations.length) {
+      final op = _operations[_index];
+      return op.length - _offset;
+    }
+    return _maxInt;
+  }
+
+  TextOperation _next([int? length]) {
+    length ??= _maxInt;
+
+    if (_index >= _operations.length) {
+      return TextRetain(_maxInt);
+    }
+
+    final nextOp = _operations[_index];
+
+    final offset = _offset;
+    final opLength = nextOp.length;
+    if (length >= opLength - offset) {
+      length = opLength - offset;
+      _index += 1;
+      _offset = 0;
+    } else {
+      _offset += length;
+    }
+    if (nextOp is TextDelete) {
+      return TextDelete(length: length);
+    }
+
+    if (nextOp is TextRetain) {
+      return TextRetain(length, attributes: nextOp.attributes);
+    }
+
+    if (nextOp is TextInsert) {
+      return TextInsert(
+        nextOp.text.substring(offset, offset + length),
+        attributes: nextOp.attributes,
+      );
+    }
+
+    return TextRetain(_maxInt);
+  }
+
+  List<TextOperation> rest() {
+    if (!hasNext) {
+      return [];
+    } else if (_offset == 0) {
+      return _operations.sublist(_index);
+    } else {
+      final offset = _offset;
+      final index = _index;
+      final next = _next();
+      final rest = _operations.sublist(_index);
+      _offset = offset;
+      _index = index;
+      return [next] + rest;
+    }
+  }
 }

+ 1 - 1
frontend/app_flowy/packages/appflowy_editor/lib/src/document/state_tree.dart

@@ -2,7 +2,7 @@ import 'dart:math';
 
 import 'package:appflowy_editor/src/core/document/node.dart';
 import 'package:appflowy_editor/src/core/document/path.dart';
-import 'package:appflowy_editor/src/document/text_delta.dart';
+import 'package:appflowy_editor/src/core/document/text_delta.dart';
 import '../core/document/attributes.dart';
 
 class StateTree {

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

@@ -2,7 +2,7 @@ import 'package:appflowy_editor/src/core/document/node.dart';
 import 'package:appflowy_editor/src/core/document/path.dart';
 import 'package:appflowy_editor/src/document/position.dart';
 import 'package:appflowy_editor/src/document/selection.dart';
-import 'package:appflowy_editor/src/document/text_delta.dart';
+import 'package:appflowy_editor/src/core/document/text_delta.dart';
 import 'package:appflowy_editor/src/document/built_in_attribute_keys.dart';
 
 extension TextNodeExtension on TextNode {

+ 19 - 14
frontend/app_flowy/packages/appflowy_editor/lib/src/infra/html_converter.dart

@@ -3,7 +3,7 @@ import 'dart:ui';
 
 import 'package:appflowy_editor/src/core/document/attributes.dart';
 import 'package:appflowy_editor/src/core/document/node.dart';
-import 'package:appflowy_editor/src/document/text_delta.dart';
+import 'package:appflowy_editor/src/core/document/text_delta.dart';
 import 'package:appflowy_editor/src/extensions/color_extension.dart';
 import 'package:flutter/material.dart';
 import 'package:html/parser.dart' show parse;
@@ -218,24 +218,29 @@ class HTMLToNodesConverter {
 
   _handleRichTextElement(Delta delta, html.Element element) {
     if (element.localName == HTMLTag.span) {
-      delta.insert(element.text,
-          _getDeltaAttributesFromHtmlAttributes(element.attributes));
+      delta.insert(
+        element.text,
+        attributes: _getDeltaAttributesFromHtmlAttributes(element.attributes),
+      );
     } else if (element.localName == HTMLTag.anchor) {
       final hyperLink = element.attributes["href"];
       Map<String, dynamic>? attributes;
       if (hyperLink != null) {
         attributes = {"href": hyperLink};
       }
-      delta.insert(element.text, attributes);
+      delta.insert(element.text, attributes: attributes);
     } else if (element.localName == HTMLTag.strong ||
         element.localName == HTMLTag.bold) {
-      delta.insert(element.text, {BuiltInAttributeKey.bold: true});
+      delta.insert(element.text, attributes: {BuiltInAttributeKey.bold: true});
     } else if (element.localName == HTMLTag.underline) {
-      delta.insert(element.text, {BuiltInAttributeKey.underline: true});
+      delta.insert(element.text,
+          attributes: {BuiltInAttributeKey.underline: true});
     } else if (element.localName == HTMLTag.italic) {
-      delta.insert(element.text, {BuiltInAttributeKey.italic: true});
+      delta
+          .insert(element.text, attributes: {BuiltInAttributeKey.italic: true});
     } else if (element.localName == HTMLTag.del) {
-      delta.insert(element.text, {BuiltInAttributeKey.strikethrough: true});
+      delta.insert(element.text,
+          attributes: {BuiltInAttributeKey.strikethrough: true});
     } else {
       delta.insert(element.text);
     }
@@ -535,22 +540,22 @@ class NodesToHTMLConverter {
           if (attributes.length == 1 &&
               attributes[BuiltInAttributeKey.bold] == true) {
             final strong = html.Element.tag(HTMLTag.strong);
-            strong.append(html.Text(op.content));
+            strong.append(html.Text(op.text));
             childNodes.add(strong);
           } else if (attributes.length == 1 &&
               attributes[BuiltInAttributeKey.underline] == true) {
             final strong = html.Element.tag(HTMLTag.underline);
-            strong.append(html.Text(op.content));
+            strong.append(html.Text(op.text));
             childNodes.add(strong);
           } else if (attributes.length == 1 &&
               attributes[BuiltInAttributeKey.italic] == true) {
             final strong = html.Element.tag(HTMLTag.italic);
-            strong.append(html.Text(op.content));
+            strong.append(html.Text(op.text));
             childNodes.add(strong);
           } else if (attributes.length == 1 &&
               attributes[BuiltInAttributeKey.strikethrough] == true) {
             final strong = html.Element.tag(HTMLTag.del);
-            strong.append(html.Text(op.content));
+            strong.append(html.Text(op.text));
             childNodes.add(strong);
           } else {
             final span = html.Element.tag(HTMLTag.span);
@@ -558,11 +563,11 @@ class NodesToHTMLConverter {
             if (cssString.isNotEmpty) {
               span.attributes["style"] = cssString;
             }
-            span.append(html.Text(op.content));
+            span.append(html.Text(op.text));
             childNodes.add(span);
           }
         } else {
-          childNodes.add(html.Text(op.content));
+          childNodes.add(html.Text(op.text));
         }
       }
     }

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

@@ -6,7 +6,7 @@ import 'package:appflowy_editor/src/core/document/node.dart';
 import 'package:appflowy_editor/src/core/document/path.dart';
 import 'package:appflowy_editor/src/document/position.dart';
 import 'package:appflowy_editor/src/document/selection.dart';
-import 'package:appflowy_editor/src/document/text_delta.dart';
+import 'package:appflowy_editor/src/core/document/text_delta.dart';
 import 'package:appflowy_editor/src/editor_state.dart';
 import 'package:appflowy_editor/src/operation/operation.dart';
 import 'package:appflowy_editor/src/operation/transaction.dart';
@@ -134,7 +134,7 @@ class TransactionBuilder {
         ..retain(index)
         ..insert(
           content,
-          newAttributes,
+          attributes: newAttributes,
         ),
     );
     afterSelection = Selection.collapsed(
@@ -148,7 +148,7 @@ class TransactionBuilder {
         node,
         () => Delta()
           ..retain(index)
-          ..retain(length, attributes));
+          ..retain(length, attributes: attributes));
     afterSelection = beforeSelection;
   }
 
@@ -177,7 +177,7 @@ class TransactionBuilder {
       () => Delta()
         ..retain(index)
         ..delete(length)
-        ..insert(content, newAttributes),
+        ..insert(content, attributes: newAttributes),
     );
     afterSelection = Selection.collapsed(
       Position(

+ 2 - 2
frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/flowy_rich_text.dart

@@ -9,7 +9,7 @@ import 'package:appflowy_editor/src/core/document/node.dart';
 import 'package:appflowy_editor/src/core/document/path.dart';
 import 'package:appflowy_editor/src/document/position.dart';
 import 'package:appflowy_editor/src/document/selection.dart';
-import 'package:appflowy_editor/src/document/text_delta.dart';
+import 'package:appflowy_editor/src/core/document/text_delta.dart';
 import 'package:appflowy_editor/src/editor_state.dart';
 import 'package:appflowy_editor/src/extensions/url_launcher_extension.dart';
 import 'package:appflowy_editor/src/extensions/text_style_extension.dart';
@@ -257,7 +257,7 @@ class _FlowyRichTextState extends State<FlowyRichText> with SelectableMixin {
       offset += textInsert.length;
       textSpans.add(
         TextSpan(
-          text: textInsert.content,
+          text: textInsert.text,
           style: textStyle,
           recognizer: recognizer,
         ),

+ 1 - 1
frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/copy_paste_handler.dart

@@ -222,7 +222,7 @@ Delta _lineContentToDelta(String lineContent) {
       delta.insert(lineContent.substring(lastUrlEndOffset, match.start));
     }
     final linkContent = lineContent.substring(match.start, match.end);
-    delta.insert(linkContent, {"href": linkContent});
+    delta.insert(linkContent, attributes: {"href": linkContent});
     lastUrlEndOffset = match.end;
   }
 

+ 332 - 0
frontend/app_flowy/packages/appflowy_editor/test/core/document/text_delta_test.dart

@@ -0,0 +1,332 @@
+import 'package:appflowy_editor/src/core/document/attributes.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:appflowy_editor/src/core/document/text_delta.dart';
+
+void main() {
+  group('text_delta.dart', () {
+    group('compose', () {
+      test('test delta', () {
+        final delta = Delta(operations: <TextOperation>[
+          TextInsert('Gandalf', attributes: {
+            'bold': true,
+          }),
+          TextInsert(' the '),
+          TextInsert('Grey', attributes: {
+            'color': '#ccc',
+          })
+        ]);
+
+        final death = Delta()
+          ..retain(12)
+          ..insert("White", attributes: {
+            'color': '#fff',
+          })
+          ..delete(4);
+
+        final restores = delta.compose(death);
+        expect(restores.toList(), <TextOperation>[
+          TextInsert('Gandalf', attributes: {'bold': true}),
+          TextInsert(' the '),
+          TextInsert('White', attributes: {'color': '#fff'}),
+        ]);
+      });
+      test('compose()', () {
+        final a = Delta()..insert('A');
+        final b = Delta()..insert('B');
+        final expected = Delta()
+          ..insert('B')
+          ..insert('A');
+        expect(a.compose(b), expected);
+      });
+      test('insert + retain', () {
+        final a = Delta()..insert('A');
+        final b = Delta()
+          ..retain(1, attributes: {
+            'bold': true,
+            'color': 'red',
+          });
+        final expected = Delta()
+          ..insert('A', attributes: {
+            'bold': true,
+            'color': 'red',
+          });
+        expect(a.compose(b), expected);
+      });
+      test('insert + delete', () {
+        final a = Delta()..insert('A');
+        final b = Delta()..delete(1);
+        final expected = Delta();
+        expect(a.compose(b), expected);
+      });
+      test('delete + insert', () {
+        final a = Delta()..delete(1);
+        final b = Delta()..insert('B');
+        final expected = Delta()
+          ..insert('B')
+          ..delete(1);
+        expect(a.compose(b), expected);
+      });
+      test('delete + retain', () {
+        final a = Delta()..delete(1);
+        final b = Delta()
+          ..retain(1, attributes: {
+            'bold': true,
+            'color': 'red',
+          });
+        final expected = Delta()
+          ..delete(1)
+          ..retain(1, attributes: {
+            'bold': true,
+            'color': 'red',
+          });
+        expect(a.compose(b), expected);
+      });
+      test('delete + delete', () {
+        final a = Delta()..delete(1);
+        final b = Delta()..delete(1);
+        final expected = Delta()..delete(2);
+        expect(a.compose(b), expected);
+      });
+      test('retain + insert', () {
+        final a = Delta()..retain(1, attributes: {'color': 'blue'});
+        final b = Delta()..insert('B');
+        final expected = Delta()
+          ..insert('B')
+          ..retain(1, attributes: {
+            'color': 'blue',
+          });
+        expect(a.compose(b), expected);
+      });
+      test('retain + retain', () {
+        final a = Delta()
+          ..retain(1, attributes: {
+            'color': 'blue',
+          });
+        final b = Delta()
+          ..retain(1, attributes: {
+            'bold': true,
+            'color': 'red',
+          });
+        final expected = Delta()
+          ..retain(1, attributes: {
+            'bold': true,
+            'color': 'red',
+          });
+        expect(a.compose(b), expected);
+      });
+      test('retain + delete', () {
+        final a = Delta()
+          ..retain(1, attributes: {
+            'color': 'blue',
+          });
+        final b = Delta()..delete(1);
+        final expected = Delta()..delete(1);
+        expect(a.compose(b), expected);
+      });
+      test('insert in middle of text', () {
+        final a = Delta()..insert('Hello');
+        final b = Delta()
+          ..retain(3)
+          ..insert('X');
+        final expected = Delta()..insert('HelXlo');
+        expect(a.compose(b), expected);
+      });
+      test('insert and delete ordering', () {
+        final a = Delta()..insert('Hello');
+        final b = Delta()..insert('Hello');
+        final insertFirst = Delta()
+          ..retain(3)
+          ..insert('X')
+          ..delete(1);
+        final deleteFirst = Delta()
+          ..retain(3)
+          ..delete(1)
+          ..insert('X');
+        final expected = Delta()..insert('HelXo');
+        expect(a.compose(insertFirst), expected);
+        expect(b.compose(deleteFirst), expected);
+      });
+      test('delete entire text', () {
+        final a = Delta()
+          ..retain(4)
+          ..insert('Hello');
+        final b = Delta()..delete(9);
+        final expected = Delta()..delete(4);
+        expect(a.compose(b), expected);
+      });
+      test('retain more than length of text', () {
+        final a = Delta()..insert('Hello');
+        final b = Delta()..retain(10);
+        final expected = Delta()..insert('Hello');
+        expect(a.compose(b), expected);
+      });
+      test('retain start optimization', () {
+        final a = Delta()
+          ..insert('A', attributes: {'bold': true})
+          ..insert('B')
+          ..insert('C', attributes: {'bold': true})
+          ..delete(1);
+        final b = Delta()
+          ..retain(3)
+          ..insert('D');
+        final expected = Delta()
+          ..insert('A', attributes: {'bold': true})
+          ..insert('B')
+          ..insert('C', attributes: {'bold': true})
+          ..insert('D')
+          ..delete(1);
+        expect(a.compose(b), expected);
+      });
+      test('retain end optimization', () {
+        final a = Delta()
+          ..insert('A', attributes: {'bold': true})
+          ..insert('B')
+          ..insert('C', attributes: {'bold': true});
+        final b = Delta()..delete(1);
+        final expected = Delta()
+          ..insert('B')
+          ..insert('C', attributes: {'bold': true});
+        expect(a.compose(b), expected);
+      });
+      test('retain end optimization join', () {
+        final a = Delta()
+          ..insert('A', attributes: {'bold': true})
+          ..insert('B')
+          ..insert('C', attributes: {'bold': true})
+          ..insert('D')
+          ..insert('E', attributes: {'bold': true})
+          ..insert('F');
+        final b = Delta()
+          ..retain(1)
+          ..delete(1);
+        final expected = Delta()
+          ..insert('AC', attributes: {'bold': true})
+          ..insert('D')
+          ..insert('E', attributes: {'bold': true})
+          ..insert('F');
+        expect(a.compose(b), expected);
+      });
+    });
+    group('invert', () {
+      test('insert', () {
+        final delta = Delta()
+          ..retain(2)
+          ..insert('A');
+        final base = Delta()..insert('12346');
+        final expected = Delta()
+          ..retain(2)
+          ..delete(1);
+        final inverted = delta.invert(base);
+        expect(expected, inverted);
+        expect(base.compose(delta).compose(inverted), base);
+      });
+      test('delete', () {
+        final delta = Delta()
+          ..retain(2)
+          ..delete(3);
+        final base = Delta()..insert('123456');
+        final expected = Delta()
+          ..retain(2)
+          ..insert('345');
+        final inverted = delta.invert(base);
+        expect(expected, inverted);
+        expect(base.compose(delta).compose(inverted), base);
+      });
+      test('retain', () {
+        final delta = Delta()
+          ..retain(2)
+          ..retain(3, attributes: {'bold': true});
+        final base = Delta()..insert('123456');
+        final expected = Delta()
+          ..retain(2)
+          ..retain(3, attributes: {'bold': null});
+        final inverted = delta.invert(base);
+        expect(expected, inverted);
+        final t = base.compose(delta).compose(inverted);
+        expect(t, base);
+      });
+    });
+    group('json', () {
+      test('toJson()', () {
+        final delta = Delta()
+          ..retain(2)
+          ..insert('A')
+          ..delete(3);
+        expect(delta.toJson(), [
+          {'retain': 2},
+          {'insert': 'A'},
+          {'delete': 3}
+        ]);
+      });
+      test('attributes', () {
+        final delta = Delta()
+          ..retain(2, attributes: {'bold': true})
+          ..insert('A', attributes: {'italic': true});
+        expect(delta.toJson(), [
+          {
+            'retain': 2,
+            'attributes': {'bold': true},
+          },
+          {
+            'insert': 'A',
+            'attributes': {'italic': true},
+          },
+        ]);
+      });
+      test('fromJson()', () {
+        final delta = Delta.fromJson([
+          {'retain': 2},
+          {'insert': 'A'},
+          {'delete': 3},
+        ]);
+        final expected = Delta()
+          ..retain(2)
+          ..insert('A')
+          ..delete(3);
+        expect(delta, expected);
+      });
+    });
+    group('runes', () {
+      test("stringIndexes", () {
+        final indexes = stringIndexes('😊');
+        expect(indexes[0], 0);
+        expect(indexes[1], 0);
+      });
+      test("next rune 1", () {
+        final delta = Delta()..insert('😊');
+        expect(delta.nextRunePosition(0), 2);
+      });
+      test("next rune 2", () {
+        final delta = Delta()..insert('😊a');
+        expect(delta.nextRunePosition(0), 2);
+      });
+      test("next rune 3", () {
+        final delta = Delta()..insert('😊陈');
+        expect(delta.nextRunePosition(2), 3);
+      });
+      test("prev rune 1", () {
+        final delta = Delta()..insert('😊陈');
+        expect(delta.prevRunePosition(2), 0);
+      });
+      test("prev rune 2", () {
+        final delta = Delta()..insert('😊');
+        expect(delta.prevRunePosition(2), 0);
+      });
+      test("prev rune 3", () {
+        final delta = Delta()..insert('😊');
+        expect(delta.prevRunePosition(0), -1);
+      });
+    });
+    group("attributes", () {
+      test("compose", () {
+        final attrs =
+            composeAttributes({'a': null}, {'b': null}, keepNull: true);
+        expect(attrs != null, true);
+        expect(attrs?.containsKey("a"), true);
+        expect(attrs?.containsKey("b"), true);
+        expect(attrs?["a"], null);
+        expect(attrs?["b"], null);
+      });
+    });
+  });
+}

+ 1 - 1
frontend/app_flowy/packages/appflowy_editor/test/infra/test_editor.dart

@@ -63,7 +63,7 @@ class EditorWidgetTester {
   void insertTextNode(String? text, {Attributes? attributes, Delta? delta}) {
     insert(
       TextNode(
-        delta: delta ?? Delta([TextInsert(text ?? 'Test')]),
+        delta: delta ?? Delta(operations: [TextInsert(text ?? 'Test')]),
         attributes: attributes,
       ),
     );

+ 0 - 329
frontend/app_flowy/packages/appflowy_editor/test/legacy/delta_test.dart

@@ -1,329 +0,0 @@
-import 'package:appflowy_editor/src/core/document/attributes.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:appflowy_editor/src/document/text_delta.dart';
-
-void main() {
-  group('compose', () {
-    test('test delta', () {
-      final delta = Delta(<TextOperation>[
-        TextInsert('Gandalf', {
-          'bold': true,
-        }),
-        TextInsert(' the '),
-        TextInsert('Grey', {
-          'color': '#ccc',
-        })
-      ]);
-
-      final death = Delta()
-        ..retain(12)
-        ..insert("White", {
-          'color': '#fff',
-        })
-        ..delete(4);
-
-      final restores = delta.compose(death);
-      expect(restores.toList(), <TextOperation>[
-        TextInsert('Gandalf', {'bold': true}),
-        TextInsert(' the '),
-        TextInsert('White', {'color': '#fff'}),
-      ]);
-    });
-    test('compose()', () {
-      final a = Delta()..insert('A');
-      final b = Delta()..insert('B');
-      final expected = Delta()
-        ..insert('B')
-        ..insert('A');
-      expect(a.compose(b), expected);
-    });
-    test('insert + retain', () {
-      final a = Delta()..insert('A');
-      final b = Delta()
-        ..retain(1, {
-          'bold': true,
-          'color': 'red',
-        });
-      final expected = Delta()
-        ..insert('A', {
-          'bold': true,
-          'color': 'red',
-        });
-      expect(a.compose(b), expected);
-    });
-    test('insert + delete', () {
-      final a = Delta()..insert('A');
-      final b = Delta()..delete(1);
-      final expected = Delta();
-      expect(a.compose(b), expected);
-    });
-    test('delete + insert', () {
-      final a = Delta()..delete(1);
-      final b = Delta()..insert('B');
-      final expected = Delta()
-        ..insert('B')
-        ..delete(1);
-      expect(a.compose(b), expected);
-    });
-    test('delete + retain', () {
-      final a = Delta()..delete(1);
-      final b = Delta()
-        ..retain(1, {
-          'bold': true,
-          'color': 'red',
-        });
-      final expected = Delta()
-        ..delete(1)
-        ..retain(1, {
-          'bold': true,
-          'color': 'red',
-        });
-      expect(a.compose(b), expected);
-    });
-    test('delete + delete', () {
-      final a = Delta()..delete(1);
-      final b = Delta()..delete(1);
-      final expected = Delta()..delete(2);
-      expect(a.compose(b), expected);
-    });
-    test('retain + insert', () {
-      final a = Delta()..retain(1, {'color': 'blue'});
-      final b = Delta()..insert('B');
-      final expected = Delta()
-        ..insert('B')
-        ..retain(1, {
-          'color': 'blue',
-        });
-      expect(a.compose(b), expected);
-    });
-    test('retain + retain', () {
-      final a = Delta()
-        ..retain(1, {
-          'color': 'blue',
-        });
-      final b = Delta()
-        ..retain(1, {
-          'bold': true,
-          'color': 'red',
-        });
-      final expected = Delta()
-        ..retain(1, {
-          'bold': true,
-          'color': 'red',
-        });
-      expect(a.compose(b), expected);
-    });
-    test('retain + delete', () {
-      final a = Delta()
-        ..retain(1, {
-          'color': 'blue',
-        });
-      final b = Delta()..delete(1);
-      final expected = Delta()..delete(1);
-      expect(a.compose(b), expected);
-    });
-    test('insert in middle of text', () {
-      final a = Delta()..insert('Hello');
-      final b = Delta()
-        ..retain(3)
-        ..insert('X');
-      final expected = Delta()..insert('HelXlo');
-      expect(a.compose(b), expected);
-    });
-    test('insert and delete ordering', () {
-      final a = Delta()..insert('Hello');
-      final b = Delta()..insert('Hello');
-      final insertFirst = Delta()
-        ..retain(3)
-        ..insert('X')
-        ..delete(1);
-      final deleteFirst = Delta()
-        ..retain(3)
-        ..delete(1)
-        ..insert('X');
-      final expected = Delta()..insert('HelXo');
-      expect(a.compose(insertFirst), expected);
-      expect(b.compose(deleteFirst), expected);
-    });
-    test('delete entire text', () {
-      final a = Delta()
-        ..retain(4)
-        ..insert('Hello');
-      final b = Delta()..delete(9);
-      final expected = Delta()..delete(4);
-      expect(a.compose(b), expected);
-    });
-    test('retain more than length of text', () {
-      final a = Delta()..insert('Hello');
-      final b = Delta()..retain(10);
-      final expected = Delta()..insert('Hello');
-      expect(a.compose(b), expected);
-    });
-    test('retain start optimization', () {
-      final a = Delta()
-        ..insert('A', {'bold': true})
-        ..insert('B')
-        ..insert('C', {'bold': true})
-        ..delete(1);
-      final b = Delta()
-        ..retain(3)
-        ..insert('D');
-      final expected = Delta()
-        ..insert('A', {'bold': true})
-        ..insert('B')
-        ..insert('C', {'bold': true})
-        ..insert('D')
-        ..delete(1);
-      expect(a.compose(b), expected);
-    });
-    test('retain end optimization', () {
-      final a = Delta()
-        ..insert('A', {'bold': true})
-        ..insert('B')
-        ..insert('C', {'bold': true});
-      final b = Delta()..delete(1);
-      final expected = Delta()
-        ..insert('B')
-        ..insert('C', {'bold': true});
-      expect(a.compose(b), expected);
-    });
-    test('retain end optimization join', () {
-      final a = Delta()
-        ..insert('A', {'bold': true})
-        ..insert('B')
-        ..insert('C', {'bold': true})
-        ..insert('D')
-        ..insert('E', {'bold': true})
-        ..insert('F');
-      final b = Delta()
-        ..retain(1)
-        ..delete(1);
-      final expected = Delta()
-        ..insert('AC', {'bold': true})
-        ..insert('D')
-        ..insert('E', {'bold': true})
-        ..insert('F');
-      expect(a.compose(b), expected);
-    });
-  });
-  group('invert', () {
-    test('insert', () {
-      final delta = Delta()
-        ..retain(2)
-        ..insert('A');
-      final base = Delta()..insert('12346');
-      final expected = Delta()
-        ..retain(2)
-        ..delete(1);
-      final inverted = delta.invert(base);
-      expect(expected, inverted);
-      expect(base.compose(delta).compose(inverted), base);
-    });
-    test('delete', () {
-      final delta = Delta()
-        ..retain(2)
-        ..delete(3);
-      final base = Delta()..insert('123456');
-      final expected = Delta()
-        ..retain(2)
-        ..insert('345');
-      final inverted = delta.invert(base);
-      expect(expected, inverted);
-      expect(base.compose(delta).compose(inverted), base);
-    });
-    test('retain', () {
-      final delta = Delta()
-        ..retain(2)
-        ..retain(3, {'bold': true});
-      final base = Delta()..insert('123456');
-      final expected = Delta()
-        ..retain(2)
-        ..retain(3, {'bold': null});
-      final inverted = delta.invert(base);
-      expect(expected, inverted);
-      final t = base.compose(delta).compose(inverted);
-      expect(t, base);
-    });
-  });
-  group('json', () {
-    test('toJson()', () {
-      final delta = Delta()
-        ..retain(2)
-        ..insert('A')
-        ..delete(3);
-      expect(delta.toJson(), [
-        {'retain': 2},
-        {'insert': 'A'},
-        {'delete': 3}
-      ]);
-    });
-    test('attributes', () {
-      final delta = Delta()
-        ..retain(2, {'bold': true})
-        ..insert('A', {'italic': true});
-      expect(delta.toJson(), [
-        {
-          'retain': 2,
-          'attributes': {'bold': true},
-        },
-        {
-          'insert': 'A',
-          'attributes': {'italic': true},
-        },
-      ]);
-    });
-    test('fromJson()', () {
-      final delta = Delta.fromJson([
-        {'retain': 2},
-        {'insert': 'A'},
-        {'delete': 3},
-      ]);
-      final expected = Delta()
-        ..retain(2)
-        ..insert('A')
-        ..delete(3);
-      expect(delta, expected);
-    });
-  });
-  group('runes', () {
-    test("stringIndexes", () {
-      final indexes = stringIndexes('😊');
-      expect(indexes[0], 0);
-      expect(indexes[1], 0);
-    });
-    test("next rune 1", () {
-      final delta = Delta()..insert('😊');
-      expect(delta.nextRunePosition(0), 2);
-    });
-    test("next rune 2", () {
-      final delta = Delta()..insert('😊a');
-      expect(delta.nextRunePosition(0), 2);
-    });
-    test("next rune 3", () {
-      final delta = Delta()..insert('😊陈');
-      expect(delta.nextRunePosition(2), 3);
-    });
-    test("prev rune 1", () {
-      final delta = Delta()..insert('😊陈');
-      expect(delta.prevRunePosition(2), 0);
-    });
-    test("prev rune 2", () {
-      final delta = Delta()..insert('😊');
-      expect(delta.prevRunePosition(2), 0);
-    });
-    test("prev rune 3", () {
-      final delta = Delta()..insert('😊');
-      expect(delta.prevRunePosition(0), -1);
-    });
-  });
-  group("attributes", () {
-    test("compose", () {
-      final attrs = composeAttributes({'a': null}, {'b': null}, keepNull: true);
-      expect(attrs != null, true);
-      expect(attrs?.containsKey("a"), true);
-      expect(attrs?.containsKey("b"), true);
-      expect(attrs?["a"], null);
-      expect(attrs?["b"], null);
-    });
-  });
-}

+ 2 - 2
frontend/app_flowy/packages/appflowy_editor/test/render/rich_text/checkbox_text_test.dart

@@ -26,8 +26,8 @@ void main() async {
             BuiltInAttributeKey.subtype: BuiltInAttributeKey.checkbox,
             BuiltInAttributeKey.checkbox: false,
           },
-          delta: Delta([
-            TextInsert(text, {
+          delta: Delta(operations: [
+            TextInsert(text, attributes: {
               BuiltInAttributeKey.bold: true,
               BuiltInAttributeKey.italic: true,
               BuiltInAttributeKey.underline: true,

+ 4 - 4
frontend/app_flowy/packages/appflowy_editor/test/service/toolbar_service_test.dart

@@ -63,9 +63,9 @@ void main() async {
         ..insertTextNode(text)
         ..insertTextNode(
           null,
-          delta: Delta([
+          delta: Delta(operations: [
             TextInsert(text),
-            TextInsert(text, attributes),
+            TextInsert(text, attributes: attributes),
             TextInsert(text),
           ]),
         );
@@ -171,8 +171,8 @@ void main() async {
             BuiltInAttributeKey.subtype: BuiltInAttributeKey.heading,
             BuiltInAttributeKey.heading: BuiltInAttributeKey.h1,
           },
-          delta: Delta([
-            TextInsert(text, {
+          delta: Delta(operations: [
+            TextInsert(text, attributes: {
               BuiltInAttributeKey.bold: true,
             })
           ]),