TF-4142 Implement hyperlink insertion
This commit is contained in:
@@ -12,6 +12,7 @@ import 'package:tmail_ui_user/features/base/widget/keyboard/keyboard_handler_wra
|
||||
import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/extensions/composer_print_draft_extension.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/extensions/handle_edit_recipient_extension.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/extensions/handle_insert_link_composer_extension.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/extensions/handle_recipients_collapsed_extensions.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/extensions/handle_keyboard_shortcut_actions_extension.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/extensions/mark_as_important_extension.dart';
|
||||
@@ -248,6 +249,7 @@ class ComposerView extends GetWidget<ComposerController> {
|
||||
onEditorTextSizeChanged: controller.richTextWebController!.onEditorTextSizeChanged,
|
||||
height: constraintsEditor.maxHeight,
|
||||
horizontalPadding: ComposerStyle.mobileEditorHorizontalPadding,
|
||||
linkOverlayOptions: controller.createLinkOverlayOptions(context),
|
||||
onDragEnter: controller.handleOnDragEnterHtmlEditorWeb,
|
||||
onDragOver: controller.handleOnDragOverHtmlEditorWeb,
|
||||
onPasteImageSuccessAction: (listFileUpload) => controller.handleOnPasteImageSuccessAction(
|
||||
@@ -489,6 +491,7 @@ class ComposerView extends GetWidget<ComposerController> {
|
||||
onEditorTextSizeChanged: controller.richTextWebController?.onEditorTextSizeChanged,
|
||||
height: constraintsEditor.maxHeight,
|
||||
horizontalPadding: ComposerStyle.desktopEditorHorizontalPadding,
|
||||
linkOverlayOptions: controller.createLinkOverlayOptions(context),
|
||||
onDragEnter: controller.handleOnDragEnterHtmlEditorWeb,
|
||||
onDragOver: controller.handleOnDragOverHtmlEditorWeb,
|
||||
onPasteImageSuccessAction: (listFileUpload) => controller.handleOnPasteImageSuccessAction(
|
||||
@@ -551,6 +554,7 @@ class ComposerView extends GetWidget<ComposerController> {
|
||||
toggleRequestReadReceiptAction: () => controller.toggleRequestReadReceipt(context),
|
||||
toggleMarkAsImportantAction: () => controller.toggleMarkAsImportant(context),
|
||||
saveAsTemplateAction: () => controller.handleClickSaveAsTemplateButton(context),
|
||||
onOpenInsertLink: controller.openInsertLink,
|
||||
)),
|
||||
],
|
||||
),
|
||||
@@ -760,6 +764,7 @@ class ComposerView extends GetWidget<ComposerController> {
|
||||
onEditorTextSizeChanged: controller.richTextWebController!.onEditorTextSizeChanged,
|
||||
height: constraintsBody.maxHeight,
|
||||
horizontalPadding: ComposerStyle.desktopEditorHorizontalPadding,
|
||||
linkOverlayOptions: controller.createLinkOverlayOptions(context),
|
||||
onDragEnter: controller.handleOnDragEnterHtmlEditorWeb,
|
||||
onDragOver: controller.handleOnDragOverHtmlEditorWeb,
|
||||
onPasteImageSuccessAction: (listFileUpload) => controller.handleOnPasteImageSuccessAction(
|
||||
@@ -821,6 +826,7 @@ class ComposerView extends GetWidget<ComposerController> {
|
||||
toggleRequestReadReceiptAction: () => controller.toggleRequestReadReceipt(context),
|
||||
toggleMarkAsImportantAction: () => controller.toggleMarkAsImportant(context),
|
||||
saveAsTemplateAction: () => controller.handleClickSaveAsTemplateButton(context),
|
||||
onOpenInsertLink: controller.openInsertLink,
|
||||
)),
|
||||
],
|
||||
),
|
||||
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
import 'package:core/presentation/extensions/color_extension.dart';
|
||||
import 'package:core/presentation/utils/theme_utils.dart';
|
||||
import 'package:core/presentation/views/button/tmail_button_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:html_editor_enhanced/html_editor.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart';
|
||||
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
||||
|
||||
extension HandleInsertLinkComposerExtension on ComposerController {
|
||||
void openInsertLink() {
|
||||
richTextWebController?.editorController.openInsertLinkDialog();
|
||||
}
|
||||
|
||||
LinkOverlayOptions createLinkOverlayOptions(BuildContext context) {
|
||||
final appLocalizations = AppLocalizations.of(context);
|
||||
return LinkOverlayOptions(
|
||||
tooltipOptions: LinkTooltipOverlayOptions(
|
||||
linkPrefixLabel: '${appLocalizations.goTo}:',
|
||||
editLinkLabel: appLocalizations.change,
|
||||
removeLinkTooltipMessage: appLocalizations.removeLink,
|
||||
linkPrefixLabelStyle: ThemeUtils.textStyleBodyBody3(
|
||||
color: AppColor.gray900,
|
||||
),
|
||||
linkLabelStyle: ThemeUtils.textStyleBodyBody3(
|
||||
color: AppColor.blue700,
|
||||
),
|
||||
editLinkLabelStyle: ThemeUtils.textStyleBodyBody3(
|
||||
color: AppColor.primaryMain,
|
||||
),
|
||||
removeLinkButtonBuilder: (_, onTap) => TMailButtonWidget.fromIcon(
|
||||
icon: imagePaths.icRemoveLink,
|
||||
iconSize: 20,
|
||||
iconColor: AppColor.primaryLinShare,
|
||||
backgroundColor: Colors.transparent,
|
||||
onTapActionCallback: onTap,
|
||||
),
|
||||
),
|
||||
editDialogOptions: LinkEditDialogOverlayOptions(
|
||||
hintText: appLocalizations.text,
|
||||
hintUrl: appLocalizations.typeOrPasteLink,
|
||||
applyButtonLabel: appLocalizations.apply,
|
||||
hintTextStyle: ThemeUtils.textStyleBodyBody3(
|
||||
color: AppColor.steelGray400,
|
||||
),
|
||||
inputTextStyle: ThemeUtils.textStyleBodyBody3(
|
||||
color: AppColor.gray900,
|
||||
),
|
||||
applyButtonTextStyle: (isEnabled) => ThemeUtils.textStyleM3LabelLarge(
|
||||
color: isEnabled ? AppColor.primaryMain : AppColor.gray400,
|
||||
),
|
||||
textPrefixIcon: SvgPicture.asset(
|
||||
imagePaths.icText,
|
||||
width: 24,
|
||||
height: 24,
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
urlPrefixIcon: SvgPicture.asset(
|
||||
imagePaths.icInsertLink,
|
||||
width: 24,
|
||||
height: 24,
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
backgroundColor: Colors.white,
|
||||
inputBackgroundColor: Colors.white,
|
||||
dialogPadding: const EdgeInsetsDirectional.only(
|
||||
start: 16,
|
||||
end: 12,
|
||||
top: 16,
|
||||
bottom: 16,
|
||||
),
|
||||
border: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(10)),
|
||||
borderSide: BorderSide(
|
||||
width: 1,
|
||||
color: AppColor.m3Neutral90,
|
||||
),
|
||||
),
|
||||
focusedBorder: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(10)),
|
||||
borderSide: BorderSide(
|
||||
width: 1,
|
||||
color: AppColor.primaryMain,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ class WebEditorView extends StatelessWidget with EditorViewMixin {
|
||||
final OnEditorTextSizeChanged? onEditorTextSizeChanged;
|
||||
final double? height;
|
||||
final double? horizontalPadding;
|
||||
final LinkOverlayOptions? linkOverlayOptions;
|
||||
final OnDragEnterListener? onDragEnter;
|
||||
final OnDragOverListener? onDragOver;
|
||||
final OnPasteImageSuccessAction? onPasteImageSuccessAction;
|
||||
@@ -51,6 +52,7 @@ class WebEditorView extends StatelessWidget with EditorViewMixin {
|
||||
this.onEditorTextSizeChanged,
|
||||
this.height,
|
||||
this.horizontalPadding,
|
||||
this.linkOverlayOptions,
|
||||
this.onDragEnter,
|
||||
this.onDragOver,
|
||||
this.onPasteImageSuccessAction,
|
||||
@@ -82,6 +84,7 @@ class WebEditorView extends StatelessWidget with EditorViewMixin {
|
||||
onEditorTextSizeChanged: onEditorTextSizeChanged,
|
||||
height: height,
|
||||
horizontalPadding: horizontalPadding,
|
||||
linkOverlayOptions: linkOverlayOptions,
|
||||
onDragEnter: onDragEnter,
|
||||
onDragOver: onDragOver,
|
||||
onPasteImageSuccessAction: onPasteImageSuccessAction,
|
||||
@@ -113,6 +116,7 @@ class WebEditorView extends StatelessWidget with EditorViewMixin {
|
||||
onEditorTextSizeChanged: onEditorTextSizeChanged,
|
||||
height: height,
|
||||
horizontalPadding: horizontalPadding,
|
||||
linkOverlayOptions: linkOverlayOptions,
|
||||
onDragEnter: onDragEnter,
|
||||
onDragOver: onDragOver,
|
||||
onPasteImageSuccessAction: onPasteImageSuccessAction,
|
||||
@@ -146,6 +150,7 @@ class WebEditorView extends StatelessWidget with EditorViewMixin {
|
||||
onEditorTextSizeChanged: onEditorTextSizeChanged,
|
||||
height: height,
|
||||
horizontalPadding: horizontalPadding,
|
||||
linkOverlayOptions: linkOverlayOptions,
|
||||
onDragEnter: onDragEnter,
|
||||
onDragOver: onDragOver,
|
||||
onPasteImageSuccessAction: onPasteImageSuccessAction,
|
||||
@@ -185,6 +190,7 @@ class WebEditorView extends StatelessWidget with EditorViewMixin {
|
||||
onEditorTextSizeChanged: onEditorTextSizeChanged,
|
||||
height: height,
|
||||
horizontalPadding: horizontalPadding,
|
||||
linkOverlayOptions: linkOverlayOptions,
|
||||
onDragEnter: onDragEnter,
|
||||
onDragOver: onDragOver,
|
||||
onPasteImageSuccessAction: onPasteImageSuccessAction,
|
||||
@@ -219,6 +225,7 @@ class WebEditorView extends StatelessWidget with EditorViewMixin {
|
||||
onEditorTextSizeChanged: onEditorTextSizeChanged,
|
||||
height: height,
|
||||
horizontalPadding: horizontalPadding,
|
||||
linkOverlayOptions: linkOverlayOptions,
|
||||
onDragEnter: onDragEnter,
|
||||
onDragOver: onDragOver,
|
||||
onPasteImageSuccessAction: onPasteImageSuccessAction,
|
||||
@@ -243,6 +250,7 @@ class WebEditorView extends StatelessWidget with EditorViewMixin {
|
||||
onEditorTextSizeChanged: onEditorTextSizeChanged,
|
||||
height: height,
|
||||
horizontalPadding: horizontalPadding,
|
||||
linkOverlayOptions: linkOverlayOptions,
|
||||
onDragEnter: onDragEnter,
|
||||
onDragOver: onDragOver,
|
||||
onPasteImageSuccessAction: onPasteImageSuccessAction,
|
||||
|
||||
@@ -30,6 +30,7 @@ class BottomBarComposerWidget extends StatelessWidget {
|
||||
final VoidCallback printDraftAction;
|
||||
final VoidCallback toggleMarkAsImportantAction;
|
||||
final VoidCallback saveAsTemplateAction;
|
||||
final VoidCallback onOpenInsertLink;
|
||||
final OnMenuChanged? onPopupMenuChanged;
|
||||
|
||||
const BottomBarComposerWidget({
|
||||
@@ -52,6 +53,7 @@ class BottomBarComposerWidget extends StatelessWidget {
|
||||
required this.printDraftAction,
|
||||
required this.toggleMarkAsImportantAction,
|
||||
required this.saveAsTemplateAction,
|
||||
required this.onOpenInsertLink,
|
||||
this.onPopupMenuChanged,
|
||||
});
|
||||
|
||||
@@ -110,6 +112,22 @@ class BottomBarComposerWidget extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
const SizedBox(width: BottomBarComposerWidgetStyle.space),
|
||||
AbsorbPointer(
|
||||
absorbing: isCodeViewEnabled,
|
||||
child: TMailButtonWidget.fromIcon(
|
||||
icon: imagePaths.icInsertLink,
|
||||
iconColor: isCodeViewEnabled
|
||||
? BottomBarComposerWidgetStyle.disabledIconColor
|
||||
: BottomBarComposerWidgetStyle.iconColor,
|
||||
borderRadius: BottomBarComposerWidgetStyle.iconRadius,
|
||||
backgroundColor: Colors.transparent,
|
||||
padding: BottomBarComposerWidgetStyle.iconPadding,
|
||||
iconSize: BottomBarComposerWidgetStyle.iconSize,
|
||||
tooltipMessage: AppLocalizations.of(context).insertLink,
|
||||
onTapActionCallback: onOpenInsertLink,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: BottomBarComposerWidgetStyle.space),
|
||||
PopupMenuOverlayWidget(
|
||||
controller: menuMoreOptionController,
|
||||
iconButton: HighlightSVGIconOnHover(
|
||||
|
||||
@@ -39,6 +39,7 @@ class WebEditorWidget extends StatefulWidget {
|
||||
final OnEditorTextSizeChanged? onEditorTextSizeChanged;
|
||||
final double? height;
|
||||
final double? horizontalPadding;
|
||||
final LinkOverlayOptions? linkOverlayOptions;
|
||||
final OnDragEnterListener? onDragEnter;
|
||||
final OnDragOverListener? onDragOver;
|
||||
final OnPasteImageSuccessAction? onPasteImageSuccessAction;
|
||||
@@ -60,6 +61,7 @@ class WebEditorWidget extends StatefulWidget {
|
||||
this.onEditorTextSizeChanged,
|
||||
this.height,
|
||||
this.horizontalPadding,
|
||||
this.linkOverlayOptions,
|
||||
this.onDragEnter,
|
||||
this.onDragOver,
|
||||
this.onPasteImageSuccessAction,
|
||||
@@ -141,6 +143,9 @@ class _WebEditorState extends State<WebEditorWidget> {
|
||||
useDefaultFontStyle: true,
|
||||
customScrollbar: true,
|
||||
),
|
||||
useLinkTooltipOverlay: true,
|
||||
linkOverlayOptions: widget.linkOverlayOptions
|
||||
?? const LinkOverlayOptions(),
|
||||
spellCheck: true,
|
||||
disableDragAndDrop: true,
|
||||
normalizeHtmlTextWhenDropping: true,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"@@last_modified": "2025-11-05T13:34:06.612241",
|
||||
"@@last_modified": "2025-11-10T12:07:59.064737",
|
||||
"initializing_data": "Initializing data...",
|
||||
"@initializing_data": {
|
||||
"type": "text",
|
||||
@@ -5069,5 +5069,47 @@
|
||||
"type": "text",
|
||||
"placeholders_order": [],
|
||||
"placeholders": {}
|
||||
},
|
||||
"insertLink": "Insert link",
|
||||
"@insertLink": {
|
||||
"type": "text",
|
||||
"placeholders_order": [],
|
||||
"placeholders": {}
|
||||
},
|
||||
"goTo": "Go to",
|
||||
"@goTo": {
|
||||
"type": "text",
|
||||
"placeholders_order": [],
|
||||
"placeholders": {}
|
||||
},
|
||||
"change": "Change",
|
||||
"@change": {
|
||||
"type": "text",
|
||||
"placeholders_order": [],
|
||||
"placeholders": {}
|
||||
},
|
||||
"removeLink": "Remove link",
|
||||
"@removeLink": {
|
||||
"type": "text",
|
||||
"placeholders_order": [],
|
||||
"placeholders": {}
|
||||
},
|
||||
"text": "Text",
|
||||
"@text": {
|
||||
"type": "text",
|
||||
"placeholders_order": [],
|
||||
"placeholders": {}
|
||||
},
|
||||
"typeOrPasteLink": "Type or paste a link",
|
||||
"@typeOrPasteLink": {
|
||||
"type": "text",
|
||||
"placeholders_order": [],
|
||||
"placeholders": {}
|
||||
},
|
||||
"apply": "Apply",
|
||||
"@apply": {
|
||||
"type": "text",
|
||||
"placeholders_order": [],
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
@@ -5366,4 +5366,53 @@ class AppLocalizations {
|
||||
name: 'favoriteMailboxDisplayName',
|
||||
);
|
||||
}
|
||||
|
||||
String get insertLink {
|
||||
return Intl.message(
|
||||
'Insert link',
|
||||
name: 'insertLink',
|
||||
);
|
||||
}
|
||||
|
||||
String get goTo {
|
||||
return Intl.message(
|
||||
'Go to',
|
||||
name: 'goTo',
|
||||
);
|
||||
}
|
||||
|
||||
String get change {
|
||||
return Intl.message(
|
||||
'Change',
|
||||
name: 'change',
|
||||
);
|
||||
}
|
||||
|
||||
String get removeLink {
|
||||
return Intl.message(
|
||||
'Remove link',
|
||||
name: 'removeLink',
|
||||
);
|
||||
}
|
||||
|
||||
String get text {
|
||||
return Intl.message(
|
||||
'Text',
|
||||
name: 'text',
|
||||
);
|
||||
}
|
||||
|
||||
String get typeOrPasteLink {
|
||||
return Intl.message(
|
||||
'Type or paste a link',
|
||||
name: 'typeOrPasteLink',
|
||||
);
|
||||
}
|
||||
|
||||
String get apply {
|
||||
return Intl.message(
|
||||
'Apply',
|
||||
name: 'apply',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user