Skip to content

Commit 0a4df74

Browse files
committed
Merge branch 'master' of https://github.com/Sub6Resources/flutter_html into feature/list-style-type
� Conflicts: � lib/src/css_parser.dart
2 parents 1a1f6f7 + 5de96d7 commit 0a4df74

16 files changed

+650
-81
lines changed

.github/ISSUE_TEMPLATE/bug_report.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
---
2+
name: Bug report
3+
about: Something isn't working as intended
4+
title: "[BUG]"
5+
labels: bug
6+
assignees: ''
7+
8+
---
9+
10+
<!---
11+
12+
Please do not delete this issue template as it helps us organize and easily work on issues!!
13+
14+
NOTE: Before posting, please make sure you have
15+
1. Searched the README
16+
2. Searched the Issues tab for similar bugs
17+
3. Please provide the required information in the template - HTML code and Html widget configuration
18+
--->
19+
20+
**Describe the bug:**
21+
<!--- Please provide a clear and concise description of the bug --->
22+
23+
**HTML to reproduce the issue:**
24+
<!--- Please provide your HTML code below. If it contains sensitive information please post a minimal reproducible HTML snippet. --->
25+
26+
**`Html` widget configuration:**
27+
<!--- Please provide your HTML widget configuration below --->
28+
29+
**Expected behavior:**
30+
<!--- Expected behavior, if applicable, otherwise please delete --->
31+
32+
**Screenshots:**
33+
<!--- Screenshots can be helpful to analyze your issue. Please delete this section if you don't provide any. --->
34+
35+
**Device details and Flutter/Dart/`flutter_html` versions:**
36+
<!--- These details can be helpful to analyze your issue. Please delete this section if you don't provide any. --->
37+
38+
**Stacktrace/Logcat**
39+
<!--- The error stacktrace if applicable, otherwise please delete --->
40+
41+
**Additional info:**
42+
<!--- Any other info relevant to the bug, otherwise please delete --->
43+
44+
**A picture of a cute animal (not mandatory but encouraged)**
45+
<!--- A picture of a cute animal that would nicely complement this bug report.
46+
If you don't have one, please delete, just know we will be a little disappointed ;) --->
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
name: Feature request
3+
about: Suggest an idea for flutter_html
4+
title: "[FEATURE]"
5+
labels: enhancement
6+
assignees: ''
7+
8+
---
9+
10+
<!---
11+
12+
Please do not delete this issue template as it helps us organize and easily work on issues!!
13+
14+
NOTE: Before posting, please make sure you have
15+
1. Searched the README
16+
2. Searched the Issues tab for similar feature requests
17+
--->
18+
19+
**Describe your feature request**
20+
<!--- Please provide a clear and concise description of the feature request --->
21+
22+
**Additional context**
23+
<!--- Any other info relevant to the feature request, otherwise please delete --->
24+
25+
**A picture of a cute animal (not mandatory but encouraged)**
26+
<!--- A picture of a cute animal that would nicely complement this feature request.
27+
If you don't have one, please delete, just know we will be a little disappointed ;) --->

.github/ISSUE_TEMPLATE/question.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
name: Question
3+
about: Ask a question about flutter_html
4+
title: "[QUESTION]"
5+
labels: question
6+
assignees: ''
7+
8+
---
9+
10+
<!---
11+
NOTE: Before posting, please make sure you have
12+
1. Searched the README
13+
2. Searched the Issues tab for similar questions
14+
--->
15+
16+
Type question here.
17+
18+
**A picture of a cute animal (not mandatory but encouraged)**
19+
<!--- A picture of a cute animal that would nicely complement this question.
20+
If you don't have one, please delete, just know we will be a little disappointed ;) --->

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,20 @@
1+
## [2.0.0] - April 29, 2021:
2+
* Stable release with all 2.0.0-nullsafety.X changes
3+
4+
## [2.0.0-nullsafety.1] - April 29, 2021:
5+
* Support basic MathML
6+
* Support inner links
7+
* Supply full context tree to custom render
8+
* Include or exclude specific tags via `tagsList` parameter
9+
* Fixed lists not rendering correctly
10+
* Fixes for colspans in tables
11+
* Fixed various exceptions when using inline styles
12+
* Fixed text decoration not cascading between parent and child
13+
* [BREAKING] support whitelisting tags
14+
* See the README for details on how to migrate `blacklistedElements` (deprecated) to `tagsList`
15+
* Fixed `failed assertion` error when tap-scrolling on any link
16+
* Updated dependencies
17+
118
## [2.0.0-nullsafety.0] - March 5, 2021:
219
* Nullsafety support
320
* Official Flutter Web support

README.md

