Skip to content

Commit b53de98

Browse files
committed
Merging with upstream/master
2 parents 7a75468 + 7a6719d commit b53de98

18 files changed

+466
-118
lines changed

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
## [1.9.1]
2+
* Cursor jumps to the most appropriate offset to display selection.
3+
4+
## [1.9.0]
5+
* Support inline image.
6+
7+
## [1.8.3]
8+
* Updated quill_delta.
9+
10+
## [1.8.2]
11+
* Support mobile image alignment.
12+
113
## [1.8.1]
214
* Support mobile custom size image.
315

lib/src/models/documents/document.dart

Lines changed: 12 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,7 @@ class Document {
6060

6161
Stream<Tuple3<Delta, Delta, ChangeSource>> get changes => _observer.stream;
6262

63-
Delta insert(int index, Object? data,
64-
{int replaceLength = 0, bool autoAppendNewlineAfterImage = true}) {
63+
Delta insert(int index, Object? data, {int replaceLength = 0}) {
6564
assert(index >= 0);
6665
assert(data is String || data is Embeddable);
6766
if (data is Embeddable) {
@@ -72,8 +71,7 @@ class Document {
7271

7372
final delta = _rules.apply(RuleType.INSERT, this, index,
7473
data: data, len: replaceLength);
75-
compose(delta, ChangeSource.LOCAL,
76-
autoAppendNewlineAfterImage: autoAppendNewlineAfterImage);
74+
compose(delta, ChangeSource.LOCAL);
7775
return delta;
7876
}
7977

@@ -137,8 +135,7 @@ class Document {
137135
return delta;
138136
}
139137

140-
Delta replace(int index, int len, Object? data,
141-
{bool autoAppendNewlineAfterImage = true}) {
138+
Delta replace(int index, int len, Object? data) {
142139
assert(index >= 0);
143140
assert(data is String || data is Embeddable);
144141

@@ -151,9 +148,7 @@ class Document {
151148
// We have to insert before applying delete rules
152149
// Otherwise delete would be operating on stale document snapshot.
153150
if (dataIsNotEmpty) {
154-
delta = insert(index, data,
155-
replaceLength: len,
156-
autoAppendNewlineAfterImage: autoAppendNewlineAfterImage);
151+
delta = insert(index, data, replaceLength: len);
157152
}
158153

159154
if (len > 0) {
@@ -235,17 +230,13 @@ class Document {
235230
return block.queryChild(res.offset, true).node as Line;
236231
}
237232

238-
void compose(Delta delta, ChangeSource changeSource,
239-
{bool autoAppendNewlineAfterImage = true,
240-
bool autoAppendNewlineAfterVideo = true}) {
233+
void compose(Delta delta, ChangeSource changeSource) {
241234
assert(!_observer.isClosed);
242235
delta.trim();
243236
assert(delta.isNotEmpty);
244237

245238
var offset = 0;
246-
delta = _transform(delta,
247-
autoAppendNewlineAfterImage: autoAppendNewlineAfterImage,
248-
autoAppendNewlineAfterVideo: autoAppendNewlineAfterVideo);
239+
delta = _transform(delta);
249240
final originalDelta = toDelta();
250241
for (final op in delta.toList()) {
251242
final style =
@@ -289,44 +280,37 @@ class Document {
289280

290281
bool get hasRedo => _history.hasRedo;
291282

292-
static Delta _transform(Delta delta,
293-
{bool autoAppendNewlineAfterImage = true,
294-
bool autoAppendNewlineAfterVideo = true}) {
283+
static Delta _transform(Delta delta) {
295284
final res = Delta();
296285
final ops = delta.toList();
297286
for (var i = 0; i < ops.length; i++) {
298287
final op = ops[i];
299288
res.push(op);
300-
if (autoAppendNewlineAfterImage) {
301-
_autoAppendNewlineAfterEmbeddable(i, ops, op, res, 'image');
302-
}
303-
if (autoAppendNewlineAfterVideo) {
304-
_autoAppendNewlineAfterEmbeddable(i, ops, op, res, 'video');
305-
}
289+
_autoAppendNewlineAfterEmbeddable(i, ops, op, res, 'video');
306290
}
307291
return res;
308292
}
309293

310294
static void _autoAppendNewlineAfterEmbeddable(
311295
int i, List<Operation> ops, Operation op, Delta res, String type) {
312-
final nextOpIsImage = i + 1 < ops.length &&
296+
final nextOpIsEmbed = i + 1 < ops.length &&
313297
ops[i + 1].isInsert &&
314298
ops[i + 1].data is Map &&
315299
(ops[i + 1].data as Map).containsKey(type);
316-
if (nextOpIsImage &&
300+
if (nextOpIsEmbed &&
317301
op.data is String &&
318302
(op.data as String).isNotEmpty &&
319303
!(op.data as String).endsWith('\n')) {
320304
res.push(Operation.insert('\n'));
321305
}
322306
// embed could be image or video
323-
final opInsertImage =
307+
final opInsertEmbed =
324308
op.isInsert && op.data is Map && (op.data as Map).containsKey(type);
325309
final nextOpIsLineBreak = i + 1 < ops.length &&
326310
ops[i + 1].isInsert &&
327311
ops[i + 1].data is String &&
328312
(ops[i + 1].data as String).startsWith('\n');
329-
if (opInsertImage && (i + 1 == ops.length - 1 || !nextOpIsLineBreak)) {
313+
if (opInsertEmbed && (i + 1 == ops.length - 1 || !nextOpIsLineBreak)) {
330314
// automatically append '\n' for embeddable
331315
res.push(Operation.insert('\n'));
332316
}

lib/src/models/documents/nodes/leaf.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ class Text extends Leaf {
218218
super.val(text);
219219

220220
@override
221-
Node newInstance() => Text();
221+
Node newInstance() => Text(value);
222222

223223
@override
224224
String get value => _value as String;

lib/src/models/documents/nodes/line.dart

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,7 @@ class Line extends Container<Leaf?> {
4444

4545
/// Returns `true` if this line contains an embedded object.
4646
bool get hasEmbed {
47-
if (childCount != 1) {
48-
return false;
49-
}
50-
51-
return children.single is Embed;
47+
return children.any((child) => child is Embed);
5248
}
5349

5450
/// Returns next [Line] or `null` if this is the last line in the document.

lib/src/models/quill_delta.dart

Lines changed: 132 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ library quill_delta;
88
import 'dart:math' as math;
99

1010
import 'package:collection/collection.dart';
11+
import 'package:diff_match_patch/diff_match_patch.dart' as dmp;
1112
import 'package:quiver/core.dart';
1213

1314
const _attributeEquality = DeepCollectionEquality();
@@ -190,6 +191,9 @@ class Delta {
190191
factory Delta.from(Delta other) =>
191192
Delta._(List<Operation>.from(other._operations));
192193

194+
// Placeholder char for embed in diff()
195+
static final String _kNullCharacter = String.fromCharCode(0);
196+
193197
/// Transforms two attribute sets.
194198
static Map<String, dynamic>? transformAttributes(
195199
Map<String, dynamic>? a, Map<String, dynamic>? b, bool priority) {
@@ -248,6 +252,22 @@ class Delta {
248252
return inverted;
249253
}
250254

255+
/// Returns diff between two attribute sets
256+
static Map<String, dynamic>? diffAttributes(
257+
Map<String, dynamic>? a, Map<String, dynamic>? b) {
258+
a ??= const {};
259+
b ??= const {};
260+
261+
final attributes = <String, dynamic>{};
262+
(a.keys.toList()..addAll(b.keys)).forEach((key) {
263+
if (a![key] != b![key]) {
264+
attributes[key] = b.containsKey(key) ? b[key] : null;
265+
}
266+
});
267+
268+
return attributes.keys.isNotEmpty ? attributes : null;
269+
}
270+
251271
final List<Operation> _operations;
252272

253273
int _modificationCount = 0;
@@ -399,7 +419,7 @@ class Delta {
399419
if (thisIter.isNextDelete) return thisIter.next();
400420

401421
final length = math.min(thisIter.peekLength(), otherIter.peekLength());
402-
final thisOp = thisIter.next(length as int);
422+
final thisOp = thisIter.next(length);
403423
final otherOp = otherIter.next(length);
404424
assert(thisOp.length == otherOp.length);
405425

@@ -442,6 +462,94 @@ class Delta {
442462
return result..trim();
443463
}
444464

465+
/// Returns a new lazy Iterable with elements that are created by calling
466+
/// f on each element of this Iterable in iteration order.
467+
///
468+
/// Convenience method
469+
Iterable<T> map<T>(T Function(Operation) f) {
470+
return _operations.map<T>(f);
471+
}
472+
473+
/// Returns a [Delta] containing differences between 2 [Delta]s.
474+
/// If [cleanupSemantic] is `true` (default), applies the following:
475+
///
476+
/// The diff of "mouse" and "sofas" is
477+
/// [delete(1), insert("s"), retain(1),
478+
/// delete("u"), insert("fa"), retain(1), delete(1)].
479+
/// While this is the optimum diff, it is difficult for humans to understand.
480+
/// Semantic cleanup rewrites the diff,
481+
/// expanding it into a more intelligible format.
482+
/// The above example would become: [(-1, "mouse"), (1, "sofas")].
483+
/// (source: https://github.com/google/diff-match-patch/wiki/API)
484+
///
485+
/// Useful when one wishes to display difference between 2 documents
486+
Delta diff(Delta other, {bool cleanupSemantic = true}) {
487+
if (_operations.equals(other._operations)) {
488+
return Delta();
489+
}
490+
final stringThis = map((op) {
491+
if (op.isInsert) {
492+
return op.data is String ? op.data : _kNullCharacter;
493+
}
494+
final prep = this == other ? 'on' : 'with';
495+
throw ArgumentError('diff() call $prep non-document');
496+
}).join();
497+
final stringOther = other.map((op) {
498+
if (op.isInsert) {
499+
return op.data is String ? op.data : _kNullCharacter;
500+
}
501+
final prep = this == other ? 'on' : 'with';
502+
throw ArgumentError('diff() call $prep non-document');
503+
}).join();
504+
505+
final retDelta = Delta();
506+
final diffResult = dmp.diff(stringThis, stringOther);
507+
if (cleanupSemantic) {
508+
dmp.DiffMatchPatch().diffCleanupSemantic(diffResult);
509+
}
510+
511+
final thisIter = DeltaIterator(this);
512+
final otherIter = DeltaIterator(other);
513+
514+
diffResult.forEach((component) {
515+
var length = component.text.length;
516+
while (length > 0) {
517+
var opLength = 0;
518+
switch (component.operation) {
519+
case dmp.DIFF_INSERT:
520+
opLength = math.min(otherIter.peekLength(), length);
521+
retDelta.push(otherIter.next(opLength));
522+
break;
523+
case dmp.DIFF_DELETE:
524+
opLength = math.min(length, thisIter.peekLength());
525+
thisIter.next(opLength);
526+
retDelta.delete(opLength);
527+
break;
528+
case dmp.DIFF_EQUAL:
529+
opLength = math.min(
530+
math.min(thisIter.peekLength(), otherIter.peekLength()),
531+
length,
532+
);
533+
final thisOp = thisIter.next(opLength);
534+
final otherOp = otherIter.next(opLength);
535+
if (thisOp.data == otherOp.data) {
536+
retDelta.retain(
537+
opLength,
538+
diffAttributes(thisOp.attributes, otherOp.attributes),
539+
);
540+
} else {
541+
retDelta
542+
..push(otherOp)
543+
..delete(opLength);
544+
}
545+
break;
546+
}
547+
length -= opLength;
548+
}
549+
});
550+
return retDelta..trim();
551+
}
552+
445553
/// Transforms next operation from [otherIter] against next operation in
446554
/// [thisIter].
447555
///
@@ -455,7 +563,7 @@ class Delta {
455563
}
456564

457565
final length = math.min(thisIter.peekLength(), otherIter.peekLength());
458-
final thisOp = thisIter.next(length as int);
566+
final thisOp = thisIter.next(length);
459567
final otherOp = otherIter.next(length);
460568
assert(thisOp.length == otherOp.length);
461569

@@ -551,14 +659,14 @@ class Delta {
551659
var index = 0;
552660
final opIterator = DeltaIterator(this);
553661

554-
final actualEnd = end ?? double.infinity;
662+
final actualEnd = end ?? DeltaIterator.maxLength;
555663

556664
while (index < actualEnd && opIterator.hasNext) {
557665
Operation op;
558666
if (index < start) {
559667
op = opIterator.next(start - index);
560668
} else {
561-
op = opIterator.next(actualEnd - index as int);
669+
op = opIterator.next(actualEnd - index);
562670
delta.push(op);
563671
}
564672
index += op.length!;
@@ -602,10 +710,12 @@ class Delta {
602710
class DeltaIterator {
603711
DeltaIterator(this.delta) : _modificationCount = delta._modificationCount;
604712

713+
static const int maxLength = 1073741824;
714+
605715
final Delta delta;
606716
final int _modificationCount;
607717
int _index = 0;
608-
num _offset = 0;
718+
int _offset = 0;
609719

610720
bool get isNextInsert => nextOperationKey == Operation.insertKey;
611721

@@ -621,24 +731,32 @@ class DeltaIterator {
621731
}
622732
}
623733

624-
bool get hasNext => peekLength() < double.infinity;
734+
bool get hasNext => peekLength() < maxLength;
625735

626736
/// Returns length of next operation without consuming it.
627737
///
628-
/// Returns [double.infinity] if there is no more operations left to iterate.
629-
num peekLength() {
738+
/// Returns [maxLength] if there is no more operations left to iterate.
739+
int peekLength() {
630740
if (_index < delta.length) {
631741
final operation = delta._operations[_index];
632742
return operation.length! - _offset;
633743
}
634-
return double.infinity;
744+
return maxLength;
635745
}
636746

637747
/// Consumes and returns next operation.
638748
///
639749
/// Optional [length] specifies maximum length of operation to return. Note
640750
/// that actual length of returned operation may be less than specified value.
641-
Operation next([int length = 4294967296]) {
751+
///
752+
/// If this iterator reached the end of the Delta then returns a retain
753+
/// operation with its length set to [maxLength].
754+
// TODO: Note that we used double.infinity as the default value
755+
// for length here
756+
// but this can now cause a type error since operation length is
757+
// expected to be an int. Changing default length to [maxLength] is
758+
// a workaround to avoid breaking changes.
759+
Operation next([int length = maxLength]) {
642760
if (_modificationCount != delta._modificationCount) {
643761
throw ConcurrentModificationError(delta);
644762
}
@@ -656,13 +774,13 @@ class DeltaIterator {
656774
_offset += actualLength;
657775
}
658776
final opData = op.isInsert && op.data is String
659-
? (op.data as String).substring(
660-
_currentOffset as int, _currentOffset + (actualLength as int))
777+
? (op.data as String)
778+
.substring(_currentOffset, _currentOffset + actualLength)
661779
: op.data;
662780
final opIsNotEmpty =
663781
opData is String ? opData.isNotEmpty : true; // embeds are never empty
664782
final opLength = opData is String ? opData.length : 1;
665-
final opActualLength = opIsNotEmpty ? opLength : actualLength as int;
783+
final opActualLength = opIsNotEmpty ? opLength : actualLength;
666784
return Operation._(opKey, opActualLength, opData, opAttributes);
667785
}
668786
return Operation.retain(length);
@@ -677,7 +795,7 @@ class DeltaIterator {
677795
while (skipped < length && hasNext) {
678796
final opLength = peekLength();
679797
final skip = math.min(length - skipped, opLength);
680-
op = next(skip as int);
798+
op = next(skip);
681799
skipped += op.length!;
682800
}
683801
return op;

0 commit comments

Comments
 (0)