From 724fc895e146c7210179b3cdcb6b8fade316a673 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Tue, 22 Aug 2023 15:46:27 +0800 Subject: [PATCH] feat: format the selected text to url if available (#3245) --- .../document_copy_and_paste_test.dart | 37 ++++++++++++++++++- .../copy_and_paste/custom_paste_command.dart | 1 - .../copy_and_paste/paste_from_plain_text.dart | 28 ++++++++++++++ 3 files changed, 63 insertions(+), 3 deletions(-) diff --git a/frontend/appflowy_flutter/integration_test/document/document_copy_and_paste_test.dart b/frontend/appflowy_flutter/integration_test/document/document_copy_and_paste_test.dart index 42ed3f69e5..c5862c1957 100644 --- a/frontend/appflowy_flutter/integration_test/document/document_copy_and_paste_test.dart +++ b/frontend/appflowy_flutter/integration_test/document/document_copy_and_paste_test.dart @@ -195,11 +195,43 @@ void main() { // expect(node.attributes[ImageBlockKeys.url], isNotNull); // }); }); + + testWidgets( + 'format the selected text to href when pasting url if available', + (tester) async { + const text = 'appflowy'; + const url = 'https://appflowy.io'; + await tester.pasteContent( + plainText: url, + beforeTest: (editorState) async { + await tester.ime.insertText(text); + await tester.editor.updateSelection( + Selection.single( + path: [0], + startOffset: 0, + endOffset: text.length, + ), + ); + }, + (editorState) { + final node = editorState.getNodeAtPath([0])!; + expect(node.type, ParagraphBlockKeys.type); + expect(node.delta!.toJson(), [ + { + 'insert': text, + 'attributes': {'href': url} + } + ]); + }, + ); + }, + ); } extension on WidgetTester { Future pasteContent( void Function(EditorState editorState) test, { + Future Function(EditorState editorState)? beforeTest, String? plainText, String? html, (String, Uint8List?)? image, @@ -210,6 +242,8 @@ extension on WidgetTester { // create a new document await createNewPageWithName(); + await beforeTest?.call(editor.getCurrentEditorState()); + // mock the clipboard getIt().setData( ClipboardServiceData( @@ -227,7 +261,6 @@ extension on WidgetTester { ); await pumpAndSettle(); - final editorState = editor.getCurrentEditorState(); - test(editorState); + test(editor.getCurrentEditorState()); } } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/custom_paste_command.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/custom_paste_command.dart index f5dbf8c559..4b6d402dd0 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/custom_paste_command.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/custom_paste_command.dart @@ -53,7 +53,6 @@ CommandShortcutEventHandler _pasteCommandHandler = (editorState) { await editorState.deleteSelectionIfNeeded(); await editorState.pasteImage(image.$1, image.$2!); } else if (plainText != null && plainText.isNotEmpty) { - await editorState.deleteSelectionIfNeeded(); await editorState.pastePlainText(plainText); } }(); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_plain_text.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_plain_text.dart index e9dcde3e3d..7f31309fc6 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_plain_text.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_plain_text.dart @@ -7,6 +7,12 @@ RegExp _hrefRegex = RegExp( extension PasteFromPlainText on EditorState { Future pastePlainText(String plainText) async { + if (await pasteHtmlIfAvailable(plainText)) { + return; + } + + await deleteSelectionIfNeeded(); + final nodes = plainText .split('\n') .map( @@ -33,4 +39,26 @@ extension PasteFromPlainText on EditorState { await pasteMultiLineNodes(nodes.toList()); } } + + Future pasteHtmlIfAvailable(String plainText) async { + final selection = this.selection; + if (selection == null || + !selection.isSingle || + selection.isCollapsed || + !_hrefRegex.hasMatch(plainText)) { + return false; + } + + final node = getNodeAtPath(selection.start.path); + if (node == null) { + return false; + } + + final transaction = this.transaction; + transaction.formatText(node, selection.startIndex, selection.length, { + AppFlowyRichTextKeys.href: plainText, + }); + await apply(transaction); + return true; + } }