From dc018bc7e494e0e5a38d22c838e89c375c2f7c38 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Mon, 8 Aug 2022 16:08:01 +0800 Subject: [PATCH 1/5] fix: could not delete character when using IME --- .../lib/service/input_service.dart | 48 ++++++++++++++----- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/frontend/app_flowy/packages/flowy_editor/lib/service/input_service.dart b/frontend/app_flowy/packages/flowy_editor/lib/service/input_service.dart index 9d6e0f7470..329cd8ed6d 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/service/input_service.dart +++ b/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 void apply(List 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 } } + 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; From 2f84d7e54e7bbc3e0216224d340efcad742e1fac Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Mon, 8 Aug 2022 16:17:17 +0800 Subject: [PATCH 2/5] fix: fix crash when selecting the last node --- .../lib/service/selection_service.dart | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/frontend/app_flowy/packages/flowy_editor/lib/service/selection_service.dart b/frontend/app_flowy/packages/flowy_editor/lib/service/selection_service.dart index 8baa54c26f..95c158f3ee 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/service/selection_service.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/service/selection_service.dart @@ -313,13 +313,9 @@ class _FlowySelectionState extends State 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) { @@ -518,13 +514,13 @@ class _FlowySelectionState extends State 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, From 1ece5cfd9e3cdb7f6497c33c68b59e1b4a28f6ff Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Mon, 8 Aug 2022 18:11:20 +0800 Subject: [PATCH 3/5] feat: binary search selection supports searching child nodes --- .../flowy_editor/lib/document/node_iterator.dart | 2 +- .../lib/render/rich_text/checkbox_text.dart | 16 +++++++++++----- .../lib/service/selection_service.dart | 16 ++++++++++++++-- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/frontend/app_flowy/packages/flowy_editor/lib/document/node_iterator.dart b/frontend/app_flowy/packages/flowy_editor/lib/document/node_iterator.dart index 1f321e937a..bafe106f27 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/document/node_iterator.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/document/node_iterator.dart @@ -43,7 +43,7 @@ class NodeIterator implements Iterator { if (nextOfParent == null) { _currentNode = null; } else { - _currentNode = _findLeadingChild(node); + _currentNode = _findLeadingChild(nextOfParent); } } diff --git a/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/checkbox_text.dart b/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/checkbox_text.dart index 065ae8b595..5c02955d7b 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/checkbox_text.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/checkbox_text.dart @@ -110,11 +110,17 @@ class _CheckboxNodeWidgetState extends State .map( (child) => widget.editorState.service.renderPluginService .buildPluginWidget( - NodeWidgetContext( - context: context, - node: child, - editorState: widget.editorState, - ), + child is TextNode + ? NodeWidgetContext( + context: context, + node: child, + editorState: widget.editorState, + ) + : NodeWidgetContext( + context: context, + node: child, + editorState: widget.editorState, + ), ), ) .toList(), diff --git a/frontend/app_flowy/packages/flowy_editor/lib/service/selection_service.dart b/frontend/app_flowy/packages/flowy_editor/lib/service/selection_service.dart index 95c158f3ee..a1506bf140 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/service/selection_service.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/service/selection_service.dart @@ -527,6 +527,7 @@ class _FlowySelectionState extends State /// currently only single-level nesting is supported // find the first node's rect.bottom <= offset.dy Node _lowerBound(List sortedNodes, Offset offset, int start, int end) { + assert(start >= 0 && end < sortedNodes.length); var min = start; var max = end; while (min <= max) { @@ -537,7 +538,12 @@ class _FlowySelectionState extends State 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, @@ -549,6 +555,7 @@ class _FlowySelectionState extends State int start, int end, ) { + assert(start >= 0 && end < sortedNodes.length); var min = start; var max = end; while (min <= max) { @@ -559,7 +566,12 @@ class _FlowySelectionState extends State 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; } } From 6e71ec23a111e71bc7fd6f696fcf806215c3cf90 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Mon, 8 Aug 2022 18:24:11 +0800 Subject: [PATCH 4/5] feat: delete slash when using the popuplist and pressing enter key --- .../lib/render/rich_text/flowy_rich_text.dart | 2 +- .../slash_handler.dart | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/flowy_rich_text.dart b/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/flowy_rich_text.dart index dbf4e5f63c..366942ee6c 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/flowy_rich_text.dart +++ b/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, diff --git a/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/slash_handler.dart b/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/slash_handler.dart index f824b87234..5c476cd139 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/slash_handler.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/slash_handler.dart @@ -211,6 +211,7 @@ class _PopupListWidgetState extends State { if (event.logicalKey == LogicalKeyboardKey.enter) { if (0 <= selectedIndex && selectedIndex < widget.items.length) { + _deleteSlash(); widget.items[selectedIndex].handler(widget.editorState); return KeyEventResult.handled; } @@ -239,6 +240,22 @@ class _PopupListWidgetState extends State { } 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 { From 27ea5a11a9dd4ed7db4b7c5e5bf6b90d8c1fedee Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Mon, 8 Aug 2022 18:40:09 +0800 Subject: [PATCH 5/5] feat: disable scroll when showing popuplist --- .../slash_handler.dart | 14 ++++++++--- .../lib/service/scroll_service.dart | 24 +++++++++++++------ .../lib/service/selection_service.dart | 1 + 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/slash_handler.dart b/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/slash_handler.dart index 5c476cd139..9a3be2b887 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/slash_handler.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/slash_handler.dart @@ -106,13 +106,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; } @@ -215,11 +221,13 @@ class _PopupListWidgetState extends State { 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; diff --git a/frontend/app_flowy/packages/flowy_editor/lib/service/scroll_service.dart b/frontend/app_flowy/packages/flowy_editor/lib/service/scroll_service.dart index c3a0a6fedc..af48a78c49 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/service/scroll_service.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/service/scroll_service.dart @@ -6,7 +6,8 @@ mixin FlowyScrollService on State { void scrollTo(double dy); - RenderObject? scrollRenderObject(); + void enable(); + void disable(); } class FlowyScroll extends StatefulWidget { @@ -25,6 +26,8 @@ class _FlowyScrollState extends State 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 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(); - } } diff --git a/frontend/app_flowy/packages/flowy_editor/lib/service/selection_service.dart b/frontend/app_flowy/packages/flowy_editor/lib/service/selection_service.dart index a1506bf140..ecf8caf817 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/service/selection_service.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/service/selection_service.dart @@ -267,6 +267,7 @@ class _FlowySelectionState extends State editorState.updateCursorSelection(selection); editorState.service.keyboardService?.enable(); + editorState.service.scrollService?.enable(); } @override