Consolidated embed builders

pull/933/head
Jonathan Salmon 3 years ago
parent 616fae851b
commit 606806ff6f
  1. 34
      example/lib/pages/home_page.dart
  2. 3
      example/lib/pages/read_only_page.dart
  3. 38
      example/lib/universal_ui/universal_ui.dart
  4. 73
      lib/src/embeds/default_embed_builder.dart
  5. 48
      lib/src/widgets/editor.dart
  6. 2
      lib/src/widgets/raw_editor.dart

@ -119,7 +119,10 @@ class _HomePageState extends State<HomePage> {
null), null),
sizeSmall: const TextStyle(fontSize: 9), sizeSmall: const TextStyle(fontSize: 9),
), ),
customElementsEmbedBuilder: customElementsEmbedBuilder, embedBuilders: [
...defaultEmbedBuilders,
NotesEmbedBuilder(addEditNote: _addEditNote)
],
); );
if (kIsWeb) { if (kIsWeb) {
quillEditor = QuillEditor( quillEditor = QuillEditor(
@ -145,7 +148,7 @@ class _HomePageState extends State<HomePage> {
null), null),
sizeSmall: const TextStyle(fontSize: 9), sizeSmall: const TextStyle(fontSize: 9),
), ),
embedBuilder: defaultEmbedBuilderWeb); embedBuilders: defaultEmbedBuildersWeb);
} }
var toolbar = QuillToolbar.basic( var toolbar = QuillToolbar.basic(
controller: _controller!, controller: _controller!,
@ -386,17 +389,25 @@ class _HomePageState extends State<HomePage> {
controller.replaceText(index, length, block, null); controller.replaceText(index, length, block, null);
} }
} }
}
class NotesEmbedBuilder implements IEmbedBuilder {
NotesEmbedBuilder({required this.addEditNote});
Future<void> Function(BuildContext context, {Document? document}) addEditNote;
Widget customElementsEmbedBuilder( @override
String get key => 'notes';
@override
Widget build(
BuildContext context, BuildContext context,
QuillController controller, QuillController controller,
CustomBlockEmbed block, Embed node,
bool readOnly, bool readOnly,
void Function(GlobalKey videoContainerKey)? onVideoInit, void Function(GlobalKey<State<StatefulWidget>> videoContainerKey)?
) { onVideoInit) {
switch (block.type) { final notes = NotesBlockEmbed(node.value.data).document;
case 'notes':
final notes = NotesBlockEmbed(block.data).document;
return Material( return Material(
color: Colors.transparent, color: Colors.transparent,
@ -407,16 +418,13 @@ class _HomePageState extends State<HomePage> {
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
leading: const Icon(Icons.notes), leading: const Icon(Icons.notes),
onTap: () => _addEditNote(context, document: notes), onTap: () => addEditNote(context, document: notes),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
side: const BorderSide(color: Colors.grey), side: const BorderSide(color: Colors.grey),
), ),
), ),
); );
default:
return const SizedBox();
}
} }
} }

