Skip to content

[flutter_markdown] Pass parent TextStyle down to MarkdownElementBuilder.visitElementAfter #4393

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jul 7, 2023
5 changes: 5 additions & 0 deletions packages/flutter_markdown/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 0.6.17

* Introduces a new `MarkdownElementBuilder.visitElementAfterWithContext()` method passing the widget `BuildContext` and
the parent text's `TextStyle`.

## 0.6.16

* Adds `tableVerticalAlignment` property to allow aligning table cells vertically.
Expand Down
15 changes: 13 additions & 2 deletions packages/flutter_markdown/lib/src/builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -454,8 +461,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) {
if (current.children.isEmpty) {
current.children.add(child);
Expand Down
20 changes: 20 additions & 0 deletions packages/flutter_markdown/lib/src/widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,33 @@ 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 with this tag, it will be
/// passed as [preferredStyle].
///
/// If parent element has [TextStyle] set, it will be passed as
/// [parentStyle].
///
/// If a widget build isn't needed, 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.
///
/// If [MarkdownWidget.styleSheet] has a style of this tag, will passing
/// to [preferredStyle].
///
/// If you needn't build a widget, return null.
@Deprecated('Use visitElementAfterWithContext() instead.')
Widget? visitElementAfter(md.Element element, TextStyle? preferredStyle) =>
null;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/flutter_markdown/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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.16
version: 0.6.17

environment:
sdk: ">=3.0.0 <4.0.0"
Expand Down
81 changes: 80 additions & 1 deletion packages/flutter_markdown/test/custom_syntax_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -83,6 +83,34 @@ void defineTests() {
expect(widgetSpan.child, isInstanceOf<Container>());
},
);

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: <md.InlineSyntax>[InlineTextColorSyntax()],
builders: <String, MarkdownElementBuilder>{
'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(
Expand Down Expand Up @@ -250,6 +278,57 @@ 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<String, Color> contentColors = <String, Color>{
'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),
),
);
}
}

class ImgBuilder extends MarkdownElementBuilder {
@override
Widget? visitElementAfter(md.Element element, TextStyle? preferredStyle) {
Expand Down