Skip to content

Commit c24b684

Browse files
authored
feat: support creating subpage block in row detail page (#6824)
* feat: support creating subpage block in row detail page * feat: hide the row page from sidebar * test: support creating a sub-page block in row detail page * fix: update drag block logic * feat: support toggle heading in outline * test: add toggle headings show in outline block test * fix: unable to get focus when opening subpage from card
1 parent df7fe97 commit c24b684

File tree

15 files changed

+298
-143
lines changed

15 files changed

+298
-143
lines changed

frontend/appflowy_flutter/integration_test/desktop/database/database_row_page_test.dart

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
import 'package:appflowy/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_checklist_cell.dart';
2-
import 'package:appflowy/plugins/database/widgets/cell_editor/checklist_cell_editor.dart';
3-
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
4-
import 'package:flutter/material.dart';
5-
61
import 'package:appflowy/generated/locale_keys.g.dart';
72
import 'package:appflowy/plugins/database/grid/presentation/widgets/header/desktop_field_cell.dart';
3+
import 'package:appflowy/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_checklist_cell.dart';
4+
import 'package:appflowy/plugins/database/widgets/cell_editor/checklist_cell_editor.dart';
85
import 'package:appflowy/plugins/database/widgets/row/row_detail.dart';
6+
import 'package:appflowy/plugins/document/document_page.dart';
97
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart';
108
import 'package:appflowy/util/field_type_extension.dart';
119
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
1210
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
1311
import 'package:appflowy_editor/appflowy_editor.dart';
1412
import 'package:easy_localization/easy_localization.dart';
13+
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
14+
import 'package:flutter/material.dart';
1515
import 'package:flutter/services.dart';
1616
import 'package:flutter_test/flutter_test.dart';
1717
import 'package:integration_test/integration_test.dart';
@@ -76,6 +76,24 @@ void main() {
7676
// The number of emoji should be two. One in the row displayed in the grid
7777
// one in the row detail page.
7878
expect(emojiText, findsNWidgets(2));
79+
80+
// insert a sub page in database
81+
await tester.editor.tapLineOfEditorAt(0);
82+
await tester.editor.showSlashMenu();
83+
await tester.pumpAndSettle();
84+
await tester.editor.tapSlashMenuItemWithName(
85+
LocaleKeys.document_slashMenu_subPage_name.tr(),
86+
offset: 100,
87+
);
88+
await tester.pumpAndSettle();
89+
90+
// the row detail page should be closed
91+
final rowDetailPage = find.byType(RowDetailPage);
92+
await tester.pumpUntilNotFound(rowDetailPage);
93+
94+
// expect to see a document page
95+
final documentPage = find.byType(DocumentPage);
96+
expect(documentPage, findsOneWidget);
7997
});
8098

8199
testWidgets('remove emoji', (tester) async {

frontend/appflowy_flutter/integration_test/desktop/document/document_sub_page_test.dart

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
import 'dart:io';
22

3-
import 'package:appflowy/workspace/presentation/home/menu/view/view_action_type.dart';
4-
import 'package:flutter/material.dart';
5-
import 'package:flutter/services.dart';
6-
73
import 'package:appflowy/generated/locale_keys.g.dart';
84
import 'package:appflowy/plugins/document/presentation/editor_plugins/sub_page/sub_page_block_component.dart';
5+
import 'package:appflowy/workspace/presentation/home/menu/view/view_action_type.dart';
96
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
107
import 'package:appflowy_editor/appflowy_editor.dart';
118
import 'package:easy_localization/easy_localization.dart';
9+
import 'package:flutter/material.dart';
10+
import 'package:flutter/services.dart';
1211
import 'package:flutter_test/flutter_test.dart';
1312
import 'package:integration_test/integration_test.dart';
1413

frontend/appflowy_flutter/integration_test/desktop/document/document_with_outline_block_test.dart

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'package:appflowy/generated/locale_keys.g.dart';
22
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
33
import 'package:easy_localization/easy_localization.dart';
4+
import 'package:flutter/services.dart';
45
import 'package:flutter_test/flutter_test.dart';
56
import 'package:integration_test/integration_test.dart';
67

@@ -43,6 +44,9 @@ void main() {
4344
* # Heading 1
4445
* ## Heading 2
4546
* ### Heading 3
47+
* > # Heading 1
48+
* > ## Heading 2
49+
* > ### Heading 3
4650
*/
4751

4852
await tester.editor.tapLineOfEditorAt(3);
@@ -53,7 +57,7 @@ void main() {
5357
of: find.byType(OutlineBlockWidget),
5458
matching: find.text(heading1),
5559
),
56-
findsOneWidget,
60+
findsNWidgets(2),
5761
);
5862

5963
// Heading 2 is prefixed with a bullet
@@ -62,7 +66,7 @@ void main() {
6266
of: find.byType(OutlineBlockWidget),
6367
matching: find.text(heading2),
6468
),
65-
findsOneWidget,
69+
findsNWidgets(2),
6670
);
6771

6872
// Heading 3 is prefixed with a dash
@@ -71,7 +75,7 @@ void main() {
7175
of: find.byType(OutlineBlockWidget),
7276
matching: find.text(heading3),
7377
),
74-
findsOneWidget,
78+
findsNWidgets(2),
7579
);
7680

