Sfoglia il codice sorgente

fix: unable insert a reference database (#2798)

* fix: unable insert a reference database

* test: add reference database tests

* feat: set min height for document inside database
Lucas.Xu 1 anno fa
parent
commit
d5884ad2b5

+ 1 - 1
frontend/appflowy_flutter/integration_test/cover_image_test.dart

@@ -28,7 +28,7 @@ void main() {
       await tester.initializeAppFlowy();
 
       await tester.tapGoButton();
-      await tester.hoverOnCoverPluginAddButton();
+      await tester.editor.hoverOnCoverPluginAddButton();
 
       tester.expectToSeePluginAddCoverAndIconButton();
     });

+ 110 - 0
frontend/appflowy_flutter/integration_test/document_with_database_test.dart

@@ -0,0 +1,110 @@
+import 'package:appflowy/plugins/database_view/board/presentation/board_page.dart';
+import 'package:appflowy/plugins/database_view/grid/presentation/grid_page.dart';
+import 'package:appflowy/plugins/document/presentation/editor_plugins/base/link_to_page_widget.dart';
+import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
+import 'package:appflowy_editor/appflowy_editor.dart';
+import 'package:flowy_infra/uuid.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:integration_test/integration_test.dart';
+
+import 'util/util.dart';
+
+void main() {
+  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+
+  group('database view in document', () {
+    const location = 'database_view';
+
+    setUp(() async {
+      await TestFolder.cleanTestLocation(location);
+      await TestFolder.setTestLocation(location);
+    });
+
+    tearDown(() async {
+      await TestFolder.cleanTestLocation(null);
+    });
+
+    testWidgets('insert a referenced grid', (tester) async {
+      await tester.initializeAppFlowy();
+      await tester.tapGoButton();
+
+      await insertReferenceDatabase(tester, ViewLayoutPB.Grid);
+
+      // validate the referenced grid is inserted
+      expect(
+        find.descendant(
+          of: find.byType(AppFlowyEditor),
+          matching: find.byType(GridPage),
+        ),
+        findsOneWidget,
+      );
+    });
+
+    testWidgets('insert a referenced board', (tester) async {
+      await tester.initializeAppFlowy();
+      await tester.tapGoButton();
+
+      await insertReferenceDatabase(tester, ViewLayoutPB.Board);
+
+      // validate the referenced board is inserted
+      expect(
+        find.descendant(
+          of: find.byType(AppFlowyEditor),
+          matching: find.byType(BoardPage),
+        ),
+        findsOneWidget,
+      );
+    });
+
+    // testWidgets('insert a referenced calendar', (tester) async {
+    //   await tester.initializeAppFlowy();
+    //   await tester.tapGoButton();
+
+    //   await insertReferenceDatabase(tester, ViewLayoutPB.Calendar);
+
+    //   // validate the referenced grid is inserted
+    //   expect(
+    //     find.descendant(
+    //       of: find.byType(AppFlowyEditor),
+    //       matching: find.byType(CalendarPage),
+    //     ),
+    //     findsOneWidget,
+    //   );
+    // });
+  });
+}
+
+/// Insert a referenced database of [layout] into the document
+Future<void> insertReferenceDatabase(
+  WidgetTester tester,
+  ViewLayoutPB layout,
+) async {
+  // create a new grid
+  final id = uuid();
+  final name = '${layout.name}_$id';
+  await tester.createNewPageWithName(
+    layout,
+    name,
+  );
+  // create a new document
+  await tester.createNewPageWithName(
+    ViewLayoutPB.Document,
+    'insert_a_reference_${layout.name}',
+  );
+  // tap the first line of the document
+  await tester.editor.tapLineOfEditorAt(0);
+  // insert a referenced grid
+  await tester.editor.showSlashMenu();
+  await tester.editor.tapSlashMenuItemWithName(
+    layout.referencedMenuName,
+  );
+
+  final linkToPageMenu = find.byType(LinkToPageMenu);
+  expect(linkToPageMenu, findsOneWidget);
+  final referencedDatabase = find.descendant(
+    of: linkToPageMenu,
+    matching: find.findTextInFlowyText(name),
+  );
+  expect(referencedDatabase, findsOneWidget);
+  await tester.tapButton(referencedDatabase);
+}

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

