diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/markdown_text_robot.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/markdown_text_robot.dart index 031e157b33..208bfdb40d 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/markdown_text_robot.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/markdown_text_robot.dart @@ -320,6 +320,12 @@ class MarkdownTextRobot { required Selection selection, required String markdownText, }) async { + if (markdownText.isEmpty) { + assert(false, 'Expected non-empty markdown text'); + Log.error('Expected non-empty markdown text'); + return; + } + selection = selection.normalized; // If the selection is not a single node, do nothing. @@ -372,15 +378,42 @@ class MarkdownTextRobot { // } // ] final document = customMarkdownToDocument(markdownText); + final nodes = document.root.children; final decoder = DeltaMarkdownDecoder(); final markdownDelta = - document.nodeAtPath([0])?.delta ?? decoder.convert(markdownText); + nodes.firstOrNull?.delta ?? decoder.convert(markdownText); + + if (markdownDelta.isEmpty) { + assert(false, 'Expected non-empty markdown delta'); + Log.error('Expected non-empty markdown delta'); + return; + } // Replace the delta of the selected node. final transaction = editorState.transaction; - transaction - ..deleteText(node, startIndex, length) - ..insertTextDelta(node, startIndex, markdownDelta); + + // it means the user selected the entire sentence, we just replace the node + if (startIndex == 0 && length == node.delta?.length) { + transaction + ..insertNodes(node.path.next, nodes) + ..deleteNode(node); + } else { + // it means the user selected a part of the sentence, we need to delete the + // selected part and insert the new delta. + transaction + ..deleteText(node, startIndex, length) + ..insertTextDelta(node, startIndex, markdownDelta); + + // Add the remaining nodes to the document. + final remainingNodes = nodes.skip(1); + if (remainingNodes.isNotEmpty) { + transaction.insertNodes( + node.path.next, + remainingNodes, + ); + } + } + await editorState.apply(transaction); } diff --git a/frontend/appflowy_flutter/test/unit_test/document/text_robot/markdown_text_robot_test.dart b/frontend/appflowy_flutter/test/unit_test/document/text_robot/markdown_text_robot_test.dart index f2fcf8cd65..414098c206 100644 --- a/frontend/appflowy_flutter/test/unit_test/document/text_robot/markdown_text_robot_test.dart +++ b/frontend/appflowy_flutter/test/unit_test/document/text_robot/markdown_text_robot_test.dart @@ -487,6 +487,63 @@ void main() { ); expect(d7.attributes, null); }); + + test('replace markdown text with selection from start to end', () async { + final document = Document( + root: pageNode( + children: [ + paragraphNode(delta: Delta()..insert(text1)), + paragraphNode(delta: Delta()..insert(text2)), + paragraphNode(delta: Delta()..insert(text3)), + ], + ), + ); + final editorState = EditorState(document: document); + + editorState.selection = Selection( + start: Position(path: [0]), + end: Position(path: [0], offset: text1.length), + ); + + final markdownText = '''1. $text1 + +2. $text1 + +3. $text1'''; + final markdownTextRobot = MarkdownTextRobot( + editorState: editorState, + ); + await markdownTextRobot.replace( + selection: editorState.selection!, + markdownText: markdownText, + ); + + final nodes = editorState.document.root.children; + expect(nodes.length, 5); + + final d1 = nodes[0].delta!.toList()[0] as TextInsert; + expect(d1.text, text1); + expect(d1.attributes, null); + expect(nodes[0].type, NumberedListBlockKeys.type); + + final d2 = nodes[1].delta!.toList()[1] as TextInsert; + expect(d2.text, text1); + expect(d2.attributes, null); + expect(nodes[1].type, NumberedListBlockKeys.type); + + final d3 = nodes[2].delta!.toList()[2] as TextInsert; + expect(d3.text, text1); + expect(d3.attributes, null); + expect(nodes[2].type, NumberedListBlockKeys.type); + + final d4 = nodes[3].delta!.toList()[3] as TextInsert; + expect(d4.text, text2); + expect(d4.attributes, null); + + final d5 = nodes[4].delta!.toList()[4] as TextInsert; + expect(d5.text, text3); + expect(d5.attributes, null); + }); }); group('markdown text robot - replace in multiple lines:', () {