Skip to content

Commit db25b9c

Browse files
committed
support inline image
1 parent 0a409ae commit db25b9c

File tree

5 files changed

+65
-84
lines changed

5 files changed

+65
-84
lines changed

lib/src/models/documents/document.dart

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

5252
Stream<Tuple3<Delta, Delta, ChangeSource>> get changes => _observer.stream;
5353

54-
Delta insert(int index, Object? data,
55-
{int replaceLength = 0, bool autoAppendNewlineAfterImage = true}) {
54+
Delta insert(int index, Object? data, {int replaceLength = 0}) {
5655
assert(index >= 0);
5756
assert(data is String || data is Embeddable);
5857
if (data is Embeddable) {
@@ -63,8 +62,7 @@ class Document {
6362

6463
final delta = _rules.apply(RuleType.INSERT, this, index,
6564
data: data, len: replaceLength);
66-
compose(delta, ChangeSource.LOCAL,
67-
autoAppendNewlineAfterImage: autoAppendNewlineAfterImage);
65+
compose(delta, ChangeSource.LOCAL);
6866
return delta;
6967
}
7068

@@ -77,8 +75,7 @@ class Document {
7775
return delta;
7876
}
7977

80-
Delta replace(int index, int len, Object? data,
81-
{bool autoAppendNewlineAfterImage = true}) {
78+
Delta replace(int index, int len, Object? data) {
8279
assert(index >= 0);
8380
assert(data is String || data is Embeddable);
8481

@@ -91,9 +88,7 @@ class Document {
9188
// We have to insert before applying delete rules
9289
// Otherwise delete would be operating on stale document snapshot.
9390
if (dataIsNotEmpty) {
94-
delta = insert(index, data,
95-
replaceLength: len,
96-
autoAppendNewlineAfterImage: autoAppendNewlineAfterImage);
91+
delta = insert(index, data, replaceLength: len);
9792
}
9893

9994
if (len > 0) {
@@ -141,17 +136,13 @@ class Document {
141136
return block.queryChild(res.offset, true);
142137
}
143138

144-
void compose(Delta delta, ChangeSource changeSource,
145-
{bool autoAppendNewlineAfterImage = true,
146-
bool autoAppendNewlineAfterVideo = true}) {
139+
void compose(Delta delta, ChangeSource changeSource) {
147140
assert(!_observer.isClosed);
148141
delta.trim();
149142
assert(delta.isNotEmpty);
150143

151144
var offset = 0;
152-
delta = _transform(delta,
153-
autoAppendNewlineAfterImage: autoAppendNewlineAfterImage,
154-
autoAppendNewlineAfterVideo: autoAppendNewlineAfterVideo);
145+
delta = _transform(delta);
155146
final originalDelta = toDelta();
156147
for (final op in delta.toList()) {
157148
final style =
@@ -195,44 +186,37 @@ class Document {
195186

196187
bool get hasRedo => _history.hasRedo;
197188

198-
static Delta _transform(Delta delta,
199-
{bool autoAppendNewlineAfterImage = true,
200-
bool autoAppendNewlineAfterVideo = true}) {
189+
static Delta _transform(Delta delta) {
201190
final res = Delta();
202191
final ops = delta.toList();
203192
for (var i = 0; i < ops.length; i++) {
204193
final op = ops[i];
205194
res.push(op);
206-
if (autoAppendNewlineAfterImage) {
207-
_autoAppendNewlineAfterEmbeddable(i, ops, op, res, 'image');
208-
}
209-
if (autoAppendNewlineAfterVideo) {
210-
_autoAppendNewlineAfterEmbeddable(i, ops, op, res, 'video');
211-
}
195+
_autoAppendNewlineAfterEmbeddable(i, ops, op, res, 'video');
212196
}
213197
return res;
214198
}
215199

216200
static void _autoAppendNewlineAfterEmbeddable(
217201
int i, List<Operation> ops, Operation op, Delta res, String type) {
218-
final nextOpIsImage = i + 1 < ops.length &&
202+
final nextOpIsEmbed = i + 1 < ops.length &&
219203
ops[i + 1].isInsert &&
220204
ops[i + 1].data is Map &&
221205
(ops[i + 1].data as Map).containsKey(type);
222-
if (nextOpIsImage &&
206+
if (nextOpIsEmbed &&
223207
op.data is String &&
224208
(op.data as String).isNotEmpty &&
225209
!(op.data as String).endsWith('\n')) {
226210
res.push(Operation.insert('\n'));
227211
}
228212
// embed could be image or video
229-
final opInsertImage =
213+
final opInsertEmbed =
230214
op.isInsert && op.data is Map && (op.data as Map).containsKey(type);
231215
final nextOpIsLineBreak = i + 1 < ops.length &&
232216
ops[i + 1].isInsert &&
233217
ops[i + 1].data is String &&
234218
(ops[i + 1].data as String).startsWith('\n');
235-
if (opInsertImage && (i + 1 == ops.length - 1 || !nextOpIsLineBreak)) {
219+
if (opInsertEmbed && (i + 1 == ops.length - 1 || !nextOpIsLineBreak)) {
236220
// automatically append '\n' for embeddable
237221
res.push(Operation.insert('\n'));
238222
}

lib/src/models/rules/insert.dart

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -273,37 +273,6 @@ class InsertEmbedsRule extends InsertRule {
273273
}
274274
}
275275

276-
class ForceNewlineForInsertsAroundEmbedRule extends InsertRule {
277-
const ForceNewlineForInsertsAroundEmbedRule();
278-
279-
@override
280-
Delta? applyRule(Delta document, int index,
281-
{int? len, Object? data, Attribute? attribute}) {
282-
if (data is! String) {
283-
return null;
284-
}
285-
286-
final text = data;
287-
final itr = DeltaIterator(document);
288-
final prev = itr.skip(index);
289-
final cur = itr.next();
290-
final cursorBeforeEmbed = cur.data is! String;
291-
final cursorAfterEmbed = prev != null && prev.data is! String;
292-
293-
if (!cursorBeforeEmbed && !cursorAfterEmbed) {
294-
return null;
295-
}
296-
final delta = Delta()..retain(index + (len ?? 0));
297-
if (cursorBeforeEmbed && !text.endsWith('\n')) {
298-
return delta..insert(text)..insert('\n');
299-
}
300-
if (cursorAfterEmbed && !text.startsWith('\n')) {
301-
return delta..insert('\n')..insert(text);
302-
}
303-
return delta..insert(text);
304-
}
305-
}
306-
307276
class AutoFormatLinksRule extends InsertRule {
308277
const AutoFormatLinksRule();
309278

lib/src/models/rules/rule.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ class Rules {
3636
const ResolveLineFormatRule(),
3737
const ResolveInlineFormatRule(),
3838
const InsertEmbedsRule(),
39-
const ForceNewlineForInsertsAroundEmbedRule(),
4039
const AutoExitBlockRule(),
4140
const PreserveBlockStyleOnInsertRule(),
4241
const PreserveLineStyleOnSplitRule(),

lib/src/widgets/controller.dart

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,13 +106,12 @@ class QuillController extends ChangeNotifier {
106106

107107
void replaceText(
108108
int index, int len, Object? data, TextSelection? textSelection,
109-
{bool ignoreFocus = false, bool autoAppendNewlineAfterImage = true}) {
109+
{bool ignoreFocus = false}) {
110110
assert(data is String || data is Embeddable);
111111

112112
Delta? delta;
113113
if (len > 0 || data is! String || data.isNotEmpty) {
114-
delta = document.replace(index, len, data,
115-
autoAppendNewlineAfterImage: autoAppendNewlineAfterImage);
114+
delta = document.replace(index, len, data);
116115
var shouldRetainDelta = toggledStyle.isNotEmpty &&
117116
delta.isNotEmpty &&
118117
delta.length <= 2 &&

lib/src/widgets/text_line.dart

Lines changed: 51 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import 'dart:collection';
12
import 'dart:math' as math;
23

34
import 'package:flutter/foundation.dart';
@@ -38,22 +39,12 @@ class TextLine extends StatelessWidget {
3839
@override
3940
Widget build(BuildContext context) {
4041
assert(debugCheckHasMediaQuery(context));
41-
42-
if (line.hasEmbed) {
43-
if (line.childCount == 1) {
44-
// For video, it is always single child
45-
final embed = line.children.single as Embed;
46-
return EmbedProxy(embedBuilder(context, embed, readOnly));
47-
}
48-
49-
// The line could contain more than one Embed & more than one Text
50-
// TODO: handle more than one Embed
51-
final embed =
52-
line.children.firstWhere((child) => child is Embed) as Embed;
42+
if (line.hasEmbed && line.childCount == 1) {
43+
// For video, it is always single child
44+
final embed = line.children.single as Embed;
5345
return EmbedProxy(embedBuilder(context, embed, readOnly));
5446
}
55-
56-
final textSpan = _buildTextSpan(context);
47+
final textSpan = _getTextSpanForWholeLine(context);
5748
final strutStyle = StrutStyle.fromTextStyle(textSpan.style!);
5849
final textAlign = _getTextAlign();
5950
final child = RichText(
@@ -75,6 +66,42 @@ class TextLine extends StatelessWidget {
7566
null);
7667
}
7768

69+
InlineSpan _getTextSpanForWholeLine(BuildContext context) {
70+
final lineStyle = _getLineStyle(styles);
71+
if (!line.hasEmbed) {
72+
return _buildTextSpan(styles, line.children, lineStyle);
73+
}
74+
75+
// The line could contain more than one Embed & more than one Text
76+
final textSpanChildren = <InlineSpan>[];
77+
var textNodes = LinkedList<Node>();
78+
for (final child in line.children) {
79+
if (child is Embed) {
80+
if (textNodes.isNotEmpty) {
81+
textSpanChildren.add(_buildTextSpan(styles, textNodes, lineStyle));
82+
textNodes = LinkedList<Node>();
83+
}
84+
// Here it should be image
85+
final embed = WidgetSpan(
86+
child: EmbedProxy(embedBuilder(context, child, readOnly)));
87+
textSpanChildren.add(embed);
88+
continue;
89+
}
90+
91+
// here child is Text node and its value is cloned
92+
textNodes.add(child.clone());
93+
}
94+
95+
if (textNodes.isNotEmpty) {
96+
textSpanChildren.add(_buildTextSpan(styles, textNodes, lineStyle));
97+
}
98+
99+
return TextSpan(style: lineStyle, children: textSpanChildren);
100+
}
101+
102+
// test with different combinations
103+
// comb through logic , see if any refactoring is needed
104+
78105
TextAlign _getTextAlign() {
79106
final alignment = line.style.attributes[Attribute.align.key];
80107
if (alignment == Attribute.leftAlignment) {
@@ -89,17 +116,20 @@ class TextLine extends StatelessWidget {
89116
return TextAlign.start;
90117
}
91118

92-
TextSpan _buildTextSpan(BuildContext context) {
93-
final defaultStyles = styles;
94-
final children = line.children
119+
TextSpan _buildTextSpan(DefaultStyles defaultStyles, LinkedList<Node> nodes,
120+
TextStyle lineStyle) {
121+
final children = nodes
95122
.map((node) => _getTextSpanFromNode(defaultStyles, node))
96123
.toList(growable: false);
97124

125+
return TextSpan(children: children, style: lineStyle);
126+
}
127+
128+
TextStyle _getLineStyle(DefaultStyles defaultStyles) {
98129
var textStyle = const TextStyle();
99130

100131
if (line.style.containsKey(Attribute.placeholder.key)) {
101-
textStyle = defaultStyles.placeHolder!.style;
102-
return TextSpan(children: children, style: textStyle);
132+
return defaultStyles.placeHolder!.style;
103133
}
104134

105135
final header = line.style.attributes[Attribute.header.key];
@@ -123,13 +153,13 @@ class TextLine extends StatelessWidget {
123153

124154
textStyle = textStyle.merge(toMerge);
125155

126-
return TextSpan(children: children, style: textStyle);
156+
return textStyle;
127157
}
128158

129159
TextSpan _getTextSpanFromNode(DefaultStyles defaultStyles, Node node) {
130160
final textNode = node as leaf.Text;
131161
final style = textNode.style;
132-
var res = const TextStyle();
162+
var res = const TextStyle(); // This is inline text style
133163
final color = textNode.style.attributes[Attribute.color.key];
134164

135165
<String, TextStyle?>{

0 commit comments

Comments
 (0)