TF-2160 Fix email content is cut off
(cherry picked from commit 045cd382add2538858b24491b41bd597371239c8)
This commit is contained in:
@@ -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
|
||||
|
||||
+1
-1
@@ -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');
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
+1
-1
@@ -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 {
|
||||
|
||||
-32
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user