Skip to content

Commit 858d61a

Browse files
AtlasAutocodeDouglas Ward
and
Douglas Ward
authored
Fix style settings (singerdmx#1962)
* Value setting Stateful toolbar buttons derive from base class * Rename base class as QuillToolbarBaseValueButton * Removed deprecated functions * Move clipboard actions to QuillController * Fix: collectAllIndividualStylesAndEmbed for result span * Add: Clipboard toolbar buttons * Add: test for QuillController clipboard Dart Formatted * Translation Justify * Translation alignJustify * Fix: Translation en-US * Fix style settings --------- Co-authored-by: Douglas Ward <[email protected]>
1 parent 02ad051 commit 858d61a

File tree

4 files changed

+127
-19
lines changed

4 files changed

+127
-19
lines changed

example/lib/screens/quill/my_quill_editor.dart

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class MyQuillEditor extends StatelessWidget {
3030

3131
@override
3232
Widget build(BuildContext context) {
33+
final defaultTextStyle = DefaultTextStyle.of(context);
3334
return QuillEditor(
3435
scrollController: scrollController,
3536
focusNode: focusNode,
@@ -43,27 +44,21 @@ class MyQuillEditor extends StatelessWidget {
4344
useTextColorForDot: true,
4445
),
4546
),
46-
customStyles: const DefaultStyles(
47+
customStyles: DefaultStyles(
4748
h1: DefaultTextBlockStyle(
48-
TextStyle(
49+
defaultTextStyle.style.copyWith(
4950
fontSize: 32,
5051
height: 1.15,
5152
fontWeight: FontWeight.w300,
5253
),
53-
VerticalSpacing(16, 0),
54-
VerticalSpacing(0, 0),
54+
const VerticalSpacing(16, 0),
55+
const VerticalSpacing(0, 0),
5556
null,
5657
),
57-
sizeSmall: TextStyle(fontSize: 9),
58-
subscript: TextStyle(
59-
fontFeatures: [FontFeature.subscripts()],
60-
),
61-
superscript: TextStyle(
62-
fontFeatures: [FontFeature.superscripts()],
63-
),
58+
sizeSmall: defaultTextStyle.style.copyWith(fontSize: 9),
6459
),
6560
scrollable: true,
66-
placeholder: 'Start writting your notes...',
61+
placeholder: 'Start writing your notes...',
6762
padding: const EdgeInsets.all(16),
6863
onImagePaste: (imageBytes) async {
6964
if (isWeb()) {

lib/src/models/documents/document.dart

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -177,26 +177,46 @@ class Document {
177177

178178
/// Only attributes applied to all characters within this range are
179179
/// included in the result.
180+
/// Special case of no-selection at start of empty line: gets inline style(s) from preceding non-empty line.
180181
Style collectStyle(int index, int len) {
181-
final res = queryChild(index);
182-
Style rangeStyle;
182+
var res = queryChild(index);
183183
if (len > 0) {
184184
return (res.node as Line).collectStyle(res.offset, len);
185185
}
186+
//
186187
if (res.offset == 0) {
187-
return rangeStyle = (res.node as Line).collectStyle(res.offset, len);
188+
final current = (res.node as Line).collectStyle(0, 0);
189+
//
190+
while ((res.node as Line).length == 1 && index > 0) {
191+
res = queryChild(--index);
192+
}
193+
//
194+
final style = (res.node as Line).collectStyle(res.offset, 0);
195+
final remove = <Attribute>{};
196+
for (final attr in style.attributes.values) {
197+
if (!Attribute.inlineKeys.contains(attr.key)) {
198+
if (!current.containsKey(attr.key)) {
199+
remove.add(attr);
200+
}
201+
}
202+
}
203+
if (remove.isNotEmpty) {
204+
return style.removeAll(remove);
205+
}
206+
return style;
188207
}
189-
rangeStyle = (res.node as Line).collectStyle(res.offset - 1, len);
190-
final linkAttribute = rangeStyle.attributes[Attribute.link.key];
208+
//
209+
final style = (res.node as Line).collectStyle(res.offset - 1, 0);
210+
final linkAttribute = style.attributes[Attribute.link.key];
191211
if ((linkAttribute != null) &&
192212
(linkAttribute.value !=
193213
(res.node as Line)
194214
.collectStyle(res.offset, len)
195215
.attributes[Attribute.link.key]
196216
?.value)) {
197-
return rangeStyle.removeAll({linkAttribute});
217+
return style.removeAll({linkAttribute});
198218
}
199-
return rangeStyle;
219+
return style;
200220
}
201221

202222
/// Returns all styles and Embed for each node within selection

lib/src/widgets/raw_editor/raw_editor_state.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -875,6 +875,14 @@ class QuillRawEditorState extends EditorState
875875
..formatSelection(attribute)
876876
// Remove the added newline.
877877
..replaceText(controller.selection.baseOffset + 1, 1, '', null);
878+
//
879+
final style =
880+
controller.document.collectStyle(controller.selection.baseOffset, 0);
881+
if (style.isNotEmpty) {
882+
for (final attr in style.values) {
883+
controller.formatSelection(attr);
884+
}
885+
}
878886
}
879887

880888
void _handleSelectionChanged(

test/utils/document_test.dart

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import 'package:flutter_quill/flutter_quill.dart';
2+
import 'package:flutter_quill/quill_delta.dart';
3+
import 'package:test/test.dart';
4+
5+
void main() {
6+
group('collectStyle', () {
7+
/// Enter key inserts newline as plain text without inline styles.
8+
/// collectStyle needs to retrieve style of preceding line
9+
test('Simulate double enter key at end', () {
10+
final delta = Delta()
11+
..insert('data\n')
12+
..insert('second\n', <String, dynamic>{'bold': true})
13+
..insert('\n\nplain\n');
14+
final document = Document.fromDelta(delta);
15+
//
16+
expect(document.getPlainText(0, document.length),
17+
'data\nsecond\n\n\nplain\n');
18+
expect(document.length, 20);
19+
//
20+
expect('data\n', document.getPlainText(0, 5));
21+
for (var index = 0; index < 5; index++) {
22+
expect(const Style(), document.collectStyle(index, 0));
23+
}
24+
//
25+
expect('second\n', document.getPlainText(5, 7));
26+
for (var index = 5; index < 12; index++) {
27+
expect(const Style.attr({'bold': Attribute.bold}),
28+
document.collectStyle(index, 0));
29+
}
30+
//
31+
expect('\n\n', document.getPlainText(12, 2));
32+
for (var index = 12; index < 14; index++) {
33+
expect(const Style.attr({'bold': Attribute.bold}),
34+
document.collectStyle(index, 0));
35+
}
36+
//
37+
for (var index = 14; index < document.length; index++) {
38+
expect(const Style(), document.collectStyle(index, 0));
39+
}
40+
});
41+
42+
test('No selection', () {
43+
final delta = Delta()
44+
..insert('plain\n')
45+
..insert('bold\n', <String, dynamic>{'bold': true})
46+
..insert('italic\n', <String, dynamic>{'italic': true});
47+
final document = Document.fromDelta(delta);
48+
//
49+
expect(
50+
document.getPlainText(0, document.length), 'plain\nbold\nitalic\n');
51+
expect(document.length, 18);
52+
//
53+
for (var index = 0; index < 6; index++) {
54+
expect(const Style(), document.collectStyle(index, 0));
55+
}
56+
//
57+
for (var index = 6; index < 11; index++) {
58+
expect(const Style.attr({'bold': Attribute.bold}),
59+
document.collectStyle(index, 0));
60+
}
61+
//
62+
for (var index = 11; index < document.length; index++) {
63+
expect(const Style.attr({'italic': Attribute.italic}),
64+
document.collectStyle(index, 0));
65+
}
66+
});
67+
68+
test('Selection', () {
69+
final delta = Delta()
70+
..insert('data\n')
71+
..insert('second\n', <String, dynamic>{'bold': true});
72+
final document = Document.fromDelta(delta);
73+
//
74+
expect(const Style(), document.collectStyle(0, 4));
75+
expect(const Style(), document.collectStyle(1, 3));
76+
//
77+
expect(const Style.attr({'bold': Attribute.bold}),
78+
document.collectStyle(5, 3));
79+
expect(const Style.attr({'bold': Attribute.bold}),
80+
document.collectStyle(8, 3));
81+
//
82+
expect(const Style(), document.collectStyle(3, 3));
83+
});
84+
});
85+
}

0 commit comments

Comments
 (0)