|
|
|
import 'dart:collection';
|
|
|
|
|
|
|
|
import '../../../widgets/embeds.dart';
|
|
|
|
import '../../quill_delta.dart';
|
|
|
|
import '../attribute.dart';
|
|
|
|
import '../style.dart';
|
|
|
|
import 'container.dart';
|
|
|
|
import 'line.dart';
|
|
|
|
|
|
|
|
/// An abstract node in a document tree.
|
|
|
|
///
|
|
|
|
/// Represents a segment of a Quill document with specified [offset]
|
|
|
|
/// and [length].
|
|
|
|
///
|
|
|
|
/// The [offset] property is relative to [parent]. See also [documentOffset]
|
|
|
|
/// which provides absolute offset of this node within the document.
|
|
|
|
///
|
|
|
|
/// The current parent node is exposed by the [parent] property. A node is
|
|
|
|
/// considered [mounted] when the [parent] property is not `null`.
|
|
|
|
abstract base class Node extends LinkedListEntry<Node> {
|
|
|
|
/// Current parent of this node. May be null if this node is not mounted.
|
|
|
|
Container? parent;
|
|
|
|
|
|
|
|
/// The style attributes
|
|
|
|
/// Note: This is not the same as style attribute of css
|
|
|
|
///
|
|
|
|
/// Example:
|
|
|
|
///
|
|
|
|
/// {
|
|
|
|
/// "insert": {
|
|
|
|
/// "image": "https://user-images.githubusercontent.com/122956/72955931-ccc07900-3d52-11ea-89b1-d468a6e2aa2b.png"
|
|
|
|
/// },
|
|
|
|
/// "attributes": { // this one
|
|
|
|
/// "width": "230",
|
|
|
|
/// "style": "display: block; margin: auto; width: 500px;" // Not this
|
|
|
|
/// }
|
|
|
|
/// },
|
|
|
|
Style get style => _style;
|
|
|
|
Style _style = const Style();
|
|
|
|
|
|
|
|
/// Returns `true` if this node is the first node in the [parent] list.
|
|
|
|
bool get isFirst => list!.first == this;
|
|
|
|
|
|
|
|
/// Returns `true` if this node is the last node in the [parent] list.
|
|
|
|
bool get isLast => list!.last == this;
|
|
|
|
|
|
|
|
/// Length of this node in characters.
|
|
|
|
int get length;
|
|
|
|
|
|
|
|
Node clone() => newInstance()..applyStyle(style);
|
|
|
|
|
|
|
|
/// Offset in characters of this node relative to [parent] node.
|
|
|
|
///
|
|
|
|
/// To get offset of this node in the document see [documentOffset].
|
|
|
|
int get offset {
|
|
|
|
var offset = 0;
|
|
|
|
|
|
|
|
if (list == null || isFirst) {
|
|
|
|
return offset;
|
|
|
|
}
|
|
|
|
|
|
|
|
var cur = this;
|
|
|
|
do {
|
|
|
|
cur = cur.previous!;
|
|
|
|
offset += cur.length;
|
|
|
|
} while (!cur.isFirst);
|
|
|
|
return offset;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Offset in characters of this node in the document.
|
|
|
|
int get documentOffset {
|
|
|
|
if (parent == null) {
|
|
|
|
return offset;
|
|
|
|
}
|
|
|
|
final parentOffset = (parent is! Root) ? parent!.documentOffset : 0;
|
|
|
|
return parentOffset + offset;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns `true` if this node contains character at specified [offset] in
|
|
|
|
/// the document.
|
|
|
|
bool containsOffset(int offset) {
|
|
|
|
final o = documentOffset;
|
|
|
|
return o <= offset && offset < o + length;
|
|
|
|
}
|
|
|
|
|
|
|
|
void applyAttribute(Attribute attribute) {
|
|
|
|
_style = _style.merge(attribute);
|
|
|
|
}
|
|
|
|
|
|
|
|
void applyStyle(Style value) {
|
|
|
|
_style = _style.mergeAll(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
void clearStyle() {
|
|
|
|
_style = const Style();
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void insertBefore(Node entry) {
|
|
|
|
assert(entry.parent == null && parent != null);
|
|
|
|
entry.parent = parent;
|
|
|
|
super.insertBefore(entry);
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void insertAfter(Node entry) {
|
|
|
|
assert(entry.parent == null && parent != null);
|
|
|
|
entry.parent = parent;
|
|
|
|
super.insertAfter(entry);
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void unlink() {
|
|
|
|
assert(parent != null);
|
|
|
|
parent = null;
|
|
|
|
super.unlink();
|
|
|
|
}
|
|
|
|
|
|
|
|
void adjust() {
|
|
|
|
/* no-op */
|
|
|
|
}
|
|
|
|
|
|
|
|
/// abstract methods begin
|
|
|
|
|
|
|
|
Node newInstance();
|
|
|
|
|
|
|
|
String toPlainText([
|
|
|
|
Iterable<EmbedBuilder>? embedBuilders,
|
|
|
|
EmbedBuilder? unknownEmbedBuilder,
|
|
|
|
]);
|
|
|
|
|
|
|
|
Delta toDelta();
|
|
|
|
|
|
|
|
void insert(int index, Object data, Style? style);
|
|
|
|
|
|
|
|
void retain(int index, int? len, Style? style);
|
|
|
|
|
|
|
|
void delete(int index, int? len);
|
|
|
|
|
|
|
|
/// abstract methods end
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Root node of document tree.
|
|
|
|
base class Root extends Container<Container<Node?>> {
|
|
|
|
@override
|
|
|
|
Node newInstance() => Root();
|
|
|
|
|
|
|
|
@override
|
|
|
|
Container<Node?> get defaultChild => Line();
|
|
|
|
|
|
|
|
@override
|
|
|
|
Delta toDelta() => children
|
|
|
|
.map((child) => child.toDelta())
|
|
|
|
.fold(Delta(), (a, b) => a.concat(b));
|
|
|
|
}
|