From 57933736eaa440afca77c33deaf56992cd0263ec Mon Sep 17 00:00:00 2001 From: Lucas Date: Wed, 13 Nov 2024 17:16:48 +0800 Subject: [PATCH] feat: support embed webp (#6775) * feat: support uploading webp * feat: support jpeg and bmp image format --- .github/workflows/mobile_ci.yml | 11 +- .../document_with_image_block_test.dart | 118 ++++++++++-------- .../lib/shared/patterns/common_patterns.dart | 4 +- 3 files changed, 73 insertions(+), 60 deletions(-) diff --git a/.github/workflows/mobile_ci.yml b/.github/workflows/mobile_ci.yml index fbc17b7b70..4606a67799 100644 --- a/.github/workflows/mobile_ci.yml +++ b/.github/workflows/mobile_ci.yml @@ -45,16 +45,17 @@ jobs: id: check_status run: | while true; do - RESPONSE=$(curl -X GET \ + curl -X GET \ --header "Content-Type: application/json" \ --header "x-auth-token: $CODEMAGIC_API_TOKEN" \ - https://api.codemagic.io/builds/${{ steps.trigger_build.outputs.build_id }}) + https://api.codemagic.io/builds/${{ steps.trigger_build.outputs.build_id }} > /tmp/response.json - STATUS=$(echo $RESPONSE | jq -r '.build.status') + RESPONSE_WITHOUT_COMMAND=$(cat /tmp/response.json | jq 'walk(if type == "object" and has("subactions") then .subactions |= map(del(.command)) else . end)') + STATUS=$(echo $RESPONSE_WITHOUT_COMMAND | jq -r '.build.status') if [ "$STATUS" = "finished" ]; then - SUCCESS=$(echo $RESPONSE | jq -r '.success') - BUILD_URL=$(echo $RESPONSE | jq -r '.buildUrl') + SUCCESS=$(echo $RESPONSE_WITHOUT_COMMAND | jq -r '.success') + BUILD_URL=$(echo $RESPONSE_WITHOUT_COMMAND | jq -r '.buildUrl') echo "status=$STATUS" >> $GITHUB_OUTPUT echo "success=$SUCCESS" >> $GITHUB_OUTPUT echo "build_url=$BUILD_URL" >> $GITHUB_OUTPUT diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_with_image_block_test.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_with_image_block_test.dart index 7d500b600f..b6c2e4cc8c 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/document/document_with_image_block_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_with_image_block_test.dart @@ -24,11 +24,71 @@ import 'package:run_with_network_images/run_with_network_images.dart'; import '../../shared/mock/mock_file_picker.dart'; import '../../shared/util.dart'; +const _testImageUrls = [ + 'https://images.unsplash.com/photo-1469474968028-56623f02e42e?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&dl=david-marcu-78A265wPiO4-unsplash.jpg&w=640', + 'https://mathiasbynens.be/demo/animated-webp-supported.webp', + 'https://www.easygifanimator.net/images/samples/eglite.gif', + 'https://people.math.sc.edu/Burkardt/data/bmp/snail.bmp', + 'https://file-examples.com/storage/fe9566cb7d67345489a5a97/2017/10/file_example_JPG_100kB.jpg', +]; + void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized(); group('image block in document', () { + Future testEmbedImage(WidgetTester tester, String url) async { + await tester.initializeAppFlowy(); + await tester.tapAnonymousSignInButton(); + + // create a new document + await tester.createNewPageWithNameUnderParent( + name: LocaleKeys.document_plugins_image_addAnImageDesktop.tr(), + ); + + // tap the first line of the document + await tester.editor.tapLineOfEditorAt(0); + await tester.editor.showSlashMenu(); + await tester.editor.tapSlashMenuItemWithName( + LocaleKeys.document_slashMenu_name_image.tr(), + ); + expect(find.byType(CustomImageBlockComponent), findsOneWidget); + expect(find.byType(ImagePlaceholder), findsOneWidget); + expect( + find.descendant( + of: find.byType(ImagePlaceholder), + matching: find.byType(AppFlowyPopover), + ), + findsOneWidget, + ); + expect(find.byType(UploadImageMenu), findsOneWidget); + + await tester.tapButtonWithName( + LocaleKeys.document_imageBlock_embedLink_label.tr(), + ); + await tester.enterText( + find.descendant( + of: find.byType(EmbedImageUrlWidget), + matching: find.byType(TextField), + ), + url, + ); + await tester.tapButton( + find.descendant( + of: find.byType(EmbedImageUrlWidget), + matching: find.text( + LocaleKeys.document_imageBlock_embedLink_label.tr(), + findRichText: true, + ), + ), + ); + await tester.pumpAndSettle(); + expect(find.byType(ResizableImage), findsOneWidget); + final node = tester.editor.getCurrentEditorState().getNodeAtPath([0])!; + expect(node.type, ImageBlockKeys.type); + expect(node.attributes[ImageBlockKeys.url], url); + } + testWidgets('insert an image from local file', (tester) async { await tester.initializeAppFlowy(); await tester.tapAnonymousSignInButton(); @@ -79,59 +139,11 @@ void main() { file.deleteSync(); }); - testWidgets('insert an image from network', (tester) async { - await tester.initializeAppFlowy(); - await tester.tapAnonymousSignInButton(); - - // create a new document - await tester.createNewPageWithNameUnderParent( - name: LocaleKeys.document_plugins_image_addAnImageDesktop.tr(), - ); - - // tap the first line of the document - await tester.editor.tapLineOfEditorAt(0); - await tester.editor.showSlashMenu(); - await tester.editor.tapSlashMenuItemWithName( - LocaleKeys.document_slashMenu_name_image.tr(), - ); - expect(find.byType(CustomImageBlockComponent), findsOneWidget); - expect(find.byType(ImagePlaceholder), findsOneWidget); - expect( - find.descendant( - of: find.byType(ImagePlaceholder), - matching: find.byType(AppFlowyPopover), - ), - findsOneWidget, - ); - expect(find.byType(UploadImageMenu), findsOneWidget); - - await tester.tapButtonWithName( - LocaleKeys.document_imageBlock_embedLink_label.tr(), - ); - const url = - 'https://images.unsplash.com/photo-1469474968028-56623f02e42e?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&dl=david-marcu-78A265wPiO4-unsplash.jpg&w=640'; - await tester.enterText( - find.descendant( - of: find.byType(EmbedImageUrlWidget), - matching: find.byType(TextField), - ), - url, - ); - await tester.tapButton( - find.descendant( - of: find.byType(EmbedImageUrlWidget), - matching: find.text( - LocaleKeys.document_imageBlock_embedLink_label.tr(), - findRichText: true, - ), - ), - ); - await tester.pumpAndSettle(); - expect(find.byType(ResizableImage), findsOneWidget); - final node = tester.editor.getCurrentEditorState().getNodeAtPath([0])!; - expect(node.type, ImageBlockKeys.type); - expect(node.attributes[ImageBlockKeys.url], url); - }); + for (final url in _testImageUrls) { + testWidgets('insert an image from network: $url', (tester) async { + await testEmbedImage(tester, url); + }); + } testWidgets('insert an image from unsplash', (tester) async { await runWithNetworkImages(() async { diff --git a/frontend/appflowy_flutter/lib/shared/patterns/common_patterns.dart b/frontend/appflowy_flutter/lib/shared/patterns/common_patterns.dart index e7181d8538..20444f7205 100644 --- a/frontend/appflowy_flutter/lib/shared/patterns/common_patterns.dart +++ b/frontend/appflowy_flutter/lib/shared/patterns/common_patterns.dart @@ -7,10 +7,10 @@ final hrefRegex = RegExp(_hrefPattern); /// This pattern allows for both HTTP and HTTPS Scheme /// It allows for query parameters -/// It only allows the following image extensions: .png, .jpg, .gif, .webm +/// It only allows the following image extensions: .png, .jpg, .jpeg, .gif, .webm, .webp, .bmp /// const _imgUrlPattern = - r'(https?:\/\/)([^\s(["<,>/]*)(\/)[^\s[",><]*(.png|.jpg|.gif|.webm)(\?[^\s[",><]*)?'; + r'(https?:\/\/)([^\s(["<,>/]*)(\/)[^\s[",><]*(.png|.jpg|.jpeg|.gif|.webm|.webp|.bmp)(\?[^\s[",><]*)?'; final imgUrlRegex = RegExp(_imgUrlPattern); /// This pattern allows for both HTTP and HTTPS Scheme