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/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 f8cc80b161..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 @@ -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) { @@ -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/copy_paste_handler.dart b/frontend/app_flowy/packages/flowy_editor/lib/src/service/internal_key_event_handlers/copy_paste_handler.dart index 363b84967a..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; - if (selection == null) { + final selection = editorState.cursorSelection?.normalize(); + 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(); } 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