feat: keyboard service improvement

This commit is contained in:
Lucas.Xu 2022-09-05 22:16:28 +08:00
parent a3d2cef40a
commit 43a0a02328
26 changed files with 1085 additions and 130 deletions

View File

@ -1,6 +1,7 @@
import 'dart:convert';
import 'dart:io';
import 'package:example/plugin/underscore_to_italic_key_event_handler.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@ -95,6 +96,9 @@ class _MyHomePageState extends State<MyHomePage> {
child: AppFlowyEditor(
editorState: _editorState,
editorStyle: const EditorStyle.defaultStyle(),
keyEventHandlers: [
underscoreToItalicEvent,
],
),
);
} else {

View File

@ -2,7 +2,13 @@ import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
AppFlowyKeyEventHandler underscoreToItalicHandler = (editorState, event) {
ShortcutEvent underscoreToItalicEvent = ShortcutEvent(
key: 'Underscore to italic',
command: 'shift+underscore',
handler: _underscoreToItalicHandler,
);
ShortcutEventHandler _underscoreToItalicHandler = (editorState, event) {
// Since we only need to handler the input of `underscore`.
// All inputs except `underscore` will be ignored directly.
if (event.logicalKey != LogicalKeyboardKey.underscore) {

View File

@ -23,3 +23,6 @@ export 'src/service/scroll_service.dart';
export 'src/service/toolbar_service.dart';
export 'src/service/keyboard_service.dart';
export 'src/service/input_service.dart';
export 'src/service/shortcut_event/keybinding.dart';
export 'src/service/shortcut_event/shortcut_event.dart';
export 'src/service/shortcut_event/shortcut_event_handler.dart';

View File

@ -1,7 +1,8 @@
import 'package:appflowy_editor/src/render/image/image_node_builder.dart';
import 'package:appflowy_editor/src/render/selection_menu/selection_menu_widget.dart';
import 'package:appflowy_editor/src/render/style/editor_style.dart';
import 'package:appflowy_editor/src/service/internal_key_event_handlers/default_key_event_handlers.dart';
import 'package:appflowy_editor/src/service/shortcut_event/built_in_shortcut_events.dart';
import 'package:appflowy_editor/src/service/shortcut_event/shortcut_event.dart';
import 'package:flutter/material.dart';
import 'package:appflowy_editor/src/editor_state.dart';
@ -46,7 +47,7 @@ class AppFlowyEditor extends StatefulWidget {
final NodeWidgetBuilders customBuilders;
/// Keyboard event handlers.
final List<AppFlowyKeyEventHandler> keyEventHandlers;
final List<ShortcutEvent> keyEventHandlers;
final List<SelectionMenuItem> selectionMenuItems;
@ -93,8 +94,8 @@ class _AppFlowyEditorState extends State<AppFlowyEditor> {
editorState: editorState,
child: AppFlowyKeyboard(
key: editorState.service.keyboardServiceKey,
handlers: [
...defaultKeyEventHandlers,
shortcutEvents: [
...builtInShortcutEvents,
...widget.keyEventHandlers,
],
editorState: editorState,

View File

@ -3,7 +3,7 @@ import 'package:appflowy_editor/src/extensions/node_extensions.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
AppFlowyKeyEventHandler arrowKeysHandler = (editorState, event) {
ShortcutEventHandler arrowKeysHandler = (editorState, event) {
if (!_arrowKeys.contains(event.logicalKey)) {
return KeyEventResult.ignored;
}

View File

@ -4,6 +4,18 @@ import 'package:flutter/services.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
// Handle delete text.
ShortcutEventHandler deleteTextHandler = (editorState, event) {
if (event.logicalKey == LogicalKeyboardKey.backspace) {
return _handleBackspace(editorState, event);
}
if (event.logicalKey == LogicalKeyboardKey.delete) {
return _handleDelete(editorState, event);
}
return KeyEventResult.ignored;
};
KeyEventResult _handleBackspace(EditorState editorState, RawKeyEvent event) {
var selection = editorState.service.selectionService.currentSelection.value;
if (selection == null) {
@ -159,15 +171,3 @@ void _deleteTextNodes(TransactionBuilder transactionBuilder,
secondOffset: selection.end.offset,
);
}
// Handle delete text.
AppFlowyKeyEventHandler deleteTextHandler = (editorState, event) {
if (event.logicalKey == LogicalKeyboardKey.backspace) {
return _handleBackspace(editorState, event);
}
if (event.logicalKey == LogicalKeyboardKey.delete) {
return _handleDelete(editorState, event);
}
return KeyEventResult.ignored;
};

View File

@ -1,6 +1,7 @@
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_editor/src/infra/html_converter.dart';
import 'package:appflowy_editor/src/document/node_iterator.dart';
import 'package:appflowy_editor/src/service/shortcut_event/shortcut_event_handler.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:rich_clipboard/rich_clipboard.dart';
@ -303,7 +304,7 @@ _deleteSelectedContent(EditorState editorState) {
tb.commit();
}
AppFlowyKeyEventHandler copyPasteKeysHandler = (editorState, event) {
ShortcutEventHandler copyPasteKeysHandler = (editorState, event) {
if (event.isMetaPressed && event.logicalKey == LogicalKeyboardKey.keyC) {
_handleCopy(editorState);
return KeyEventResult.handled;

View File

@ -1,25 +0,0 @@
import 'package:appflowy_editor/src/service/internal_key_event_handlers/copy_paste_handler.dart';
import 'package:appflowy_editor/src/service/internal_key_event_handlers/backspace_handler.dart';
import 'package:appflowy_editor/src/service/internal_key_event_handlers/enter_without_shift_in_text_node_handler.dart';
import 'package:appflowy_editor/src/service/internal_key_event_handlers/arrow_keys_handler.dart';
import 'package:appflowy_editor/src/service/internal_key_event_handlers/redo_undo_handler.dart';
import 'package:appflowy_editor/src/service/internal_key_event_handlers/slash_handler.dart';
import 'package:appflowy_editor/src/service/internal_key_event_handlers/update_text_style_by_command_x_handler.dart';
import 'package:appflowy_editor/src/service/internal_key_event_handlers/whitespace_handler.dart';
import 'package:appflowy_editor/src/service/internal_key_event_handlers/select_all_handler.dart';
import 'package:appflowy_editor/src/service/internal_key_event_handlers/page_up_down_handler.dart';
import 'package:appflowy_editor/src/service/keyboard_service.dart';
List<AppFlowyKeyEventHandler> defaultKeyEventHandlers = [
deleteTextHandler,
slashShortcutHandler,
// arrowKeysHandler,
arrowKeysHandler,
copyPasteKeysHandler,
redoUndoKeysHandler,
enterWithoutShiftInTextNodesHandler,
updateTextStyleByCommandXHandler,
whiteSpaceHandler,
selectAllHandler,
pageUpDownHandler,
];

View File

@ -8,7 +8,7 @@ import 'package:appflowy_editor/src/document/selection.dart';
import 'package:appflowy_editor/src/extensions/path_extensions.dart';
import 'package:appflowy_editor/src/operation/transaction_builder.dart';
import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart';
import 'package:appflowy_editor/src/service/keyboard_service.dart';
import 'package:appflowy_editor/src/service/shortcut_event/shortcut_event_handler.dart';
/// Handle some cases where enter is pressed and shift is not pressed.
///
@ -18,7 +18,7 @@ import 'package:appflowy_editor/src/service/keyboard_service.dart';
/// 2. Single selection and the selected node is [TextNode]
/// 2.1 split the node into two nodes with style
/// 2.2 or insert a empty text node before.
AppFlowyKeyEventHandler enterWithoutShiftInTextNodesHandler =
ShortcutEventHandler enterWithoutShiftInTextNodesHandler =
(editorState, event) {
if (event.logicalKey != LogicalKeyboardKey.enter || event.isShiftPressed) {
return KeyEventResult.ignored;

View File

@ -1,4 +1,5 @@
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_editor/src/service/shortcut_event/shortcut_event_handler.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@ -103,7 +104,7 @@ KeyEventResult _handleShiftKey(EditorState editorState, RawKeyEvent event) {
return KeyEventResult.ignored;
}
AppFlowyKeyEventHandler arrowKeysHandler = (editorState, event) {
ShortcutEventHandler arrowKeysHandler = (editorState, event) {
if (event.isShiftPressed) {
return _handleShiftKey(editorState, event);
}

View File

@ -1,8 +1,8 @@
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:appflowy_editor/src/service/shortcut_event/shortcut_event_handler.dart';
AppFlowyKeyEventHandler pageUpDownHandler = (editorState, event) {
ShortcutEventHandler pageUpDownHandler = (editorState, event) {
if (event.logicalKey == LogicalKeyboardKey.pageUp) {
final scrollHeight = editorState.service.scrollService?.onePageHeight;
final scrollService = editorState.service.scrollService;

View File

@ -1,8 +1,8 @@
import 'package:appflowy_editor/src/service/keyboard_service.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:appflowy_editor/src/service/shortcut_event/shortcut_event_handler.dart';
AppFlowyKeyEventHandler redoUndoKeysHandler = (editorState, event) {
ShortcutEventHandler redoUndoKeysHandler = (editorState, event) {
if (event.isMetaPressed && event.logicalKey == LogicalKeyboardKey.keyZ) {
if (event.isShiftPressed) {
editorState.undoManager.redo();

View File

@ -1,4 +1,5 @@
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_editor/src/service/shortcut_event/shortcut_event_handler.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@ -18,7 +19,7 @@ KeyEventResult _selectAll(EditorState editorState) {
return KeyEventResult.handled;
}
AppFlowyKeyEventHandler selectAllHandler = (editorState, event) {
ShortcutEventHandler selectAllHandler = (editorState, event) {
if (event.isMetaPressed && event.logicalKey == LogicalKeyboardKey.keyA) {
return _selectAll(editorState);
}

View File

@ -1,13 +1,13 @@
import 'package:appflowy_editor/src/document/node.dart';
import 'package:appflowy_editor/src/operation/transaction_builder.dart';
import 'package:appflowy_editor/src/render/selection_menu/selection_menu_service.dart';
import 'package:appflowy_editor/src/service/keyboard_service.dart';
import 'package:appflowy_editor/src/extensions/node_extensions.dart';
import 'package:appflowy_editor/src/service/shortcut_event/shortcut_event_handler.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
SelectionMenuService? _selectionMenuService;
AppFlowyKeyEventHandler slashShortcutHandler = (editorState, event) {
ShortcutEventHandler slashShortcutHandler = (editorState, event) {
if (event.logicalKey != LogicalKeyboardKey.slash) {
return KeyEventResult.ignored;
}

View File

@ -1,12 +1,12 @@
import 'package:appflowy_editor/src/service/shortcut_event/shortcut_event_handler.dart';
import 'package:flutter/material.dart';
import 'package:appflowy_editor/src/document/node.dart';
import 'package:appflowy_editor/src/service/default_text_operations/format_rich_text_style.dart';
import 'package:appflowy_editor/src/service/keyboard_service.dart';
import 'package:flutter/services.dart';
AppFlowyKeyEventHandler updateTextStyleByCommandXHandler =
(editorState, event) {
ShortcutEventHandler updateTextStyleByCommandXHandler = (editorState, event) {
if (!event.isMetaPressed) {
return KeyEventResult.ignored;
}

View File

@ -1,3 +1,4 @@
import 'package:appflowy_editor/src/service/shortcut_event/shortcut_event_handler.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@ -7,7 +8,6 @@ import 'package:appflowy_editor/src/document/selection.dart';
import 'package:appflowy_editor/src/editor_state.dart';
import 'package:appflowy_editor/src/operation/transaction_builder.dart';
import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart';
import 'package:appflowy_editor/src/service/keyboard_service.dart';
@visibleForTesting
List<String> get checkboxListSymbols => _checkboxListSymbols;
@ -20,7 +20,7 @@ const _bulletedListSymbols = ['*', '-'];
const _checkboxListSymbols = ['[x]', '-[x]'];
const _unCheckboxListSymbols = ['[]', '-[]'];
AppFlowyKeyEventHandler whiteSpaceHandler = (editorState, event) {
ShortcutEventHandler whiteSpaceHandler = (editorState, event) {
if (event.logicalKey != LogicalKeyboardKey.space) {
return KeyEventResult.ignored;
}

View File

@ -22,6 +22,9 @@ abstract class AppFlowyKeyboardService {
/// Processes shortcut key input.
KeyEventResult onKey(RawKeyEvent event);
/// Gets the shortcut events
List<ShortcutEvent> get shortcutEvents;
/// Enables shortcuts service.
void enable();
@ -35,23 +38,18 @@ abstract class AppFlowyKeyboardService {
void disable();
}
typedef AppFlowyKeyEventHandler = KeyEventResult Function(
EditorState editorState,
RawKeyEvent event,
);
/// Process keyboard events
class AppFlowyKeyboard extends StatefulWidget {
const AppFlowyKeyboard({
Key? key,
required this.handlers,
required this.shortcutEvents,
required this.editorState,
required this.child,
}) : super(key: key);
final EditorState editorState;
final Widget child;
final List<AppFlowyKeyEventHandler> handlers;
final List<ShortcutEvent> shortcutEvents;
@override
State<AppFlowyKeyboard> createState() => _AppFlowyKeyboardState();
@ -63,6 +61,10 @@ class _AppFlowyKeyboardState extends State<AppFlowyKeyboard>
bool isFocus = true;
@override
// TODO: implement shortcutEvents
List<ShortcutEvent> get shortcutEvents => widget.shortcutEvents;
@override
Widget build(BuildContext context) {
return Focus(
@ -111,16 +113,14 @@ class _AppFlowyKeyboardState extends State<AppFlowyKeyboard>
return KeyEventResult.ignored;
}
for (final handler in widget.handlers) {
KeyEventResult result = handler(widget.editorState, event);
switch (result) {
case KeyEventResult.handled:
// TODO: use cache to optimize the searching time.
for (final shortcutEvent in widget.shortcutEvents) {
if (shortcutEvent.keybindings.containsKeyEvent(event)) {
final result = shortcutEvent.handler(widget.editorState, event);
if (result == KeyEventResult.handled) {
return KeyEventResult.handled;
case KeyEventResult.skipRemainingHandlers:
return KeyEventResult.skipRemainingHandlers;
case KeyEventResult.ignored:
continue;
}
continue;
}
}
@ -139,3 +139,15 @@ class _AppFlowyKeyboardState extends State<AppFlowyKeyboard>
return onKey(event);
}
}
extension on RawKeyEvent {
Keybinding get toKeybinding {
return Keybinding(
isAltPressed: isAltPressed,
isControlPressed: isControlPressed,
isMetaPressed: isMetaPressed,
isShiftPressed: isShiftPressed,
keyLabel: logicalKey.keyLabel,
);
}
}

View File

@ -0,0 +1,121 @@
// List<>
import 'package:appflowy_editor/src/service/internal_key_event_handlers/arrow_keys_handler.dart';
import 'package:appflowy_editor/src/service/internal_key_event_handlers/backspace_handler.dart';
import 'package:appflowy_editor/src/service/internal_key_event_handlers/copy_paste_handler.dart';
import 'package:appflowy_editor/src/service/internal_key_event_handlers/enter_without_shift_in_text_node_handler.dart';
import 'package:appflowy_editor/src/service/internal_key_event_handlers/page_up_down_handler.dart';
import 'package:appflowy_editor/src/service/internal_key_event_handlers/redo_undo_handler.dart';
import 'package:appflowy_editor/src/service/internal_key_event_handlers/select_all_handler.dart';
import 'package:appflowy_editor/src/service/internal_key_event_handlers/slash_handler.dart';
import 'package:appflowy_editor/src/service/internal_key_event_handlers/update_text_style_by_command_x_handler.dart';
import 'package:appflowy_editor/src/service/internal_key_event_handlers/whitespace_handler.dart';
import 'package:appflowy_editor/src/service/shortcut_event/shortcut_event.dart';
//
List<ShortcutEvent> builtInShortcutEvents = [
ShortcutEvent(
key: 'Move cursor up',
command: 'arrow up',
handler: arrowKeysHandler,
),
ShortcutEvent(
key: 'Move cursor down',
command: 'arrow down',
handler: arrowKeysHandler,
),
ShortcutEvent(
key: 'Move cursor left',
command: 'arrow left',
handler: arrowKeysHandler,
),
ShortcutEvent(
key: 'Move cursor right',
command: 'arrow right',
handler: arrowKeysHandler,
),
// TODO: split the keys.
ShortcutEvent(
key: 'Shift + Arrow Keys',
command:
'shift+arrow up,shift+arrow down,shift+arrow left,shift+arrow right',
handler: arrowKeysHandler,
),
ShortcutEvent(
key: 'Control + Arrow Keys',
command: 'meta+arrow up,meta+arrow down,meta+arrow left,meta+arrow right',
windowsCommand:
'ctrl+arrow up,ctrl+arrow down,ctrl+arrow left,ctrl+arrow right',
macOSCommand: 'cmd+arrow up,cmd+arrow down,cmd+arrow left,cmd+arrow right',
handler: arrowKeysHandler,
),
ShortcutEvent(
key: 'Meta + Shift + Arrow Keys',
command:
'meta+shift+arrow up,meta+shift+arrow down,meta+shift+arrow left,meta+shift+arrow right',
windowsCommand:
'ctrl+shift+arrow up,ctrl+shift+arrow down,ctrl+shift+arrow left,ctrl+shift+arrow right',
macOSCommand:
'cmd+shift+arrow up,cmd+shift+arrow down,cmd+shift+arrow left,cmd+shift+arrow right',
handler: arrowKeysHandler,
),
ShortcutEvent(
key: 'Meta + Shift + Arrow Keys',
command:
'meta+shift+arrow up,meta+shift+arrow down,meta+shift+arrow left,meta+shift+arrow right',
windowsCommand:
'ctrl+shift+arrow up,ctrl+shift+arrow down,ctrl+shift+arrow left,ctrl+shift+arrow right',
macOSCommand:
'cmd+shift+arrow up,cmd+shift+arrow down,cmd+shift+arrow left,cmd+shift+arrow right',
handler: arrowKeysHandler,
),
ShortcutEvent(
key: 'Delete Text',
command: 'delete,backspace',
handler: deleteTextHandler,
),
ShortcutEvent(
key: 'selection menu',
command: 'slash',
handler: slashShortcutHandler,
),
ShortcutEvent(
key: 'copy / paste',
command: 'meta+c,meta+v',
windowsCommand: 'ctrl+c,ctrl+v',
handler: copyPasteKeysHandler,
),
ShortcutEvent(
key: 'redo / undo',
command: 'meta+z,meta+meta+shift+z',
windowsCommand: 'ctrl+z,meta+ctrl+shift+z',
handler: redoUndoKeysHandler,
),
ShortcutEvent(
key: 'enter',
command: 'enter',
handler: enterWithoutShiftInTextNodesHandler,
),
ShortcutEvent(
key: 'update text style',
command: 'meta+b,meta+i,meta+u,meta+shift+s,meta+shift+h,meta+k',
windowsCommand: 'ctrl+b,ctrl+i,ctrl+u,ctrl+shift+s,ctrl+shift+h,ctrl+k',
handler: updateTextStyleByCommandXHandler,
),
ShortcutEvent(
key: 'markdown',
command: 'space',
handler: whiteSpaceHandler,
),
ShortcutEvent(
key: 'select all',
command: 'meta+a',
windowsCommand: 'ctrl+a',
handler: selectAllHandler,
),
ShortcutEvent(
key: 'page up / page down',
command: 'page up,page down',
handler: pageUpDownHandler,
),
];

View File

@ -0,0 +1,451 @@
/// Keyboard key to keycode mapping table
///
/// Copy from flutter project, keyboard_key.dart.
///
Map<String, int> keyToCodeMapping = <String, int>{
'Space': 0x00000000020,
'Exclamation': 0x00000000021,
'Quote': 0x00000000022,
'Number Sign': 0x00000000023,
'Dollar': 0x00000000024,
'Percent': 0x00000000025,
'Ampersand': 0x00000000026,
'Quote Single': 0x00000000027,
'Parenthesis Left': 0x00000000028,
'Parenthesis Right': 0x00000000029,
'Asterisk': 0x0000000002a,
'Add': 0x0000000002b,
'Comma': 0x0000000002c,
'Minus': 0x0000000002d,
'Period': 0x0000000002e,
'Slash': 0x0000000002f,
'Digit 0': 0x00000000030,
'Digit 1': 0x00000000031,
'Digit 2': 0x00000000032,
'Digit 3': 0x00000000033,
'Digit 4': 0x00000000034,
'Digit 5': 0x00000000035,
'Digit 6': 0x00000000036,
'Digit 7': 0x00000000037,
'Digit 8': 0x00000000038,
'Digit 9': 0x00000000039,
'Colon': 0x0000000003a,
'Semicolon': 0x0000000003b,
'Less': 0x0000000003c,
'Equal': 0x0000000003d,
'Greater': 0x0000000003e,
'Question': 0x0000000003f,
'At': 0x00000000040,
'Bracket Left': 0x0000000005b,
'Backslash': 0x0000000005c,
'Bracket Right': 0x0000000005d,
'Caret': 0x0000000005e,
'Underscore': 0x0000000005f,
'Backquote': 0x00000000060,
'A': 0x00000000061,
'B': 0x00000000062,
'C': 0x00000000063,
'D': 0x00000000064,
'E': 0x00000000065,
'F': 0x00000000066,
'G': 0x00000000067,
'H': 0x00000000068,
'I': 0x00000000069,
'J': 0x0000000006a,
'K': 0x0000000006b,
'L': 0x0000000006c,
'M': 0x0000000006d,
'N': 0x0000000006e,
'O': 0x0000000006f,
'P': 0x00000000070,
'Q': 0x00000000071,
'R': 0x00000000072,
'S': 0x00000000073,
'T': 0x00000000074,
'U': 0x00000000075,
'V': 0x00000000076,
'W': 0x00000000077,
'X': 0x00000000078,
'Y': 0x00000000079,
'Z': 0x0000000007a,
'Brace Left': 0x0000000007b,
'Bar': 0x0000000007c,
'Brace Right': 0x0000000007d,
'Tilde': 0x0000000007e,
'Unidentified': 0x00100000001,
'Backspace': 0x00100000008,
'Tab': 0x00100000009,
'Enter': 0x0010000000d,
'Escape': 0x0010000001b,
'Delete': 0x0010000007f,
'Accel': 0x00100000101,
'Alt Graph': 0x00100000103,
'Caps Lock': 0x00100000104,
'Fn': 0x00100000106,
'Fn Lock': 0x00100000107,
'Hyper': 0x00100000108,
'Num Lock': 0x0010000010a,
'Scroll Lock': 0x0010000010c,
'Super': 0x0010000010e,
'Symbol': 0x0010000010f,
'Symbol Lock': 0x00100000110,
'Shift Level 5': 0x00100000111,
'Arrow Down': 0x00100000301,
'Arrow Left': 0x00100000302,
'Arrow Right': 0x00100000303,
'Arrow Up': 0x00100000304,
'End': 0x00100000305,
'Home': 0x00100000306,
'Page Down': 0x00100000307,
'Page Up': 0x00100000308,
'Clear': 0x00100000401,
'Copy': 0x00100000402,
'Cr Sel': 0x00100000403,
'Cut': 0x00100000404,
'Erase Eof': 0x00100000405,
'Ex Sel': 0x00100000406,
'Insert': 0x00100000407,
'Paste': 0x00100000408,
'Redo': 0x00100000409,
'Undo': 0x0010000040a,
'Accept': 0x00100000501,
'Again': 0x00100000502,
'Attn': 0x00100000503,
'Cancel': 0x00100000504,
'Context Menu': 0x00100000505,
'Execute': 0x00100000506,
'Find': 0x00100000507,
'Help': 0x00100000508,
'Pause': 0x00100000509,
'Play': 0x0010000050a,
'Props': 0x0010000050b,
'Select': 0x0010000050c,
'Zoom In': 0x0010000050d,
'Zoom Out': 0x0010000050e,
'Brightness Down': 0x00100000601,
'Brightness Up': 0x00100000602,
'Camera': 0x00100000603,
'Eject': 0x00100000604,
'Log Off': 0x00100000605,
'Power': 0x00100000606,
'Power Off': 0x00100000607,
'Print Screen': 0x00100000608,
'Hibernate': 0x00100000609,
'Standby': 0x0010000060a,
'Wake Up': 0x0010000060b,
'All Candidates': 0x00100000701,
'Alphanumeric': 0x00100000702,
'Code Input': 0x00100000703,
'Compose': 0x00100000704,
'Convert': 0x00100000705,
'Final Mode': 0x00100000706,
'Group First': 0x00100000707,
'Group Last': 0x00100000708,
'Group Next': 0x00100000709,
'Group Previous': 0x0010000070a,
'Mode Change': 0x0010000070b,
'Next Candidate': 0x0010000070c,
'Non Convert': 0x0010000070d,
'Previous Candidate': 0x0010000070e,
'Process': 0x0010000070f,
'Single Candidate': 0x00100000710,
'Hangul Mode': 0x00100000711,
'Hanja Mode': 0x00100000712,
'Junja Mode': 0x00100000713,
'Eisu': 0x00100000714,
'Hankaku': 0x00100000715,
'Hiragana': 0x00100000716,
'Hiragana Katakana': 0x00100000717,
'Kana Mode': 0x00100000718,
'Kanji Mode': 0x00100000719,
'Katakana': 0x0010000071a,
'Romaji': 0x0010000071b,
'Zenkaku': 0x0010000071c,
'Zenkaku Hankaku': 0x0010000071d,
'F1': 0x00100000801,
'F2': 0x00100000802,
'F3': 0x00100000803,
'F4': 0x00100000804,
'F5': 0x00100000805,
'F6': 0x00100000806,
'F7': 0x00100000807,
'F8': 0x00100000808,
'F9': 0x00100000809,
'F10': 0x0010000080a,
'F11': 0x0010000080b,
'F12': 0x0010000080c,
'F13': 0x0010000080d,
'F14': 0x0010000080e,
'F15': 0x0010000080f,
'F16': 0x00100000810,
'F17': 0x00100000811,
'F18': 0x00100000812,
'F19': 0x00100000813,
'F20': 0x00100000814,
'F21': 0x00100000815,
'F22': 0x00100000816,
'F23': 0x00100000817,
'F24': 0x00100000818,
'Soft 1': 0x00100000901,
'Soft 2': 0x00100000902,
'Soft 3': 0x00100000903,
'Soft 4': 0x00100000904,
'Soft 5': 0x00100000905,
'Soft 6': 0x00100000906,
'Soft 7': 0x00100000907,
'Soft 8': 0x00100000908,
'Close': 0x00100000a01,
'Mail Forward': 0x00100000a02,
'Mail Reply': 0x00100000a03,
'Mail Send': 0x00100000a04,
'Media Play Pause': 0x00100000a05,
'Media Stop': 0x00100000a07,
'Media Track Next': 0x00100000a08,
'Media Track Previous': 0x00100000a09,
'New': 0x00100000a0a,
'Open': 0x00100000a0b,
'Print': 0x00100000a0c,
'Save': 0x00100000a0d,
'Spell Check': 0x00100000a0e,
'Audio Volume Down': 0x00100000a0f,
'Audio Volume Up': 0x00100000a10,
'Audio Volume Mute': 0x00100000a11,
'Launch Application 2': 0x00100000b01,
'Launch Calendar': 0x00100000b02,
'Launch Mail': 0x00100000b03,
'Launch Media Player': 0x00100000b04,
'Launch Music Player': 0x00100000b05,
'Launch Application 1': 0x00100000b06,
'Launch Screen Saver': 0x00100000b07,
'Launch Spreadsheet': 0x00100000b08,
'Launch Web Browser': 0x00100000b09,
'Launch Web Cam': 0x00100000b0a,
'Launch Word Processor': 0x00100000b0b,
'Launch Contacts': 0x00100000b0c,
'Launch Phone': 0x00100000b0d,
'Launch Assistant': 0x00100000b0e,
'Launch Control Panel': 0x00100000b0f,
'Browser Back': 0x00100000c01,
'Browser Favorites': 0x00100000c02,
'Browser Forward': 0x00100000c03,
'Browser Home': 0x00100000c04,
'Browser Refresh': 0x00100000c05,
'Browser Search': 0x00100000c06,
'Browser Stop': 0x00100000c07,
'Audio Balance Left': 0x00100000d01,
'Audio Balance Right': 0x00100000d02,
'Audio Bass Boost Down': 0x00100000d03,
'Audio Bass Boost Up': 0x00100000d04,
'Audio Fader Front': 0x00100000d05,
'Audio Fader Rear': 0x00100000d06,
'Audio Surround Mode Next': 0x00100000d07,
'AVR Input': 0x00100000d08,
'AVR Power': 0x00100000d09,
'Channel Down': 0x00100000d0a,
'Channel Up': 0x00100000d0b,
'Color F0 Red': 0x00100000d0c,
'Color F1 Green': 0x00100000d0d,
'Color F2 Yellow': 0x00100000d0e,
'Color F3 Blue': 0x00100000d0f,
'Color F4 Grey': 0x00100000d10,
'Color F5 Brown': 0x00100000d11,
'Closed Caption Toggle': 0x00100000d12,
'Dimmer': 0x00100000d13,
'Display Swap': 0x00100000d14,
'Exit': 0x00100000d15,
'Favorite Clear 0': 0x00100000d16,
'Favorite Clear 1': 0x00100000d17,
'Favorite Clear 2': 0x00100000d18,
'Favorite Clear 3': 0x00100000d19,
'Favorite Recall 0': 0x00100000d1a,
'Favorite Recall 1': 0x00100000d1b,
'Favorite Recall 2': 0x00100000d1c,
'Favorite Recall 3': 0x00100000d1d,
'Favorite Store 0': 0x00100000d1e,
'Favorite Store 1': 0x00100000d1f,
'Favorite Store 2': 0x00100000d20,
'Favorite Store 3': 0x00100000d21,
'Guide': 0x00100000d22,
'Guide Next Day': 0x00100000d23,
'Guide Previous Day': 0x00100000d24,
'Info': 0x00100000d25,
'Instant Replay': 0x00100000d26,
'Link': 0x00100000d27,
'List Program': 0x00100000d28,
'Live Content': 0x00100000d29,
'Lock': 0x00100000d2a,
'Media Apps': 0x00100000d2b,
'Media Fast Forward': 0x00100000d2c,
'Media Last': 0x00100000d2d,
'Media Pause': 0x00100000d2e,
'Media Play': 0x00100000d2f,
'Media Record': 0x00100000d30,
'Media Rewind': 0x00100000d31,
'Media Skip': 0x00100000d32,
'Next Favorite Channel': 0x00100000d33,
'Next User Profile': 0x00100000d34,
'On Demand': 0x00100000d35,
'P In P Down': 0x00100000d36,
'P In P Move': 0x00100000d37,
'P In P Toggle': 0x00100000d38,
'P In P Up': 0x00100000d39,
'Play Speed Down': 0x00100000d3a,
'Play Speed Reset': 0x00100000d3b,
'Play Speed Up': 0x00100000d3c,
'Random Toggle': 0x00100000d3d,
'Rc Low Battery': 0x00100000d3e,
'Record Speed Next': 0x00100000d3f,
'Rf Bypass': 0x00100000d40,
'Scan Channels Toggle': 0x00100000d41,
'Screen Mode Next': 0x00100000d42,
'Settings': 0x00100000d43,
'Split Screen Toggle': 0x00100000d44,
'STB Input': 0x00100000d45,
'STB Power': 0x00100000d46,
'Subtitle': 0x00100000d47,
'Teletext': 0x00100000d48,
'TV': 0x00100000d49,
'TV Input': 0x00100000d4a,
'TV Power': 0x00100000d4b,
'Video Mode Next': 0x00100000d4c,
'Wink': 0x00100000d4d,
'Zoom Toggle': 0x00100000d4e,
'DVR': 0x00100000d4f,
'Media Audio Track': 0x00100000d50,
'Media Skip Backward': 0x00100000d51,
'Media Skip Forward': 0x00100000d52,
'Media Step Backward': 0x00100000d53,
'Media Step Forward': 0x00100000d54,
'Media Top Menu': 0x00100000d55,
'Navigate In': 0x00100000d56,
'Navigate Next': 0x00100000d57,
'Navigate Out': 0x00100000d58,
'Navigate Previous': 0x00100000d59,
'Pairing': 0x00100000d5a,
'Media Close': 0x00100000d5b,
'Audio Bass Boost Toggle': 0x00100000e02,
'Audio Treble Down': 0x00100000e04,
'Audio Treble Up': 0x00100000e05,
'Microphone Toggle': 0x00100000e06,
'Microphone Volume Down': 0x00100000e07,
'Microphone Volume Up': 0x00100000e08,
'Microphone Volume Mute': 0x00100000e09,
'Speech Correction List': 0x00100000f01,
'Speech Input Toggle': 0x00100000f02,
'App Switch': 0x00100001001,
'Call': 0x00100001002,
'Camera Focus': 0x00100001003,
'End Call': 0x00100001004,
'Go Back': 0x00100001005,
'Go Home': 0x00100001006,
'Headset Hook': 0x00100001007,
'Last Number Redial': 0x00100001008,
'Notification': 0x00100001009,
'Manner Mode': 0x0010000100a,
'Voice Dial': 0x0010000100b,
'TV 3 D Mode': 0x00100001101,
'TV Antenna Cable': 0x00100001102,
'TV Audio Description': 0x00100001103,
'TV Audio Description Mix Dow': 0x00100001104,
'TV Audio Description Mix Up': 0x00100001105,
'TV Contents Menu': 0x00100001106,
'TV Data Service': 0x00100001107,
'TV Input Component 1': 0x00100001108,
'TV Input Component 2': 0x00100001109,
'TV Input Composite 1': 0x0010000110a,
'TV Input Composite 2': 0x0010000110b,
'TV Input HDMI 1': 0x0010000110c,
'TV Input HDMI 2': 0x0010000110d,
'TV Input HDMI 3': 0x0010000110e,
'TV Input HDMI 4': 0x0010000110f,
'TV Input VGA 1': 0x00100001110,
'TV Media Context': 0x00100001111,
'TV Network': 0x00100001112,
'TV Number Entry': 0x00100001113,
'TV Radio Service': 0x00100001114,
'TV Satellite': 0x00100001115,
'TV Satellite BS': 0x00100001116,
'TV Satellite CS': 0x00100001117,
'TV Satellite Toggle': 0x00100001118,
'TV Terrestrial Analog': 0x00100001119,
'TV Terrestrial Digital': 0x0010000111a,
'TV Timer': 0x0010000111b,
'Key 11': 0x00100001201,
'Key 12': 0x00100001202,
'Suspend': 0x00200000000,
'Resume': 0x00200000001,
'Sleep': 0x00200000002,
'Abort': 0x00200000003,
'Lang 1': 0x00200000010,
'Lang 2': 0x00200000011,
'Lang 3': 0x00200000012,
'Lang 4': 0x00200000013,
'Lang 5': 0x00200000014,
'Intl Backslash': 0x00200000020,
'Intl Ro': 0x00200000021,
'Intl Yen': 0x00200000022,
'Control Left': 0x00200000100,
'Control Right': 0x00200000101,
'Shift Left': 0x00200000102,
'Shift Right': 0x00200000103,
'Alt Left': 0x00200000104,
'Alt Right': 0x00200000105,
'Meta Left': 0x00200000106,
'Meta Right': 0x00200000107,
'Control': 0x002000001f0,
'Shift': 0x002000001f2,
'Alt': 0x002000001f4,
'Meta': 0x002000001f6,
'Numpad Enter': 0x0020000020d,
'Numpad Paren Left': 0x00200000228,
'Numpad Paren Right': 0x00200000229,
'Numpad Multiply': 0x0020000022a,
'Numpad Add': 0x0020000022b,
'Numpad Comma': 0x0020000022c,
'Numpad Subtract': 0x0020000022d,
'Numpad Decimal': 0x0020000022e,
'Numpad Divide': 0x0020000022f,
'Numpad 0': 0x00200000230,
'Numpad 1': 0x00200000231,
'Numpad 2': 0x00200000232,
'Numpad 3': 0x00200000233,
'Numpad 4': 0x00200000234,
'Numpad 5': 0x00200000235,
'Numpad 6': 0x00200000236,
'Numpad 7': 0x00200000237,
'Numpad 8': 0x00200000238,
'Numpad 9': 0x00200000239,
'Numpad Equal': 0x0020000023d,
'Game Button 1': 0x00200000301,
'Game Button 2': 0x00200000302,
'Game Button 3': 0x00200000303,
'Game Button 4': 0x00200000304,
'Game Button 5': 0x00200000305,
'Game Button 6': 0x00200000306,
'Game Button 7': 0x00200000307,
'Game Button 8': 0x00200000308,
'Game Button 9': 0x00200000309,
'Game Button 10': 0x0020000030a,
'Game Button 11': 0x0020000030b,
'Game Button 12': 0x0020000030c,
'Game Button 13': 0x0020000030d,
'Game Button 14': 0x0020000030e,
'Game Button 15': 0x0020000030f,
'Game Button 16': 0x00200000310,
'Game Button A': 0x00200000311,
'Game Button B': 0x00200000312,
'Game Button C': 0x00200000313,
'Game Button Left 1': 0x00200000314,
'Game Button Left 2': 0x00200000315,
'Game Button Mode': 0x00200000316,
'Game Button Right 1': 0x00200000317,
'Game Button Right 2': 0x00200000318,
'Game Button Select': 0x00200000319,
'Game Button Start': 0x0020000031a,
'Game Button Thumb Left': 0x0020000031b,
'Game Button Thumb Right': 0x0020000031c,
'Game Button X': 0x0020000031d,
'Game Button Y': 0x0020000031e,
'Game Button Z': 0x0020000031f,
}.map((key, value) => MapEntry(key.toLowerCase(), value));

View File

@ -0,0 +1,153 @@
import 'dart:convert';
import 'package:appflowy_editor/src/service/shortcut_event/key_mapping.dart';
import 'package:flutter/material.dart';
extension KeybindingsExtension on List<Keybinding> {
bool containsKeyEvent(RawKeyEvent keyEvent) {
for (final keybinding in this) {
if (keybinding.isMetaPressed == keyEvent.isMetaPressed &&
keybinding.isControlPressed == keyEvent.isControlPressed &&
keybinding.isAltPressed == keyEvent.isAltPressed &&
keybinding.isShiftPressed == keyEvent.isShiftPressed &&
keybinding.keyCode == keyEvent.logicalKey.keyId) {
return true;
}
}
return false;
}
}
class Keybinding {
Keybinding({
required this.isAltPressed,
required this.isControlPressed,
required this.isMetaPressed,
required this.isShiftPressed,
required this.keyLabel,
});
factory Keybinding.parse(String command) {
command = command.toLowerCase().trim();
var isAltPressed = false;
var isControlPressed = false;
var isMetaPressed = false;
var isShiftPressed = false;
var matchedModifier = false;
do {
matchedModifier = false;
if (RegExp(r'^alt(\+|\-)').hasMatch(command)) {
isAltPressed = true;
command = command.substring(4); // 4 = 'alt '.length
matchedModifier = true;
}
if (RegExp(r'^ctrl(\+|\-)').hasMatch(command)) {
isControlPressed = true;
command = command.substring(5); // 5 = 'ctrl '.length
matchedModifier = true;
}
if (RegExp(r'^shift(\+|\-)').hasMatch(command)) {
isShiftPressed = true;
command = command.substring(6); // 6 = 'shift '.length
matchedModifier = true;
}
if (RegExp(r'^meta(\+|\-)').hasMatch(command)) {
isMetaPressed = true;
command = command.substring(5); // 5 = 'meta '.length
matchedModifier = true;
}
if (RegExp(r'^cmd(\+|\-)').hasMatch(command) ||
RegExp(r'^win(\+|\-)').hasMatch(command)) {
isMetaPressed = true;
command = command.substring(4); // 4 = 'win '.length
matchedModifier = true;
}
} while (matchedModifier);
return Keybinding(
isAltPressed: isAltPressed,
isControlPressed: isControlPressed,
isMetaPressed: isMetaPressed,
isShiftPressed: isShiftPressed,
keyLabel: command,
);
}
final bool isAltPressed;
final bool isControlPressed;
final bool isMetaPressed;
final bool isShiftPressed;
final String keyLabel;
int get keyCode => keyToCodeMapping[keyLabel.toLowerCase()]!;
Keybinding copyWith({
bool? isAltPressed,
bool? isControlPressed,
bool? isMetaPressed,
bool? isShiftPressed,
String? keyLabel,
}) {
return Keybinding(
isAltPressed: isAltPressed ?? this.isAltPressed,
isControlPressed: isControlPressed ?? this.isControlPressed,
isMetaPressed: isMetaPressed ?? this.isMetaPressed,
isShiftPressed: isShiftPressed ?? this.isShiftPressed,
keyLabel: keyLabel ?? this.keyLabel,
);
}
Map<String, dynamic> toMap() {
return {
'isAltPressed': isAltPressed,
'isControlPressed': isControlPressed,
'isMetaPressed': isMetaPressed,
'isShiftPressed': isShiftPressed,
'keyLabel': keyLabel,
};
}
factory Keybinding.fromMap(Map<String, dynamic> map) {
return Keybinding(
isAltPressed: map['isAltPressed'] ?? false,
isControlPressed: map['isControlPressed'] ?? false,
isMetaPressed: map['isMetaPressed'] ?? false,
isShiftPressed: map['isShiftPressed'] ?? false,
keyLabel: map['keyLabel'] ?? '',
);
}
String toJson() => json.encode(toMap());
factory Keybinding.fromJson(String source) =>
Keybinding.fromMap(json.decode(source));
@override
String toString() {
return 'Keybinding(isAltPressed: $isAltPressed, isControlPressed: $isControlPressed, isMetaPressed: $isMetaPressed, isShiftPressed: $isShiftPressed, keyLabel: $keyLabel)';
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is Keybinding &&
other.isAltPressed == isAltPressed &&
other.isControlPressed == isControlPressed &&
other.isMetaPressed == isMetaPressed &&
other.isShiftPressed == isShiftPressed &&
other.keyCode == keyCode;
}
@override
int get hashCode {
return isAltPressed.hashCode ^
isControlPressed.hashCode ^
isMetaPressed.hashCode ^
isShiftPressed.hashCode ^
keyCode;
}
}

View File

@ -0,0 +1,108 @@
import 'dart:io';
import 'package:appflowy_editor/src/service/shortcut_event/keybinding.dart';
import 'package:appflowy_editor/src/service/shortcut_event/shortcut_event_handler.dart';
/// Defines the implementation of shortcut event.
class ShortcutEvent {
ShortcutEvent({
required this.key,
required this.command,
required this.handler,
String? windowsCommand,
String? macOSCommand,
String? linuxCommand,
}) {
updateCommand(
command,
windowsCommand: windowsCommand,
macOSCommand: macOSCommand,
linuxCommand: linuxCommand,
);
}
/// The unique key.
///
/// Usually, uses the description as the key.
final String key;
/// The string representation for the keyboard keys.
///
/// The following is the mapping relationship of modify key.
/// ctrl: Ctrl
/// meta: Command in macOS or Control in Windows.
/// alt: Alt
/// shift: Shift
/// cmd: meta
/// win: meta
///
/// Refer to [keyMapping] for other keys.
///
/// Uses ',' to split different keyboard key combinations.
///
/// Like, 'ctrl+c,cmd+c'
///
String command;
final ShortcutEventHandler handler;
List<Keybinding> keybindings = [];
void updateCommand(
String command, {
String? windowsCommand,
String? macOSCommand,
String? linuxCommand,
}) {
if (Platform.isWindows &&
windowsCommand != null &&
windowsCommand.isNotEmpty) {
this.command = windowsCommand;
} else if (Platform.isMacOS &&
macOSCommand != null &&
macOSCommand.isNotEmpty) {
this.command = macOSCommand;
} else if (Platform.isLinux &&
linuxCommand != null &&
linuxCommand.isNotEmpty) {
this.command = linuxCommand;
} else {
this.command = command;
}
keybindings = this
.command
.split(',')
.map((e) => Keybinding.parse(e))
.toList(growable: false);
}
ShortcutEvent copyWith({
String? key,
String? command,
ShortcutEventHandler? handler,
}) {
return ShortcutEvent(
key: key ?? this.key,
command: command ?? this.command,
handler: handler ?? this.handler,
);
}
@override
String toString() =>
'ShortcutEvent(key: $key, command: $command, handler: $handler)';
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is ShortcutEvent &&
other.key == key &&
other.command == command &&
other.handler == handler;
}
@override
int get hashCode => key.hashCode ^ command.hashCode ^ handler.hashCode;
}

View File

@ -0,0 +1,7 @@
import 'package:appflowy_editor/src/editor_state.dart';
import 'package:flutter/material.dart';
typedef ShortcutEventHandler = KeyEventResult Function(
EditorState editorState,
RawKeyEvent event,
);

View File

@ -1,3 +1,5 @@
import 'dart:io';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
@ -328,37 +330,69 @@ Future<void> _testPressArrowKeyWithMetaInSelection(
}
}
await editor.updateSelection(selection);
await editor.pressLogicKey(
LogicalKeyboardKey.arrowLeft,
isMetaPressed: true,
);
if (Platform.isWindows) {
await editor.pressLogicKey(
LogicalKeyboardKey.arrowLeft,
isControlPressed: true,
);
} else {
await editor.pressLogicKey(
LogicalKeyboardKey.arrowLeft,
isMetaPressed: true,
);
}
expect(
editor.documentSelection,
Selection.single(path: [0], startOffset: 0),
);
await editor.pressLogicKey(
LogicalKeyboardKey.arrowRight,
isMetaPressed: true,
);
if (Platform.isWindows) {
await editor.pressLogicKey(
LogicalKeyboardKey.arrowRight,
isControlPressed: true,
);
} else {
await editor.pressLogicKey(
LogicalKeyboardKey.arrowRight,
isMetaPressed: true,
);
}
expect(
editor.documentSelection,
Selection.single(path: [0], startOffset: text.length),
);
await editor.pressLogicKey(
LogicalKeyboardKey.arrowUp,
isMetaPressed: true,
);
if (Platform.isWindows) {
await editor.pressLogicKey(
LogicalKeyboardKey.arrowUp,
isControlPressed: true,
);
} else {
await editor.pressLogicKey(
LogicalKeyboardKey.arrowUp,
isMetaPressed: true,
);
}
expect(
editor.documentSelection,
Selection.single(path: [0], startOffset: 0),
);
await editor.pressLogicKey(
LogicalKeyboardKey.arrowDown,
isMetaPressed: true,
);
if (Platform.isWindows) {
await editor.pressLogicKey(
LogicalKeyboardKey.arrowDown,
isControlPressed: true,
);
} else {
await editor.pressLogicKey(
LogicalKeyboardKey.arrowDown,
isMetaPressed: true,
);
}
expect(
editor.documentSelection,
Selection.single(path: [1], startOffset: text.length),

View File

@ -1,3 +1,5 @@
import 'dart:io';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
@ -41,20 +43,35 @@ Future<void> _testBackspaceUndoRedo(
await editor.pressLogicKey(LogicalKeyboardKey.backspace);
expect(editor.documentLength, 2);
await editor.pressLogicKey(
LogicalKeyboardKey.keyZ,
isMetaPressed: true,
);
if (Platform.isWindows) {
await editor.pressLogicKey(
LogicalKeyboardKey.keyZ,
isControlPressed: true,
);
} else {
await editor.pressLogicKey(
LogicalKeyboardKey.keyZ,
isMetaPressed: true,
);
}
expect(editor.documentLength, 3);
expect((editor.nodeAtPath([1]) as TextNode).toRawString(), text);
expect(editor.documentSelection, selection);
await editor.pressLogicKey(
LogicalKeyboardKey.keyZ,
isMetaPressed: true,
isShiftPressed: true,
);
if (Platform.isWindows) {
await editor.pressLogicKey(
LogicalKeyboardKey.keyZ,
isControlPressed: true,
isShiftPressed: true,
);
} else {
await editor.pressLogicKey(
LogicalKeyboardKey.keyZ,
isMetaPressed: true,
isShiftPressed: true,
);
}
expect(editor.documentLength, 2);
}

View File

@ -1,3 +1,5 @@
import 'dart:io';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
@ -26,7 +28,11 @@ Future<void> _testSelectAllHandler(WidgetTester tester, int lines) async {
editor.insertTextNode(text);
}
await editor.startTesting();
await editor.pressLogicKey(LogicalKeyboardKey.keyA, isMetaPressed: true);
if (Platform.isWindows) {
await editor.pressLogicKey(LogicalKeyboardKey.keyA, isControlPressed: true);
} else {
await editor.pressLogicKey(LogicalKeyboardKey.keyA, isMetaPressed: true);
}
expect(
editor.documentSelection,

View File

@ -1,3 +1,5 @@
import 'dart:io';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_editor/src/render/link_menu/link_menu.dart';
import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart';
@ -82,11 +84,19 @@ Future<void> _testUpdateTextStyleByCommandX(
var selection =
Selection.single(path: [1], startOffset: 2, endOffset: text.length - 2);
await editor.updateSelection(selection);
await editor.pressLogicKey(
key,
isShiftPressed: isShiftPressed,
isMetaPressed: true,
);
if (Platform.isWindows) {
await editor.pressLogicKey(
key,
isShiftPressed: isShiftPressed,
isControlPressed: true,
);
} else {
await editor.pressLogicKey(
key,
isShiftPressed: isShiftPressed,
isMetaPressed: true,
);
}
var textNode = editor.nodeAtPath([1]) as TextNode;
expect(
textNode.allSatisfyInSelection(
@ -101,11 +111,19 @@ Future<void> _testUpdateTextStyleByCommandX(
selection =
Selection.single(path: [1], startOffset: 0, endOffset: text.length);
await editor.updateSelection(selection);
await editor.pressLogicKey(
key,
isShiftPressed: isShiftPressed,
isMetaPressed: true,
);
if (Platform.isWindows) {
await editor.pressLogicKey(
key,
isShiftPressed: isShiftPressed,
isControlPressed: true,
);
} else {
await editor.pressLogicKey(
key,
isShiftPressed: isShiftPressed,
isMetaPressed: true,
);
}
textNode = editor.nodeAtPath([1]) as TextNode;
expect(
textNode.allSatisfyInSelection(
@ -118,11 +136,19 @@ Future<void> _testUpdateTextStyleByCommandX(
true);
await editor.updateSelection(selection);
await editor.pressLogicKey(
key,
isShiftPressed: isShiftPressed,
isMetaPressed: true,
);
if (Platform.isWindows) {
await editor.pressLogicKey(
key,
isShiftPressed: isShiftPressed,
isControlPressed: true,
);
} else {
await editor.pressLogicKey(
key,
isShiftPressed: isShiftPressed,
isMetaPressed: true,
);
}
textNode = editor.nodeAtPath([1]) as TextNode;
expect(textNode.allNotSatisfyInSelection(matchStyle, matchValue, selection),
true);
@ -132,11 +158,19 @@ Future<void> _testUpdateTextStyleByCommandX(
end: Position(path: [2], offset: text.length),
);
await editor.updateSelection(selection);
await editor.pressLogicKey(
key,
isShiftPressed: isShiftPressed,
isMetaPressed: true,
);
if (Platform.isWindows) {
await editor.pressLogicKey(
key,
isShiftPressed: isShiftPressed,
isControlPressed: true,
);
} else {
await editor.pressLogicKey(
key,
isShiftPressed: isShiftPressed,
isMetaPressed: true,
);
}
var nodes = editor.editorState.service.selectionService.currentSelectedNodes
.whereType<TextNode>();
expect(nodes.length, 3);
@ -158,11 +192,20 @@ Future<void> _testUpdateTextStyleByCommandX(
}
await editor.updateSelection(selection);
await editor.pressLogicKey(
key,
isShiftPressed: isShiftPressed,
isMetaPressed: true,
);
if (Platform.isWindows) {
await editor.pressLogicKey(
key,
isShiftPressed: isShiftPressed,
isControlPressed: true,
);
} else {
await editor.pressLogicKey(
key,
isShiftPressed: isShiftPressed,
isMetaPressed: true,
);
}
nodes = editor.editorState.service.selectionService.currentSelectedNodes
.whereType<TextNode>();
expect(nodes.length, 3);
@ -196,8 +239,11 @@ Future<void> _testLinkMenuInSingleTextSelection(WidgetTester tester) async {
expect(find.byType(ToolbarWidget), findsOneWidget);
// trigger the link menu
await editor.pressLogicKey(LogicalKeyboardKey.keyK, isMetaPressed: true);
if (Platform.isWindows) {
await editor.pressLogicKey(LogicalKeyboardKey.keyK, isControlPressed: true);
} else {
await editor.pressLogicKey(LogicalKeyboardKey.keyK, isMetaPressed: true);
}
expect(find.byType(LinkMenu), findsOneWidget);
await tester.enterText(find.byType(TextField), link);
@ -216,7 +262,11 @@ Future<void> _testLinkMenuInSingleTextSelection(WidgetTester tester) async {
true);
await editor.updateSelection(selection);
await editor.pressLogicKey(LogicalKeyboardKey.keyK, isMetaPressed: true);
if (Platform.isWindows) {
await editor.pressLogicKey(LogicalKeyboardKey.keyK, isControlPressed: true);
} else {
await editor.pressLogicKey(LogicalKeyboardKey.keyK, isMetaPressed: true);
}
expect(find.byType(LinkMenu), findsOneWidget);
expect(
find.text(link, findRichText: true, skipOffstage: false), findsOneWidget);
@ -229,7 +279,11 @@ Future<void> _testLinkMenuInSingleTextSelection(WidgetTester tester) async {
expect(find.byType(LinkMenu), findsNothing);
// Remove link
await editor.pressLogicKey(LogicalKeyboardKey.keyK, isMetaPressed: true);
if (Platform.isWindows) {
await editor.pressLogicKey(LogicalKeyboardKey.keyK, isControlPressed: true);
} else {
await editor.pressLogicKey(LogicalKeyboardKey.keyK, isMetaPressed: true);
}
final removeLink = find.text('Remove link');
expect(removeLink, findsOneWidget);
await tester.tap(removeLink);