From 2e2de297899a7332db607447fbd78a71323fce8c Mon Sep 17 00:00:00 2001 From: Vincent Chan Date: Mon, 1 Aug 2022 11:34:35 +0800 Subject: [PATCH 1/3] feat: transaction to json --- .../flowy_editor/lib/document/position.dart | 7 +++ .../flowy_editor/lib/document/selection.dart | 7 +++ .../flowy_editor/lib/operation/operation.dart | 39 ++++++++++++++++ .../lib/operation/transaction.dart | 13 ++++++ .../flowy_editor/test/operation_test.dart | 44 +++++++++++++++++++ 5 files changed, 110 insertions(+) diff --git a/frontend/app_flowy/packages/flowy_editor/lib/document/position.dart b/frontend/app_flowy/packages/flowy_editor/lib/document/position.dart index a60f04e89b..a87064d85a 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/document/position.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/document/position.dart @@ -34,4 +34,11 @@ class Position { @override String toString() => 'path = $path, offset = $offset'; + + Map toJson() { + return { + "path": path.toList(), + "offset": offset, + }; + } } diff --git a/frontend/app_flowy/packages/flowy_editor/lib/document/selection.dart b/frontend/app_flowy/packages/flowy_editor/lib/document/selection.dart index a3919a21f6..f1fa0682f6 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/document/selection.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/document/selection.dart @@ -48,4 +48,11 @@ class Selection { @override String toString() => '[Selection] start = $start, end = $end'; + + Map toJson() { + return { + "start": start.toJson(), + "end": end.toJson(), + }; + } } diff --git a/frontend/app_flowy/packages/flowy_editor/lib/operation/operation.dart b/frontend/app_flowy/packages/flowy_editor/lib/operation/operation.dart index eafa4a31da..e4d2c1d8ed 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/operation/operation.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/operation/operation.dart @@ -6,6 +6,7 @@ abstract class Operation { Operation({required this.path}); Operation copyWithPath(Path path); Operation invert(); + Map toJson(); } class InsertOperation extends Operation { @@ -29,6 +30,15 @@ class InsertOperation extends Operation { removedValue: value, ); } + + @override + Map toJson() { + return { + "type": "insert-operation", + "path": path.toList(), + "value": value.toJson(), + }; + } } class UpdateOperation extends Operation { @@ -59,6 +69,16 @@ class UpdateOperation extends Operation { oldAttributes: attributes, ); } + + @override + Map toJson() { + return { + "type": "update-operation", + "path": path.toList(), + "attributes": {...attributes}, + "oldAttributes": {...oldAttributes}, + }; + } } class DeleteOperation extends Operation { @@ -82,6 +102,15 @@ class DeleteOperation extends Operation { value: removedValue, ); } + + @override + Map toJson() { + return { + "type": "delete-operation", + "path": path.toList(), + "removedValue": removedValue.toJson(), + }; + } } class TextEditOperation extends Operation { @@ -107,6 +136,16 @@ class TextEditOperation extends Operation { Operation invert() { return TextEditOperation(path: path, delta: inverted, inverted: delta); } + + @override + Map toJson() { + return { + "type": "text-edit-operation", + "path": path.toList(), + "delta": delta.toJson(), + "invert": inverted.toJson(), + }; + } } Path transformPath(Path preInsertPath, Path b, [int delta = 1]) { diff --git a/frontend/app_flowy/packages/flowy_editor/lib/operation/transaction.dart b/frontend/app_flowy/packages/flowy_editor/lib/operation/transaction.dart index 85bc43f537..5dcf167628 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/operation/transaction.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/operation/transaction.dart @@ -23,4 +23,17 @@ class Transaction { this.beforeSelection, this.afterSelection, }); + + Map toJson() { + final Map result = { + "operations": operations.map((e) => e.toJson()), + }; + if (beforeSelection != null) { + result["beforeSelection"] = beforeSelection!.toJson(); + } + if (afterSelection != null) { + result["afterSelection"] = afterSelection!.toJson(); + } + return result; + } } diff --git a/frontend/app_flowy/packages/flowy_editor/test/operation_test.dart b/frontend/app_flowy/packages/flowy_editor/test/operation_test.dart index b0b6cec141..a027fa8027 100644 --- a/frontend/app_flowy/packages/flowy_editor/test/operation_test.dart +++ b/frontend/app_flowy/packages/flowy_editor/test/operation_test.dart @@ -78,4 +78,48 @@ void main() { expect(transaction.operations[1].path, [0]); expect(transaction.operations[2].path, [0]); }); + group("toJson", () { + test("insert", () { + final root = Node(type: "root", attributes: {}, children: LinkedList()); + final state = EditorState(document: StateTree(root: root)); + + final item1 = Node(type: "node", attributes: {}, children: LinkedList()); + final tb = TransactionBuilder(state); + tb.insertNode([0], item1); + + final transaction = tb.finish(); + expect(transaction.toJson(), { + "operations": [ + { + "type": "insert-operation", + "path": [0], + "value": item1.toJson(), + } + ], + }); + }); + test("delete", () { + final item1 = Node(type: "node", attributes: {}, children: LinkedList()); + final root = Node( + type: "root", + attributes: {}, + children: LinkedList() + ..addAll([ + item1, + ])); + final state = EditorState(document: StateTree(root: root)); + final tb = TransactionBuilder(state); + tb.deleteNode(item1); + final transaction = tb.finish(); + expect(transaction.toJson(), { + "operations": [ + { + "type": "delete-operation", + "path": [0], + "removedValue": item1.toJson(), + } + ], + }); + }); + }); } From 5e86b83eee8ae7395d2affde5692ef1b49b56bb6 Mon Sep 17 00:00:00 2001 From: Vincent Chan Date: Mon, 1 Aug 2022 12:41:51 +0800 Subject: [PATCH 2/3] feat: fromJson --- .../packages/flowy_editor/lib/operation/operation.dart | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/frontend/app_flowy/packages/flowy_editor/lib/operation/operation.dart b/frontend/app_flowy/packages/flowy_editor/lib/operation/operation.dart index e4d2c1d8ed..d3b3144873 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/operation/operation.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/operation/operation.dart @@ -2,6 +2,16 @@ import 'package:flowy_editor/document/attributes.dart'; import 'package:flowy_editor/flowy_editor.dart'; abstract class Operation { + factory Operation.fromJson(Map map) { + String t = map["type"] as String; + if (t == "insert-operation") { + final path = map["path"] as List; + final value = Node.fromJson(map["value"]); + return InsertOperation(path: path, value: value); + } + + throw ArgumentError('unexpected type $t'); + } final Path path; Operation({required this.path}); Operation copyWithPath(Path path); From 46dba122bde150812e608b50d91ffce5d342ff9d Mon Sep 17 00:00:00 2001 From: Vincent Chan Date: Mon, 1 Aug 2022 12:58:32 +0800 Subject: [PATCH 3/3] refactor: remove named parameters --- .../flowy_editor/lib/operation/operation.dart | 104 +++++++++++------- .../lib/operation/transaction_builder.dart | 19 ++-- .../flowy_editor/test/operation_test.dart | 24 ++-- 3 files changed, 79 insertions(+), 68 deletions(-) diff --git a/frontend/app_flowy/packages/flowy_editor/lib/operation/operation.dart b/frontend/app_flowy/packages/flowy_editor/lib/operation/operation.dart index d3b3144873..a505df4b1d 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/operation/operation.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/operation/operation.dart @@ -5,15 +5,19 @@ abstract class Operation { factory Operation.fromJson(Map map) { String t = map["type"] as String; if (t == "insert-operation") { - final path = map["path"] as List; - final value = Node.fromJson(map["value"]); - return InsertOperation(path: path, value: value); + return InsertOperation.fromJson(map); + } else if (t == "update-operation") { + return UpdateOperation.fromJson(map); + } else if (t == "delete-operation") { + return DeleteOperation.fromJson(map); + } else if (t == "text-edit-operation") { + return TextEditOperation.fromJson(map); } throw ArgumentError('unexpected type $t'); } final Path path; - Operation({required this.path}); + Operation(this.path); Operation copyWithPath(Path path); Operation invert(); Map toJson(); @@ -22,13 +26,16 @@ abstract class Operation { class InsertOperation extends Operation { final Node value; - InsertOperation({ - required super.path, - required this.value, - }); + factory InsertOperation.fromJson(Map map) { + final path = map["path"] as List; + final value = Node.fromJson(map["value"]); + return InsertOperation(path, value); + } + + InsertOperation(Path path, this.value) : super(path); InsertOperation copyWith({Path? path, Node? value}) => - InsertOperation(path: path ?? this.path, value: value ?? this.value); + InsertOperation(path ?? this.path, value ?? this.value); @override Operation copyWithPath(Path path) => copyWith(path: path); @@ -36,8 +43,8 @@ class InsertOperation extends Operation { @override Operation invert() { return DeleteOperation( - path: path, - removedValue: value, + path, + value, ); } @@ -55,18 +62,23 @@ class UpdateOperation extends Operation { final Attributes attributes; final Attributes oldAttributes; - UpdateOperation({ - required super.path, - required this.attributes, - required this.oldAttributes, - }); + factory UpdateOperation.fromJson(Map map) { + final path = map["path"] as List; + final attributes = map["attributes"] as Map; + final oldAttributes = map["oldAttributes"] as Map; + return UpdateOperation(path, attributes, oldAttributes); + } + + UpdateOperation( + Path path, + this.attributes, + this.oldAttributes, + ) : super(path); UpdateOperation copyWith( {Path? path, Attributes? attributes, Attributes? oldAttributes}) => - UpdateOperation( - path: path ?? this.path, - attributes: attributes ?? this.attributes, - oldAttributes: oldAttributes ?? this.oldAttributes); + UpdateOperation(path ?? this.path, attributes ?? this.attributes, + oldAttributes ?? this.oldAttributes); @override Operation copyWithPath(Path path) => copyWith(path: path); @@ -74,9 +86,9 @@ class UpdateOperation extends Operation { @override Operation invert() { return UpdateOperation( - path: path, - attributes: oldAttributes, - oldAttributes: attributes, + path, + oldAttributes, + attributes, ); } @@ -94,23 +106,26 @@ class UpdateOperation extends Operation { class DeleteOperation extends Operation { final Node removedValue; - DeleteOperation({ - required super.path, - required this.removedValue, - }); + factory DeleteOperation.fromJson(Map map) { + final path = map["path"] as List; + final removedValue = Node.fromJson(map["removedValue"]); + return DeleteOperation(path, removedValue); + } - DeleteOperation copyWith({Path? path, Node? removedValue}) => DeleteOperation( - path: path ?? this.path, removedValue: removedValue ?? this.removedValue); + DeleteOperation( + Path path, + this.removedValue, + ) : super(path); + + DeleteOperation copyWith({Path? path, Node? removedValue}) => + DeleteOperation(path ?? this.path, removedValue ?? this.removedValue); @override Operation copyWithPath(Path path) => copyWith(path: path); @override Operation invert() { - return InsertOperation( - path: path, - value: removedValue, - ); + return InsertOperation(path, removedValue); } @override @@ -127,24 +142,29 @@ class TextEditOperation extends Operation { final Delta delta; final Delta inverted; - TextEditOperation({ - required super.path, - required this.delta, - required this.inverted, - }); + factory TextEditOperation.fromJson(Map map) { + final path = map["path"] as List; + final delta = Delta.fromJson(map["delta"]); + final invert = Delta.fromJson(map["invert"]); + return TextEditOperation(path, delta, invert); + } + + TextEditOperation( + Path path, + this.delta, + this.inverted, + ) : super(path); TextEditOperation copyWith({Path? path, Delta? delta, Delta? inverted}) => TextEditOperation( - path: path ?? this.path, - delta: delta ?? this.delta, - inverted: inverted ?? this.inverted); + path ?? this.path, delta ?? this.delta, inverted ?? this.inverted); @override Operation copyWithPath(Path path) => copyWith(path: path); @override Operation invert() { - return TextEditOperation(path: path, delta: inverted, inverted: delta); + return TextEditOperation(path, inverted, delta); } @override diff --git a/frontend/app_flowy/packages/flowy_editor/lib/operation/transaction_builder.dart b/frontend/app_flowy/packages/flowy_editor/lib/operation/transaction_builder.dart index fb042fe566..2877e62588 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/operation/transaction_builder.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/operation/transaction_builder.dart @@ -1,5 +1,4 @@ import 'dart:collection'; -import 'dart:math'; import 'package:flowy_editor/editor_state.dart'; import 'package:flowy_editor/document/node.dart'; import 'package:flowy_editor/document/path.dart'; @@ -31,21 +30,21 @@ class TransactionBuilder { insertNode(Path path, Node node) { beforeSelection = state.cursorSelection; - add(InsertOperation(path: path, value: node)); + add(InsertOperation(path, node)); } updateNode(Node node, Attributes attributes) { beforeSelection = state.cursorSelection; add(UpdateOperation( - path: node.path, - attributes: Attributes.from(node.attributes)..addAll(attributes), - oldAttributes: node.attributes, + node.path, + Attributes.from(node.attributes)..addAll(attributes), + node.attributes, )); } deleteNode(Node node) { beforeSelection = state.cursorSelection; - add(DeleteOperation(path: node.path, removedValue: node)); + add(DeleteOperation(node.path, node)); } textEdit(TextNode node, Delta Function() f) { @@ -56,7 +55,7 @@ class TransactionBuilder { final inverted = delta.invert(node.delta); - add(TextEditOperation(path: path, delta: delta, inverted: inverted)); + add(TextEditOperation(path, delta, inverted)); } insertText(TextNode node, int index, String content) { @@ -82,9 +81,9 @@ class TransactionBuilder { last is TextEditOperation && pathEquals(op.path, last.path)) { final newOp = TextEditOperation( - path: op.path, - delta: last.delta.compose(op.delta), - inverted: op.inverted.compose(last.inverted), + op.path, + last.delta.compose(op.delta), + op.inverted.compose(last.inverted), ); operations[operations.length - 1] = newOp; return; diff --git a/frontend/app_flowy/packages/flowy_editor/test/operation_test.dart b/frontend/app_flowy/packages/flowy_editor/test/operation_test.dart index a027fa8027..c6811b86e2 100644 --- a/frontend/app_flowy/packages/flowy_editor/test/operation_test.dart +++ b/frontend/app_flowy/packages/flowy_editor/test/operation_test.dart @@ -27,26 +27,18 @@ void main() { group('transform operation', () { test('insert + insert', () { final t = transformOperation( - InsertOperation(path: [ - 0, - 1 - ], value: Node(type: "node", attributes: {}, children: LinkedList())), - InsertOperation( - path: [0, 1], - value: - Node(type: "node", attributes: {}, children: LinkedList()))); + InsertOperation([0, 1], + Node(type: "node", attributes: {}, children: LinkedList())), + InsertOperation([0, 1], + Node(type: "node", attributes: {}, children: LinkedList()))); expect(t.path, [0, 2]); }); test('delete + delete', () { final t = transformOperation( - DeleteOperation( - path: [0, 1], - removedValue: - Node(type: "node", attributes: {}, children: LinkedList())), - DeleteOperation( - path: [0, 2], - removedValue: - Node(type: "node", attributes: {}, children: LinkedList()))); + DeleteOperation([0, 1], + Node(type: "node", attributes: {}, children: LinkedList())), + DeleteOperation([0, 2], + Node(type: "node", attributes: {}, children: LinkedList()))); expect(t.path, [0, 1]); }); });