* chore: Nicer invite selection view
* chore: Do not request thousands of users on invite page
* build(deps): bump rexml from 3.3.6 to 3.3.9 in /ios
Bumps [rexml](https://github.com/ruby/rexml) from 3.3.6 to 3.3.9.
- [Release notes](https://github.com/ruby/rexml/releases)
- [Changelog](https://github.com/ruby/rexml/blob/master/NEWS.md)
- [Commits](https://github.com/ruby/rexml/compare/v3.3.6...v3.3.9)
---
updated-dependencies:
- dependency-name: rexml
dependency-type: indirect
...
Signed-off-by: dependabot[bot] <support@github.com>
* design: Highlight emoji only messages
* chore: Follow up emoji only messages
* Translated using Weblate (Galician)
Currently translated at 100.0% (672 of 672 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/gl/
* Translated using Weblate (Russian)
Currently translated at 99.7% (670 of 672 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ru/
* design: New login design
* chore: Improve spaces design
* chore: Improve spaces design
* chore: Improved UX for creating groups and spaces
* Translated using Weblate (German)
Currently translated at 100.0% (672 of 672 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/de/
* feat: Better wallpapers with blur and opacity sliders and improved styles page
* chore: Follow up wallpaper configs
* chore: Add max length to state messages
* chore: Follow up wallpaper design
* feat: Open account manage url when using MAS
* chore: follow up wellknown fetch
* Translated using Weblate (Arabic)
Currently translated at 100.0% (674 of 674 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ar/
* Translated using Weblate (Estonian)
Currently translated at 100.0% (674 of 674 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/et/
* Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (674 of 674 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/zh_Hans/
* Translated using Weblate (Indonesian)
Currently translated at 100.0% (674 of 674 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/id/
* Translated using Weblate (Finnish)
Currently translated at 79.0% (533 of 674 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/fi/
* Translated using Weblate (Latvian)
Currently translated at 100.0% (674 of 674 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/lv/
* build: Add links to snapcraft.yaml file
* chore: Nicer empty page
* chore: Polish chat bubble colors
* chore: Follow up chat bubble design
* refactor: Remove unnecessary builder widget
* chore: Design adjustments
* chore: Follow up design
* refactor: Display two lines on new messages
* chore: Design follow up
* Translated using Weblate (Arabic)
Currently translated at 100.0% (678 of 678 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ar/
* Translated using Weblate (German)
Currently translated at 100.0% (678 of 678 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/de/
* Translated using Weblate (Estonian)
Currently translated at 99.7% (676 of 678 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/et/
* Translated using Weblate (Basque)
Currently translated at 100.0% (678 of 678 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/eu/
* Translated using Weblate (Ukrainian)
Currently translated at 100.0% (678 of 678 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/uk/
* Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (678 of 678 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/zh_Hans/
* chore: Follow up message bubbles
* chore: Follow up design
* chore: Follow up design
* chore: Follow up colors
* chore: Follow up homeserverpicker UX
* chore: Design follow up
* feat: Add about server page
* chore: Follow up update snackbar
* chore: Polish login design
* chore: Follow up login page
* chore: Follow up homeserver picker
* chore: Follow up appbar shadow
* refactor: Performance boost for avatar widget
* Revert "refactor: Performance boost for avatar widget"
This reverts commit 58577bb9e8.
* Translated using Weblate (Estonian)
Currently translated at 100.0% (678 of 678 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/et/
* Translated using Weblate (Ukrainian)
Currently translated at 100.0% (678 of 678 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/uk/
* Translated using Weblate (Latvian)
Currently translated at 100.0% (678 of 678 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/lv/
* Translated using Weblate (Arabic)
Currently translated at 100.0% (687 of 687 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ar/
* Translated using Weblate (Estonian)
Currently translated at 100.0% (687 of 687 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/et/
* Translated using Weblate (Basque)
Currently translated at 100.0% (687 of 687 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/eu/
* Translated using Weblate (Galician)
Currently translated at 100.0% (687 of 687 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/gl/
* Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (687 of 687 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/zh_Hans/
* Translated using Weblate (Indonesian)
Currently translated at 100.0% (687 of 687 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/id/
* Translated using Weblate (Latvian)
Currently translated at 100.0% (687 of 687 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/lv/
* Translated using Weblate (Ukrainian)
Currently translated at 100.0% (687 of 687 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/uk/
* Translated using Weblate (Korean)
Currently translated at 100.0% (687 of 687 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ko/
* chore: Follow up homeserver input field
* refactor: Move to upstream geolocator
* chore: Follow up send file dialog
* Translated using Weblate (Spanish)
Currently translated at 74.6% (513 of 687 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/es/
* refactor: Migrate to newer keyboard shortcuts package
* refactor: Remove keyboard shortcuts
This package right now
makes the web app
nearly unusable as it
throws multiple errors on
every key press. The
package seems to be
unmaintained. I tried
two other packages
and none of them worked.
* build: Update matrix dart sdk to 0.35.0
* chore: Better FluffyChat Logo for PWA
* build: (deps): bump samuelmeuli/action-snapcraft from 2 to 3
Bumps [samuelmeuli/action-snapcraft](https://github.com/samuelmeuli/action-snapcraft) from 2 to 3.
- [Release notes](https://github.com/samuelmeuli/action-snapcraft/releases)
- [Commits](https://github.com/samuelmeuli/action-snapcraft/compare/v2...v3)
---
updated-dependencies:
- dependency-name: samuelmeuli/action-snapcraft
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot] <support@github.com>
* chore: Follow up send file dialog
* feat: Add markdown context actions for text input
* build: Update flutter to 3.24.5
* build: Remove snapcraft build workaround
* chore: Better error message when join room failed
* chore: Follow up join room
* chore: Make error dialog show full error
* chore: Follow up loading dialog
* chore: Follow up loading dialog
* build: Snapcraft from local build file
* chore: Follow up build snap
* chore: Follow up snapcraft in ci
* build: Revert build snapcraft changes
* build: Try downgrading flutter web auth
* chore: add hint in pubspec.yaml regarding flutter_web_auth_2
* Translated using Weblate (Estonian)
Currently translated at 100.0% (688 of 688 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/et/
* Translated using Weblate (Galician)
Currently translated at 100.0% (688 of 688 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/gl/
* Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (688 of 688 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/zh_Hans/
* Translated using Weblate (Indonesian)
Currently translated at 100.0% (688 of 688 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/id/
* Translated using Weblate (Irish)
Currently translated at 100.0% (688 of 688 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ga/
* Translated using Weblate (Arabic)
Currently translated at 100.0% (688 of 688 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ar/
* Translated using Weblate (Basque)
Currently translated at 100.0% (688 of 688 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/eu/
* Translated using Weblate (Ukrainian)
Currently translated at 100.0% (688 of 688 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/uk/
* Translated using Weblate (Latvian)
Currently translated at 100.0% (688 of 688 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/lv/
* Translated using Weblate (Italian)
Currently translated at 100.0% (688 of 688 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/it/
* Translated using Weblate (Estonian)
Currently translated at 100.0% (694 of 694 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/et/
* Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (694 of 694 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/zh_Hans/
* Translated using Weblate (Arabic)
Currently translated at 100.0% (694 of 694 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ar/
* Translated using Weblate (Basque)
Currently translated at 100.0% (694 of 694 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/eu/
* Translated using Weblate (Irish)
Currently translated at 100.0% (694 of 694 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ga/
* Translated using Weblate (Indonesian)
Currently translated at 100.0% (694 of 694 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/id/
* Translated using Weblate (Latvian)
Currently translated at 100.0% (694 of 694 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/lv/
* Translated using Weblate (Arabic)
Currently translated at 100.0% (695 of 695 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ar/
* Translated using Weblate (Estonian)
Currently translated at 100.0% (695 of 695 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/et/
* Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (695 of 695 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/zh_Hans/
* Translated using Weblate (Irish)
Currently translated at 99.8% (694 of 695 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ga/
* Translated using Weblate (German)
Currently translated at 99.5% (692 of 695 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/de/
* fix: dont use thumbnails for emoticons
* chore: Improve presence performance
* Translated using Weblate (Basque)
Currently translated at 100.0% (695 of 695 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/eu/
* Translated using Weblate (Galician)
Currently translated at 100.0% (695 of 695 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/gl/
* Translated using Weblate (Italian)
Currently translated at 100.0% (695 of 695 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/it/
* Translated using Weblate (Irish)
Currently translated at 100.0% (695 of 695 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ga/
* Translated using Weblate (Russian)
Currently translated at 100.0% (695 of 695 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ru/
* Translated using Weblate (Ukrainian)
Currently translated at 100.0% (695 of 695 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/uk/
* Translated using Weblate (Catalan)
Currently translated at 95.1% (661 of 695 strings)
Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ca/
* build: Bump version
* chore: Follow up send file dialog for images
* chore: Follow up send multiple images
* build: Add android build workaround for new flutter version
* build: Use file selector to save files
* chore: Follow up save file on desktop
* chore: Adjust default linux window height
* refactor: Update to new receive sharing intent package
* fluffychat merge
* fluffychat merge
* fluffychat merge
* fix android build
* fluffychat merge
* fluffychat merge
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Krille <c.kussowski@famedly.com>
Co-authored-by: Krille-chan <christian-kussowski@posteo.de>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: josé m <correoxm@disroot.org>
Co-authored-by: v1s7 <v1s7@users.noreply.hosted.weblate.org>
Co-authored-by: Christian <christian-pauly@posteo.de>
Co-authored-by: Rex_sa <rex.sa@pm.me>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Edgars Andersons <Edgars+Weblate@gaitenis.id.lv>
Co-authored-by: xabirequejo <xabi.rn@gmail.com>
Co-authored-by: Bezruchenko Simon <worcposj44@gmail.com>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Bruno Roh <kane.roh429@gmail.com>
Co-authored-by: Kimby <kimisaes@naver.com>
Co-authored-by: Aindriú Mac Giolla Eoin <aindriu80@gmail.com>
Co-authored-by: Angelo Schirinzi <muten619@hotmail.it>
Co-authored-by: Marek Vospěl <marek@vospel.cz>
Co-authored-by: Александр (Alexandr1995) <stupino19951406@gmail.com>
568 lines
15 KiB
Dart
568 lines
15 KiB
Dart
import 'package:collection/collection.dart';
|
|
import 'package:fluffychat/config/app_config.dart';
|
|
import 'package:fluffychat/pages/chat/chat.dart';
|
|
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
|
|
import 'package:fluffychat/widgets/avatar.dart';
|
|
import 'package:fluffychat/widgets/mxc_image.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_highlighter/flutter_highlighter.dart';
|
|
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';
|
|
|
|
import '../../../utils/url_launcher.dart';
|
|
|
|
class HtmlMessage extends StatelessWidget {
|
|
final String html;
|
|
final Room room;
|
|
final Color textColor;
|
|
// #Pangea
|
|
final bool isOverlay;
|
|
final PangeaMessageEvent? pangeaMessageEvent;
|
|
final ChatController controller;
|
|
final Event event;
|
|
final Event? nextEvent;
|
|
final Event? prevEvent;
|
|
// Pangea#
|
|
|
|
const HtmlMessage({
|
|
super.key,
|
|
required this.html,
|
|
required this.room,
|
|
this.textColor = Colors.black,
|
|
// #Pangea
|
|
required this.isOverlay,
|
|
required this.event,
|
|
this.pangeaMessageEvent,
|
|
required this.controller,
|
|
this.nextEvent,
|
|
this.prevEvent,
|
|
// Pangea#
|
|
});
|
|
|
|
dom.Node _linkifyHtml(dom.Node element) {
|
|
for (final node in element.nodes) {
|
|
if (node is! dom.Text ||
|
|
(element is dom.Element && element.localName == 'code')) {
|
|
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.replaceAll('<', '<')
|
|
: '<a href="${linkifyElement.text}">${linkifyElement.text}</a>',
|
|
)
|
|
.join(' ');
|
|
|
|
node.replaceWith(dom.Element.html('<p>$newHtml</p>'));
|
|
}
|
|
return element;
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final fontSize = AppConfig.messageFontSize * AppConfig.fontSizeFactor;
|
|
|
|
final linkColor = textColor.withAlpha(150);
|
|
|
|
final blockquoteStyle = Style(
|
|
border: Border(
|
|
left: BorderSide(
|
|
width: 3,
|
|
color: textColor,
|
|
),
|
|
),
|
|
padding: HtmlPaddings.only(left: 6, bottom: 0),
|
|
);
|
|
|
|
final element = _linkifyHtml(HtmlParser.parseHTML(html));
|
|
|
|
// there is no need to pre-validate the html, as we validate it while rendering
|
|
// #Pangea
|
|
return SelectionArea(
|
|
child: GestureDetector(
|
|
onTap: () {
|
|
if (!isOverlay) {
|
|
controller.showToolbar(
|
|
event,
|
|
pangeaMessageEvent: pangeaMessageEvent,
|
|
nextEvent: nextEvent,
|
|
prevEvent: prevEvent,
|
|
);
|
|
}
|
|
},
|
|
// Pangea#
|
|
child: 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, fontSize, linkColor),
|
|
CodeExtension(fontSize: fontSize),
|
|
MatrixMathExtension(
|
|
style: TextStyle(fontSize: fontSize, color: textColor),
|
|
),
|
|
const TableHtmlExtension(),
|
|
SpoilerExtension(textColor: textColor),
|
|
const ImageExtension(),
|
|
FontColorExtension(),
|
|
FallbackTextExtension(fontSize: fontSize),
|
|
],
|
|
onLinkTap: (url, _, element) => UrlLauncher(
|
|
context,
|
|
url,
|
|
element?.text,
|
|
).launchUrl(),
|
|
onlyRenderTheseTags: const {
|
|
...allowedHtmlTags,
|
|
// Needed to make it work properly
|
|
'body',
|
|
'html',
|
|
},
|
|
shrinkWrap: true,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
static const Set<String> fallbackTextTags = {'tg-forward'};
|
|
|
|
/// Keep in sync with: https://spec.matrix.org/v1.6/client-server-api/#mroommessage-msgtypes
|
|
static const Set<String> allowedHtmlTags = {
|
|
'font',
|
|
'del',
|
|
'h1',
|
|
'h2',
|
|
'h3',
|
|
'h4',
|
|
'h5',
|
|
'h6',
|
|
'blockquote',
|
|
'p',
|
|
'a',
|
|
'ul',
|
|
'ol',
|
|
'sup',
|
|
'sub',
|
|
'li',
|
|
'b',
|
|
'i',
|
|
'u',
|
|
'strong',
|
|
'em',
|
|
'strike',
|
|
'code',
|
|
'hr',
|
|
'br',
|
|
'div',
|
|
'table',
|
|
'thead',
|
|
'tbody',
|
|
'tr',
|
|
'th',
|
|
'td',
|
|
'caption',
|
|
'pre',
|
|
'span',
|
|
'img',
|
|
'details',
|
|
'summary',
|
|
// Not in the allowlist of the matrix spec yet but should be harmless:
|
|
'ruby',
|
|
'rp',
|
|
'rt',
|
|
// Workaround for https://github.com/krille-chan/fluffychat/issues/507
|
|
...fallbackTextTags,
|
|
};
|
|
}
|
|
|
|
class FontColorExtension extends HtmlExtension {
|
|
static const String colorAttribute = 'color';
|
|
static const String mxColorAttribute = 'data-mx-color';
|
|
static const String bgColorAttribute = 'data-mx-bg-color';
|
|
|
|
@override
|
|
Set<String> get supportedTags => {'font', 'span'};
|
|
|
|
@override
|
|
bool matches(ExtensionContext context) {
|
|
if (!supportedTags.contains(context.elementName)) return false;
|
|
return context.element?.attributes.keys.any(
|
|
{
|
|
colorAttribute,
|
|
mxColorAttribute,
|
|
bgColorAttribute,
|
|
}.contains,
|
|
) ??
|
|
false;
|
|
}
|
|
|
|
Color? hexToColor(String? hexCode) {
|
|
if (hexCode == null) return null;
|
|
if (hexCode.startsWith('#')) hexCode = hexCode.substring(1);
|
|
if (hexCode.length == 6) hexCode = 'FF$hexCode';
|
|
final colorValue = int.tryParse(hexCode, radix: 16);
|
|
return colorValue == null ? null : Color(colorValue);
|
|
}
|
|
|
|
@override
|
|
InlineSpan build(
|
|
ExtensionContext context,
|
|
) {
|
|
final colorText = context.element?.attributes[colorAttribute] ??
|
|
context.element?.attributes[mxColorAttribute];
|
|
final bgColor = context.element?.attributes[bgColorAttribute];
|
|
return TextSpan(
|
|
style: TextStyle(
|
|
color: hexToColor(colorText),
|
|
backgroundColor: hexToColor(bgColor),
|
|
),
|
|
text: context.innerHtml,
|
|
);
|
|
}
|
|
}
|
|
|
|
class ImageExtension extends HtmlExtension {
|
|
final double defaultDimension;
|
|
|
|
const ImageExtension({this.defaultDimension = 64});
|
|
|
|
@override
|
|
Set<String> get supportedTags => {'img'};
|
|
|
|
@override
|
|
InlineSpan build(ExtensionContext context) {
|
|
final mxcUrl = Uri.tryParse(context.attributes['src'] ?? '');
|
|
if (mxcUrl == null || mxcUrl.scheme != 'mxc') {
|
|
return TextSpan(text: context.attributes['alt']);
|
|
}
|
|
|
|
final width = double.tryParse(context.attributes['width'] ?? '');
|
|
final height = double.tryParse(context.attributes['height'] ?? '');
|
|
|
|
final actualWidth = width ?? height ?? defaultDimension;
|
|
final actualHeight = height ?? width ?? defaultDimension;
|
|
|
|
return WidgetSpan(
|
|
child: SizedBox(
|
|
width: actualWidth,
|
|
height: actualHeight,
|
|
child: MxcImage(
|
|
uri: mxcUrl,
|
|
width: actualWidth,
|
|
height: actualHeight,
|
|
isThumbnail: (actualWidth * actualHeight) > (256 * 256),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class SpoilerExtension extends HtmlExtension {
|
|
final Color textColor;
|
|
|
|
const SpoilerExtension({required this.textColor});
|
|
|
|
@override
|
|
Set<String> get supportedTags => {'span'};
|
|
|
|
static const String customDataAttribute = 'data-mx-spoiler';
|
|
|
|
@override
|
|
bool matches(ExtensionContext context) {
|
|
if (context.elementName != 'span') return false;
|
|
return context.element?.attributes.containsKey(customDataAttribute) ??
|
|
false;
|
|
}
|
|
|
|
@override
|
|
InlineSpan build(ExtensionContext context) {
|
|
var obscure = true;
|
|
final children = context.inlineSpanChildren;
|
|
return WidgetSpan(
|
|
child: StatefulBuilder(
|
|
builder: (context, setState) {
|
|
return InkWell(
|
|
onTap: () => setState(() {
|
|
obscure = !obscure;
|
|
}),
|
|
child: RichText(
|
|
text: TextSpan(
|
|
style: obscure ? TextStyle(backgroundColor: textColor) : null,
|
|
children: children,
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class MatrixMathExtension extends HtmlExtension {
|
|
final TextStyle? style;
|
|
|
|
MatrixMathExtension({this.style});
|
|
@override
|
|
Set<String> get supportedTags => {'div'};
|
|
|
|
@override
|
|
bool matches(ExtensionContext context) {
|
|
if (context.elementName != 'div') return false;
|
|
final mathData = context.element?.attributes['data-mx-maths'];
|
|
return mathData != null;
|
|
}
|
|
|
|
@override
|
|
InlineSpan build(ExtensionContext context) {
|
|
final data = context.element?.attributes['data-mx-maths'] ?? '';
|
|
return WidgetSpan(
|
|
child: Math.tex(
|
|
data,
|
|
textStyle: style,
|
|
onErrorFallback: (e) {
|
|
Logs().d('Flutter math parse error', e);
|
|
return Text(
|
|
data,
|
|
style: style,
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class CodeExtension extends HtmlExtension {
|
|
final double fontSize;
|
|
|
|
CodeExtension({required this.fontSize});
|
|
@override
|
|
Set<String> get supportedTags => {'code'};
|
|
|
|
@override
|
|
InlineSpan build(ExtensionContext context) => WidgetSpan(
|
|
child: Material(
|
|
clipBehavior: Clip.hardEdge,
|
|
borderRadius: BorderRadius.circular(4),
|
|
child: SingleChildScrollView(
|
|
scrollDirection: Axis.horizontal,
|
|
child: HighlightView(
|
|
context.element?.text ?? '',
|
|
language: context.element?.className
|
|
.split(' ')
|
|
.singleWhereOrNull(
|
|
(className) => className.startsWith('language-'),
|
|
)
|
|
?.split('language-')
|
|
.last ??
|
|
'md',
|
|
theme: shadesOfPurpleTheme,
|
|
padding: EdgeInsets.symmetric(
|
|
horizontal: 6,
|
|
vertical: context.element?.parent?.localName == 'pre' ? 6 : 0,
|
|
),
|
|
textStyle: TextStyle(fontSize: fontSize),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
class FallbackTextExtension extends HtmlExtension {
|
|
final double fontSize;
|
|
|
|
FallbackTextExtension({required this.fontSize});
|
|
@override
|
|
Set<String> get supportedTags => HtmlMessage.fallbackTextTags;
|
|
|
|
@override
|
|
InlineSpan build(ExtensionContext context) => TextSpan(
|
|
text: context.element?.text ?? '',
|
|
style: TextStyle(
|
|
fontSize: fontSize,
|
|
),
|
|
);
|
|
}
|
|
|
|
class RoomPillExtension extends HtmlExtension {
|
|
final Room room;
|
|
final BuildContext context;
|
|
final double fontSize;
|
|
final Color color;
|
|
|
|
RoomPillExtension(this.context, this.room, this.fontSize, this.color);
|
|
@override
|
|
Set<String> get supportedTags => {'a'};
|
|
|
|
@override
|
|
bool matches(ExtensionContext context) {
|
|
if (context.elementName != 'a') return false;
|
|
final userId = context.element?.attributes['href']
|
|
?.parseIdentifierIntoParts()
|
|
?.primaryIdentifier;
|
|
return userId != null;
|
|
}
|
|
|
|
static final _cachedUsers = <String, User?>{};
|
|
|
|
Future<User?> _fetchUser(String matrixId) async =>
|
|
_cachedUsers[room.id + matrixId] ??= await room.requestUser(matrixId);
|
|
|
|
@override
|
|
InlineSpan build(ExtensionContext context) {
|
|
final href = context.element?.attributes['href'];
|
|
final matrixId = href?.parseIdentifierIntoParts()?.primaryIdentifier;
|
|
if (href == null || matrixId == null) {
|
|
return TextSpan(text: context.innerHtml);
|
|
}
|
|
if (matrixId.sigil == '@') {
|
|
return WidgetSpan(
|
|
child: FutureBuilder<User?>(
|
|
future: _fetchUser(matrixId),
|
|
builder: (context, snapshot) => MatrixPill(
|
|
key: Key('user_pill_$matrixId'),
|
|
name: _cachedUsers[room.id + matrixId]?.calcDisplayname() ??
|
|
matrixId.localpart ??
|
|
matrixId,
|
|
avatar: _cachedUsers[room.id + matrixId]?.avatarUrl,
|
|
uri: href,
|
|
outerContext: this.context,
|
|
fontSize: fontSize,
|
|
color: color,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
if (matrixId.sigil == '#' || matrixId.sigil == '!') {
|
|
final room = matrixId.sigil == '!'
|
|
? this.room.client.getRoomById(matrixId)
|
|
: this.room.client.getRoomByAlias(matrixId);
|
|
if (room != null) {
|
|
return WidgetSpan(
|
|
child: MatrixPill(
|
|
name: room.getLocalizedDisplayname(),
|
|
avatar: room.avatar,
|
|
uri: href,
|
|
outerContext: this.context,
|
|
fontSize: fontSize,
|
|
color: color,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
return TextSpan(text: context.innerHtml);
|
|
}
|
|
}
|
|
|
|
class MatrixPill extends StatelessWidget {
|
|
final String name;
|
|
final BuildContext outerContext;
|
|
final Uri? avatar;
|
|
final String uri;
|
|
final double? fontSize;
|
|
final Color? color;
|
|
|
|
const MatrixPill({
|
|
super.key,
|
|
required this.name,
|
|
required this.outerContext,
|
|
this.avatar,
|
|
required this.uri,
|
|
required this.fontSize,
|
|
required this.color,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return InkWell(
|
|
onTap: UrlLauncher(outerContext, uri).launchUrl,
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Avatar(
|
|
mxContent: avatar,
|
|
name: name,
|
|
size: 16,
|
|
),
|
|
const SizedBox(width: 6),
|
|
Text(
|
|
name,
|
|
style: TextStyle(
|
|
color: color,
|
|
decorationColor: color,
|
|
decoration: TextDecoration.underline,
|
|
fontSize: fontSize,
|
|
height: 1.25,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|