import 'dart:async'; import 'package:tuple/tuple.dart'; import '../quill_delta.dart'; import '../rules/rule.dart'; import 'attribute.dart'; import 'history.dart'; import 'nodes/block.dart'; import 'nodes/container.dart'; import 'nodes/embeddable.dart'; import 'nodes/line.dart'; import 'nodes/node.dart'; import 'style.dart'; /// The rich text document class Document { Document() : _delta = Delta()..insert('\n') { _loadDocument(_delta); } Document.fromJson(List data) : _delta = _transform(Delta.fromJson(data)) { _loadDocument(_delta); } Document.fromDelta(Delta delta) : _delta = delta { _loadDocument(delta); } /// The root node of the document tree final Root _root = Root(); Root get root => _root; int get length => _root.length; Delta _delta; Delta toDelta() => Delta.from(_delta); final Rules _rules = Rules.getInstance(); void setCustomRules(List customRules) { _rules.setCustomRules(customRules); } final StreamController> _observer = StreamController.broadcast(); final History _history = History(); Stream> get changes => _observer.stream; Delta insert(int index, Object? data, {int replaceLength = 0}) { assert(index >= 0); assert(data is String || data is Embeddable); if (data is Embeddable) { data = data.toJson(); } else if ((data as String).isEmpty) { return Delta(); } final delta = _rules.apply(RuleType.INSERT, this, index, data: data, len: replaceLength); compose(delta, ChangeSource.LOCAL); return delta; } Delta delete(int index, int len) { assert(index >= 0 && len > 0); final delta = _rules.apply(RuleType.DELETE, this, index, len: len); if (delta.isNotEmpty) { compose(delta, ChangeSource.LOCAL); } return delta; } Delta replace(int index, int len, Object? data) { assert(index >= 0); assert(data is String || data is Embeddable); final dataIsNotEmpty = (data is String) ? data.isNotEmpty : true; assert(dataIsNotEmpty || len > 0); var delta = Delta(); // We have to insert before applying delete rules // Otherwise delete would be operating on stale document snapshot. if (dataIsNotEmpty) { delta = insert(index, data, replaceLength: len); } if (len > 0) { final deleteDelta = delete(index, len); delta = delta.compose(deleteDelta); } return delta; } Delta format(int index, int len, Attribute? attribute) { assert(index >= 0 && len >= 0 && attribute != null); var delta = Delta(); final formatDelta = _rules.apply(RuleType.FORMAT, this, index, len: len, attribute: attribute); if (formatDelta.isNotEmpty) { compose(formatDelta, ChangeSource.LOCAL); delta = delta.compose(formatDelta); } return delta; } /// Only attributes applied to all characters within this range are /// included in the result. Style collectStyle(int index, int len) { final res = queryChild(index); return (res.node as Line).collectStyle(res.offset, len); } /// Returns all styles for any character within the specified text range. List