Skip to content

Commit f56bbff

Browse files
authored
Support for building custom inline styles (#351)
1 parent 9c40eb4 commit f56bbff

File tree

8 files changed

+151
-123
lines changed

8 files changed

+151
-123
lines changed

lib/src/models/documents/attribute.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -165,11 +165,11 @@ class Attribute<T> {
165165

166166
Map<String, dynamic> toJson() => <String, dynamic>{key: value};
167167

168-
static Attribute fromKeyValue(String key, dynamic value) {
169-
if (!_registry.containsKey(key)) {
170-
throw ArgumentError.value(key, 'key "$key" not found.');
168+
static Attribute? fromKeyValue(String key, dynamic value) {
169+
final origin = _registry[key];
170+
if (origin == null) {
171+
return null;
171172
}
172-
final origin = _registry[key]!;
173173
final attribute = clone(origin, value);
174174
return attribute;
175175
}

lib/src/models/documents/style.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ class Style {
1818

1919
final result = attributes.map((key, dynamic value) {
2020
final attr = Attribute.fromKeyValue(key, value);
21-
return MapEntry<String, Attribute>(key, attr);
21+
return MapEntry<String, Attribute>(
22+
key, attr ?? Attribute(key, AttributeScope.IGNORE, value));
2223
});
2324
return Style.attr(result);
2425
}

lib/src/widgets/delegate.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import 'text_selection.dart';
1010
typedef EmbedBuilder = Widget Function(
1111
BuildContext context, Embed node, bool readOnly);
1212

13+
typedef StyleBuilder = TextStyle Function(String attributeKey);
14+
1315
abstract class EditorTextSelectionGestureDetectorBuilderDelegate {
1416
GlobalKey<EditorState> getEditableTextKey();
1517

lib/src/widgets/editor.dart

Lines changed: 72 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -225,33 +225,35 @@ Widget _defaultEmbedBuilder(
225225
}
226226

227227
class QuillEditor extends StatefulWidget {
228-
const QuillEditor(
229-
{required this.controller,
230-
required this.focusNode,
231-
required this.scrollController,
232-
required this.scrollable,
233-
required this.padding,
234-
required this.autoFocus,
235-
required this.readOnly,
236-
required this.expands,
237-
this.showCursor,
238-
this.paintCursorAboveText,
239-
this.placeholder,
240-
this.enableInteractiveSelection = true,
241-
this.scrollBottomInset = 0,
242-
this.minHeight,
243-
this.maxHeight,
244-
this.customStyles,
245-
this.textCapitalization = TextCapitalization.sentences,
246-
this.keyboardAppearance = Brightness.light,
247-
this.scrollPhysics,
248-
this.onLaunchUrl,
249-
this.onTapDown,
250-
this.onTapUp,
251-
this.onSingleLongTapStart,
252-
this.onSingleLongTapMoveUpdate,
253-
this.onSingleLongTapEnd,
254-
this.embedBuilder = _defaultEmbedBuilder});
228+
const QuillEditor({
229+
required this.controller,
230+
required this.focusNode,
231+
required this.scrollController,
232+
required this.scrollable,
233+
required this.padding,
234+
required this.autoFocus,
235+
required this.readOnly,
236+
required this.expands,
237+
this.showCursor,
238+
this.paintCursorAboveText,
239+
this.placeholder,
240+
this.enableInteractiveSelection = true,
241+
this.scrollBottomInset = 0,
242+
this.minHeight,
243+
this.maxHeight,
244+
this.customStyles,
245+
this.textCapitalization = TextCapitalization.sentences,
246+
this.keyboardAppearance = Brightness.light,
247+
this.scrollPhysics,
248+
this.onLaunchUrl,
249+
this.onTapDown,
250+
this.onTapUp,
251+
this.onSingleLongTapStart,
252+
this.onSingleLongTapMoveUpdate,
253+
this.onSingleLongTapEnd,
254+
this.embedBuilder = _defaultEmbedBuilder,
255+
this.styleBuilder,
256+
});
255257

256258
factory QuillEditor.basic({
257259
required QuillController controller,
@@ -310,6 +312,7 @@ class QuillEditor extends StatefulWidget {
310312
onSingleLongTapEnd;
311313

312314
final EmbedBuilder embedBuilder;
315+
final StyleBuilder? styleBuilder;
313316

314317
@override
315318
_QuillEditorState createState() => _QuillEditorState();
@@ -374,46 +377,48 @@ class _QuillEditorState extends State<QuillEditor>
374377
return _selectionGestureDetectorBuilder.build(
375378
HitTestBehavior.translucent,
376379
RawEditor(
377-
_editorKey,
378-
widget.controller,
379-
widget.focusNode,
380-
widget.scrollController,
381-
widget.scrollable,
382-
widget.scrollBottomInset,
383-
widget.padding,
384-
widget.readOnly,
385-
widget.placeholder,
386-
widget.onLaunchUrl,
387-
ToolbarOptions(
388-
copy: widget.enableInteractiveSelection,
389-
cut: widget.enableInteractiveSelection,
390-
paste: widget.enableInteractiveSelection,
391-
selectAll: widget.enableInteractiveSelection,
392-
),
393-
theme.platform == TargetPlatform.iOS ||
394-
theme.platform == TargetPlatform.android,
395-
widget.showCursor,
396-
CursorStyle(
397-
color: cursorColor,
398-
backgroundColor: Colors.grey,
399-
width: 2,
400-
radius: cursorRadius,
401-
offset: cursorOffset,
402-
paintAboveText: widget.paintCursorAboveText ?? paintCursorAboveText,
403-
opacityAnimates: cursorOpacityAnimates,
404-
),
405-
widget.textCapitalization,
406-
widget.maxHeight,
407-
widget.minHeight,
408-
widget.customStyles,
409-
widget.expands,
410-
widget.autoFocus,
411-
selectionColor,
412-
textSelectionControls,
413-
widget.keyboardAppearance,
414-
widget.enableInteractiveSelection,
415-
widget.scrollPhysics,
416-
widget.embedBuilder),
380+
_editorKey,
381+
widget.controller,
382+
widget.focusNode,
383+
widget.scrollController,
384+
widget.scrollable,
385+
widget.scrollBottomInset,
386+
widget.padding,
387+
widget.readOnly,
388+
widget.placeholder,
389+
widget.onLaunchUrl,
390+
ToolbarOptions(
391+
copy: widget.enableInteractiveSelection,
392+
cut: widget.enableInteractiveSelection,
393+
paste: widget.enableInteractiveSelection,
394+
selectAll: widget.enableInteractiveSelection,
395+
),
396+
theme.platform == TargetPlatform.iOS ||
397+
theme.platform == TargetPlatform.android,
398+
widget.showCursor,
399+
CursorStyle(
400+
color: cursorColor,
401+
backgroundColor: Colors.grey,
402+
width: 2,
403+
radius: cursorRadius,
404+
offset: cursorOffset,
405+
paintAboveText: widget.paintCursorAboveText ?? paintCursorAboveText,
406+
opacityAnimates: cursorOpacityAnimates,
407+
),
408+
widget.textCapitalization,
409+
widget.maxHeight,
410+
widget.minHeight,
411+
widget.customStyles,
412+
widget.expands,
413+
widget.autoFocus,
414+
selectionColor,
415+
textSelectionControls,
416+
widget.keyboardAppearance,
417+
widget.enableInteractiveSelection,
418+
widget.scrollPhysics,
419+
widget.embedBuilder,
420+
widget.styleBuilder,
421+
),
417422
);
418423
}
419424

lib/src/widgets/raw_editor.dart

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ class RawEditor extends StatefulWidget {
5757
this.enableInteractiveSelection,
5858
this.scrollPhysics,
5959
this.embedBuilder,
60+
this.styleBuilder,
6061
) : assert(maxHeight == null || maxHeight > 0, 'maxHeight cannot be null'),
6162
assert(minHeight == null || minHeight >= 0, 'minHeight cannot be null'),
6263
assert(maxHeight == null || minHeight == null || maxHeight >= minHeight,
@@ -89,7 +90,7 @@ class RawEditor extends StatefulWidget {
8990
final bool enableInteractiveSelection;
9091
final ScrollPhysics? scrollPhysics;
9192
final EmbedBuilder embedBuilder;
92-
93+
final StyleBuilder? styleBuilder;
9394
@override
9495
State<StatefulWidget> createState() => RawEditorState();
9596
}
@@ -231,23 +232,24 @@ class RawEditorState extends EditorState
231232
} else if (node is Block) {
232233
final attrs = node.style.attributes;
233234
final editableTextBlock = EditableTextBlock(
234-
node,
235-
_textDirection,
236-
widget.scrollBottomInset,
237-
_getVerticalSpacingForBlock(node, _styles),
238-
widget.controller.selection,
239-
widget.selectionColor,
240-
_styles,
241-
widget.enableInteractiveSelection,
242-
_hasFocus,
243-
attrs.containsKey(Attribute.codeBlock.key)
235+
block: node,
236+
textDirection: _textDirection,
237+
scrollBottomInset: widget.scrollBottomInset,
238+
verticalSpacing: _getVerticalSpacingForBlock(node, _styles),
239+
textSelection: widget.controller.selection,
240+
color: widget.selectionColor,
241+
styles: _styles,
242+
enableInteractiveSelection: widget.enableInteractiveSelection,
243+
hasFocus: _hasFocus,
244+
contentPadding: attrs.containsKey(Attribute.codeBlock.key)
244245
? const EdgeInsets.all(16)
245246
: null,
246-
widget.embedBuilder,
247-
_cursorCont,
248-
indentLevelCounts,
249-
_handleCheckboxTap,
250-
widget.readOnly);
247+
embedBuilder: widget.embedBuilder,
248+
cursorCont: _cursorCont,
249+
indentLevelCounts: indentLevelCounts,
250+
onCheckboxTap: _handleCheckboxTap,
251+
readOnly: widget.readOnly,
252+
styleBuilder: widget.styleBuilder);
251253
result.add(editableTextBlock);
252254
} else {
253255
throw StateError('Unreachable.');
@@ -262,6 +264,7 @@ class RawEditorState extends EditorState
262264
line: node,
263265
textDirection: _textDirection,
264266
embedBuilder: widget.embedBuilder,
267+
styleBuilder: widget.styleBuilder,
265268
styles: _styles!,
266269
readOnly: widget.readOnly,
267270
);

lib/src/widgets/simple_viewer.dart

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -202,26 +202,23 @@ class _QuillSimpleViewerState extends State<QuillSimpleViewer>
202202
} else if (node is Block) {
203203
final attrs = node.style.attributes;
204204
final editableTextBlock = EditableTextBlock(
205-
node,
206-
_textDirection,
207-
widget.scrollBottomInset,
208-
_getVerticalSpacingForBlock(node, _styles),
209-
widget.controller.selection,
210-
Colors.black,
211-
// selectionColor,
212-
_styles,
213-
false,
214-
// enableInteractiveSelection,
215-
false,
216-
// hasFocus,
217-
attrs.containsKey(Attribute.codeBlock.key)
205+
block: node,
206+
textDirection: _textDirection,
207+
scrollBottomInset: widget.scrollBottomInset,
208+
verticalSpacing: _getVerticalSpacingForBlock(node, _styles),
209+
textSelection: widget.controller.selection,
210+
color: Colors.black,
211+
styles: _styles,
212+
enableInteractiveSelection: false,
213+
hasFocus: false,
214+
contentPadding: attrs.containsKey(Attribute.codeBlock.key)
218215
? const EdgeInsets.all(16)
219216
: null,
220-
embedBuilder,
221-
_cursorCont,
222-
indentLevelCounts,
223-
_handleCheckboxTap,
224-
widget.readOnly);
217+
embedBuilder: embedBuilder,
218+
cursorCont: _cursorCont,
219+
indentLevelCounts: indentLevelCounts,
220+
onCheckboxTap: _handleCheckboxTap,
221+
readOnly: widget.readOnly);
225222
result.add(editableTextBlock);
226223
} else {
227224
throw StateError('Unreachable.');

lib/src/widgets/text_block.dart

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -48,22 +48,23 @@ const List<String> romanNumbers = [
4848

4949
class EditableTextBlock extends StatelessWidget {
5050
const EditableTextBlock(
51-
this.block,
52-
this.textDirection,
53-
this.scrollBottomInset,
54-
this.verticalSpacing,
55-
this.textSelection,
56-
this.color,
57-
this.styles,
58-
this.enableInteractiveSelection,
59-
this.hasFocus,
60-
this.contentPadding,
61-
this.embedBuilder,
62-
this.cursorCont,
63-
this.indentLevelCounts,
64-
this.onCheckboxTap,
65-
this.readOnly,
66-
);
51+
{required this.block,
52+
required this.textDirection,
53+
required this.scrollBottomInset,
54+
required this.verticalSpacing,
55+
required this.textSelection,
56+
required this.color,
57+
required this.styles,
58+
required this.enableInteractiveSelection,
59+
required this.hasFocus,
60+
required this.contentPadding,
61+
required this.embedBuilder,
62+
required this.cursorCont,
63+
required this.indentLevelCounts,
64+
required this.onCheckboxTap,
65+
required this.readOnly,
66+
this.styleBuilder,
67+
Key? key});
6768

6869
final Block block;
6970
final TextDirection textDirection;
@@ -76,6 +77,7 @@ class EditableTextBlock extends StatelessWidget {
7677
final bool hasFocus;
7778
final EdgeInsets? contentPadding;
7879
final EmbedBuilder embedBuilder;
80+
final StyleBuilder? styleBuilder;
7981
final CursorCont cursorCont;
8082
final Map<int, int> indentLevelCounts;
8183
final Function(int, bool) onCheckboxTap;
@@ -123,6 +125,7 @@ class EditableTextBlock extends StatelessWidget {
123125
line: line,
124126
textDirection: textDirection,
125127
embedBuilder: embedBuilder,
128+
styleBuilder: styleBuilder,
126129
styles: styles!,
127130
readOnly: readOnly,
128131
),

0 commit comments

Comments
 (0)