feat: implement find the forward nearest text node

This commit is contained in:
Lucas.Xu 2022-09-23 22:59:56 +08:00
parent c5af7db2cd
commit ab353551d1
3 changed files with 189 additions and 2 deletions

View File

@ -43,7 +43,11 @@ class RichTextNodeWidget extends BuiltInTextWidget {
// customize
class _RichTextNodeWidgetState extends State<RichTextNodeWidget>
with SelectableMixin, DefaultSelectable, BuiltInStyleMixin {
with
SelectableMixin,
DefaultSelectable,
BuiltInStyleMixin,
BuiltInTextWidgetMixin {
@override
GlobalKey? get iconKey => null;
@ -59,7 +63,7 @@ class _RichTextNodeWidgetState extends State<RichTextNodeWidget>
}
@override
Widget build(BuildContext context) {
Widget buildWithSingle(BuildContext context) {
return Padding(
padding: padding,
child: FlowyRichText(

View File

@ -288,3 +288,55 @@ Node? _closestTextNode(Node? node) {
}
return null;
}
TextNode? findLastTextNode(Node node) {
final children = node.children.toList(growable: false).reversed;
for (final child in children) {
if (child.children.isNotEmpty) {
final result = findLastTextNode(child);
if (result != null) {
return result;
}
}
if (child is TextNode) {
return child;
}
}
if (node is TextNode) {
return node;
}
return null;
}
// find the forward nearest text node
TextNode? forwardNearestTextNode(Node node) {
var previous = node.previous;
while (previous != null) {
final lastTextNode = findLastTextNode(previous);
if (lastTextNode != null) {
return lastTextNode;
}
if (previous is TextNode) {
return previous;
}
previous = previous.previous;
}
final parent = node.parent;
if (parent != null) {
if (parent is TextNode) {
return parent;
}
return forwardNearestTextNode(parent);
}
return null;
}
Node? _forwardNearestTextNode(Node node) {
if (node is TextNode) {
return node;
}
if (node.next != null) {
return _forwardNearestTextNode(node.next!);
}
return null;
}

View File

@ -1,5 +1,9 @@
import 'dart:collection';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_editor/src/render/image/image_node_widget.dart';
import 'package:appflowy_editor/src/render/rich_text/flowy_rich_text.dart';
import 'package:appflowy_editor/src/service/internal_key_event_handlers/backspace_handler.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:network_image_mock/network_image_mock.dart';
@ -320,6 +324,133 @@ void main() async {
);
expect((editor.nodeAtPath([0, 0]) as TextNode).toRawString(), text * 2);
});
testWidgets('Delete the complicated nested bulleted list', (tester) async {
// * Welcome to Appflowy 😁
// * Welcome to Appflowy 😁
// * Welcome to Appflowy 😁
// * Welcome to Appflowy 😁
// * Welcome to Appflowy 😁
const text = 'Welcome to Appflowy 😁';
final node = TextNode(
type: 'text',
delta: Delta()..insert(text),
attributes: {
BuiltInAttributeKey.subtype: BuiltInAttributeKey.bulletedList,
},
);
node
..insert(
node.copyWith(children: LinkedList()),
)
..insert(
node.copyWith(children: LinkedList())
..insert(
node.copyWith(children: LinkedList()),
)
..insert(
node.copyWith(children: LinkedList()),
),
);
final editor = tester.editor..insert(node);
await editor.startTesting();
await editor.updateSelection(
Selection.single(path: [0, 1], startOffset: 0),
);
await editor.pressLogicKey(LogicalKeyboardKey.backspace);
expect(
editor.nodeAtPath([0, 1])!.subtype != BuiltInAttributeKey.bulletedList,
true,
);
expect(
editor.nodeAtPath([0, 1, 0])!.subtype,
BuiltInAttributeKey.bulletedList,
);
expect(
editor.nodeAtPath([0, 1, 1])!.subtype,
BuiltInAttributeKey.bulletedList,
);
expect(find.byType(FlowyRichText), findsNWidgets(5));
// Before
// * Welcome to Appflowy 😁
// * Welcome to Appflowy 😁
// Welcome to Appflowy 😁
// * Welcome to Appflowy 😁
// * Welcome to Appflowy 😁
// After
// * Welcome to Appflowy 😁
// * Welcome to Appflowy 😁
// Welcome to Appflowy 😁
// * Welcome to Appflowy 😁
// * Welcome to Appflowy 😁
await editor.pressLogicKey(LogicalKeyboardKey.backspace);
expect(
editor.nodeAtPath([1])!.subtype != BuiltInAttributeKey.bulletedList,
true,
);
expect(
editor.nodeAtPath([1, 0])!.subtype == BuiltInAttributeKey.bulletedList,
true,
);
expect(
editor.nodeAtPath([1, 1])!.subtype == BuiltInAttributeKey.bulletedList,
true,
);
// After
// * Welcome to Appflowy 😁
// * Welcome to Appflowy 😁Welcome to Appflowy 😁
// * Welcome to Appflowy 😁
// * Welcome to Appflowy 😁
});
test('find the last text node', () {
// * Welcome to Appflowy 😁
// * Welcome to Appflowy 😁
// * Welcome to Appflowy 😁
// * Welcome to Appflowy 😁
// * Welcome to Appflowy 😁
// * Welcome to Appflowy 😁
// * Welcome to Appflowy 😁
const text = 'Welcome to Appflowy 😁';
TextNode textNode() {
return TextNode(
type: 'text',
delta: Delta()..insert(text),
);
}
final node110 = textNode();
final node111 = textNode();
final node11 = textNode()
..insert(node110)
..insert(node111);
final node10 = textNode();
final node1 = textNode()
..insert(node10)
..insert(node11);
final node0 = textNode();
final node = textNode()
..insert(node0)
..insert(node1);
expect(findLastTextNode(node)?.path, [1, 1, 1]);
expect(findLastTextNode(node0)?.path, [0]);
expect(findLastTextNode(node1)?.path, [1, 1, 1]);
expect(findLastTextNode(node10)?.path, [1, 0]);
expect(findLastTextNode(node11)?.path, [1, 1, 1]);
expect(forwardNearestTextNode(node111)?.path, [1, 1, 0]);
expect(forwardNearestTextNode(node110)?.path, [1, 1]);
expect(forwardNearestTextNode(node11)?.path, [1, 0]);
expect(forwardNearestTextNode(node10)?.path, [1]);
expect(forwardNearestTextNode(node1)?.path, [0]);
expect(forwardNearestTextNode(node0)?.path, []);
});
}
Future<void> _deleteFirstImage(WidgetTester tester, bool isBackward) async {