Fix displaying the external url of the link tag
This commit is contained in:
@@ -20,6 +20,10 @@ export 'presentation/utils/responsive_utils.dart';
|
||||
export 'presentation/utils/keyboard_utils.dart';
|
||||
export 'presentation/utils/style_utils.dart';
|
||||
export 'presentation/utils/app_toast.dart';
|
||||
export 'presentation/utils/html_transformer/html_template.dart';
|
||||
export 'presentation/utils/html_transformer/html_transform.dart';
|
||||
export 'presentation/utils/html_transformer/transform_configuration.dart';
|
||||
export 'presentation/utils/html_transformer/dom/add_tooltip_link_transformers.dart';
|
||||
export 'data/utils/device_manager.dart';
|
||||
export 'utils/app_logger.dart';
|
||||
|
||||
|
||||
@@ -13,8 +13,10 @@ abstract class DomTransformer {
|
||||
Future<void> process(
|
||||
Document document,
|
||||
String message,
|
||||
Map<String, String>? mapUrlDownloadCID,
|
||||
DioClient dioClient
|
||||
{
|
||||
Map<String, String>? mapUrlDownloadCID,
|
||||
DioClient? dioClient,
|
||||
}
|
||||
);
|
||||
|
||||
/// Adds a HEAD element if necessary
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
|
||||
import 'package:core/presentation/utils/html_transformer/transform_configuration.dart';
|
||||
|
||||
/// Transforms plain text messages.
|
||||
abstract class TextTransformer {
|
||||
const TextTransformer();
|
||||
|
||||
String transform(String text, String message, TransformConfiguration configuration);
|
||||
String process(String text);
|
||||
}
|
||||
+9
-13
@@ -1,40 +1,36 @@
|
||||
|
||||
import 'package:core/data/network/dio_client.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/base/dom_transformer.dart';
|
||||
import 'package:core/utils/app_logger.dart';
|
||||
import 'package:html/dom.dart';
|
||||
|
||||
class LinkTransformer extends DomTransformer {
|
||||
class AddTooltipLinkTransformer extends DomTransformer {
|
||||
|
||||
const LinkTransformer();
|
||||
const AddTooltipLinkTransformer();
|
||||
|
||||
@override
|
||||
Future<void> process(
|
||||
Document document,
|
||||
String message,
|
||||
Map<String, String>? mapUrlDownloadCID,
|
||||
DioClient dioClient
|
||||
{
|
||||
Map<String, String>? mapUrlDownloadCID,
|
||||
DioClient? dioClient
|
||||
}
|
||||
) async {
|
||||
final linkElements = document.getElementsByTagName('a');
|
||||
final linkElements = document.querySelectorAll('a[href^="http"]');
|
||||
await Future.wait(linkElements.map((linkElement) async {
|
||||
linkElement.attributes['rel'] = 'noopener noreferrer';
|
||||
_addToolTipWhenHoverLink(linkElement);
|
||||
}));
|
||||
}
|
||||
|
||||
void _addToolTipWhenHoverLink(Element element) {
|
||||
log('LinkTransformer::_addToolTipWhenHoverLink(): Before: ${element.outerHtml}');
|
||||
final url = element.attributes['href'];
|
||||
final text = element.text;
|
||||
final children = element.children;
|
||||
if (children.isEmpty && text.isNotEmpty && url?.isNotEmpty == true) {
|
||||
if (children.isEmpty && text.isNotEmpty && url != null) {
|
||||
final innerHtml = element.innerHtml;
|
||||
final tagClass = element.attributes['class'];
|
||||
element.attributes['class'] = '$tagClass tooltip';
|
||||
if (text.isNotEmpty && url != null && url.isNotEmpty) {
|
||||
element.innerHtml = innerHtml + textHasToolTip(url);
|
||||
}
|
||||
log('LinkTransformer::_addToolTipWhenHoverLink(): After: ${element.outerHtml}');
|
||||
element.innerHtml = innerHtml + textHasToolTip(url);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,10 @@ class BlockQuotedTransformer extends DomTransformer {
|
||||
Future<void> process(
|
||||
Document document,
|
||||
String message,
|
||||
Map<String, String>? mapUrlDownloadCID,
|
||||
DioClient dioClient
|
||||
{
|
||||
Map<String, String>? mapUrlDownloadCID,
|
||||
DioClient? dioClient
|
||||
}
|
||||
) async {
|
||||
final quotedElements = document.getElementsByTagName('blockquote');
|
||||
await Future.wait(quotedElements.map((quotedElement) async {
|
||||
|
||||
@@ -14,8 +14,10 @@ class ImageTransformer extends DomTransformer {
|
||||
Future<void> process(
|
||||
Document document,
|
||||
String message,
|
||||
Map<String, String>? mapUrlDownloadCID,
|
||||
DioClient dioClient
|
||||
{
|
||||
Map<String, String>? mapUrlDownloadCID,
|
||||
DioClient? dioClient
|
||||
}
|
||||
) async {
|
||||
final imageElements = document.getElementsByTagName('img');
|
||||
|
||||
@@ -29,7 +31,7 @@ class ImageTransformer extends DomTransformer {
|
||||
) {
|
||||
final cid = src.replaceFirst('cid:', '').trim();
|
||||
final cidUrlDownload = mapUrlDownloadCID[cid];
|
||||
if (cidUrlDownload != null && cidUrlDownload.isNotEmpty) {
|
||||
if (cidUrlDownload != null && cidUrlDownload.isNotEmpty && dioClient != null) {
|
||||
final imgBase64 = await loadAsyncNetworkImageToBase64(dioClient, cidUrlDownload);
|
||||
if (imgBase64 != null && imgBase64.isNotEmpty) {
|
||||
imageElement.attributes['src'] = 'data:image/jpeg;base64,$imgBase64';
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
|
||||
import 'package:core/data/network/dio_client.dart';
|
||||
import 'package:html/dom.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/base/dom_transformer.dart';
|
||||
|
||||
class MetaTransformer extends DomTransformer {
|
||||
|
||||
static final Element _viewPortMetaElement = Element.html(
|
||||
'<meta name="viewport" content="width=device-width, initial-scale=1.0">');
|
||||
static final Element _contentTypeMetaElement = Element.html(
|
||||
'<meta http-equiv="Content-Type" content="text/html; charset=utf-8">');
|
||||
|
||||
const MetaTransformer();
|
||||
|
||||
@override
|
||||
Future<void> process(
|
||||
Document document,
|
||||
String message,
|
||||
Map<String, String>? mapUrlDownloadCID,
|
||||
DioClient dioClient
|
||||
) async {
|
||||
final metaElements = document.getElementsByTagName('meta');
|
||||
var viewportNeedsToBeAdded = true;
|
||||
var contentTypeNeedsToBeAdded = true;
|
||||
|
||||
await Future.wait(metaElements.map((metaElement) async {
|
||||
if (metaElement.attributes['name'] == 'viewport') {
|
||||
viewportNeedsToBeAdded = false;
|
||||
metaElement.attributes['content'] = 'width=device-width, initial-scale=1.0';
|
||||
} else if (metaElement.attributes['charset'] != null) {
|
||||
metaElement.attributes['charset'] = 'utf-8';
|
||||
} else {
|
||||
final httpEquiv = metaElement.attributes['http-equiv'];
|
||||
if (httpEquiv != null && httpEquiv.toLowerCase() == 'content-type') {
|
||||
contentTypeNeedsToBeAdded = false;
|
||||
metaElement.attributes['content'] = 'text/html; charset=utf-8';
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
if (contentTypeNeedsToBeAdded) {
|
||||
ensureDocumentHeadIsAvailable(document);
|
||||
document.head!.append(_contentTypeMetaElement);
|
||||
}
|
||||
if (viewportNeedsToBeAdded) {
|
||||
ensureDocumentHeadIsAvailable(document);
|
||||
document.head!.append(_viewPortMetaElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,10 @@ class RemoveScriptTransformer extends DomTransformer {
|
||||
Future<void> process(
|
||||
Document document,
|
||||
String message,
|
||||
Map<String, String>? mapUrlDownloadCID,
|
||||
DioClient dioClient
|
||||
{
|
||||
Map<String, String>? mapUrlDownloadCID,
|
||||
DioClient? dioClient
|
||||
}
|
||||
) async {
|
||||
final scriptElements = document.getElementsByTagName('script');
|
||||
await Future.wait(scriptElements.map((scriptElement) async {
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
|
||||
final tooltipLinkCss = '''
|
||||
.tooltip .tooltiptext {
|
||||
visibility: hidden;
|
||||
max-width: 400px;
|
||||
background-color: black;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
border-radius: 6px;
|
||||
padding: 5px 8px 5px 8px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
}
|
||||
.tooltip:hover .tooltiptext {
|
||||
visibility: visible;
|
||||
}
|
||||
''';
|
||||
|
||||
String generateHtml(String content, {
|
||||
double? minHeight,
|
||||
double? minWidth,
|
||||
String? styleCSS,
|
||||
String? javaScripts
|
||||
}) {
|
||||
return '''
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<style>
|
||||
.tmail-content {
|
||||
min-height: ${minHeight ?? 0}px;
|
||||
min-width: ${minWidth ?? 0}px;
|
||||
color: #000000;
|
||||
font-family: Inter;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
td {
|
||||
padding: 13px;
|
||||
margin: 0px;
|
||||
}
|
||||
th {
|
||||
padding: 13px;
|
||||
margin: 0px;
|
||||
}
|
||||
${styleCSS ?? ''}
|
||||
</style>
|
||||
${javaScripts ?? ''}
|
||||
</head>
|
||||
<body>
|
||||
<div class="tmail-content">${content}</div>
|
||||
</body>
|
||||
</html>
|
||||
''';
|
||||
}
|
||||
@@ -7,17 +7,17 @@ class HtmlTransform {
|
||||
|
||||
final String _contentHtml;
|
||||
Map<String, String>? mapUrlDownloadCID;
|
||||
DioClient dioClient;
|
||||
DioClient? dioClient;
|
||||
|
||||
HtmlTransform(
|
||||
this._contentHtml,
|
||||
this.dioClient,
|
||||
this.mapUrlDownloadCID
|
||||
{
|
||||
this.mapUrlDownloadCID,
|
||||
this.dioClient,
|
||||
}
|
||||
);
|
||||
|
||||
/// Transforms this message to HTML code.
|
||||
/// Optionally specify the [transformConfiguration] to control all aspects of the transformation
|
||||
/// - in that case other parameters are ignored.
|
||||
Future<String> transformToHtml({TransformConfiguration? transformConfiguration}) async {
|
||||
final document = await transformToDocument(transformConfiguration: transformConfiguration);
|
||||
return document.outerHtml;
|
||||
@@ -26,7 +26,7 @@ class HtmlTransform {
|
||||
/// Transforms this message to Document.
|
||||
Future<Document> transformToDocument({TransformConfiguration? transformConfiguration}) async {
|
||||
transformConfiguration ??= TransformConfiguration.create();
|
||||
final transformer = MessageContentTransformer(transformConfiguration, dioClient);
|
||||
return await transformer.toDocument(_contentHtml, mapUrlDownloadCID);
|
||||
final transformer = MessageContentTransformer(transformConfiguration);
|
||||
return await transformer.toDocument(_contentHtml, mapUrlDownloadCID: mapUrlDownloadCID, dioClient: dioClient);
|
||||
}
|
||||
}
|
||||
@@ -7,24 +7,29 @@ import 'package:html/parser.dart' show parse;
|
||||
class MessageContentTransformer {
|
||||
/// The configuration used for the transformation
|
||||
final TransformConfiguration configuration;
|
||||
final DioClient dioClient;
|
||||
|
||||
MessageContentTransformer(this.configuration, this.dioClient);
|
||||
MessageContentTransformer(this.configuration);
|
||||
|
||||
Future<void> transformDocument(
|
||||
Document document,
|
||||
String message,
|
||||
Map<String, String>? mapUrlDownloadCID,
|
||||
{
|
||||
Map<String, String>? mapUrlDownloadCID,
|
||||
DioClient? dioClient
|
||||
}
|
||||
) async {
|
||||
await Future.wait(configuration.domTransformers.map((domTransformer) async {
|
||||
await domTransformer.process(document, message, mapUrlDownloadCID, dioClient);
|
||||
}));
|
||||
await Future.wait([
|
||||
if (configuration.domTransformers.isNotEmpty) ...configuration.domTransformers.map((domTransformer) async =>
|
||||
domTransformer.process(document, message, mapUrlDownloadCID: mapUrlDownloadCID, dioClient: dioClient)),
|
||||
if (configuration.textTransformers.isNotEmpty) ...configuration.textTransformers.map((textTransformer) async =>
|
||||
textTransformer.process(message))
|
||||
]);
|
||||
}
|
||||
|
||||
Future<Document> toDocument(String message, Map<String, String>? mapUrlDownloadCID) async {
|
||||
Future<Document> toDocument(String message, {Map<String, String>? mapUrlDownloadCID, DioClient? dioClient}) async {
|
||||
var html = message;
|
||||
final document = parse(html);
|
||||
await transformDocument(document, message, mapUrlDownloadCID);
|
||||
await transformDocument(document, message, mapUrlDownloadCID: mapUrlDownloadCID, dioClient: dioClient);
|
||||
return document;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
|
||||
import 'package:core/presentation/utils/html_transformer/base/text_transformer.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/transform_configuration.dart';
|
||||
|
||||
class ConvertTagsTextTransformer implements TextTransformer {
|
||||
|
||||
const ConvertTagsTextTransformer();
|
||||
|
||||
@override
|
||||
String transform(String text, String message, TransformConfiguration configuration) {
|
||||
return text.replaceAll('<', '<').replaceAll('>', '>');
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
|
||||
import 'package:core/presentation/utils/html_transformer/base/text_transformer.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/transform_configuration.dart';
|
||||
|
||||
class LineBreakTextTransformer extends TextTransformer {
|
||||
|
||||
const LineBreakTextTransformer();
|
||||
|
||||
@override
|
||||
String transform(String text, String message, TransformConfiguration configuration) {
|
||||
text = text.replaceAll('\r\n', '<br/>');
|
||||
text = text.replaceAll('\n', '<br/>');
|
||||
return text;
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
|
||||
import 'package:core/presentation/utils/html_transformer/base/text_transformer.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/transform_configuration.dart';
|
||||
|
||||
class LinksTextTransformer extends TextTransformer {
|
||||
|
||||
static final RegExp schemeRegEx = RegExp(r'[a-z]{3,6}://');
|
||||
// Not a perfect but good enough regular expression to match URLs in text.
|
||||
// It also matches a space at the beginning and a dot at the end,
|
||||
// so this is filtered out manually in the found matches
|
||||
static final RegExp linkRegEx = RegExp(
|
||||
r'(([a-z]{3,6}:\/\/)|(^|\s))([a-zA-Z0-9\-]+\.)+[a-z]{2,13}([\?\/]+[\.\?\=\&\%\/\w\+\-]*)?');
|
||||
const LinksTextTransformer();
|
||||
|
||||
@override
|
||||
String transform(String text, String message, TransformConfiguration configuration) {
|
||||
final matches = linkRegEx.allMatches(text);
|
||||
if (matches.isEmpty) {
|
||||
return text;
|
||||
}
|
||||
final buffer = StringBuffer();
|
||||
var end = 0;
|
||||
for (final match in matches) {
|
||||
if (match.end < text.length && text[match.end] == '@') {
|
||||
// this is an email address, abort abort!
|
||||
continue;
|
||||
}
|
||||
final originalGroup = match.group(0)!;
|
||||
final group = originalGroup.trimLeft();
|
||||
final start = match.start + originalGroup.length - group.length;
|
||||
buffer.write(text.substring(end, start));
|
||||
final endsWithDot = group.endsWith('.');
|
||||
final urlText =
|
||||
endsWithDot ? group.substring(0, group.length - 1) : group;
|
||||
buffer.write('<a href="');
|
||||
if (!group.startsWith(schemeRegEx)) {
|
||||
buffer.write('https://');
|
||||
}
|
||||
buffer..write(urlText)..write('">')..write(urlText)..write('</a>');
|
||||
end = endsWithDot ? match.end - 1 : match.end;
|
||||
}
|
||||
if (end < text.length) {
|
||||
buffer.write(text.substring(end));
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,7 @@ import 'package:core/presentation/utils/html_transformer/base/dom_transformer.da
|
||||
import 'package:core/presentation/utils/html_transformer/base/text_transformer.dart';
|
||||
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/link_transformers.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/dom/meta_transformers.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/dom/script_transformers.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/text/convert_tags_text_transformer.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/text/linebreak_text_transformer.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/text/links_text_transformer.dart';
|
||||
|
||||
/// Contains the configuration for all transformations.
|
||||
class TransformConfiguration {
|
||||
@@ -37,37 +32,26 @@ class TransformConfiguration {
|
||||
///
|
||||
/// Any specified [customDomTransformers] or [customTextTransformers] are being appended to the standard transformers.
|
||||
static TransformConfiguration create({
|
||||
bool? blockExternalImages,
|
||||
int? maxImageWidth,
|
||||
List<DomTransformer>? customDomTransformers,
|
||||
List<TextTransformer>? customTextTransformers
|
||||
}) {
|
||||
final domTransformers = (customDomTransformers != null)
|
||||
? [...standardDomTransformers, ...customDomTransformers]
|
||||
final domTransformers = (customDomTransformers != null && customDomTransformers.isNotEmpty)
|
||||
? [...customDomTransformers]
|
||||
: [...standardDomTransformers];
|
||||
final textTransformers = (customTextTransformers != null)
|
||||
? [...standardTextTransformers, ...customTextTransformers]
|
||||
final textTransformers = (customTextTransformers != null && customTextTransformers.isNotEmpty)
|
||||
? [...customTextTransformers]
|
||||
: standardTextTransformers;
|
||||
maxImageWidth ??= standardMaxImageWidth;
|
||||
return TransformConfiguration(
|
||||
domTransformers,
|
||||
textTransformers
|
||||
);
|
||||
}
|
||||
|
||||
static const int? standardMaxImageWidth = null;
|
||||
|
||||
static const List<DomTransformer> standardDomTransformers = [
|
||||
MetaTransformer(),
|
||||
RemoveScriptTransformer(),
|
||||
ImageTransformer(),
|
||||
LinkTransformer(),
|
||||
BlockQuotedTransformer(),
|
||||
ImageTransformer(),
|
||||
];
|
||||
|
||||
static const List<TextTransformer> standardTextTransformers = [
|
||||
ConvertTagsTextTransformer(),
|
||||
LinksTextTransformer(),
|
||||
LineBreakTextTransformer(),
|
||||
];
|
||||
static const List<TextTransformer> standardTextTransformers = [];
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import 'dart:convert';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:core/presentation/extensions/color_extension.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/html_template.dart';
|
||||
import 'package:core/presentation/views/html_viewer/html_viewer_controller_for_web.dart';
|
||||
import 'package:core/utils/app_logger.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -36,16 +37,16 @@ class _HtmlContentViewerOnWebState extends State<HtmlContentViewerOnWeb> {
|
||||
|
||||
/// The view ID for the IFrameElement. Must be unique.
|
||||
late String createdViewId;
|
||||
/// The actual height of the editor, used to automatically set the height
|
||||
/// The actual height of the content view, used to automatically set the height
|
||||
late double actualHeight;
|
||||
/// The actual width of the editor, used to automatically set the width
|
||||
/// The actual width of the content view, used to automatically set the width
|
||||
late double actualWidth;
|
||||
|
||||
Future<bool>? webInit;
|
||||
String? _htmlData;
|
||||
bool _isLoading = true;
|
||||
int minHeight = 100;
|
||||
int minWidth = 300;
|
||||
double minHeight = 100;
|
||||
double minWidth = 300;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -64,7 +65,7 @@ class _HtmlContentViewerOnWebState extends State<HtmlContentViewerOnWeb> {
|
||||
}
|
||||
|
||||
String _generateHtmlDocument(String content) {
|
||||
final htmlScripts = '''
|
||||
final webViewActionScripts = '''
|
||||
<script type="text/javascript">
|
||||
window.parent.addEventListener('message', handleMessage, false);
|
||||
window.addEventListener('click', handleOnClickLink, true);
|
||||
@@ -122,63 +123,11 @@ class _HtmlContentViewerOnWebState extends State<HtmlContentViewerOnWeb> {
|
||||
</script>
|
||||
''';
|
||||
|
||||
final tooltipLinkCss = '''
|
||||
.tooltip .tooltiptext {
|
||||
visibility: hidden;
|
||||
max-width: 400px;
|
||||
background-color: black;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
border-radius: 6px;
|
||||
padding: 5px 8px 5px 8px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
}
|
||||
.tooltip:hover .tooltiptext {
|
||||
visibility: visible;
|
||||
}
|
||||
''';
|
||||
|
||||
final htmlTemplate = '''
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<style>
|
||||
#editor {
|
||||
outline: 0px solid transparent;
|
||||
min-height: ${minHeight}px;
|
||||
min-width: ${minWidth}px;
|
||||
color: #000000;
|
||||
font-family: Inter;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
td {
|
||||
padding: 13px;
|
||||
margin: 0px;
|
||||
}
|
||||
th {
|
||||
padding: 13px;
|
||||
margin: 0px;
|
||||
}
|
||||
$tooltipLinkCss
|
||||
</style>
|
||||
$htmlScripts
|
||||
</head>
|
||||
<body>
|
||||
<div id="editor">$content</div>
|
||||
</body>
|
||||
</html>
|
||||
''';
|
||||
final htmlTemplate = generateHtml(content,
|
||||
minHeight: minHeight,
|
||||
minWidth: minWidth,
|
||||
styleCSS: tooltipLinkCss,
|
||||
javaScripts: webViewActionScripts);
|
||||
|
||||
return htmlTemplate;
|
||||
}
|
||||
|
||||
@@ -43,7 +43,8 @@ class _HtmlContentViewState extends State<HtmlContentViewer> {
|
||||
|
||||
double? _webViewHeight = 1.0;
|
||||
double? _webViewWidth = 1.0;
|
||||
int minHeight = 100;
|
||||
double minHeight = 100;
|
||||
double minWidth = 300;
|
||||
String? _htmlData;
|
||||
late WebViewController _webViewController;
|
||||
bool _isLoading = true;
|
||||
@@ -56,47 +57,7 @@ class _HtmlContentViewState extends State<HtmlContentViewer> {
|
||||
}
|
||||
|
||||
String _generateHtmlDocument(String content) {
|
||||
final htmlTemplate = '''
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<style>
|
||||
#editor {
|
||||
outline: 0px solid transparent;
|
||||
min-height: ${minHeight}px;
|
||||
min-width: 300px;
|
||||
color: #182952;
|
||||
font-family: verdana;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
td {
|
||||
padding: 13px;
|
||||
margin: 0px;
|
||||
}
|
||||
th {
|
||||
padding: 13px;
|
||||
margin: 0px;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
var documentHeight;
|
||||
|
||||
function onLoaded() {
|
||||
documentHeight = document.body.scrollHeight;
|
||||
document.execCommand("styleWithCSS", false, true);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="onLoaded();">
|
||||
<div id="editor">$content</div>
|
||||
</body>
|
||||
</html>
|
||||
''';
|
||||
final htmlTemplate = generateHtml(content);
|
||||
return htmlTemplate;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,4 +6,6 @@ abstract class HtmlDataSource {
|
||||
EmailContent emailContent,
|
||||
Map<String, String> mapUrlDownloadCID
|
||||
);
|
||||
|
||||
Future<EmailContent> addTooltipWhenHoverOnLink(EmailContent emailContent);
|
||||
}
|
||||
@@ -21,4 +21,13 @@ class HtmlDataSourceImpl extends HtmlDataSource {
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<EmailContent> addTooltipWhenHoverOnLink(EmailContent emailContent) {
|
||||
return Future.sync(() async {
|
||||
return await _htmlAnalyzer.addTooltipWhenHoverOnLink(emailContent);
|
||||
}).catchError((error) {
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
|
||||
import 'package:core/core.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/html_transform.dart';
|
||||
import 'package:model/model.dart';
|
||||
|
||||
class HtmlAnalyzer {
|
||||
@@ -14,12 +13,24 @@ class HtmlAnalyzer {
|
||||
case EmailContentType.textHtml:
|
||||
final htmlTransform = HtmlTransform(
|
||||
emailContent.content,
|
||||
dioClient,
|
||||
mapUrlDownloadCID);
|
||||
dioClient: dioClient,
|
||||
mapUrlDownloadCID: mapUrlDownloadCID);
|
||||
final htmlContent = await htmlTransform.transformToHtml();
|
||||
return EmailContent(emailContent.type, htmlContent);
|
||||
default:
|
||||
return emailContent;
|
||||
}
|
||||
}
|
||||
|
||||
Future<EmailContent> addTooltipWhenHoverOnLink(EmailContent emailContent) async {
|
||||
switch(emailContent.type) {
|
||||
case EmailContentType.textHtml:
|
||||
final htmlTransform = HtmlTransform(emailContent.content);
|
||||
final htmlContent = await htmlTransform.transformToHtml(
|
||||
transformConfiguration: TransformConfiguration.create(customDomTransformers: [AddTooltipLinkTransformer()]));
|
||||
return EmailContent(emailContent.type, htmlContent);
|
||||
default:
|
||||
return emailContent;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -124,4 +124,11 @@ class EmailRepositoryImpl extends EmailRepository {
|
||||
Future<bool> deleteEmailPermanently(AccountId accountId, EmailId emailId) {
|
||||
return emailDataSource.deleteEmailPermanently(accountId, emailId);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<EmailContent>> addTooltipWhenHoverOnLink(List<EmailContent> emailContents) {
|
||||
return Future.wait(emailContents
|
||||
.map((emailContent) => _htmlDataSource.addTooltipWhenHoverOnLink(emailContent))
|
||||
.toList());
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,8 @@ abstract class EmailRepository {
|
||||
AccountId accountId
|
||||
);
|
||||
|
||||
Future<List<EmailContent>> addTooltipWhenHoverOnLink(List<EmailContent> emailContents);
|
||||
|
||||
Future<Email?> saveEmailAsDrafts(AccountId accountId, Email email);
|
||||
|
||||
Future<bool> removeEmailDrafts(AccountId accountId, EmailId emailId);
|
||||
|
||||
@@ -3,12 +3,13 @@ import 'package:model/model.dart';
|
||||
|
||||
class GetEmailContentSuccess extends UIState {
|
||||
final List<EmailContent> emailContents;
|
||||
final List<EmailContent> emailContentsDisplayed;
|
||||
final List<Attachment> attachments;
|
||||
|
||||
GetEmailContentSuccess(this.emailContents, this.attachments);
|
||||
GetEmailContentSuccess(this.emailContents, this.emailContentsDisplayed, this.attachments);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [emailContents, attachments];
|
||||
List<Object?> get props => [emailContents, emailContentsDisplayed, attachments];
|
||||
}
|
||||
|
||||
class GetEmailContentFailure extends FeatureFailure {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:core/core.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:model/model.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:jmap_dart_client/jmap/account_id.dart';
|
||||
@@ -22,7 +23,10 @@ class GetEmailContentInteractor {
|
||||
email.allAttachments.listAttachmentsDisplayedInContent,
|
||||
baseDownloadUrl,
|
||||
accountId);
|
||||
yield Right<Failure, Success>(GetEmailContentSuccess(newEmailContents, email.allAttachments));
|
||||
final newEmailContentsDisplayed = kIsWeb
|
||||
? await emailRepository.addTooltipWhenHoverOnLink(newEmailContents)
|
||||
: newEmailContents;
|
||||
yield Right<Failure, Success>(GetEmailContentSuccess(newEmailContents, newEmailContentsDisplayed, email.allAttachments));
|
||||
} else {
|
||||
yield Left(GetEmailContentFailure(null));
|
||||
}
|
||||
|
||||
@@ -65,6 +65,7 @@ class EmailController extends BaseController {
|
||||
final emailContents = <EmailContent>[].obs;
|
||||
final attachments = <Attachment>[].obs;
|
||||
EmailId? _currentEmailId;
|
||||
List<EmailContent>? initialEmailContents;
|
||||
|
||||
PresentationMailbox? get currentMailbox => mailboxDashBoardController.selectedMailbox.value;
|
||||
|
||||
@@ -152,7 +153,8 @@ class EmailController extends BaseController {
|
||||
}
|
||||
|
||||
void _getEmailContentSuccess(GetEmailContentSuccess success) {
|
||||
emailContents.value = success.emailContents;
|
||||
emailContents.value = success.emailContentsDisplayed;
|
||||
initialEmailContents = success.emailContents;
|
||||
attachments.value = success.attachments;
|
||||
}
|
||||
|
||||
@@ -161,6 +163,7 @@ class EmailController extends BaseController {
|
||||
emailAddressExpandMode.value = ExpandMode.COLLAPSE;
|
||||
isDisplayFullEmailAddress.value = false;
|
||||
emailContents.clear();
|
||||
initialEmailContents?.clear();
|
||||
attachments.clear();
|
||||
}
|
||||
|
||||
@@ -592,7 +595,7 @@ class EmailController extends BaseController {
|
||||
final arguments = ComposerArguments(
|
||||
emailActionType: emailActionType,
|
||||
presentationEmail: mailboxDashBoardController.selectedEmail.value!,
|
||||
emailContents: emailContents,
|
||||
emailContents: initialEmailContents,
|
||||
attachments: attachments,
|
||||
mailboxRole: mailboxDashBoardController.selectedMailbox.value?.role);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user