Fix appear save draft dialog when reload editing composer

Signed-off-by: dab246 <tdvu@linagora.com>
This commit is contained in:
dab246
2025-04-01 00:50:53 +07:00
committed by Dat H. Pham
parent 9410de9399
commit 0b9bb3530c
13 changed files with 93 additions and 21 deletions
@@ -39,6 +39,7 @@ class DownloadClient {
String cid, String cid,
String fileExtension, String fileExtension,
String fileName, String fileName,
String mimeType,
{ {
String? filePath, String? filePath,
double? maxWidth, double? maxWidth,
@@ -58,10 +59,14 @@ class DownloadClient {
return null; return null;
} }
final imageType = mimeType == 'application/octet-stream'
? 'image/$fileExtension'
: mimeType;
if (PlatformInfo.isWeb) { if (PlatformInfo.isWeb) {
final base64Uri = encodeToBase64Uri({ final base64Uri = encodeToBase64Uri({
'bytesData': bytesData, 'bytesData': bytesData,
'mimeType': 'image/$fileExtension', 'mimeType': imageType,
'cid': cid, 'cid': cid,
'fileName': fileName, 'fileName': fileName,
'maxWidth': maxWidth 'maxWidth': maxWidth
@@ -76,7 +81,7 @@ class DownloadClient {
final base64Uri = await compute(encodeToBase64Uri, { final base64Uri = await compute(encodeToBase64Uri, {
'bytesData': bytesDataCompressed, 'bytesData': bytesDataCompressed,
'mimeType': 'image/$fileExtension', 'mimeType': imageType,
'cid': cid, 'cid': cid,
'fileName': fileName, 'fileName': fileName,
'maxWidth': maxWidth 'maxWidth': maxWidth
@@ -86,7 +91,7 @@ class DownloadClient {
} else { } else {
final base64Uri = await compute(encodeToBase64Uri, { final base64Uri = await compute(encodeToBase64Uri, {
'bytesData': bytesData, 'bytesData': bytesData,
'mimeType': 'image/$fileExtension', 'mimeType': imageType,
'cid': cid, 'cid': cid,
'fileName': fileName, 'fileName': fileName,
'maxWidth': maxWidth 'maxWidth': maxWidth
@@ -113,7 +118,8 @@ class DownloadClient {
if (fileName.contains('.')) { if (fileName.contains('.')) {
fileName = fileName.split('.').first; fileName = fileName.split('.').first;
} }
final base64Uri = '<img src="${HtmlUtils.convertBase64ToImageResourceData(base64Data: base64Data, mimeType: mimeType)}" alt="$fileName" id="cid:$cid" style="max-width: $maxWidth;" />'; mimeType = HtmlUtils.validateHtmlImageResourceMimeType(mimeType);
final base64Uri = '<img src="${HtmlUtils.convertBase64ToImageResourceData(base64Data: base64Data, mimeType: mimeType)}" alt="$fileName" style="max-width: $maxWidth;" data-mimetype="$mimeType" id="cid:$cid" />';
return base64Uri; return base64Uri;
} }
} }
@@ -45,12 +45,13 @@ class ImageTransformer extends DomTransformer {
final src = imageElement.attributes['src']; final src = imageElement.attributes['src'];
if (src == null) return; if (src == null) return;
final mimeType = imageElement.attributes['data-mimetype'];
if (src.startsWith('cid:') && mapUrlDownloadCID != null) { if (src.startsWith('cid:') && mapUrlDownloadCID != null) {
final imageBase64 = await _convertCidToBase64Image( final imageBase64 = await _convertCidToBase64Image(
dioClient: dioClient, dioClient: dioClient,
mapUrlDownloadCID: mapUrlDownloadCID, mapUrlDownloadCID: mapUrlDownloadCID,
imageSource: src imageSource: src,
mimeType: mimeType,
); );
imageElement.attributes['src'] = imageBase64 ?? src; imageElement.attributes['src'] = imageBase64 ?? src;
imageElement.attributes['id'] ??= src; imageElement.attributes['id'] ??= src;
@@ -68,7 +69,8 @@ class ImageTransformer extends DomTransformer {
Future<String?> _convertCidToBase64Image({ Future<String?> _convertCidToBase64Image({
required DioClient dioClient, required DioClient dioClient,
required Map<String, String> mapUrlDownloadCID, required Map<String, String> mapUrlDownloadCID,
required String imageSource required String imageSource,
required String? mimeType,
}) async { }) async {
final cid = imageSource.replaceFirst('cid:', '').trim(); final cid = imageSource.replaceFirst('cid:', '').trim();
final urlDownloadCid = mapUrlDownloadCID[cid]; final urlDownloadCid = mapUrlDownloadCID[cid];
@@ -79,7 +81,8 @@ class ImageTransformer extends DomTransformer {
final imgBase64Uri = await loadAsyncNetworkImageToBase64( final imgBase64Uri = await loadAsyncNetworkImageToBase64(
dioClient, dioClient,
compressFileUtils, compressFileUtils,
urlDownloadCid urlDownloadCid,
mimeType,
); );
if (imgBase64Uri.isEmpty) return null; if (imgBase64Uri.isEmpty) return null;
@@ -90,7 +93,8 @@ class ImageTransformer extends DomTransformer {
Future<String> loadAsyncNetworkImageToBase64( Future<String> loadAsyncNetworkImageToBase64(
DioClient dioClient, DioClient dioClient,
CompressFileUtils compressFileUtils, CompressFileUtils compressFileUtils,
String imageUrl String imageUrl,
String? mimeType,
) async { ) async {
try { try {
var responseData = await dioClient.get( var responseData = await dioClient.get(
@@ -99,10 +103,16 @@ class ImageTransformer extends DomTransformer {
if (responseData != null) { if (responseData != null) {
if (PlatformInfo.isWeb) { if (PlatformInfo.isWeb) {
return encodeToBase64Uri(responseData); return encodeToBase64Uri({
'bytesData': responseData,
'mimeType': mimeType,
});
} else { } else {
final bytesCompressed = await compressFileUtils.compressBytesDataImage(responseData); final bytesCompressed = await compressFileUtils.compressBytesDataImage(responseData);
final base64Uri = await compute(encodeToBase64Uri, bytesCompressed); final base64Uri = await compute(encodeToBase64Uri, {
'bytesData': bytesCompressed,
'mimeType': mimeType,
});
return base64Uri; return base64Uri;
} }
} else { } else {
@@ -113,12 +123,12 @@ class ImageTransformer extends DomTransformer {
} }
} }
static String encodeToBase64Uri(dynamic bytesData) { static String encodeToBase64Uri(Map<String, dynamic> entryParam) {
final base64Data = base64Encode(bytesData); final base64Data = base64Encode(entryParam['bytesData']);
if (!base64Data.endsWith('==')) { if (!base64Data.endsWith('==')) {
base64Data.append('=='); base64Data.append('==');
} }
final base64Uri = 'data:image/jpeg;base64,$base64Data'; final mimeType = entryParam['mimeType'] ?? 'image/jpeg';
return base64Uri; return 'data:$mimeType;base64,$base64Data';
} }
} }
@@ -11,6 +11,7 @@ class StandardizeHtmlSanitizingTransformers extends TextTransformer {
'bgcolor', 'bgcolor',
'id', 'id',
'class', 'class',
'data-mimetype',
]; ];
static const List<String> mailAllowedHtmlTags = [ static const List<String> mailAllowedHtmlTags = [
+1 -4
View File
@@ -99,13 +99,10 @@ class HtmlUtils {
required String base64Data, required String base64Data,
required String mimeType required String mimeType
}) { }) {
log('HtmlUtils::convertBase64ToImageResourceData:');
mimeType = validateHtmlImageResourceMimeType(mimeType);
if (!base64Data.endsWith('==')) { if (!base64Data.endsWith('==')) {
base64Data.append('=='); base64Data.append('==');
} }
final imageResource = 'data:$mimeType;base64,$base64Data'; return 'data:$mimeType;base64,$base64Data';
return imageResource;
} }
static String generateHtmlDocument({ static String generateHtmlDocument({
@@ -27,6 +27,7 @@ class ComposerDataSourceImpl extends ComposerDataSource {
cid, cid,
fileInfo.fileExtension, fileInfo.fileExtension,
fileInfo.fileName, fileInfo.fileName,
fileInfo.mimeType,
filePath: fileInfo.filePath, filePath: fileInfo.filePath,
maxWidth: maxWidth, maxWidth: maxWidth,
compress: compress); compress: compress);
@@ -113,4 +113,14 @@ class ComposerRepositoryImpl extends ComposerRepository {
return Future.value(emailContent); return Future.value(emailContent);
} }
} }
@override
Future<String> removeStyleLazyLoadDisplayInlineImages({required String emailContent}) {
try {
return _htmlDataSource.removeStyleLazyLoadDisplayInlineImages(emailContent: emailContent);
} catch (e) {
logError('ComposerRepositoryImpl::removeStyleLazyLoadDisplayInlineImages: Exception: $e');
return Future.value(emailContent);
}
}
} }
@@ -17,4 +17,6 @@ abstract class ComposerRepository {
Future<String?> downloadImageAsBase64(String url, String cid, FileInfo fileInfo, {double? maxWidth, bool? compress}); Future<String?> downloadImageAsBase64(String url, String cid, FileInfo fileInfo, {double? maxWidth, bool? compress});
Future<String> removeCollapsedExpandedSignatureEffect({required String emailContent}); Future<String> removeCollapsedExpandedSignatureEffect({required String emailContent});
Future<String> removeStyleLazyLoadDisplayInlineImages({required String emailContent});
} }
@@ -1112,6 +1112,13 @@ class ComposerController extends BaseController
emailContent = await _composerRepository.removeCollapsedExpandedSignatureEffect( emailContent = await _composerRepository.removeCollapsedExpandedSignatureEffect(
emailContent: emailContent, emailContent: emailContent,
); );
if (emailIdEditing != null &&
savedActionType == EmailActionType.compose &&
currentEmailActionType == EmailActionType.reopenComposerBrowser) {
emailContent = await _composerRepository.removeStyleLazyLoadDisplayInlineImages(
emailContent: emailContent,
);
}
final savedEmailDraft = SavedEmailDraft( final savedEmailDraft = SavedEmailDraft(
subject: subjectEmail.value ?? '', subject: subjectEmail.value ?? '',
@@ -282,8 +282,9 @@ class RichTextWebController extends BaseRichTextController {
void insertImageAsBase64({required PlatformFile platformFile, int? maxWidth}) { void insertImageAsBase64({required PlatformFile platformFile, int? maxWidth}) {
if (platformFile.bytes != null) { if (platformFile.bytes != null) {
final base64Data = base64Encode(platformFile.bytes!); final base64Data = base64Encode(platformFile.bytes!);
final mimeType = HtmlUtils.validateHtmlImageResourceMimeType('image/${platformFile.extension}');
editorController.insertHtml( editorController.insertHtml(
'<img src="${HtmlUtils.convertBase64ToImageResourceData(base64Data: base64Data, mimeType: 'image/${platformFile.extension}')}" data-filename="${platformFile.name}" alt="Image in my signature" style="max-width: ${maxWidth != null ? '${maxWidth}px' : '100%'};"/>' '<img src="${HtmlUtils.convertBase64ToImageResourceData(base64Data: base64Data, mimeType: mimeType)}" data-filename="${platformFile.name}" alt="Image in my signature" style="max-width: ${maxWidth != null ? '${maxWidth}px' : '100%'};" data-mimetype="$mimeType"/>'
); );
} else { } else {
logError("RichTextWebController::insertImageAsBase64: bytes is null"); logError("RichTextWebController::insertImageAsBase64: bytes is null");
@@ -23,4 +23,6 @@ abstract class HtmlDataSource {
}); });
Future<String> removeCollapsedExpandedSignatureEffect({required String emailContent}); Future<String> removeCollapsedExpandedSignatureEffect({required String emailContent});
Future<String> removeStyleLazyLoadDisplayInlineImages({required String emailContent});
} }
@@ -62,4 +62,13 @@ class HtmlDataSourceImpl extends HtmlDataSource {
); );
}).catchError(_exceptionThrower.throwException); }).catchError(_exceptionThrower.throwException);
} }
@override
Future<String> removeStyleLazyLoadDisplayInlineImages({required String emailContent}) {
return Future.sync(() async {
return await _htmlAnalyzer.removeStyleLazyLoadDisplayInlineImages(
emailContent: emailContent,
);
}).catchError(_exceptionThrower.throwException);
}
} }
@@ -237,4 +237,30 @@ class HtmlAnalyzer {
return null; return null;
} }
} }
Future<String> 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;
}
}
} }
@@ -186,7 +186,7 @@ class PublicAssetController extends BaseController {
newlyPickedPublicAssetIds.add(publicAsset.id!); newlyPickedPublicAssetIds.add(publicAsset.id!);
final imageTag = '<img ' final imageTag = '<img '
'src="${publicAsset.publicURI!}" ' 'src="${publicAsset.publicURI!}" '
'style="max-width: 100%" ' 'style="max-width: 100%;" '
'public-asset-id="${publicAsset.id!.value}">'; 'public-asset-id="${publicAsset.id!.value}">';
if (PlatformInfo.isWeb) { if (PlatformInfo.isWeb) {
Get Get