mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
refactor: text-delta
This commit is contained in:
parent
e25b507ccb
commit
737e374a54
@ -219,7 +219,5 @@ class TextNode extends Node {
|
||||
delta: delta ?? this.delta,
|
||||
);
|
||||
|
||||
// TODO: It's unneccesry to compute everytime.
|
||||
String toRawString() =>
|
||||
_delta.operations.whereType<TextInsert>().map((op) => op.content).join();
|
||||
String toRawString() => _delta.toRawString();
|
||||
}
|
||||
|
@ -257,8 +257,8 @@ TextOperation? _textOperationFromJson(Map<String, dynamic> json) {
|
||||
}
|
||||
|
||||
// basically copy from: https://github.com/quilljs/delta
|
||||
class Delta {
|
||||
final List<TextOperation> operations;
|
||||
class Delta extends Iterable<TextOperation> {
|
||||
final List<TextOperation> _operations;
|
||||
|
||||
factory Delta.fromJson(List<dynamic> list) {
|
||||
final operations = <TextOperation>[];
|
||||
@ -273,9 +273,9 @@ class Delta {
|
||||
return Delta(operations);
|
||||
}
|
||||
|
||||
Delta([List<TextOperation>? ops]) : operations = ops ?? <TextOperation>[];
|
||||
Delta([List<TextOperation>? ops]) : _operations = ops ?? <TextOperation>[];
|
||||
|
||||
Delta addAll(List<TextOperation> textOps) {
|
||||
Delta addAll(Iterable<TextOperation> textOps) {
|
||||
textOps.forEach(add);
|
||||
return this;
|
||||
}
|
||||
@ -285,8 +285,8 @@ class Delta {
|
||||
return this;
|
||||
}
|
||||
|
||||
if (operations.isNotEmpty) {
|
||||
final lastOp = operations.last;
|
||||
if (_operations.isNotEmpty) {
|
||||
final lastOp = _operations.last;
|
||||
if (lastOp is TextDelete && textOp is TextDelete) {
|
||||
lastOp.length += textOp.length;
|
||||
return this;
|
||||
@ -299,9 +299,9 @@ class Delta {
|
||||
// if there is an delete before the insert
|
||||
// swap the order
|
||||
if (lastOp is TextDelete && textOp is TextInsert) {
|
||||
operations.removeLast();
|
||||
operations.add(textOp);
|
||||
operations.add(lastOp);
|
||||
_operations.removeLast();
|
||||
_operations.add(textOp);
|
||||
_operations.add(lastOp);
|
||||
return this;
|
||||
}
|
||||
if (lastOp is TextRetain && textOp is TextRetain) {
|
||||
@ -311,13 +311,13 @@ class Delta {
|
||||
}
|
||||
}
|
||||
|
||||
operations.add(textOp);
|
||||
_operations.add(textOp);
|
||||
return this;
|
||||
}
|
||||
|
||||
Delta slice(int start, [int? end]) {
|
||||
final result = Delta();
|
||||
final iterator = _OpIterator(operations);
|
||||
final iterator = _OpIterator(_operations);
|
||||
int index = 0;
|
||||
|
||||
while ((end == null || index < end) && iterator.hasNext) {
|
||||
@ -351,13 +351,13 @@ class Delta {
|
||||
}
|
||||
|
||||
int get length {
|
||||
return operations.fold(
|
||||
return _operations.fold(
|
||||
0, (previousValue, element) => previousValue + element.length);
|
||||
}
|
||||
|
||||
Delta compose(Delta other) {
|
||||
final thisIter = _OpIterator(operations);
|
||||
final otherIter = _OpIterator(other.operations);
|
||||
final thisIter = _OpIterator(_operations);
|
||||
final otherIter = _OpIterator(other._operations);
|
||||
final ops = <TextOperation>[];
|
||||
|
||||
final firstOther = otherIter.peek();
|
||||
@ -405,7 +405,7 @@ class Delta {
|
||||
|
||||
// Optimization if rest of other is just retain
|
||||
if (!otherIter.hasNext &&
|
||||
delta.operations[delta.operations.length - 1] == newOp) {
|
||||
delta._operations[delta._operations.length - 1] == newOp) {
|
||||
final rest = Delta(thisIter.rest());
|
||||
return delta.concat(rest).chop();
|
||||
}
|
||||
@ -419,21 +419,21 @@ class Delta {
|
||||
}
|
||||
|
||||
Delta concat(Delta other) {
|
||||
var ops = [...operations];
|
||||
if (other.operations.isNotEmpty) {
|
||||
ops.add(other.operations[0]);
|
||||
ops.addAll(other.operations.sublist(1));
|
||||
var ops = [..._operations];
|
||||
if (other._operations.isNotEmpty) {
|
||||
ops.add(other._operations[0]);
|
||||
ops.addAll(other._operations.sublist(1));
|
||||
}
|
||||
return Delta(ops);
|
||||
}
|
||||
|
||||
Delta chop() {
|
||||
if (operations.isEmpty) {
|
||||
if (_operations.isEmpty) {
|
||||
return this;
|
||||
}
|
||||
final lastOp = operations.last;
|
||||
final lastOp = _operations.last;
|
||||
if (lastOp is TextRetain && (lastOp.attributes?.length ?? 0) == 0) {
|
||||
operations.removeLast();
|
||||
_operations.removeLast();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@ -443,17 +443,17 @@ class Delta {
|
||||
if (other is! Delta) {
|
||||
return false;
|
||||
}
|
||||
return listEquals(operations, other.operations);
|
||||
return listEquals(_operations, other._operations);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return hashList(operations);
|
||||
return hashList(_operations);
|
||||
}
|
||||
|
||||
Delta invert(Delta base) {
|
||||
final inverted = Delta();
|
||||
operations.fold(0, (int previousValue, op) {
|
||||
_operations.fold(0, (int previousValue, op) {
|
||||
if (op is TextInsert) {
|
||||
inverted.delete(op.length);
|
||||
} else if (op is TextRetain && op.attributes == null) {
|
||||
@ -462,7 +462,7 @@ class Delta {
|
||||
} else if (op is TextDelete || op is TextRetain) {
|
||||
final length = op.length;
|
||||
final slice = base.slice(previousValue, previousValue + length);
|
||||
for (final baseOp in slice.operations) {
|
||||
for (final baseOp in slice._operations) {
|
||||
if (op is TextDelete) {
|
||||
inverted.add(baseOp);
|
||||
} else if (op is TextRetain && op.attributes != null) {
|
||||
@ -478,6 +478,13 @@ class Delta {
|
||||
}
|
||||
|
||||
List<dynamic> toJson() {
|
||||
return operations.map((e) => e.toJson()).toList();
|
||||
return _operations.map((e) => e.toJson()).toList();
|
||||
}
|
||||
|
||||
// TODO: It's unneccesry to compute everytime.
|
||||
String toRawString() =>
|
||||
_operations.whereType<TextInsert>().map((op) => op.content).join();
|
||||
|
||||
@override
|
||||
Iterator<TextOperation> get iterator => _operations.iterator;
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ extension TextNodeExtension on TextNode {
|
||||
allSatisfyInSelection(StyleKey.strikethrough, selection);
|
||||
|
||||
bool allSatisfyInSelection(String styleKey, Selection selection) {
|
||||
final ops = delta.operations.whereType<TextInsert>();
|
||||
final ops = delta.whereType<TextInsert>();
|
||||
var start = 0;
|
||||
for (final op in ops) {
|
||||
if (start >= selection.end.offset) {
|
||||
|
@ -71,7 +71,7 @@ class HTMLToNodesConverter {
|
||||
delta.insert(child.text ?? "");
|
||||
}
|
||||
}
|
||||
if (delta.operations.isNotEmpty) {
|
||||
if (delta.isNotEmpty) {
|
||||
result.add(TextNode(type: "text", delta: delta));
|
||||
}
|
||||
return result;
|
||||
@ -101,7 +101,7 @@ class HTMLToNodesConverter {
|
||||
} else {
|
||||
final delta = Delta();
|
||||
delta.insert(element.text);
|
||||
if (delta.operations.isNotEmpty) {
|
||||
if (delta.isNotEmpty) {
|
||||
return [TextNode(type: "text", delta: delta)];
|
||||
}
|
||||
}
|
||||
@ -446,7 +446,7 @@ class NodesToHTMLConverter {
|
||||
childNodes.add(node);
|
||||
}
|
||||
|
||||
for (final op in delta.operations) {
|
||||
for (final op in delta) {
|
||||
if (op is TextInsert) {
|
||||
final attributes = op.attributes;
|
||||
if (attributes != null) {
|
||||
|
@ -94,7 +94,7 @@ class TransactionBuilder {
|
||||
() => Delta()
|
||||
..retain(firstOffset ?? firstLength)
|
||||
..delete(firstLength - (firstOffset ?? firstLength))
|
||||
..addAll(secondNode.delta.slice(secondOffset, secondLength).operations),
|
||||
..addAll(secondNode.delta.slice(secondOffset, secondLength)),
|
||||
);
|
||||
afterSelection = Selection.collapsed(
|
||||
Position(
|
||||
@ -108,11 +108,8 @@ class TransactionBuilder {
|
||||
[Attributes? attributes]) {
|
||||
var newAttributes = attributes;
|
||||
if (index != 0 && attributes == null) {
|
||||
newAttributes = node.delta
|
||||
.slice(max(index - 1, 0), index)
|
||||
.operations
|
||||
.first
|
||||
.attributes;
|
||||
newAttributes =
|
||||
node.delta.slice(max(index - 1, 0), index).first.attributes;
|
||||
}
|
||||
textEdit(
|
||||
node,
|
||||
@ -140,7 +137,7 @@ class TransactionBuilder {
|
||||
[Attributes? attributes]) {
|
||||
var newAttributes = attributes;
|
||||
if (attributes == null) {
|
||||
final ops = node.delta.slice(index, index + length).operations;
|
||||
final ops = node.delta.slice(index, index + length);
|
||||
if (ops.isNotEmpty) {
|
||||
newAttributes = ops.first.attributes;
|
||||
}
|
||||
|
@ -198,7 +198,7 @@ class _FlowyRichTextState extends State<FlowyRichText> with Selectable {
|
||||
}
|
||||
|
||||
TextSpan get _textSpan => TextSpan(
|
||||
children: widget.textNode.delta.operations
|
||||
children: widget.textNode.delta
|
||||
.whereType<TextInsert>()
|
||||
.map((insert) => RichTextStyle(
|
||||
attributes: insert.attributes ?? {},
|
||||
|
@ -175,8 +175,7 @@ _handlePastePlainText(EditorState editorState, String plainText) {
|
||||
final nodes = remains.map((e) {
|
||||
if (index++ == remains.length - 1) {
|
||||
return TextNode(
|
||||
type: "text",
|
||||
delta: Delta().insert(e).addAll(insertedLineSuffix.operations));
|
||||
type: "text", delta: Delta().insert(e).addAll(insertedLineSuffix));
|
||||
}
|
||||
return TextNode(type: "text", delta: Delta().insert(e));
|
||||
}).toList();
|
||||
@ -246,7 +245,7 @@ _deleteSelectedContent(EditorState editorState) {
|
||||
|
||||
if (endNode is TextNode) {
|
||||
final remain = endNode.delta.slice(selection.end.offset);
|
||||
delta.addAll(remain.operations);
|
||||
delta.addAll(remain);
|
||||
}
|
||||
|
||||
return delta;
|
||||
|
@ -19,7 +19,7 @@ void main() {
|
||||
}).delete(4);
|
||||
|
||||
final restores = delta.compose(death);
|
||||
expect(restores.operations, <TextOperation>[
|
||||
expect(restores.toList(), <TextOperation>[
|
||||
TextInsert('Gandalf', {'bold': true}),
|
||||
TextInsert(' the '),
|
||||
TextInsert('White', {'color': '#fff'}),
|
||||
|
Loading…
Reference in New Issue
Block a user