diff --git a/core/lib/data/network/download/download_client.dart b/core/lib/data/network/download/download_client.dart index 8686739f1..e3e6461d8 100644 --- a/core/lib/data/network/download/download_client.dart +++ b/core/lib/data/network/download/download_client.dart @@ -39,6 +39,7 @@ class DownloadClient { String cid, String fileExtension, String fileName, + String mimeType, { String? filePath, double? maxWidth, @@ -58,10 +59,14 @@ class DownloadClient { return null; } + final imageType = mimeType == 'application/octet-stream' + ? 'image/$fileExtension' + : mimeType; + if (PlatformInfo.isWeb) { final base64Uri = encodeToBase64Uri({ 'bytesData': bytesData, - 'mimeType': 'image/$fileExtension', + 'mimeType': imageType, 'cid': cid, 'fileName': fileName, 'maxWidth': maxWidth @@ -76,7 +81,7 @@ class DownloadClient { final base64Uri = await compute(encodeToBase64Uri, { 'bytesData': bytesDataCompressed, - 'mimeType': 'image/$fileExtension', + 'mimeType': imageType, 'cid': cid, 'fileName': fileName, 'maxWidth': maxWidth @@ -86,7 +91,7 @@ class DownloadClient { } else { final base64Uri = await compute(encodeToBase64Uri, { 'bytesData': bytesData, - 'mimeType': 'image/$fileExtension', + 'mimeType': imageType, 'cid': cid, 'fileName': fileName, 'maxWidth': maxWidth @@ -113,7 +118,8 @@ class DownloadClient { if (fileName.contains('.')) { fileName = fileName.split('.').first; } - final base64Uri = '$fileName'; + mimeType = HtmlUtils.validateHtmlImageResourceMimeType(mimeType); + final base64Uri = '$fileName'; return base64Uri; } } \ No newline at end of file diff --git a/core/lib/presentation/utils/html_transformer/dom/image_transformers.dart b/core/lib/presentation/utils/html_transformer/dom/image_transformers.dart index f0dd38661..140df0bed 100644 --- a/core/lib/presentation/utils/html_transformer/dom/image_transformers.dart +++ b/core/lib/presentation/utils/html_transformer/dom/image_transformers.dart @@ -45,12 +45,13 @@ class ImageTransformer extends DomTransformer { final src = imageElement.attributes['src']; if (src == null) return; - + final mimeType = imageElement.attributes['data-mimetype']; if (src.startsWith('cid:') && mapUrlDownloadCID != null) { final imageBase64 = await _convertCidToBase64Image( dioClient: dioClient, mapUrlDownloadCID: mapUrlDownloadCID, - imageSource: src + imageSource: src, + mimeType: mimeType, ); imageElement.attributes['src'] = imageBase64 ?? src; imageElement.attributes['id'] ??= src; @@ -68,7 +69,8 @@ class ImageTransformer extends DomTransformer { Future _convertCidToBase64Image({ required DioClient dioClient, required Map mapUrlDownloadCID, - required String imageSource + required String imageSource, + required String? mimeType, }) async { final cid = imageSource.replaceFirst('cid:', '').trim(); final urlDownloadCid = mapUrlDownloadCID[cid]; @@ -79,7 +81,8 @@ class ImageTransformer extends DomTransformer { final imgBase64Uri = await loadAsyncNetworkImageToBase64( dioClient, compressFileUtils, - urlDownloadCid + urlDownloadCid, + mimeType, ); if (imgBase64Uri.isEmpty) return null; @@ -90,7 +93,8 @@ class ImageTransformer extends DomTransformer { Future loadAsyncNetworkImageToBase64( DioClient dioClient, CompressFileUtils compressFileUtils, - String imageUrl + String imageUrl, + String? mimeType, ) async { try { var responseData = await dioClient.get( @@ -99,10 +103,16 @@ class ImageTransformer extends DomTransformer { if (responseData != null) { if (PlatformInfo.isWeb) { - return encodeToBase64Uri(responseData); + return encodeToBase64Uri({ + 'bytesData': responseData, + 'mimeType': mimeType, + }); } else { final bytesCompressed = await compressFileUtils.compressBytesDataImage(responseData); - final base64Uri = await compute(encodeToBase64Uri, bytesCompressed); + final base64Uri = await compute(encodeToBase64Uri, { + 'bytesData': bytesCompressed, + 'mimeType': mimeType, + }); return base64Uri; } } else { @@ -113,12 +123,12 @@ class ImageTransformer extends DomTransformer { } } - static String encodeToBase64Uri(dynamic bytesData) { - final base64Data = base64Encode(bytesData); + static String encodeToBase64Uri(Map entryParam) { + final base64Data = base64Encode(entryParam['bytesData']); if (!base64Data.endsWith('==')) { base64Data.append('=='); } - final base64Uri = 'data:image/jpeg;base64,$base64Data'; - return base64Uri; + final mimeType = entryParam['mimeType'] ?? 'image/jpeg'; + return 'data:$mimeType;base64,$base64Data'; } } \ No newline at end of file diff --git a/core/lib/presentation/utils/html_transformer/text/standardize_html_sanitizing_transformers.dart b/core/lib/presentation/utils/html_transformer/text/standardize_html_sanitizing_transformers.dart index 9ff53ac2a..2c117e151 100644 --- a/core/lib/presentation/utils/html_transformer/text/standardize_html_sanitizing_transformers.dart +++ b/core/lib/presentation/utils/html_transformer/text/standardize_html_sanitizing_transformers.dart @@ -11,6 +11,7 @@ class StandardizeHtmlSanitizingTransformers extends TextTransformer { 'bgcolor', 'id', 'class', + 'data-mimetype', ]; static const List mailAllowedHtmlTags = [ diff --git a/core/lib/utils/html/html_utils.dart b/core/lib/utils/html/html_utils.dart index 4bc132aa0..71a465d22 100644 --- a/core/lib/utils/html/html_utils.dart +++ b/core/lib/utils/html/html_utils.dart @@ -99,13 +99,10 @@ class HtmlUtils { required String base64Data, required String mimeType }) { - log('HtmlUtils::convertBase64ToImageResourceData:'); - mimeType = validateHtmlImageResourceMimeType(mimeType); if (!base64Data.endsWith('==')) { base64Data.append('=='); } - final imageResource = 'data:$mimeType;base64,$base64Data'; - return imageResource; + return 'data:$mimeType;base64,$base64Data'; } static String generateHtmlDocument({ diff --git a/lib/features/composer/data/datasource_impl/composer_datasource_impl.dart b/lib/features/composer/data/datasource_impl/composer_datasource_impl.dart index deef7c58e..4248bd9a6 100644 --- a/lib/features/composer/data/datasource_impl/composer_datasource_impl.dart +++ b/lib/features/composer/data/datasource_impl/composer_datasource_impl.dart @@ -27,6 +27,7 @@ class ComposerDataSourceImpl extends ComposerDataSource { cid, fileInfo.fileExtension, fileInfo.fileName, + fileInfo.mimeType, filePath: fileInfo.filePath, maxWidth: maxWidth, compress: compress); diff --git a/lib/features/composer/data/repository/composer_repository_impl.dart b/lib/features/composer/data/repository/composer_repository_impl.dart index ba5160561..a370f24c9 100644 --- a/lib/features/composer/data/repository/composer_repository_impl.dart +++ b/lib/features/composer/data/repository/composer_repository_impl.dart @@ -113,4 +113,14 @@ class ComposerRepositoryImpl extends ComposerRepository { return Future.value(emailContent); } } + + @override + Future removeStyleLazyLoadDisplayInlineImages({required String emailContent}) { + try { + return _htmlDataSource.removeStyleLazyLoadDisplayInlineImages(emailContent: emailContent); + } catch (e) { + logError('ComposerRepositoryImpl::removeStyleLazyLoadDisplayInlineImages: Exception: $e'); + return Future.value(emailContent); + } + } } \ No newline at end of file diff --git a/lib/features/composer/domain/repository/composer_repository.dart b/lib/features/composer/domain/repository/composer_repository.dart index 5a28d848d..38fdd16ea 100644 --- a/lib/features/composer/domain/repository/composer_repository.dart +++ b/lib/features/composer/domain/repository/composer_repository.dart @@ -17,4 +17,6 @@ abstract class ComposerRepository { Future downloadImageAsBase64(String url, String cid, FileInfo fileInfo, {double? maxWidth, bool? compress}); Future removeCollapsedExpandedSignatureEffect({required String emailContent}); + + Future removeStyleLazyLoadDisplayInlineImages({required String emailContent}); } \ No newline at end of file diff --git a/lib/features/composer/presentation/composer_controller.dart b/lib/features/composer/presentation/composer_controller.dart index 0be843b27..91b0c7757 100644 --- a/lib/features/composer/presentation/composer_controller.dart +++ b/lib/features/composer/presentation/composer_controller.dart @@ -1112,6 +1112,13 @@ class ComposerController extends BaseController emailContent = await _composerRepository.removeCollapsedExpandedSignatureEffect( emailContent: emailContent, ); + if (emailIdEditing != null && + savedActionType == EmailActionType.compose && + currentEmailActionType == EmailActionType.reopenComposerBrowser) { + emailContent = await _composerRepository.removeStyleLazyLoadDisplayInlineImages( + emailContent: emailContent, + ); + } final savedEmailDraft = SavedEmailDraft( subject: subjectEmail.value ?? '', diff --git a/lib/features/composer/presentation/controller/rich_text_web_controller.dart b/lib/features/composer/presentation/controller/rich_text_web_controller.dart index d4b1e61fd..2eff51dff 100644 --- a/lib/features/composer/presentation/controller/rich_text_web_controller.dart +++ b/lib/features/composer/presentation/controller/rich_text_web_controller.dart @@ -282,8 +282,9 @@ class RichTextWebController extends BaseRichTextController { void insertImageAsBase64({required PlatformFile platformFile, int? maxWidth}) { if (platformFile.bytes != null) { final base64Data = base64Encode(platformFile.bytes!); + final mimeType = HtmlUtils.validateHtmlImageResourceMimeType('image/${platformFile.extension}'); editorController.insertHtml( - 'Image in my signature' + 'Image in my signature' ); } else { logError("RichTextWebController::insertImageAsBase64: bytes is null"); diff --git a/lib/features/email/data/datasource/html_datasource.dart b/lib/features/email/data/datasource/html_datasource.dart index ec9220ed7..9fccbb094 100644 --- a/lib/features/email/data/datasource/html_datasource.dart +++ b/lib/features/email/data/datasource/html_datasource.dart @@ -23,4 +23,6 @@ abstract class HtmlDataSource { }); Future removeCollapsedExpandedSignatureEffect({required String emailContent}); + + Future removeStyleLazyLoadDisplayInlineImages({required String emailContent}); } \ No newline at end of file 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 e5e02faa7..b6cd9659e 100644 --- a/lib/features/email/data/datasource_impl/html_datasource_impl.dart +++ b/lib/features/email/data/datasource_impl/html_datasource_impl.dart @@ -62,4 +62,13 @@ class HtmlDataSourceImpl extends HtmlDataSource { ); }).catchError(_exceptionThrower.throwException); } + + @override + Future removeStyleLazyLoadDisplayInlineImages({required String emailContent}) { + return Future.sync(() async { + return await _htmlAnalyzer.removeStyleLazyLoadDisplayInlineImages( + emailContent: emailContent, + ); + }).catchError(_exceptionThrower.throwException); + } } \ No newline at end of file diff --git a/lib/features/email/data/local/html_analyzer.dart b/lib/features/email/data/local/html_analyzer.dart index 0e14bec5b..7ece84d17 100644 --- a/lib/features/email/data/local/html_analyzer.dart +++ b/lib/features/email/data/local/html_analyzer.dart @@ -237,4 +237,30 @@ class HtmlAnalyzer { return null; } } + + Future removeStyleLazyLoadDisplayInlineImages({required String emailContent}) async { + try { + final document = parse(emailContent); + final imgElements = document.querySelectorAll('img[style], img[loading]'); + await Future.wait(imgElements.map((img) async { + String? style = img.attributes['style']; + if (style != null) { + style = style.replaceAll(RegExp(r'display\s*:\s*inline;?'), '').trim(); + if (style.isEmpty) { + img.attributes.remove('style'); + } else { + img.attributes['style'] = style; + } + } + + if (img.attributes['loading'] == 'lazy') { + img.attributes.remove('loading'); + } + })); + return document.body?.innerHtml ?? emailContent; + } catch (e) { + logError('HtmlAnalyzer::removeStyleLazyLoadDisplayInlineImages:Exception = $e'); + return emailContent; + } + } } \ No newline at end of file diff --git a/lib/features/public_asset/presentation/public_asset_controller.dart b/lib/features/public_asset/presentation/public_asset_controller.dart index c66208475..f79d6b79f 100644 --- a/lib/features/public_asset/presentation/public_asset_controller.dart +++ b/lib/features/public_asset/presentation/public_asset_controller.dart @@ -186,7 +186,7 @@ class PublicAssetController extends BaseController { newlyPickedPublicAssetIds.add(publicAsset.id!); final imageTag = ''; if (PlatformInfo.isWeb) { Get