Browse Source

feat: implement command + K to trigger link menu

Lucas.Xu 2 years ago
parent
commit
886c1f00e5

+ 11 - 6
frontend/app_flowy/packages/appflowy_editor/lib/src/render/link_menu/link_menu.dart

@@ -21,12 +21,21 @@ class LinkMenu extends StatefulWidget {
 
 class _LinkMenuState extends State<LinkMenu> {
   final _textEditingController = TextEditingController();
+  final _focusNode = FocusNode();
 
   @override
   void initState() {
     super.initState();
 
     _textEditingController.text = widget.linkText ?? '';
+    _focusNode.requestFocus();
+  }
+
+  @override
+  void dispose() {
+    _focusNode.dispose();
+
+    super.dispose();
   }
 
   @override
@@ -88,7 +97,7 @@ class _LinkMenuState extends State<LinkMenu> {
 
   Widget _buildInput() {
     return TextField(
-      autofocus: true,
+      focusNode: _focusNode,
       style: const TextStyle(fontSize: 14.0),
       textAlign: TextAlign.left,
       controller: _textEditingController,
@@ -112,11 +121,7 @@ class _LinkMenuState extends State<LinkMenu> {
     required VoidCallback onPressed,
   }) {
     return TextButton.icon(
-      icon: FlowySvg(
-        name: iconName,
-        width: 20.0,
-        height: 20.0,
-      ),
+      icon: FlowySvg(name: iconName),
       style: TextButton.styleFrom(
         minimumSize: const Size.fromHeight(40),
         padding: EdgeInsets.zero,

+ 18 - 6
frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_item.dart

@@ -13,12 +13,14 @@ typedef ToolbarShowValidator = bool Function(EditorState editorState);
 
 class ToolbarItem {
   ToolbarItem({
+    required this.id,
     required this.icon,
     this.tooltipsMessage = '',
     required this.validator,
     required this.handler,
   });
 
+  final String id;
   final Widget icon;
   final String tooltipsMessage;
   final ToolbarShowValidator validator;
@@ -26,6 +28,7 @@ class ToolbarItem {
 
   factory ToolbarItem.divider() {
     return ToolbarItem(
+      id: 'divider',
       icon: const FlowySvg(name: 'toolbar/divider'),
       validator: (editorState) => true,
       handler: (editorState, context) {},
@@ -35,18 +38,21 @@ class ToolbarItem {
 
 List<ToolbarItem> defaultToolbarItems = [
   ToolbarItem(
+    id: 'appflowy.toolbar.h1',
     tooltipsMessage: 'Heading 1',
     icon: const FlowySvg(name: 'toolbar/h1'),
     validator: _onlyShowInSingleTextSelection,
     handler: (editorState, context) => formatHeading(editorState, StyleKey.h1),
   ),
   ToolbarItem(
+    id: 'appflowy.toolbar.h2',
     tooltipsMessage: 'Heading 2',
     icon: const FlowySvg(name: 'toolbar/h2'),
     validator: _onlyShowInSingleTextSelection,
     handler: (editorState, context) => formatHeading(editorState, StyleKey.h2),
   ),
   ToolbarItem(
+    id: 'appflowy.toolbar.h3',
     tooltipsMessage: 'Heading 3',
     icon: const FlowySvg(name: 'toolbar/h3'),
     validator: _onlyShowInSingleTextSelection,
@@ -54,24 +60,28 @@ List<ToolbarItem> defaultToolbarItems = [
   ),
   ToolbarItem.divider(),
   ToolbarItem(
+    id: 'appflowy.toolbar.bold',
     tooltipsMessage: 'Bold',
     icon: const FlowySvg(name: 'toolbar/bold'),
     validator: _showInTextSelection,
     handler: (editorState, context) => formatBold(editorState),
   ),
   ToolbarItem(
+    id: 'appflowy.toolbar.italic',
     tooltipsMessage: 'Italic',
     icon: const FlowySvg(name: 'toolbar/italic'),
     validator: _showInTextSelection,
     handler: (editorState, context) => formatItalic(editorState),
   ),
   ToolbarItem(
+    id: 'appflowy.toolbar.underline',
     tooltipsMessage: 'Underline',
     icon: const FlowySvg(name: 'toolbar/underline'),
     validator: _showInTextSelection,
     handler: (editorState, context) => formatUnderline(editorState),
   ),
   ToolbarItem(
+    id: 'appflowy.toolbar.strikethrough',
     tooltipsMessage: 'Strikethrough',
     icon: const FlowySvg(name: 'toolbar/strikethrough'),
     validator: _showInTextSelection,
@@ -79,12 +89,14 @@ List<ToolbarItem> defaultToolbarItems = [
   ),
   ToolbarItem.divider(),
   ToolbarItem(
+    id: 'appflowy.toolbar.quote',
     tooltipsMessage: 'Quote',
     icon: const FlowySvg(name: 'toolbar/quote'),
     validator: _onlyShowInSingleTextSelection,
     handler: (editorState, context) => formatQuote(editorState),
   ),
   ToolbarItem(
+    id: 'appflowy.toolbar.bulleted_list',
     tooltipsMessage: 'Bulleted list',
     icon: const FlowySvg(name: 'toolbar/bulleted_list'),
     validator: _onlyShowInSingleTextSelection,
@@ -92,12 +104,14 @@ List<ToolbarItem> defaultToolbarItems = [
   ),
   ToolbarItem.divider(),
   ToolbarItem(
+    id: 'appflowy.toolbar.link',
     tooltipsMessage: 'Link',
     icon: const FlowySvg(name: 'toolbar/link'),
     validator: _onlyShowInSingleTextSelection,
     handler: (editorState, context) => _showLinkMenu(editorState, context),
   ),
   ToolbarItem(
+    id: 'appflowy.toolbar.highlight',
     tooltipsMessage: 'Highlight',
     icon: const FlowySvg(name: 'toolbar/highlight'),
     validator: _showInTextSelection,
@@ -152,9 +166,7 @@ void _showLinkMenu(EditorState editorState, BuildContext context) {
           linkText: linkText,
           onSubmitted: (text) {
             TransactionBuilder(editorState)
-              ..formatText(node, index, length, {
-                StyleKey.href: text,
-              })
+              ..formatText(node, index, length, {StyleKey.href: text})
               ..commit();
             _dismissLinkMenu();
           },
@@ -164,9 +176,7 @@ void _showLinkMenu(EditorState editorState, BuildContext context) {
           },
           onRemoveLink: () {
             TransactionBuilder(editorState)
-              ..formatText(node, index, length, {
-                StyleKey.href: null,
-              })
+              ..formatText(node, index, length, {StyleKey.href: null})
               ..commit();
             _dismissLinkMenu();
           },
@@ -177,6 +187,7 @@ void _showLinkMenu(EditorState editorState, BuildContext context) {
   Overlay.of(context)?.insert(_linkMenuOverlay!);
 
   editorState.service.scrollService?.disable();
+  editorState.service.keyboardService?.disable();
   editorState.service.selectionService.currentSelection
       .addListener(_dismissLinkMenu);
 }
@@ -186,6 +197,7 @@ void _dismissLinkMenu() {
   _linkMenuOverlay = null;
 
   _editorState?.service.scrollService?.enable();
+  _editorState?.service.keyboardService?.enable();
   _editorState?.service.selectionService.currentSelection
       .removeListener(_dismissLinkMenu);
   _editorState = null;

+ 6 - 0
frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/update_text_style_by_command_x_handler.dart

@@ -36,6 +36,12 @@ AppFlowyKeyEventHandler updateTextStyleByCommandXHandler =
       event.isShiftPressed) {
     formatHighlight(editorState);
     return KeyEventResult.handled;
+  } else if (event.logicalKey == LogicalKeyboardKey.keyK) {
+    if (editorState.service.toolbarService
+            ?.triggerHandler('appflowy.toolbar.link') ==
+        true) {
+      return KeyEventResult.handled;
+    }
   }
 
   return KeyEventResult.ignored;

+ 14 - 0
frontend/app_flowy/packages/appflowy_editor/lib/src/service/toolbar_service.dart

@@ -11,6 +11,9 @@ abstract class FlowyToolbarService {
 
   /// Hide the toolbar widget.
   void hide();
+
+  /// Trigger the specified handler.
+  bool triggerHandler(String id);
 }
 
 class FlowyToolbar extends StatefulWidget {
@@ -55,6 +58,17 @@ class _FlowyToolbarState extends State<FlowyToolbar>
     _toolbarOverlay = null;
   }
 
+  @override
+  bool triggerHandler(String id) {
+    final items = defaultToolbarItems.where((item) => item.id == id);
+    if (items.length != 1) {
+      assert(items.length == 1, 'The toolbar item\'s id must be unique');
+      return false;
+    }
+    items.first.handler(widget.editorState, context);
+    return true;
+  }
+
   @override
   Widget build(BuildContext context) {
     return Container(