Преглед изворни кода

feat: support floating selection and delete textnode

Lucas.Xu пре 2 година
родитељ
комит
e1d990e4ae

+ 11 - 11
frontend/app_flowy/packages/flowy_editor/example/assets/document.json

@@ -74,7 +74,7 @@
         "type": "text",
         "type": "text",
         "delta": [
         "delta": [
           {
           {
-            "insert": "Click the '?' at the bottom right for help and support."
+            "insert": "1. Click the '?' at the bottom right for help and support."
           }
           }
         ],
         ],
         "attributes": {}
         "attributes": {}
@@ -83,7 +83,7 @@
         "type": "text",
         "type": "text",
         "delta": [
         "delta": [
           {
           {
-            "insert": "Click the '?' at the bottom right for help and support."
+            "insert": "2. Click the '?' at the bottom right for help and support."
           }
           }
         ],
         ],
         "attributes": {}
         "attributes": {}
@@ -92,7 +92,7 @@
         "type": "text",
         "type": "text",
         "delta": [
         "delta": [
           {
           {
-            "insert": "Click the '?' at the bottom right for help and support."
+            "insert": "3. Click the '?' at the bottom right for help and support."
           }
           }
         ],
         ],
         "attributes": {}
         "attributes": {}
@@ -101,7 +101,7 @@
         "type": "text",
         "type": "text",
         "delta": [
         "delta": [
           {
           {
-            "insert": "Click the '?' at the bottom right for help and support.Click the '?' at the bottom right for help and support.Click the '?' at the bottom right for help and support.Click the '?' at the bottom right for help and support.Click the '?' at the bottom right for help and support.Click the '?' at the bottom right for help and support.Click the '?' at the bottom right for help and support."
+            "insert": "4. Click the '?' at the bottom right for help and support.Click the '?' at the bottom right for help and support.Click the '?' at the bottom right for help and support.Click the '?' at the bottom right for help and support.Click the '?' at the bottom right for help and support.Click the '?' at the bottom right for help and support.Click the '?' at the bottom right for help and support."
           }
           }
         ],
         ],
         "attributes": {}
         "attributes": {}
@@ -110,7 +110,7 @@
         "type": "text",
         "type": "text",
         "delta": [
         "delta": [
           {
           {
-            "insert": "Click the '?' at the bottom right for help and support."
+            "insert": "5. Click the '?' at the bottom right for help and support."
           }
           }
         ],
         ],
         "attributes": {}
         "attributes": {}
@@ -119,7 +119,7 @@
         "type": "text",
         "type": "text",
         "delta": [
         "delta": [
           {
           {
-            "insert": "Click the '?' at the bottom right for help and support."
+            "insert": "6. Click the '?' at the bottom right for help and support."
           }
           }
         ],
         ],
         "attributes": {}
         "attributes": {}
@@ -128,7 +128,7 @@
         "type": "text",
         "type": "text",
         "delta": [
         "delta": [
           {
           {
-            "insert": "Click the '?' at the bottom right for help and support."
+            "insert": "7. Click the '?' at the bottom right for help and support."
           }
           }
         ],
         ],
         "attributes": {}
         "attributes": {}
@@ -137,7 +137,7 @@
         "type": "text",
         "type": "text",
         "delta": [
         "delta": [
           {
           {
-            "insert": "Click the '?' at the bottom right for help and support."
+            "insert": "8. Click the '?' at the bottom right for help and support."
           }
           }
         ],
         ],
         "attributes": {}
         "attributes": {}
@@ -146,7 +146,7 @@
         "type": "text",
         "type": "text",
         "delta": [
         "delta": [
           {
           {
-            "insert": "Click the '?' at the bottom right for help and support."
+            "insert": "9. Click the '?' at the bottom right for help and support."
           }
           }
         ],
         ],
         "attributes": {}
         "attributes": {}
@@ -155,7 +155,7 @@
         "type": "text",
         "type": "text",
         "delta": [
         "delta": [
           {
           {
-            "insert": "Click the '?' at the bottom right for help and support."
+            "insert": "10. Click the '?' at the bottom right for help and support."
           }
           }
         ],
         ],
         "attributes": {}
         "attributes": {}
