Bladeren bron

Merge pull request #788 from LucasXu0/fix/ime_error

fix: could not delete character when using IME
Nathan.fooo 2 jaren geleden
bovenliggende
commit
ef8d154736

+ 1 - 1
frontend/app_flowy/packages/flowy_editor/lib/document/node_iterator.dart

@@ -43,7 +43,7 @@ class NodeIterator implements Iterator<Node> {
       if (nextOfParent == null) {
         _currentNode = null;
       } else {
-        _currentNode = _findLeadingChild(node);
+        _currentNode = _findLeadingChild(nextOfParent);
       }
     }
 

+ 11 - 5
frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/checkbox_text.dart

@@ -110,11 +110,17 @@ class _CheckboxNodeWidgetState extends State<CheckboxNodeWidget>
                   .map(
                     (child) => widget.editorState.service.renderPluginService
                         .buildPluginWidget(
-                      NodeWidgetContext(
-                        context: context,
-                        node: child,
-                        editorState: widget.editorState,
-                      ),
+                      child is TextNode
+                          ? NodeWidgetContext<TextNode>(
+                              context: context,
+                              node: child,
+                              editorState: widget.editorState,
+                            )
+                          : NodeWidgetContext<Node>(
+                              context: context,
+                              node: child,
+                              editorState: widget.editorState,
+                            ),
                     ),
                   )
                   .toList(),

+ 1 - 1
frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/flowy_rich_text.dart

@@ -35,7 +35,7 @@ class FlowyRichText extends StatefulWidget {
     this.cursorHeight,
     this.cursorWidth = 2.0,
     this.textSpanDecorator,
-    this.placeholderText = ' ',
+    this.placeholderText = 'Type \'/\' for commands',
     this.placeholderTextSpanDecorator,
     required this.textNode,
     required this.editorState,

+ 35 - 13
frontend/app_flowy/packages/flowy_editor/lib/service/input_service.dart

@@ -2,8 +2,6 @@ import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
 
 import 'package:flowy_editor/document/node.dart';
-import 'package:flowy_editor/document/path.dart';
-import 'package:flowy_editor/document/position.dart';
 import 'package:flowy_editor/document/selection.dart';
 import 'package:flowy_editor/editor_state.dart';
 import 'package:flowy_editor/extensions/node_extensions.dart';
@@ -83,22 +81,29 @@ class _FlowyInputState extends State<FlowyInput>
   void apply(List<TextEditingDelta> deltas) {
     // TODO: implement the detail
     for (final delta in deltas) {
-      if (delta is TextEditingDeltaInsertion) {
-        if (_composingTextRange != null) {
-          _composingTextRange = TextRange(
-            start: _composingTextRange!.start,
-            end: delta.composing.end,
-          );
-        } else {
-          _composingTextRange = delta.composing;
-        }
+      _updateComposing(delta);
 
+      if (delta is TextEditingDeltaInsertion) {
         _applyInsert(delta);
       } else if (delta is TextEditingDeltaDeletion) {
+        _applyDelete(delta);
       } else if (delta is TextEditingDeltaReplacement) {
         _applyReplacement(delta);
-      } else if (delta is TextEditingDeltaNonTextUpdate) {
-        _composingTextRange = null;
+      } else if (delta is TextEditingDeltaNonTextUpdate) {}
+    }
+  }
+
+  void _updateComposing(TextEditingDelta delta) {
+    if (delta is! TextEditingDeltaNonTextUpdate) {
+      if (_composingTextRange != null &&
+          delta.composing.end != -1 &&
+          _composingTextRange!.start != -1) {
+        _composingTextRange = TextRange(
+          start: _composingTextRange!.start,
+          end: delta.composing.end,
+        );
+      } else {
+        _composingTextRange = delta.composing;
       }
     }
   }
@@ -123,6 +128,23 @@ class _FlowyInputState extends State<FlowyInput>
     }
   }
 
