From d09b9fcc831000d3981bef13b9f01987f27031f6 Mon Sep 17 00:00:00 2001 From: Amir Panahandeh Date: Fri, 26 Sep 2025 01:00:03 +0330 Subject: [PATCH 1/3] Refactor embed API to accept WidgetSpan configs --- packages/fleather/example/lib/main.dart | 32 +++++++++++-------- packages/fleather/lib/src/widgets/editor.dart | 24 +++++++++++--- .../fleather/lib/src/widgets/text_line.dart | 12 +++++-- 3 files changed, 48 insertions(+), 20 deletions(-) diff --git a/packages/fleather/example/lib/main.dart b/packages/fleather/example/lib/main.dart index 9dca5aef..4d989585 100644 --- a/packages/fleather/example/lib/main.dart +++ b/packages/fleather/example/lib/main.dart @@ -143,14 +143,17 @@ class _HomePageState extends State { ); } - Widget _embedBuilder(BuildContext context, EmbedNode node) { + FleatherEmbed _embedBuilder(BuildContext context, EmbedNode node) { if (node.value.type == 'icon') { final data = node.value.data; // Icons.rocket_launch_outlined - return Icon( - IconData(int.parse(data['codePoint']), fontFamily: data['fontFamily']), - color: Color(int.parse(data['color'])), - size: 18, + return FleatherEmbed( + child: Icon( + IconData(int.parse(data['codePoint']), + fontFamily: data['fontFamily']), + color: Color(int.parse(data['color'])), + size: 18, + ), ); } @@ -176,14 +179,17 @@ class _HomePageState extends State { } } if (image != null) { - return Padding( - // Caret takes 2 pixels, hence not symmetric padding values. - padding: const EdgeInsets.only(left: 4, right: 2, top: 2, bottom: 2), - child: Container( - width: (node.value.data['width'] as num?)?.toDouble() ?? 300, - height: (node.value.data['height'] as num?)?.toDouble() ?? 300, - decoration: BoxDecoration( - image: DecorationImage(image: image, fit: BoxFit.cover), + return FleatherEmbed( + child: Padding( + // Caret takes 2 pixels, hence not symmetric padding values. + padding: + const EdgeInsets.only(left: 4, right: 2, top: 2, bottom: 2), + child: Container( + width: (node.value.data['width'] as num?)?.toDouble() ?? 300, + height: (node.value.data['height'] as num?)?.toDouble() ?? 300, + decoration: BoxDecoration( + image: DecorationImage(image: image, fit: BoxFit.cover), + ), ), ), ); diff --git a/packages/fleather/lib/src/widgets/editor.dart b/packages/fleather/lib/src/widgets/editor.dart index a9b82990..5a499623 100644 --- a/packages/fleather/lib/src/widgets/editor.dart +++ b/packages/fleather/lib/src/widgets/editor.dart @@ -76,22 +76,24 @@ Widget defaultSpellCheckMenuBuilder( } /// Builder function for embeddable objects in [FleatherEditor]. -typedef FleatherEmbedBuilder = Widget Function( +typedef FleatherEmbedBuilder = FleatherEmbed Function( BuildContext context, EmbedNode node); /// Default implementation of a builder function for embeddable objects in /// Fleather. /// /// Only supports "horizontal rule" embeds. -Widget defaultFleatherEmbedBuilder(BuildContext context, EmbedNode node) { +FleatherEmbed defaultFleatherEmbedBuilder( + BuildContext context, EmbedNode node) { if (node.value.type == 'hr') { final fleatherThemeData = FleatherTheme.of(context)!; - return Divider( + return FleatherEmbed( + child: Divider( height: fleatherThemeData.horizontalRule.height, thickness: fleatherThemeData.horizontalRule.thickness, color: fleatherThemeData.horizontalRule.color, - ); + )); } throw UnimplementedError( 'Embeddable type "${node.value.type}" is not supported by default embed ' @@ -99,6 +101,20 @@ Widget defaultFleatherEmbedBuilder(BuildContext context, EmbedNode node) { 'embedBuilder property of FleatherEditor or FleatherField widgets.'); } +final class FleatherEmbed { + final Widget child; + final PlaceholderAlignment placeholderAlignment; + final TextBaseline? textBaseline; + final TextStyle? textStyle; + + const FleatherEmbed({ + required this.child, + this.placeholderAlignment = PlaceholderAlignment.bottom, + this.textBaseline, + this.textStyle, + }); +} + /// Widget for editing rich text documents. class FleatherEditor extends StatefulWidget { /// Controller object which establishes a link between a rich text document diff --git a/packages/fleather/lib/src/widgets/text_line.dart b/packages/fleather/lib/src/widgets/text_line.dart index 7cf3e2e2..e8ee8e1e 100644 --- a/packages/fleather/lib/src/widgets/text_line.dart +++ b/packages/fleather/lib/src/widgets/text_line.dart @@ -122,8 +122,9 @@ class _TextLineState extends State { Widget build(BuildContext context) { assert(debugCheckHasMediaQuery(context)); if (widget.node.hasBlockEmbed) { - final embed = widget.node.children.single as EmbedNode; - return EmbedProxy(child: widget.embedBuilder(context, embed)); + final embedNode = widget.node.children.single as EmbedNode; + final embedObject = widget.embedBuilder(context, embedNode); + return EmbedProxy(child: embedObject.child); } final theme = FleatherTheme.of(context)!; final text = buildText(context, widget.node, theme); @@ -174,8 +175,13 @@ class _TextLineState extends State { InlineSpan _segmentToTextSpan(Node segment, FleatherThemeData theme) { if (segment is EmbedNode) { + final embedObject = widget.embedBuilder(context, segment); return WidgetSpan( - child: EmbedProxy(child: widget.embedBuilder(context, segment))); + child: EmbedProxy(child: embedObject.child), + alignment: embedObject.placeholderAlignment, + baseline: embedObject.textBaseline, + style: embedObject.textStyle, + ); } final text = segment as TextNode; final attrs = text.style; From aef97c358406ba7e1d67c0d32580544c2a032bd4 Mon Sep 17 00:00:00 2001 From: Amir Panahandeh Date: Fri, 26 Sep 2025 12:56:49 +0330 Subject: [PATCH 2/3] Introduce FleatherBlockEmbed and FleatherSpanEmbed --- packages/fleather/example/lib/main.dart | 4 ++-- packages/fleather/lib/src/widgets/editor.dart | 17 +++++++++++++---- .../fleather/lib/src/widgets/text_line.dart | 17 +++++++++++------ 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/packages/fleather/example/lib/main.dart b/packages/fleather/example/lib/main.dart index 4d989585..3060e923 100644 --- a/packages/fleather/example/lib/main.dart +++ b/packages/fleather/example/lib/main.dart @@ -147,7 +147,7 @@ class _HomePageState extends State { if (node.value.type == 'icon') { final data = node.value.data; // Icons.rocket_launch_outlined - return FleatherEmbed( + return FleatherSpanEmbed( child: Icon( IconData(int.parse(data['codePoint']), fontFamily: data['fontFamily']), @@ -179,7 +179,7 @@ class _HomePageState extends State { } } if (image != null) { - return FleatherEmbed( + return FleatherBlockEmbed( child: Padding( // Caret takes 2 pixels, hence not symmetric padding values. padding: diff --git a/packages/fleather/lib/src/widgets/editor.dart b/packages/fleather/lib/src/widgets/editor.dart index 5a499623..57ec147c 100644 --- a/packages/fleather/lib/src/widgets/editor.dart +++ b/packages/fleather/lib/src/widgets/editor.dart @@ -88,7 +88,7 @@ FleatherEmbed defaultFleatherEmbedBuilder( if (node.value.type == 'hr') { final fleatherThemeData = FleatherTheme.of(context)!; - return FleatherEmbed( + return FleatherBlockEmbed( child: Divider( height: fleatherThemeData.horizontalRule.height, thickness: fleatherThemeData.horizontalRule.thickness, @@ -101,14 +101,23 @@ FleatherEmbed defaultFleatherEmbedBuilder( 'embedBuilder property of FleatherEditor or FleatherField widgets.'); } -final class FleatherEmbed { +sealed class FleatherEmbed { final Widget child; + + const FleatherEmbed({required this.child}); +} + +class FleatherBlockEmbed extends FleatherEmbed { + const FleatherBlockEmbed({required super.child}); +} + +class FleatherSpanEmbed extends FleatherEmbed { final PlaceholderAlignment placeholderAlignment; final TextBaseline? textBaseline; final TextStyle? textStyle; - const FleatherEmbed({ - required this.child, + const FleatherSpanEmbed({ + required super.child, this.placeholderAlignment = PlaceholderAlignment.bottom, this.textBaseline, this.textStyle, diff --git a/packages/fleather/lib/src/widgets/text_line.dart b/packages/fleather/lib/src/widgets/text_line.dart index e8ee8e1e..965f36b7 100644 --- a/packages/fleather/lib/src/widgets/text_line.dart +++ b/packages/fleather/lib/src/widgets/text_line.dart @@ -176,12 +176,17 @@ class _TextLineState extends State { InlineSpan _segmentToTextSpan(Node segment, FleatherThemeData theme) { if (segment is EmbedNode) { final embedObject = widget.embedBuilder(context, segment); - return WidgetSpan( - child: EmbedProxy(child: embedObject.child), - alignment: embedObject.placeholderAlignment, - baseline: embedObject.textBaseline, - style: embedObject.textStyle, - ); + final embedWidget = EmbedProxy(child: embedObject.child); + if (embedObject is FleatherBlockEmbed) { + return WidgetSpan(child: embedWidget); + } else if (embedObject is FleatherSpanEmbed) { + return WidgetSpan( + child: embedWidget, + alignment: embedObject.placeholderAlignment, + baseline: embedObject.textBaseline, + style: embedObject.textStyle, + ); + } } final text = segment as TextNode; final attrs = text.style; From 7721d049af9bf53cb929272ce161fbdfc7867b35 Mon Sep 17 00:00:00 2001 From: Amir Panahandeh Date: Thu, 9 Oct 2025 19:14:19 +0330 Subject: [PATCH 3/3] Refactor --- packages/fleather/example/lib/main.dart | 2 +- packages/fleather/lib/src/widgets/editor.dart | 10 +++------- packages/fleather/lib/src/widgets/text_line.dart | 6 +++--- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/packages/fleather/example/lib/main.dart b/packages/fleather/example/lib/main.dart index 3060e923..2bbee2e2 100644 --- a/packages/fleather/example/lib/main.dart +++ b/packages/fleather/example/lib/main.dart @@ -179,7 +179,7 @@ class _HomePageState extends State { } } if (image != null) { - return FleatherBlockEmbed( + return FleatherEmbed( child: Padding( // Caret takes 2 pixels, hence not symmetric padding values. padding: diff --git a/packages/fleather/lib/src/widgets/editor.dart b/packages/fleather/lib/src/widgets/editor.dart index 57ec147c..ba444044 100644 --- a/packages/fleather/lib/src/widgets/editor.dart +++ b/packages/fleather/lib/src/widgets/editor.dart @@ -88,7 +88,7 @@ FleatherEmbed defaultFleatherEmbedBuilder( if (node.value.type == 'hr') { final fleatherThemeData = FleatherTheme.of(context)!; - return FleatherBlockEmbed( + return FleatherEmbed( child: Divider( height: fleatherThemeData.horizontalRule.height, thickness: fleatherThemeData.horizontalRule.thickness, @@ -101,17 +101,13 @@ FleatherEmbed defaultFleatherEmbedBuilder( 'embedBuilder property of FleatherEditor or FleatherField widgets.'); } -sealed class FleatherEmbed { +final class FleatherEmbed { final Widget child; const FleatherEmbed({required this.child}); } -class FleatherBlockEmbed extends FleatherEmbed { - const FleatherBlockEmbed({required super.child}); -} - -class FleatherSpanEmbed extends FleatherEmbed { +final class FleatherSpanEmbed extends FleatherEmbed { final PlaceholderAlignment placeholderAlignment; final TextBaseline? textBaseline; final TextStyle? textStyle; diff --git a/packages/fleather/lib/src/widgets/text_line.dart b/packages/fleather/lib/src/widgets/text_line.dart index 965f36b7..31e4b718 100644 --- a/packages/fleather/lib/src/widgets/text_line.dart +++ b/packages/fleather/lib/src/widgets/text_line.dart @@ -177,15 +177,15 @@ class _TextLineState extends State { if (segment is EmbedNode) { final embedObject = widget.embedBuilder(context, segment); final embedWidget = EmbedProxy(child: embedObject.child); - if (embedObject is FleatherBlockEmbed) { - return WidgetSpan(child: embedWidget); - } else if (embedObject is FleatherSpanEmbed) { + if (embedObject is FleatherSpanEmbed) { return WidgetSpan( child: embedWidget, alignment: embedObject.placeholderAlignment, baseline: embedObject.textBaseline, style: embedObject.textStyle, ); + } else { + return WidgetSpan(child: embedWidget); } } final text = segment as TextNode;