@@ -164,7 +164,7 @@
         "type": "text",
         "type": "text",
         "delta": [
         "delta": [
           {
           {
-            "insert": "Click the '?' at the bottom right for help and support."
+            "insert": "11. Click the '?' at the bottom right for help and support."
           }
           }
         ],
         ],
         "attributes": {}
         "attributes": {}

+ 1 - 3
frontend/app_flowy/packages/flowy_editor/example/lib/main.dart

@@ -96,9 +96,7 @@ class _MyHomePageState extends State<MyHomePage> {
             );
             );
             return FlowyEditor(
             return FlowyEditor(
               editorState: _editorState,
               editorState: _editorState,
-              keyEventHandler: [
-                deleteSingleImageNode,
-              ],
+              keyEventHandler: const [],
             );
             );
           }
           }
         },
         },

+ 5 - 11
frontend/app_flowy/packages/flowy_editor/example/lib/plugin/image_node_widget.dart

@@ -1,17 +1,6 @@
 import 'package:flowy_editor/flowy_editor.dart';
 import 'package:flowy_editor/flowy_editor.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 
 
-FlowyKeyEventHandler deleteSingleImageNode = (editorState, event) {
-  final selectNodes = editorState.selectedNodes;
-  if (selectNodes.length != 1 || selectNodes.first.type != 'image') {
-    return KeyEventResult.ignored;
-  }
-  TransactionBuilder(editorState)
-    ..deleteNode(selectNodes.first)
-    ..commit();
-  return KeyEventResult.handled;
-};
-
 class ImageNodeBuilder extends NodeWidgetBuilder {
 class ImageNodeBuilder extends NodeWidgetBuilder {
   ImageNodeBuilder.create({
   ImageNodeBuilder.create({
     required super.node,
     required super.node,
@@ -67,6 +56,11 @@ class __ImageNodeWidgetState extends State<_ImageNodeWidget> with Selectable {
     return null;
     return null;
   }
   }
 
 
+  @override
+  Offset getOffsetByTextSelection(TextSelection textSelection) {
+    return Offset.zero;
+  }
+
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     return _build(context);
     return _build(context);

+ 8 - 0
frontend/app_flowy/packages/flowy_editor/example/lib/plugin/selected_text_node_widget.dart

@@ -93,6 +93,7 @@ class _SelectedTextNodeWidgetState extends State<_SelectedTextNodeWidget>
     final selectionBaseOffset = _getTextPositionAtOffset(localStart).offset;
     final selectionBaseOffset = _getTextPositionAtOffset(localStart).offset;
     final textSelection = TextSelection.collapsed(offset: selectionBaseOffset);
     final textSelection = TextSelection.collapsed(offset: selectionBaseOffset);
     _textSelection = textSelection;
     _textSelection = textSelection;
+    print('text selection = $textSelection');
     return _computeCursorRect(textSelection.baseOffset);
     return _computeCursorRect(textSelection.baseOffset);
   }
   }
 
 
@@ -101,6 +102,12 @@ class _SelectedTextNodeWidgetState extends State<_SelectedTextNodeWidget>
     return _textSelection;
     return _textSelection;
   }
   }
 
 
+  @override
+  Offset getOffsetByTextSelection(TextSelection textSelection) {
+    final offset = _computeCursorRect(textSelection.baseOffset).center;
+    return _renderParagraph.localToGlobal(offset);
+  }
+
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     Widget richText;
     Widget richText;
@@ -148,6 +155,7 @@ class _SelectedTextNodeWidgetState extends State<_SelectedTextNodeWidget>
     final cursorOffset =
     final cursorOffset =
         _renderParagraph.getOffsetForCaret(position, Rect.zero);
         _renderParagraph.getOffsetForCaret(position, Rect.zero);
     final cursorHeight = _renderParagraph.getFullHeightForCaret(position);
     final cursorHeight = _renderParagraph.getFullHeightForCaret(position);
+    print('offset = $offset, cursorHeight = $cursorHeight');
     if (cursorHeight != null) {
     if (cursorHeight != null) {
       const cursorWidth = 2;
       const cursorWidth = 2;
       return Rect.fromLTWH(
       return Rect.fromLTWH(

+ 3 - 0
frontend/app_flowy/packages/flowy_editor/lib/editor_state.dart

@@ -19,6 +19,9 @@ class ApplyOptions {
   });
   });
 }
 }
 
 
