mirror of
				https://github.com/AppFlowy-IO/AppFlowy.git
				synced 2025-10-31 18:15:09 +00:00 
			
		
		
		
	fix: insert reference page in nested page
This commit is contained in:
		
							parent
							
								
									5e2ed56f5b
								
							
						
					
					
						commit
						f0d2cf7da3
					
				
							
								
								
									
										1
									
								
								.github/workflows/flutter_ci.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/flutter_ci.yaml
									
									
									
									
										vendored
									
									
								
							| @ -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
									
								
								.github/workflows/integration_test.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/integration_test.yml
									
									
									
									
										vendored
									
									
								
							| @ -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 | ||||
| @ -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); | ||||
| } | ||||
|  | ||||
| @ -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, | ||||
|  | ||||
| @ -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 value in viewChildren) { | ||||
|             for (final view in views) { | ||||
|               children.add( | ||||
|                 FlowyButton( | ||||
|                   isSelected: index == _selectedIndex, | ||||
|                   leftIcon: svgWidget( | ||||
|                         value.iconName, | ||||
|                     view.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; | ||||
|             } | ||||
|           } | ||||
|             } | ||||
|           } | ||||
| 
 | ||||
|           return Column( | ||||
|             crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|             children: children, | ||||
|  | ||||
| @ -82,6 +82,11 @@ class _DatabaseBlockComponentWidgetState | ||||
|       }, | ||||
|     ); | ||||
| 
 | ||||
|     child = Padding( | ||||
|       padding: padding, | ||||
|       child: child, | ||||
|     ); | ||||
| 
 | ||||
|     if (widget.actionBuilder != null) { | ||||
|       child = BlockComponentActionWrapper( | ||||
|         node: widget.node, | ||||
|  | ||||
| @ -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,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) { | ||||
|  | ||||
| @ -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; | ||||
|     final views = await fetchViews(); | ||||
|     if (layoutType == null) { | ||||
|       return views; | ||||
|     } | ||||
|       return true; | ||||
|     }); | ||||
|     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 { | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Lucas.Xu
						Lucas.Xu