Lines changed: 47 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ A Flutter widget for rendering HTML and CSS as Flutter widgets.
9696
Add the following to your `pubspec.yaml` file:
9797

9898
dependencies:
99-
flutter_html: ^1.3.0
99+
flutter_html: ^2.0.0
100100

101101
## Currently Supported HTML Tags:
102102
| | | | | | | | | | | |
@@ -123,9 +123,9 @@ Add the following to your `pubspec.yaml` file:
123123
## Currently Supported Inline CSS Attributes:
124124
| | | | | | | |
125125
|------------------|--------|------------|----------|--------------|------------------------|------------|
126-
|`background-color`| `border` | `color`| `direction`| `display`| `font-family`| `font-feature-settings` |
127-
| `font-size`|`font-style` | `font-weight`| `line-height` | `list-style-type` | `list-style-position`|`padding` |
128-
| `margin`| `text-align`| `text-decoration`| `text-decoration-color`| `text-decoration-style`| `text-shadow` | |
126+
|`background-color`| `border` (including specific directions) | `color`| `direction`| `display`| `font-family`| `font-feature-settings` |
127+
| `font-size`|`font-style` | `font-weight`| `line-height` | `list-style-type` | `list-style-position`|`padding` (including specific directions) |
128+
| `margin` (including specific directions) | `text-align`| `text-decoration`| `text-decoration-color`| `text-decoration-style`| `text-shadow` | |
129129

130130
Don't see a tag or attribute you need? File a feature request or contribute to the project!
131131

