Fix cannot copy & paste base64 image as inline image in composer web
This commit is contained in:
@@ -15,6 +15,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:html_editor_enhanced/html_editor.dart';
|
||||
import 'package:jmap_dart_client/jmap/account_id.dart';
|
||||
import 'package:jmap_dart_client/jmap/identities/identity.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
|
||||
@@ -86,6 +87,7 @@ import 'package:tmail_ui_user/features/server_settings/domain/usecases/get_alway
|
||||
import 'package:tmail_ui_user/features/upload/domain/exceptions/pick_file_exception.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/extensions/list_file_info_extension.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/extensions/file_info_extension.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/extensions/list_file_upload_extension.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/model/upload_task_id.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/state/attachment_upload_state.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/state/local_file_picker_state.dart';
|
||||
@@ -2136,11 +2138,7 @@ class ComposerController extends BaseController with DragDropFileMixin implement
|
||||
required DropDoneDetails details,
|
||||
required double maxWidth
|
||||
}) async {
|
||||
if (responsiveUtils.isMobile(context)) {
|
||||
maxWithEditor = maxWidth - 40;
|
||||
} else {
|
||||
maxWithEditor = maxWidth - 70;
|
||||
}
|
||||
_setUpMaxWidthInlineImage(context: context, maxWidth: maxWidth);
|
||||
|
||||
final listFileInfo = await onDragDone(context: context, details: details);
|
||||
|
||||
@@ -2152,13 +2150,9 @@ class ComposerController extends BaseController with DragDropFileMixin implement
|
||||
return;
|
||||
}
|
||||
|
||||
final listAttachments = listFileInfo
|
||||
.where((fileInfo) => fileInfo.isInline != true)
|
||||
.toList();
|
||||
|
||||
uploadController.validateTotalSizeAttachmentsBeforeUpload(
|
||||
totalSizePreparedFiles: listFileInfo.totalSize,
|
||||
totalSizePreparedFilesWithDispositionAttachment: listAttachments.totalSize,
|
||||
totalSizePreparedFilesWithDispositionAttachment: listFileInfo.listAttachmentFiles.totalSize,
|
||||
onValidationSuccess: () => _uploadAttachmentsAction(pickedFiles: listFileInfo)
|
||||
);
|
||||
}
|
||||
@@ -2341,4 +2335,43 @@ class ComposerController extends BaseController with DragDropFileMixin implement
|
||||
await _saveComposerCacheOnWebAction();
|
||||
}
|
||||
}
|
||||
|
||||
void _setUpMaxWidthInlineImage({
|
||||
required BuildContext context,
|
||||
required double maxWidth
|
||||
}) {
|
||||
if (responsiveUtils.isMobile(context)) {
|
||||
maxWithEditor = maxWidth - 40;
|
||||
} else {
|
||||
maxWithEditor = maxWidth - 70;
|
||||
}
|
||||
}
|
||||
|
||||
void handleOnPasteImageSuccessAction({
|
||||
required BuildContext context,
|
||||
required double maxWidth,
|
||||
required List<FileUpload> listFileUpload
|
||||
}) {
|
||||
log('ComposerController::handleOnPasteImageSuccessAction: listFileUpload = ${listFileUpload.length}');
|
||||
_setUpMaxWidthInlineImage(context: context, maxWidth: maxWidth);
|
||||
|
||||
final listFileInfo = listFileUpload.toListFileInfo();
|
||||
|
||||
uploadController.validateTotalSizeAttachmentsBeforeUpload(
|
||||
totalSizePreparedFiles: listFileInfo.totalSize,
|
||||
onValidationSuccess: () => _uploadAttachmentsAction(pickedFiles: listFileInfo)
|
||||
);
|
||||
}
|
||||
|
||||
void handleOnPasteImageFailureAction({
|
||||
required BuildContext context,
|
||||
List<FileUpload>? listFileUpload,
|
||||
String? base64,
|
||||
required UploadError uploadError
|
||||
}) {
|
||||
logError('ComposerController::handleOnPasteImageFailureAction: $uploadError');
|
||||
appToast.showToastErrorMessage(
|
||||
context,
|
||||
AppLocalizations.of(context).thisImageCannotBePastedIntoTheEditor);
|
||||
}
|
||||
}
|
||||
@@ -190,6 +190,18 @@ class ComposerView extends GetWidget<ComposerController> {
|
||||
width: constraints.maxWidth,
|
||||
height: constraints.maxHeight,
|
||||
onDragEnter: controller.handleOnDragEnterHtmlEditorWeb,
|
||||
onPasteImageSuccessAction: (listFileUpload) => controller.handleOnPasteImageSuccessAction(
|
||||
context: context,
|
||||
maxWidth: constraintsEditor.maxWidth,
|
||||
listFileUpload: listFileUpload
|
||||
),
|
||||
onPasteImageFailureAction: (listFileUpload, base64, uploadError) =>
|
||||
controller.handleOnPasteImageFailureAction(
|
||||
context: context,
|
||||
listFileUpload: listFileUpload,
|
||||
base64: base64,
|
||||
uploadError: uploadError
|
||||
),
|
||||
)),
|
||||
),
|
||||
),
|
||||
@@ -430,6 +442,18 @@ class ComposerView extends GetWidget<ComposerController> {
|
||||
width: constraints.maxWidth,
|
||||
height: constraints.maxHeight,
|
||||
onDragEnter: controller.handleOnDragEnterHtmlEditorWeb,
|
||||
onPasteImageSuccessAction: (listFileUpload) => controller.handleOnPasteImageSuccessAction(
|
||||
context: context,
|
||||
maxWidth: constraintsEditor.maxWidth,
|
||||
listFileUpload: listFileUpload
|
||||
),
|
||||
onPasteImageFailureAction: (listFileUpload, base64, uploadError) =>
|
||||
controller.handleOnPasteImageFailureAction(
|
||||
context: context,
|
||||
listFileUpload: listFileUpload,
|
||||
base64: base64,
|
||||
uploadError: uploadError
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
@@ -688,6 +712,18 @@ class ComposerView extends GetWidget<ComposerController> {
|
||||
width: constraints.maxWidth,
|
||||
height: constraints.maxHeight,
|
||||
onDragEnter: controller.handleOnDragEnterHtmlEditorWeb,
|
||||
onPasteImageSuccessAction: (listFileUpload) => controller.handleOnPasteImageSuccessAction(
|
||||
context: context,
|
||||
maxWidth: constraintsBody.maxWidth,
|
||||
listFileUpload: listFileUpload
|
||||
),
|
||||
onPasteImageFailureAction: (listFileUpload, base64, uploadError) =>
|
||||
controller.handleOnPasteImageFailureAction(
|
||||
context: context,
|
||||
listFileUpload: listFileUpload,
|
||||
base64: base64,
|
||||
uploadError: uploadError
|
||||
),
|
||||
)),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import 'dart:convert' as convert;
|
||||
import 'dart:typed_data' as type_data;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:html_editor_enhanced/utils/file_upload_model.dart';
|
||||
import 'package:model/upload/file_info.dart';
|
||||
|
||||
@@ -23,22 +20,17 @@ extension FileUploadExtension on FileUpload {
|
||||
return base64;
|
||||
}
|
||||
|
||||
Future<FileInfo?> toFileInfo() async {
|
||||
FileInfo? toFileInfo() {
|
||||
if (base64Data != null) {
|
||||
final bytes = await compute(convertBase64ToBytes, base64Data!);
|
||||
return FileInfo.fromBytes(
|
||||
bytes: bytes,
|
||||
bytes: convert.base64Decode(base64Data!),
|
||||
name: name,
|
||||
size: size,
|
||||
type: type,
|
||||
isInline: true
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static Uint8List convertBase64ToBytes(String base64) {
|
||||
type_data.Uint8List decodeBytes = convert.base64Decode(base64);
|
||||
return decodeBytes;
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,6 @@ import 'package:tmail_ui_user/features/email/domain/state/transform_html_email_c
|
||||
import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart';
|
||||
import 'package:tmail_ui_user/main/utils/app_utils.dart';
|
||||
|
||||
typedef OnDragEnterListener = Function(List<dynamic>? types);
|
||||
|
||||
class WebEditorView extends StatelessWidget with EditorViewMixin {
|
||||
|
||||
final HtmlEditorController editorController;
|
||||
@@ -33,6 +31,8 @@ class WebEditorView extends StatelessWidget with EditorViewMixin {
|
||||
final double? width;
|
||||
final double? height;
|
||||
final OnDragEnterListener? onDragEnter;
|
||||
final OnPasteImageSuccessAction? onPasteImageSuccessAction;
|
||||
final OnPasteImageFailureAction? onPasteImageFailureAction;
|
||||
|
||||
const WebEditorView({
|
||||
super.key,
|
||||
@@ -50,6 +50,8 @@ class WebEditorView extends StatelessWidget with EditorViewMixin {
|
||||
this.width,
|
||||
this.height,
|
||||
this.onDragEnter,
|
||||
this.onPasteImageSuccessAction,
|
||||
this.onPasteImageFailureAction,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -76,6 +78,8 @@ class WebEditorView extends StatelessWidget with EditorViewMixin {
|
||||
width: width,
|
||||
height: height,
|
||||
onDragEnter: onDragEnter,
|
||||
onPasteImageSuccessAction: onPasteImageSuccessAction,
|
||||
onPasteImageFailureAction: onPasteImageFailureAction,
|
||||
);
|
||||
case EmailActionType.editDraft:
|
||||
case EmailActionType.editSendingEmail:
|
||||
@@ -101,6 +105,8 @@ class WebEditorView extends StatelessWidget with EditorViewMixin {
|
||||
width: width,
|
||||
height: height,
|
||||
onDragEnter: onDragEnter,
|
||||
onPasteImageSuccessAction: onPasteImageSuccessAction,
|
||||
onPasteImageFailureAction: onPasteImageFailureAction,
|
||||
),
|
||||
(success) {
|
||||
if (success is GetEmailContentLoading || success is RestoringEmailInlineImages) {
|
||||
@@ -126,6 +132,8 @@ class WebEditorView extends StatelessWidget with EditorViewMixin {
|
||||
width: width,
|
||||
height: height,
|
||||
onDragEnter: onDragEnter,
|
||||
onPasteImageSuccessAction: onPasteImageSuccessAction,
|
||||
onPasteImageFailureAction: onPasteImageFailureAction,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -158,6 +166,8 @@ class WebEditorView extends StatelessWidget with EditorViewMixin {
|
||||
width: width,
|
||||
height: height,
|
||||
onDragEnter: onDragEnter,
|
||||
onPasteImageSuccessAction: onPasteImageSuccessAction,
|
||||
onPasteImageFailureAction: onPasteImageFailureAction,
|
||||
);
|
||||
},
|
||||
(success) {
|
||||
@@ -186,6 +196,8 @@ class WebEditorView extends StatelessWidget with EditorViewMixin {
|
||||
width: width,
|
||||
height: height,
|
||||
onDragEnter: onDragEnter,
|
||||
onPasteImageSuccessAction: onPasteImageSuccessAction,
|
||||
onPasteImageFailureAction: onPasteImageFailureAction,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -205,6 +217,8 @@ class WebEditorView extends StatelessWidget with EditorViewMixin {
|
||||
width: width,
|
||||
height: height,
|
||||
onDragEnter: onDragEnter,
|
||||
onPasteImageSuccessAction: onPasteImageSuccessAction,
|
||||
onPasteImageFailureAction: onPasteImageFailureAction,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import 'package:core/utils/app_logger.dart';
|
||||
import 'package:core/utils/html/html_utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:html_editor_enhanced/html_editor.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/view/web/web_editor_view.dart';
|
||||
import 'package:universal_html/html.dart' hide VoidCallback;
|
||||
|
||||
typedef OnChangeContentEditorAction = Function(String? text);
|
||||
@@ -13,6 +12,12 @@ typedef OnInitialContentEditorAction = Function(String text);
|
||||
typedef OnMouseDownEditorAction = Function(BuildContext context);
|
||||
typedef OnEditorSettingsChange = Function(EditorSettings settings);
|
||||
typedef OnEditorTextSizeChanged = Function(int? size);
|
||||
typedef OnDragEnterListener = Function(List<dynamic>? types);
|
||||
typedef OnPasteImageSuccessAction = Function(List<FileUpload> listFileUpload);
|
||||
typedef OnPasteImageFailureAction = Function(
|
||||
List<FileUpload>? listFileUpload,
|
||||
String? base64,
|
||||
UploadError uploadError);
|
||||
|
||||
class WebEditorWidget extends StatefulWidget {
|
||||
|
||||
@@ -29,6 +34,8 @@ class WebEditorWidget extends StatefulWidget {
|
||||
final double? width;
|
||||
final double? height;
|
||||
final OnDragEnterListener? onDragEnter;
|
||||
final OnPasteImageSuccessAction? onPasteImageSuccessAction;
|
||||
final OnPasteImageFailureAction? onPasteImageFailureAction;
|
||||
|
||||
const WebEditorWidget({
|
||||
super.key,
|
||||
@@ -45,6 +52,8 @@ class WebEditorWidget extends StatefulWidget {
|
||||
this.width,
|
||||
this.height,
|
||||
this.onDragEnter,
|
||||
this.onPasteImageSuccessAction,
|
||||
this.onPasteImageFailureAction,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -179,6 +188,8 @@ class _WebEditorState extends State<WebEditorWidget> {
|
||||
),
|
||||
onDragEnter: widget.onDragEnter,
|
||||
onDragLeave: (_) {},
|
||||
onImageUpload: widget.onPasteImageSuccessAction,
|
||||
onImageUploadError: widget.onPasteImageFailureAction,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import 'package:model/upload/file_info.dart';
|
||||
|
||||
extension ListFileInfoExtension on List<FileInfo> {
|
||||
@@ -7,4 +6,6 @@ extension ListFileInfoExtension on List<FileInfo> {
|
||||
num get totalSize => listSize.isEmpty ? 0 : listSize.reduce((sum, size) => sum + size);
|
||||
|
||||
List<FileInfo> get listInlineFiles => where((fileInfo) => fileInfo.isInline == true).toList();
|
||||
|
||||
List<FileInfo> get listAttachmentFiles => where((fileInfo) => fileInfo.isInline != true).toList();
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:html_editor_enhanced/html_editor.dart';
|
||||
import 'package:model/upload/file_info.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/extensions/file_upload_extension.dart';
|
||||
|
||||
extension ListFileUploadExtension on List<FileUpload> {
|
||||
List<FileInfo> toListFileInfo() => map((fileUpload) => fileUpload.toFileInfo()).whereNotNull().toList();
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"@@last_modified": "2024-08-20T10:36:35.487743",
|
||||
"@@last_modified": "2024-08-19T19:20:23.876948",
|
||||
"initializing_data": "Initializing data...",
|
||||
"@initializing_data": {
|
||||
"type": "text",
|
||||
@@ -3987,5 +3987,11 @@
|
||||
"type": "text",
|
||||
"placeholders_order": [],
|
||||
"placeholders": {}
|
||||
},
|
||||
"thisImageCannotBePastedIntoTheEditor": "This image cannot be pasted into the editor.",
|
||||
"@thisImageCannotBePastedIntoTheEditor": {
|
||||
"type": "text",
|
||||
"placeholders_order": [],
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
@@ -4159,13 +4159,13 @@ class AppLocalizations {
|
||||
}
|
||||
|
||||
String get emptySpamFolderFailed {
|
||||
return Intl.message(''
|
||||
return Intl.message(
|
||||
'Empty spam folder failed',
|
||||
name: 'emptySpamFolderFailed');
|
||||
}
|
||||
|
||||
String get markAsSpamFailed {
|
||||
return Intl.message(''
|
||||
return Intl.message(
|
||||
'Mark as spam failed',
|
||||
name: 'markAsSpamFailed');
|
||||
}
|
||||
@@ -4176,4 +4176,10 @@ class AppLocalizations {
|
||||
name: 'canNotUploadFileToSignature'
|
||||
);
|
||||
}
|
||||
|
||||
String get thisImageCannotBePastedIntoTheEditor {
|
||||
return Intl.message(
|
||||
'This image cannot be pasted into the editor.',
|
||||
name: 'thisImageCannotBePastedIntoTheEditor');
|
||||
}
|
||||
}
|
||||
@@ -28,12 +28,14 @@ class FileInfo with EquatableMixin {
|
||||
String? name,
|
||||
int? size,
|
||||
String? type,
|
||||
bool? isInline,
|
||||
}) {
|
||||
return FileInfo(
|
||||
fileName: name ?? '',
|
||||
fileSize: size ?? 0,
|
||||
bytes: bytes,
|
||||
type: type
|
||||
type: type,
|
||||
isInline: isInline,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user