7781
// update the Heading 1 to Heading 1Hello world
@@ -99,13 +103,16 @@ void main() {
99103
* # Heading 1
100104
* ## Heading 2
101105
* ### Heading 3
106+
* > # Heading 1
107+
* > ## Heading 2
108+
* > ### Heading 3
102109
*/
103110

104-
await tester.editor.tapLineOfEditorAt(3);
111+
await tester.editor.tapLineOfEditorAt(7);
105112
await insertOutlineInDocument(tester);
106113

107114
// expect to find only the `heading1` widget under the [OutlineBlockWidget]
108-
await hoverAndClickDepthOptionAction(tester, [3], 1);
115+
await hoverAndClickDepthOptionAction(tester, [6], 1);
109116
expect(
110117
find.descendant(
111118
of: find.byType(OutlineBlockWidget),
@@ -123,7 +130,7 @@ void main() {
123130
//////
124131
125132
/// expect to find only the 'heading1' and 'heading2' under the [OutlineBlockWidget]
126-
await hoverAndClickDepthOptionAction(tester, [3], 2);
133+
await hoverAndClickDepthOptionAction(tester, [6], 2);
127134
expect(
128135
find.descendant(
129136
of: find.byType(OutlineBlockWidget),
@@ -134,29 +141,29 @@ void main() {
134141
//////
135142
136143
// expect to find all the headings under the [OutlineBlockWidget]
137-
await hoverAndClickDepthOptionAction(tester, [3], 3);
144+
await hoverAndClickDepthOptionAction(tester, [6], 3);
138145
expect(
139146
find.descendant(
140147
of: find.byType(OutlineBlockWidget),
141148
matching: find.text(heading1),
142149
),
143-
findsOneWidget,
150+
findsNWidgets(2),
144151
);
145152

146153
expect(
147154
find.descendant(
148155
of: find.byType(OutlineBlockWidget),
149156
matching: find.text(heading2),
150157
),
151-
findsOneWidget,
158+
findsNWidgets(2),
152159
);
153160

154161
expect(
155162
find.descendant(
156163
of: find.byType(OutlineBlockWidget),
157164
matching: find.text(heading3),
158165
),
159-
findsOneWidget,
166+
findsNWidgets(2),
160167
);
161168
//////
162169
});
@@ -186,7 +193,17 @@ Future<void> hoverAndClickDepthOptionAction(
186193

187194
Future<void> insertHeadingComponent(WidgetTester tester) async {
188195
await tester.editor.tapLineOfEditorAt(0);
196+
197+
// # heading 1-3
189198
await tester.ime.insertText('# $heading1\n');
190199
await tester.ime.insertText('## $heading2\n');
191200
await tester.ime.insertText('### $heading3\n');
201+
202+
// > # toggle heading 1-3
203+
await tester.ime.insertText('> # $heading1\n');
204+
await tester.simulateKeyEvent(LogicalKeyboardKey.backspace);
205+
await tester.ime.insertText('> ## $heading2\n');
206+
await tester.simulateKeyEvent(LogicalKeyboardKey.backspace);
207+
await tester.ime.insertText('> ### $heading3\n');
208+
await tester.simulateKeyEvent(LogicalKeyboardKey.backspace);
192209
}

frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_document.dart

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
1-
import 'package:appflowy/plugins/document/presentation/editor_drop_manager.dart';
2-
import 'package:flutter/material.dart';
3-
41
import 'package:appflowy/generated/locale_keys.g.dart';
52
import 'package:appflowy/plugins/database/grid/application/row/row_document_bloc.dart';
63
import 'package:appflowy/plugins/document/application/document_bloc.dart';
74
import 'package:appflowy/plugins/document/presentation/editor_drop_handler.dart';
5+
import 'package:appflowy/plugins/document/presentation/editor_drop_manager.dart';
86
import 'package:appflowy/plugins/document/presentation/editor_page.dart';
7+
import 'package:appflowy/plugins/document/presentation/editor_plugins/shared_context/shared_context.dart';
8+
import 'package:appflowy/plugins/document/presentation/editor_plugins/transaction_handler/editor_transaction_service.dart';
99
import 'package:appflowy/plugins/document/presentation/editor_style.dart';
1010
import 'package:appflowy/shared/flowy_error_page.dart';
1111
import 'package:appflowy/workspace/application/view_info/view_info_bloc.dart';
1212
import 'package:appflowy_backend/log.dart';
1313
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
1414
import 'package:easy_localization/easy_localization.dart';
15+
import 'package:flutter/material.dart';
1516
import 'package:flutter_bloc/flutter_bloc.dart';
17+
import 'package:provider/provider.dart';
1618

1719
class RowDocument extends StatelessWidget {
1820
const RowDocument({
@@ -103,23 +105,35 @@ class _RowEditor extends StatelessWidget {
103105
child: IntrinsicHeight(
104106
child: Container(
105107
constraints: const BoxConstraints(minHeight: 300),
106-
child: EditorDropHandler(
107-
viewId: view.id,
108-
editorState: editorState,
109-
isLocalMode: context.read<DocumentBloc>().isLocalMode,
110-
dropManagerState: context.read<EditorDropManagerState>(),
111-
child: AppFlowyEditorPage(
112-
shrinkWrap: true,
113-
autoFocus: false,
108+
child: Provider(
109+
create: (_) {
110+
final context = SharedEditorContext();
111+
context.isInDatabaseRowPage = true;
112+
return context;
113+
},
114+
dispose: (_, editorContext) => editorContext.dispose(),
115+
child: EditorDropHandler(
116+
viewId: view.id,
114117
editorState: editorState,
115-
styleCustomizer: EditorStyleCustomizer(
116-
context: context,
117-
padding: const EdgeInsets.only(left: 16, right: 54),
118+
isLocalMode: context.read<DocumentBloc>().isLocalMode,
119+
dropManagerState: context.read<EditorDropManagerState>(),
120+
child: EditorTransactionService(
121+
viewId: view.id,
122+
editorState: editorState,
123+
child: AppFlowyEditorPage(
124+
shrinkWrap: true,
125+
autoFocus: false,
126+
editorState: editorState,
127+
styleCustomizer: EditorStyleCustomizer(
128+
context: context,
129+
padding: const EdgeInsets.only(left: 16, right: 54),
130+
),
131+
showParagraphPlaceholder: (editorState, _) =>
132+
editorState.document.isEmpty,
133+
placeholderText: (_) =>
134+
LocaleKeys.cardDetails_notesPlaceholder.tr(),
135+
),
118136
),
119-
showParagraphPlaceholder: (editorState, _) =>
120-
editorState.document.isEmpty,
121-
placeholderText: (_) =>
122-
LocaleKeys.cardDetails_notesPlaceholder.tr(),
123137
),
124138
),
125139
),

frontend/appflowy_flutter/lib/plugins/database_document/database_document_page.dart

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
import 'package:appflowy/plugins/document/presentation/editor_drop_handler.dart';
2-
import 'package:flutter/material.dart';
3-
41
import 'package:appflowy/plugins/database/application/row/related_row_detail_bloc.dart';
52
import 'package:appflowy/plugins/database/grid/application/row/row_detail_bloc.dart';
63
import 'package:appflowy/plugins/database/grid/presentation/widgets/common/type_option_separator.dart';
@@ -9,8 +6,10 @@ import 'package:appflowy/plugins/database/widgets/row/row_banner.dart';
96
import 'package:appflowy/plugins/database/widgets/row/row_property.dart';
107
import 'package:appflowy/plugins/document/application/document_bloc.dart';
118
import 'package:appflowy/plugins/document/presentation/banner.dart';
9+
import 'package:appflowy/plugins/document/presentation/editor_drop_handler.dart';
1210
import 'package:appflowy/plugins/document/presentation/editor_notification.dart';
1311
import 'package:appflowy/plugins/document/presentation/editor_page.dart';
12+
import 'package:appflowy/plugins/document/presentation/editor_plugins/transaction_handler/editor_transaction_service.dart';
1413
import 'package:appflowy/plugins/document/presentation/editor_style.dart';
1514
import 'package:appflowy/shared/flowy_error_page.dart';
1615
import 'package:appflowy/startup/startup.dart';
@@ -19,6 +18,7 @@ import 'package:appflowy/workspace/application/action_navigation/navigation_acti
1918
import 'package:appflowy_backend/log.dart';
2019
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
2120
import 'package:appflowy_editor/appflowy_editor.dart';
21+
import 'package:flutter/material.dart';
2222
import 'package:flutter_bloc/flutter_bloc.dart';
2323

2424
// This widget is largely copied from `plugins/document/document_page.dart` intentionally instead of opting for an abstraction. We can make an abstraction after the view refactor is done and there's more clarity in that department.
@@ -122,11 +122,15 @@ class _DatabaseDocumentPageState extends State<DatabaseDocumentPage> {
122122
),
123123
);
124124

125-
return Column(
126-
children: [
127-
if (state.isDeleted) _buildBanner(context),
128-
Expanded(child: appflowyEditorPage),
129-
],
125+
return EditorTransactionService(
126+
viewId: widget.view.id,
127+
editorState: state.editorState!,
128+
child: Column(
129+
children: [
130+
if (state.isDeleted) _buildBanner(context),
131+
Expanded(child: appflowyEditorPage),
132+
],
133+
),
130134
);
131135
}
132136

frontend/appflowy_flutter/lib/plugins/database_document/presentation/database_document_title.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import 'package:flutter/material.dart';
2-
31
import 'package:appflowy/generated/flowy_svgs.g.dart';
42
import 'package:appflowy/generated/locale_keys.g.dart';
53
import 'package:appflowy/plugins/database/application/cell/bloc/text_cell_bloc.dart';
@@ -13,6 +11,7 @@ import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
1311
import 'package:collection/collection.dart';
1412
import 'package:easy_localization/easy_localization.dart';
1513
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
14+
import 'package:flutter/material.dart';
1615
import 'package:flutter_bloc/flutter_bloc.dart';
1716

1817
import 'database_document_title_bloc.dart';
@@ -192,6 +191,8 @@ class _TitleSkin extends IEditableTextCellSkin {
192191
child: FlowyText.regular(
193192
name,
194193
overflow: TextOverflow.ellipsis,
194+
fontSize: 14.0,
195+
figmaLineHeight: 18.0,
195196
),
196197
),
197198
],

frontend/appflowy_flutter/lib/plugins/document/document_page.dart

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,7 @@ class _DocumentPageState extends State<DocumentPage>
164164
create: (_) {
165165
final context = SharedEditorContext();
166166
if (widget.view.name.isEmpty) {
167-
WidgetsBinding.instance.addPostFrameCallback((_) {
168-
context.coverTitleFocusNode.requestFocus();
169-
});
167+
context.requestCoverTitleFocus = true;
170168
}
171169
return context;
172170
},

frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import 'package:appflowy/workspace/presentation/home/af_focus_manager.dart';
1919
import 'package:appflowy_editor/appflowy_editor.dart';
2020
import 'package:collection/collection.dart';
2121
import 'package:flowy_infra/theme_extension.dart';
22-
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
2322
import 'package:flutter/material.dart';
2423
import 'package:flutter/services.dart';
2524
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -348,7 +347,10 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage>
348347
// if the last one isn't a empty node, insert a new empty node.
349348
await _focusOnLastEmptyParagraph();
350349
},
351-
child: VSpace(UniversalPlatform.isDesktopOrWeb ? 200 : 400),
350+
child: SizedBox(
351+
width: double.infinity,
352+
height: UniversalPlatform.isDesktopOrWeb ? 200 : 400,
353+
),
352354
),
353355
dropTargetStyle: AppFlowyDropTargetStyle(
354356
color: Theme.of(context).colorScheme.primary.withOpacity(0.8),

frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/drag_to_reorder/util.dart

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -52,21 +52,10 @@ Future<void> dragToMoveNode(
5252

5353
Log.info('Moving node($node, ${node.path}) to path($newPath)');
5454

55-
final newPathNode = editorState.getNodeAtPath(newPath);
56-
if (newPathNode == null) {
57-
// if the new path is not a valid path, it means the node is not in the editor.
58-
// we should perform insertion before deletion.
59-
final transaction = editorState.transaction;
60-
transaction.insertNode(newPath, node.copyWith());
61-
transaction.deleteNode(node);
62-
await editorState.apply(transaction);
63-
} else {
64-
// Perform the node move operation
65-
final transaction = editorState.transaction;
66-
transaction.deleteNode(node);
67-
transaction.insertNode(newPath, node.copyWith());
68-
await editorState.apply(transaction);
69-
}
55+
final transaction = editorState.transaction;
56+
transaction.insertNode(newPath, node.copyWith());
57+
transaction.deleteNode(node);
58+
await editorState.apply(transaction);
7059
}
7160

7261
(VerticalPosition, HorizontalPosition, Rect)? getDragAreaPosition(

0 commit comments

Comments
 (0)