Add lazy loading for html tag use background-image
Signed-off-by: dab246 <tdvu@linagora.com> (cherry picked from commit 7f1a3b22ee8973c6756534a2c7da3cc1964ea9de)
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
|
||||
import 'package:core/data/network/dio_client.dart';
|
||||
import 'package:core/utils/app_logger.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:html/dom.dart';
|
||||
|
||||
/// Transforms the HTML DOM.
|
||||
@@ -22,4 +24,26 @@ abstract class DomTransformer {
|
||||
document.children.insert(0, Element.html('<head></head>'));
|
||||
}
|
||||
}
|
||||
|
||||
Tuple2<String, String>? findImageUrlFromStyleTag(String style) {
|
||||
try {
|
||||
final regExp = RegExp(r'background-image:\s*url\(([^)]+)\).*?');
|
||||
final match = regExp.firstMatch(style);
|
||||
if (match == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final backgroundImageUrl = match.group(0) ?? '';
|
||||
final imageUrl = match.group(1)?.replaceAll('\'', '').replaceAll('"', '') ?? '';
|
||||
log('DomTransformer::findImageUrlFromStyleTag:backgroundImageUrl: $backgroundImageUrl | imageUrl: $imageUrl');
|
||||
if (backgroundImageUrl.isNotEmpty && imageUrl.isNotEmpty) {
|
||||
return Tuple2(backgroundImageUrl, imageUrl);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (e) {
|
||||
logError('DomTransformer::findImageUrlFromStyleTag:Exception: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
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 AddLazyLoadingForBackgroundImageTransformer extends DomTransformer {
|
||||
const AddLazyLoadingForBackgroundImageTransformer();
|
||||
|
||||
@override
|
||||
Future<void> process({
|
||||
required Document document,
|
||||
required DioClient dioClient,
|
||||
Map<String, String>? mapUrlDownloadCID,
|
||||
}) async {
|
||||
final elements = document.querySelectorAll('[style*="background-image"]');
|
||||
log('AddLazyLoadingForBackgroundImageTagTransformer::process:elements: ${elements.length}');
|
||||
await Future.wait(elements.map((element) async {
|
||||
var exStyle = element.attributes['style'];
|
||||
final imageUrls = findImageUrlFromStyleTag(exStyle!);
|
||||
if (imageUrls != null) {
|
||||
exStyle = exStyle.replaceFirst(imageUrls.value1, '');
|
||||
element.attributes['style'] = exStyle;
|
||||
element.attributes['data-src'] = imageUrls.value2;
|
||||
element.attributes.addAll({'lazy': ''});
|
||||
log('AddLazyLoadingForBackgroundImageTagTransformer::process:NEW_ELEMENT: ${element.outerHtml}');
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -72,6 +72,29 @@ class HtmlUtils {
|
||||
</script>
|
||||
''';
|
||||
|
||||
static const scriptsHandleLazyLoadingBackgroundImage = '''
|
||||
<script>
|
||||
const lazyImages = document.querySelectorAll('[lazy]');
|
||||
const lazyImageObserver = new IntersectionObserver((entries, observer) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
const lazyImage = entry.target;
|
||||
const src = lazyImage.dataset.src;
|
||||
lazyImage.tagName.toLowerCase() === 'img'
|
||||
? lazyImage.src = src
|
||||
: lazyImage.style.backgroundImage = "url(\'" + src + "\')";
|
||||
lazyImage.removeAttribute('lazy');
|
||||
observer.unobserve(lazyImage);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
lazyImages.forEach((lazyImage) => {
|
||||
lazyImageObserver.observe(lazyImage);
|
||||
});
|
||||
</script>
|
||||
''';
|
||||
|
||||
static String customCssStyleHtmlEditor({TextDirection direction = TextDirection.ltr}) {
|
||||
if (PlatformInfo.isWeb) {
|
||||
return '''
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import 'package:core/core.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/base/dom_transformer.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/base/text_transformer.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/dom/add_lazy_loading_for_background_image_transformers.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/dom/add_target_blank_in_tag_a_transformers.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/dom/blockcode_transformers.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/dom/blockquoted_transformers.dart';
|
||||
@@ -48,6 +49,7 @@ class TransformConfiguration {
|
||||
const AddTargetBlankInTagATransformer(),
|
||||
const ImageTransformer(),
|
||||
const AddTooltipLinkTransformer(),
|
||||
const AddLazyLoadingForBackgroundImageTransformer(),
|
||||
]
|
||||
);
|
||||
|
||||
@@ -86,6 +88,7 @@ class TransformConfiguration {
|
||||
BlockCodeTransformer(),
|
||||
AddTargetBlankInTagATransformer(),
|
||||
ImageTransformer(),
|
||||
AddLazyLoadingForBackgroundImageTransformer(),
|
||||
];
|
||||
|
||||
static const List<TextTransformer> standardTextTransformers = [
|
||||
|
||||
@@ -47,6 +47,7 @@ class _HtmlContentViewState extends State<HtmlContentViewer> {
|
||||
late InAppWebViewController _webViewController;
|
||||
late double _actualHeight;
|
||||
late Set<Factory<OneSequenceGestureRecognizer>> _gestureRecognizers;
|
||||
late String _customScripts;
|
||||
|
||||
final _loadingBarNotifier = ValueNotifier(true);
|
||||
|
||||
@@ -60,7 +61,6 @@ class _HtmlContentViewState extends State<HtmlContentViewer> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_actualHeight = _minHeight;
|
||||
if (PlatformInfo.isAndroid) {
|
||||
_gestureRecognizers = {
|
||||
Factory<LongPressGestureRecognizer>(() => LongPressGestureRecognizer()),
|
||||
@@ -71,13 +71,12 @@ class _HtmlContentViewState extends State<HtmlContentViewer> {
|
||||
Factory<LongPressGestureRecognizer>(() => LongPressGestureRecognizer()),
|
||||
};
|
||||
}
|
||||
_htmlData = generateHtml(
|
||||
widget.contentHtml,
|
||||
direction: widget.direction,
|
||||
javaScripts: PlatformInfo.isAndroid
|
||||
? HtmlUtils.scriptsHandleContentSizeChanged
|
||||
: null
|
||||
);
|
||||
if (PlatformInfo.isAndroid) {
|
||||
_customScripts = HtmlUtils.scriptsHandleLazyLoadingBackgroundImage + HtmlUtils.scriptsHandleContentSizeChanged;
|
||||
} else {
|
||||
_customScripts = HtmlUtils.scriptsHandleLazyLoadingBackgroundImage;
|
||||
}
|
||||
_initialData();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -86,17 +85,19 @@ class _HtmlContentViewState extends State<HtmlContentViewer> {
|
||||
log('_HtmlContentViewState::didUpdateWidget():Old-Direction: ${oldWidget.direction} | Current-Direction: ${widget.direction}');
|
||||
if (widget.contentHtml != oldWidget.contentHtml ||
|
||||
widget.direction != oldWidget.direction) {
|
||||
_actualHeight = _minHeight;
|
||||
_htmlData = generateHtml(
|
||||
widget.contentHtml,
|
||||
direction: widget.direction,
|
||||
javaScripts: PlatformInfo.isAndroid
|
||||
? HtmlUtils.scriptsHandleContentSizeChanged
|
||||
: null
|
||||
);
|
||||
_initialData();
|
||||
}
|
||||
}
|
||||
|
||||
void _initialData() {
|
||||
_actualHeight = _minHeight;
|
||||
_htmlData = generateHtml(
|
||||
widget.contentHtml,
|
||||
direction: widget.direction,
|
||||
javaScripts: _customScripts
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(children: [
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import 'package:core/presentation/utils/html_transformer/dom/image_transformers.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
const imageTransformer = ImageTransformer();
|
||||
|
||||
group('findImageUrlFromStyleTag test', () {
|
||||
test('Test findImageUrlFromStyleTag with valid input', () {
|
||||
const style = 'background-image: url(\'example.com/image.jpg\');';
|
||||
final result = imageTransformer.findImageUrlFromStyleTag(style);
|
||||
expect(result!.value1, 'background-image: url(\'example.com/image.jpg\')');
|
||||
expect(result.value2, 'example.com/image.jpg');
|
||||
});
|
||||
|
||||
test('Test findImageUrlFromStyleTag with valid input and no quotation marks', () {
|
||||
const style = 'background-image: url(example.com/image.jpg);';
|
||||
final result = imageTransformer.findImageUrlFromStyleTag(style);
|
||||
expect(result!.value1, 'background-image: url(example.com/image.jpg)');
|
||||
expect(result.value2, 'example.com/image.jpg');
|
||||
});
|
||||
|
||||
test('Test findImageUrlFromStyleTag with empty input', () {
|
||||
const style = '';
|
||||
final result = imageTransformer.findImageUrlFromStyleTag(style);
|
||||
expect(result, null);
|
||||
});
|
||||
|
||||
test('Test findImageUrlFromStyleTag with invalid input', () {
|
||||
const style = 'background-image: invalid-url(\'example.com/image.jpg\');';
|
||||
final result = imageTransformer.findImageUrlFromStyleTag(style);
|
||||
expect(result, null);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user