@@ -5,6 +5,7 @@ import 'document_test.dart' as document_test;
 import 'cover_image_test.dart' as cover_image_test;
 import 'share_markdown_test.dart' as share_markdown_test;
 import 'import_files_test.dart' as import_files_test;
+import 'document_with_database_test.dart' as document_with_database_test;
 
 /// The main task runner for all integration tests in AppFlowy.
 ///
@@ -20,6 +21,7 @@ void main() {
   document_test.main();
   share_markdown_test.main();
   import_files_test.main();
+  document_with_database_test.main();
   // board_test.main();
   // empty_document_test.main();
   // smart_menu_test.main();

+ 8 - 0
frontend/appflowy_flutter/integration_test/util/base.dart

@@ -126,3 +126,11 @@ extension AppFlowyTestBase on WidgetTester {
     return;
   }
 }
+
+extension AppFlowyFinderTestBase on CommonFinders {
+  Finder findTextInFlowyText(String text) {
+    return find.byWidgetPredicate(
+      (widget) => widget is FlowyText && widget.title == text,
+    );
+  }
+}

+ 61 - 19
frontend/appflowy_flutter/integration_test/util/common_operations.dart

@@ -7,7 +7,7 @@ import 'package:appflowy/user/presentation/skip_log_in_screen.dart';
 import 'package:appflowy/workspace/presentation/home/menu/app/header/add_button.dart';
 import 'package:appflowy/workspace/presentation/home/menu/app/section/item.dart';
 import 'package:appflowy/workspace/presentation/settings/widgets/settings_language_view.dart';
-import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
+import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra_ui/widget/buttons/primary_button.dart';
 import 'package:flutter/material.dart';
@@ -112,20 +112,32 @@ extension CommonOperations on WidgetTester {
   Future<void> hoverOnWidget(
     Finder finder, {
     Offset? offset,
+    Future<void> Function()? onHover,
   }) async {
     try {
       final gesture = await createGesture(kind: PointerDeviceKind.mouse);
       await gesture.addPointer(location: Offset.zero);
-      addTearDown(gesture.removePointer);
       await pump();
       await gesture.moveTo(offset ?? getCenter(finder));
       await pumpAndSettle();
-    } catch (_) {}
+      await onHover?.call();
+      await gesture.removePointer();
+    } catch (err) {
+      Log.error('hoverOnWidget error: $err');
+    }
   }
 
   /// Hover on the page name.
