diff --git a/contact/pubspec.lock b/contact/pubspec.lock index 3831417ba5..c8b105133b 100644 --- a/contact/pubspec.lock +++ b/contact/pubspec.lock @@ -520,6 +520,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.6.1" + linkify: + dependency: transitive + description: + name: linkify + sha256: "4139ea77f4651ab9c315b577da2dd108d9aa0bd84b5d03d33323f1970c645832" + url: "https://pub.dev" + source: hosted + version: "5.0.0" lints: dependency: transitive description: diff --git a/core/lib/presentation/utils/html_transformer/dom/add_target_blank_in_tag_a_transformers.dart b/core/lib/presentation/utils/html_transformer/dom/add_target_blank_in_tag_a_transformers.dart index d9454297dd..59c46f6272 100644 --- a/core/lib/presentation/utils/html_transformer/dom/add_target_blank_in_tag_a_transformers.dart +++ b/core/lib/presentation/utils/html_transformer/dom/add_target_blank_in_tag_a_transformers.dart @@ -14,6 +14,10 @@ class AddTargetBlankInTagATransformer extends DomTransformer { final elements = document.querySelectorAll('a'); await Future.wait(elements.map((element) async { element.attributes['target'] = '_blank'; + final rel = element.attributes['rel']; + if (rel == null || (!rel.contains('noopener') && !rel.contains('noreferrer'))) { + element.attributes['rel'] = 'noreferrer'; + } })); } } diff --git a/core/lib/presentation/utils/html_transformer/sanitize_autolink_filter.dart b/core/lib/presentation/utils/html_transformer/sanitize_autolink_filter.dart new file mode 100644 index 0000000000..86b2712db3 --- /dev/null +++ b/core/lib/presentation/utils/html_transformer/sanitize_autolink_filter.dart @@ -0,0 +1,65 @@ + +import 'dart:convert'; + +import 'package:core/utils/app_logger.dart'; +import 'package:linkify/linkify.dart'; + +class SanitizeAutolinkFilter { + + final HtmlEscape htmlEscape; + final _linkifyOption = const LinkifyOptions( + humanize: true, + looseUrl: true, + defaultToHttps: true, + removeWww: true + ); + final _linkifier = [ + const EmailLinkifier(), + const UrlLinkifier() + ]; + + SanitizeAutolinkFilter(this.htmlEscape); + + String process(String inputText) { + if (inputText.isEmpty) { + return ''; + } + + final elements = linkify( + inputText, + options: _linkifyOption, + linkifiers: _linkifier + ); + log('AutolinkFilter::process:elements: $elements'); + final htmlTextBuffer = StringBuffer(); + + for (var element in elements) { + if (element is TextElement) { + final escapedHtml = htmlEscape.convert(element.text); + htmlTextBuffer.write(escapedHtml); + } else if (element is EmailElement) { + final emailLinkTag = _buildEmailLinkTag( + mailToLink: element.url, + value: element.text + ); + htmlTextBuffer.write(emailLinkTag); + } else if (element is UrlElement) { + final urlLinkTag = _buildUrlLinkTag( + urlLink: element.url, + value: element.text + ); + htmlTextBuffer.write(urlLinkTag); + } + } + + return htmlTextBuffer.toString(); + } + + String _buildUrlLinkTag({required String urlLink, required String value}) { + return '$value'; + } + + String _buildEmailLinkTag({required String mailToLink, required String value}) { + return '$value'; + } +} \ No newline at end of file diff --git a/core/lib/presentation/utils/html_transformer/text/convert_url_string_to_html_links_transformers.dart b/core/lib/presentation/utils/html_transformer/text/convert_url_string_to_html_links_transformers.dart deleted file mode 100644 index 5f9f3d0cb1..0000000000 --- a/core/lib/presentation/utils/html_transformer/text/convert_url_string_to_html_links_transformers.dart +++ /dev/null @@ -1,14 +0,0 @@ - -import 'package:core/presentation/utils/html_transformer/base/text_transformer.dart'; -import 'package:core/utils/linkify_html.dart'; - -class ConvertUrlStringToHtmlLinksTransformers extends TextTransformer { - - const ConvertUrlStringToHtmlLinksTransformers(); - - @override - String process(String text) { - final texValid = LinkifyHtml().generateLinkify(text); - return texValid; - } -} \ No newline at end of file diff --git a/core/lib/presentation/utils/html_transformer/text/sanitize_autolink_html_transformers.dart b/core/lib/presentation/utils/html_transformer/text/sanitize_autolink_html_transformers.dart new file mode 100644 index 0000000000..e1bcbb30c9 --- /dev/null +++ b/core/lib/presentation/utils/html_transformer/text/sanitize_autolink_html_transformers.dart @@ -0,0 +1,15 @@ + +import 'dart:convert'; + +import 'package:core/presentation/utils/html_transformer/sanitize_autolink_filter.dart'; +import 'package:core/presentation/utils/html_transformer/base/text_transformer.dart'; + +class SanitizeAutolinkHtmlTransformers extends TextTransformer { + + final HtmlEscape htmlEscape; + + SanitizeAutolinkHtmlTransformers(this.htmlEscape); + + @override + String process(String text) => SanitizeAutolinkFilter(htmlEscape).process(text); +} \ No newline at end of file diff --git a/core/lib/presentation/utils/html_transformer/text/sanitize_html_transformers.dart b/core/lib/presentation/utils/html_transformer/text/sanitize_html_transformers.dart deleted file mode 100644 index baf2b9cd71..0000000000 --- a/core/lib/presentation/utils/html_transformer/text/sanitize_html_transformers.dart +++ /dev/null @@ -1,17 +0,0 @@ - -import 'dart:convert'; - -import 'package:core/presentation/utils/html_transformer/base/text_transformer.dart'; -import 'package:core/utils/app_logger.dart'; - -class SanitizeHtmlTransformers extends TextTransformer { - - const SanitizeHtmlTransformers(); - - @override - String process(String text) { - final htmlEncoded = const HtmlEscape().convert(text); - log('HtmlSanitizerTransformers::process:htmlEncoded: $htmlEncoded'); - return htmlEncoded; - } -} \ No newline at end of file diff --git a/core/lib/utils/linkify_html.dart b/core/lib/utils/linkify_html.dart deleted file mode 100644 index 2c2372500f..0000000000 --- a/core/lib/utils/linkify_html.dart +++ /dev/null @@ -1,78 +0,0 @@ - -import 'package:core/utils/app_logger.dart'; - -class LinkifyHtml { - - String generateLinkify(String inputText) { - var replacedText = _linkifyUrlAddress(inputText); - replacedText = _linkifyMailToAddress(replacedText); - return replacedText; - } - - RegExp _generateRegExp(String pattern) { - return RegExp(pattern, multiLine: true, caseSensitive: false); - } - - /// URLs starting with http://, https://, ftp://, or "www." without //. - String _linkifyUrlAddress(String inputText) { - var replacedText = inputText; - - // URLs starting with http://, https://, ftp:// - final regexLinkWithHttp = _generateRegExp(r'(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])'); - final newReplacedTextWithHttp = replacedText.replaceAllMapped(regexLinkWithHttp, (regexMatch) { - final link = regexMatch.group(1); - log('LinkifyHtml::_linkifyUrlAddress():link: $link'); - if (link?.isNotEmpty == true) { - if (replacedText.contains('$link'; - } - } else { - return ''; - } - }); - replacedText = newReplacedTextWithHttp; - - // URLs starting with "www." without // before it or it'd re-link the ones done above. - final regexLinkWithWWW = _generateRegExp(r'(^|[^\/])(www\.[\S]+(\b|\$))'); - final newReplacedTextWithWWW = replacedText.replaceAllMapped(regexLinkWithWWW, (regexMatch) { - final previousChar = regexMatch.group(1); - log('LinkifyHtml::_linkifyUrlAddress(): previousChar: $previousChar'); - final link = regexMatch.group(2); - log('LinkifyHtml::_linkifyUrlAddress():link: $link'); - if (link?.isNotEmpty == true) { - if (replacedText.contains('$link'; - } - } else { - return ''; - } - }); - replacedText = newReplacedTextWithWWW; - - return replacedText; - } - - /// Change email addresses to mailto:: links. - String _linkifyMailToAddress(String inputText) { - final regexMailTo = _generateRegExp(r'(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)'); - - final newReplacedTextWitMailTo = inputText.replaceAllMapped(regexMailTo, (regexMatch) { - final emailAddress = regexMatch.group(1); - if (emailAddress?.isNotEmpty == true) { - if (inputText.contains('$emailAddress'; - } - } else { - return ''; - } - }); - - return newReplacedTextWitMailTo; - } -} \ No newline at end of file diff --git a/core/pubspec.lock b/core/pubspec.lock index 66fa328979..8b538d217a 100644 --- a/core/pubspec.lock +++ b/core/pubspec.lock @@ -320,6 +320,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.7" + linkify: + dependency: "direct main" + description: + name: linkify + sha256: "4139ea77f4651ab9c315b577da2dd108d9aa0bd84b5d03d33323f1970c645832" + url: "https://pub.dev" + source: hosted + version: "5.0.0" lints: dependency: transitive description: diff --git a/core/pubspec.yaml b/core/pubspec.yaml index 8b77862b42..f92967c708 100644 --- a/core/pubspec.yaml +++ b/core/pubspec.yaml @@ -73,6 +73,8 @@ dependencies: flutter_typeahead: 4.6.0 + linkify: 5.0.0 + dev_dependencies: flutter_test: sdk: flutter diff --git a/core/test/linkify_html_test.dart b/core/test/linkify_html_test.dart deleted file mode 100644 index 00434a55f7..0000000000 --- a/core/test/linkify_html_test.dart +++ /dev/null @@ -1,59 +0,0 @@ - -import 'package:core/utils/linkify_html.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - group('linkify_html test', () { - - final linkifyHtml = LinkifyHtml(); - - test( - 'generateLinkify should return url when input text contain http/https/ftp', - () async { - final htmlValidate = linkifyHtml.generateLinkify( - 'See at Hanoi' - ); - expect( - htmlValidate, - equals('See <https://linagora.com> at Hanoi')); - } - ); - - test( - 'generateLinkify should return www when input text contain www', - () async { - final htmlValidate = linkifyHtml.generateLinkify( - 'See www.google.com at Hanoi' - ); - expect( - htmlValidate, - equals('See www.google.com at Hanoi')); - } - ); - - test( - 'generateLinkify should return url when input text contain email address', - () async { - final htmlValidate = linkifyHtml.generateLinkify( - 'See tdvu@linagora.com at Hanoi' - ); - expect( - htmlValidate, - equals('See tdvu@linagora.com at Hanoi')); - } - ); - - test( - 'generateLinkify should preserve the text when input text contain james/ApacheJames/master [master] [765]"' - ); - expect( - htmlValidate, - equals('Check console output at "james/ApacheJames/master [master] [765]"') - ); - } - ); - }); -} \ No newline at end of file diff --git a/core/test/sanitize_autolink_filter_test.dart b/core/test/sanitize_autolink_filter_test.dart new file mode 100644 index 0000000000..98fe740ded --- /dev/null +++ b/core/test/sanitize_autolink_filter_test.dart @@ -0,0 +1,55 @@ +import 'dart:convert'; + +import 'package:core/presentation/utils/html_transformer/sanitize_autolink_filter.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('SanitizeAutolinkFilter test', () { + + final sanitizeAutolinkFilter = SanitizeAutolinkFilter(const HtmlEscape()); + + test( + 'SanitizeAutolinkFilter should return html a tag with href="urlLink" when input text contain https', + () { + final htmlValidate = sanitizeAutolinkFilter.process('See https://linagora.com at Hanoi'); + expect( + htmlValidate, + equals('See linagora.com at Hanoi') + ); + } + ); + + test( + 'SanitizeAutolinkFilter should return html a tag with href="urlLink" when input text contain http', + () { + final htmlValidate = sanitizeAutolinkFilter.process('See http://linagora.com at Hanoi'); + expect( + htmlValidate, + equals('See linagora.com at Hanoi') + ); + } + ); + + test( + 'SanitizeAutolinkFilter should return html a tag with href="urlLink" when input text contain www', + () { + final htmlValidate = sanitizeAutolinkFilter.process('See www.linagora.com at Hanoi'); + expect( + htmlValidate, + equals('See linagora.com at Hanoi') + ); + } + ); + + test( + 'SanitizeAutolinkFilter should return html a tag with href="mailToLink" when input text contain email address', + () { + final htmlValidate = sanitizeAutolinkFilter.process('See tdvu@linagora.com at Hanoi'); + expect( + htmlValidate, + equals('See tdvu@linagora.com at Hanoi') + ); + } + ); + }); +} \ No newline at end of file diff --git a/docs/adr/0030-fix-can-not-see-link-when-type-is-text-plain-in-email-view.md b/docs/adr/0030-fix-can-not-see-link-when-type-is-text-plain-in-email-view.md new file mode 100644 index 0000000000..a378037ce1 --- /dev/null +++ b/docs/adr/0030-fix-can-not-see-link-when-type-is-text-plain-in-email-view.md @@ -0,0 +1,39 @@ +# 30. Fix can not see link when type is text/plain in email view + +Date: 2023-08-01 + +## Status + +- Issue: + +[1881](https://github.com/linagora/tmail-flutter/issues/1881) +[2047](https://github.com/linagora/tmail-flutter/issues/2047) + +## Context + +- When `Content-Type=Text/Plain` the `url` and `email-addreess` links are not automatically `highlighted`. +- Some `url` links are `highlighted` when clicking, opening the content in the email view itself does not create a new tab. + +## Root cause + +- Because we perform `html escape` before creating `autolink`, it makes lost tags undetectable. Moreover, the detect function is ignored in some cases + +## Decision + +- Use linkify to detect `url/email/text` and return a list of corresponding elements. +```dart + final elements = linkify(inputText); +``` + +- If element is `TextElement` we perform html escape for it. +- If element is `UrlElement` or `EmailElement` we make html link tag (``) for it. +```dart + String _buildLinkTag({required String link, required String value}) { + return '$value'; + } +``` + +## Consequences + +- Accurately detect and fully highlight `url` and `email-address` links in email body when `Content-Type=Text/plain` +- Automatically open new tab when clicking on `url` link and open composer when clicking on `email-address` diff --git a/lib/features/composer/presentation/composer_bindings.dart b/lib/features/composer/presentation/composer_bindings.dart index ce20cb83b2..0210639ac2 100644 --- a/lib/features/composer/presentation/composer_bindings.dart +++ b/lib/features/composer/presentation/composer_bindings.dart @@ -94,7 +94,6 @@ class ComposerBindings extends BaseBindings { Get.find())); Get.lazyPut(() => HtmlDataSourceImpl( Get.find(), - Get.find(), Get.find())); Get.lazyPut(() => StateDataSourceImpl(Get.find(), Get.find())); Get.lazyPut(() => EmailHiveCacheDataSourceImpl( diff --git a/lib/features/email/data/datasource_impl/html_datasource_impl.dart b/lib/features/email/data/datasource_impl/html_datasource_impl.dart index 992ac9a9dd..c6f65a32db 100644 --- a/lib/features/email/data/datasource_impl/html_datasource_impl.dart +++ b/lib/features/email/data/datasource_impl/html_datasource_impl.dart @@ -1,5 +1,4 @@ -import 'package:core/core.dart'; -import 'package:model/model.dart'; +import 'package:model/email/email_content.dart'; import 'package:tmail_ui_user/features/email/data/datasource/html_datasource.dart'; import 'package:tmail_ui_user/features/email/data/local/html_analyzer.dart'; import 'package:tmail_ui_user/main/exceptions/exception_thrower.dart'; @@ -7,10 +6,9 @@ import 'package:tmail_ui_user/main/exceptions/exception_thrower.dart'; class HtmlDataSourceImpl extends HtmlDataSource { final HtmlAnalyzer _htmlAnalyzer; - final DioClient _dioClient; final ExceptionThrower _exceptionThrower; - HtmlDataSourceImpl(this._htmlAnalyzer, this._dioClient, this._exceptionThrower); + HtmlDataSourceImpl(this._htmlAnalyzer, this._exceptionThrower); @override Future transformEmailContent( @@ -22,7 +20,6 @@ class HtmlDataSourceImpl extends HtmlDataSource { return await _htmlAnalyzer.transformEmailContent( emailContent, mapUrlDownloadCID, - _dioClient, draftsEmail: draftsEmail ); }).catchError(_exceptionThrower.throwException); diff --git a/lib/features/email/data/local/html_analyzer.dart b/lib/features/email/data/local/html_analyzer.dart index 465507a187..ef95312277 100644 --- a/lib/features/email/data/local/html_analyzer.dart +++ b/lib/features/email/data/local/html_analyzer.dart @@ -1,26 +1,31 @@ +import 'dart:convert'; + import 'package:core/data/network/dio_client.dart'; import 'package:core/presentation/utils/html_transformer/dom/add_tooltip_link_transformers.dart'; import 'package:core/presentation/utils/html_transformer/html_transform.dart'; -import 'package:core/presentation/utils/html_transformer/text/convert_url_string_to_html_links_transformers.dart'; -import 'package:core/presentation/utils/html_transformer/text/sanitize_html_transformers.dart'; +import 'package:core/presentation/utils/html_transformer/text/sanitize_autolink_html_transformers.dart'; import 'package:core/presentation/utils/html_transformer/transform_configuration.dart'; import 'package:model/email/email_content.dart'; import 'package:model/email/email_content_type.dart'; class HtmlAnalyzer { + final DioClient _dioClient; + final HtmlEscape _htmlEscape; + + HtmlAnalyzer(this._dioClient, this._htmlEscape); + Future transformEmailContent( EmailContent emailContent, Map? mapUrlDownloadCID, - DioClient dioClient, {bool draftsEmail = false} ) async { switch(emailContent.type) { case EmailContentType.textHtml: final htmlTransform = HtmlTransform( emailContent.content, - dioClient: dioClient, + dioClient: _dioClient, mapUrlDownloadCID: mapUrlDownloadCID ); @@ -35,10 +40,7 @@ class HtmlAnalyzer { final htmlTransform = HtmlTransform(emailContent.content); final message = htmlTransform.transformToTextPlain( transformConfiguration: TransformConfiguration.create( - customTextTransformers: [ - const ConvertUrlStringToHtmlLinksTransformers(), - const SanitizeHtmlTransformers(), - ] + customTextTransformers: [SanitizeAutolinkHtmlTransformers(_htmlEscape)] ) ); return EmailContent(emailContent.type, message); diff --git a/lib/features/email/presentation/bindings/email_bindings.dart b/lib/features/email/presentation/bindings/email_bindings.dart index 3c03de51a1..9174bd368e 100644 --- a/lib/features/email/presentation/bindings/email_bindings.dart +++ b/lib/features/email/presentation/bindings/email_bindings.dart @@ -97,7 +97,6 @@ class EmailBindings extends BaseBindings { Get.find())); Get.lazyPut(() => HtmlDataSourceImpl( Get.find(), - Get.find(), Get.find())); Get.lazyPut(() => StateDataSourceImpl(Get.find(), Get.find())); Get.lazyPut(() => EmailHiveCacheDataSourceImpl( diff --git a/lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart b/lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart index fc8f47a963..1f38c32163 100644 --- a/lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart +++ b/lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart @@ -1,5 +1,4 @@ import 'package:core/data/model/source_type/data_source_type.dart'; -import 'package:core/data/network/dio_client.dart'; import 'package:core/utils/config/app_config_loader.dart'; import 'package:core/utils/file_utils.dart'; import 'package:get/get.dart'; @@ -207,7 +206,6 @@ class MailboxDashBoardBindings extends BaseBindings { Get.find())); Get.lazyPut(() => HtmlDataSourceImpl( Get.find(), - Get.find(), Get.find())); Get.lazyPut(() => SearchDataSourceImpl( Get.find(), diff --git a/lib/features/offline_mode/bindings/sending_email_interactor_bindings.dart b/lib/features/offline_mode/bindings/sending_email_interactor_bindings.dart index d272d0d857..cd5f6d8304 100644 --- a/lib/features/offline_mode/bindings/sending_email_interactor_bindings.dart +++ b/lib/features/offline_mode/bindings/sending_email_interactor_bindings.dart @@ -1,5 +1,4 @@ import 'package:core/data/model/source_type/data_source_type.dart'; -import 'package:core/data/network/dio_client.dart'; import 'package:core/utils/file_utils.dart'; import 'package:get/get.dart'; import 'package:tmail_ui_user/features/base/interactors_bindings.dart'; @@ -57,7 +56,6 @@ class SendEmailInteractorBindings extends InteractorsBindings { Get.find())); Get.lazyPut(() => HtmlDataSourceImpl( Get.find(), - Get.find(), Get.find())); Get.lazyPut(() => StateDataSourceImpl( Get.find(), diff --git a/lib/features/push_notification/presentation/bindings/fcm_interactor_bindings.dart b/lib/features/push_notification/presentation/bindings/fcm_interactor_bindings.dart index 8c8a442c51..649e9e660f 100644 --- a/lib/features/push_notification/presentation/bindings/fcm_interactor_bindings.dart +++ b/lib/features/push_notification/presentation/bindings/fcm_interactor_bindings.dart @@ -1,5 +1,4 @@ import 'package:core/data/model/source_type/data_source_type.dart'; -import 'package:core/data/network/dio_client.dart'; import 'package:core/utils/file_utils.dart'; import 'package:get/get.dart'; import 'package:tmail_ui_user/features/base/interactors_bindings.dart'; @@ -98,7 +97,6 @@ class FcmInteractorBindings extends InteractorsBindings { Get.find())); Get.lazyPut(() => HtmlDataSourceImpl( Get.find(), - Get.find(), Get.find())); Get.lazyPut(() => StateDataSourceImpl( Get.find(), diff --git a/lib/main/bindings/core/core_bindings.dart b/lib/main/bindings/core/core_bindings.dart index f1aa7fd885..13be1151c6 100644 --- a/lib/main/bindings/core/core_bindings.dart +++ b/lib/main/bindings/core/core_bindings.dart @@ -9,7 +9,6 @@ import 'package:core/utils/platform_info.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:get/get.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import 'package:tmail_ui_user/features/email/data/local/html_analyzer.dart'; import 'package:tmail_ui_user/features/sending_queue/presentation/utils/sending_queue_isolate_manager.dart'; import 'package:tmail_ui_user/main/utils/email_receive_manager.dart'; import 'package:uuid/uuid.dart'; @@ -21,7 +20,6 @@ class CoreBindings extends Bindings { await _bindingSharePreference(); _bindingAppImagePaths(); _bindingResponsiveManager(); - _bindingTransformer(); _bindingToast(); _bindingDeviceManager(); _bindingReceivingSharingStream(); @@ -41,10 +39,6 @@ class CoreBindings extends Bindings { await Get.putAsync(() async => await SharedPreferences.getInstance(), permanent: true); } - void _bindingTransformer() { - Get.put(HtmlAnalyzer()); - } - void _bindingToast() { Get.put(AppToast()); } diff --git a/lib/main/bindings/network/network_bindings.dart b/lib/main/bindings/network/network_bindings.dart index 0bff9870fd..cced0d53c5 100644 --- a/lib/main/bindings/network/network_bindings.dart +++ b/lib/main/bindings/network/network_bindings.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'dart:io'; import 'package:connectivity_plus/connectivity_plus.dart'; @@ -8,6 +9,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter_appauth/flutter_appauth.dart'; import 'package:get/get.dart'; import 'package:jmap_dart_client/http/http_client.dart'; +import 'package:tmail_ui_user/features/email/data/local/html_analyzer.dart'; import 'package:tmail_ui_user/features/email/data/network/email_api.dart'; import 'package:tmail_ui_user/features/email/data/network/mdn_api.dart'; import 'package:tmail_ui_user/features/login/data/local/account_cache_manager.dart'; @@ -36,6 +38,7 @@ class NetworkBindings extends Bindings { _bindingConnection(); _bindingDio(); _bindingApi(); + _bindingTransformer(); _bindingException(); } @@ -104,4 +107,9 @@ class NetworkBindings extends Bindings { void _bindingException() { Get.put(RemoteExceptionThrower()); } + + void _bindingTransformer() { + Get.put(const HtmlEscape()); + Get.put(HtmlAnalyzer(Get.find(), Get.find())); + } } \ No newline at end of file diff --git a/model/pubspec.lock b/model/pubspec.lock index 22134872a8..ed8d858189 100644 --- a/model/pubspec.lock +++ b/model/pubspec.lock @@ -512,6 +512,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.6.1" + linkify: + dependency: transitive + description: + name: linkify + sha256: "4139ea77f4651ab9c315b577da2dd108d9aa0bd84b5d03d33323f1970c645832" + url: "https://pub.dev" + source: hosted + version: "5.0.0" lints: dependency: transitive description: diff --git a/pubspec.lock b/pubspec.lock index 4e16ebc52b..c184debf4c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1007,6 +1007,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.6.1" + linkify: + dependency: transitive + description: + name: linkify + sha256: "4139ea77f4651ab9c315b577da2dd108d9aa0bd84b5d03d33323f1970c645832" + url: "https://pub.dev" + source: hosted + version: "5.0.0" lints: dependency: transitive description: