diff --git a/packages/flutter_markdown/CHANGELOG.md b/packages/flutter_markdown/CHANGELOG.md index dba9cb95878..002d6d150ab 100644 --- a/packages/flutter_markdown/CHANGELOG.md +++ b/packages/flutter_markdown/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.7.4+1 + +* Makes it so that custom blocks are not limited to being a Column or + SizedBox. + ## 0.7.4 * Makes paragraphs in blockquotes soft-wrap like a normal `
` instead of hard-wrapping like a `` block.
diff --git a/packages/flutter_markdown/lib/src/builder.dart b/packages/flutter_markdown/lib/src/builder.dart
index 3a0f4b15e99..ea9af16acfb 100644
--- a/packages/flutter_markdown/lib/src/builder.dart
+++ b/packages/flutter_markdown/lib/src/builder.dart
@@ -383,20 +383,29 @@ class MarkdownBuilder implements md.NodeVisitor {
_addAnonymousBlockIfNeeded();
final _BlockElement current = _blocks.removeLast();
- Widget child;
- if (current.children.isNotEmpty) {
- child = Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: fitContent
- ? CrossAxisAlignment.start
- : CrossAxisAlignment.stretch,
- children: current.children,
- );
- } else {
- child = const SizedBox();
+ Widget defaultChild() {
+ if (current.children.isNotEmpty) {
+ return Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: fitContent
+ ? CrossAxisAlignment.start
+ : CrossAxisAlignment.stretch,
+ children: current.children,
+ );
+ } else {
+ return const SizedBox();
+ }
}
+ Widget child = builders[tag]?.visitElementAfterWithContext(
+ delegate.context,
+ element,
+ styleSheet.styles[tag],
+ _inlines.isNotEmpty ? _inlines.last.style : null,
+ ) ??
+ defaultChild();
+
if (_isListTag(tag)) {
assert(_listIndents.isNotEmpty);
_listIndents.removeLast();
diff --git a/packages/flutter_markdown/pubspec.yaml b/packages/flutter_markdown/pubspec.yaml
index f0ac1fd8353..5f743a8b032 100644
--- a/packages/flutter_markdown/pubspec.yaml
+++ b/packages/flutter_markdown/pubspec.yaml
@@ -4,7 +4,7 @@ description: A Markdown renderer for Flutter. Create rich text output,
formatted with simple Markdown tags.
repository: https://github.com/flutter/packages/tree/main/packages/flutter_markdown
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+flutter_markdown%22
-version: 0.7.4
+version: 0.7.4+1
environment:
sdk: ^3.3.0
diff --git a/packages/flutter_markdown/test/custom_syntax_test.dart b/packages/flutter_markdown/test/custom_syntax_test.dart
index 28d55cdfd0b..8dc0c806e51 100644
--- a/packages/flutter_markdown/test/custom_syntax_test.dart
+++ b/packages/flutter_markdown/test/custom_syntax_test.dart
@@ -59,6 +59,35 @@ void defineTests() {
},
);
+ testWidgets(
+ 'Block with custom tag',
+ (WidgetTester tester) async {
+ const String textBefore = 'Before ';
+ const String textAfter = ' After';
+ const String blockContent = 'Custom content rendered in a ColoredBox';
+
+ await tester.pumpWidget(
+ boilerplate(
+ Markdown(
+ data:
+ '$textBefore\n{{custom}}\n$blockContent\n{{/custom}}\n$textAfter',
+ extensionSet: md.ExtensionSet.none,
+ blockSyntaxes: [CustomTagBlockSyntax()],
+ builders: {
+ 'custom': CustomTagBlockBuilder(),
+ },
+ ),
+ ),
+ );
+
+ final ColoredBox container =
+ tester.widgetList(find.byType(ColoredBox)).first as ColoredBox;
+ expect(container.color, Colors.red);
+ expect(container.child, isInstanceOf());
+ expect((container.child! as Text).data, blockContent);
+ },
+ );
+
testWidgets(
'link for wikistyle',
(WidgetTester tester) async {
@@ -380,3 +409,54 @@ class NoteSyntax extends md.BlockSyntax {
@override
RegExp get pattern => RegExp(r'^\[!NOTE] ');
}
+
+class CustomTagBlockBuilder extends MarkdownElementBuilder {
+ @override
+ bool isBlockElement() => true;
+
+ @override
+ Widget visitElementAfterWithContext(
+ BuildContext context,
+ md.Element element,
+ TextStyle? preferredStyle,
+ TextStyle? parentStyle,
+ ) {
+ if (element.tag == 'custom') {
+ final String content = element.attributes['content']!;
+ return ColoredBox(
+ color: Colors.red, child: Text(content, style: preferredStyle));
+ }
+ return const SizedBox.shrink();
+ }
+}
+
+class CustomTagBlockSyntax extends md.BlockSyntax {
+ @override
+ bool canParse(md.BlockParser parser) {
+ return parser.current.content.startsWith('{{custom}}');
+ }
+
+ @override
+ RegExp get pattern => RegExp(r'\{\{custom\}\}([\s\S]*?)\{\{/custom\}\}');
+
+ @override
+ md.Node parse(md.BlockParser parser) {
+ parser.advance();
+
+ final StringBuffer buffer = StringBuffer();
+ while (
+ !parser.current.content.startsWith('{{/custom}}') && !parser.isDone) {
+ buffer.writeln(parser.current.content);
+ parser.advance();
+ }
+
+ if (!parser.isDone) {
+ parser.advance();
+ }
+
+ final String content = buffer.toString().trim();
+ final md.Element element = md.Element.empty('custom');
+ element.attributes['content'] = content;
+ return element;
+ }
+}