+  void _applyDelete(TextEditingDeltaDeletion delta) {
+    final selectionService = _editorState.service.selectionService;
+    final currentSelection = selectionService.currentSelection.value;
+    if (currentSelection == null) {
+      return;
+    }
+    if (currentSelection.isSingle) {
+      final textNode = selectionService.currentSelectedNodes.first as TextNode;
+      final length = delta.deletedRange.end - delta.deletedRange.start;
+      TransactionBuilder(_editorState)
+        ..deleteText(textNode, delta.deletedRange.start, length)
+        ..commit();
+    } else {
+      // TODO: implement
+    }
+  }
+
   void _applyReplacement(TextEditingDeltaReplacement delta) {
     final selectionService = _editorState.service.selectionService;
     final currentSelection = selectionService.currentSelection.value;

+ 28 - 3
frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/slash_handler.dart

@@ -109,13 +109,19 @@ void showPopupList(
       .removeListener(clearPopupList);
   editorState.service.selectionService.currentSelection
       .addListener(clearPopupList);
+
+  editorState.service.scrollService?.disable();
 }
 
 void clearPopupList() {
+  if (_popupListOverlay == null || _editorState == null) {
+    return;
+  }
   _popupListOverlay?.remove();
   _popupListOverlay = null;
 
   _editorState?.service.keyboardService?.enable();
+  _editorState?.service.scrollService?.enable();
   _editorState = null;
 }
 
@@ -214,14 +220,17 @@ class _PopupListWidgetState extends State<PopupListWidget> {
 
     if (event.logicalKey == LogicalKeyboardKey.enter) {
       if (0 <= selectedIndex && selectedIndex < widget.items.length) {
+        _deleteSlash();
         widget.items[selectedIndex].handler(widget.editorState);
         return KeyEventResult.handled;
       }
-    }
-
-    if (event.logicalKey == LogicalKeyboardKey.escape) {
+    } else if (event.logicalKey == LogicalKeyboardKey.escape) {
       clearPopupList();
       return KeyEventResult.handled;
+    } else if (event.logicalKey == LogicalKeyboardKey.backspace) {
+      clearPopupList();
+      _deleteSlash();
+      return KeyEventResult.handled;
     }
 
     var newSelectedIndex = selectedIndex;
@@ -242,6 +251,22 @@ class _PopupListWidgetState extends State<PopupListWidget> {
     }
     return KeyEventResult.ignored;
   }
+
+  void _deleteSlash() {
+    final selection =
+        widget.editorState.service.selectionService.currentSelection.value;
+    final nodes =
+        widget.editorState.service.selectionService.currentSelectedNodes;
+    if (selection != null && nodes.length == 1) {
+      TransactionBuilder(widget.editorState)
+        ..deleteText(
+          nodes.first as TextNode,
+          selection.start.offset - 1,
+          1,
+        )
+        ..commit();
+    }
+  }
 }
 
 class _PopupListItemWidget extends StatelessWidget {

+ 17 - 7
frontend/app_flowy/packages/flowy_editor/lib/service/scroll_service.dart

@@ -6,7 +6,8 @@ mixin FlowyScrollService<T extends StatefulWidget> on State<T> {
 
   void scrollTo(double dy);
 
-  RenderObject? scrollRenderObject();
+  void enable();
+  void disable();
 }
 
 class FlowyScroll extends StatefulWidget {
@@ -25,6 +26,8 @@ class _FlowyScrollState extends State<FlowyScroll> with FlowyScrollService {
   final _scrollController = ScrollController();
   final _scrollViewKey = GlobalKey();
 
+  bool _scrollEnabled = true;
+
   @override
   double get dy => _scrollController.position.pixels;
 
@@ -51,15 +54,22 @@ class _FlowyScrollState extends State<FlowyScroll> with FlowyScrollService {
     );
   }
 
+  @override
+  void disable() {
+    _scrollEnabled = false;
+    debugPrint('[scroll] $_scrollEnabled');
+  }
+
+  @override
+  void enable() {
+    _scrollEnabled = true;
+    debugPrint('[scroll] $_scrollEnabled');
+  }
+
   void _onPointerSignal(PointerSignalEvent event) {
-    if (event is PointerScrollEvent) {
+    if (event is PointerScrollEvent && _scrollEnabled) {
       final dy = (_scrollController.position.pixels + event.scrollDelta.dy);
       scrollTo(dy);
     }
   }
-
-  @override
-  RenderObject? scrollRenderObject() {
-    return _scrollViewKey.currentContext?.findRenderObject();
-  }
 }

+ 20 - 11
frontend/app_flowy/packages/flowy_editor/lib/service/selection_service.dart

@@ -287,6 +287,7 @@ class _FlowySelectionState extends State<FlowySelection>
     editorState.updateCursorSelection(selection);
 
     editorState.service.keyboardService?.enable();
+    editorState.service.scrollService?.enable();
   }
 
   @override
@@ -333,13 +334,9 @@ class _FlowySelectionState extends State<FlowySelection>
           panStartOffsetWithScrollDyGap.translate(0, panStartScrollDy! - dy);
     }
 
