diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/infra/infra.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/infra/infra.dart new file mode 100644 index 0000000000..346e9331f5 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/infra/infra.dart @@ -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; + } +} diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/backspace_handler.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/backspace_handler.dart index c9504918af..3f7a54810f 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/backspace_handler.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/backspace_handler.dart @@ -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 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; -} diff --git a/frontend/app_flowy/packages/appflowy_editor/test/infra/infra_test.dart b/frontend/app_flowy/packages/appflowy_editor/test/infra/infra_test.dart new file mode 100644 index 0000000000..e03d524d76 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_editor/test/infra/infra_test.dart @@ -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, []); + }); + }); +} diff --git a/frontend/app_flowy/packages/appflowy_editor/test/service/internal_key_event_handlers/backspace_handler_test.dart b/frontend/app_flowy/packages/appflowy_editor/test/service/internal_key_event_handlers/backspace_handler_test.dart index 246c6ecb27..37ad7bade3 100644 --- a/frontend/app_flowy/packages/appflowy_editor/test/service/internal_key_event_handlers/backspace_handler_test.dart +++ b/frontend/app_flowy/packages/appflowy_editor/test/service/internal_key_event_handlers/backspace_handler_test.dart @@ -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, []); }); }