fix: insert reference page in nested page

This commit is contained in:
Lucas.Xu
2023-08-01 20:47:20 +07:00
committed by GitHub
parent 5e2ed56f5b
commit f0d2cf7da3
9 changed files with 166 additions and 90 deletions

View File

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

View File

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

View File

@ -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/board/presentation/board_page.dart';
import 'package:appflowy/plugins/database_view/calendar/presentation/calendar_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/database_view/grid/presentation/grid_page.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/link_to_page_widget.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_backend/protobuf/flowy-folder2/protobuf.dart';
import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/uuid.dart'; import 'package:flowy_infra/uuid.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart'; import 'package:integration_test/integration_test.dart';
@ -61,6 +64,54 @@ void main() {
findsOneWidget, 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( await tester.createNewPageWithName(
name: name, name: name,
layout: layout, layout: layout,
openAfterCreated: false,
); );
// create a new document // create a new document
await tester.createNewPageWithName( await tester.createNewPageWithName(
name: 'insert_a_reference_${layout.name}', name: 'insert_a_reference_${layout.name}',
layout: ViewLayoutPB.Document, layout: ViewLayoutPB.Document,
openAfterCreated: true,
); );
// tap the first line of the document // tap the first line of the document
await tester.editor.tapLineOfEditorAt(0); await tester.editor.tapLineOfEditorAt(0);
@ -98,3 +151,38 @@ Future<void> insertReferenceDatabase(
expect(referencedDatabase, findsOneWidget); expect(referencedDatabase, findsOneWidget);
await tester.tapButton(referencedDatabase); 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);
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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/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_ext.dart';
import 'package:appflowy/workspace/application/view/view_service.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'; import 'package:appflowy_editor/appflowy_editor.dart';
enum MentionType { enum MentionType {
@ -98,17 +97,11 @@ class InlinePageReferenceService {
Future<List<SelectionMenuItem>> generatePageItems(String character) async { Future<List<SelectionMenuItem>> generatePageItems(String character) async {
final service = ViewBackendService(); final service = ViewBackendService();
final List<(ViewPB, List<ViewPB>)> pbViews = await service.fetchViews( final views = await service.fetchViews();
(_, __) => true, if (views.isEmpty) {
);
if (pbViews.isEmpty) {
return []; return [];
} }
final List<SelectionMenuItem> pages = []; 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))); views.sort(((a, b) => b.createTime.compareTo(a.createTime)));
for (final view in views) { for (final view in views) {

View File

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