mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: keyboard service improvement
This commit is contained in:
parent
a3d2cef40a
commit
43a0a02328
@ -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 {
|
||||
|
@ -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) {
|
||||
|
@ -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';
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
];
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
),
|
||||
];
|
@ -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));
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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,
|
||||
);
|
@ -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),
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user