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
|
@override
|
||||||
String toString() => 'path = $path, offset = $offset';
|
String toString() => 'path = $path, offset = $offset';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
"path": path.toList(),
|
||||||
|
"offset": offset,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,4 +48,11 @@ class Selection {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => '[Selection] start = $start, end = $end';
|
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';
|
import 'package:flowy_editor/flowy_editor.dart';
|
||||||
|
|
||||||
abstract class Operation {
|
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;
|
final Path path;
|
||||||
Operation({required this.path});
|
Operation(this.path);
|
||||||
Operation copyWithPath(Path path);
|
Operation copyWithPath(Path path);
|
||||||
Operation invert();
|
Operation invert();
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
}
|
}
|
||||||
|
|
||||||
class InsertOperation extends Operation {
|
class InsertOperation extends Operation {
|
||||||
final Node value;
|
final Node value;
|
||||||
|
|
||||||
InsertOperation({
|
factory InsertOperation.fromJson(Map<String, dynamic> map) {
|
||||||
required super.path,
|
final path = map["path"] as List<int>;
|
||||||
required this.value,
|
final value = Node.fromJson(map["value"]);
|
||||||
});
|
return InsertOperation(path, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
InsertOperation(Path path, this.value) : super(path);
|
||||||
|
|
||||||
InsertOperation copyWith({Path? path, Node? value}) =>
|
InsertOperation copyWith({Path? path, Node? value}) =>
|
||||||
InsertOperation(path: path ?? this.path, value: value ?? this.value);
|
InsertOperation(path ?? this.path, value ?? this.value);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Operation copyWithPath(Path path) => copyWith(path: path);
|
Operation copyWithPath(Path path) => copyWith(path: path);
|
||||||
@ -25,28 +43,42 @@ class InsertOperation extends Operation {
|
|||||||
@override
|
@override
|
||||||
Operation invert() {
|
Operation invert() {
|
||||||
return DeleteOperation(
|
return DeleteOperation(
|
||||||
path: path,
|
path,
|
||||||
removedValue: value,
|
value,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
"type": "insert-operation",
|
||||||
|
"path": path.toList(),
|
||||||
|
"value": value.toJson(),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class UpdateOperation extends Operation {
|
class UpdateOperation extends Operation {
|
||||||
final Attributes attributes;
|
final Attributes attributes;
|
||||||
final Attributes oldAttributes;
|
final Attributes oldAttributes;
|
||||||
|
|
||||||
UpdateOperation({
|
factory UpdateOperation.fromJson(Map<String, dynamic> map) {
|
||||||
required super.path,
|
final path = map["path"] as List<int>;
|
||||||
required this.attributes,
|
final attributes = map["attributes"] as Map<String, dynamic>;
|
||||||
required this.oldAttributes,
|
final oldAttributes = map["oldAttributes"] as Map<String, dynamic>;
|
||||||
});
|
return UpdateOperation(path, attributes, oldAttributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateOperation(
|
||||||
|
Path path,
|
||||||
|
this.attributes,
|
||||||
|
this.oldAttributes,
|
||||||
|
) : super(path);
|
||||||
|
|
||||||
UpdateOperation copyWith(
|
UpdateOperation copyWith(
|
||||||
{Path? path, Attributes? attributes, Attributes? oldAttributes}) =>
|
{Path? path, Attributes? attributes, Attributes? oldAttributes}) =>
|
||||||
UpdateOperation(
|
UpdateOperation(path ?? this.path, attributes ?? this.attributes,
|
||||||
path: path ?? this.path,
|
oldAttributes ?? this.oldAttributes);
|
||||||
attributes: attributes ?? this.attributes,
|
|
||||||
oldAttributes: oldAttributes ?? this.oldAttributes);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Operation copyWithPath(Path path) => copyWith(path: path);
|
Operation copyWithPath(Path path) => copyWith(path: path);
|
||||||
@ -54,33 +86,55 @@ class UpdateOperation extends Operation {
|
|||||||
@override
|
@override
|
||||||
Operation invert() {
|
Operation invert() {
|
||||||
return UpdateOperation(
|
return UpdateOperation(
|
||||||
path: path,
|
path,
|
||||||
attributes: oldAttributes,
|
oldAttributes,
|
||||||
oldAttributes: attributes,
|
attributes,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
"type": "update-operation",
|
||||||
|
"path": path.toList(),
|
||||||
|
"attributes": {...attributes},
|
||||||
|
"oldAttributes": {...oldAttributes},
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DeleteOperation extends Operation {
|
class DeleteOperation extends Operation {
|
||||||
final Node removedValue;
|
final Node removedValue;
|
||||||
|
|
||||||
DeleteOperation({
|
factory DeleteOperation.fromJson(Map<String, dynamic> map) {
|
||||||
required super.path,
|
final path = map["path"] as List<int>;
|
||||||
required this.removedValue,
|
final removedValue = Node.fromJson(map["removedValue"]);
|
||||||
});
|
return DeleteOperation(path, removedValue);
|
||||||
|
}
|
||||||
|
|
||||||
DeleteOperation copyWith({Path? path, Node? removedValue}) => DeleteOperation(
|
DeleteOperation(
|
||||||
path: path ?? this.path, removedValue: removedValue ?? this.removedValue);
|
Path path,
|
||||||
|
this.removedValue,
|
||||||
|
) : super(path);
|
||||||
|
|
||||||
|
DeleteOperation copyWith({Path? path, Node? removedValue}) =>
|
||||||
|
DeleteOperation(path ?? this.path, removedValue ?? this.removedValue);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Operation copyWithPath(Path path) => copyWith(path: path);
|
Operation copyWithPath(Path path) => copyWith(path: path);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Operation invert() {
|
Operation invert() {
|
||||||
return InsertOperation(
|
return InsertOperation(path, removedValue);
|
||||||
path: path,
|
}
|
||||||
value: 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 delta;
|
||||||
final Delta inverted;
|
final Delta inverted;
|
||||||
|
|
||||||
TextEditOperation({
|
factory TextEditOperation.fromJson(Map<String, dynamic> map) {
|
||||||
required super.path,
|
final path = map["path"] as List<int>;
|
||||||
required this.delta,
|
final delta = Delta.fromJson(map["delta"]);
|
||||||
required this.inverted,
|
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 copyWith({Path? path, Delta? delta, Delta? inverted}) =>
|
||||||
TextEditOperation(
|
TextEditOperation(
|
||||||
path: path ?? this.path,
|
path ?? this.path, delta ?? this.delta, inverted ?? this.inverted);
|
||||||
delta: delta ?? this.delta,
|
|
||||||
inverted: inverted ?? this.inverted);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Operation copyWithPath(Path path) => copyWith(path: path);
|
Operation copyWithPath(Path path) => copyWith(path: path);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Operation invert() {
|
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.beforeSelection,
|
||||||
this.afterSelection,
|
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:collection';
|
||||||
import 'dart:math';
|
|
||||||
import 'package:flowy_editor/editor_state.dart';
|
import 'package:flowy_editor/editor_state.dart';
|
||||||
import 'package:flowy_editor/document/node.dart';
|
import 'package:flowy_editor/document/node.dart';
|
||||||
import 'package:flowy_editor/document/path.dart';
|
import 'package:flowy_editor/document/path.dart';
|
||||||
@ -31,21 +30,21 @@ class TransactionBuilder {
|
|||||||
|
|
||||||
insertNode(Path path, Node node) {
|
insertNode(Path path, Node node) {
|
||||||
beforeSelection = state.cursorSelection;
|
beforeSelection = state.cursorSelection;
|
||||||
add(InsertOperation(path: path, value: node));
|
add(InsertOperation(path, node));
|
||||||
}
|
}
|
||||||
|
|
||||||
updateNode(Node node, Attributes attributes) {
|
updateNode(Node node, Attributes attributes) {
|
||||||
beforeSelection = state.cursorSelection;
|
beforeSelection = state.cursorSelection;
|
||||||
add(UpdateOperation(
|
add(UpdateOperation(
|
||||||
path: node.path,
|
node.path,
|
||||||
attributes: Attributes.from(node.attributes)..addAll(attributes),
|
Attributes.from(node.attributes)..addAll(attributes),
|
||||||
oldAttributes: node.attributes,
|
node.attributes,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteNode(Node node) {
|
deleteNode(Node node) {
|
||||||
beforeSelection = state.cursorSelection;
|
beforeSelection = state.cursorSelection;
|
||||||
add(DeleteOperation(path: node.path, removedValue: node));
|
add(DeleteOperation(node.path, node));
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteNodes(List<Node> nodes) {
|
deleteNodes(List<Node> nodes) {
|
||||||
@ -60,7 +59,7 @@ class TransactionBuilder {
|
|||||||
|
|
||||||
final inverted = delta.invert(node.delta);
|
final inverted = delta.invert(node.delta);
|
||||||
|
|
||||||
add(TextEditOperation(path: path, delta: delta, inverted: inverted));
|
add(TextEditOperation(path, delta, inverted));
|
||||||
}
|
}
|
||||||
|
|
||||||
mergeText(TextNode firstNode, TextNode secondNode,
|
mergeText(TextNode firstNode, TextNode secondNode,
|
||||||
@ -119,9 +118,9 @@ class TransactionBuilder {
|
|||||||
last is TextEditOperation &&
|
last is TextEditOperation &&
|
||||||
pathEquals(op.path, last.path)) {
|
pathEquals(op.path, last.path)) {
|
||||||
final newOp = TextEditOperation(
|
final newOp = TextEditOperation(
|
||||||
path: op.path,
|
op.path,
|
||||||
delta: last.delta.compose(op.delta),
|
last.delta.compose(op.delta),
|
||||||
inverted: op.inverted.compose(last.inverted),
|
op.inverted.compose(last.inverted),
|
||||||
);
|
);
|
||||||
operations[operations.length - 1] = newOp;
|
operations[operations.length - 1] = newOp;
|
||||||
return;
|
return;
|
||||||
|
@ -27,26 +27,18 @@ void main() {
|
|||||||
group('transform operation', () {
|
group('transform operation', () {
|
||||||
test('insert + insert', () {
|
test('insert + insert', () {
|
||||||
final t = transformOperation(
|
final t = transformOperation(
|
||||||
InsertOperation(path: [
|
InsertOperation([0, 1],
|
||||||
0,
|
Node(type: "node", attributes: {}, children: LinkedList())),
|
||||||
1
|
InsertOperation([0, 1],
|
||||||
], value: Node(type: "node", attributes: {}, children: LinkedList())),
|
Node(type: "node", attributes: {}, children: LinkedList())));
|
||||||
InsertOperation(
|
|
||||||
path: [0, 1],
|
|
||||||
value:
|
|
||||||
Node(type: "node", attributes: {}, children: LinkedList())));
|
|
||||||
expect(t.path, [0, 2]);
|
expect(t.path, [0, 2]);
|
||||||
});
|
});
|
||||||
test('delete + delete', () {
|
test('delete + delete', () {
|
||||||
final t = transformOperation(
|
final t = transformOperation(
|
||||||
DeleteOperation(
|
DeleteOperation([0, 1],
|
||||||
path: [0, 1],
|
Node(type: "node", attributes: {}, children: LinkedList())),
|
||||||
removedValue:
|
DeleteOperation([0, 2],
|
||||||
Node(type: "node", attributes: {}, children: LinkedList())),
|
Node(type: "node", attributes: {}, children: LinkedList())));
|
||||||
DeleteOperation(
|
|
||||||
path: [0, 2],
|
|
||||||
removedValue:
|
|
||||||
Node(type: "node", attributes: {}, children: LinkedList())));
|
|
||||||
expect(t.path, [0, 1]);
|
expect(t.path, [0, 1]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -78,4 +70,48 @@ void main() {
|
|||||||
expect(transaction.operations[1].path, [0]);
|
expect(transaction.operations[1].path, [0]);
|
||||||
expect(transaction.operations[2].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