TF-1961 Transform html email content in composer
(cherry picked from commit f5470c130744242195b265a0ea4aad044ad8e740)
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
/// Transforms plain text messages.
|
||||
abstract class TextTransformer {
|
||||
const TextTransformer();
|
||||
|
||||
String process(String text);
|
||||
String process(String text, HtmlEscape htmlEscape);
|
||||
}
|
||||
@@ -12,7 +12,9 @@ import 'package:html/dom.dart';
|
||||
|
||||
class ImageTransformer extends DomTransformer {
|
||||
|
||||
const ImageTransformer();
|
||||
final bool useLoadingAttribute;
|
||||
|
||||
const ImageTransformer({this.useLoadingAttribute = false});
|
||||
|
||||
@override
|
||||
Future<void> process({
|
||||
@@ -40,14 +42,19 @@ class ImageTransformer extends DomTransformer {
|
||||
);
|
||||
imageElement.attributes['src'] = imageBase64 ?? src;
|
||||
} else if (src.startsWith('https://') || src.startsWith('http://')) {
|
||||
final classAttribute = imageElement.attributes['class'];
|
||||
if (classAttribute != null) {
|
||||
imageElement.attributes['class'] = '$classAttribute lazy-loading';
|
||||
if (useLoadingAttribute) {
|
||||
imageElement.attributes['loading'] = 'lazy';
|
||||
} else {
|
||||
imageElement.attributes['class'] = 'lazy-loading';
|
||||
final classAttribute = imageElement.attributes['class'];
|
||||
if (classAttribute != null) {
|
||||
imageElement.attributes['class'] = '$classAttribute lazy-loading';
|
||||
} else {
|
||||
imageElement.attributes['class'] = 'lazy-loading';
|
||||
}
|
||||
imageElement.attributes['data-src'] = src;
|
||||
imageElement.attributes.remove('src');
|
||||
imageElement.attributes.remove('loading');
|
||||
}
|
||||
imageElement.attributes['data-src'] = src;
|
||||
imageElement.attributes.remove('src');
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
|
||||
import 'package:core/data/network/dio_client.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/base/dom_transformer.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/html_template.dart';
|
||||
import 'package:core/utils/app_logger.dart';
|
||||
import 'package:html/dom.dart';
|
||||
|
||||
class RemoveTooltipLinkTransformer extends DomTransformer {
|
||||
|
||||
const RemoveTooltipLinkTransformer();
|
||||
|
||||
@override
|
||||
Future<void> process({
|
||||
required Document document,
|
||||
Map<String, String>? mapUrlDownloadCID,
|
||||
DioClient? dioClient
|
||||
}) async {
|
||||
final linkElements = document.querySelectorAll('a.$nameClassToolTip');
|
||||
await Future.wait(linkElements.map((linkElement) async {
|
||||
final classAttribute = linkElement.attributes['class'];
|
||||
if (classAttribute != null) {
|
||||
final newClassAttribute = classAttribute.replaceFirst(nameClassToolTip, '');
|
||||
linkElement.attributes['class'] = newClassAttribute;
|
||||
}
|
||||
final listSpanTag = linkElement.querySelectorAll('span.tooltiptext');
|
||||
log('RemoveTooltipLinkTransformer::process:listSpanTag: ${listSpanTag.length}');
|
||||
if (listSpanTag.isNotEmpty) {
|
||||
for (var element in listSpanTag) {
|
||||
element.remove();
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
|
||||
|
||||
import 'package:core/data/network/dio_client.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/base/dom_transformer.dart';
|
||||
import 'package:html/dom.dart';
|
||||
|
||||
class ReplaceLazyLoadImageTransformer extends DomTransformer {
|
||||
|
||||
const ReplaceLazyLoadImageTransformer();
|
||||
|
||||
@override
|
||||
Future<void> process({
|
||||
required Document document,
|
||||
Map<String, String>? mapUrlDownloadCID,
|
||||
DioClient? dioClient
|
||||
}) async {
|
||||
final imageElements = document.querySelectorAll('img.lazy-loading');
|
||||
await Future.wait(imageElements.map((imageElement) async {
|
||||
final classAttribute = imageElement.attributes['class'];
|
||||
if (classAttribute != null) {
|
||||
final newClassAttribute = classAttribute.replaceFirst('lazy-loading', '');
|
||||
imageElement.attributes['class'] = newClassAttribute;
|
||||
}
|
||||
final dataSrc = imageElement.attributes['data-src'];
|
||||
if (dataSrc != null) {
|
||||
imageElement.attributes['src'] = dataSrc;
|
||||
imageElement.attributes.remove('data-src');
|
||||
}
|
||||
imageElement.attributes['loading'] = 'lazy';
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,26 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:core/core.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/message_content_transformer.dart';
|
||||
|
||||
class HtmlTransform {
|
||||
|
||||
final DioClient _dioClient;
|
||||
final HtmlEscape _htmlEscape;
|
||||
|
||||
HtmlTransform(this._dioClient);
|
||||
HtmlTransform(this._dioClient, this._htmlEscape);
|
||||
|
||||
/// Transforms this message to HTML code.
|
||||
Future<String> transformToHtml({
|
||||
required String contentHtml,
|
||||
Map<String, String>? mapUrlDownloadCID,
|
||||
required String htmlContent,
|
||||
Map<String, String>? mapCidImageDownloadUrl,
|
||||
TransformConfiguration? transformConfiguration,
|
||||
}) async {
|
||||
transformConfiguration ??= TransformConfiguration.create();
|
||||
final transformer = MessageContentTransformer(transformConfiguration, _dioClient);
|
||||
final transformer = MessageContentTransformer(transformConfiguration, _dioClient, _htmlEscape);
|
||||
final document = await transformer.toDocument(
|
||||
message: contentHtml,
|
||||
mapUrlDownloadCID: mapUrlDownloadCID
|
||||
message: htmlContent,
|
||||
mapUrlDownloadCID: mapCidImageDownloadUrl
|
||||
);
|
||||
return document.outerHtml;
|
||||
}
|
||||
@@ -28,7 +31,7 @@ class HtmlTransform {
|
||||
TransformConfiguration? transformConfiguration
|
||||
}) {
|
||||
transformConfiguration ??= TransformConfiguration.create();
|
||||
final transformer = MessageContentTransformer(transformConfiguration, _dioClient);
|
||||
final transformer = MessageContentTransformer(transformConfiguration, _dioClient, _htmlEscape);
|
||||
final message = transformer.toMessage(content);
|
||||
return message;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:core/data/network/dio_client.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/transform_configuration.dart';
|
||||
import 'package:html/dom.dart';
|
||||
@@ -5,23 +7,28 @@ import 'package:html/parser.dart' show parse;
|
||||
|
||||
/// Transforms messages
|
||||
class MessageContentTransformer {
|
||||
/// The configuration used for the transformation
|
||||
final TransformConfiguration configuration;
|
||||
final DioClient dioClient;
|
||||
/// The _configuration used for the transformation
|
||||
final TransformConfiguration _configuration;
|
||||
final DioClient _dioClient;
|
||||
final HtmlEscape _htmlEscape;
|
||||
|
||||
MessageContentTransformer(this.configuration, this.dioClient);
|
||||
MessageContentTransformer(
|
||||
this._configuration,
|
||||
this._dioClient,
|
||||
this._htmlEscape
|
||||
);
|
||||
|
||||
Future<void> _transformDocument({
|
||||
required Document document,
|
||||
Map<String, String>? mapUrlDownloadCID
|
||||
}) async {
|
||||
await Future.wait([
|
||||
if (configuration.domTransformers.isNotEmpty)
|
||||
...configuration.domTransformers.map((domTransformer) async =>
|
||||
if (_configuration.domTransformers.isNotEmpty)
|
||||
..._configuration.domTransformers.map((domTransformer) async =>
|
||||
domTransformer.process(
|
||||
document: document,
|
||||
mapUrlDownloadCID: mapUrlDownloadCID,
|
||||
dioClient: dioClient
|
||||
dioClient: _dioClient
|
||||
)
|
||||
)
|
||||
]);
|
||||
@@ -40,9 +47,9 @@ class MessageContentTransformer {
|
||||
}
|
||||
|
||||
String _transformMessage(String message) {
|
||||
if (configuration.textTransformers.isNotEmpty) {
|
||||
for (var transformer in configuration.textTransformers) {
|
||||
message = transformer.process(message);
|
||||
if (_configuration.textTransformers.isNotEmpty) {
|
||||
for (var transformer in _configuration.textTransformers) {
|
||||
message = transformer.process(message, _htmlEscape);
|
||||
}
|
||||
}
|
||||
return message;
|
||||
|
||||
+2
-4
@@ -6,10 +6,8 @@ import 'package:core/presentation/utils/html_transformer/base/text_transformer.d
|
||||
|
||||
class SanitizeAutolinkHtmlTransformers extends TextTransformer {
|
||||
|
||||
final HtmlEscape htmlEscape;
|
||||
|
||||
SanitizeAutolinkHtmlTransformers(this.htmlEscape);
|
||||
const SanitizeAutolinkHtmlTransformers();
|
||||
|
||||
@override
|
||||
String process(String text) => SanitizeAutolinkFilter(htmlEscape).process(text);
|
||||
String process(String text, HtmlEscape htmlEscape) => SanitizeAutolinkFilter(htmlEscape).process(text);
|
||||
}
|
||||
@@ -1,12 +1,16 @@
|
||||
|
||||
import 'package:core/core.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/base/dom_transformer.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/base/text_transformer.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/dom/add_target_blank_in_tag_a_transformers.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/dom/blockcode_transformers.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/dom/blockquoted_transformers.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/dom/image_transformers.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/dom/remove_tooltip_link_transformers.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/dom/replace_lazy_load_image_transformer.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/dom/script_transformers.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/dom/sigature_transformers.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/text/sanitize_autolink_html_transformers.dart';
|
||||
|
||||
/// Contains the configuration for all transformations.
|
||||
class TransformConfiguration {
|
||||
@@ -25,6 +29,45 @@ class TransformConfiguration {
|
||||
this.textTransformers
|
||||
);
|
||||
|
||||
factory TransformConfiguration.forReplyForwardEmail() => TransformConfiguration.create(
|
||||
customDomTransformers: [
|
||||
const ReplaceLazyLoadImageTransformer(),
|
||||
if (PlatformInfo.isWeb)
|
||||
const RemoveTooltipLinkTransformer(),
|
||||
]
|
||||
);
|
||||
|
||||
factory TransformConfiguration.forDraftsEmail() => TransformConfiguration.create(
|
||||
customDomTransformers: [
|
||||
const RemoveScriptTransformer(),
|
||||
const BlockQuotedTransformer(),
|
||||
const BlockCodeTransformer(),
|
||||
const AddTargetBlankInTagATransformer(),
|
||||
const ImageTransformer(useLoadingAttribute: true),
|
||||
]
|
||||
);
|
||||
|
||||
factory TransformConfiguration.forComposeEmailPlatformWeb() => TransformConfiguration.create(
|
||||
customDomTransformers: [
|
||||
const RemoveScriptTransformer(),
|
||||
const BlockQuotedTransformer(),
|
||||
const BlockCodeTransformer(),
|
||||
const AddTargetBlankInTagATransformer(),
|
||||
const ImageTransformer(useLoadingAttribute: true)
|
||||
]
|
||||
);
|
||||
|
||||
factory TransformConfiguration.forPreviewEmailPlatformWeb() => TransformConfiguration.create(
|
||||
customDomTransformers: [
|
||||
const RemoveScriptTransformer(),
|
||||
const BlockQuotedTransformer(),
|
||||
const BlockCodeTransformer(),
|
||||
const AddTargetBlankInTagATransformer(),
|
||||
const ImageTransformer(),
|
||||
const AddTooltipLinkTransformer(),
|
||||
]
|
||||
);
|
||||
|
||||
/// Provides easy access to a standard configuration that does not block external images.
|
||||
static const TransformConfiguration standardConfiguration = TransformConfiguration(
|
||||
standardDomTransformers,
|
||||
@@ -61,13 +104,7 @@ class TransformConfiguration {
|
||||
ImageTransformer(),
|
||||
];
|
||||
|
||||
static const List<DomTransformer> domTransformersForDraftEmail = [
|
||||
RemoveScriptTransformer(),
|
||||
BlockQuotedTransformer(),
|
||||
BlockCodeTransformer(),
|
||||
AddTargetBlankInTagATransformer(),
|
||||
ImageTransformer(),
|
||||
static const List<TextTransformer> standardTextTransformers = [
|
||||
SanitizeAutolinkHtmlTransformers()
|
||||
];
|
||||
|
||||
static const List<TextTransformer> standardTextTransformers = [];
|
||||
}
|
||||
Reference in New Issue
Block a user