Browse Source

fix: cannot click on links (#3017)

Lucas.Xu 1 year ago
parent
commit
706a5e784f

+ 0 - 1
frontend/appflowy_flutter/integration_test/database_row_page_test.dart

@@ -8,7 +8,6 @@ import 'package:integration_test/integration_test.dart';
 
 import 'util/database_test_op.dart';
 import 'util/emoji.dart';
-import 'util/ime.dart';
 import 'util/util.dart';
 
 void main() {

+ 2 - 0
frontend/appflowy_flutter/integration_test/document/document_test_runner.dart

@@ -9,6 +9,7 @@ import 'document_with_inline_math_equation_test.dart'
 import 'document_with_inline_page_test.dart' as document_with_inline_page_test;
 import 'document_with_toggle_list_test.dart' as document_with_toggle_list_test;
 import 'edit_document_test.dart' as document_edit_test;
+import 'document_with_outline_block_test.dart' as document_with_outline_block;
 
 void startTesting() {
   IntegrationTestWidgetsFlutterBinding.ensureInitialized();
@@ -20,5 +21,6 @@ void startTesting() {
   document_with_inline_page_test.main();
   document_with_inline_math_equation_test.main();
   document_with_cover_image_test.main();
+  document_with_outline_block.main();
   document_with_toggle_list_test.main();
 }

+ 0 - 1
frontend/appflowy_flutter/integration_test/document/document_with_inline_math_equation_test.dart

@@ -8,7 +8,6 @@ import 'package:flutter/material.dart';
 import 'package:flutter_test/flutter_test.dart';
 import 'package:integration_test/integration_test.dart';
 
-import '../util/ime.dart';
 import '../util/util.dart';
 
 void main() {

+ 85 - 0
frontend/appflowy_flutter/integration_test/document/document_with_link_test.dart

@@ -0,0 +1,85 @@
+import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
+import 'package:appflowy_editor/appflowy_editor.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:integration_test/integration_test.dart';
+import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart';
+
+import '../util/util.dart';
+
+void main() {
+  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+
+  group('test editing link in document', () {
+    late MockUrlLauncher mock;
+
+    setUp(() {
+      mock = MockUrlLauncher();
+      UrlLauncherPlatform.instance = mock;
+    });
+
+    testWidgets('insert/edit/open link', (tester) async {
+      await tester.initializeAppFlowy();
+      await tester.tapGoButton();
+
+      // create a new document
+      await tester.createNewPageWithName(
+        ViewLayoutPB.Document,
+      );
+
+      // tap the first line of the document
+      await tester.editor.tapLineOfEditorAt(0);
+      // insert a inline page
+      const link = 'AppFlowy';
+      await tester.ime.insertText(link);
+      await tester.editor.updateSelection(
+        Selection.single(path: [0], startOffset: 0, endOffset: link.length),
+      );
+
+      // tap the link button
+      final linkButton = find.byTooltip(
+        'Link',
+      );
+      await tester.tapButton(linkButton);
+      expect(find.text('Add your link', findRichText: true), findsOneWidget);
+
+      // input the link
+      const url = 'https://appflowy.io';
+      final textField = find.byWidgetPredicate(
+        (widget) => widget is TextField && widget.decoration!.hintText == 'URL',
+      );
+      await tester.enterText(textField, url);
+      await tester.testTextInput.receiveAction(TextInputAction.done);
+      await tester.pumpAndSettle();
+
+      // single-click the link menu to show the menu
+      await tester.tapButton(find.text(link, findRichText: true));
+      expect(find.text('Open link', findRichText: true), findsOneWidget);
+      expect(find.text('Copy link', findRichText: true), findsOneWidget);
+      expect(find.text('Remove link', findRichText: true), findsOneWidget);
+
+      // double-click the link menu to open the link
+      mock
+        ..setLaunchExpectations(
+          url: url,
+          useSafariVC: false,
+          useWebView: false,
+          universalLinksOnly: false,
+          enableJavaScript: true,
+          enableDomStorage: true,
+          headers: <String, String>{},
+          webOnlyWindowName: null,
+          launchMode: PreferredLaunchMode.platformDefault,
+        )
+        ..setResponse(true);
+
+      await tester.simulateKeyEvent(LogicalKeyboardKey.escape);
+      await tester.doubleTapAt(
+        tester.getTopLeft(find.text(link, findRichText: true)).translate(5, 5),
+      );
+      expect(mock.canLaunchCalled, isTrue);
+      expect(mock.launchCalled, isTrue);
+    });
+  });
+}

+ 0 - 1
frontend/appflowy_flutter/integration_test/plugins/outline_block_test.dart → frontend/appflowy_flutter/integration_test/document/document_with_outline_block_test.dart

@@ -5,7 +5,6 @@ import 'package:easy_localization/easy_localization.dart';
 import 'package:flutter_test/flutter_test.dart';
 import 'package:integration_test/integration_test.dart';
 
-import '../util/ime.dart';
 import '../util/util.dart';
 
 void main() {

+ 0 - 1
frontend/appflowy_flutter/integration_test/document/document_with_toggle_list_test.dart

@@ -8,7 +8,6 @@ import 'package:flutter/services.dart';
 import 'package:flutter_test/flutter_test.dart';
 import 'package:integration_test/integration_test.dart';
 
-import '../util/ime.dart';
 import '../util/util.dart';
 
 void main() {

+ 0 - 1
frontend/appflowy_flutter/integration_test/document/edit_document_test.dart

@@ -6,7 +6,6 @@ import 'package:flutter/services.dart';
 import 'package:flutter_test/flutter_test.dart';
 import 'package:integration_test/integration_test.dart';
 
-import '../util/ime.dart';
 import '../util/util.dart';
 
 void main() {

+ 0 - 1
frontend/appflowy_flutter/integration_test/util/editor_test_operations.dart

@@ -12,7 +12,6 @@ import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
 import 'package:flutter_test/flutter_test.dart';
 
-import 'ime.dart';
 import 'util.dart';
 
 extension EditorWidgetTester on WidgetTester {

+ 110 - 0
frontend/appflowy_flutter/integration_test/util/mock/mock_url_launcher.dart

@@ -0,0 +1,110 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter_test/flutter_test.dart';
+import 'package:plugin_platform_interface/plugin_platform_interface.dart';
+import 'package:url_launcher_platform_interface/link.dart';
+import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart';
+
+class MockUrlLauncher extends Fake
+    with MockPlatformInterfaceMixin
+    implements UrlLauncherPlatform {
+  String? url;
+  PreferredLaunchMode? launchMode;
+  bool? useSafariVC;
+  bool? useWebView;
+  bool? enableJavaScript;
+  bool? enableDomStorage;
+  bool? universalLinksOnly;
+  Map<String, String>? headers;
+  String? webOnlyWindowName;
+
+  bool? response;
+
+  bool closeWebViewCalled = false;
+  bool canLaunchCalled = false;
+  bool launchCalled = false;
+
+  // ignore: use_setters_to_change_properties
+  void setCanLaunchExpectations(String url) {
+    this.url = url;
+  }
+
+  void setLaunchExpectations({
+    required String url,
+    PreferredLaunchMode? launchMode,
+    bool? useSafariVC,
+    bool? useWebView,
+    required bool enableJavaScript,
+    required bool enableDomStorage,
+    required bool universalLinksOnly,
+    required Map<String, String> headers,
+    required String? webOnlyWindowName,
+  }) {
+    this.url = url;
+    this.launchMode = launchMode;
+    this.useSafariVC = useSafariVC;
+    this.useWebView = useWebView;
+    this.enableJavaScript = enableJavaScript;
+    this.enableDomStorage = enableDomStorage;
+    this.universalLinksOnly = universalLinksOnly;
+    this.headers = headers;
+    this.webOnlyWindowName = webOnlyWindowName;
+  }
+
+  // ignore: use_setters_to_change_properties
+  void setResponse(bool response) {
+    this.response = response;
+  }
+
+  @override
+  LinkDelegate? get linkDelegate => null;
+
+  @override
+  Future<bool> canLaunch(String url) async {
+    expect(url, this.url);
+    canLaunchCalled = true;
+    return response!;
+  }
+
+  @override
+  Future<bool> launch(
+    String url, {
+    required bool useSafariVC,
+    required bool useWebView,
+    required bool enableJavaScript,
+    required bool enableDomStorage,
+    required bool universalLinksOnly,
+    required Map<String, String> headers,
+    String? webOnlyWindowName,
+  }) async {
+    expect(url, this.url);
+    expect(useSafariVC, this.useSafariVC);
+    expect(useWebView, this.useWebView);
+    expect(enableJavaScript, this.enableJavaScript);
+    expect(enableDomStorage, this.enableDomStorage);
+    expect(universalLinksOnly, this.universalLinksOnly);
+    expect(headers, this.headers);
+    expect(webOnlyWindowName, this.webOnlyWindowName);
+    launchCalled = true;
+    return response!;
+  }
+
+  @override
+  Future<bool> launchUrl(String url, LaunchOptions options) async {
+    expect(url, this.url);
+    expect(options.mode, launchMode);
+    expect(options.webViewConfiguration.enableJavaScript, enableJavaScript);
+    expect(options.webViewConfiguration.enableDomStorage, enableDomStorage);
+    expect(options.webViewConfiguration.headers, headers);
+    expect(options.webOnlyWindowName, webOnlyWindowName);
+    launchCalled = true;
+    return response!;
+  }
+
+  @override
+  Future<void> closeWebView() async {
+    closeWebViewCalled = true;
+  }
+}

+ 2 - 0
frontend/appflowy_flutter/integration_test/util/util.dart

@@ -4,3 +4,5 @@ export 'settings.dart';
 export 'data.dart';
 export 'expectation.dart';
 export 'editor_test_operations.dart';
+export 'mock/mock_url_launcher.dart';
+export 'ime.dart';

+ 1 - 0
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart

@@ -357,6 +357,7 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
       outlineItem,
       mathEquationItem,
       codeBlockItem,
+      toggleListBlockItem,
       emojiMenuItem,
       autoGeneratorMenuItem,
     ];

+ 11 - 0
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toggle/toggle_block_component.dart

@@ -1,4 +1,6 @@
+import 'package:appflowy/generated/locale_keys.g.dart';
 import 'package:appflowy_editor/appflowy_editor.dart';
+import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flutter/material.dart';
 
@@ -42,6 +44,15 @@ Node toggleListBlockNode({
   );
 }
 
+// defining the toggle list block menu item
+SelectionMenuItem toggleListBlockItem = SelectionMenuItem.node(
+  name: LocaleKeys.document_plugins_toggleList.tr(),
+  iconData: Icons.arrow_right,
+  keywords: ['collapsed list', 'toggle list', 'list'],
+  nodeBuilder: (editorState) => toggleListBlockNode(),
+  replace: (_, node) => node.delta?.isEmpty ?? false,
+);
+
 class ToggleListBlockComponentBuilder extends BlockComponentBuilder {
   ToggleListBlockComponentBuilder({
     this.configuration = const BlockComponentConfiguration(),

+ 7 - 1
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_style.dart

@@ -220,6 +220,12 @@ class EditorStyleCustomizer {
       );
     }
 
-    return textSpan;
+    return defaultTextSpanDecoratorForAttribute(
+      context,
+      node,
+      index,
+      text,
+      textSpan,
+    );
   }
 }

+ 4 - 4
frontend/appflowy_flutter/pubspec.lock

@@ -53,8 +53,8 @@ packages:
     dependency: "direct main"
     description:
       path: "."
-      ref: "33b18d9"
-      resolved-ref: "33b18d98dcc6db996eef3d6b869f293da3da3615"
+      ref: "023f3c8"
+      resolved-ref: "023f3c835dc427a932bb2022a0d213c0084ffb99"
       url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
     source: git
     version: "1.2.0"
@@ -1002,7 +1002,7 @@ packages:
     source: hosted
     version: "3.1.0"
   plugin_platform_interface:
-    dependency: transitive
+    dependency: "direct dev"
     description:
       name: plugin_platform_interface
       sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc"
@@ -1567,7 +1567,7 @@ packages:
     source: hosted
     version: "3.0.5"
   url_launcher_platform_interface:
-    dependency: transitive
+    dependency: "direct dev"
     description:
       name: url_launcher_platform_interface
       sha256: "6c9ca697a5ae218ce56cece69d46128169a58aa8653c1b01d26fcd4aad8c4370"

+ 3 - 0
frontend/appflowy_flutter/pubspec.yaml

@@ -115,6 +115,9 @@ dev_dependencies:
   json_serializable: ^6.7.0
   envied_generator: ^0.3.0+3
 
+  plugin_platform_interface: any
+  url_launcher_platform_interface: any
+
 dependency_overrides:
   http: ^1.0.0
 

+ 1 - 0
frontend/resources/translations/en.json

@@ -453,6 +453,7 @@
       "smartEditDisabled": "Connect OpenAI in Settings",
       "discardResponse": "Do you want to discard the AI responses?",
       "createInlineMathEquation": "Create equation",
+      "toggleList": "Toggle List",
       "cover": {
         "changeCover": "Change Cover",
         "colors": "Colors",