瀏覽代碼

fix: insert reference page in nested page

Lucas.Xu 1 年之前
父節點
當前提交
f0d2cf7da3

+ 1 - 0
.github/workflows/flutter_ci.yaml

@@ -134,6 +134,7 @@ jobs:
             fail_ci_if_error: true
             verbose: true
             os: ${{ matrix.os }}
+            token: ${{ secrets.CODECOV_TOKEN }}
           attempt_limit: 20
           attempt_delay: 10000
 

+ 1 - 0
.github/workflows/integration_test.yml

@@ -136,5 +136,6 @@ jobs:
             fail_ci_if_error: true
             verbose: true
             os: ${{ matrix.os }}
+            token: ${{ secrets.CODECOV_TOKEN }}
           attempt_limit: 20
           attempt_delay: 10000

+ 88 - 0
frontend/appflowy_flutter/integration_test/document/document_with_database_test.dart

@@ -1,9 +1,12 @@
+import 'package:appflowy/generated/locale_keys.g.dart';
 import 'package:appflowy/plugins/database_view/board/presentation/board_page.dart';
 import 'package:appflowy/plugins/database_view/calendar/presentation/calendar_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/workspace/presentation/home/menu/view/view_item.dart';
 import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
 import 'package:appflowy_editor/appflowy_editor.dart';
+import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra/uuid.dart';
 import 'package:flutter_test/flutter_test.dart';
 import 'package:integration_test/integration_test.dart';
@@ -61,6 +64,54 @@ void main() {
         findsOneWidget,
       );
     });
+
+    testWidgets('create a grid inside a document', (tester) async {
+      await tester.initializeAppFlowy();
+      await tester.tapGoButton();
+
+      await createInlineDatabase(tester, ViewLayoutPB.Grid);
+
+      // validate the referenced grid is inserted
+      expect(
+        find.descendant(
+          of: find.byType(AppFlowyEditor),
+          matching: find.byType(GridPage),
+        ),
+        findsOneWidget,
+      );
+    });
+
+    testWidgets('create a board inside a document', (tester) async {
+      await tester.initializeAppFlowy();
+      await tester.tapGoButton();
+
+      await createInlineDatabase(tester, ViewLayoutPB.Board);
+
+      // validate the referenced grid is inserted
+      expect(
+        find.descendant(
+          of: find.byType(AppFlowyEditor),
+          matching: find.byType(BoardPage),
+        ),
+        findsOneWidget,
+      );
+    });
+
+    testWidgets('create a calendar inside a document', (tester) async {
+      await tester.initializeAppFlowy();
+      await tester.tapGoButton();
+
+      await createInlineDatabase(tester, ViewLayoutPB.Calendar);
+
+      // validate the referenced grid is inserted
+      expect(
+        find.descendant(
+          of: find.byType(AppFlowyEditor),
+          matching: find.byType(CalendarPage),
+        ),
+        findsOneWidget,
+      );
+    });
   });
 }
 
