mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
refactor: batch insert and delete nodes
This commit is contained in:
parent
9b764731e7
commit
2f58c54b81
@ -22,17 +22,21 @@ class StateTree {
|
|||||||
return root.childAtPath(path);
|
return root.childAtPath(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool insert(Path path, Node node) {
|
bool insert(Path path, List<Node> nodes) {
|
||||||
if (path.isEmpty) {
|
if (path.isEmpty) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final insertedNode = root.childAtPath(
|
Node? insertedNode = root.childAtPath(
|
||||||
path.sublist(0, path.length - 1) + [path.last - 1],
|
path.sublist(0, path.length - 1) + [path.last - 1],
|
||||||
);
|
);
|
||||||
if (insertedNode == null) {
|
if (insertedNode == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
insertedNode.insertAfter(node);
|
for (var i = 0; i < nodes.length; i++) {
|
||||||
|
final node = nodes[i];
|
||||||
|
insertedNode!.insertAfter(node);
|
||||||
|
insertedNode = node;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,13 +52,17 @@ class StateTree {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Node? delete(Path path) {
|
delete(Path path, [int length = 1]) {
|
||||||
if (path.isEmpty) {
|
if (path.isEmpty) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
final deletedNode = root.childAtPath(path);
|
var deletedNode = root.childAtPath(path);
|
||||||
deletedNode?.unlink();
|
while (deletedNode != null && length > 0) {
|
||||||
return deletedNode;
|
final next = deletedNode.next;
|
||||||
|
deletedNode.unlink();
|
||||||
|
length--;
|
||||||
|
deletedNode = next;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Attributes? update(Path path, Attributes attributes) {
|
Attributes? update(Path path, Attributes attributes) {
|
||||||
|
@ -94,11 +94,11 @@ class EditorState {
|
|||||||
|
|
||||||
_applyOperation(Operation op) {
|
_applyOperation(Operation op) {
|
||||||
if (op is InsertOperation) {
|
if (op is InsertOperation) {
|
||||||
document.insert(op.path, op.value);
|
document.insert(op.path, op.nodes);
|
||||||
} else if (op is UpdateOperation) {
|
} else if (op is UpdateOperation) {
|
||||||
document.update(op.path, op.attributes);
|
document.update(op.path, op.attributes);
|
||||||
} else if (op is DeleteOperation) {
|
} else if (op is DeleteOperation) {
|
||||||
document.delete(op.path);
|
document.delete(op.path, op.nodes.length);
|
||||||
} else if (op is TextEditOperation) {
|
} else if (op is TextEditOperation) {
|
||||||
document.textEdit(op.path, op.delta);
|
document.textEdit(op.path, op.delta);
|
||||||
}
|
}
|
||||||
|
@ -24,18 +24,19 @@ abstract class Operation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class InsertOperation extends Operation {
|
class InsertOperation extends Operation {
|
||||||
final Node value;
|
final List<Node> nodes;
|
||||||
|
|
||||||
factory InsertOperation.fromJson(Map<String, dynamic> map) {
|
factory InsertOperation.fromJson(Map<String, dynamic> map) {
|
||||||
final path = map["path"] as List<int>;
|
final path = map["path"] as List<int>;
|
||||||
final value = Node.fromJson(map["value"]);
|
final value =
|
||||||
|
(map["nodes"] as List<dynamic>).map((n) => Node.fromJson(n)).toList();
|
||||||
return InsertOperation(path, value);
|
return InsertOperation(path, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
InsertOperation(Path path, this.value) : super(path);
|
InsertOperation(Path path, this.nodes) : super(path);
|
||||||
|
|
||||||
InsertOperation copyWith({Path? path, Node? value}) =>
|
InsertOperation copyWith({Path? path, List<Node>? nodes}) =>
|
||||||
InsertOperation(path ?? this.path, value ?? this.value);
|
InsertOperation(path ?? this.path, nodes ?? this.nodes);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Operation copyWithPath(Path path) => copyWith(path: path);
|
Operation copyWithPath(Path path) => copyWith(path: path);
|
||||||
@ -44,7 +45,7 @@ class InsertOperation extends Operation {
|
|||||||
Operation invert() {
|
Operation invert() {
|
||||||
return DeleteOperation(
|
return DeleteOperation(
|
||||||
path,
|
path,
|
||||||
value,
|
nodes,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,7 +54,7 @@ class InsertOperation extends Operation {
|
|||||||
return {
|
return {
|
||||||
"type": "insert-operation",
|
"type": "insert-operation",
|
||||||
"path": path.toList(),
|
"path": path.toList(),
|
||||||
"value": value.toJson(),
|
"nodes": nodes.map((n) => n.toJson()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -104,28 +105,29 @@ class UpdateOperation extends Operation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class DeleteOperation extends Operation {
|
class DeleteOperation extends Operation {
|
||||||
final Node removedValue;
|
final List<Node> nodes;
|
||||||
|
|
||||||
factory DeleteOperation.fromJson(Map<String, dynamic> map) {
|
factory DeleteOperation.fromJson(Map<String, dynamic> map) {
|
||||||
final path = map["path"] as List<int>;
|
final path = map["path"] as List<int>;
|
||||||
final removedValue = Node.fromJson(map["removedValue"]);
|
final List<Node> nodes =
|
||||||
return DeleteOperation(path, removedValue);
|
(map["nodes"] as List<dynamic>).map((e) => Node.fromJson(e)).toList();
|
||||||
|
return DeleteOperation(path, nodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
DeleteOperation(
|
DeleteOperation(
|
||||||
Path path,
|
Path path,
|
||||||
this.removedValue,
|
this.nodes,
|
||||||
) : super(path);
|
) : super(path);
|
||||||
|
|
||||||
DeleteOperation copyWith({Path? path, Node? removedValue}) =>
|
DeleteOperation copyWith({Path? path, List<Node>? nodes}) =>
|
||||||
DeleteOperation(path ?? this.path, removedValue ?? this.removedValue);
|
DeleteOperation(path ?? this.path, nodes ?? this.nodes);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Operation copyWithPath(Path path) => copyWith(path: path);
|
Operation copyWithPath(Path path) => copyWith(path: path);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Operation invert() {
|
Operation invert() {
|
||||||
return InsertOperation(path, removedValue);
|
return InsertOperation(path, nodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -133,7 +135,7 @@ class DeleteOperation extends Operation {
|
|||||||
return {
|
return {
|
||||||
"type": "delete-operation",
|
"type": "delete-operation",
|
||||||
"path": path.toList(),
|
"path": path.toList(),
|
||||||
"removedValue": removedValue.toJson(),
|
"nodes": nodes.map((n) => n.toJson()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,8 +29,12 @@ class TransactionBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
insertNode(Path path, Node node) {
|
insertNode(Path path, Node node) {
|
||||||
|
insertNodes(path, [node]);
|
||||||
|
}
|
||||||
|
|
||||||
|
insertNodes(Path path, List<Node> nodes) {
|
||||||
beforeSelection = state.cursorSelection;
|
beforeSelection = state.cursorSelection;
|
||||||
add(InsertOperation(path, node));
|
add(InsertOperation(path, nodes));
|
||||||
}
|
}
|
||||||
|
|
||||||
updateNode(Node node, Attributes attributes) {
|
updateNode(Node node, Attributes attributes) {
|
||||||
@ -43,14 +47,28 @@ class TransactionBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
deleteNode(Node node) {
|
deleteNode(Node node) {
|
||||||
beforeSelection = state.cursorSelection;
|
deleteNodesAtPath(node.path);
|
||||||
add(DeleteOperation(node.path, node));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteNodes(List<Node> nodes) {
|
deleteNodes(List<Node> nodes) {
|
||||||
nodes.forEach(deleteNode);
|
nodes.forEach(deleteNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleteNodesAtPath(Path path, [int length = 1]) {
|
||||||
|
if (path.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final nodes = <Node>[];
|
||||||
|
final prefix = path.sublist(0, path.length - 1);
|
||||||
|
final last = path.last;
|
||||||
|
for (var i = 0; i < length; i++) {
|
||||||
|
final node = state.document.nodeAtPath(prefix + [last + i])!;
|
||||||
|
nodes.add(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
add(DeleteOperation(path, nodes));
|
||||||
|
}
|
||||||
|
|
||||||
textEdit(TextNode node, Delta Function() f) {
|
textEdit(TextNode node, Delta Function() f) {
|
||||||
beforeSelection = state.cursorSelection;
|
beforeSelection = state.cursorSelection;
|
||||||
final path = node.path;
|
final path = node.path;
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:math';
|
|
||||||
|
|
||||||
import 'package:flowy_editor/document/node.dart';
|
import 'package:flowy_editor/document/node.dart';
|
||||||
import 'package:flowy_editor/document/state_tree.dart';
|
import 'package:flowy_editor/document/state_tree.dart';
|
||||||
@ -50,7 +49,7 @@ void main() {
|
|||||||
final insertNode = Node.fromJson({
|
final insertNode = Node.fromJson({
|
||||||
'type': 'text',
|
'type': 'text',
|
||||||
});
|
});
|
||||||
bool result = stateTree.insert([1, 1], insertNode);
|
bool result = stateTree.insert([1, 1], [insertNode]);
|
||||||
expect(result, true);
|
expect(result, true);
|
||||||
expect(identical(insertNode, stateTree.nodeAtPath([1, 1])), true);
|
expect(identical(insertNode, stateTree.nodeAtPath([1, 1])), true);
|
||||||
});
|
});
|
||||||
@ -59,9 +58,7 @@ void main() {
|
|||||||
final String response = await rootBundle.loadString('assets/document.json');
|
final String response = await rootBundle.loadString('assets/document.json');
|
||||||
final data = Map<String, Object>.from(json.decode(response));
|
final data = Map<String, Object>.from(json.decode(response));
|
||||||
final stateTree = StateTree.fromJson(data);
|
final stateTree = StateTree.fromJson(data);
|
||||||
final deletedNode = stateTree.delete([1, 1]);
|
stateTree.delete([1, 1], 1);
|
||||||
expect(deletedNode != null, true);
|
|
||||||
expect(deletedNode!.attributes['text-type'], 'checkbox');
|
|
||||||
final node = stateTree.nodeAtPath([1, 1]);
|
final node = stateTree.nodeAtPath([1, 1]);
|
||||||
expect(node != null, true);
|
expect(node != null, true);
|
||||||
expect(node!.attributes['tag'], '**');
|
expect(node!.attributes['tag'], '**');
|
||||||
|
@ -28,17 +28,17 @@ void main() {
|
|||||||
test('insert + insert', () {
|
test('insert + insert', () {
|
||||||
final t = transformOperation(
|
final t = transformOperation(
|
||||||
InsertOperation([0, 1],
|
InsertOperation([0, 1],
|
||||||
Node(type: "node", attributes: {}, children: LinkedList())),
|
[Node(type: "node", attributes: {}, children: LinkedList())]),
|
||||||
InsertOperation([0, 1],
|
InsertOperation([0, 1],
|
||||||
Node(type: "node", attributes: {}, children: LinkedList())));
|
[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([0, 1],
|
DeleteOperation([0, 1],
|
||||||
Node(type: "node", attributes: {}, children: LinkedList())),
|
[Node(type: "node", attributes: {}, children: LinkedList())]),
|
||||||
DeleteOperation([0, 2],
|
DeleteOperation([0, 2],
|
||||||
Node(type: "node", attributes: {}, children: LinkedList())));
|
[Node(type: "node", attributes: {}, children: LinkedList())]));
|
||||||
expect(t.path, [0, 1]);
|
expect(t.path, [0, 1]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -85,7 +85,7 @@ void main() {
|
|||||||
{
|
{
|
||||||
"type": "insert-operation",
|
"type": "insert-operation",
|
||||||
"path": [0],
|
"path": [0],
|
||||||
"value": item1.toJson(),
|
"nodes": [item1.toJson()],
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@ -108,7 +108,7 @@ void main() {
|
|||||||
{
|
{
|
||||||
"type": "delete-operation",
|
"type": "delete-operation",
|
||||||
"path": [0],
|
"path": [0],
|
||||||
"removedValue": item1.toJson(),
|
"nodes": [item1.toJson()],
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user