mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Merge pull request #838 from AppFlowy-IO/feat/deep-copy-nodes
Feat: deep clone nodes
This commit is contained in:
commit
5b1c66ee1e
@ -163,6 +163,18 @@ class Node extends ChangeNotifier with LinkedListEntry<Node> {
|
|||||||
}
|
}
|
||||||
return parent!._path([index, ...previous]);
|
return parent!._path([index, ...previous]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Node deepClone() {
|
||||||
|
final newNode = Node(
|
||||||
|
type: type, children: LinkedList<Node>(), attributes: {...attributes});
|
||||||
|
|
||||||
|
for (final node in children) {
|
||||||
|
final newNode = node.deepClone();
|
||||||
|
newNode.parent = this;
|
||||||
|
newNode.children.add(newNode);
|
||||||
|
}
|
||||||
|
return newNode;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TextNode extends Node {
|
class TextNode extends Node {
|
||||||
@ -213,5 +225,21 @@ class TextNode extends Node {
|
|||||||
delta: delta ?? this.delta,
|
delta: delta ?? this.delta,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
TextNode deepClone() {
|
||||||
|
final newNode = TextNode(
|
||||||
|
type: type,
|
||||||
|
children: LinkedList<Node>(),
|
||||||
|
delta: delta.slice(0),
|
||||||
|
attributes: {...attributes});
|
||||||
|
|
||||||
|
for (final node in children) {
|
||||||
|
final newNode = node.deepClone();
|
||||||
|
newNode.parent = this;
|
||||||
|
newNode.children.add(newNode);
|
||||||
|
}
|
||||||
|
return newNode;
|
||||||
|
}
|
||||||
|
|
||||||
String toRawString() => _delta.toRawString();
|
String toRawString() => _delta.toRawString();
|
||||||
}
|
}
|
||||||
|
@ -417,7 +417,8 @@ class Delta extends Iterable<TextOperation> {
|
|||||||
|
|
||||||
// Optimization if rest of other is just retain
|
// Optimization if rest of other is just retain
|
||||||
if (!otherIter.hasNext &&
|
if (!otherIter.hasNext &&
|
||||||
delta._operations[delta._operations.length - 1] == newOp) {
|
delta._operations.isNotEmpty &&
|
||||||
|
delta._operations.last == newOp) {
|
||||||
final rest = Delta(thisIter.rest());
|
final rest = Delta(thisIter.rest());
|
||||||
return (delta + rest)..chop();
|
return (delta + rest)..chop();
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@ class TransactionBuilder {
|
|||||||
/// Insert a sequence of nodes at the position of path.
|
/// Insert a sequence of nodes at the position of path.
|
||||||
insertNodes(Path path, List<Node> nodes) {
|
insertNodes(Path path, List<Node> nodes) {
|
||||||
beforeSelection = state.cursorSelection;
|
beforeSelection = state.cursorSelection;
|
||||||
add(InsertOperation(path, nodes));
|
add(InsertOperation(path, nodes.map((node) => node.deepClone()).toList()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the attributes of nodes.
|
/// Update the attributes of nodes.
|
||||||
@ -75,7 +75,7 @@ class TransactionBuilder {
|
|||||||
nodes.add(node);
|
nodes.add(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
add(DeleteOperation(path, nodes));
|
add(DeleteOperation(path, nodes.map((node) => node.deepClone()).toList()));
|
||||||
}
|
}
|
||||||
|
|
||||||
textEdit(TextNode node, Delta Function() f) {
|
textEdit(TextNode node, Delta Function() f) {
|
||||||
@ -203,6 +203,9 @@ class TransactionBuilder {
|
|||||||
for (var i = 0; i < operations.length; i++) {
|
for (var i = 0; i < operations.length; i++) {
|
||||||
op = transformOperation(operations[i], op);
|
op = transformOperation(operations[i], op);
|
||||||
}
|
}
|
||||||
|
if (op is TextEditOperation && op.delta.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
operations.add(op);
|
operations.add(op);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,11 +6,10 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:rich_clipboard/rich_clipboard.dart';
|
import 'package:rich_clipboard/rich_clipboard.dart';
|
||||||
|
|
||||||
_handleCopy(EditorState editorState) async {
|
_handleCopy(EditorState editorState) async {
|
||||||
var selection = editorState.cursorSelection;
|
final selection = editorState.cursorSelection?.normalize();
|
||||||
if (selection == null || selection.isCollapsed) {
|
if (selection == null || selection.isCollapsed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
selection = selection.normalize();
|
|
||||||
if (pathEquals(selection.start.path, selection.end.path)) {
|
if (pathEquals(selection.start.path, selection.end.path)) {
|
||||||
final nodeAtPath = editorState.document.nodeAtPath(selection.end.path)!;
|
final nodeAtPath = editorState.document.nodeAtPath(selection.end.path)!;
|
||||||
if (nodeAtPath.type == "text") {
|
if (nodeAtPath.type == "text") {
|
||||||
@ -43,11 +42,13 @@ _handleCopy(EditorState editorState) async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_pasteHTML(EditorState editorState, String html) {
|
_pasteHTML(EditorState editorState, String html) {
|
||||||
final selection = editorState.cursorSelection;
|
final selection = editorState.cursorSelection?.normalize();
|
||||||
if (selection == null) {
|
if (selection == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert(selection.isCollapsed);
|
||||||
|
|
||||||
final path = [...selection.end.path];
|
final path = [...selection.end.path];
|
||||||
if (path.isEmpty) {
|
if (path.isEmpty) {
|
||||||
return;
|
return;
|
||||||
@ -124,6 +125,20 @@ _pasteMultipleLinesInText(
|
|||||||
|
|
||||||
_handlePaste(EditorState editorState) async {
|
_handlePaste(EditorState editorState) async {
|
||||||
final data = await RichClipboard.getData();
|
final data = await RichClipboard.getData();
|
||||||
|
|
||||||
|
if (editorState.cursorSelection?.isCollapsed ?? false) {
|
||||||
|
_pastRichClipboard(editorState, data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_deleteSelectedContent(editorState);
|
||||||
|
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
_pastRichClipboard(editorState, data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_pastRichClipboard(EditorState editorState, RichClipboardData data) {
|
||||||
if (data.html != null) {
|
if (data.html != null) {
|
||||||
_pasteHTML(editorState, data.html!);
|
_pasteHTML(editorState, data.html!);
|
||||||
return;
|
return;
|
||||||
@ -135,7 +150,7 @@ _handlePaste(EditorState editorState) async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_handlePastePlainText(EditorState editorState, String plainText) {
|
_handlePastePlainText(EditorState editorState, String plainText) {
|
||||||
final selection = editorState.cursorSelection;
|
final selection = editorState.cursorSelection?.normalize();
|
||||||
if (selection == null) {
|
if (selection == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -208,22 +223,13 @@ _handlePastePlainText(EditorState editorState, String plainText) {
|
|||||||
/// 2. delete selected content
|
/// 2. delete selected content
|
||||||
_handleCut(EditorState editorState) {
|
_handleCut(EditorState editorState) {
|
||||||
debugPrint('cut');
|
debugPrint('cut');
|
||||||
final selection = editorState.cursorSelection;
|
|
||||||
if (selection == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selection.isCollapsed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_handleCopy(editorState);
|
_handleCopy(editorState);
|
||||||
_deleteSelectedContent(editorState);
|
_deleteSelectedContent(editorState);
|
||||||
}
|
}
|
||||||
|
|
||||||
_deleteSelectedContent(EditorState editorState) {
|
_deleteSelectedContent(EditorState editorState) {
|
||||||
final selection = editorState.cursorSelection;
|
final selection = editorState.cursorSelection?.normalize();
|
||||||
if (selection == null) {
|
if (selection == null || selection.isCollapsed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final beginNode = editorState.document.nodeAtPath(selection.start.path)!;
|
final beginNode = editorState.document.nodeAtPath(selection.start.path)!;
|
||||||
@ -262,11 +268,11 @@ _deleteSelectedContent(EditorState editorState) {
|
|||||||
|
|
||||||
return delta;
|
return delta;
|
||||||
});
|
});
|
||||||
tb.setAfterSelection(Selection.collapsed(selection.start));
|
|
||||||
} else {
|
} else {
|
||||||
tb.deleteNode(item);
|
tb.deleteNode(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
tb.setAfterSelection(Selection.collapsed(selection.start));
|
||||||
tb.commit();
|
tb.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,7 +86,7 @@ FlowyKeyEventHandler enterWithoutShiftInTextNodesHandler =
|
|||||||
);
|
);
|
||||||
TransactionBuilder(editorState)
|
TransactionBuilder(editorState)
|
||||||
..insertNode(
|
..insertNode(
|
||||||
textNode.path,
|
textNode.path.next,
|
||||||
TextNode.empty(),
|
TextNode.empty(),
|
||||||
)
|
)
|
||||||
..afterSelection = afterSelection
|
..afterSelection = afterSelection
|
||||||
|
Loading…
Reference in New Issue
Block a user