From 0424c14c7d99fa9128ca08f3319d9c8b396374ca Mon Sep 17 00:00:00 2001 From: Vincent Chan Date: Fri, 12 Aug 2022 17:58:16 +0800 Subject: [PATCH 1/3] feat: deep clone nodes --- .../flowy_editor/lib/src/document/node.dart | 28 +++++++++++++++++++ .../src/operation/transaction_builder.dart | 4 +-- .../copy_paste_handler.dart | 2 +- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/frontend/app_flowy/packages/flowy_editor/lib/src/document/node.dart b/frontend/app_flowy/packages/flowy_editor/lib/src/document/node.dart index 24c3035e90..6bfc877524 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/src/document/node.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/src/document/node.dart @@ -163,6 +163,18 @@ class Node extends ChangeNotifier with LinkedListEntry { } return parent!._path([index, ...previous]); } + + Node deepClone() { + final newNode = Node( + type: type, children: LinkedList(), attributes: {...attributes}); + + for (final node in children) { + final newNode = node.deepClone(); + newNode.parent = this; + newNode.children.add(newNode); + } + return newNode; + } } class TextNode extends Node { @@ -213,5 +225,21 @@ class TextNode extends Node { delta: delta ?? this.delta, ); + @override + TextNode deepClone() { + final newNode = TextNode( + type: type, + children: LinkedList(), + 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(); } diff --git a/frontend/app_flowy/packages/flowy_editor/lib/src/operation/transaction_builder.dart b/frontend/app_flowy/packages/flowy_editor/lib/src/operation/transaction_builder.dart index f8cc80b161..dfca3cd661 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/src/operation/transaction_builder.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/src/operation/transaction_builder.dart @@ -36,7 +36,7 @@ class TransactionBuilder { /// Insert a sequence of nodes at the position of path. insertNodes(Path path, List nodes) { beforeSelection = state.cursorSelection; - add(InsertOperation(path, nodes)); + add(InsertOperation(path, nodes.map((node) => node.deepClone()).toList())); } /// Update the attributes of nodes. @@ -75,7 +75,7 @@ class TransactionBuilder { nodes.add(node); } - add(DeleteOperation(path, nodes)); + add(DeleteOperation(path, nodes.map((node) => node.deepClone()).toList())); } textEdit(TextNode node, Delta Function() f) { diff --git a/frontend/app_flowy/packages/flowy_editor/lib/src/service/internal_key_event_handlers/copy_paste_handler.dart b/frontend/app_flowy/packages/flowy_editor/lib/src/service/internal_key_event_handlers/copy_paste_handler.dart index 363b84967a..b1bf170672 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/src/service/internal_key_event_handlers/copy_paste_handler.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/src/service/internal_key_event_handlers/copy_paste_handler.dart @@ -222,7 +222,7 @@ _handleCut(EditorState editorState) { } _deleteSelectedContent(EditorState editorState) { - final selection = editorState.cursorSelection; + final selection = editorState.cursorSelection?.normalize(); if (selection == null) { return; } From ba05aa137ecb9ef514f240cfb3d511957d02b08f Mon Sep 17 00:00:00 2001 From: Vincent Chan Date: Fri, 12 Aug 2022 18:03:22 +0800 Subject: [PATCH 2/3] feat: filter the empty delta --- .../packages/flowy_editor/lib/src/document/text_delta.dart | 3 ++- .../flowy_editor/lib/src/operation/transaction_builder.dart | 3 +++ .../enter_without_shift_in_text_node_handler.dart | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/frontend/app_flowy/packages/flowy_editor/lib/src/document/text_delta.dart b/frontend/app_flowy/packages/flowy_editor/lib/src/document/text_delta.dart index 05f0d61b8d..658114df7a 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/src/document/text_delta.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/src/document/text_delta.dart @@ -417,7 +417,8 @@ class Delta extends Iterable { // Optimization if rest of other is just retain if (!otherIter.hasNext && - delta._operations[delta._operations.length - 1] == newOp) { + delta._operations.isNotEmpty && + delta._operations.last == newOp) { final rest = Delta(thisIter.rest()); return (delta + rest)..chop(); } diff --git a/frontend/app_flowy/packages/flowy_editor/lib/src/operation/transaction_builder.dart b/frontend/app_flowy/packages/flowy_editor/lib/src/operation/transaction_builder.dart index dfca3cd661..68a46d8a93 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/src/operation/transaction_builder.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/src/operation/transaction_builder.dart @@ -203,6 +203,9 @@ class TransactionBuilder { for (var i = 0; i < operations.length; i++) { op = transformOperation(operations[i], op); } + if (op is TextEditOperation && op.delta.isEmpty) { + return; + } operations.add(op); } diff --git a/frontend/app_flowy/packages/flowy_editor/lib/src/service/internal_key_event_handlers/enter_without_shift_in_text_node_handler.dart b/frontend/app_flowy/packages/flowy_editor/lib/src/service/internal_key_event_handlers/enter_without_shift_in_text_node_handler.dart index 39c74d2eab..1a25a2531d 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/src/service/internal_key_event_handlers/enter_without_shift_in_text_node_handler.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/src/service/internal_key_event_handlers/enter_without_shift_in_text_node_handler.dart @@ -86,7 +86,7 @@ FlowyKeyEventHandler enterWithoutShiftInTextNodesHandler = ); TransactionBuilder(editorState) ..insertNode( - textNode.path, + textNode.path.next, TextNode.empty(), ) ..afterSelection = afterSelection From d822e1e2ca15d58181638fd816ed9d79331f09bd Mon Sep 17 00:00:00 2001 From: Vincent Chan Date: Fri, 12 Aug 2022 18:44:56 +0800 Subject: [PATCH 3/3] fix: paste nodes --- .../copy_paste_handler.dart | 36 +++++++++++-------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/frontend/app_flowy/packages/flowy_editor/lib/src/service/internal_key_event_handlers/copy_paste_handler.dart b/frontend/app_flowy/packages/flowy_editor/lib/src/service/internal_key_event_handlers/copy_paste_handler.dart index b1bf170672..7c89c2ed83 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/src/service/internal_key_event_handlers/copy_paste_handler.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/src/service/internal_key_event_handlers/copy_paste_handler.dart @@ -6,11 +6,10 @@ import 'package:flutter/services.dart'; import 'package:rich_clipboard/rich_clipboard.dart'; _handleCopy(EditorState editorState) async { - var selection = editorState.cursorSelection; + final selection = editorState.cursorSelection?.normalize(); if (selection == null || selection.isCollapsed) { return; } - selection = selection.normalize(); if (pathEquals(selection.start.path, selection.end.path)) { final nodeAtPath = editorState.document.nodeAtPath(selection.end.path)!; if (nodeAtPath.type == "text") { @@ -43,11 +42,13 @@ _handleCopy(EditorState editorState) async { } _pasteHTML(EditorState editorState, String html) { - final selection = editorState.cursorSelection; + final selection = editorState.cursorSelection?.normalize(); if (selection == null) { return; } + assert(selection.isCollapsed); + final path = [...selection.end.path]; if (path.isEmpty) { return; @@ -124,6 +125,20 @@ _pasteMultipleLinesInText( _handlePaste(EditorState editorState) async { 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) { _pasteHTML(editorState, data.html!); return; @@ -135,7 +150,7 @@ _handlePaste(EditorState editorState) async { } _handlePastePlainText(EditorState editorState, String plainText) { - final selection = editorState.cursorSelection; + final selection = editorState.cursorSelection?.normalize(); if (selection == null) { return; } @@ -208,22 +223,13 @@ _handlePastePlainText(EditorState editorState, String plainText) { /// 2. delete selected content _handleCut(EditorState editorState) { debugPrint('cut'); - final selection = editorState.cursorSelection; - if (selection == null) { - return; - } - - if (selection.isCollapsed) { - return; - } - _handleCopy(editorState); _deleteSelectedContent(editorState); } _deleteSelectedContent(EditorState editorState) { final selection = editorState.cursorSelection?.normalize(); - if (selection == null) { + if (selection == null || selection.isCollapsed) { return; } final beginNode = editorState.document.nodeAtPath(selection.start.path)!; @@ -262,11 +268,11 @@ _deleteSelectedContent(EditorState editorState) { return delta; }); - tb.setAfterSelection(Selection.collapsed(selection.start)); } else { tb.deleteNode(item); } } + tb.setAfterSelection(Selection.collapsed(selection.start)); tb.commit(); }