fix: Some links not clickable in messages

This commit is contained in:
Krille 2023-12-27 15:26:22 +01:00
parent 6a75d07dea
commit 6b53d27c4c
No known key found for this signature in database
GPG key ID: E067ECD60F1A0652
3 changed files with 111 additions and 96 deletions

View file

@ -6,6 +6,7 @@ import 'package:flutter_highlighter/themes/shades-of-purple.dart';
import 'package:flutter_html/flutter_html.dart'; import 'package:flutter_html/flutter_html.dart';
import 'package:flutter_html_table/flutter_html_table.dart'; import 'package:flutter_html_table/flutter_html_table.dart';
import 'package:flutter_math_fork/flutter_math.dart'; import 'package:flutter_math_fork/flutter_math.dart';
import 'package:html/dom.dart' as dom;
import 'package:linkify/linkify.dart'; import 'package:linkify/linkify.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
@ -26,6 +27,35 @@ class HtmlMessage extends StatelessWidget {
this.textColor = Colors.black, 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
: '<a href="${linkifyElement.text}">${linkifyElement.text}</a>',
)
.join(' ');
node.replaceWith(dom.Element.html(newHtml));
}
return element;
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// riot-web is notorious for creating bad reply fallback events from invalid messages which, if // 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 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 '<a href="${element.url}">${element.text}</a>';
},
).join('');
final linkColor = textColor.withAlpha(150); final linkColor = textColor.withAlpha(150);
final blockquoteStyle = Style( final blockquoteStyle = Style(
@ -73,87 +88,86 @@ class HtmlMessage extends StatelessWidget {
padding: HtmlPaddings.only(left: 6, bottom: 0), 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 // there is no need to pre-validate the html, as we validate it while rendering
return MouseRegion( return Html.fromElement(
cursor: SystemMouseCursors.text, documentElement: element as dom.Element,
child: Html( style: {
data: linkifiedRenderHtml, '*': Style(
style: { color: textColor,
'*': Style( margin: Margins.all(0),
color: textColor, fontSize: FontSize(fontSize),
margin: Margins.all(0), ),
fontSize: FontSize(fontSize), 'a': Style(color: linkColor, textDecorationColor: linkColor),
), 'h1': Style(
'a': Style(color: linkColor, textDecorationColor: linkColor), fontSize: FontSize(fontSize * 2),
'h1': Style( lineHeight: LineHeight.number(1.5),
fontSize: FontSize(fontSize * 2), fontWeight: FontWeight.w600,
lineHeight: LineHeight.number(1.5), ),
fontWeight: FontWeight.w600, 'h2': Style(
), fontSize: FontSize(fontSize * 1.75),
'h2': Style( lineHeight: LineHeight.number(1.5),
fontSize: FontSize(fontSize * 1.75), fontWeight: FontWeight.w500,
lineHeight: LineHeight.number(1.5), ),
fontWeight: FontWeight.w500, 'h3': Style(
), fontSize: FontSize(fontSize * 1.5),
'h3': Style( lineHeight: LineHeight.number(1.5),
fontSize: FontSize(fontSize * 1.5), ),
lineHeight: LineHeight.number(1.5), 'h4': Style(
), fontSize: FontSize(fontSize * 1.25),
'h4': Style( lineHeight: LineHeight.number(1.5),
fontSize: FontSize(fontSize * 1.25), ),
lineHeight: LineHeight.number(1.5), 'h5': Style(
), fontSize: FontSize(fontSize * 1.25),
'h5': Style( lineHeight: LineHeight.number(1.5),
fontSize: FontSize(fontSize * 1.25), ),
lineHeight: LineHeight.number(1.5), 'h6': Style(
), fontSize: FontSize(fontSize),
'h6': Style( lineHeight: LineHeight.number(1.5),
fontSize: FontSize(fontSize), ),
lineHeight: LineHeight.number(1.5), 'blockquote': blockquoteStyle,
), 'tg-forward': blockquoteStyle,
'blockquote': blockquoteStyle, 'hr': Style(
'tg-forward': blockquoteStyle, border: Border.all(color: textColor, width: 0.5),
'hr': Style( ),
border: Border.all(color: textColor, width: 0.5), 'table': 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),
'tr': Style( ),
border: Border.all(color: textColor, width: 0.5), 'td': Style(
), border: Border.all(color: textColor, width: 0.5),
'td': Style( padding: HtmlPaddings.all(2),
border: Border.all(color: textColor, width: 0.5), ),
padding: HtmlPaddings.all(2), 'th': Style(
), border: Border.all(color: textColor, width: 0.5),
'th': Style( ),
border: Border.all(color: textColor, width: 0.5), },
), extensions: [
}, RoomPillExtension(context, room),
extensions: [ CodeExtension(fontSize: fontSize),
RoomPillExtension(context, room), MatrixMathExtension(
CodeExtension(fontSize: fontSize), style: TextStyle(fontSize: fontSize, color: textColor),
MatrixMathExtension( ),
style: TextStyle(fontSize: fontSize, color: textColor), const TableHtmlExtension(),
), SpoilerExtension(textColor: textColor),
const TableHtmlExtension(), const ImageExtension(),
SpoilerExtension(textColor: textColor), FontColorExtension(),
const ImageExtension(), ],
FontColorExtension(), onLinkTap: (url, _, element) => UrlLauncher(
], context,
onLinkTap: (url, _, element) => UrlLauncher( url,
context, element?.text,
url, ).launchUrl(),
element?.text, onlyRenderTheseTags: const {
).launchUrl(), ...allowedHtmlTags,
onlyRenderTheseTags: const { // Needed to make it work properly
...allowedHtmlTags, 'body',
// Needed to make it work properly 'html',
'body', },
'html', shrinkWrap: true,
},
shrinkWrap: true,
),
); );
} }

View file

@ -872,7 +872,7 @@ packages:
source: hosted source: hosted
version: "1.1.0" version: "1.1.0"
html: html:
dependency: transitive dependency: "direct main"
description: description:
name: html name: html
sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a"

View file

@ -54,6 +54,7 @@ dependencies:
go_router: ^12.1.1 go_router: ^12.1.1
hive: ^2.2.3 hive: ^2.2.3
hive_flutter: ^1.1.0 hive_flutter: ^1.1.0
html: ^0.15.4
http: ^0.13.6 http: ^0.13.6
image_picker: ^1.0.0 image_picker: ^1.0.0
intl: any intl: any