-  Future<void> hoverOnPageName(String name) async {
-    await hoverOnWidget(findPageName(name));
+  Future<void> hoverOnPageName(
+    String name, {
+    Future<void> Function()? onHover,
+    bool useLast = true,
+  }) async {
+    if (useLast) {
+      await hoverOnWidget(findPageName(name).last, onHover: onHover);
+    } else {
+      await hoverOnWidget(findPageName(name).first, onHover: onHover);
+    }
   }
 
   /// Tap the ... button beside the page name.
@@ -137,24 +149,18 @@ extension CommonOperations on WidgetTester {
   }
 
   /// Tap the delete page button.
-  ///
-  /// Must call [tapPageOptionButton] first.
   Future<void> tapDeletePageButton() async {
     await tapPageOptionButton();
     await tapButtonWithName(ViewDisclosureAction.delete.name);
   }
 
   /// Tap the rename page button.
-  ///
-  /// Must call [tapPageOptionButton] first.
   Future<void> tapRenamePageButton() async {
     await tapPageOptionButton();
     await tapButtonWithName(ViewDisclosureAction.rename.name);
   }
 
   /// Rename the page.
-  ///
-  /// Must call [tapPageOptionButton] first.
   Future<void> renamePage(String name) async {
     await tapRenamePageButton();
     await enterText(find.byType(TextFormField), name);
@@ -208,14 +214,50 @@ extension CommonOperations on WidgetTester {
     await tapButton(markdownButton);
   }
 
-  /// Hover on cover plugin button above the document
-  Future<void> hoverOnCoverPluginAddButton() async {
-    final editor = find.byWidgetPredicate(
-      (widget) => widget is AppFlowyEditor,
-    );
-    await hoverOnWidget(
-      editor,
-      offset: getTopLeft(editor).translate(20, 20),
+  Future<void> createNewPageWithName(ViewLayoutPB layout, String name) async {
+    // create a new page
+    await tapAddButton();
+    await tapButtonWithName(layout.menuName);
+    await pumpAndSettle();
+
+    // hover on it and change it's name
+    await hoverOnPageName(
+      LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
+      onHover: () async {
+        await renamePage(name);
+        await pumpAndSettle();
+      },
     );
+    await pumpAndSettle();
+  }
+}
+
+extension ViewLayoutPBTest on ViewLayoutPB {
+  String get menuName {
+    switch (this) {
+      case ViewLayoutPB.Grid:
+        return LocaleKeys.grid_menuName.tr();
+      case ViewLayoutPB.Board:
+        return LocaleKeys.board_menuName.tr();
+      case ViewLayoutPB.Document:
+        return LocaleKeys.document_menuName.tr();
+      case ViewLayoutPB.Calendar:
+        return LocaleKeys.calendar_menuName.tr();
+      default:
+        throw UnsupportedError('Unsupported layout: $this');
+    }
+  }
+
+  String get referencedMenuName {
+    switch (this) {
+      case ViewLayoutPB.Grid:
+        return LocaleKeys.document_plugins_referencedGrid.tr();
+      case ViewLayoutPB.Board:
+        return LocaleKeys.document_plugins_referencedBoard.tr();
+      case ViewLayoutPB.Calendar:
+        return LocaleKeys.document_plugins_referencedCalendar.tr();
+      default:
+        throw UnsupportedError('Unsupported layout: $this');
+    }
   }
 }

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

@@ -0,0 +1,45 @@
+import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
+import 'package:flutter_test/flutter_test.dart';
+
+import 'ime.dart';
+import 'util.dart';
+
+extension EditorWidgetTester on WidgetTester {
+  EditorOperations get editor => EditorOperations(this);
+}
+
+class EditorOperations {
+  const EditorOperations(this.tester);
+
+  final WidgetTester tester;
+
+  /// Tap the line of editor at [index]
+  Future<void> tapLineOfEditorAt(int index) async {
+    final textBlocks = find.byType(TextBlockComponentWidget);
+    await tester.tapAt(tester.getTopRight(textBlocks.at(index)));
+  }
+
+  /// Hover on cover plugin button above the document
+  Future<void> hoverOnCoverPluginAddButton() async {
+    final editor = find.byWidgetPredicate(
+      (widget) => widget is AppFlowyEditor,
+    );
+    await tester.hoverOnWidget(
+      editor,
+      offset: tester.getTopLeft(editor).translate(20, 20),
+    );
+  }
+
+  /// trigger the slash command (selection menu)
+  Future<void> showSlashMenu() async {
+    await tester.ime.insertCharacter('/');
+  }
+
+  /// Tap the slash menu item with [name]
+  ///
+  /// Must call [showSlashMenu] first.
+  Future<void> tapSlashMenuItemWithName(String name) async {
+    final slashMenuItem = find.text(name, findRichText: true);
+    await tester.tapButton(slashMenuItem);
+  }
+}

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

@@ -3,3 +3,4 @@ export 'common_operations.dart';
 export 'settings.dart';
 export 'data.dart';
 export 'expectation.dart';
+export 'editor_test_operations.dart';

+ 13 - 5
frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/row_document.dart

@@ -1,6 +1,7 @@
 import 'package:appflowy/plugins/database_view/grid/application/row/row_document_bloc.dart';
 import 'package:appflowy/plugins/document/application/doc_bloc.dart';
 import 'package:appflowy/plugins/document/presentation/editor_page.dart';
+import 'package:appflowy/plugins/document/presentation/editor_style.dart';
 import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
 import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
 import 'package:flowy_infra_ui/widget/error_page.dart';
@@ -102,11 +103,18 @@ class _RowEditorState extends State<RowEditor> {
                     return const SizedBox.shrink();
                   }
                   return IntrinsicHeight(
-                    child: AppFlowyEditorPage(
-                      shrinkWrap: true,
-                      autoFocus: false,
-                      editorState: editorState,
-                      scrollController: widget.scrollController,
+                    child: Container(
+                      constraints: const BoxConstraints(minHeight: 300),
+                      child: AppFlowyEditorPage(
+                        shrinkWrap: true,
+                        autoFocus: false,
+                        editorState: editorState,
+                        scrollController: widget.scrollController,
+                        styleCustomizer: EditorStyleCustomizer(
+                          context: context,
+                          padding: const EdgeInsets.symmetric(horizontal: 10),
+                        ),
+                      ),
                     ),
                   );
                 },

+ 5 - 0
frontend/appflowy_flutter/lib/plugins/document/document_page.dart

@@ -5,6 +5,7 @@ import 'package:appflowy/plugins/document/application/doc_bloc.dart';
 import 'package:appflowy/plugins/document/presentation/banner.dart';
 import 'package:appflowy/plugins/document/presentation/editor_page.dart';
 import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
+import 'package:appflowy/plugins/document/presentation/editor_style.dart';
 import 'package:appflowy/plugins/document/presentation/export_page_widget.dart';
 import 'package:appflowy/startup/startup.dart';
 import 'package:appflowy/util/base64_string.dart';
@@ -90,6 +91,10 @@ class _DocumentPageState extends State<DocumentPage> {
   Widget _buildEditorPage(BuildContext context, DocumentState state) {
     final appflowyEditorPage = AppFlowyEditorPage(
       editorState: editorState!,
+      styleCustomizer: EditorStyleCustomizer(
+        context: context,
+        padding: const EdgeInsets.symmetric(horizontal: 50),
+      ),
       header: _buildCoverAndIcon(context),
     );
     return Column(

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

@@ -17,6 +17,7 @@ class AppFlowyEditorPage extends StatefulWidget {
     this.shrinkWrap = false,
     this.scrollController,
     this.autoFocus,
+    required this.styleCustomizer,
   });
 
   final Widget? header;
@@ -24,6 +25,7 @@ class AppFlowyEditorPage extends StatefulWidget {
   final ScrollController? scrollController;
   final bool shrinkWrap;
   final bool? autoFocus;
+  final EditorStyleCustomizer styleCustomizer;
 
   @override
   State<AppFlowyEditorPage> createState() => _AppFlowyEditorPageState();
@@ -91,9 +93,7 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
     style: styleCustomizer.selectionMenuStyleBuilder(),
   ).handler;
 
-  EditorStyleCustomizer get styleCustomizer => EditorStyleCustomizer(
-        context: context,
-      );
+  EditorStyleCustomizer get styleCustomizer => widget.styleCustomizer;
   DocumentBloc get documentBloc => context.read<DocumentBloc>();
 
   @override

+ 2 - 0
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/link_to_page_widget.dart

@@ -23,11 +23,13 @@ void showLinkToPageMenu(
   final top = alignment == Alignment.bottomLeft ? offset.dy : null;
   final bottom = alignment == Alignment.topLeft ? offset.dy : null;
 
+  keepEditorFocusNotifier.value += 1;
   late OverlayEntry linkToPageMenuEntry;
   linkToPageMenuEntry = FullScreenOverlayEntry(
     top: top,
     bottom: bottom,
     left: offset.dx,
+    dismissCallback: () => keepEditorFocusNotifier.value -= 1,
     builder: (context) => Material(
       color: Colors.transparent,
       child: LinkToPageMenu(

+ 4 - 5
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/cover/cover_node_widget.dart

@@ -4,7 +4,6 @@ import 'package:appflowy/generated/locale_keys.g.dart';
 import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/change_cover_popover.dart';
 import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/emoji_popover.dart';
 import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/emoji_icon_widget.dart';
-import 'package:appflowy/plugins/document/presentation/editor_style.dart';
 import 'package:appflowy/workspace/presentation/widgets/emoji_picker/emoji_picker.dart';
 import 'package:appflowy_editor/appflowy_editor.dart' hide FlowySvg;
 import 'package:appflowy_popover/appflowy_popover.dart';
@@ -164,8 +163,8 @@ class _AddCoverButtonState extends State<_AddCoverButton> {
         height: widget.hasIcon ? 180 : 50.0,
         alignment: Alignment.bottomLeft,
         width: double.infinity,
-        padding: EdgeInsets.only(
-          left: EditorStyleCustomizer.horizontalPadding + 30,
+        padding: const EdgeInsets.only(
+          left: 80,
           top: 20,
           bottom: 5,
         ),
@@ -333,7 +332,7 @@ class _CoverImageState extends State<_CoverImage> {
         ),
         hasIcon
             ? Positioned(
-                left: EditorStyleCustomizer.horizontalPadding + 30,
+                left: 80,
                 bottom: !hasCover ? 30 : 40,
                 child: AppFlowyPopover(
                   offset: const Offset(100, 0),
@@ -416,7 +415,7 @@ class _CoverImageState extends State<_CoverImage> {
   Widget _buildCoverOverlayButtons(BuildContext context) {
     return Positioned(
       bottom: 20,
-      right: EditorStyleCustomizer.horizontalPadding,
+      right: 50,
       child: Row(
         mainAxisSize: MainAxisSize.min,
         children: [

+ 2 - 0
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/emoji_picker/emoji_menu_item.dart

@@ -35,10 +35,12 @@ void showEmojiPickerMenu(
   final top = alignment == Alignment.bottomLeft ? offset.dy : null;
   final bottom = alignment == Alignment.topLeft ? offset.dy : null;
 
+  keepEditorFocusNotifier.value += 1;
   final emojiPickerMenuEntry = FullScreenOverlayEntry(
     top: top,
     bottom: bottom,
     left: offset.dx,
+    dismissCallback: () => keepEditorFocusNotifier.value -= 1,
     builder: (context) => Material(
       child: Container(
         width: 300,

+ 4 - 5
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_style.dart

@@ -8,12 +8,11 @@ import 'package:google_fonts/google_fonts.dart';
 class EditorStyleCustomizer {
   EditorStyleCustomizer({
     required this.context,
+    required this.padding,
   });
 
-  static double get horizontalPadding =>
-      PlatformExtension.isDesktop ? 50.0 : 10.0;
-
   final BuildContext context;
+  final EdgeInsets padding;
 
   EditorStyle style() {
     if (PlatformExtension.isDesktopOrWeb) {
@@ -28,7 +27,7 @@ class EditorStyleCustomizer {
     final theme = Theme.of(context);
     final fontSize = context.read<DocumentAppearanceCubit>().state.fontSize;
     return EditorStyle.desktop(
-      padding: EdgeInsets.symmetric(horizontal: horizontalPadding),
+      padding: padding,
       backgroundColor: theme.colorScheme.surface,
       cursorColor: theme.colorScheme.primary,
       textStyleConfiguration: TextStyleConfiguration(
@@ -65,7 +64,7 @@ class EditorStyleCustomizer {
     final theme = Theme.of(context);
     final fontSize = context.read<DocumentAppearanceCubit>().state.fontSize;
     return EditorStyle.desktop(
-      padding: EdgeInsets.symmetric(horizontal: horizontalPadding),
+      padding: padding,
       backgroundColor: theme.colorScheme.surface,
       cursorColor: theme.colorScheme.primary,
       textStyleConfiguration: TextStyleConfiguration(

+ 5 - 6
frontend/appflowy_flutter/pubspec.lock

@@ -52,12 +52,11 @@ packages:
   appflowy_editor:
     dependency: "direct main"
     description:
-      path: "."
-      ref: "23bc6d2"
-      resolved-ref: "23bc6d2f58ab7ab4ff21c507d53753de35094ec0"
-      url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
-    source: git
-    version: "1.0.2"
+      name: appflowy_editor
+      sha256: "19c2567e23bbd8894243b2e57fa8436e3192c8dcb50c23499b6aea90a674a045"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.0.3"
   appflowy_popover:
     dependency: "direct main"
     description:

+ 5 - 5
frontend/appflowy_flutter/pubspec.yaml

@@ -42,11 +42,11 @@ dependencies:
     git:
       url: https://github.com/AppFlowy-IO/appflowy-board.git
       ref: a183c57
-  # appflowy_editor: ^1.0.2
-  appflowy_editor:
-    git:
-      url: https://github.com/AppFlowy-IO/appflowy-editor.git
-      ref: 23bc6d2
+  appflowy_editor: ^1.0.3
+  # appflowy_editor:
+  #   git:
+  #     url: https://github.com/AppFlowy-IO/appflowy-editor.git
+  #     ref: d2460c9
   appflowy_popover:
     path: packages/appflowy_popover