@ -38,6 +38,7 @@ class _ReadOnlyPageState extends State<ReadOnlyPage> {
readOnly: !_edit, readOnly: !_edit,
expands: false, expands: false,
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
embedBuilders: defaultEmbedBuilders,
); );
if (kIsWeb) { if (kIsWeb) {
quillEditor = QuillEditor( quillEditor = QuillEditor(
@ -49,7 +50,7 @@ class _ReadOnlyPageState extends State<ReadOnlyPage> {
readOnly: !_edit, readOnly: !_edit,
expands: false, expands: false,
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
embedBuilder: defaultEmbedBuilderWeb); embedBuilders: defaultEmbedBuildersWeb);
} }
return Padding( return Padding(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),

@ -26,15 +26,13 @@ class UniversalUI {
var ui = UniversalUI(); var ui = UniversalUI();
Widget defaultEmbedBuilderWeb( class ImageEmbedBuilderWeb implements IEmbedBuilder {
BuildContext context, @override
QuillController controller, String get key => BlockEmbed.imageType;
Embed node,
bool readOnly, @override
void Function(GlobalKey videoContainerKey)? onVideoInit, Widget build(BuildContext context, QuillController controller, Embed node,
) { bool readOnly, void Function(GlobalKey videoContainerKey)? onVideoInit) {
switch (node.value.type) {
case BlockEmbed.imageType:
final imageUrl = node.value.data; final imageUrl = node.value.data;
if (isImageBase64(imageUrl)) { if (isImageBase64(imageUrl)) {
// TODO: handle imageUrl of base64 // TODO: handle imageUrl of base64
@ -58,7 +56,16 @@ Widget defaultEmbedBuilderWeb(
), ),
), ),
); );
case BlockEmbed.videoType: }
}
class VideoEmbedBuilderWeb implements IEmbedBuilder {
@override
String get key => BlockEmbed.videoType;
@override
Widget build(BuildContext context, QuillController controller, Embed node,
bool readOnly, void Function(GlobalKey videoContainerKey)? onVideoInit) {
var videoUrl = node.value.data; var videoUrl = node.value.data;
if (videoUrl.contains('youtube.com') || videoUrl.contains('youtu.be')) { if (videoUrl.contains('youtube.com') || videoUrl.contains('youtu.be')) {
final youtubeID = YoutubePlayer.convertUrlToId(videoUrl); final youtubeID = YoutubePlayer.convertUrlToId(videoUrl);
@ -81,11 +88,10 @@ Widget defaultEmbedBuilderWeb(
viewType: videoUrl, viewType: videoUrl,
), ),
); );
default:
throw UnimplementedError(
'Embeddable type "${node.value.type}" is not supported by default '
'embed builder of QuillEditor. You must pass your own builder function '
'to embedBuilder property of QuillEditor or QuillField widgets.',
);
} }
} }
List<IEmbedBuilder> get defaultEmbedBuildersWeb => [
ImageEmbedBuilderWeb(),
VideoEmbedBuilderWeb(),
];

