diff --git a/frontend/app_flowy/packages/flowy_editor/lib/src/service/internal_key_event_handlers/slash_handler.dart b/frontend/app_flowy/packages/flowy_editor/lib/src/service/internal_key_event_handlers/slash_handler.dart index 83f1b9e13a..093318813d 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/src/service/internal_key_event_handlers/slash_handler.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/src/service/internal_key_event_handlers/slash_handler.dart @@ -11,6 +11,9 @@ import 'package:flowy_editor/src/extensions/node_extensions.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +@visibleForTesting +List get popupListItems => _popupListItems; + final List _popupListItems = [ PopupListItem( text: 'Text', @@ -94,6 +97,14 @@ FlowyKeyEventHandler slashShortcutHandler = (editorState, event) { _editorState = editorState; WidgetsBinding.instance.addPostFrameCallback((_) { _selectionChangeBySlash = false; + + editorState.service.selectionService.currentSelection + .removeListener(clearPopupList); + editorState.service.selectionService.currentSelection + .addListener(clearPopupList); + + editorState.service.scrollService?.disable(); + showPopupList(context, editorState, selectionRects.first.bottomRight); }); @@ -115,23 +126,20 @@ void showPopupList( ); Overlay.of(context)?.insert(_popupListOverlay!); - - editorState.service.selectionService.currentSelection - .removeListener(clearPopupList); - editorState.service.selectionService.currentSelection - .addListener(clearPopupList); - - editorState.service.scrollService?.disable(); } void clearPopupList() { if (_popupListOverlay == null || _editorState == null) { return; } - final selection = - _editorState?.service.selectionService.currentSelection.value; - if (selection == null) { - return; + final isSelectionDisposed = + _editorState?.service.selectionServiceKey.currentState != null; + if (isSelectionDisposed) { + final selection = + _editorState?.service.selectionService.currentSelection.value; + if (selection == null) { + return; + } } if (_selectionChangeBySlash) { _selectionChangeBySlash = false; diff --git a/frontend/app_flowy/packages/flowy_editor/lib/src/service/internal_key_event_handlers/update_text_style_by_command_x_handler.dart b/frontend/app_flowy/packages/flowy_editor/lib/src/service/internal_key_event_handlers/update_text_style_by_command_x_handler.dart index 4bd9382e3a..4dab96e54a 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/src/service/internal_key_event_handlers/update_text_style_by_command_x_handler.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/src/service/internal_key_event_handlers/update_text_style_by_command_x_handler.dart @@ -3,9 +3,10 @@ import 'package:flutter/material.dart'; import 'package:flowy_editor/src/document/node.dart'; import 'package:flowy_editor/src/service/default_text_operations/format_rich_text_style.dart'; import 'package:flowy_editor/src/service/keyboard_service.dart'; +import 'package:flutter/services.dart'; FlowyKeyEventHandler updateTextStyleByCommandXHandler = (editorState, event) { - if (!event.isMetaPressed || event.character == null) { + if (!event.isMetaPressed) { return KeyEventResult.ignored; } @@ -17,26 +18,19 @@ FlowyKeyEventHandler updateTextStyleByCommandXHandler = (editorState, event) { return KeyEventResult.ignored; } - switch (event.character!) { - // bold - case 'B': - case 'b': - formatBold(editorState); - return KeyEventResult.handled; - case 'I': - case 'i': - formatItalic(editorState); - return KeyEventResult.handled; - case 'U': - case 'u': - formatUnderline(editorState); - return KeyEventResult.handled; - case 'S': - case 's': - formatStrikethrough(editorState); - return KeyEventResult.handled; - default: - break; + if (event.logicalKey == LogicalKeyboardKey.keyB) { + formatBold(editorState); + return KeyEventResult.handled; + } else if (event.logicalKey == LogicalKeyboardKey.keyI) { + formatItalic(editorState); + return KeyEventResult.handled; + } else if (event.logicalKey == LogicalKeyboardKey.keyU) { + formatUnderline(editorState); + return KeyEventResult.handled; + } else if (event.logicalKey == LogicalKeyboardKey.keyS && + event.isShiftPressed) { + formatStrikethrough(editorState); + return KeyEventResult.handled; } return KeyEventResult.ignored; diff --git a/frontend/app_flowy/packages/flowy_editor/test/infra/test_raw_key_event.dart b/frontend/app_flowy/packages/flowy_editor/test/infra/test_raw_key_event.dart index 78ed06383e..d3dac12677 100644 --- a/frontend/app_flowy/packages/flowy_editor/test/infra/test_raw_key_event.dart +++ b/frontend/app_flowy/packages/flowy_editor/test/infra/test_raw_key_event.dart @@ -91,12 +91,27 @@ extension on LogicalKeyboardKey { if (this == LogicalKeyboardKey.pageUp) { return PhysicalKeyboardKey.pageUp; } - if (this == LogicalKeyboardKey.keyZ) { - return PhysicalKeyboardKey.keyZ; + if (this == LogicalKeyboardKey.slash) { + return PhysicalKeyboardKey.slash; + } + if (this == LogicalKeyboardKey.arrowDown) { + return PhysicalKeyboardKey.arrowDown; } if (this == LogicalKeyboardKey.keyA) { return PhysicalKeyboardKey.keyA; } + if (this == LogicalKeyboardKey.keyB) { + return PhysicalKeyboardKey.keyB; + } + if (this == LogicalKeyboardKey.keyI) { + return PhysicalKeyboardKey.keyI; + } + if (this == LogicalKeyboardKey.keyS) { + return PhysicalKeyboardKey.keyS; + } + if (this == LogicalKeyboardKey.keyU) { + return PhysicalKeyboardKey.keyU; + } throw UnimplementedError(); } } diff --git a/frontend/app_flowy/packages/flowy_editor/test/service/internal_key_event_handlers/slash_handler_test.dart b/frontend/app_flowy/packages/flowy_editor/test/service/internal_key_event_handlers/slash_handler_test.dart new file mode 100644 index 0000000000..a8d1cc36f1 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_editor/test/service/internal_key_event_handlers/slash_handler_test.dart @@ -0,0 +1,39 @@ +import 'package:flowy_editor/flowy_editor.dart'; +import 'package:flowy_editor/src/service/internal_key_event_handlers/slash_handler.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import '../../infra/test_editor.dart'; + +void main() async { + setUpAll(() { + TestWidgetsFlutterBinding.ensureInitialized(); + }); + + group('slash_handler.dart', () { + testWidgets('Presses / to trigger popup list ', (tester) async { + const text = 'Welcome to Appflowy 😁'; + const lines = 3; + final editor = tester.editor; + for (var i = 0; i < lines; i++) { + editor.insertTextNode(text); + } + await editor.startTesting(); + await editor.updateSelection(Selection.single(path: [1], startOffset: 0)); + await editor.pressLogicKey(LogicalKeyboardKey.slash); + + await tester.pumpAndSettle(const Duration(milliseconds: 1000)); + + expect(find.byType(PopupListWidget, skipOffstage: false), findsOneWidget); + + for (final item in popupListItems) { + expect(find.byWidget(item.icon), findsOneWidget); + } + + await editor.updateSelection(Selection.single(path: [1], startOffset: 0)); + + await tester.pumpAndSettle(const Duration(milliseconds: 200)); + + expect(find.byType(PopupListWidget, skipOffstage: false), findsNothing); + }); + }); +} diff --git a/frontend/app_flowy/packages/flowy_editor/test/service/internal_key_event_handlers/update_text_style_by_command_x_handler_test.dart b/frontend/app_flowy/packages/flowy_editor/test/service/internal_key_event_handlers/update_text_style_by_command_x_handler_test.dart new file mode 100644 index 0000000000..3d4e1aff3c --- /dev/null +++ b/frontend/app_flowy/packages/flowy_editor/test/service/internal_key_event_handlers/update_text_style_by_command_x_handler_test.dart @@ -0,0 +1,87 @@ +import 'package:flowy_editor/flowy_editor.dart'; +import 'package:flowy_editor/src/render/rich_text/rich_text_style.dart'; +import 'package:flowy_editor/src/extensions/text_node_extensions.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import '../../infra/test_editor.dart'; + +void main() async { + setUpAll(() { + TestWidgetsFlutterBinding.ensureInitialized(); + }); + + group('update_text_style_by_command_x_handler.dart', () { + testWidgets('Presses Command + B to update text style', (tester) async { + await _testUpdateTextStyleByCommandX( + tester, + StyleKey.bold, + LogicalKeyboardKey.keyB, + ); + }); + + testWidgets('Presses Command + I to update text style', (tester) async { + await _testUpdateTextStyleByCommandX( + tester, + StyleKey.bold, + LogicalKeyboardKey.keyI, + ); + }); + + testWidgets('Presses Command + U to update text style', (tester) async { + await _testUpdateTextStyleByCommandX( + tester, + StyleKey.bold, + LogicalKeyboardKey.keyU, + ); + }); + + testWidgets('Presses Command + S to update text style', (tester) async { + await _testUpdateTextStyleByCommandX( + tester, + StyleKey.bold, + LogicalKeyboardKey.keyS, + ); + }); + }); +} + +Future _testUpdateTextStyleByCommandX( + WidgetTester tester, String matchStyle, LogicalKeyboardKey key) async { + const text = 'Welcome to Appflowy 😁'; + final editor = tester.editor + ..insertTextNode(text) + ..insertTextNode(text) + ..insertTextNode(text); + await editor.startTesting(); + + var selection = + Selection.single(path: [1], startOffset: 2, endOffset: text.length - 2); + await editor.updateSelection(selection); + await editor.pressLogicKey( + key, + isShiftPressed: key == LogicalKeyboardKey.keyS, + isMetaPressed: true, + ); + var textNode = editor.nodeAtPath([1]) as TextNode; + expect(textNode.allSatisfyInSelection(matchStyle, selection), true); + + selection = + Selection.single(path: [1], startOffset: 0, endOffset: text.length); + await editor.updateSelection(selection); + await editor.pressLogicKey( + key, + isShiftPressed: key == LogicalKeyboardKey.keyS, + isMetaPressed: true, + ); + textNode = editor.nodeAtPath([1]) as TextNode; + expect(textNode.allSatisfyInSelection(matchStyle, selection), true); + + await editor.updateSelection(selection); + await editor.pressLogicKey( + key, + isShiftPressed: key == LogicalKeyboardKey.keyS, + isMetaPressed: true, + ); + textNode = editor.nodeAtPath([1]) as TextNode; + expect(textNode.allSatisfyInSelection(matchStyle, selection), false); +}