瀏覽代碼

feat: delta to string

Vincent Chan 2 年之前
父節點
當前提交
f910b9dc61

+ 5 - 0
frontend/app_flowy/packages/flowy_editor/assets/document.json

@@ -4,18 +4,21 @@
     "children": [
       {
         "type": "text",
+        "delta": [],
         "attributes": {
           "subtype": "with-heading"
         }
       },
       {
         "type": "text", 
+        "delta": [],
         "attributes": {
           "tag": "*"
         },
         "children": [
           {
             "type": "text",
+            "delta": [],
             "attributes": {
               "text-type": "heading2",
               "check": true
@@ -23,6 +26,7 @@
           },
           {
             "type": "text",
+            "delta": [],
             "attributes": {
               "text-type": "checkbox",
               "check": true
@@ -30,6 +34,7 @@
           },
           {
             "type": "text",
+            "delta": [],
             "attributes": {
               "tag": "**"
             }

+ 7 - 0
frontend/app_flowy/packages/flowy_editor/lib/document/node.dart

@@ -176,4 +176,11 @@ class TextNode extends Node {
     _delta = v;
     notifyListeners();
   }
+
+  @override
+  Map<String, Object> toJson() {
+    final map = super.toJson();
+    map['delta'] = _delta.toJson();
+    return map;
+  }
 }

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

@@ -40,7 +40,7 @@ class StateTree {
     if (path.isEmpty) {
       return false;
     }
-    var node = root.childAtPath(path);
+    final node = root.childAtPath(path);
     if (node == null || node is! TextNode) {
       return false;
     }

+ 45 - 18
frontend/app_flowy/packages/flowy_editor/lib/document/text_delta.dart

@@ -1,6 +1,7 @@
 import 'dart:collection';
 import 'dart:math';
 
+import 'package:flowy_editor/document/attributes.dart';
 import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
 import './attributes.dart';
@@ -8,7 +9,7 @@ import './attributes.dart';
 // constant number: 2^53 - 1
 const int _maxInt = 9007199254740991;
 
-class TextOperation {
+abstract class TextOperation {
   bool get isEmpty {
     return length == 0;
   }
@@ -20,6 +21,8 @@ class TextOperation {
   Attributes? get attributes {
     return null;
   }
+
+  Map<String, dynamic> toJson();
 }
 
 class TextInsert extends TextOperation {
@@ -54,6 +57,18 @@ class TextInsert extends TextOperation {
     return Object.hash(
         contentHash, attrs == null ? null : hashAttributes(attrs));
   }
+
+  @override
+  Map<String, dynamic> toJson() {
+    final result = <String, dynamic>{
+      'insert': content,
+    };
+    final attrs = _attributes;
+    if (attrs != null) {
+      result['attributes'] = {...attrs};
+    }
+    return result;
+  }
 }
 
 class TextRetain extends TextOperation {
@@ -96,6 +111,18 @@ class TextRetain extends TextOperation {
     final attrs = _attributes;
     return Object.hash(_length, attrs == null ? null : hashAttributes(attrs));
   }
+
+  @override
+  Map<String, dynamic> toJson() {
+    final result = <String, dynamic>{
+      'retain': _length,
+    };
+    final attrs = _attributes;
+    if (attrs != null) {
+      result['attributes'] = {...attrs};
+    }
+    return result;
+  }
 }
 
 class TextDelete extends TextOperation {
@@ -129,6 +156,13 @@ class TextDelete extends TextOperation {
   int get hashCode {
     return _length.hashCode;
   }
+
+  @override
+  Map<String, dynamic> toJson() {
+    return {
+      'delete': _length,
+    };
+  }
 }
 
 class _OpIterator {
@@ -215,28 +249,17 @@ class _OpIterator {
   }
 }
 
-Attributes? _attributesFromJSON(Map<String, dynamic>? json) {
-  if (json == null) {
-    return null;
-  }
-  final result = <String, dynamic>{};
-
-  for (final entry in json.entries) {
-    result[entry.key] = entry.value;
-  }
-
-  return result;
-}
-
 TextOperation? _textOperationFromJson(Map<String, dynamic> json) {
   TextOperation? result;
 
   if (json['insert'] is String) {
-    result = TextInsert(json['insert'] as String,
-        _attributesFromJSON(json['attributes'] as Map<String, dynamic>?));
+    final attrs = json['attributes'] as Map<String, dynamic>?;
+    result =
+        TextInsert(json['insert'] as String, attrs == null ? null : {...attrs});
   } else if (json['retain'] is int) {
-    result = TextRetain(json['retain'] as int,
-        _attributesFromJSON(json['attributes'] as Map<String, Object>?));
+    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);
   }
@@ -459,4 +482,8 @@ class Delta {
     });
     return inverted.chop();
   }
+
+  List<dynamic> toJson() {
+    return operations.map((e) => e.toJson()).toList();
+  }
 }

+ 33 - 0
frontend/app_flowy/packages/flowy_editor/test/delta_test.dart

@@ -197,4 +197,37 @@ void main() {
     //   expect(base.compose(delta).compose(inverted), 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);
+    });
+  });
 }