From d471d54252c59c83ef774a22471eb6696f3a07bc Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Tue, 8 Apr 2025 15:54:41 +0800 Subject: [PATCH] fix: replace the selected text with ai response in the multiple lines --- .../base/markdown_text_robot.dart | 59 ++++- .../text_robot/markdown_text_robot_test.dart | 245 ++++++++++++++++++ 2 files changed, 303 insertions(+), 1 deletion(-) 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 270b41a562..f84d759470 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 @@ -358,10 +358,67 @@ class MarkdownTextRobot { return; } + final markdownNodes = customMarkdownToDocument( + markdownText, + tableWidth: 250.0, + ).root.children; + // Get the selected nodes. final nodes = editorState.getNodesInSelection(selection); - // Replace the delta of the selected nodes. + // step 1. merge the first selected node and the first node from the ai response + // step 2. merge the last selected node and the last node from the ai response + // step 3. insert the middle nodes from the ai response + // step 4. delete the middle nodes + final transaction = editorState.transaction; + + // step 1 + final firstNode = nodes.firstOrNull; + final delta = firstNode?.delta; + final firstMarkdownNode = markdownNodes.firstOrNull; + final firstMarkdownDelta = firstMarkdownNode?.delta; + if (firstNode != null && + delta != null && + firstMarkdownNode != null && + firstMarkdownDelta != null) { + final startIndex = selection.startIndex; + final length = delta.length - startIndex; + transaction + ..deleteText(firstNode, startIndex, length) + ..insertTextDelta(firstNode, startIndex, firstMarkdownDelta); + } + + // step 2 + final lastNode = nodes.lastOrNull; + final lastDelta = lastNode?.delta; + final lastMarkdownNode = markdownNodes.lastOrNull; + final lastMarkdownDelta = lastMarkdownNode?.delta; + if (lastNode != null && + lastDelta != null && + lastMarkdownNode != null && + lastMarkdownDelta != null) { + final endIndex = selection.endIndex; + transaction + ..deleteText(lastNode, 0, endIndex) + ..insertTextDelta(lastNode, 0, lastMarkdownDelta); + } + + // step 3 + final insertedPath = selection.start.path.nextNPath(1); + if (markdownNodes.length > 2) { + transaction.insertNodes( + insertedPath, + markdownNodes.skip(1).take(markdownNodes.length - 2).toList(), + ); + } + + // step 4 + if (nodes.length > 2) { + final middleNodes = nodes.skip(1).take(nodes.length - 2).toList(); + transaction.deleteNodes(middleNodes); + } + + 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 e0da259269..b4b6c3e1c8 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 @@ -488,6 +488,251 @@ void main() { expect(d7.attributes, null); }); }); + + group('markdown text robot - replace in multiple lines:', () { + final text1 = + '''The introduction of the World Wide Web in the early 1990s marked a turning point. '''; + final text2 = + '''Tim Berners-Lee's invention made the internet accessible to non-technical users, opening the floodgates for mass adoption. '''; + final text3 = + '''Email became widespread, and instant messaging services like ICQ and AOL Instant Messenger gained popularity, allowing for real-time text communication.'''; + + Document buildTestDocument() { + return Document( + root: pageNode( + children: [ + paragraphNode(delta: Delta()..insert(text1)), + paragraphNode(delta: Delta()..insert(text2)), + paragraphNode(delta: Delta()..insert(text3)), + ], + ), + ); + } + + // 1. create a document with 3 paragraph nodes + // 2. use the text robot to replace the selected content in the multiple lines + // 3. check the document + test( + 'the selection starts with the first paragraph and ends with the middle of second paragraph', + () async { + final document = buildTestDocument(); + final editorState = EditorState(document: document); + + editorState.selection = Selection( + start: Position( + path: [0], + ), + end: Position( + path: [1], + offset: text2.length - + ', opening the floodgates for mass adoption. '.length, + ), + ); + + final markdownText = + '''The **introduction** of the World Wide Web in the *early 1990s* marked a significant turning point. + +Tim Berners-Lee's **revolutionary invention** made the internet accessible to non-technical users'''; + final markdownTextRobot = MarkdownTextRobot( + editorState: editorState, + ); + await markdownTextRobot.replace( + selection: editorState.selection!, + markdownText: markdownText, + ); + + final afterNodes = editorState.document.root.children; + expect(afterNodes.length, 3); + + { + // first paragraph + final delta1 = afterNodes[0].delta!.toList(); + expect(delta1.length, 5); + + final d1 = delta1[0] as TextInsert; + expect(d1.text, 'The '); + expect(d1.attributes, null); + + final d2 = delta1[1] as TextInsert; + expect(d2.text, 'introduction'); + expect(d2.attributes, {AppFlowyRichTextKeys.bold: true}); + + final d3 = delta1[2] as TextInsert; + expect(d3.text, ' of the World Wide Web in the '); + expect(d3.attributes, null); + + final d4 = delta1[3] as TextInsert; + expect(d4.text, 'early 1990s'); + expect(d4.attributes, {AppFlowyRichTextKeys.italic: true}); + + final d5 = delta1[4] as TextInsert; + expect(d5.text, ' marked a significant turning point.'); + expect(d5.attributes, null); + } + + { + // second paragraph + final delta2 = afterNodes[1].delta!.toList(); + expect(delta2.length, 3); + + final d1 = delta2[0] as TextInsert; + expect(d1.text, "Tim Berners-Lee's "); + expect(d1.attributes, null); + + final d2 = delta2[1] as TextInsert; + expect(d2.text, "revolutionary invention"); + expect(d2.attributes, {AppFlowyRichTextKeys.bold: true}); + + final d3 = delta2[2] as TextInsert; + expect( + d3.text, + " made the internet accessible to non-technical users, opening the floodgates for mass adoption. ", + ); + expect(d3.attributes, null); + } + + { + // third paragraph + final delta3 = afterNodes[2].delta!.toList(); + expect(delta3.length, 1); + + final d1 = delta3[0] as TextInsert; + expect(d1.text, text3); + expect(d1.attributes, null); + } + }); + + test( + 'the selection starts with the middle of the first paragraph and ends with the middle of last paragraph', + () async { + final document = buildTestDocument(); + final editorState = EditorState(document: document); + + editorState.selection = Selection( + start: Position( + path: [0], + offset: 'The introduction of the World Wide Web'.length, + ), + end: Position( + path: [2], + offset: + 'Email became widespread, and instant messaging services like ICQ and AOL Instant Messenger gained popularity' + .length, + ), + ); + + final markdownText = + ''' in the **early 1990s** marked a *significant turning point* in technological history. + +Tim Berners-Lee's **revolutionary invention** made the internet accessible to non-technical users, opening the floodgates for *unprecedented mass adoption*. + +Email became **widely prevalent**, and instant messaging services like *ICQ* and *AOL Instant Messenger* gained tremendous popularity + '''; + final markdownTextRobot = MarkdownTextRobot( + editorState: editorState, + ); + await markdownTextRobot.replace( + selection: editorState.selection!, + markdownText: markdownText, + ); + + final afterNodes = editorState.document.root.children; + expect(afterNodes.length, 3); + + { + // first paragraph + final delta1 = afterNodes[0].delta!.toList(); + expect(delta1.length, 5); + + final d1 = delta1[0] as TextInsert; + expect(d1.text, 'The introduction of the World Wide Web in the '); + expect(d1.attributes, null); + + final d2 = delta1[1] as TextInsert; + expect(d2.text, 'early 1990s'); + expect(d2.attributes, {AppFlowyRichTextKeys.bold: true}); + + final d3 = delta1[2] as TextInsert; + expect(d3.text, ' marked a '); + expect(d3.attributes, null); + + final d4 = delta1[3] as TextInsert; + expect(d4.text, 'significant turning point'); + expect(d4.attributes, {AppFlowyRichTextKeys.italic: true}); + + final d5 = delta1[4] as TextInsert; + expect(d5.text, ' in technological history.'); + expect(d5.attributes, null); + } + + { + // second paragraph + final delta2 = afterNodes[1].delta!.toList(); + expect(delta2.length, 5); + + final d1 = delta2[0] as TextInsert; + expect(d1.text, "Tim Berners-Lee's "); + expect(d1.attributes, null); + + final d2 = delta2[1] as TextInsert; + expect(d2.text, "revolutionary invention"); + expect(d2.attributes, {AppFlowyRichTextKeys.bold: true}); + + final d3 = delta2[2] as TextInsert; + expect( + d3.text, + " made the internet accessible to non-technical users, opening the floodgates for ", + ); + expect(d3.attributes, null); + + final d4 = delta2[3] as TextInsert; + expect(d4.text, "unprecedented mass adoption"); + expect(d4.attributes, {AppFlowyRichTextKeys.italic: true}); + + final d5 = delta2[4] as TextInsert; + expect(d5.text, "."); + expect(d5.attributes, null); + } + + { + // third paragraph + // third paragraph + final delta3 = afterNodes[2].delta!.toList(); + expect(delta3.length, 7); + + final d1 = delta3[0] as TextInsert; + expect(d1.text, "Email became "); + expect(d1.attributes, null); + + final d2 = delta3[1] as TextInsert; + expect(d2.text, "widely prevalent"); + expect(d2.attributes, {AppFlowyRichTextKeys.bold: true}); + + final d3 = delta3[2] as TextInsert; + expect(d3.text, ", and instant messaging services like "); + expect(d3.attributes, null); + + final d4 = delta3[3] as TextInsert; + expect(d4.text, "ICQ"); + expect(d4.attributes, {AppFlowyRichTextKeys.italic: true}); + + final d5 = delta3[4] as TextInsert; + expect(d5.text, " and "); + expect(d5.attributes, null); + + final d6 = delta3[5] as TextInsert; + expect(d6.text, "AOL Instant Messenger"); + expect(d6.attributes, {AppFlowyRichTextKeys.italic: true}); + + final d7 = delta3[6] as TextInsert; + expect( + d7.text, + " gained tremendous popularity, allowing for real-time text communication.", + ); + expect(d7.attributes, null); + } + }); + }); } const _sample1 = '''# The Curious Cat