diff --git a/frontend/app_flowy/packages/flowy_editor/assets/document.json b/frontend/app_flowy/packages/flowy_editor/assets/document.json index 715b6c4e64..5c9ca5175b 100644 --- a/frontend/app_flowy/packages/flowy_editor/assets/document.json +++ b/frontend/app_flowy/packages/flowy_editor/assets/document.json @@ -40,6 +40,7 @@ "attributes": { "url": "x.mp4" } + } ] } diff --git a/frontend/app_flowy/packages/flowy_editor/lib/document/node.dart b/frontend/app_flowy/packages/flowy_editor/lib/document/node.dart index deef71b7ec..743ae6fd15 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/document/node.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/document/node.dart @@ -1,5 +1,4 @@ import 'dart:collection'; - import 'package:flowy_editor/document/path.dart'; class Node extends LinkedListEntry { @@ -35,11 +34,23 @@ class Node extends LinkedListEntry { ); } - return Node( + final node = Node( type: jType, children: children, attributes: jAttributes, ); + + for (final child in children) { + child.parent = node; + } + + return node; + } + + void updateAttributes(Map attributes) { + for (final attribute in attributes.entries) { + this.attributes[attribute.key] = attribute.value; + } } Node? childAtIndex(int index) { @@ -58,6 +69,24 @@ class Node extends LinkedListEntry { return childAtIndex(path.first)?.childAtPath(path.sublist(1)); } + @override + void insertAfter(Node entry) { + entry.parent = parent; + super.insertAfter(entry); + } + + @override + void insertBefore(Node entry) { + entry.parent = parent; + super.insertBefore(entry); + } + + @override + void unlink() { + parent = null; + super.unlink(); + } + Map toJson() { var map = { 'type': type, diff --git a/frontend/app_flowy/packages/flowy_editor/lib/document/state_tree.dart b/frontend/app_flowy/packages/flowy_editor/lib/document/state_tree.dart index 82ee25976c..57b601c0e2 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/document/state_tree.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/document/state_tree.dart @@ -1,7 +1,8 @@ import 'package:flowy_editor/document/node.dart'; +import 'package:flowy_editor/document/path.dart'; class StateTree { - Node root; + final Node root; StateTree({required this.root}); @@ -13,8 +14,43 @@ class StateTree { return StateTree(root: root); } - // bool insert(Path path, Node node) { - // final insertedNode = root - // return false; - // } + Node? nodeAtPath(Path path) { + return root.childAtPath(path); + } + + bool insert(Path path, Node node) { + if (path.isEmpty) { + return false; + } + final insertedNode = root.childAtPath( + path.sublist(0, path.length - 1) + [path.last - 1], + ); + if (insertedNode == null) { + return false; + } + insertedNode.insertAfter(node); + return true; + } + + Node? delete(Path path) { + if (path.isEmpty) { + return null; + } + final deletedNode = root.childAtPath(path); + deletedNode?.unlink(); + return deletedNode; + } + + Map? update(Path path, Map attributes) { + if (path.isEmpty) { + return null; + } + final updatedNode = root.childAtPath(path); + if (updatedNode == null) { + return null; + } + final previousAttributes = {...updatedNode.attributes}; + updatedNode.updateAttributes(attributes); + return previousAttributes; + } } diff --git a/frontend/app_flowy/packages/flowy_editor/test/flowy_editor_test.dart b/frontend/app_flowy/packages/flowy_editor/test/flowy_editor_test.dart index 0c9dcfee0c..8e3c93b865 100644 --- a/frontend/app_flowy/packages/flowy_editor/test/flowy_editor_test.dart +++ b/frontend/app_flowy/packages/flowy_editor/test/flowy_editor_test.dart @@ -1,5 +1,6 @@ import 'dart:convert'; +import 'package:flowy_editor/document/node.dart'; import 'package:flowy_editor/document/state_tree.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -13,11 +14,48 @@ void main() { final stateTree = StateTree.fromJson(data); expect(stateTree.root.type, 'root'); expect(stateTree.root.toJson(), data['document']); - expect(stateTree.root.children.last.type, 'video'); + }); + test('search node in state tree', () async { + final String response = await rootBundle.loadString('assets/document.json'); + final data = Map.from(json.decode(response)); + final stateTree = StateTree.fromJson(data); final checkBoxNode = stateTree.root.childAtPath([1, 0]); expect(checkBoxNode != null, true); final textType = checkBoxNode!.attributes['text-type']; expect(textType != null, true); }); + + test('insert node in state tree', () async { + final String response = await rootBundle.loadString('assets/document.json'); + final data = Map.from(json.decode(response)); + final stateTree = StateTree.fromJson(data); + final insertNode = Node.fromJson({ + 'type': 'text', + }); + bool result = stateTree.insert([1, 1], insertNode); + expect(result, true); + expect(identical(insertNode, stateTree.nodeAtPath([1, 1])), true); + }); + + test('delete node in state tree', () async { + final String response = await rootBundle.loadString('assets/document.json'); + final data = Map.from(json.decode(response)); + final stateTree = StateTree.fromJson(data); + final deletedNode = stateTree.delete([1, 0]); + expect(deletedNode != null, true); + expect(deletedNode!.attributes['text-type'], 'check-box'); + }); + + test('update node in state tree', () async { + final String response = await rootBundle.loadString('assets/document.json'); + final data = Map.from(json.decode(response)); + final stateTree = StateTree.fromJson(data); + final attributes = stateTree.update([1, 0], {'text-type': 'heading1'}); + expect(attributes != null, true); + expect(attributes!['text-type'], 'check-box'); + final updatedNode = stateTree.nodeAtPath([1, 0]); + expect(updatedNode != null, true); + expect(updatedNode!.attributes['text-type'], 'heading1'); + }); }