diff --git a/lib/pages/chat/events/html_message.dart b/lib/pages/chat/events/html_message.dart index 3a47121f..d8c7b9be 100644 --- a/lib/pages/chat/events/html_message.dart +++ b/lib/pages/chat/events/html_message.dart @@ -6,6 +6,7 @@ import 'package:flutter_highlighter/themes/shades-of-purple.dart'; import 'package:flutter_html/flutter_html.dart'; import 'package:flutter_html_table/flutter_html_table.dart'; import 'package:flutter_math_fork/flutter_math.dart'; +import 'package:html/dom.dart' as dom; import 'package:linkify/linkify.dart'; import 'package:matrix/matrix.dart'; @@ -26,6 +27,35 @@ class HtmlMessage extends StatelessWidget { this.textColor = Colors.black, }); + dom.Node _linkifyHtml(dom.Node element) { + for (final node in element.nodes) { + if (node is! dom.Text) { + node.replaceWith(_linkifyHtml(node)); + continue; + } + + final parts = linkify( + node.text, + options: const LinkifyOptions(humanize: false), + ); + + if (!parts.any((part) => part is UrlElement)) { + continue; + } + + final newHtml = parts + .map( + (linkifyElement) => linkifyElement is! UrlElement + ? linkifyElement.text + : '${linkifyElement.text}', + ) + .join(' '); + + node.replaceWith(dom.Element.html(newHtml)); + } + return element; + } + @override Widget build(BuildContext context) { // riot-web is notorious for creating bad reply fallback events from invalid messages which, if @@ -46,21 +76,6 @@ class HtmlMessage extends StatelessWidget { final fontSize = AppConfig.messageFontSize * AppConfig.fontSizeFactor; - final linkifiedRenderHtml = linkify( - renderHtml, - options: const LinkifyOptions(humanize: false), - ).map( - (element) { - if (element is! UrlElement || - element.text.contains('<') || - element.text.contains('>') || - element.text.contains('"')) { - return element.text; - } - return '${element.text}'; - }, - ).join(''); - final linkColor = textColor.withAlpha(150); final blockquoteStyle = Style( @@ -73,87 +88,86 @@ class HtmlMessage extends StatelessWidget { padding: HtmlPaddings.only(left: 6, bottom: 0), ); + final element = _linkifyHtml(HtmlParser.parseHTML(renderHtml)); + // there is no need to pre-validate the html, as we validate it while rendering - return MouseRegion( - cursor: SystemMouseCursors.text, - child: Html( - data: linkifiedRenderHtml, - style: { - '*': Style( - color: textColor, - margin: Margins.all(0), - fontSize: FontSize(fontSize), - ), - 'a': Style(color: linkColor, textDecorationColor: linkColor), - 'h1': Style( - fontSize: FontSize(fontSize * 2), - lineHeight: LineHeight.number(1.5), - fontWeight: FontWeight.w600, - ), - 'h2': Style( - fontSize: FontSize(fontSize * 1.75), - lineHeight: LineHeight.number(1.5), - fontWeight: FontWeight.w500, - ), - 'h3': Style( - fontSize: FontSize(fontSize * 1.5), - lineHeight: LineHeight.number(1.5), - ), - 'h4': Style( - fontSize: FontSize(fontSize * 1.25), - lineHeight: LineHeight.number(1.5), - ), - 'h5': Style( - fontSize: FontSize(fontSize * 1.25), - lineHeight: LineHeight.number(1.5), - ), - 'h6': Style( - fontSize: FontSize(fontSize), - lineHeight: LineHeight.number(1.5), - ), - 'blockquote': blockquoteStyle, - 'tg-forward': blockquoteStyle, - 'hr': Style( - border: Border.all(color: textColor, width: 0.5), - ), - 'table': Style( - border: Border.all(color: textColor, width: 0.5), - ), - 'tr': Style( - border: Border.all(color: textColor, width: 0.5), - ), - 'td': Style( - border: Border.all(color: textColor, width: 0.5), - padding: HtmlPaddings.all(2), - ), - 'th': Style( - border: Border.all(color: textColor, width: 0.5), - ), - }, - extensions: [ - RoomPillExtension(context, room), - CodeExtension(fontSize: fontSize), - MatrixMathExtension( - style: TextStyle(fontSize: fontSize, color: textColor), - ), - const TableHtmlExtension(), - SpoilerExtension(textColor: textColor), - const ImageExtension(), - FontColorExtension(), - ], - onLinkTap: (url, _, element) => UrlLauncher( - context, - url, - element?.text, - ).launchUrl(), - onlyRenderTheseTags: const { - ...allowedHtmlTags, - // Needed to make it work properly - 'body', - 'html', - }, - shrinkWrap: true, - ), + return Html.fromElement( + documentElement: element as dom.Element, + style: { + '*': Style( + color: textColor, + margin: Margins.all(0), + fontSize: FontSize(fontSize), + ), + 'a': Style(color: linkColor, textDecorationColor: linkColor), + 'h1': Style( + fontSize: FontSize(fontSize * 2), + lineHeight: LineHeight.number(1.5), + fontWeight: FontWeight.w600, + ), + 'h2': Style( + fontSize: FontSize(fontSize * 1.75), + lineHeight: LineHeight.number(1.5), + fontWeight: FontWeight.w500, + ), + 'h3': Style( + fontSize: FontSize(fontSize * 1.5), + lineHeight: LineHeight.number(1.5), + ), + 'h4': Style( + fontSize: FontSize(fontSize * 1.25), + lineHeight: LineHeight.number(1.5), + ), + 'h5': Style( + fontSize: FontSize(fontSize * 1.25), + lineHeight: LineHeight.number(1.5), + ), + 'h6': Style( + fontSize: FontSize(fontSize), + lineHeight: LineHeight.number(1.5), + ), + 'blockquote': blockquoteStyle, + 'tg-forward': blockquoteStyle, + 'hr': Style( + border: Border.all(color: textColor, width: 0.5), + ), + 'table': Style( + border: Border.all(color: textColor, width: 0.5), + ), + 'tr': Style( + border: Border.all(color: textColor, width: 0.5), + ), + 'td': Style( + border: Border.all(color: textColor, width: 0.5), + padding: HtmlPaddings.all(2), + ), + 'th': Style( + border: Border.all(color: textColor, width: 0.5), + ), + }, + extensions: [ + RoomPillExtension(context, room), + CodeExtension(fontSize: fontSize), + MatrixMathExtension( + style: TextStyle(fontSize: fontSize, color: textColor), + ), + const TableHtmlExtension(), + SpoilerExtension(textColor: textColor), + const ImageExtension(), + FontColorExtension(), + ], + onLinkTap: (url, _, element) => UrlLauncher( + context, + url, + element?.text, + ).launchUrl(), + onlyRenderTheseTags: const { + ...allowedHtmlTags, + // Needed to make it work properly + 'body', + 'html', + }, + shrinkWrap: true, ); } diff --git a/pubspec.lock b/pubspec.lock index ab9190f6..14f70ea6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -872,7 +872,7 @@ packages: source: hosted version: "1.1.0" html: - dependency: transitive + dependency: "direct main" description: name: html sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" diff --git a/pubspec.yaml b/pubspec.yaml index 15001d64..c303f580 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -54,6 +54,7 @@ dependencies: go_router: ^12.1.1 hive: ^2.2.3 hive_flutter: ^1.1.0 + html: ^0.15.4 http: ^0.13.6 image_picker: ^1.0.0 intl: any