diff --git a/packages/flutter_markdown/CHANGELOG.md b/packages/flutter_markdown/CHANGELOG.md index 1ec37e27fcf..2d4760e7527 100644 --- a/packages/flutter_markdown/CHANGELOG.md +++ b/packages/flutter_markdown/CHANGELOG.md @@ -1,5 +1,9 @@ -## NEXT +## 0.6.15 +* Introduce a new `MarkdownElementBuilder.visitElementAfterWithContext()` method passing the widget `BuildContext` and + the parent texts' `TextStyle`. + This should allow custom syntax implementations to get data from the current context, as well as properly style any + text, e.g. a color syntax can make parts of a headline colored, while still keeping the font size of the headline. * Aligns Dart and Flutter SDK constraints. ## 0.6.14 diff --git a/packages/flutter_markdown/lib/src/builder.dart b/packages/flutter_markdown/lib/src/builder.dart index e8521136b84..7eecc54a004 100644 --- a/packages/flutter_markdown/lib/src/builder.dart +++ b/packages/flutter_markdown/lib/src/builder.dart @@ -74,6 +74,13 @@ class _InlineElement { /// A delegate used by [MarkdownBuilder] to control the widgets it creates. abstract class MarkdownBuilderDelegate { + /// Returns the [BuildContext] of the [MarkdownWidget]. + /// + /// The context will be passed down to the + /// [MarkdownElementBuilder.visitElementBefore] method and allows elements to + /// get information from the context. + BuildContext get context; + /// Returns a gesture recognizer to use for an `a` element with the given /// text, `href` attribute, and title. GestureRecognizer createLink(String text, String? href, String title); @@ -451,8 +458,12 @@ class MarkdownBuilder implements md.NodeVisitor { } if (builders.containsKey(tag)) { - final Widget? child = - builders[tag]!.visitElementAfter(element, styleSheet.styles[tag]); + final Widget? child = builders[tag]!.visitElementAfterWithContext( + delegate.context, + element, + styleSheet.styles[tag], + parent.style, + ); if (child != null) { current.children[0] = child; } diff --git a/packages/flutter_markdown/lib/src/widget.dart b/packages/flutter_markdown/lib/src/widget.dart index 690f5b89572..42f4aa03ca8 100644 --- a/packages/flutter_markdown/lib/src/widget.dart +++ b/packages/flutter_markdown/lib/src/widget.dart @@ -70,6 +70,25 @@ abstract class MarkdownElementBuilder { /// If you needn't build a widget, return null. Widget? visitText(md.Text text, TextStyle? preferredStyle) => null; + /// Called when an Element has been reached, after its children have been + /// visited. + /// + /// If [MarkdownWidget.styleSheet] has a style of this tag, will passing + /// to [preferredStyle]. + /// + /// If parent element has [TextStyle]'s set, it will be passed as + /// [parentStyle]. + /// + /// If you needn't build a widget, return null. + Widget? visitElementAfterWithContext( + BuildContext context, + md.Element element, + TextStyle? preferredStyle, + TextStyle? parentStyle, + ) { + return visitElementAfter(element, preferredStyle); + } + /// Called when an Element has been reached, after its children have been /// visited. /// @@ -77,6 +96,7 @@ abstract class MarkdownElementBuilder { /// to [preferredStyle]. /// /// If you needn't build a widget, return null. + @Deprecated('Use visitElementAfterWithContext() instead.') Widget? visitElementAfter(md.Element element, TextStyle? preferredStyle) => null; } diff --git a/packages/flutter_markdown/pubspec.yaml b/packages/flutter_markdown/pubspec.yaml index e2a1c2e0564..427c279c5b3 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.6.14 +version: 0.6.15 environment: sdk: ">=2.17.0 <3.0.0" diff --git a/packages/flutter_markdown/test/custom_syntax_test.dart b/packages/flutter_markdown/test/custom_syntax_test.dart index 6254a1be990..0360a117e42 100644 --- a/packages/flutter_markdown/test/custom_syntax_test.dart +++ b/packages/flutter_markdown/test/custom_syntax_test.dart @@ -3,7 +3,7 @@ // found in the LICENSE file. import 'package:flutter/gestures.dart'; -import 'package:flutter/widgets.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:markdown/markdown.dart' as md; @@ -83,6 +83,34 @@ void defineTests() { expect(widgetSpan.child, isInstanceOf()); }, ); + + testWidgets( + 'visitElementAfterWithContext is handled correctly', + (WidgetTester tester) async { + await tester.pumpWidget( + boilerplate( + Markdown( + data: r'# This is a header with some \color1{color} in it', + extensionSet: md.ExtensionSet.none, + inlineSyntaxes: [InlineTextColorSyntax()], + builders: { + 'inlineTextColor': InlineTextColorElementBuilder(), + }, + ), + ), + ); + + final RichText textWidget = tester.widget(find.byType(RichText)); + final TextSpan rootSpan = textWidget.text as TextSpan; + final TextSpan firstSpan = rootSpan.children![0] as TextSpan; + final TextSpan secondSpan = rootSpan.children![1] as TextSpan; + final TextSpan thirdSpan = rootSpan.children![2] as TextSpan; + + expect(secondSpan.style!.color, Colors.red); + expect(secondSpan.style!.fontSize, firstSpan.style!.fontSize); + expect(secondSpan.style!.fontSize, thirdSpan.style!.fontSize); + }, + ); }); testWidgets( @@ -225,3 +253,54 @@ class ContainerBuilder2 extends MarkdownElementBuilder { ); } } + +// Note: The implementation of inline span is incomplete, it does not handle +// bold, italic, ... text with a colored block. +// This would not work: `\color1{Text with *bold* text}` +class InlineTextColorSyntax extends md.InlineSyntax { + InlineTextColorSyntax() : super(r'\\color([1-9]){(.*?)}'); + + @override + bool onMatch(md.InlineParser parser, Match match) { + final String colorId = match.group(1)!; + final String textContent = match.group(2)!; + final md.Element node = md.Element.text( + 'inlineTextColor', + textContent, + )..attributes['color'] = colorId; + + parser.addNode(node); + + parser.addNode( + md.Text(''), + ); + return true; + } +} + +class InlineTextColorElementBuilder extends MarkdownElementBuilder { + @override + Widget visitElementAfterWithContext( + BuildContext context, + md.Element element, + TextStyle? preferredStyle, + TextStyle? parentStyle, + ) { + final String innerText = element.textContent; + final String color = element.attributes['color'] ?? ''; + + final Map contentColors = { + '1': Colors.red, + '2': Colors.green, + '3': Colors.blue, + }; + final Color? contentColor = contentColors[color]; + + return RichText( + text: TextSpan( + text: innerText, + style: parentStyle?.copyWith(color: contentColor), + ), + ); + } +}