fix: delete the nested bulleted list will lost the children nodes

This commit is contained in:
Lucas.Xu 2022-09-24 12:01:23 +08:00
parent d648f2b5b9
commit 6bda1fd2ea
4 changed files with 123 additions and 114 deletions

View File

@ -0,0 +1,46 @@
import 'package:appflowy_editor/src/document/node.dart';
class Infra {
// find the forward nearest text node
static 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;
}
// find the last text node
static 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;
}
}

View File

@ -1,7 +1,9 @@
import 'package:appflowy_editor/src/infra/infra.dart';
import 'package:appflowy_editor/src/service/internal_key_event_handlers/number_list_helper.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_editor/src/extensions/path_extensions.dart';
// Handle delete text.
ShortcutEventHandler deleteTextHandler = (editorState, event) {
@ -126,33 +128,34 @@ KeyEventResult _backDeleteToPreviousTextNode(
List<Node> nonTextNodes,
Selection selection,
) {
// Not reach to the root.
// if (textNode.parent?.parent != null) {
// transactionBuilder
// ..deleteNode(textNode)
// ..insertNode(textNode.parent!.path.next, textNode)
// ..afterSelection = Selection.collapsed(
// Position(path: textNode.parent!.path.next, offset: 0),
// )
// ..commit();
// return KeyEventResult.handled;
// }
if (textNode.next == null &&
textNode.children.isEmpty &&
textNode.parent?.parent != null) {
transactionBuilder
..deleteNode(textNode)
..insertNode(textNode.parent!.path.next, textNode)
..afterSelection = Selection.collapsed(
Position(path: textNode.parent!.path.next, offset: 0),
)
..commit();
return KeyEventResult.handled;
}
bool prevIsNumberList = false;
final previousTextNode = forwardNearestTextNode(textNode);
final previousTextNode = Infra.forwardNearestTextNode(textNode);
if (previousTextNode != null) {
if (previousTextNode.subtype == BuiltInAttributeKey.numberList) {
prevIsNumberList = true;
}
transactionBuilder.mergeText(previousTextNode, textNode);
transactionBuilder.deleteNode(textNode);
if (textNode.children.isNotEmpty) {
transactionBuilder.insertNodes(
previousTextNode.path + [0],
previousTextNode.path.next,
textNode.children.toList(growable: false),
);
}
transactionBuilder.deleteNode(textNode);
transactionBuilder.afterSelection = Selection.collapsed(
Position(
path: previousTextNode.path,
@ -273,46 +276,3 @@ void _deleteTextNodes(TransactionBuilder transactionBuilder,
secondOffset: selection.end.offset,
);
}
// TODO: Just a simple solution for textNode, need to be optimized.
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;
}

View File

@ -0,0 +1,51 @@
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_editor/src/infra/infra.dart';
import 'package:flutter_test/flutter_test.dart';
void main() async {
group('infra.dart', () {
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(Infra.findLastTextNode(node)?.path, [1, 1, 1]);
expect(Infra.findLastTextNode(node0)?.path, [0]);
expect(Infra.findLastTextNode(node1)?.path, [1, 1, 1]);
expect(Infra.findLastTextNode(node10)?.path, [1, 0]);
expect(Infra.findLastTextNode(node11)?.path, [1, 1, 1]);
expect(Infra.forwardNearestTextNode(node111)?.path, [1, 1, 0]);
expect(Infra.forwardNearestTextNode(node110)?.path, [1, 1]);
expect(Infra.forwardNearestTextNode(node11)?.path, [1, 0]);
expect(Infra.forwardNearestTextNode(node10)?.path, [1]);
expect(Infra.forwardNearestTextNode(node1)?.path, [0]);
expect(Infra.forwardNearestTextNode(node0)?.path, []);
});
});
}

View File

@ -3,7 +3,6 @@ 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';
@ -383,73 +382,26 @@ void main() async {
// * Welcome to Appflowy 😁
// After
// * Welcome to Appflowy 😁
// * Welcome to Appflowy 😁Welcome to Appflowy 😁
// * 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,
editor.nodeAtPath([0, 0])!.subtype == BuiltInAttributeKey.bulletedList,
true,
);
expect(
editor.nodeAtPath([1, 0])!.subtype == BuiltInAttributeKey.bulletedList,
(editor.nodeAtPath([0, 0]) as TextNode).toRawString() == text * 2,
true,
);
expect(
editor.nodeAtPath([1, 1])!.subtype == BuiltInAttributeKey.bulletedList,
editor.nodeAtPath([0, 1])!.subtype == BuiltInAttributeKey.bulletedList,
true,
);
expect(
editor.nodeAtPath([0, 2])!.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, []);
});
}