mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
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 <lucas.xu@appflowy.io>
This commit is contained in:
parent
4e863bc87f
commit
b88aed887a
@ -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<void> 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();
|
||||||
|
}
|
@ -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 'edit_document_test.dart' as document_edit_test;
|
||||||
import 'document_with_outline_block_test.dart' as document_with_outline_block;
|
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_copy_and_paste_test.dart' as document_copy_and_paste_test;
|
||||||
|
import 'document_codeblock_paste_test.dart' as document_codeblock_paste_test;
|
||||||
|
|
||||||
void startTesting() {
|
void startTesting() {
|
||||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
@ -25,4 +26,5 @@ void startTesting() {
|
|||||||
document_with_outline_block.main();
|
document_with_outline_block.main();
|
||||||
document_with_toggle_list_test.main();
|
document_with_toggle_list_test.main();
|
||||||
document_copy_and_paste_test.main();
|
document_copy_and_paste_test.main();
|
||||||
|
document_codeblock_paste_test.main();
|
||||||
}
|
}
|
||||||
|
@ -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/selectable_item_list_menu.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/string_extension.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/string_extension.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:appflowy_popover/appflowy_popover.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:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:highlight/highlight.dart' as highlight;
|
import 'package:highlight/highlight.dart' as highlight;
|
||||||
@ -40,7 +42,7 @@ Node codeBlockNode({
|
|||||||
|
|
||||||
// defining the callout block menu item for selection
|
// defining the callout block menu item for selection
|
||||||
SelectionMenuItem codeBlockItem = SelectionMenuItem.node(
|
SelectionMenuItem codeBlockItem = SelectionMenuItem.node(
|
||||||
name: 'Code Block',
|
name: LocaleKeys.document_selectionMenu_codeBlock.tr(),
|
||||||
iconData: Icons.abc,
|
iconData: Icons.abc,
|
||||||
keywords: ['code', 'codeblock'],
|
keywords: ['code', 'codeblock'],
|
||||||
nodeBuilder: (editorState, _) => codeBlockNode(),
|
nodeBuilder: (editorState, _) => codeBlockNode(),
|
||||||
|
@ -9,9 +9,10 @@ final List<CharacterShortcutEvent> codeBlockCharacterEvents = [
|
|||||||
|
|
||||||
final List<CommandShortcutEvent> codeBlockCommands = [
|
final List<CommandShortcutEvent> codeBlockCommands = [
|
||||||
insertNewParagraphNextToCodeBlockCommand,
|
insertNewParagraphNextToCodeBlockCommand,
|
||||||
|
pasteInCodeblock,
|
||||||
|
selectAllInCodeBlockCommand,
|
||||||
tabToInsertSpacesInCodeBlockCommand,
|
tabToInsertSpacesInCodeBlockCommand,
|
||||||
tabToDeleteSpacesInCodeBlockCommand,
|
tabToDeleteSpacesInCodeBlockCommand,
|
||||||
selectAllInCodeBlockCommand,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
/// press the enter key in code block to insert a new line in it.
|
/// press the enter key in code block to insert a new line in it.
|
||||||
@ -97,6 +98,18 @@ final CommandShortcutEvent selectAllInCodeBlockCommand = CommandShortcutEvent(
|
|||||||
handler: _selectAllInCodeBlockCommandHandler,
|
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 =
|
CharacterShortcutEventHandler _enterInCodeBlockCommandHandler =
|
||||||
(editorState) async {
|
(editorState) async {
|
||||||
final selection = editorState.selection;
|
final selection = editorState.selection;
|
||||||
@ -267,3 +280,49 @@ CommandShortcutEventHandler _selectAllInCodeBlockCommandHandler =
|
|||||||
|
|
||||||
return KeyEventResult.handled;
|
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;
|
||||||
|
};
|
||||||
|
@ -477,7 +477,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"selectionMenu": {
|
"selectionMenu": {
|
||||||
"outline": "Outline"
|
"outline": "Outline",
|
||||||
|
"codeBlock": "Code Block"
|
||||||
},
|
},
|
||||||
"plugins": {
|
"plugins": {
|
||||||
"referencedBoard": "Referenced Board",
|
"referencedBoard": "Referenced Board",
|
||||||
|
Loading…
Reference in New Issue
Block a user