@@ -242,17 +242,18 @@ Widget html = Html(
242242
);
243243
```
244244

245+
Inner links (such as `<a href="#top">Back to the top</a>` will work out of the box by scrolling the viewport, as long as your `Html` widget is wrapped in a scroll container such as a `SingleChildScrollView`.
246+
245247
### customRender:
246248

247-
A powerful API that allows you to customize everything when rendering a specific HTML tag. This means you can add support for HTML elements that aren't supported natively. You can also make up your own custom tags in your HTML!
249+
A powerful API that allows you to customize everything when rendering a specific HTML tag. This means you can change the default behaviour or add support for HTML elements that aren't supported natively. You can also make up your own custom tags in your HTML!
248250

249-
`customRender` accepts a `Map<String, CustomRender>`. The `CustomRender` type is a function that requires a `Widget` to be returned. It exposes `RenderContext`, the `Widget` that would have been rendered by `Html` without a `customRender` defined, the `attributes` of the HTML element as a `Map<String, String>`, and the HTML element itself as `Element`.
251+
`customRender` accepts a `Map<String, CustomRender>`. The `CustomRender` type is a function that requires a `Widget` or `InlineSpan` to be returned. It exposes `RenderContext` and the `Widget` that would have been rendered by `Html` without a `customRender` defined. The `RenderContext` contains the build context, styling and the HTML element, with attrributes and its subtree,.
250252

251-
To use this API, set the key as the tag of the HTML element you wish to provide a custom implementation for, and create a function with the above parameters that returns a `Widget`.
253+
To use this API, set the key as the tag of the HTML element you wish to provide a custom implementation for, and create a function with the above parameters that returns a `Widget` or `InlineSpan`.
252254

253255
#### Example Usages - customRender:
254256
1. Simple example - rendering custom HTML tags
255-
<details><summary>View code</summary>
256257

257258
```dart
258259
Widget html = Html(
@@ -262,24 +263,48 @@ Widget html = Html(
262263
<flutter horizontal></flutter>
263264
""",
264265
customRender: {
265-
"bird": (RenderContext context, Widget child, Map<String, String> attributes, dom.Element? element) {
266+
"bird": (RenderContext context, Widget child) {
266267
return TextSpan(text: "🐦");
267268
},
268-
"flutter": (RenderContext context, Widget child, Map<String, String> attributes, dom.Element? element) {
269+
"flutter": (RenderContext context, Widget child) {
269270
return FlutterLogo(
270-
style: (attributes['horizontal'] != null)
271+
style: (context.tree.element!.attributes['horizontal'] != null)
271272
? FlutterLogoStyle.horizontal
272273
: FlutterLogoStyle.markOnly,
273274
textColor: context.style.color,
274-
size: context.style.fontSize.size * 5,
275+
size: context.style.fontSize!.size! * 5,
275276
);
276277
},
277278
},
278279
);
279280
```
280-
</details>
281281

282-
2. Complex example - rendering an `iframe` differently based on whether it is an embedded youtube video or some other embedded content
282+
2. Complex example - wrapping the default widget with your own, in this case placing a horizontal scroll around a (potentially too wide) table.
283+
284+
<details><summary>View code</summary>
285+
286+
```dart
287+
Widget html = Html(
288+
data: """
289+
<table style="width:100%">
290+
<caption>Monthly savings</caption>
291+
<tr> <th>January</th> <th>February</th> <th>March</th> <th>April</th> <th>May</th> <th>June</th> <th>July</th> <th>August</th> <th>September</th> <th>October</th> <th>November</th> <th>December</th> </tr>
292+
<tr> <td>\$100</td> <td>\$50</td> <td>\$80</td> <td>\$60</td> <td>\$90</td> <td>\$140</td> <td>\$110</td> <td>\$80</td> <td>\$90</td> <td>\$60</td> <td>\$40</td> <td>\$70</td> </tr>
293+
<tr> <td>\90</td> <td>\$60</td> <td>\$80</td> <td>\$80</td> <td>\$100</td> <td>\$160</td> <td>\$150</td> <td>\$110</td> <td>\$100</td> <td>\$60</td> <td>\$30</td> <td>\$80</td> </tr>
294+
</table>
295+
""",
296+
customRender: {
297+
"table": (context, child) {
298+
return SingleChildScrollView(
299+
scrollDirection: Axis.horizontal,
300+
child: (context.tree as TableLayoutElement).toWidget(context),
301+
);
302+
}
303+
},
304+
);
305+
```
306+
307+
3. Complex example - rendering an `iframe` differently based on whether it is an embedded youtube video or some other embedded content.
283308

284309
<details><summary>View code</summary>
285310

@@ -292,25 +317,26 @@ Widget html = Html(
292317
<iframe src="https://www.youtube.com/embed/tgbNymZ7vqY"></iframe>
293318
""",
294319
customRender: {
295-
"iframe": (RenderContext context, Widget child, Map<String, String> attributes, dom.Element? element) {
296-
if (attributes != null) {
297-
double width = double.tryParse(attributes['width'] ?? "");
298-
double height = double.tryParse(attributes['height'] ?? "");
320+
"iframe": (RenderContext context, Widget child) {
321+
final attrs = context.tree.element?.attributes;
322+
if (attrs != null) {
323+
double? width = double.tryParse(attrs['width'] ?? "");
324+
double? height = double.tryParse(attrs['height'] ?? "");
299325
return Container(
300326
width: width ?? (height ?? 150) * 2,
301327
height: height ?? (width ?? 300) / 2,
302328
child: WebView(
303-
initialUrl: attributes['src'] ?? "about:blank",
329+
initialUrl: attrs['src'] ?? "about:blank",
304330
javascriptMode: JavascriptMode.unrestricted,
305331
//no need for scrolling gesture recognizers on embedded youtube, so set gestureRecognizers null
306332
//on other iframe content scrolling might be necessary, so use VerticalDragGestureRecognizer
307-
gestureRecognizers: attributes['src'] != null && attributes['src']!.contains("youtube.com/embed") ? null : [
333+
gestureRecognizers: attrs['src'] != null && attrs['src']!.contains("youtube.com/embed") ? null : [
308334
Factory(() => VerticalDragGestureRecognizer())
309335
].toSet(),
310336
navigationDelegate: (NavigationRequest request) async {
311337
//no need to load any url besides the embedded youtube url when displaying embedded youtube, so prevent url loading
312338
//on other iframe content allow all url loading
313-
if (attributes['src'] != null && attributes['src']!.contains("youtube.com/embed")) {
339+
if (attrs['src'] != null && attrs['src']!.contains("youtube.com/embed")) {
314340
if (!request.url.contains("youtube.com/embed")) {
315341
return NavigationDecision.prevent;
316342
} else {

example/lib/main.dart

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ const htmlData = r"""
4242
</ruby>
4343
&nbsp;is Japanese Kanji.
4444
</p>
45+
<h3>Support for maxLines:</h3>
46+
<h5>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vestibulum sapien feugiat lorem tempor, id porta orci elementum. Fusce sed justo id arcu egestas congue. Fusce tincidunt lacus ipsum, in imperdiet felis ultricies eu. In ullamcorper risus felis, ac maximus dui bibendum vel. Integer ligula tortor, facilisis eu mauris ut, ultrices hendrerit ex. Donec scelerisque massa consequat, eleifend mauris eu, mollis dui. Donec placerat augue tortor, et tincidunt quam tempus non. Quisque sagittis enim nisi, eu condimentum lacus egestas ac. Nam facilisis luctus ipsum, at aliquam urna fermentum a. Quisque tortor dui, faucibus in ante eget, pellentesque mattis nibh. In augue dolor, euismod vitae eleifend nec, tempus vel urna. Donec vitae augue accumsan ligula fringilla ultrices et vel ex.</h5>
4547
<h3>Support for <code>sub</code>/<code>sup</code></h3>
4648
Solve for <var>x<sub>n</sub></var>: log<sub>2</sub>(<var>x</var><sup>2</sup>+<var>n</var>) = 9<sup>3</sup>
4749
<p>One of the most <span>common</span> equations in all of physics is <br /><var>E</var>=<var>m</var><var>c</var><sup>2</sup>.</p>
@@ -263,30 +265,48 @@ class _MyHomePageState extends State<MyHomePage> {
263265
padding: EdgeInsets.all(6),
264266
alignment: Alignment.topLeft,
265267
),
268+
'h5': Style(maxLines: 2, textOverflow: TextOverflow.ellipsis),
266269
},
267270
customRender: {
268271
"table": (context, child) {
269272
return SingleChildScrollView(
270273
scrollDirection: Axis.horizontal,
271-
child: (context.tree as TableLayoutElement).toWidget(context),
274+
child:
275+
(context.tree as TableLayoutElement).toWidget(context),
272276
);
273-
}
277+
},
278+
"bird": (RenderContext context, Widget child) {
279+
return TextSpan(text: "🐦");
280+
},
281+
"flutter": (RenderContext context, Widget child) {
282+
return FlutterLogo(
283+
style: (context.tree.element!.attributes['horizontal'] != null)
284+
? FlutterLogoStyle.horizontal
285+
: FlutterLogoStyle.markOnly,
286+
textColor: context.style.color!,
287+
size: context.style.fontSize!.size! * 5,
288+
);
289+
},
274290
},
275291
customImageRenders: {
276-
networkSourceMatcher(domains: ["flutter.dev"]): (context, attributes, element) {
292+
networkSourceMatcher(domains: ["flutter.dev"]):
293+
(context, attributes, element) {
277294
return FlutterLogo(size: 36);
278295
},
279-
networkSourceMatcher(domains: ["mydomain.com"]): networkImageRender(
296+
networkSourceMatcher(domains: ["mydomain.com"]):
297+
networkImageRender(
280298
headers: {"Custom-Header": "some-value"},
281299
altWidget: (alt) => Text(alt ?? ""),
282300
loadingWidget: () => Text("Loading..."),
283301
),
284302
// On relative paths starting with /wiki, prefix with a base url
285-
(attr, _) => attr["src"] != null && attr["src"]!.startsWith("/wiki"):
303+
(attr, _) =>
304+
attr["src"] != null && attr["src"]!.startsWith("/wiki"):
286305
networkImageRender(
287306
mapUrl: (url) => "https://upload.wikimedia.org" + url!),
288307
// Custom placeholder image for broken links
289-
networkSourceMatcher(): networkImageRender(altWidget: (_) => FlutterLogo()),
308+
networkSourceMatcher():
309+
networkImageRender(altWidget: (_) => FlutterLogo()),
290310
},
291311
onLinkTap: (url, _, __, ___) {
292312
print("Opening $url...");
@@ -297,6 +317,13 @@ class _MyHomePageState extends State<MyHomePage> {
297317
onImageError: (exception, stackTrace) {
298318
print(exception);
299319
},
320+
onCssParseError: (css, messages) {
321+
print("css that errored: $css");
322+
print("error messages:");
323+
messages.forEach((element) {
324+
print(element);
325+
});
326+
},
300327
),
301328
),
302329
);

lib/flutter_html.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ class Html extends StatelessWidget {
5353
this.onLinkTap,
5454
this.customRender = const {},
5555
this.customImageRenders = const {},
56+
this.onCssParseError,
5657
this.onImageError,
5758
this.onMathError,
5859
this.shrinkWrap = false,
@@ -71,6 +72,7 @@ class Html extends StatelessWidget {
7172
this.onLinkTap,
7273
this.customRender = const {},
7374
this.customImageRenders = const {},
75+
this.onCssParseError,
7476
this.onImageError,
7577
this.onMathError,
7678
this.shrinkWrap = false,
@@ -99,6 +101,9 @@ class Html extends StatelessWidget {
99101
/// See the README for more details.
100102
final Map<ImageSourceMatcher, ImageRender> customImageRenders;
101103

104+
/// A function that defines what to do when CSS fails to parse
105+
final OnCssParseError? onCssParseError;
106+
102107
/// A function that defines what to do when an image errors
103108
final ImageErrorListener? onImageError;
104109

@@ -148,6 +153,7 @@ class Html extends StatelessWidget {
148153
htmlData: doc,
149154
onLinkTap: onLinkTap,
150155
onImageTap: onImageTap,
156+
onCssParseError: onCssParseError,
151157
onImageError: onImageError,
152158
onMathError: onMathError,
153159
shrinkWrap: shrinkWrap,

0 commit comments

Comments
 (0)