@@ -75,11 +126,13 @@ Future<void> insertReferenceDatabase(
   await tester.createNewPageWithName(
     name: name,
     layout: layout,
+    openAfterCreated: false,
   );
   // create a new document
   await tester.createNewPageWithName(
     name: 'insert_a_reference_${layout.name}',
     layout: ViewLayoutPB.Document,
+    openAfterCreated: true,
   );
   // tap the first line of the document
   await tester.editor.tapLineOfEditorAt(0);
@@ -98,3 +151,38 @@ Future<void> insertReferenceDatabase(
   expect(referencedDatabase, findsOneWidget);
   await tester.tapButton(referencedDatabase);
 }
+
+Future<void> createInlineDatabase(
+  WidgetTester tester,
+  ViewLayoutPB layout,
+) async {
+  // create a new document
+  final documentName = 'insert_a_inline_${layout.name}';
+  await tester.createNewPageWithName(
+    name: documentName,
+    layout: ViewLayoutPB.Document,
+    openAfterCreated: true,
+  );
+  // tap the first line of the document
+  await tester.editor.tapLineOfEditorAt(0);
+  // insert a referenced view
+  await tester.editor.showSlashMenu();
+  final name = switch (layout) {
+    ViewLayoutPB.Grid => LocaleKeys.document_slashMenu_grid_createANewGrid.tr(),
+    ViewLayoutPB.Board =>
+      LocaleKeys.document_slashMenu_board_createANewBoard.tr(),
+    ViewLayoutPB.Calendar =>
+      LocaleKeys.document_slashMenu_calendar_createANewCalendar.tr(),
+    _ => '',
+  };
+  await tester.editor.tapSlashMenuItemWithName(
+    name,
+  );
+  await tester.pumpAndSettle();
+
+  final childViews = tester
+      .widget<SingleInnerViewItem>(tester.findPageName(documentName))
+      .view
+      .childViews;
+  expect(childViews.length, 1);
+}

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

@@ -234,13 +234,19 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
         ),
       ),
       DatabaseBlockKeys.gridType: DatabaseViewBlockComponentBuilder(
-        configuration: configuration,
+        configuration: configuration.copyWith(
+          padding: (_) => const EdgeInsets.symmetric(vertical: 10),
+        ),
       ),
       DatabaseBlockKeys.boardType: DatabaseViewBlockComponentBuilder(
-        configuration: configuration,
+        configuration: configuration.copyWith(
+          padding: (_) => const EdgeInsets.symmetric(vertical: 10),
+        ),
       ),
       DatabaseBlockKeys.calendarType: DatabaseViewBlockComponentBuilder(
-        configuration: configuration,
+        configuration: configuration.copyWith(
+          padding: (_) => const EdgeInsets.symmetric(vertical: 10),
+        ),
       ),
       CalloutBlockKeys.type: CalloutBlockComponentBuilder(
         configuration: configuration,

+ 24 - 43
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/link_to_page_widget.dart

@@ -82,23 +82,16 @@ class _LinkToPageMenuState extends State<LinkToPageMenu> {
   final _focusNode = FocusNode(debugLabel: 'reference_list_widget');
   EditorStyle get style => widget.editorState.editorStyle;
   int _selectedIndex = 0;
-  int _totalItems = 0;
-  Future<List<(ViewPB, List<ViewPB>)>>? _availableLayout;
-  final Map<int, (ViewPB, ViewPB)> _items = {};
+  final int _totalItems = 0;
+  Future<List<ViewPB>>? _availableLayout;
+  final List<ViewPB> _items = [];
 
-  Future<List<(ViewPB, List<ViewPB>)>> fetchItems() async {
+  Future<List<ViewPB>> fetchItems() async {
     final items =
         await ViewBackendService().fetchViewsWithLayoutType(widget.layoutType);
-
-    int index = 0;
-    for (final (app, children) in items) {
-      for (final view in children) {
-        _items.putIfAbsent(index, () => (app, view));
-        index += 1;
-      }
-    }
-
-    _totalItems = _items.length;
+    _items
+      ..clear()
+      ..addAll(items);
     return items;
   }
 
@@ -176,8 +169,8 @@ class _LinkToPageMenuState extends State<LinkToPageMenu> {
       newSelectedIndex %= _totalItems;
     } else if (event.logicalKey == LogicalKeyboardKey.enter) {
       widget.onSelected(
-        _items[_selectedIndex]!.$1,
-        _items[_selectedIndex]!.$2,
+        _items[_selectedIndex],
+        _items[_selectedIndex],
       );
     }
 
@@ -191,10 +184,10 @@ class _LinkToPageMenuState extends State<LinkToPageMenu> {
   Widget _buildListWidget(
     BuildContext context,
     int selectedIndex,
-    Future<List<(ViewPB, List<ViewPB>)>>? items,
+    Future<List<ViewPB>>? items,
   ) {
     int index = 0;
-    return FutureBuilder<List<(ViewPB, List<ViewPB>)>>(
+    return FutureBuilder<List<ViewPB>>(
       builder: (context, snapshot) {
         if (snapshot.hasData &&
             snapshot.connectionState == ConnectionState.done) {
@@ -211,35 +204,23 @@ class _LinkToPageMenuState extends State<LinkToPageMenu> {
           ];
 
           if (views != null && views.isNotEmpty) {
-            for (final (view, viewChildren) in views) {
-              if (viewChildren.isNotEmpty) {
-                children.add(
-                  Padding(
-                    padding: const EdgeInsets.symmetric(vertical: 4),
-                    child: FlowyText.regular(
-                      view.name,
-                    ),
+            for (final view in views) {
+              children.add(
+                FlowyButton(
+                  isSelected: index == _selectedIndex,
+                  leftIcon: svgWidget(
+                    view.iconName,
+                    color: Theme.of(context).iconTheme.color,
                   ),
-                );
-
-                for (final value in viewChildren) {
-                  children.add(
-                    FlowyButton(
-                      isSelected: index == _selectedIndex,
-                      leftIcon: svgWidget(
-                        value.iconName,
-                        color: Theme.of(context).iconTheme.color,
-                      ),
-                      text: FlowyText.regular(value.name),
-                      onTap: () => widget.onSelected(view, value),
-                    ),
-                  );
+                  text: FlowyText.regular(view.name),
+                  onTap: () => widget.onSelected(view, view),
+                ),
+              );
 
-                  index += 1;
-                }
-              }
+              index += 1;
             }
           }
+
           return Column(
             crossAxisAlignment: CrossAxisAlignment.stretch,
             children: children,

+ 5 - 0
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/database/database_view_block_component.dart

@@ -82,6 +82,11 @@ class _DatabaseBlockComponentWidgetState
       },
     );
 
+    child = Padding(
+      padding: padding,
+      child: child,
+    );
+
     if (widget.actionBuilder != null) {
       child = BlockComponentActionWrapper(
         node: widget.node,

+ 6 - 15
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/database/inline_database_menu_item.dart

@@ -17,11 +17,8 @@ SelectionMenuItem inlineGridMenuItem(DocumentBloc documentBloc) =>
       ),
       keywords: ['grid', 'database'],
       handler: (editorState, menuService, context) async {
-        if (!documentBloc.view.hasParentViewId()) {
-          return;
-        }
-
-        final parentViewId = documentBloc.view.parentViewId;
+        // create the view inside current page
+        final parentViewId = documentBloc.view.id;
         ViewBackendService.createView(
           parentViewId: parentViewId,
           openAfterCreate: false,
@@ -45,11 +42,8 @@ SelectionMenuItem inlineBoardMenuItem(DocumentBloc documentBloc) =>
       ),
       keywords: ['board', 'kanban', 'database'],
       handler: (editorState, menuService, context) async {
-        if (!documentBloc.view.hasParentViewId()) {
-          return;
-        }
-
-        final parentViewId = documentBloc.view.parentViewId;
+        // create the view inside current page
+        final parentViewId = documentBloc.view.id;
         ViewBackendService.createView(
           parentViewId: parentViewId,
           name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
@@ -72,11 +66,8 @@ SelectionMenuItem inlineCalendarMenuItem(DocumentBloc documentBloc) =>
       ),
       keywords: ['calendar', 'database'],
       handler: (editorState, menuService, context) async {
-        if (!documentBloc.view.hasParentViewId()) {
-          return;
-        }
-
-        final parentViewId = documentBloc.view.parentViewId;
+        // create the view inside current page
+        final parentViewId = documentBloc.view.id;
         ViewBackendService.createView(
           parentViewId: parentViewId,
           name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(),

+ 2 - 9
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/inline_page/inline_page_reference.dart

@@ -2,7 +2,6 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/base/selec
 import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart';
 import 'package:appflowy/workspace/application/view/view_ext.dart';
 import 'package:appflowy/workspace/application/view/view_service.dart';
-import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
 import 'package:appflowy_editor/appflowy_editor.dart';
 
 enum MentionType {
@@ -98,17 +97,11 @@ class InlinePageReferenceService {
 
   Future<List<SelectionMenuItem>> generatePageItems(String character) async {
     final service = ViewBackendService();
-    final List<(ViewPB, List<ViewPB>)> pbViews = await service.fetchViews(
-      (_, __) => true,
-    );
-    if (pbViews.isEmpty) {
+    final views = await service.fetchViews();
+    if (views.isEmpty) {
       return [];
     }
     final List<SelectionMenuItem> pages = [];
-    final List<ViewPB> views = [];
-    for (final element in pbViews) {
-      views.addAll(element.$2);
-    }
     views.sort(((a, b) => b.createTime.compareTo(a.createTime)));
 
     for (final view in views) {

+ 30 - 20
frontend/appflowy_flutter/lib/workspace/application/view/view_service.dart

@@ -173,41 +173,51 @@ class ViewBackendService {
     return FolderEventMoveNestedView(payload).send();
   }
 
-  Future<List<(ViewPB, List<ViewPB>)>> fetchViewsWithLayoutType(
+  Future<List<ViewPB>> fetchViewsWithLayoutType(
     ViewLayoutPB? layoutType,
   ) async {
-    return fetchViews((workspace, view) {
-      if (layoutType != null) {
-        return view.layout == layoutType;
-      }
-      return true;
-    });
+    final views = await fetchViews();
+    if (layoutType == null) {
+      return views;
+    }
+    return views
+        .where(
+          (element) => layoutType == element.layout,
+        )
+        .toList();
   }
 
-  Future<List<(ViewPB, List<ViewPB>)>> fetchViews(
-    bool Function(WorkspaceSettingPB workspace, ViewPB view) filter,
-  ) async {
-    final result = <(ViewPB, List<ViewPB>)>[];
+  Future<List<ViewPB>> fetchViews() async {
+    final result = <ViewPB>[];
     return FolderEventGetCurrentWorkspace().send().then((value) async {
       final workspaces = value.getLeftOrNull<WorkspaceSettingPB>();
       if (workspaces != null) {
         final views = workspaces.workspace.views;
         for (final view in views) {
-          final childViews = await getChildViews(viewId: view.id).then(
-            (value) => value
-                .getLeftOrNull<List<ViewPB>>()
-                ?.where((e) => filter(workspaces, e))
-                .toList(),
-          );
-          if (childViews != null && childViews.isNotEmpty) {
-            result.add((view, childViews));
-          }
+          result.add(view);
+          final childViews = await getAllViews(view);
+          result.addAll(childViews);
         }
       }
       return result;
     });
   }
 
+  Future<List<ViewPB>> getAllViews(ViewPB view) async {
+    final result = <ViewPB>[];
+    final childViews = await getChildViews(viewId: view.id).then(
+      (value) => value.getLeftOrNull<List<ViewPB>>()?.toList(),
+    );
+    if (childViews != null && childViews.isNotEmpty) {
+      result.addAll(childViews);
+      final views = await Future.wait(
+        childViews.map((e) async => await getAllViews(e)),
+      );
+      result.addAll(views.expand((element) => element));
+    }
+    return result;
+  }
+
   static Future<Either<ViewPB, FlowyError>> getView(
     String viewID,
   ) async {