mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Merge pull request #788 from LucasXu0/fix/ime_error
fix: could not delete character when using IME
This commit is contained in:
commit
ef8d154736
@ -43,7 +43,7 @@ class NodeIterator implements Iterator<Node> {
|
||||
if (nextOfParent == null) {
|
||||
_currentNode = null;
|
||||
} else {
|
||||
_currentNode = _findLeadingChild(node);
|
||||
_currentNode = _findLeadingChild(nextOfParent);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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(),
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user