mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Create a new board from the slash menu (#2018)
* feat: create a new board. * feat: switch slash menu keywords * fix: remove unused imports * chore: export SelectionMenuItem from appflowy_editor for integration test * feat: add integration test for slash commands * fix: test in new file was unable to start * feat: add translations
This commit is contained in:
@ -334,7 +334,8 @@
|
|||||||
},
|
},
|
||||||
"slashMenu": {
|
"slashMenu": {
|
||||||
"board": {
|
"board": {
|
||||||
"selectABoardToLinkTo": "Select a Board to link to"
|
"selectABoardToLinkTo": "Select a Board to link to",
|
||||||
|
"createANewBoard": "Create a new Board"
|
||||||
},
|
},
|
||||||
"grid": {
|
"grid": {
|
||||||
"selectAGridToLinkTo": "Select a Grid to link to"
|
"selectAGridToLinkTo": "Select a Grid to link to"
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
|
import 'package:appflowy/plugins/document/presentation/plugins/base/built_in_page_widget.dart';
|
||||||
import 'package:appflowy/user/presentation/folder/folder_widget.dart';
|
import 'package:appflowy/user/presentation/folder/folder_widget.dart';
|
||||||
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/text_field.dart';
|
import 'package:flowy_infra_ui/style_widget/text_field.dart';
|
||||||
|
import 'package:flutter/services.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';
|
||||||
|
|
||||||
@ -157,5 +162,71 @@ void main() {
|
|||||||
await TestFolder.currentLocation(),
|
await TestFolder.currentLocation(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('/board shortcut creates a new board', (tester) async {
|
||||||
|
const folderName = 'appflowy';
|
||||||
|
await TestFolder.cleanTestLocation(folderName);
|
||||||
|
await TestFolder.setTestLocation(folderName);
|
||||||
|
|
||||||
|
await tester.initializeAppFlowy();
|
||||||
|
|
||||||
|
// tap open button
|
||||||
|
await mockGetDirectoryPath(folderName);
|
||||||
|
await tester.tapOpenFolderButton();
|
||||||
|
|
||||||
|
await tester.wait(1000);
|
||||||
|
await tester.expectToSeeWelcomePage();
|
||||||
|
|
||||||
|
final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
// Necessary for being able to enterText when not in debug mode
|
||||||
|
binding.testTextInput.register();
|
||||||
|
|
||||||
|
// Needs tab to obtain focus for the app flowy editor.
|
||||||
|
// by default the tap appears at the center of the widget.
|
||||||
|
final Finder editor = find.byType(AppFlowyEditor);
|
||||||
|
await tester.tap(editor);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// tester.sendText() cannot be used since the editor
|
||||||
|
// does not contain any EditableText widgets.
|
||||||
|
// to interact with the app during an integration test,
|
||||||
|
// simulate physical keyboard events.
|
||||||
|
await simulateKeyDownEvent(LogicalKeyboardKey.enter);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await simulateKeyDownEvent(LogicalKeyboardKey.enter);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await simulateKeyDownEvent(LogicalKeyboardKey.arrowLeft);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await simulateKeyDownEvent(LogicalKeyboardKey.slash);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await simulateKeyDownEvent(LogicalKeyboardKey.keyB);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await simulateKeyDownEvent(LogicalKeyboardKey.keyO);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await simulateKeyDownEvent(LogicalKeyboardKey.keyA);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await simulateKeyDownEvent(LogicalKeyboardKey.keyR);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await simulateKeyDownEvent(LogicalKeyboardKey.keyD);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await simulateKeyDownEvent(LogicalKeyboardKey.arrowDown);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// Checks whether the options in the selection menu
|
||||||
|
// for /board exist.
|
||||||
|
expect(find.byType(SelectionMenuItemWidget), findsAtLeastNWidgets(2));
|
||||||
|
|
||||||
|
// Finalizes the slash command that creates the board.
|
||||||
|
await simulateKeyDownEvent(LogicalKeyboardKey.enter);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// Checks whether new board is referenced and properly on the page.
|
||||||
|
expect(find.byType(BuiltInPageWidget), findsOneWidget);
|
||||||
|
|
||||||
|
// Checks whether the new board is in the side bar.
|
||||||
|
final sidebarLabel = LocaleKeys.newPageText.tr();
|
||||||
|
expect(find.text(sidebarLabel), findsOneWidget);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
import 'package:appflowy/plugins/document/presentation/plugins/board/board_menu_item.dart';
|
import 'package:appflowy/plugins/document/presentation/plugins/board/board_view_menu_item.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||||
|
import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart';
|
||||||
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/plugins/board/board_node_widget.dart';
|
import 'package:appflowy/plugins/document/presentation/plugins/board/board_node_widget.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/plugins/cover/cover_node_widget.dart';
|
import 'package:appflowy/plugins/document/presentation/plugins/cover/cover_node_widget.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/plugins/grid/grid_menu_item.dart';
|
import 'package:appflowy/plugins/document/presentation/plugins/grid/grid_menu_item.dart';
|
||||||
@ -7,19 +10,17 @@ import 'package:appflowy/plugins/document/presentation/plugins/openai/widgets/au
|
|||||||
import 'package:appflowy/plugins/document/presentation/plugins/openai/widgets/auto_completion_plugins.dart';
|
import 'package:appflowy/plugins/document/presentation/plugins/openai/widgets/auto_completion_plugins.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/plugins/openai/widgets/smart_edit_node_widget.dart';
|
import 'package:appflowy/plugins/document/presentation/plugins/openai/widgets/smart_edit_node_widget.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/plugins/openai/widgets/smart_edit_toolbar_item.dart';
|
import 'package:appflowy/plugins/document/presentation/plugins/openai/widgets/smart_edit_toolbar_item.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
|
||||||
import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart';
|
|
||||||
import 'package:dartz/dartz.dart' as dartz;
|
import 'package:dartz/dartz.dart' as dartz;
|
||||||
import 'package:flowy_infra_ui/widget/error_page.dart';
|
import 'package:flowy_infra_ui/widget/error_page.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
import '../../startup/startup.dart';
|
import '../../startup/startup.dart';
|
||||||
import 'application/doc_bloc.dart';
|
import 'application/doc_bloc.dart';
|
||||||
import 'editor_styles.dart';
|
import 'editor_styles.dart';
|
||||||
import 'presentation/banner.dart';
|
import 'presentation/banner.dart';
|
||||||
|
import 'presentation/plugins/board/board_menu_item.dart';
|
||||||
|
|
||||||
class DocumentPage extends StatefulWidget {
|
class DocumentPage extends StatefulWidget {
|
||||||
final VoidCallback onDeleted;
|
final VoidCallback onDeleted;
|
||||||
@ -172,6 +173,8 @@ class _AppFlowyEditorPageState extends State<_AppFlowyEditorPage> {
|
|||||||
emojiMenuItem,
|
emojiMenuItem,
|
||||||
// Board
|
// Board
|
||||||
boardMenuItem,
|
boardMenuItem,
|
||||||
|
// Create Board
|
||||||
|
boardViewMenuItem(documentBloc),
|
||||||
// Grid
|
// Grid
|
||||||
gridMenuItem,
|
gridMenuItem,
|
||||||
// Callout
|
// Callout
|
||||||
|
@ -17,7 +17,8 @@ SelectionMenuItem boardMenuItem = SelectionMenuItem(
|
|||||||
: editorState.editorStyle.selectionMenuItemIconColor,
|
: editorState.editorStyle.selectionMenuItemIconColor,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
keywords: ['board', 'kanban'],
|
// TODO(a-wallen): Translate keywords
|
||||||
|
keywords: ['referenced board', 'referenced kanban'],
|
||||||
handler: (editorState, menuService, context) {
|
handler: (editorState, menuService, context) {
|
||||||
showLinkToPageMenu(
|
showLinkToPageMenu(
|
||||||
editorState,
|
editorState,
|
||||||
|
@ -0,0 +1,61 @@
|
|||||||
|
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||||
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
|
import 'package:appflowy/plugins/document/application/prelude.dart';
|
||||||
|
import 'package:appflowy/plugins/document/presentation/plugins/base/insert_page_command.dart';
|
||||||
|
import 'package:appflowy/workspace/application/app/app_service.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flowy_infra/image.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
SelectionMenuItem boardViewMenuItem(DocumentBloc documentBloc) =>
|
||||||
|
SelectionMenuItem(
|
||||||
|
name: LocaleKeys.document_slashMenu_board_createANewBoard.tr(),
|
||||||
|
icon: (editorState, onSelected) {
|
||||||
|
return svgWidget(
|
||||||
|
'editor/board',
|
||||||
|
size: const Size.square(18.0),
|
||||||
|
color: onSelected
|
||||||
|
? editorState.editorStyle.selectionMenuItemSelectedIconColor
|
||||||
|
: editorState.editorStyle.selectionMenuItemIconColor,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
// TODO(a-wallen): Translate keywords.
|
||||||
|
keywords: ['board', 'kanban'],
|
||||||
|
handler: (editorState, menuService, context) async {
|
||||||
|
if (!documentBloc.view.hasAppId()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final appId = documentBloc.view.appId;
|
||||||
|
final service = AppBackendService();
|
||||||
|
|
||||||
|
final result = (await service.createView(
|
||||||
|
appId: appId,
|
||||||
|
name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
|
||||||
|
layoutType: ViewLayoutTypePB.Board,
|
||||||
|
))
|
||||||
|
.getLeftOrNull();
|
||||||
|
|
||||||
|
// If the result is null, then something went wrong here.
|
||||||
|
if (result == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final app =
|
||||||
|
(await service.readApp(appId: result.appId)).getLeftOrNull();
|
||||||
|
// We should show an error dialog.
|
||||||
|
if (app == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final view =
|
||||||
|
(await service.getView(result.appId, result.id)).getLeftOrNull();
|
||||||
|
// As this.
|
||||||
|
if (view == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
editorState.insertPage(app, view);
|
||||||
|
},
|
||||||
|
);
|
@ -31,6 +31,7 @@ export 'src/extensions/attributes_extension.dart';
|
|||||||
export 'src/render/rich_text/default_selectable.dart';
|
export 'src/render/rich_text/default_selectable.dart';
|
||||||
export 'src/render/rich_text/flowy_rich_text.dart';
|
export 'src/render/rich_text/flowy_rich_text.dart';
|
||||||
export 'src/render/selection_menu/selection_menu_widget.dart';
|
export 'src/render/selection_menu/selection_menu_widget.dart';
|
||||||
|
export 'src/render/selection_menu/selection_menu_item_widget.dart';
|
||||||
export 'src/l10n/l10n.dart';
|
export 'src/l10n/l10n.dart';
|
||||||
export 'src/render/style/plugin_styles.dart';
|
export 'src/render/style/plugin_styles.dart';
|
||||||
export 'src/render/style/editor_style.dart';
|
export 'src/render/style/editor_style.dart';
|
||||||
|
Reference in New Issue
Block a user