mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
fix: delete the nested bulleted list will lost the children nodes
This commit is contained in:
parent
d648f2b5b9
commit
6bda1fd2ea
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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:appflowy_editor/src/service/internal_key_event_handlers/number_list_helper.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
import 'package:appflowy_editor/src/extensions/path_extensions.dart';
|
||||||
|
|
||||||
// Handle delete text.
|
// Handle delete text.
|
||||||
ShortcutEventHandler deleteTextHandler = (editorState, event) {
|
ShortcutEventHandler deleteTextHandler = (editorState, event) {
|
||||||
@ -126,33 +128,34 @@ KeyEventResult _backDeleteToPreviousTextNode(
|
|||||||
List<Node> nonTextNodes,
|
List<Node> nonTextNodes,
|
||||||
Selection selection,
|
Selection selection,
|
||||||
) {
|
) {
|
||||||
// Not reach to the root.
|
if (textNode.next == null &&
|
||||||
// if (textNode.parent?.parent != null) {
|
textNode.children.isEmpty &&
|
||||||
// transactionBuilder
|
textNode.parent?.parent != null) {
|
||||||
// ..deleteNode(textNode)
|
transactionBuilder
|
||||||
// ..insertNode(textNode.parent!.path.next, textNode)
|
..deleteNode(textNode)
|
||||||
// ..afterSelection = Selection.collapsed(
|
..insertNode(textNode.parent!.path.next, textNode)
|
||||||
// Position(path: textNode.parent!.path.next, offset: 0),
|
..afterSelection = Selection.collapsed(
|
||||||
// )
|
Position(path: textNode.parent!.path.next, offset: 0),
|
||||||
// ..commit();
|
)
|
||||||
// return KeyEventResult.handled;
|
..commit();
|
||||||
// }
|
return KeyEventResult.handled;
|
||||||
|
}
|
||||||
|
|
||||||
bool prevIsNumberList = false;
|
bool prevIsNumberList = false;
|
||||||
final previousTextNode = forwardNearestTextNode(textNode);
|
final previousTextNode = Infra.forwardNearestTextNode(textNode);
|
||||||
if (previousTextNode != null) {
|
if (previousTextNode != null) {
|
||||||
if (previousTextNode.subtype == BuiltInAttributeKey.numberList) {
|
if (previousTextNode.subtype == BuiltInAttributeKey.numberList) {
|
||||||
prevIsNumberList = true;
|
prevIsNumberList = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
transactionBuilder.mergeText(previousTextNode, textNode);
|
transactionBuilder.mergeText(previousTextNode, textNode);
|
||||||
transactionBuilder.deleteNode(textNode);
|
|
||||||
if (textNode.children.isNotEmpty) {
|
if (textNode.children.isNotEmpty) {
|
||||||
transactionBuilder.insertNodes(
|
transactionBuilder.insertNodes(
|
||||||
previousTextNode.path + [0],
|
previousTextNode.path.next,
|
||||||
textNode.children.toList(growable: false),
|
textNode.children.toList(growable: false),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
transactionBuilder.deleteNode(textNode);
|
||||||
transactionBuilder.afterSelection = Selection.collapsed(
|
transactionBuilder.afterSelection = Selection.collapsed(
|
||||||
Position(
|
Position(
|
||||||
path: previousTextNode.path,
|
path: previousTextNode.path,
|
||||||
@ -273,46 +276,3 @@ void _deleteTextNodes(TransactionBuilder transactionBuilder,
|
|||||||
secondOffset: selection.end.offset,
|
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;
|
|
||||||
}
|
|
||||||
|
@ -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, []);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -3,7 +3,6 @@ import 'dart:collection';
|
|||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:appflowy_editor/src/render/image/image_node_widget.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/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/services.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:network_image_mock/network_image_mock.dart';
|
import 'package:network_image_mock/network_image_mock.dart';
|
||||||
@ -383,73 +382,26 @@ void main() async {
|
|||||||
// * Welcome to Appflowy 😁
|
// * Welcome to Appflowy 😁
|
||||||
// After
|
// 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 😁
|
|
||||||
// * Welcome to Appflowy 😁
|
|
||||||
// * Welcome to Appflowy 😁
|
|
||||||
await editor.pressLogicKey(LogicalKeyboardKey.backspace);
|
await editor.pressLogicKey(LogicalKeyboardKey.backspace);
|
||||||
expect(
|
expect(
|
||||||
editor.nodeAtPath([1])!.subtype != BuiltInAttributeKey.bulletedList,
|
editor.nodeAtPath([0, 0])!.subtype == BuiltInAttributeKey.bulletedList,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
editor.nodeAtPath([1, 0])!.subtype == BuiltInAttributeKey.bulletedList,
|
(editor.nodeAtPath([0, 0]) as TextNode).toRawString() == text * 2,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
expect(
|
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,
|
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, []);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user