From b88aed887a481bd86daaef09b6bc1c7f28011764 Mon Sep 17 00:00:00 2001 From: Mayur Mahajan <47064215+MayurSMahajan@users.noreply.github.com> Date: Wed, 30 Aug 2023 07:28:56 +0530 Subject: [PATCH] fix: paste multiple lines in codeblock (#3151) * fix: paste multiple lines in codeblock * fix: works with non collapsed selection * chore: localize code block * test: multiline paste in codeblock * chore: remove unused import * fix: only hanlde code block paste command if all the selected nodes are code block --------- Co-authored-by: Lucas.Xu --- .../document_codeblock_paste_test.dart | 60 ++++++++++++++++++ .../document/document_test_runner.dart | 2 + .../code_block/code_block_component.dart | 4 +- .../code_block/code_block_shortcut_event.dart | 61 ++++++++++++++++++- frontend/resources/translations/en.json | 3 +- 5 files changed, 127 insertions(+), 3 deletions(-) create mode 100644 frontend/appflowy_flutter/integration_test/document/document_codeblock_paste_test.dart diff --git a/frontend/appflowy_flutter/integration_test/document/document_codeblock_paste_test.dart b/frontend/appflowy_flutter/integration_test/document/document_codeblock_paste_test.dart new file mode 100644 index 0000000000..4a73bc4414 --- /dev/null +++ b/frontend/appflowy_flutter/integration_test/document/document_codeblock_paste_test.dart @@ -0,0 +1,60 @@ +import 'dart:io'; + +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import '../util/util.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('paste in codeblock', () { + testWidgets('paste multiple lines in codeblock', (tester) async { + await tester.initializeAppFlowy(); + await tester.tapGoButton(); + + // create a new document + await tester.createNewPageWithName(); + + // mock the clipboard + const lines = 3; + final text = List.generate(lines, (index) => 'line $index').join('\n'); + AppFlowyClipboard.mockSetData( + AppFlowyClipboardData( + text: text, + ), + ); + + await insertCodeBlockInDocument(tester); + + // paste the text + await tester.simulateKeyEvent( + LogicalKeyboardKey.keyV, + isControlPressed: Platform.isLinux || Platform.isWindows, + isMetaPressed: Platform.isMacOS, + ); + await tester.pumpAndSettle(); + + final editorState = tester.editor.getCurrentEditorState(); + expect(editorState.document.root.children.length, 1); + expect( + editorState.getNodeAtPath([0])!.delta!.toPlainText(), + text, + ); + }); + }); +} + +/// Inserts an codeBlock in the document +Future insertCodeBlockInDocument(WidgetTester tester) async { + // open the actions menu and insert the codeBlock + await tester.editor.showSlashMenu(); + await tester.editor.tapSlashMenuItemWithName( + LocaleKeys.document_selectionMenu_codeBlock.tr(), + ); + await tester.pumpAndSettle(); +} diff --git a/frontend/appflowy_flutter/integration_test/document/document_test_runner.dart b/frontend/appflowy_flutter/integration_test/document/document_test_runner.dart index 1d05ef55d0..30da907992 100644 --- a/frontend/appflowy_flutter/integration_test/document/document_test_runner.dart +++ b/frontend/appflowy_flutter/integration_test/document/document_test_runner.dart @@ -11,6 +11,7 @@ import 'document_with_toggle_list_test.dart' as document_with_toggle_list_test; import 'edit_document_test.dart' as document_edit_test; import 'document_with_outline_block_test.dart' as document_with_outline_block; import 'document_copy_and_paste_test.dart' as document_copy_and_paste_test; +import 'document_codeblock_paste_test.dart' as document_codeblock_paste_test; void startTesting() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); @@ -25,4 +26,5 @@ void startTesting() { document_with_outline_block.main(); document_with_toggle_list_test.main(); document_copy_and_paste_test.main(); + document_codeblock_paste_test.main(); } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_component.dart index 727606d0a0..56002c1815 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_component.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_component.dart @@ -1,7 +1,9 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/base/selectable_item_list_menu.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/base/string_extension.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:highlight/highlight.dart' as highlight; @@ -40,7 +42,7 @@ Node codeBlockNode({ // defining the callout block menu item for selection SelectionMenuItem codeBlockItem = SelectionMenuItem.node( - name: 'Code Block', + name: LocaleKeys.document_selectionMenu_codeBlock.tr(), iconData: Icons.abc, keywords: ['code', 'codeblock'], nodeBuilder: (editorState, _) => codeBlockNode(), diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_shortcut_event.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_shortcut_event.dart index 08cd956d68..1326886148 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_shortcut_event.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_shortcut_event.dart @@ -9,9 +9,10 @@ final List codeBlockCharacterEvents = [ final List codeBlockCommands = [ insertNewParagraphNextToCodeBlockCommand, + pasteInCodeblock, + selectAllInCodeBlockCommand, tabToInsertSpacesInCodeBlockCommand, tabToDeleteSpacesInCodeBlockCommand, - selectAllInCodeBlockCommand, ]; /// press the enter key in code block to insert a new line in it. @@ -97,6 +98,18 @@ final CommandShortcutEvent selectAllInCodeBlockCommand = CommandShortcutEvent( handler: _selectAllInCodeBlockCommandHandler, ); +/// ctrl + v to paste text in code block. +/// +/// - support +/// - desktop +/// - web +final CommandShortcutEvent pasteInCodeblock = CommandShortcutEvent( + key: 'paste in codeblock', + command: 'ctrl+v', + macOSCommand: 'cmd+v', + handler: _pasteInCodeBlock, +); + CharacterShortcutEventHandler _enterInCodeBlockCommandHandler = (editorState) async { final selection = editorState.selection; @@ -267,3 +280,49 @@ CommandShortcutEventHandler _selectAllInCodeBlockCommandHandler = return KeyEventResult.handled; }; + +CommandShortcutEventHandler _pasteInCodeBlock = (editorState) { + var selection = editorState.selection; + + if (selection == null) { + return KeyEventResult.ignored; + } + + if (editorState.getNodesInSelection(selection).length != 1) { + return KeyEventResult.ignored; + } + + final node = editorState.getNodeAtPath(selection.end.path); + if (node == null || node.type != CodeBlockKeys.type) { + return KeyEventResult.ignored; + } + + // delete the selection first. + if (!selection.isCollapsed) { + editorState.deleteSelection(selection); + } + + // fetch selection again. + selection = editorState.selection; + if (selection == null) { + return KeyEventResult.skipRemainingHandlers; + } + assert(selection.isCollapsed); + + () async { + final data = await AppFlowyClipboard.getData(); + final text = data.text; + if (text != null && text.isNotEmpty) { + final transaction = editorState.transaction + ..insertText( + node, + selection!.end.offset, + text, + ); + + await editorState.apply(transaction); + } + }(); + + return KeyEventResult.handled; +}; diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index 6a93a3e835..969d3f395f 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -477,7 +477,8 @@ } }, "selectionMenu": { - "outline": "Outline" + "outline": "Outline", + "codeBlock": "Code Block" }, "plugins": { "referencedBoard": "Referenced Board",