Sonnat Project
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

326 lines
11 KiB

library flutter_html;
import 'package:flutter/material.dart';
import 'package:html/dom.dart' as dom;
import 'package:sonnat/core/html/custom_render.dart';
import 'package:sonnat/core/html/html_parser.dart';
import 'package:sonnat/core/html/src/html_elements.dart';
import 'package:sonnat/core/html/style.dart';
class Html extends StatefulWidget {
Html({
super.key,
GlobalKey? anchorKey,
required this.data,
this.onLinkTap,
this.onAnchorTap,
this.customRenders = const {},
this.onCssParseError,
this.onImageError,
this.shrinkWrap = false,
this.onImageTap,
this.tagsList = const [],
this.style = const {},
}) : documentElement = null,
assert(data != null),
_anchorKey = anchorKey ?? GlobalKey();
Html.fromDom({
super.key,
GlobalKey? anchorKey,
@required dom.Document? document,
this.onLinkTap,
this.onAnchorTap,
this.customRenders = const {},
this.onCssParseError,
this.onImageError,
this.shrinkWrap = false,
this.onImageTap,
this.tagsList = const [],
this.style = const {},
}) : data = null,
assert(document != null),
documentElement = document!.documentElement,
_anchorKey = anchorKey ?? GlobalKey();
Html.fromElement({
super.key,
GlobalKey? anchorKey,
@required this.documentElement,
this.onLinkTap,
this.onAnchorTap,
this.customRenders = const {},
this.onCssParseError,
this.onImageError,
this.shrinkWrap = false,
this.onImageTap,
this.tagsList = const [],
this.style = const {},
}) : data = null,
assert(documentElement != null),
_anchorKey = anchorKey ?? GlobalKey();
/// A unique key for this Html widget to ensure uniqueness of anchors
final GlobalKey _anchorKey;
/// The HTML data passed to the widget as a String
final String? data;
/// The HTML data passed to the widget as a pre-processed [dom.Element]
final dom.Element? documentElement;
/// A function that defines what to do when a link is tapped
final OnTap? onLinkTap;
/// A function that defines what to do when an anchor link is tapped. When this value is set,
/// the default anchor behaviour is overwritten.
final OnTap? onAnchorTap;
/// A function that defines what to do when CSS fails to parse
final OnCssParseError? onCssParseError;
/// A function that defines what to do when an image errors
final ImageErrorListener? onImageError;
/// A parameter that should be set when the HTML widget is expected to be
/// flexible
final bool shrinkWrap;
/// A function that defines what to do when an image is tapped
final OnTap? onImageTap;
/// A list of HTML tags that are the only tags that are rendered. By default, this list is empty and all supported HTML tags are rendered.
final List<String> tagsList;
/// Either return a custom widget for specific node types or return null to
/// fallback to the default rendering.
final Map<CustomRenderMatcher, CustomRender> customRenders;
/// An API that allows you to override the default style for any HTML element
final Map<String, Style> style;
static List<String> get tags => List<String>.from(HtmlElements.styledElements)
..addAll(HtmlElements.interactableElements)
..addAll(HtmlElements.replacedElements)
..addAll(HtmlElements.layoutElements)
..addAll(HtmlElements.tableCellElements)
..addAll(HtmlElements.tableDefinitionElements)
..addAll(HtmlElements.externalElements);
@override
State<StatefulWidget> createState() => _HtmlState();
}
class _HtmlState extends State<Html> {
late dom.Element documentElement;
@override
void initState() {
super.initState();
documentElement = widget.data != null
? HtmlParser.parseHTML(widget.data!)
: widget.documentElement!;
}
@override
void didUpdateWidget(Html oldWidget) {
super.didUpdateWidget(oldWidget);
if ((widget.data != null && oldWidget.data != widget.data) ||
oldWidget.documentElement != widget.documentElement) {
documentElement = widget.data != null
? HtmlParser.parseHTML(widget.data!)
: widget.documentElement!;
}
}
@override
Widget build(BuildContext context) {
return HtmlParser(
key: widget._anchorKey,
htmlData: documentElement,
onLinkTap: widget.onLinkTap,
onAnchorTap: widget.onAnchorTap,
onImageTap: widget.onImageTap,
onCssParseError: widget.onCssParseError,
onImageError: widget.onImageError,
shrinkWrap: widget.shrinkWrap,
selectable: false,
style: widget.style,
customRenders: {}
..addAll(widget.customRenders)
..addAll(generateDefaultRenders()),
tagsList: widget.tagsList.isEmpty ? Html.tags : widget.tagsList,
);
}
}
class SelectableHtml extends StatefulWidget {
/// The `SelectableHtml` widget takes HTML as input and displays a RichText
/// tree of the parsed HTML content (which is selectable)
///
/// **Attributes**
/// **data** *required* takes in a String of HTML data (required only for `Html` constructor).
/// **documentElement** *required* takes in a Element of HTML data (required only for `Html.fromDom` and `Html.fromElement` constructor).
///
/// **onLinkTap** This function is called whenever a link (`<a href>`)
/// is tapped.
///
/// **onAnchorTap** This function is called whenever an anchor (#anchor-id)
/// is tapped.
///
/// **tagsList** Tag names in this array will be the only tags rendered. By default, all tags that support selectable content are rendered.
///
/// **style** Pass in the style information for the Html here.
/// See [its wiki page](https://github.com/Sub6Resources/flutter_html/wiki/Style) for more info.
///
/// **PLEASE NOTE**
///
/// There are a few caveats due to Flutter [#38474](https://github.com/flutter/flutter/issues/38474):
///
/// 1. The list of tags that can be rendered is significantly reduced.
/// Key omissions include no support for images/video/audio, table, and ul/ol because they all require widgets and `WidgetSpan`s.
///
/// 2. No support for `customRender`, `customImageRender`, `onImageError`, `onImageTap`, `onMathError`, and `navigationDelegateForIframe`.
///
/// 3. Styling support is significantly reduced. Only text-related styling works
/// (e.g. bold or italic), while container related styling (e.g. borders or padding/margin)
/// do not work because we can't use the `ContainerSpan` class (it needs an enclosing `WidgetSpan`).
SelectableHtml({
super.key,
GlobalKey? anchorKey,
required this.data,
this.onLinkTap,
this.onAnchorTap,
this.onCssParseError,
this.shrinkWrap = false,
this.style = const {},
this.customRenders = const {},
this.tagsList = const [],
this.selectionControls,
this.scrollPhysics,
}) : documentElement = null,
assert(data != null),
_anchorKey = anchorKey ?? GlobalKey();
SelectableHtml.fromDom({
super.key,
GlobalKey? anchorKey,
@required dom.Document? document,
this.onLinkTap,
this.onAnchorTap,
this.onCssParseError,
this.shrinkWrap = false,
this.style = const {},
this.customRenders = const {},
this.tagsList = const [],
this.selectionControls,
this.scrollPhysics,
}) : data = null,
assert(document != null),
documentElement = document!.documentElement,
_anchorKey = anchorKey ?? GlobalKey();
SelectableHtml.fromElement({
super.key,
GlobalKey? anchorKey,
@required this.documentElement,
this.onLinkTap,
this.onAnchorTap,
this.onCssParseError,
this.shrinkWrap = false,
this.style = const {},
this.customRenders = const {},
this.tagsList = const [],
this.selectionControls,
this.scrollPhysics,
}) : data = null,
assert(documentElement != null),
_anchorKey = anchorKey ?? GlobalKey();
/// A unique key for this Html widget to ensure uniqueness of anchors
final GlobalKey _anchorKey;
/// The HTML data passed to the widget as a String
final String? data;
/// The HTML data passed to the widget as a pre-processed [dom.Element]
final dom.Element? documentElement;
/// A function that defines what to do when a link is tapped
final OnTap? onLinkTap;
/// A function that defines what to do when an anchor link is tapped. When this value is set,
/// the default anchor behaviour is overwritten.
final OnTap? onAnchorTap;
/// A function that defines what to do when CSS fails to parse
final OnCssParseError? onCssParseError;
/// A parameter that should be set when the HTML widget is expected to be
/// have a flexible width, that doesn't always fill its maximum width
/// constraints. For example, auto horizontal margins are ignored, and
/// block-level elements only take up the width they need.
final bool shrinkWrap;
/// A list of HTML tags that are the only tags that are rendered. By default, this list is empty and all supported HTML tags are rendered.
final List<String> tagsList;
/// An API that allows you to override the default style for any HTML element
final Map<String, Style> style;
/// Custom Selection controls allows you to override default toolbar and build custom toolbar
/// options
final TextSelectionControls? selectionControls;
/// Allows you to override the default scrollPhysics for [SelectableText.rich]
final ScrollPhysics? scrollPhysics;
/// Either return a custom widget for specific node types or return null to
/// fallback to the default rendering.
final Map<CustomRenderMatcher, SelectableCustomRender> customRenders;
static List<String> get tags =>
List<String>.from(HtmlElements.selectableElements);
@override
State<StatefulWidget> createState() => _SelectableHtmlState();
}
class _SelectableHtmlState extends State<SelectableHtml> {
late final dom.Element documentElement;
@override
void initState() {
super.initState();
documentElement = widget.data != null
? HtmlParser.parseHTML(widget.data!)
: widget.documentElement!;
}
@override
Widget build(BuildContext context) {
return SizedBox(
width: widget.shrinkWrap ? null : MediaQuery.of(context).size.width,
child: HtmlParser(
key: widget._anchorKey,
htmlData: documentElement,
onLinkTap: widget.onLinkTap,
onAnchorTap: widget.onAnchorTap,
onImageTap: null,
onCssParseError: widget.onCssParseError,
onImageError: null,
shrinkWrap: widget.shrinkWrap,
selectable: true,
style: widget.style,
customRenders: {}
..addAll(widget.customRenders)
..addAll(generateDefaultRenders()),
tagsList:
widget.tagsList.isEmpty ? SelectableHtml.tags : widget.tagsList,
selectionControls: widget.selectionControls,
scrollPhysics: widget.scrollPhysics,
),
);
}
}