mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Merge pull request #749 from AppFlowy-IO/feat/transaction-to-json
Feat: transaction to json
This commit is contained in:
commit
9b764731e7
@ -34,4 +34,11 @@ class Position {
|
||||
|
||||
@override
|
||||
String toString() => 'path = $path, offset = $offset';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
"path": path.toList(),
|
||||
"offset": offset,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -48,4 +48,11 @@ class Selection {
|
||||
|
||||
@override
|
||||
String toString() => '[Selection] start = $start, end = $end';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
"start": start.toJson(),
|
||||
"end": end.toJson(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -2,22 +2,40 @@ import 'package:flowy_editor/document/attributes.dart';
|
||||
import 'package:flowy_editor/flowy_editor.dart';
|
||||
|
||||
abstract class Operation {
|
||||
factory Operation.fromJson(Map<String, dynamic> map) {
|
||||
String t = map["type"] as String;
|
||||
if (t == "insert-operation") {
|
||||
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<String, dynamic> toJson();
|
||||
}
|
||||
|
||||
class InsertOperation extends Operation {
|
||||
final Node value;
|
||||
|
||||
InsertOperation({
|
||||
required super.path,
|
||||
required this.value,
|
||||
});
|
||||
factory InsertOperation.fromJson(Map<String, dynamic> map) {
|
||||
final path = map["path"] as List<int>;
|
||||
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);
|
||||
@ -25,28 +43,42 @@ class InsertOperation extends Operation {
|
||||
@override
|
||||
Operation invert() {
|
||||
return DeleteOperation(
|
||||
path: path,
|
||||
removedValue: value,
|
||||
path,
|
||||
value,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
"type": "insert-operation",
|
||||
"path": path.toList(),
|
||||
"value": value.toJson(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class UpdateOperation extends Operation {
|
||||
final Attributes attributes;
|
||||
final Attributes oldAttributes;
|
||||
|
||||
UpdateOperation({
|
||||
required super.path,
|
||||
required this.attributes,
|
||||
required this.oldAttributes,
|
||||
});
|
||||
factory UpdateOperation.fromJson(Map<String, dynamic> map) {
|
||||
final path = map["path"] as List<int>;
|
||||
final attributes = map["attributes"] as Map<String, dynamic>;
|
||||
final oldAttributes = map["oldAttributes"] as Map<String, dynamic>;
|
||||
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);
|
||||
@ -54,33 +86,55 @@ class UpdateOperation extends Operation {
|
||||
@override
|
||||
Operation invert() {
|
||||
return UpdateOperation(
|
||||
path: path,
|
||||
attributes: oldAttributes,
|
||||
oldAttributes: attributes,
|
||||
path,
|
||||
oldAttributes,
|
||||
attributes,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
"type": "update-operation",
|
||||
"path": path.toList(),
|
||||
"attributes": {...attributes},
|
||||
"oldAttributes": {...oldAttributes},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class DeleteOperation extends Operation {
|
||||
final Node removedValue;
|
||||
|
||||
DeleteOperation({
|
||||
required super.path,
|
||||
required this.removedValue,
|
||||
});
|
||||
factory DeleteOperation.fromJson(Map<String, dynamic> map) {
|
||||
final path = map["path"] as List<int>;
|
||||
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
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
"type": "delete-operation",
|
||||
"path": path.toList(),
|
||||
"removedValue": removedValue.toJson(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,24 +142,39 @@ class TextEditOperation extends Operation {
|
||||
final Delta delta;
|
||||
final Delta inverted;
|
||||
|
||||
TextEditOperation({
|
||||
required super.path,
|
||||
required this.delta,
|
||||
required this.inverted,
|
||||
});
|
||||
factory TextEditOperation.fromJson(Map<String, dynamic> map) {
|
||||
final path = map["path"] as List<int>;
|
||||
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
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
"type": "text-edit-operation",
|
||||
"path": path.toList(),
|
||||
"delta": delta.toJson(),
|
||||
"invert": inverted.toJson(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,4 +23,17 @@ class Transaction {
|
||||
this.beforeSelection,
|
||||
this.afterSelection,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> result = {
|
||||
"operations": operations.map((e) => e.toJson()),
|
||||
};
|
||||
if (beforeSelection != null) {
|
||||
result["beforeSelection"] = beforeSelection!.toJson();
|
||||
}
|
||||
if (afterSelection != null) {
|
||||
result["afterSelection"] = afterSelection!.toJson();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
deleteNodes(List<Node> nodes) {
|
||||
@ -60,7 +59,7 @@ class TransactionBuilder {
|
||||
|
||||
final inverted = delta.invert(node.delta);
|
||||
|
||||
add(TextEditOperation(path: path, delta: delta, inverted: inverted));
|
||||
add(TextEditOperation(path, delta, inverted));
|
||||
}
|
||||
|
||||
mergeText(TextNode firstNode, TextNode secondNode,
|
||||
@ -119,9 +118,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;
|
||||
|
@ -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]);
|
||||
});
|
||||
});
|
||||
@ -78,4 +70,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(),
|
||||
}
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user