node.dart 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. import 'dart:collection';
  2. import 'package:flowy_editor/document/path.dart';
  3. import 'package:flowy_editor/document/text_delta.dart';
  4. import 'package:flowy_editor/operation/operation.dart';
  5. import 'package:flutter/material.dart';
  6. import './attributes.dart';
  7. class Node extends ChangeNotifier with LinkedListEntry<Node> {
  8. Node? parent;
  9. final String type;
  10. final LinkedList<Node> children;
  11. final Attributes attributes;
  12. GlobalKey? key;
  13. // TODO: abstract a selectable node??
  14. final layerLink = LayerLink();
  15. String? get subtype {
  16. // TODO: make 'subtype' as a const value.
  17. if (attributes.containsKey('subtype')) {
  18. assert(attributes['subtype'] is String?,
  19. 'subtype must be a [String] or [null]');
  20. return attributes['subtype'] as String?;
  21. }
  22. return null;
  23. }
  24. Path get path => _path();
  25. Node({
  26. required this.type,
  27. required this.children,
  28. required this.attributes,
  29. this.parent,
  30. });
  31. factory Node.fromJson(Map<String, Object> json) {
  32. assert(json['type'] is String);
  33. // TODO: check the type that not exist on plugins.
  34. final jType = json['type'] as String;
  35. final jChildren = json['children'] as List?;
  36. final jAttributes = json['attributes'] != null
  37. ? Attributes.from(json['attributes'] as Map)
  38. : Attributes.from({});
  39. final LinkedList<Node> children = LinkedList();
  40. if (jChildren != null) {
  41. children.addAll(
  42. jChildren.map(
  43. (jChild) => Node.fromJson(
  44. Map<String, Object>.from(jChild),
  45. ),
  46. ),
  47. );
  48. }
  49. Node node;
  50. if (jType == "text") {
  51. final jDelta = json['delta'] as List<dynamic>?;
  52. final delta = jDelta == null ? Delta() : Delta.fromJson(jDelta);
  53. node = TextNode(
  54. type: jType,
  55. children: children,
  56. attributes: jAttributes,
  57. delta: delta);
  58. } else {
  59. node = Node(
  60. type: jType,
  61. children: children,
  62. attributes: jAttributes,
  63. );
  64. }
  65. for (final child in children) {
  66. child.parent = node;
  67. }
  68. return node;
  69. }
  70. void updateAttributes(Attributes attributes) {
  71. bool shouldNotifyParent =
  72. this.attributes['subtype'] != attributes['subtype'];
  73. for (final attribute in attributes.entries) {
  74. this.attributes[attribute.key] = attribute.value;
  75. }
  76. // Notify the new attributes
  77. // if attributes contains 'subtype', should notify parent to rebuild node
  78. // else, just notify current node.
  79. shouldNotifyParent ? parent?.notifyListeners() : notifyListeners();
  80. }
  81. Node? childAtIndex(int index) {
  82. if (children.length <= index) {
  83. return null;
  84. }
  85. return children.elementAt(index);
  86. }
  87. Node? childAtPath(Path path) {
  88. if (path.isEmpty) {
  89. return this;
  90. }
  91. return childAtIndex(path.first)?.childAtPath(path.sublist(1));
  92. }
  93. @override
  94. void insertAfter(Node entry) {
  95. entry.parent = parent;
  96. super.insertAfter(entry);
  97. // Notify the new node.
  98. parent?.notifyListeners();
  99. }
  100. @override
  101. void insertBefore(Node entry) {
  102. entry.parent = parent;
  103. super.insertBefore(entry);
  104. // Notify the new node.
  105. parent?.notifyListeners();
  106. }
  107. @override
  108. void unlink() {
  109. super.unlink();
  110. parent?.notifyListeners();
  111. parent = null;
  112. }
  113. Map<String, Object> toJson() {
  114. var map = <String, Object>{
  115. 'type': type,
  116. };
  117. if (children.isNotEmpty) {
  118. map['children'] = children.map((node) => node.toJson());
  119. }
  120. if (attributes.isNotEmpty) {
  121. map['attributes'] = attributes;
  122. }
  123. return map;
  124. }
  125. Path _path([Path previous = const []]) {
  126. if (parent == null) {
  127. return previous;
  128. }
  129. var index = 0;
  130. for (var child in parent!.children) {
  131. if (child == this) {
  132. break;
  133. }
  134. index += 1;
  135. }
  136. return parent!._path([index, ...previous]);
  137. }
  138. }
  139. class TextNode extends Node {
  140. Delta _delta;
  141. TextNode({
  142. required super.type,
  143. required super.children,
  144. required super.attributes,
  145. required Delta delta,
  146. }) : _delta = delta;
  147. TextNode.empty()
  148. : _delta = Delta([TextInsert('')]),
  149. super(
  150. type: 'text',
  151. children: LinkedList(),
  152. attributes: {},
  153. );
  154. Delta get delta {
  155. return _delta;
  156. }
  157. set delta(Delta v) {
  158. _delta = v;
  159. notifyListeners();
  160. }
  161. @override
  162. Map<String, Object> toJson() {
  163. final map = super.toJson();
  164. map['delta'] = _delta.toJson();
  165. return map;
  166. }
  167. // TODO: It's unneccesry to compute everytime.
  168. String toRawString() =>
  169. _delta.operations.whereType<TextInsert>().map((op) => op.content).join();
  170. }