dartlangeditorflutterflutter-appsflutter-examplesflutter-packageflutter-widgetquillquill-deltaquilljsreactquillrich-textrich-text-editorwysiwygwysiwyg-editor
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
141 lines
3.3 KiB
141 lines
3.3 KiB
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; |
|
|
|
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)); |
|
}
|
|
|