+// TODO
+final selectionServiceKey = GlobalKey();
+
 class EditorState {
 class EditorState {
   final StateTree document;
   final StateTree document;
   final RenderPlugins renderPlugins;
   final RenderPlugins renderPlugins;

+ 8 - 0
frontend/app_flowy/packages/flowy_editor/lib/extensions/object_extensions.dart

@@ -0,0 +1,8 @@
+extension FlowyObjectExtensions on Object {
+  T? unwrapOrNull<T>() {
+    if (this is T) {
+      return this as T;
+    }
+    return null;
+  }
+}

+ 1 - 2
frontend/app_flowy/packages/flowy_editor/lib/flowy_editor.dart

@@ -11,5 +11,4 @@ export 'package:flowy_editor/operation/transaction.dart';
 export 'package:flowy_editor/operation/transaction_builder.dart';
 export 'package:flowy_editor/operation/transaction_builder.dart';
 export 'package:flowy_editor/operation/operation.dart';
 export 'package:flowy_editor/operation/operation.dart';
 export 'package:flowy_editor/editor_state.dart';
 export 'package:flowy_editor/editor_state.dart';
-export 'package:flowy_editor/service/flowy_editor_service.dart';
-export 'package:flowy_editor/service/flowy_keyboard_service.dart';
+export 'package:flowy_editor/service/editor_service.dart';

+ 3 - 0
frontend/app_flowy/packages/flowy_editor/lib/render/selection/selectable.dart

@@ -13,4 +13,7 @@ mixin Selectable<T extends StatefulWidget> on State<T> {
 
 
   /// For [TextNode] only.
   /// For [TextNode] only.
   TextSelection? getTextSelection();
   TextSelection? getTextSelection();
+
+  /// For [TextNode] only.
+  Offset getOffsetByTextSelection(TextSelection textSelection);
 }
 }

+ 8 - 4
frontend/app_flowy/packages/flowy_editor/lib/service/flowy_editor_service.dart → frontend/app_flowy/packages/flowy_editor/lib/service/editor_service.dart

@@ -1,5 +1,7 @@
-import 'package:flowy_editor/service/flowy_keyboard_service.dart';
-import 'package:flowy_editor/service/flowy_selection_service.dart';
+import 'package:flowy_editor/service/flowy_key_event_handlers/delete_nodes_handler.dart';
+import 'package:flowy_editor/service/flowy_key_event_handlers/delete_single_text_node_handler.dart';
+import 'package:flowy_editor/service/keyboard_service.dart';
+import 'package:flowy_editor/service/selection_service.dart';
 
 
 import '../editor_state.dart';
 import '../editor_state.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
@@ -23,11 +25,13 @@ class _FlowyEditorState extends State<FlowyEditor> {
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
-    return FlowySelectionService(
+    return FlowySelection(
+      key: selectionServiceKey,
       editorState: editorState,
       editorState: editorState,
-      child: FlowyKeyboardWidget(
+      child: FlowyKeyboard(
         handlers: [
         handlers: [
           flowyDeleteNodesHandler,
           flowyDeleteNodesHandler,
+          deleteSingleTextNodeHandler,
           ...widget.keyEventHandler,
           ...widget.keyEventHandler,
         ],
         ],
         editorState: editorState,
         editorState: editorState,

+ 21 - 0
frontend/app_flowy/packages/flowy_editor/lib/service/flowy_key_event_handlers/delete_nodes_handler.dart

@@ -0,0 +1,21 @@
+import 'package:flowy_editor/flowy_editor.dart';
+import 'package:flowy_editor/service/keyboard_service.dart';
+import 'package:flutter/material.dart';
+
+FlowyKeyEventHandler flowyDeleteNodesHandler = (editorState, event) {
+  // Handle delete nodes.
+  final nodes = editorState.selectedNodes;
+  if (nodes.length <= 1) {
+    return KeyEventResult.ignored;
+  }
+
+  debugPrint('delete nodes = $nodes');
+
+  nodes
+      .fold<TransactionBuilder>(
+        TransactionBuilder(editorState),
+        (previousValue, node) => previousValue..deleteNode(node),
+      )
+      .commit();
+  return KeyEventResult.handled;
+};

+ 73 - 0
frontend/app_flowy/packages/flowy_editor/lib/service/flowy_key_event_handlers/delete_single_text_node_handler.dart

@@ -0,0 +1,73 @@
+import 'package:flowy_editor/document/node.dart';
+import 'package:flowy_editor/editor_state.dart';
+import 'package:flowy_editor/operation/transaction_builder.dart';
+import 'package:flowy_editor/render/selection/selectable.dart';
+import 'package:flowy_editor/service/keyboard_service.dart';
+import 'package:flowy_editor/extensions/object_extensions.dart';
+import 'package:flowy_editor/service/selection_service.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+
+// TODO: need to be refactored, just a example code.
+FlowyKeyEventHandler deleteSingleTextNodeHandler = (editorState, event) {
+  if (event.logicalKey != LogicalKeyboardKey.backspace) {
+    return KeyEventResult.ignored;
+  }
+
+  final selectionNodes = editorState.selectedNodes;
+  if (selectionNodes.length == 1 && selectionNodes.first is TextNode) {
+    final node = selectionNodes.first.unwrapOrNull<TextNode>();
+    final selectable = node?.key?.currentState?.unwrapOrNull<Selectable>();
+    if (selectable != null) {
+      final textSelection = selectable.getTextSelection();
+      if (textSelection != null) {
+        if (textSelection.isCollapsed) {
+          /// Three cases:
+          /// Delete the zero character,
+          ///   1. if there is still text node in front of it, then merge them.
+          ///   2. if not, just ignore
+          /// Delete the non-zero character,
+          ///   3. delete the single character.
+          if (textSelection.baseOffset == 0) {
+            if (node?.previous != null && node?.previous is TextNode) {
+              final previous = node!.previous! as TextNode;
+              final newTextSelection = TextSelection.collapsed(
+                  offset: previous.toRawString().length);
+              final selectionService =
+                  selectionServiceKey.currentState as FlowySelectionService;
+              final previousSelectable =
+                  previous.key?.currentState?.unwrapOrNull<Selectable>();
+              final newOfset = previousSelectable
+                  ?.getOffsetByTextSelection(newTextSelection);
+              if (newOfset != null) {
+                selectionService.updateCursor(newOfset);
+              }
+              // merge
+              TransactionBuilder(editorState)
+                ..deleteNode(node)
+                ..insertText(
+                    previous, previous.toRawString().length, node.toRawString())
+                ..commit();
+              return KeyEventResult.handled;
+            } else {
+              return KeyEventResult.ignored;
+            }
+          } else {
+            TransactionBuilder(editorState)
+              ..deleteText(node!, textSelection.baseOffset - 1, 1)
+              ..commit();
+            final newTextSelection =
+                TextSelection.collapsed(offset: textSelection.baseOffset - 1);
+            final selectionService =
+                selectionServiceKey.currentState as FlowySelectionService;
+            final newOfset =
+                selectable.getOffsetByTextSelection(newTextSelection);
+            selectionService.updateCursor(newOfset);
+            return KeyEventResult.handled;
+          }
+        }
+      }
+    }
+  }
+  return KeyEventResult.ignored;
+};

+ 4 - 23
frontend/app_flowy/packages/flowy_editor/lib/service/flowy_keyboard_service.dart → frontend/app_flowy/packages/flowy_editor/lib/service/keyboard_service.dart

@@ -1,4 +1,3 @@
-import 'package:flowy_editor/operation/transaction_builder.dart';
 import 'package:flutter/services.dart';
 import 'package:flutter/services.dart';
 
 
 import '../editor_state.dart';
 import '../editor_state.dart';
@@ -9,27 +8,9 @@ typedef FlowyKeyEventHandler = KeyEventResult Function(
   RawKeyEvent event,
   RawKeyEvent event,
 );
 );
 
 
-FlowyKeyEventHandler flowyDeleteNodesHandler = (editorState, event) {
-  // Handle delete nodes.
-  final nodes = editorState.selectedNodes;
-  if (nodes.length <= 1) {
-    return KeyEventResult.ignored;
-  }
-
-  debugPrint('delete nodes = $nodes');
-
-  nodes
-      .fold<TransactionBuilder>(
-        TransactionBuilder(editorState),
-        (previousValue, node) => previousValue..deleteNode(node),
-      )
-      .commit();
-  return KeyEventResult.handled;
-};
-
 /// Process keyboard events
 /// Process keyboard events
-class FlowyKeyboardWidget extends StatefulWidget {
-  const FlowyKeyboardWidget({
+class FlowyKeyboard extends StatefulWidget {
+  const FlowyKeyboard({
     Key? key,
     Key? key,
     required this.handlers,
     required this.handlers,
     required this.editorState,
     required this.editorState,
@@ -41,10 +22,10 @@ class FlowyKeyboardWidget extends StatefulWidget {
   final List<FlowyKeyEventHandler> handlers;
   final List<FlowyKeyEventHandler> handlers;
 
 
   @override
   @override
-  State<FlowyKeyboardWidget> createState() => _FlowyKeyboardWidgetState();
+  State<FlowyKeyboard> createState() => _FlowyKeyboardState();
 }
 }
 
 
-class _FlowyKeyboardWidgetState extends State<FlowyKeyboardWidget> {
+class _FlowyKeyboardState extends State<FlowyKeyboard> {
   final FocusNode focusNode = FocusNode(debugLabel: 'flowy_keyboard_service');
   final FocusNode focusNode = FocusNode(debugLabel: 'flowy_keyboard_service');
 
 
   @override
   @override

+ 30 - 29
frontend/app_flowy/packages/flowy_editor/lib/service/flowy_selection_service.dart → frontend/app_flowy/packages/flowy_editor/lib/service/selection_service.dart

@@ -8,7 +8,7 @@ import '../document/node.dart';
 import '../render/selection/selectable.dart';
 import '../render/selection/selectable.dart';
 
 
 /// Process selection and cursor
 /// Process selection and cursor
-mixin _FlowySelectionService<T extends StatefulWidget> on State<T> {
+mixin FlowySelectionService<T extends StatefulWidget> on State<T> {
   /// [Pan] and [Tap] must be mutually exclusive.
   /// [Pan] and [Tap] must be mutually exclusive.
   /// Pan
   /// Pan
   Offset? panStartOffset;
   Offset? panStartOffset;
@@ -19,20 +19,20 @@ mixin _FlowySelectionService<T extends StatefulWidget> on State<T> {
 
 
   void updateSelection(Offset start, Offset end);
   void updateSelection(Offset start, Offset end);
 
 
-  void updateCursor(Offset offset);
+  void updateCursor(Offset start);
 
 
   /// Returns selected node(s)
   /// Returns selected node(s)
   /// Returns empty list if no nodes are being selected.
   /// Returns empty list if no nodes are being selected.
-  List<Node> get selectedNodes;
+  List<Node> getSelectedNodes(Offset start, [Offset? end]);
 
 
   /// Compute selected node triggered by [Tap]
   /// Compute selected node triggered by [Tap]
-  Node? computeSelectedNodeByTap(
+  Node? computeSelectedNodeInOffset(
     Node node,
     Node node,
     Offset offset,
     Offset offset,
   );
   );
 
 
   /// Compute selected nodes triggered by [Pan]
   /// Compute selected nodes triggered by [Pan]
-  List<Node> computeSelectedNodesByPan(
+  List<Node> computeSelectedNodesInRange(
     Node node,
     Node node,
     Offset start,
     Offset start,
     Offset end,
     Offset end,
@@ -52,8 +52,8 @@ mixin _FlowySelectionService<T extends StatefulWidget> on State<T> {
   );
   );
 }
 }
 
 
-class FlowySelectionService extends StatefulWidget {
-  const FlowySelectionService({
+class FlowySelection extends StatefulWidget {
+  const FlowySelection({
     Key? key,
     Key? key,
     required this.editorState,
     required this.editorState,
     required this.child,
     required this.child,
@@ -63,11 +63,11 @@ class FlowySelectionService extends StatefulWidget {
   final Widget child;
   final Widget child;
 
 
   @override
   @override
-  State<FlowySelectionService> createState() => _FlowySelectionServiceState();
+  State<FlowySelection> createState() => _FlowySelectionState();
 }
 }
 
 
-class _FlowySelectionServiceState extends State<FlowySelectionService>
-    with _FlowySelectionService {
+class _FlowySelectionState extends State<FlowySelection>
+    with FlowySelectionService {
   final _cursorKey = GlobalKey(debugLabel: 'cursor');
   final _cursorKey = GlobalKey(debugLabel: 'cursor');
 
 
   final List<OverlayEntry> _selectionOverlays = [];
   final List<OverlayEntry> _selectionOverlays = [];
@@ -106,7 +106,7 @@ class _FlowySelectionServiceState extends State<FlowySelectionService>
   void updateSelection(Offset start, Offset end) {
   void updateSelection(Offset start, Offset end) {
     _clearAllOverlayEntries();
     _clearAllOverlayEntries();
 
 
-    final nodes = selectedNodes;
+    final nodes = getSelectedNodes(start, end);
     editorState.selectedNodes = nodes;
     editorState.selectedNodes = nodes;
     if (nodes.isEmpty) {
     if (nodes.isEmpty) {
       return;
       return;
@@ -133,10 +133,10 @@ class _FlowySelectionServiceState extends State<FlowySelectionService>
   }
   }
 
 
   @override
   @override
-  void updateCursor(Offset offset) {
+  void updateCursor(Offset start) {
     _clearAllOverlayEntries();
     _clearAllOverlayEntries();
 
 
-    final nodes = selectedNodes;
+    final nodes = getSelectedNodes(start);
     editorState.selectedNodes = nodes;
     editorState.selectedNodes = nodes;
     if (nodes.isEmpty) {
     if (nodes.isEmpty) {
       return;
       return;
@@ -147,7 +147,7 @@ class _FlowySelectionServiceState extends State<FlowySelectionService>
       return;
       return;
     }
     }
     final selectable = selectedNode.key?.currentState as Selectable;
     final selectable = selectedNode.key?.currentState as Selectable;
-    final rect = selectable.getCursorRect(offset);
+    final rect = selectable.getCursorRect(start);
     final cursor = OverlayEntry(
     final cursor = OverlayEntry(
       builder: ((context) => FlowyCursorWidget(
       builder: ((context) => FlowyCursorWidget(
             key: _cursorKey,
             key: _cursorKey,
@@ -161,13 +161,18 @@ class _FlowySelectionServiceState extends State<FlowySelectionService>
   }
   }
 
 
   @override
   @override
-  List<Node> get selectedNodes {
-    if (panStartOffset != null && panEndOffset != null) {
-      return computeSelectedNodesByPan(
-          editorState.document.root, panStartOffset!, panEndOffset!);
-    } else if (tapOffset != null) {
-      final reuslt =
-          computeSelectedNodeByTap(editorState.document.root, tapOffset!);
+  List<Node> getSelectedNodes(Offset start, [Offset? end]) {
+    if (end != null) {
+      return computeSelectedNodesInRange(
+        editorState.document.root,
+        start,
+        end,
+      );
+    } else {
+      final reuslt = computeSelectedNodeInOffset(
+        editorState.document.root,
+        start,
+      );
       if (reuslt != null) {
       if (reuslt != null) {
         return [reuslt];
         return [reuslt];
       }
       }
@@ -176,13 +181,9 @@ class _FlowySelectionServiceState extends State<FlowySelectionService>
   }
   }
 
 
   @override
   @override
-  Node? computeSelectedNodeByTap(Node node, Offset offset) {
-    assert(this.tapOffset != null);
-    final tapOffset = this.tapOffset;
-    if (tapOffset != null) {}
-
+  Node? computeSelectedNodeInOffset(Node node, Offset offset) {
     for (final child in node.children) {
     for (final child in node.children) {
-      final result = computeSelectedNodeByTap(child, offset);
+      final result = computeSelectedNodeInOffset(child, offset);
       if (result != null) {
       if (result != null) {
         return result;
         return result;
       }
       }
@@ -198,7 +199,7 @@ class _FlowySelectionServiceState extends State<FlowySelectionService>
   }
   }
 
 
   @override
   @override
-  List<Node> computeSelectedNodesByPan(Node node, Offset start, Offset end) {
+  List<Node> computeSelectedNodesInRange(Node node, Offset start, Offset end) {
     List<Node> result = [];
     List<Node> result = [];
     if (node.parent != null && node.key != null) {
     if (node.parent != null && node.key != null) {
       if (isNodeInSelection(node, start, end)) {
       if (isNodeInSelection(node, start, end)) {
@@ -206,7 +207,7 @@ class _FlowySelectionServiceState extends State<FlowySelectionService>
       }
       }
     }
     }
     for (final child in node.children) {
     for (final child in node.children) {
-      result.addAll(computeSelectedNodesByPan(child, start, end));
+      result.addAll(computeSelectedNodesInRange(child, start, end));
     }
     }
     // TODO: sort the result
     // TODO: sort the result
     return result;
     return result;