Skip to content

Commit

Permalink
fix: improve API for ExtensionContext and export marker.dart (#1273)
Browse files Browse the repository at this point in the history
  • Loading branch information
Sub6Resources authored May 18, 2023
1 parent 15cb05e commit 27e33a9
Show file tree
Hide file tree
Showing 24 changed files with 96 additions and 84 deletions.
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

0 comments on commit 27e33a9

Please sign in to comment.