diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/markdown_syntax_to_styled_text.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/markdown_syntax_to_styled_text.dart index 94caff83b1..dc28df524e 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/markdown_syntax_to_styled_text.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/markdown_syntax_to_styled_text.dart @@ -188,6 +188,26 @@ ShortcutEventHandler doubleTildeToStrikethrough = (editorState, event) { return KeyEventResult.handled; }; +ShortcutEventHandler greaterToBlockquote = (editorState, event) { + final selectionService = editorState.service.selectionService; + final selection = selectionService.currentSelection.value; + final textNodes = selectionService.currentSelectedNodes.whereType(); + if (selection == null || !selection.isSingle || textNodes.length != 1) { + return KeyEventResult.ignored; + } + + final textNode = textNodes.first; + final text = textNode.toPlainText(); + + //only convert > at the start of a paragraph + if (selection.startIndex != 0) { + return KeyEventResult.ignored; + } + formatQuote(editorState); + + return KeyEventResult.handled; +}; + ShortcutEventHandler markdownLinkOrImageHandler = (editorState, event) { final selectionService = editorState.service.selectionService; final selection = selectionService.currentSelection.value; diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/shortcut_event/built_in_shortcut_events.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/shortcut_event/built_in_shortcut_events.dart index c960558b24..f4093152f1 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/shortcut_event/built_in_shortcut_events.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/shortcut_event/built_in_shortcut_events.dart @@ -285,6 +285,11 @@ List builtInShortcutEvents = [ command: 'shift+tilde', handler: doubleTildeToStrikethrough, ), + ShortcutEvent( + key: 'Greater to blockquote', + command: 'shift+greater', + handler: greaterToBlockquote, + ), ShortcutEvent( key: 'Markdown link or image', command: 'shift+parenthesis right', diff --git a/frontend/app_flowy/packages/appflowy_editor/test/infra/test_raw_key_event.dart b/frontend/app_flowy/packages/appflowy_editor/test/infra/test_raw_key_event.dart index 5102407e1b..5730b9cb15 100644 --- a/frontend/app_flowy/packages/appflowy_editor/test/infra/test_raw_key_event.dart +++ b/frontend/app_flowy/packages/appflowy_editor/test/infra/test_raw_key_event.dart @@ -148,6 +148,9 @@ extension on LogicalKeyboardKey { if (this == LogicalKeyboardKey.tilde) { return PhysicalKeyboardKey.backquote; } + if (this == LogicalKeyboardKey.greater) { + return PhysicalKeyboardKey.intlBackslash; + } throw UnimplementedError(); } } diff --git a/frontend/app_flowy/packages/appflowy_editor/test/service/internal_key_event_handlers/markdown_syntax_to_styled_text_test.dart b/frontend/app_flowy/packages/appflowy_editor/test/service/internal_key_event_handlers/markdown_syntax_to_styled_text_test.dart index 662c7982b4..bdf4704a87 100644 --- a/frontend/app_flowy/packages/appflowy_editor/test/service/internal_key_event_handlers/markdown_syntax_to_styled_text_test.dart +++ b/frontend/app_flowy/packages/appflowy_editor/test/service/internal_key_event_handlers/markdown_syntax_to_styled_text_test.dart @@ -256,5 +256,76 @@ void main() async { expect(textNode.toPlainText(), text); }); }); + + group('convert geater to blockquote', () { + Future insertGreater( + EditorWidgetTester editor, { + int repeat = 1, + }) async { + for (var i = 0; i < repeat; i++) { + await editor.pressLogicKey( + LogicalKeyboardKey.greater, + isShiftPressed: true, + ); + } + } + + testWidgets('>AppFlowy to blockquote AppFlowy', (tester) async { + const text = 'AppFlowy'; + final editor = tester.editor..insertTextNode(''); + await editor.startTesting(); + await editor.updateSelection( + Selection.single(path: [0], startOffset: 0), + ); + await insertGreater(editor); + final textNode = editor.nodeAtPath([0]) as TextNode; + for (var i = 0; i < text.length; i++) { + await editor.insertText(textNode, text[i], i); + } + + final isQuote = textNode.subtype == BuiltInAttributeKey.quote; + expect(isQuote, true); + expect(textNode.toPlainText(), 'AppFlowy'); + }); + + testWidgets('AppFlowy> nothing changes', (tester) async { + const text = 'AppFlowy'; + final editor = tester.editor..insertTextNode(''); + await editor.startTesting(); + await editor.updateSelection( + Selection.single(path: [0], startOffset: 0), + ); + final textNode = editor.nodeAtPath([0]) as TextNode; + for (var i = 0; i < text.length; i++) { + await editor.insertText(textNode, text[i], i); + } + await insertGreater(editor); + + final isQuote = textNode.subtype == BuiltInAttributeKey.quote; + expect(isQuote, false); + expect(textNode.toPlainText(), text); + }); + + testWidgets('> in front of text to blockquote', (tester) async { + const text = 'AppFlowy'; + final editor = tester.editor..insertTextNode(''); + await editor.startTesting(); + await editor.updateSelection( + Selection.single(path: [0], startOffset: 0), + ); + final textNode = editor.nodeAtPath([0]) as TextNode; + for (var i = 0; i < text.length; i++) { + await editor.insertText(textNode, text[i], i); + } + await editor.updateSelection( + Selection.single(path: [0], startOffset: 0), + ); + await insertGreater(editor); + + final isQuote = textNode.subtype == BuiltInAttributeKey.quote; + expect(isQuote, true); + expect(textNode.toPlainText(), text); + }); + }); }); }