-    final sortedNodes =
-        editorState.document.root.children.toList(growable: false);
-    final first = _lowerBound(
-            sortedNodes, panStartOffsetWithScrollDyGap, 0, sortedNodes.length)
-        .selectable;
-    final last = _upperBound(sortedNodes, panEndOffset!, 0, sortedNodes.length)
-        .selectable;
+    final first =
+        _lowerBoundInDocument(panStartOffsetWithScrollDyGap).selectable;
+    final last = _upperBoundInDocument(panEndOffset!).selectable;
 
     // compute the selection in range.
     if (first != null && last != null) {
@@ -538,19 +535,20 @@ class _FlowySelectionState extends State<FlowySelection>
   Node _lowerBoundInDocument(Offset offset) {
     final sortedNodes =
         editorState.document.root.children.toList(growable: false);
-    return _lowerBound(sortedNodes, offset, 0, sortedNodes.length);
+    return _lowerBound(sortedNodes, offset, 0, sortedNodes.length - 1);
   }
 
   Node _upperBoundInDocument(Offset offset) {
     final sortedNodes =
         editorState.document.root.children.toList(growable: false);
-    return _upperBound(sortedNodes, offset, 0, sortedNodes.length);
+    return _upperBound(sortedNodes, offset, 0, sortedNodes.length - 1);
   }
 
   /// TODO: Supports multi-level nesting,
   ///  currently only single-level nesting is supported
   // find the first node's rect.bottom <= offset.dy
   Node _lowerBound(List<Node> sortedNodes, Offset offset, int start, int end) {
+    assert(start >= 0 && end < sortedNodes.length);
     var min = start;
     var max = end;
     while (min <= max) {
@@ -561,7 +559,12 @@ class _FlowySelectionState extends State<FlowySelection>
         max = mid - 1;
       }
     }
-    return sortedNodes[min];
+    final node = sortedNodes[min];
+    if (node.children.isNotEmpty && node.children.first.rect.top <= offset.dy) {
+      final children = node.children.toList(growable: false);
+      return _lowerBound(children, offset, 0, children.length - 1);
+    }
+    return node;
   }
 
   /// TODO: Supports multi-level nesting,
@@ -573,6 +576,7 @@ class _FlowySelectionState extends State<FlowySelection>
     int start,
     int end,
   ) {
+    assert(start >= 0 && end < sortedNodes.length);
     var min = start;
     var max = end;
     while (min <= max) {
@@ -583,7 +587,12 @@ class _FlowySelectionState extends State<FlowySelection>
         max = mid - 1;
       }
     }
-    return sortedNodes[max];
+    final node = sortedNodes[max];
+    if (node.children.isNotEmpty && node.children.first.rect.top <= offset.dy) {
+      final children = node.children.toList(growable: false);
+      return _lowerBound(children, offset, 0, children.length - 1);
+    }
+    return node;
   }
 }