@ -36,21 +36,35 @@ typedef WebVideoPickImpl = Future<String?> Function(
typedef MediaPickSettingSelector = Future<MediaPickSetting?> Function( typedef MediaPickSettingSelector = Future<MediaPickSetting?> Function(
BuildContext context); BuildContext context);
abstract class IEmbedBuilder {
String get key;
Widget defaultEmbedBuilder( Widget build(
BuildContext context,
QuillController controller,
leaf.Embed node,
bool readOnly,
void Function(GlobalKey videoContainerKey)? onVideoInit,
);
}
class ImageEmbedBuilder implements IEmbedBuilder {
@override
String get key => BlockEmbed.imageType;
@override
Widget build(
BuildContext context, BuildContext context,
QuillController controller, QuillController controller,
leaf.Embed node, leaf.Embed node,
bool readOnly, bool readOnly,
void Function(GlobalKey videoContainerKey)? onVideoInit, void Function(GlobalKey videoContainerKey)? onVideoInit,
) { ) {
assert(!kIsWeb, 'Please provide EmbedBuilder for Web'); assert(!kIsWeb, 'Please provide image EmbedBuilder for Web');
Tuple2<double?, double?>? _widthHeight;
switch (node.value.type) {
case BlockEmbed.imageType:
final imageUrl = standardizeImageUrl(node.value.data);
var image; var image;
final imageUrl = standardizeImageUrl(node.value.data);
Tuple2<double?, double?>? _widthHeight;
final style = node.style.attributes['style']; final style = node.style.attributes['style'];
if (isMobile() && style != null) { if (isMobile() && style != null) {
final _attrs = parseKeyValuePairs(style.value.toString(), { final _attrs = parseKeyValuePairs(style.value.toString(), {
@ -162,7 +176,22 @@ Widget defaultEmbedBuilder(
// We provide option menu for mobile platform excluding base64 image // We provide option menu for mobile platform excluding base64 image
return _menuOptionsForReadonlyImage(context, imageUrl, image); return _menuOptionsForReadonlyImage(context, imageUrl, image);
case BlockEmbed.videoType: }
}
class VideoEmbedBuilder implements IEmbedBuilder {
@override
String get key => BlockEmbed.videoType;
@override
Widget build(
BuildContext context,
QuillController controller,
leaf.Embed node,
bool readOnly,
void Function(GlobalKey videoContainerKey)? onVideoInit) {
assert(!kIsWeb, 'Please provide video EmbedBuilder for Web');
final videoUrl = node.value.data; final videoUrl = node.value.data;
if (videoUrl.contains('youtube.com') || videoUrl.contains('youtu.be')) { if (videoUrl.contains('youtube.com') || videoUrl.contains('youtu.be')) {
return YoutubeVideoApp( return YoutubeVideoApp(
@ -174,9 +203,23 @@ Widget defaultEmbedBuilder(
readOnly: readOnly, readOnly: readOnly,
onVideoInit: onVideoInit, onVideoInit: onVideoInit,
); );
case BlockEmbed.formulaType: }
final mathController = MathFieldEditingController(); }
class FormulaEmbedBuilder implements IEmbedBuilder {
@override
String get key => BlockEmbed.formulaType;
@override
Widget build(
BuildContext context,
QuillController controller,
leaf.Embed node,
bool readOnly,
void Function(GlobalKey videoContainerKey)? onVideoInit) {
assert(!kIsWeb, 'Please provide formula EmbedBuilder for Web');
final mathController = MathFieldEditingController();
return Focus( return Focus(
onFocusChange: (hasFocus) { onFocusChange: (hasFocus) {
if (hasFocus) { if (hasFocus) {
@ -192,15 +235,15 @@ Widget defaultEmbedBuilder(
onSubmitted: (value) {}, onSubmitted: (value) {},
), ),
); );
default:
throw UnimplementedError(
'Embeddable type "${node.value.type}" is not supported by default '
'embed builder of QuillEditor. You must pass your own builder function '
'to embedBuilder property of QuillEditor or QuillField widgets.',
);
} }
} }
List<IEmbedBuilder> get defaultEmbedBuilders => [
ImageEmbedBuilder(),
VideoEmbedBuilder(),
FormulaEmbedBuilder(),
];
Widget _menuOptionsForReadonlyImage( Widget _menuOptionsForReadonlyImage(
BuildContext context, String imageUrl, Widget image) { BuildContext context, String imageUrl, Widget image) {
return GestureDetector( return GestureDetector(

@ -9,6 +9,8 @@ import 'package:flutter/services.dart';
import 'package:i18n_extension/i18n_widget.dart'; import 'package:i18n_extension/i18n_widget.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
import '../../flutter_quill.dart';
import '../embeds/default_embed_builder.dart';
import '../models/documents/document.dart'; import '../models/documents/document.dart';
import '../models/documents/nodes/container.dart' as container_node; import '../models/documents/nodes/container.dart' as container_node;
import '../models/documents/nodes/embeddable.dart'; import '../models/documents/nodes/embeddable.dart';
@ -19,7 +21,6 @@ import 'controller.dart';
import 'cursor.dart'; import 'cursor.dart';
import 'default_styles.dart'; import 'default_styles.dart';
import 'delegate.dart'; import 'delegate.dart';
import '../embeds/default_embed_builder.dart';
import 'float_cursor.dart'; import 'float_cursor.dart';
import 'link.dart'; import 'link.dart';
import 'raw_editor.dart'; import 'raw_editor.dart';
@ -168,8 +169,7 @@ class QuillEditor extends StatefulWidget {
this.onSingleLongTapStart, this.onSingleLongTapStart,
this.onSingleLongTapMoveUpdate, this.onSingleLongTapMoveUpdate,
this.onSingleLongTapEnd, this.onSingleLongTapEnd,
this.embedBuilder = defaultEmbedBuilder, this.embedBuilders,
this.customElementsEmbedBuilder,
this.linkActionPickerDelegate = defaultLinkActionPickerDelegate, this.linkActionPickerDelegate = defaultLinkActionPickerDelegate,
this.customStyleBuilder, this.customStyleBuilder,
this.locale, this.locale,
@ -182,6 +182,7 @@ class QuillEditor extends StatefulWidget {
required QuillController controller, required QuillController controller,
required bool readOnly, required bool readOnly,
Brightness? keyboardAppearance, Brightness? keyboardAppearance,
Iterable<IEmbedBuilder>? embedBuilders,
/// The locale to use for the editor toolbar, defaults to system locale /// The locale to use for the editor toolbar, defaults to system locale
/// More at https://github.com/singerdmx/flutter-quill#translation /// More at https://github.com/singerdmx/flutter-quill#translation
@ -198,6 +199,7 @@ class QuillEditor extends StatefulWidget {
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
keyboardAppearance: keyboardAppearance ?? Brightness.light, keyboardAppearance: keyboardAppearance ?? Brightness.light,
locale: locale, locale: locale,
embedBuilders: embedBuilders,
); );
} }
@ -346,8 +348,7 @@ class QuillEditor extends StatefulWidget {
LongPressEndDetails details, TextPosition Function(Offset offset))? LongPressEndDetails details, TextPosition Function(Offset offset))?
onSingleLongTapEnd; onSingleLongTapEnd;
final EmbedBuilder embedBuilder; final Iterable<IEmbedBuilder>? embedBuilders;
final CustomEmbedBuilder? customElementsEmbedBuilder;
final CustomStyleBuilder? customStyleBuilder; final CustomStyleBuilder? customStyleBuilder;
/// The locale to use for the editor toolbar, defaults to system locale /// The locale to use for the editor toolbar, defaults to system locale
@ -473,23 +474,28 @@ class QuillEditorState extends State<QuillEditor>
readOnly, readOnly,
onVideoInit, onVideoInit,
) { ) {
final customElementsEmbedBuilder = widget.customElementsEmbedBuilder; final builders = widget.embedBuilders;
final isCustomType = node.value.type == BlockEmbed.customType;
if (customElementsEmbedBuilder != null && isCustomType) { if (builders != null) {
return customElementsEmbedBuilder( var _node = node;
context,
controller, // Creates correct node for custom embed
CustomBlockEmbed.fromJsonString(node.value.data), if (node.value.type == BlockEmbed.customType) {
readOnly, _node = Embed(CustomBlockEmbed.fromJsonString(node.value.data));
onVideoInit,
);
} }
return widget.embedBuilder(
context, for (final builder in builders) {
controller, if (builder.key == _node.value.type) {
node, return builder.build(
readOnly, context, controller, _node, readOnly, onVideoInit);
onVideoInit, }
}
}
throw UnimplementedError(
'Embeddable type "${node.value.type}" is not supported by supplied '
'embed builders. You must pass your own builder function to '
'embedBuilders property of QuillEditor or QuillField widgets.',
); );
}, },
linkActionPickerDelegate: widget.linkActionPickerDelegate, linkActionPickerDelegate: widget.linkActionPickerDelegate,

@ -46,6 +46,7 @@ class RawEditor extends StatefulWidget {
required this.cursorStyle, required this.cursorStyle,
required this.selectionColor, required this.selectionColor,
required this.selectionCtrls, required this.selectionCtrls,
required this.embedBuilder,
Key? key, Key? key,
this.scrollable = true, this.scrollable = true,
this.padding = EdgeInsets.zero, this.padding = EdgeInsets.zero,
@ -70,7 +71,6 @@ class RawEditor extends StatefulWidget {
this.keyboardAppearance = Brightness.light, this.keyboardAppearance = Brightness.light,
this.enableInteractiveSelection = true, this.enableInteractiveSelection = true,
this.scrollPhysics, this.scrollPhysics,
this.embedBuilder = defaultEmbedBuilder,
this.linkActionPickerDelegate = defaultLinkActionPickerDelegate, this.linkActionPickerDelegate = defaultLinkActionPickerDelegate,
this.customStyleBuilder, this.customStyleBuilder,
this.floatingCursorDisabled = false}) this.floatingCursorDisabled = false})

Loading…
Cancel
Save