Skip to content

fix: improve API for ExtensionContext and export marker.dart #1273

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 1 commit into from
May 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions lib/src/builtins/details_element_builtin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,8 @@ class DetailsElementBuiltIn extends HtmlExtension {
}

@override
InlineSpan build(ExtensionContext context,
Map<StyledElement, InlineSpan> Function() buildChildren) {
final childList = buildChildren();
InlineSpan build(ExtensionContext context) {
final childList = context.builtChildrenMap!;
final children = childList.values;

InlineSpan? firstChild = children.isNotEmpty ? children.first : null;
Expand Down
3 changes: 1 addition & 2 deletions lib/src/builtins/image_builtin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,7 @@ class ImageBuiltIn extends HtmlExtension {
}

@override
InlineSpan build(ExtensionContext context,
Map<StyledElement, InlineSpan> Function() buildChildren) {
InlineSpan build(ExtensionContext context) {
final element = context.styledElement as ImageElement;

final imageStyle = Style(
Expand Down
5 changes: 2 additions & 3 deletions lib/src/builtins/interactive_element_builtin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,9 @@ class InteractiveElementBuiltIn extends HtmlExtension {
}

@override
InlineSpan build(ExtensionContext context,
Map<StyledElement, InlineSpan> Function() buildChildren) {
InlineSpan build(ExtensionContext context) {
return TextSpan(
children: buildChildren().values.map((childSpan) {
children: context.inlineSpanChildren!.map((childSpan) {
return _processInteractableChild(context, childSpan);
}).toList(),
);
Expand Down
3 changes: 1 addition & 2 deletions lib/src/builtins/ruby_builtin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@ class RubyBuiltIn extends HtmlExtension {
}

@override
InlineSpan build(ExtensionContext context,
Map<StyledElement, InlineSpan> Function() buildChildren) {
InlineSpan build(ExtensionContext context) {
StyledElement? node;
List<Widget> widgets = <Widget>[];
final rubySize = context.parser.style['rt']?.fontSize?.value ??
Expand Down
9 changes: 3 additions & 6 deletions lib/src/builtins/styled_element_builtin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -414,8 +414,7 @@ class StyledElementBuiltIn extends HtmlExtension {
}

@override
InlineSpan build(ExtensionContext context,
Map<StyledElement, InlineSpan> Function() buildChildren) {
InlineSpan build(ExtensionContext context) {
if (context.styledElement!.style.display == Display.listItem ||
((context.styledElement!.style.display == Display.block ||
context.styledElement!.style.display == Display.inlineBlock) &&
Expand All @@ -430,8 +429,7 @@ class StyledElementBuiltIn extends HtmlExtension {
shrinkWrap: context.parser.shrinkWrap,
childIsReplaced: ["iframe", "img", "video", "audio"]
.contains(context.styledElement!.name),
children: buildChildren()
.entries
children: context.builtChildrenMap!.entries
.expandIndexed((i, child) => [
child.value,
if (context.parser.shrinkWrap &&
Expand All @@ -448,8 +446,7 @@ class StyledElementBuiltIn extends HtmlExtension {

return TextSpan(
style: context.styledElement!.style.generateTextStyle(),
children: buildChildren()
.entries
children: context.builtChildrenMap!.entries
.expandIndexed((index, child) => [
child.value,
if (context.parser.shrinkWrap &&
Expand Down
3 changes: 1 addition & 2 deletions lib/src/builtins/text_builtin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,7 @@ class TextBuiltIn extends HtmlExtension {
}

@override
InlineSpan build(ExtensionContext context,
Map<StyledElement, InlineSpan> Function() buildChildren) {
InlineSpan build(ExtensionContext context) {
if (context.styledElement is LinebreakContentElement) {
return TextSpan(
text: '\n',
Expand Down
4 changes: 2 additions & 2 deletions lib/src/builtins/vertical_align_builtin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ class VerticalAlignBuiltIn extends HtmlExtension {
}

@override
InlineSpan build(ExtensionContext context, buildChildren) {
InlineSpan build(ExtensionContext context) {
return WidgetSpan(
child: Transform.translate(
offset: Offset(0, _getVerticalOffset(context.styledElement!)),
child: CssBoxWidget.withInlineSpanChildren(
children: buildChildren().values.toList(),
children: context.inlineSpanChildren!,
style: context.styledElement!.style,
),
),
Expand Down
70 changes: 46 additions & 24 deletions lib/src/extension/extension_context.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:collection';

import 'package:flutter/widgets.dart';
import 'package:flutter_html/src/html_parser.dart';
import 'package:flutter_html/src/style.dart';
import 'package:flutter_html/src/tree/styled_element.dart';
import 'package:html/dom.dart' as html;

Expand Down Expand Up @@ -59,7 +60,8 @@ class ExtensionContext {
}));
}

/// Returns the id of the element, or an empty string if it is not present
/// Returns the id of the element, or an empty string if it is not present or
/// this Node is not an html Element.
String get id {
if (node is html.Element) {
return (node as html.Element).id;
Expand All @@ -68,8 +70,8 @@ class ExtensionContext {
return '';
}

/// Returns a set of classes on the element, or an empty set if none are
/// present.
/// Returns a set of classes on this Element, or an empty set if none are
/// present or this Node is not an html Element.
Set<String> get classes {
if (node is html.Element) {
return (node as html.Element).classes;
Expand All @@ -83,38 +85,58 @@ class ExtensionContext {
final HtmlParser parser;

/// A reference to the [StyledElement] representation of this node.
/// Guaranteed to be non-null only after the lexing step
/// Guaranteed to be non-null only after the preparing step
final StyledElement? styledElement;

/// Guaranteed only when in the `parse` method of an Extension, but it might not necessarily be the nearest BuildContext. Probably should use a `Builder` Widget if you absolutely need the most relevant BuildContext.
/// A reference to the [Style] on the [StyledElement] representation of this
/// node. Guaranteed to be non-null only after the preparing step.
Style? get style {
return styledElement?.style;
}

/// The [StyledElement] version of this node's children. Guaranteed to be
/// non-null only after the preparing step.
List<StyledElement> get styledElementChildren {
return styledElement!.children;
}

final BuildChildrenCallback? _callbackToBuildChildren;
Map<StyledElement, InlineSpan>? _builtChildren;

/// A map between the original [StyledElement] children of this node and the
/// fully built [InlineSpan] children of this node.
Map<StyledElement, InlineSpan>? get builtChildrenMap {
_builtChildren ??= _callbackToBuildChildren?.call();

return _builtChildren;
}

/// The [InlineSpan] version of this node's children. Constructed lazily.
/// Guaranteed to be non-null only when `currentStep` is `building`.
List<InlineSpan>? get inlineSpanChildren {
_builtChildren ??= _callbackToBuildChildren?.call();

return _builtChildren?.values.toList();
}

/// Guaranteed to be non-null only when `currentStep` is `building`,
/// but it might not necessarily be the nearest BuildContext. Probably should
/// use a `Builder` Widget if you need the most relevant BuildContext.
final BuildContext? buildContext;

/// Constructs a new [ExtensionContext] object with the given information.
const ExtensionContext({
ExtensionContext({
required this.currentStep,
required this.node,
required this.parser,
this.styledElement,
this.buildContext,
required this.currentStep,
});

ExtensionContext copyWith({
html.Node? node,
HtmlParser? parser,
StyledElement? styledElement,
BuildContext? buildContext,
CurrentStep? currentStep,
}) {
return ExtensionContext(
node: node ?? this.node,
parser: parser ?? this.parser,
styledElement: styledElement ?? this.styledElement,
buildContext: buildContext ?? this.buildContext,
currentStep: currentStep ?? this.currentStep,
);
}
BuildChildrenCallback? buildChildrenCallback,
}) : _callbackToBuildChildren = buildChildrenCallback;
}

typedef BuildChildrenCallback = Map<StyledElement, InlineSpan> Function();

enum CurrentStep {
preparing,
preStyling,
Expand Down
4 changes: 2 additions & 2 deletions lib/src/extension/helpers/image_extension.dart
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,11 @@ class ImageExtension extends ImageBuiltIn {
}

@override
InlineSpan build(ExtensionContext context, buildChildren) {
InlineSpan build(ExtensionContext context) {
if (builder != null) {
return builder!.call(context);
} else {
return super.build(context, buildChildren);
return super.build(context);
}
}
}
10 changes: 6 additions & 4 deletions lib/src/extension/helpers/image_tap_extension.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,13 @@ class OnImageTapExtension extends ImageBuiltIn {
}

@override
InlineSpan build(ExtensionContext context, buildChildren) {
final children = buildChildren();
InlineSpan build(ExtensionContext context) {
final children = context.builtChildrenMap!;

assert(children.keys.isNotEmpty,
"The OnImageTapExtension has been thwarted! It no longer has an `img` child");
assert(
children.keys.isNotEmpty,
"The OnImageTapExtension has been thwarted! It no longer has an `img` child",
);

final actualImage = children.keys.first;

Expand Down
2 changes: 1 addition & 1 deletion lib/src/extension/helpers/matcher_extension.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class MatcherExtension extends HtmlExtension {
}

@override
InlineSpan build(ExtensionContext context, buildChildren) {
InlineSpan build(ExtensionContext context) {
return builder(context);
}
}
2 changes: 1 addition & 1 deletion lib/src/extension/helpers/tag_extension.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class TagExtension extends HtmlExtension {
Set<String> get supportedTags => tagsToExtend;

@override
InlineSpan build(ExtensionContext context, buildChildren) {
InlineSpan build(ExtensionContext context) {
return builder(context);
}
}
7 changes: 3 additions & 4 deletions lib/src/extension/helpers/tag_wrap_extension.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,10 @@ class TagWrapExtension extends HtmlExtension {
}

@override
InlineSpan build(ExtensionContext context, buildChildren) {
final children = buildChildren();
InlineSpan build(ExtensionContext context) {
final child = CssBoxWidget.withInlineSpanChildren(
children: children.values.toList(),
style: context.styledElement!.style,
children: context.inlineSpanChildren!,
style: context.style!,
);

return WidgetSpan(
Expand Down
6 changes: 3 additions & 3 deletions lib/src/extension/html_extension.dart
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,10 @@ abstract class HtmlExtension {
/// The final step in the chain. Converts the StyledElement tree, with its
/// attached `Style` elements, into an `InlineSpan` tree that includes
/// Widget/TextSpans that can be rendered in a RichText widget.
InlineSpan build(ExtensionContext context,
Map<StyledElement, InlineSpan> Function() buildChildren) {
InlineSpan build(ExtensionContext context) {
throw UnimplementedError(
"Extension `$runtimeType` matched `${context.styledElement!.name}` but didn't implement `parse`");
"Extension `$runtimeType` matched `${context.styledElement!.name}` but didn't implement `parse`",
);
}

/// Called when the Html widget is being destroyed. This would be a very
Expand Down
24 changes: 12 additions & 12 deletions lib/src/html_parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -125,23 +125,22 @@ class HtmlParser extends StatefulWidget {
/// or HtmlExtensions available. If none of the extensions matches, returns
/// an empty TextSpan.
InlineSpan buildFromExtension(
ExtensionContext extensionContext,
Map<StyledElement, InlineSpan> Function() buildChildren, {
ExtensionContext extensionContext, {
Set<HtmlExtension> extensionsToIgnore = const {},
}) {
// Loop through every extension and see if it can handle this node
for (final extension in extensions) {
if (!extensionsToIgnore.contains(extension) &&
extension.matches(extensionContext)) {
return extension.build(extensionContext, buildChildren);
return extension.build(extensionContext);
}
}

// Loop through built in elements and see if they can handle this node.
for (final builtIn in builtIns) {
if (!extensionsToIgnore.contains(builtIn) &&
builtIn.matches(extensionContext)) {
return builtIn.build(extensionContext, buildChildren);
return builtIn.build(extensionContext);
}
}

Expand Down Expand Up @@ -380,27 +379,28 @@ class _HtmlParserState extends State<HtmlParser> {
}

InlineSpan _buildTreeRecursive(StyledElement tree) {
// Generate a function that allows children to be built lazily
Map<StyledElement, InlineSpan> buildChildren() {
return Map.fromEntries(tree.children.map((child) {
return MapEntry(child, _buildTreeRecursive(child));
}));
}

// Set the extension context for this node.
final extensionContext = ExtensionContext(
parser: widget,
buildContext: context,
node: tree.node,
styledElement: tree,
currentStep: CurrentStep.building,
buildChildrenCallback: buildChildren,
);

// Block restricted tags from getting sent to extensions
if (_isTagRestricted(extensionContext)) {
return const TextSpan(text: "");
}

// Generate a function that allows children to be generated
Map<StyledElement, InlineSpan> buildChildren() {
return Map.fromEntries(tree.children.map((child) {
return MapEntry(child, _buildTreeRecursive(child));
}));
}

return widget.buildFromExtension(extensionContext, buildChildren);
return widget.buildFromExtension(extensionContext);
}
}
1 change: 0 additions & 1 deletion lib/src/processing/lists.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import 'dart:collection';

import 'package:collection/collection.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:flutter_html/src/style/marker.dart';
import 'package:list_counter/list_counter.dart';

class ListProcessing {
Expand Down
2 changes: 1 addition & 1 deletion lib/src/style.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:flutter_html/src/css_parser.dart';
import 'package:flutter_html/src/style/marker.dart';

//Export Style value-unit APIs
export 'package:flutter_html/src/style/margin.dart';
export 'package:flutter_html/src/style/length.dart';
export 'package:flutter_html/src/style/size.dart';
export 'package:flutter_html/src/style/fontsize.dart';
export 'package:flutter_html/src/style/lineheight.dart';
export 'package:flutter_html/src/style/marker.dart';

///This class represents all the available CSS attributes
///for this package.
Expand Down
2 changes: 1 addition & 1 deletion packages/flutter_html_audio/lib/flutter_html_audio.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class AudioHtmlExtension extends HtmlExtension {
Set<String> get supportedTags => {"audio"};

@override
InlineSpan build(ExtensionContext context, buildChildren) {
InlineSpan build(ExtensionContext context) {
return WidgetSpan(
child: AudioWidget(
context: context,
Expand Down
2 changes: 1 addition & 1 deletion packages/flutter_html_iframe/lib/flutter_html_iframe.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class IframeHtmlExtension extends HtmlExtension {
Set<String> get supportedTags => {"iframe"};

@override
InlineSpan build(ExtensionContext context, buildChildren) {
InlineSpan build(ExtensionContext context) {
return WidgetSpan(
child: IframeWidget(
extensionContext: context,
Expand Down
Loading