TF-2160 Fix email content is cut off

(cherry picked from commit 045cd382add2538858b24491b41bd597371239c8)
This commit is contained in:
dab246
2023-09-26 15:25:29 +07:00
committed by Dat H. Pham
parent c9d84a1981
commit be7eada7b7
17 changed files with 57 additions and 118 deletions
@@ -12,8 +12,8 @@ abstract class DomTransformer {
/// All changes will be visible to subsequent transformers.
Future<void> process({
required Document document,
required DioClient dioClient,
Map<String, String>? mapUrlDownloadCID,
DioClient? dioClient,
});
/// Adds a HEAD element if necessary
@@ -8,8 +8,8 @@ class AddTargetBlankInTagATransformer extends DomTransformer {
@override
Future<void> process({
required Document document,
required DioClient dioClient,
Map<String, String>? mapUrlDownloadCID,
DioClient? dioClient,
}) async {
final elements = document.querySelectorAll('a');
await Future.wait(elements.map((element) async {
@@ -11,8 +11,8 @@ class AddTooltipLinkTransformer extends DomTransformer {
@override
Future<void> process({
required Document document,
required DioClient dioClient,
Map<String, String>? mapUrlDownloadCID,
DioClient? dioClient
}) async {
final linkElements = document.querySelectorAll('a[href^="http"]');
await Future.wait(linkElements.map((linkElement) async {
@@ -10,8 +10,8 @@ class BlockCodeTransformer extends DomTransformer {
@override
Future<void> process({
required Document document,
required DioClient dioClient,
Map<String, String>? mapUrlDownloadCID,
DioClient? dioClient
}) async {
final codeElements = document.getElementsByTagName('pre');
await Future.wait(codeElements.map((element) async {
@@ -10,8 +10,8 @@ class BlockQuotedTransformer extends DomTransformer {
@override
Future<void> process({
required Document document,
required DioClient dioClient,
Map<String, String>? mapUrlDownloadCID,
DioClient? dioClient
}) async {
final quotedElements = document.getElementsByTagName('blockquote');
await Future.wait(quotedElements.map((quotedElement) async {
@@ -12,29 +12,33 @@ import 'package:html/dom.dart';
class ImageTransformer extends DomTransformer {
final bool useLoadingAttribute;
const ImageTransformer({this.useLoadingAttribute = false});
const ImageTransformer();
@override
Future<void> process({
required Document document,
required DioClient dioClient,
Map<String, String>? mapUrlDownloadCID,
DioClient? dioClient
}) async {
final imageElements = document.querySelectorAll('img');
await Future.wait(imageElements.map((imageElement) async {
final exStyle = imageElement.attributes['style'];
var exStyle = imageElement.attributes['style'];
if (exStyle != null) {
imageElement.attributes['style'] = '$exStyle display: inline;max-width: 100%;';
if (!exStyle.contains('display')) {
exStyle = '$exStyle display:inline;';
}
if (!exStyle.contains('max-width')) {
exStyle = '$exStyle max-width:100%;';
}
imageElement.attributes['style'] = exStyle;
} else {
imageElement.attributes['style'] = 'display: inline;max-width: 100%;';
imageElement.attributes['style'] = 'display:inline;max-width:100%;';
}
final src = imageElement.attributes['src'];
if (src == null) return;
if (src.startsWith('cid:') && dioClient != null && mapUrlDownloadCID != null) {
if (src.startsWith('cid:') && mapUrlDownloadCID != null) {
final imageBase64 = await _convertCidToBase64Image(
dioClient: dioClient,
mapUrlDownloadCID: mapUrlDownloadCID,
@@ -42,18 +46,8 @@ class ImageTransformer extends DomTransformer {
);
imageElement.attributes['src'] = imageBase64 ?? src;
} else if (src.startsWith('https://') || src.startsWith('http://')) {
if (useLoadingAttribute) {
if (!imageElement.attributes.containsKey('loading')) {
imageElement.attributes['loading'] = 'lazy';
} else {
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');
}
}
}));
@@ -12,8 +12,8 @@ class RemoveTooltipLinkTransformer extends DomTransformer {
@override
Future<void> process({
required Document document,
required DioClient dioClient,
Map<String, String>? mapUrlDownloadCID,
DioClient? dioClient
}) async {
final linkElements = document.querySelectorAll('a.$nameClassToolTip');
await Future.wait(linkElements.map((linkElement) async {
@@ -1,32 +0,0 @@
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';
}));
}
}
@@ -10,8 +10,8 @@ class RemoveScriptTransformer extends DomTransformer {
@override
Future<void> process({
required Document document,
required DioClient dioClient,
Map<String, String>? mapUrlDownloadCID,
DioClient? dioClient
}) async {
final scriptElements = document.getElementsByTagName('script');
await Future.wait(scriptElements.map((scriptElement) async {
@@ -10,8 +10,8 @@ class SignatureTransformer extends DomTransformer {
@override
Future<void> process({
required Document document,
required DioClient dioClient,
Map<String, String>? mapUrlDownloadCID,
DioClient? dioClient
}) async {
final signatureElements = document.querySelectorAll('div.tmail-signature');
await Future.wait(signatureElements.map((element) async {
@@ -79,6 +79,7 @@ class HtmlUtils {
// Replace the placeholder with the actual image source
img.src = src;
img.removeAttribute("data-src");
// Stop observing the image
observer.unobserve(img);
@@ -27,8 +27,8 @@ class MessageContentTransformer {
..._configuration.domTransformers.map((domTransformer) async =>
domTransformer.process(
document: document,
dioClient: _dioClient,
mapUrlDownloadCID: mapUrlDownloadCID,
dioClient: _dioClient
)
)
]);
@@ -7,7 +7,6 @@ import 'package:core/presentation/utils/html_transformer/dom/blockcode_transform
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';
@@ -29,51 +28,19 @@ class TransformConfiguration {
this.textTransformers
);
factory TransformConfiguration.forReplyForwardEmail() => TransformConfiguration.create(
customDomTransformers: [
const ReplaceLazyLoadImageTransformer(),
if (PlatformInfo.isWeb)
const RemoveTooltipLinkTransformer(),
const SignatureTransformer(),
]
);
factory TransformConfiguration.fromDomTransformers(List<DomTransformer> domTransformers) => TransformConfiguration(domTransformers, []);
factory TransformConfiguration.forDraftsEmail() => TransformConfiguration.create(
customDomTransformers: [
const RemoveScriptTransformer(),
const BlockQuotedTransformer(),
const BlockCodeTransformer(),
const AddTargetBlankInTagATransformer(),
const ImageTransformer(useLoadingAttribute: true),
]
);
factory TransformConfiguration.empty() => const TransformConfiguration([], []);
factory TransformConfiguration.forComposeEmailPlatformWeb() => TransformConfiguration.create(
customDomTransformers: [
const RemoveScriptTransformer(),
const BlockQuotedTransformer(),
const BlockCodeTransformer(),
const AddTargetBlankInTagATransformer(),
const ImageTransformer(useLoadingAttribute: true),
const SignatureTransformer(),
]
);
factory TransformConfiguration.forReplyForwardEmail() => TransformConfiguration.fromDomTransformers([
if (PlatformInfo.isWeb)
const RemoveTooltipLinkTransformer(),
const SignatureTransformer(),
]);
factory TransformConfiguration.forComposeEmail() => TransformConfiguration.create(
customDomTransformers: [
const RemoveScriptTransformer(),
const BlockQuotedTransformer(),
const BlockCodeTransformer(),
const AddTargetBlankInTagATransformer(),
const ImageTransformer(),
const SignatureTransformer(),
],
customTextTransformers: [
const SanitizeAutolinkHtmlTransformers()
]
);
factory TransformConfiguration.forDraftsEmail() => TransformConfiguration.empty();
factory TransformConfiguration.forPreviewEmailPlatformWeb() => TransformConfiguration.create(
factory TransformConfiguration.forPreviewEmailOnWeb() => TransformConfiguration.create(
customDomTransformers: [
const RemoveScriptTransformer(),
const BlockQuotedTransformer(),
@@ -84,6 +51,8 @@ class TransformConfiguration {
]
);
factory TransformConfiguration.forPreviewEmail() => TransformConfiguration.standardConfiguration;
/// Provides easy access to a standard configuration that does not block external images.
static const TransformConfiguration standardConfiguration = TransformConfiguration(
standardDomTransformers,
@@ -1,5 +1,6 @@
import 'dart:async';
import 'dart:io';
import 'dart:math';
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/utils/html_transformer/html_event_action.dart';
@@ -54,7 +55,6 @@ class HtmlContentViewer extends StatefulWidget {
class _HtmlContentViewState extends State<HtmlContentViewer> {
late double actualHeight;
double minHeight = 100;
double minWidth = 300;
String? _htmlData;
late InAppWebViewController _webViewController;
@@ -108,6 +108,7 @@ class _HtmlContentViewState extends State<HtmlContentViewer> {
widget.onCreated?.call(controller);
},
onLoadStop: _onLoadStop,
onContentSizeChanged: _onContentSizeChanged,
shouldOverrideUrlLoading: _shouldOverrideUrlLoading,
gestureRecognizers: {
Factory<LongPressGestureRecognizer>(() => LongPressGestureRecognizer()),
@@ -152,6 +153,21 @@ class _HtmlContentViewState extends State<HtmlContentViewer> {
);
}
void _onContentSizeChanged(
InAppWebViewController controller,
Size oldContentSize,
Size newContentSize
) async {
log('_HtmlContentViewState::_onContentSizeChanged:oldContentSize: $oldContentSize | newContentSize: $newContentSize');
final maxContentHeight = max(oldContentSize.height, newContentSize.height);
log('_HtmlContentViewState::_onContentSizeChanged:maxContentHeight: $maxContentHeight');
if (maxContentHeight > actualHeight) {
setState(() {
actualHeight = maxContentHeight;
});
}
}
void _onHandleScrollEvent(List<dynamic> parameters) {
log('_HtmlContentViewState::_onHandleScrollRightEvent():parameters: $parameters');
final message = parameters.first;
@@ -167,13 +183,11 @@ class _HtmlContentViewState extends State<HtmlContentViewer> {
final scrollHeight = await _webViewController.evaluateJavascript(source: 'document.body.scrollHeight');
if (scrollHeight != null && mounted) {
final scrollHeightWithBuffer = scrollHeight + 30.0;
if (scrollHeightWithBuffer > minHeight) {
if (scrollHeightWithBuffer > actualHeight) {
setState(() {
actualHeight = scrollHeightWithBuffer;
_isLoading = false;
});
} else {
actualHeight = minHeight;
}
}
}
@@ -1297,19 +1297,12 @@ class ComposerController extends BaseController {
final session = mailboxDashBoardController.sessionCurrent;
final accountId = mailboxDashBoardController.accountId.value;
if (session != null && accountId != null) {
TransformConfiguration transformConfiguration = TransformConfiguration.forComposeEmail();
if (isDraftEmail) {
transformConfiguration = TransformConfiguration.forDraftsEmail();
} else if (PlatformInfo.isWeb) {
transformConfiguration = TransformConfiguration.forComposeEmailPlatformWeb();
}
consumeState(_getEmailContentInteractor.execute(
session,
accountId,
emailId,
mailboxDashBoardController.baseDownloadUrl,
transformConfiguration
TransformConfiguration.empty()
));
}
}
@@ -69,11 +69,11 @@ class GetListDetailedEmailByIdInteractor {
accountId: accountId,
downloadUrl: baseDownloadUrl
);
TransformConfiguration transformConfiguration = TransformConfiguration.standardConfiguration;
TransformConfiguration transformConfiguration = TransformConfiguration.forPreviewEmail();
if (email.isDraft) {
transformConfiguration = TransformConfiguration.forDraftsEmail();
} else if (PlatformInfo.isWeb) {
transformConfiguration = TransformConfiguration.forPreviewEmailPlatformWeb();
transformConfiguration = TransformConfiguration.forPreviewEmailOnWeb();
}
final newEmailContents = await _emailRepository.transformEmailContent(
email.emailContentList,
@@ -393,8 +393,8 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
if (session != null && accountId != null) {
final baseDownloadUrl = mailboxDashBoardController.sessionCurrent?.getDownloadUrl(jmapUrl: _dynamicUrlInterceptors.jmapUrl) ?? '';
TransformConfiguration transformConfiguration = PlatformInfo.isWeb
? TransformConfiguration.forPreviewEmailPlatformWeb()
: TransformConfiguration.standardConfiguration;
? TransformConfiguration.forPreviewEmailOnWeb()
: TransformConfiguration.forPreviewEmail();
consumeState(_getEmailContentInteractor.execute(
session,