mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: inline page reference (#3859)
* feat: more methods of inserting page reference * test: add tests for inserting document reference * chore: remove unused import * chore: update editor ref * tests: fix test with an interim solution
This commit is contained in:
parent
bc502c9c5b
commit
b35d6131d4
@ -0,0 +1,136 @@
|
|||||||
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/link_to_page_widget.dart';
|
||||||
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
|
||||||
|
import 'package:flowy_infra/uuid.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:integration_test/integration_test.dart';
|
||||||
|
|
||||||
|
import '../util/keyboard.dart';
|
||||||
|
import '../util/util.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
group('insert inline document reference', () {
|
||||||
|
testWidgets('insert by slash menu', (tester) async {
|
||||||
|
await tester.initializeAppFlowy();
|
||||||
|
await tester.tapGoButton();
|
||||||
|
|
||||||
|
final name = await createDocumentToReference(tester);
|
||||||
|
|
||||||
|
await tester.editor.tapLineOfEditorAt(0);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
await triggerReferenceDocumentBySlashMenu(tester);
|
||||||
|
|
||||||
|
// Search for prefix of document
|
||||||
|
await enterDocumentText(tester);
|
||||||
|
|
||||||
|
// Select result
|
||||||
|
final optionFinder = find.descendant(
|
||||||
|
of: find.byType(LinkToPageMenu),
|
||||||
|
matching: find.text(name),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.tap(optionFinder);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
final mentionBlock = find.byType(MentionPageBlock);
|
||||||
|
expect(mentionBlock, findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('insert by `[[` character shortcut', (tester) async {
|
||||||
|
await tester.initializeAppFlowy();
|
||||||
|
await tester.tapGoButton();
|
||||||
|
|
||||||
|
final name = await createDocumentToReference(tester);
|
||||||
|
|
||||||
|
await tester.editor.tapLineOfEditorAt(0);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
await tester.ime.insertText('[[');
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// Select result
|
||||||
|
await tester.editor.tapAtMenuItemWithName(name);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
final mentionBlock = find.byType(MentionPageBlock);
|
||||||
|
expect(mentionBlock, findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('insert by `+` character shortcut', (tester) async {
|
||||||
|
await tester.initializeAppFlowy();
|
||||||
|
await tester.tapGoButton();
|
||||||
|
|
||||||
|
final name = await createDocumentToReference(tester);
|
||||||
|
|
||||||
|
await tester.editor.tapLineOfEditorAt(0);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
await tester.ime.insertText('+');
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// Select result
|
||||||
|
await tester.editor.tapAtMenuItemWithName(name);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
final mentionBlock = find.byType(MentionPageBlock);
|
||||||
|
expect(mentionBlock, findsOneWidget);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> createDocumentToReference(WidgetTester tester) async {
|
||||||
|
final name = 'document_${uuid()}';
|
||||||
|
|
||||||
|
await tester.createNewPageWithName(
|
||||||
|
name: name,
|
||||||
|
layout: ViewLayoutPB.Document,
|
||||||
|
openAfterCreated: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
// This is a workaround since the openAfterCreated
|
||||||
|
// option does not work in createNewPageWithName method
|
||||||
|
await tester.tap(find.byType(SingleInnerViewItem).first);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> triggerReferenceDocumentBySlashMenu(WidgetTester tester) async {
|
||||||
|
await tester.editor.showSlashMenu();
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// Search for referenced document action
|
||||||
|
await enterDocumentText(tester);
|
||||||
|
|
||||||
|
// Select item
|
||||||
|
await FlowyTestKeyboard.simulateKeyDownEvent(
|
||||||
|
[
|
||||||
|
LogicalKeyboardKey.enter,
|
||||||
|
],
|
||||||
|
tester: tester,
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> enterDocumentText(WidgetTester tester) async {
|
||||||
|
await FlowyTestKeyboard.simulateKeyDownEvent(
|
||||||
|
[
|
||||||
|
LogicalKeyboardKey.keyD,
|
||||||
|
LogicalKeyboardKey.keyO,
|
||||||
|
LogicalKeyboardKey.keyC,
|
||||||
|
LogicalKeyboardKey.keyU,
|
||||||
|
LogicalKeyboardKey.keyM,
|
||||||
|
LogicalKeyboardKey.keyE,
|
||||||
|
LogicalKeyboardKey.keyN,
|
||||||
|
LogicalKeyboardKey.keyT,
|
||||||
|
],
|
||||||
|
tester: tester,
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
}
|
@ -16,6 +16,8 @@ import 'document_with_inline_page_test.dart' as document_with_inline_page_test;
|
|||||||
import 'document_with_outline_block_test.dart' as document_with_outline_block;
|
import 'document_with_outline_block_test.dart' as document_with_outline_block;
|
||||||
import 'document_with_toggle_list_test.dart' as document_with_toggle_list_test;
|
import 'document_with_toggle_list_test.dart' as document_with_toggle_list_test;
|
||||||
import 'edit_document_test.dart' as document_edit_test;
|
import 'edit_document_test.dart' as document_edit_test;
|
||||||
|
import 'document_inline_page_reference_test.dart'
|
||||||
|
as document_inline_page_reference_test;
|
||||||
|
|
||||||
void startTesting() {
|
void startTesting() {
|
||||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
@ -35,4 +37,5 @@ void startTesting() {
|
|||||||
document_text_direction_test.main();
|
document_text_direction_test.main();
|
||||||
document_option_action_test.main();
|
document_option_action_test.main();
|
||||||
document_with_image_block_test.main();
|
document_with_image_block_test.main();
|
||||||
|
document_inline_page_reference_test.main();
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ void main() {
|
|||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
await insertingInlinePage(tester, ViewLayoutPB.Grid);
|
await insertInlinePage(tester, ViewLayoutPB.Grid);
|
||||||
|
|
||||||
final mentionBlock = find.byType(MentionPageBlock);
|
final mentionBlock = find.byType(MentionPageBlock);
|
||||||
expect(mentionBlock, findsOneWidget);
|
expect(mentionBlock, findsOneWidget);
|
||||||
@ -26,7 +26,7 @@ void main() {
|
|||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
await insertingInlinePage(tester, ViewLayoutPB.Board);
|
await insertInlinePage(tester, ViewLayoutPB.Board);
|
||||||
|
|
||||||
final mentionBlock = find.byType(MentionPageBlock);
|
final mentionBlock = find.byType(MentionPageBlock);
|
||||||
expect(mentionBlock, findsOneWidget);
|
expect(mentionBlock, findsOneWidget);
|
||||||
@ -37,7 +37,7 @@ void main() {
|
|||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
await insertingInlinePage(tester, ViewLayoutPB.Calendar);
|
await insertInlinePage(tester, ViewLayoutPB.Calendar);
|
||||||
|
|
||||||
final mentionBlock = find.byType(MentionPageBlock);
|
final mentionBlock = find.byType(MentionPageBlock);
|
||||||
expect(mentionBlock, findsOneWidget);
|
expect(mentionBlock, findsOneWidget);
|
||||||
@ -48,7 +48,7 @@ void main() {
|
|||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
await insertingInlinePage(tester, ViewLayoutPB.Document);
|
await insertInlinePage(tester, ViewLayoutPB.Document);
|
||||||
|
|
||||||
final mentionBlock = find.byType(MentionPageBlock);
|
final mentionBlock = find.byType(MentionPageBlock);
|
||||||
expect(mentionBlock, findsOneWidget);
|
expect(mentionBlock, findsOneWidget);
|
||||||
@ -59,7 +59,7 @@ void main() {
|
|||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
final pageName = await insertingInlinePage(tester, ViewLayoutPB.Document);
|
final pageName = await insertInlinePage(tester, ViewLayoutPB.Document);
|
||||||
|
|
||||||
// rename
|
// rename
|
||||||
const newName = 'RenameToNewPageName';
|
const newName = 'RenameToNewPageName';
|
||||||
@ -78,7 +78,7 @@ void main() {
|
|||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
final pageName = await insertingInlinePage(tester, ViewLayoutPB.Grid);
|
final pageName = await insertInlinePage(tester, ViewLayoutPB.Grid);
|
||||||
|
|
||||||
// rename
|
// rename
|
||||||
await tester.hoverOnPageName(
|
await tester.hoverOnPageName(
|
||||||
@ -98,7 +98,7 @@ void main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Insert a referenced database of [layout] into the document
|
/// Insert a referenced database of [layout] into the document
|
||||||
Future<String> insertingInlinePage(
|
Future<String> insertInlinePage(
|
||||||
WidgetTester tester,
|
WidgetTester tester,
|
||||||
ViewLayoutPB layout,
|
ViewLayoutPB layout,
|
||||||
) async {
|
) async {
|
||||||
@ -110,15 +110,19 @@ Future<String> insertingInlinePage(
|
|||||||
layout: layout,
|
layout: layout,
|
||||||
openAfterCreated: false,
|
openAfterCreated: false,
|
||||||
);
|
);
|
||||||
|
|
||||||
// create a new document
|
// create a new document
|
||||||
await tester.createNewPageWithName(
|
await tester.createNewPageWithName(
|
||||||
name: 'insert_a_inline_page_${layout.name}',
|
name: 'insert_a_inline_page_${layout.name}',
|
||||||
layout: ViewLayoutPB.Document,
|
layout: ViewLayoutPB.Document,
|
||||||
);
|
);
|
||||||
|
|
||||||
// tap the first line of the document
|
// tap the first line of the document
|
||||||
await tester.editor.tapLineOfEditorAt(0);
|
await tester.editor.tapLineOfEditorAt(0);
|
||||||
|
|
||||||
// insert a inline page
|
// insert a inline page
|
||||||
await tester.editor.showAtMenu();
|
await tester.editor.showAtMenu();
|
||||||
await tester.editor.tapAtMenuItemWithName(name);
|
await tester.editor.tapAtMenuItemWithName(name);
|
||||||
|
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
@ -113,9 +113,9 @@ const _sample = r'''
|
|||||||
|
|
||||||
[] Type followed by bullet or num to create a list.
|
[] Type followed by bullet or num to create a list.
|
||||||
|
|
||||||
[x] Click `+ New Page` button at the bottom of your sidebar to add a new page.
|
[x] Click `New Page` button at the bottom of your sidebar to add a new page.
|
||||||
|
|
||||||
[] Click `+` next to any page title in the sidebar to quickly add a new subpage, `Document`, `Grid`, or `Kanban Board`.
|
[] Click the plus sign next to any page title in the sidebar to quickly add a new subpage, `Document`, `Grid`, or `Kanban Board`.
|
||||||
---
|
---
|
||||||
* bulleted list 1
|
* bulleted list 1
|
||||||
|
|
||||||
|
@ -160,7 +160,7 @@ class EditorOperations {
|
|||||||
await tester.ime.insertCharacter('/');
|
await tester.ime.insertCharacter('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// trigger the slash command (selection menu)
|
/// trigger the mention (@) command
|
||||||
Future<void> showAtMenu() async {
|
Future<void> showAtMenu() async {
|
||||||
await tester.ime.insertCharacter('@');
|
await tester.ime.insertCharacter('@');
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:appflowy/plugins/document/application/doc_bloc.dart';
|
import 'package:appflowy/plugins/document/application/doc_bloc.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_configuration.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_configuration.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/background_color/theme_background_color.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/background_color/theme_background_color.dart';
|
||||||
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/page_reference_commands.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/i18n/editor_i18n.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/i18n/editor_i18n.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/slash_menu_items.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/slash_menu_items.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||||
@ -139,6 +140,21 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
|||||||
inlineActionsService,
|
inlineActionsService,
|
||||||
style: styleCustomizer.inlineActionsMenuStyleBuilder(),
|
style: styleCustomizer.inlineActionsMenuStyleBuilder(),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
/// Inline page menu
|
||||||
|
/// - Using `[[`
|
||||||
|
pageReferenceShortcutBrackets(
|
||||||
|
context,
|
||||||
|
documentBloc.view.id,
|
||||||
|
styleCustomizer.inlineActionsMenuStyleBuilder(),
|
||||||
|
),
|
||||||
|
|
||||||
|
/// - Using `+`
|
||||||
|
pageReferenceShortcutPlusSign(
|
||||||
|
context,
|
||||||
|
documentBloc.view.id,
|
||||||
|
styleCustomizer.inlineActionsMenuStyleBuilder(),
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
EditorStyleCustomizer get styleCustomizer => widget.styleCustomizer;
|
EditorStyleCustomizer get styleCustomizer => widget.styleCustomizer;
|
||||||
@ -322,6 +338,7 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
|||||||
referencedBoardMenuItem,
|
referencedBoardMenuItem,
|
||||||
inlineCalendarMenuItem(documentBloc),
|
inlineCalendarMenuItem(documentBloc),
|
||||||
referencedCalendarMenuItem,
|
referencedCalendarMenuItem,
|
||||||
|
referencedDocumentMenuItem,
|
||||||
calloutItem,
|
calloutItem,
|
||||||
outlineItem,
|
outlineItem,
|
||||||
mathEquationItem,
|
mathEquationItem,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/database_view_service.dart';
|
import 'package:appflowy/plugins/database_view/application/database_view_service.dart';
|
||||||
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_block.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.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-error/errors.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||||
@ -34,6 +35,7 @@ extension InsertDatabase on EditorState {
|
|||||||
|
|
||||||
Future<void> insertReferencePage(
|
Future<void> insertReferencePage(
|
||||||
ViewPB childView,
|
ViewPB childView,
|
||||||
|
ViewLayoutPB viewType,
|
||||||
) async {
|
) async {
|
||||||
final selection = this.selection;
|
final selection = this.selection;
|
||||||
if (selection == null || !selection.isCollapsed) {
|
if (selection == null || !selection.isCollapsed) {
|
||||||
@ -50,22 +52,63 @@ extension InsertDatabase on EditorState {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
late Transaction transaction;
|
||||||
|
if (viewType == ViewLayoutPB.Document) {
|
||||||
|
transaction = await _insertDocumentReference(
|
||||||
|
childView,
|
||||||
|
selection,
|
||||||
|
node,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
transaction = await _insertDatabaseReference(
|
||||||
|
childView,
|
||||||
|
selection.end.path,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await apply(transaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Transaction> _insertDocumentReference(
|
||||||
|
ViewPB view,
|
||||||
|
Selection selection,
|
||||||
|
Node node,
|
||||||
|
) async {
|
||||||
|
return transaction
|
||||||
|
..replaceText(
|
||||||
|
node,
|
||||||
|
selection.end.offset,
|
||||||
|
0,
|
||||||
|
r'$',
|
||||||
|
attributes: {
|
||||||
|
MentionBlockKeys.mention: {
|
||||||
|
MentionBlockKeys.type: MentionType.page.name,
|
||||||
|
MentionBlockKeys.pageId: view.id,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Transaction> _insertDatabaseReference(
|
||||||
|
ViewPB view,
|
||||||
|
List<int> path,
|
||||||
|
) async {
|
||||||
// get the database id that the view is associated with
|
// get the database id that the view is associated with
|
||||||
final databaseId = await DatabaseViewBackendService(viewId: childView.id)
|
final databaseId = await DatabaseViewBackendService(viewId: view.id)
|
||||||
.getDatabaseId()
|
.getDatabaseId()
|
||||||
.then((value) => value.swap().toOption().toNullable());
|
.then((value) => value.swap().toOption().toNullable());
|
||||||
|
|
||||||
if (databaseId == null) {
|
if (databaseId == null) {
|
||||||
throw StateError(
|
throw StateError(
|
||||||
'The database associated with ${childView.id} could not be found while attempting to create a referenced ${childView.layout.name}.',
|
'The database associated with ${view.id} could not be found while attempting to create a referenced ${view.layout.name}.',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final prefix = _referencedDatabasePrefix(childView.layout);
|
final prefix = _referencedDatabasePrefix(view.layout);
|
||||||
final ref = await ViewBackendService.createDatabaseLinkedView(
|
final ref = await ViewBackendService.createDatabaseLinkedView(
|
||||||
parentViewId: childView.id,
|
parentViewId: view.id,
|
||||||
name: "$prefix ${childView.name}",
|
name: "$prefix ${view.name}",
|
||||||
layoutType: childView.layout,
|
layoutType: view.layout,
|
||||||
databaseId: databaseId,
|
databaseId: databaseId,
|
||||||
).then((value) => value.swap().toOption().toNullable());
|
).then((value) => value.swap().toOption().toNullable());
|
||||||
|
|
||||||
@ -76,18 +119,17 @@ extension InsertDatabase on EditorState {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final transaction = this.transaction;
|
return transaction
|
||||||
transaction.insertNode(
|
..insertNode(
|
||||||
selection.end.path,
|
path,
|
||||||
Node(
|
Node(
|
||||||
type: _convertPageType(childView),
|
type: _convertPageType(view),
|
||||||
attributes: {
|
attributes: {
|
||||||
DatabaseBlockKeys.parentID: childView.id,
|
DatabaseBlockKeys.parentID: view.id,
|
||||||
DatabaseBlockKeys.viewID: ref.id,
|
DatabaseBlockKeys.viewID: ref.id,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
await apply(transaction);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String _referencedDatabasePrefix(ViewLayoutPB layout) {
|
String _referencedDatabasePrefix(ViewLayoutPB layout) {
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/insert_page_command.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/insert_page_command.dart';
|
||||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||||
@ -42,7 +41,7 @@ void showLinkToPageMenu(
|
|||||||
hintText: pageType.toHintText(),
|
hintText: pageType.toHintText(),
|
||||||
onSelected: (appPB, viewPB) async {
|
onSelected: (appPB, viewPB) async {
|
||||||
try {
|
try {
|
||||||
await editorState.insertReferencePage(viewPB);
|
await editorState.insertReferencePage(viewPB, pageType);
|
||||||
linkToPageMenuEntry.remove();
|
linkToPageMenuEntry.remove();
|
||||||
} on FlowyError catch (e) {
|
} on FlowyError catch (e) {
|
||||||
Dialogs.show(
|
Dialogs.show(
|
||||||
@ -188,6 +187,7 @@ class _LinkToPageMenuState extends State<LinkToPageMenu> {
|
|||||||
) {
|
) {
|
||||||
int index = 0;
|
int index = 0;
|
||||||
return FutureBuilder<List<ViewPB>>(
|
return FutureBuilder<List<ViewPB>>(
|
||||||
|
future: items,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.hasData &&
|
if (snapshot.hasData &&
|
||||||
snapshot.connectionState == ConnectionState.done) {
|
snapshot.connectionState == ConnectionState.done) {
|
||||||
@ -208,10 +208,7 @@ class _LinkToPageMenuState extends State<LinkToPageMenu> {
|
|||||||
children.add(
|
children.add(
|
||||||
FlowyButton(
|
FlowyButton(
|
||||||
isSelected: index == _selectedIndex,
|
isSelected: index == _selectedIndex,
|
||||||
leftIcon: FlowySvg(
|
leftIcon: view.defaultIcon(),
|
||||||
view.iconData,
|
|
||||||
color: Theme.of(context).iconTheme.color,
|
|
||||||
),
|
|
||||||
text: FlowyText.regular(view.name),
|
text: FlowyText.regular(view.name),
|
||||||
onTap: () => widget.onSelected(view, view),
|
onTap: () => widget.onSelected(view, view),
|
||||||
),
|
),
|
||||||
@ -229,7 +226,6 @@ class _LinkToPageMenuState extends State<LinkToPageMenu> {
|
|||||||
|
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
},
|
},
|
||||||
future: items,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -239,13 +235,14 @@ extension on ViewLayoutPB {
|
|||||||
switch (this) {
|
switch (this) {
|
||||||
case ViewLayoutPB.Grid:
|
case ViewLayoutPB.Grid:
|
||||||
return LocaleKeys.document_slashMenu_grid_selectAGridToLinkTo.tr();
|
return LocaleKeys.document_slashMenu_grid_selectAGridToLinkTo.tr();
|
||||||
|
|
||||||
case ViewLayoutPB.Board:
|
case ViewLayoutPB.Board:
|
||||||
return LocaleKeys.document_slashMenu_board_selectABoardToLinkTo.tr();
|
return LocaleKeys.document_slashMenu_board_selectABoardToLinkTo.tr();
|
||||||
|
|
||||||
case ViewLayoutPB.Calendar:
|
case ViewLayoutPB.Calendar:
|
||||||
return LocaleKeys.document_slashMenu_calendar_selectACalendarToLinkTo
|
return LocaleKeys.document_slashMenu_calendar_selectACalendarToLinkTo
|
||||||
.tr();
|
.tr();
|
||||||
|
case ViewLayoutPB.Document:
|
||||||
|
return LocaleKeys.document_slashMenu_document_selectADocumentToLinkTo
|
||||||
|
.tr();
|
||||||
default:
|
default:
|
||||||
throw Exception('Unknown layout type');
|
throw Exception('Unknown layout type');
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,117 @@
|
|||||||
|
import 'package:appflowy/plugins/inline_actions/handlers/inline_page_reference.dart';
|
||||||
|
import 'package:appflowy/plugins/inline_actions/inline_actions_menu.dart';
|
||||||
|
import 'package:appflowy/plugins/inline_actions/inline_actions_result.dart';
|
||||||
|
import 'package:appflowy/plugins/inline_actions/inline_actions_service.dart';
|
||||||
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
const _bracketChar = '[';
|
||||||
|
const _plusChar = '+';
|
||||||
|
|
||||||
|
CharacterShortcutEvent pageReferenceShortcutBrackets(
|
||||||
|
BuildContext context,
|
||||||
|
String viewId,
|
||||||
|
InlineActionsMenuStyle style,
|
||||||
|
) =>
|
||||||
|
CharacterShortcutEvent(
|
||||||
|
key: 'show the inline page reference menu by [',
|
||||||
|
character: _bracketChar,
|
||||||
|
handler: (editorState) => inlinePageReferenceCommandHandler(
|
||||||
|
_bracketChar,
|
||||||
|
context,
|
||||||
|
viewId,
|
||||||
|
editorState,
|
||||||
|
style,
|
||||||
|
previousChar: _bracketChar,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
CharacterShortcutEvent pageReferenceShortcutPlusSign(
|
||||||
|
BuildContext context,
|
||||||
|
String viewId,
|
||||||
|
InlineActionsMenuStyle style,
|
||||||
|
) =>
|
||||||
|
CharacterShortcutEvent(
|
||||||
|
key: 'show the inline page reference menu by +',
|
||||||
|
character: _plusChar,
|
||||||
|
handler: (editorState) => inlinePageReferenceCommandHandler(
|
||||||
|
_plusChar,
|
||||||
|
context,
|
||||||
|
viewId,
|
||||||
|
editorState,
|
||||||
|
style,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
InlineActionsMenuService? selectionMenuService;
|
||||||
|
Future<bool> inlinePageReferenceCommandHandler(
|
||||||
|
String character,
|
||||||
|
BuildContext context,
|
||||||
|
String currentViewId,
|
||||||
|
EditorState editorState,
|
||||||
|
InlineActionsMenuStyle style, {
|
||||||
|
String? previousChar,
|
||||||
|
}) async {
|
||||||
|
final selection = editorState.selection;
|
||||||
|
if (PlatformExtension.isMobile || selection == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!selection.isCollapsed) {
|
||||||
|
await editorState.deleteSelection(selection);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for previous character
|
||||||
|
if (previousChar != null) {
|
||||||
|
final node = editorState.getNodeAtPath(selection.end.path);
|
||||||
|
final delta = node?.delta;
|
||||||
|
if (node == null || delta == null || delta.isEmpty) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selection.end.offset > 0) {
|
||||||
|
final plain = delta.toPlainText();
|
||||||
|
|
||||||
|
final previousCharacter = plain[selection.end.offset - 1];
|
||||||
|
if (previousCharacter != _bracketChar) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore: use_build_context_synchronously
|
||||||
|
final service = InlineActionsService(
|
||||||
|
context: context,
|
||||||
|
handlers: [
|
||||||
|
InlinePageReferenceService(
|
||||||
|
currentViewId: currentViewId,
|
||||||
|
).inlinePageReferenceDelegate,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
await editorState.insertTextAtPosition(character, position: selection.start);
|
||||||
|
|
||||||
|
final List<InlineActionsResult> initialResults = [];
|
||||||
|
for (final handler in service.handlers) {
|
||||||
|
final group = await handler();
|
||||||
|
|
||||||
|
if (group.results.isNotEmpty) {
|
||||||
|
initialResults.add(group);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (service.context != null) {
|
||||||
|
selectionMenuService = InlineActionsMenu(
|
||||||
|
context: service.context!,
|
||||||
|
editorState: editorState,
|
||||||
|
service: service,
|
||||||
|
initialResults: initialResults,
|
||||||
|
style: style,
|
||||||
|
startCharAmount: previousChar != null ? 2 : 1,
|
||||||
|
);
|
||||||
|
|
||||||
|
selectionMenuService?.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
@ -7,6 +7,28 @@ import 'package:appflowy_editor/appflowy_editor.dart';
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
// Document Reference
|
||||||
|
|
||||||
|
SelectionMenuItem referencedDocumentMenuItem = SelectionMenuItem(
|
||||||
|
name: LocaleKeys.document_plugins_referencedDocument.tr(),
|
||||||
|
icon: (editorState, onSelected, style) => SelectableSvgWidget(
|
||||||
|
data: FlowySvgs.documents_s,
|
||||||
|
isSelected: onSelected,
|
||||||
|
style: style,
|
||||||
|
),
|
||||||
|
keywords: ['page', 'notes', 'referenced page', 'referenced document'],
|
||||||
|
handler: (editorState, menuService, context) {
|
||||||
|
showLinkToPageMenu(
|
||||||
|
Overlay.of(context),
|
||||||
|
editorState,
|
||||||
|
menuService,
|
||||||
|
ViewLayoutPB.Document,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Database References
|
||||||
|
|
||||||
SelectionMenuItem referencedGridMenuItem = SelectionMenuItem(
|
SelectionMenuItem referencedGridMenuItem = SelectionMenuItem(
|
||||||
name: LocaleKeys.document_plugins_referencedGrid.tr(),
|
name: LocaleKeys.document_plugins_referencedGrid.tr(),
|
||||||
icon: (editorState, onSelected, style) => SelectableSvgWidget(
|
icon: (editorState, onSelected, style) => SelectableSvgWidget(
|
||||||
|
@ -179,7 +179,7 @@ class EditorStyleCustomizer {
|
|||||||
backgroundColor: theme.cardColor,
|
backgroundColor: theme.cardColor,
|
||||||
groupTextColor: theme.colorScheme.onBackground.withOpacity(.8),
|
groupTextColor: theme.colorScheme.onBackground.withOpacity(.8),
|
||||||
menuItemTextColor: theme.colorScheme.onBackground,
|
menuItemTextColor: theme.colorScheme.onBackground,
|
||||||
menuItemSelectedColor: theme.hoverColor,
|
menuItemSelectedColor: theme.colorScheme.secondary,
|
||||||
menuItemSelectedTextColor: theme.colorScheme.onSurface,
|
menuItemSelectedTextColor: theme.colorScheme.onSurface,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -4,16 +4,20 @@ import 'package:appflowy/generated/locale_keys.g.dart';
|
|||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_block.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_block.dart';
|
||||||
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/plugins/inline_actions/inline_actions_result.dart';
|
import 'package:appflowy/plugins/inline_actions/inline_actions_result.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_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
|
||||||
class InlinePageReferenceService {
|
class InlinePageReferenceService {
|
||||||
InlinePageReferenceService({required this.currentViewId}) {
|
InlinePageReferenceService({
|
||||||
|
required this.currentViewId,
|
||||||
|
}) {
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
final Completer _initCompleter = Completer<void>();
|
final Completer _initCompleter = Completer<void>();
|
||||||
|
|
||||||
final String currentViewId;
|
final String currentViewId;
|
||||||
|
|
||||||
late final ViewBackendService service;
|
late final ViewBackendService service;
|
||||||
@ -79,6 +83,7 @@ class InlinePageReferenceService {
|
|||||||
final pageSelectionMenuItem = InlineActionsMenuItem(
|
final pageSelectionMenuItem = InlineActionsMenuItem(
|
||||||
keywords: [view.name.toLowerCase()],
|
keywords: [view.name.toLowerCase()],
|
||||||
label: view.name,
|
label: view.name,
|
||||||
|
icon: (onSelected) => view.defaultIcon(),
|
||||||
onSelected: (context, editorState, menuService, replace) async {
|
onSelected: (context, editorState, menuService, replace) async {
|
||||||
final selection = editorState.selection;
|
final selection = editorState.selection;
|
||||||
if (selection == null || !selection.isCollapsed) {
|
if (selection == null || !selection.isCollapsed) {
|
||||||
|
@ -18,6 +18,7 @@ class InlineActionsMenu extends InlineActionsMenuService {
|
|||||||
required this.service,
|
required this.service,
|
||||||
required this.initialResults,
|
required this.initialResults,
|
||||||
required this.style,
|
required this.style,
|
||||||
|
this.startCharAmount = 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
final BuildContext context;
|
final BuildContext context;
|
||||||
@ -28,6 +29,8 @@ class InlineActionsMenu extends InlineActionsMenuService {
|
|||||||
@override
|
@override
|
||||||
final InlineActionsMenuStyle style;
|
final InlineActionsMenuStyle style;
|
||||||
|
|
||||||
|
final int startCharAmount;
|
||||||
|
|
||||||
OverlayEntry? _menuEntry;
|
OverlayEntry? _menuEntry;
|
||||||
bool selectionChangedByMenu = false;
|
bool selectionChangedByMenu = false;
|
||||||
|
|
||||||
@ -130,6 +133,7 @@ class InlineActionsMenu extends InlineActionsMenuService {
|
|||||||
onDismiss: dismiss,
|
onDismiss: dismiss,
|
||||||
onSelectionUpdate: _onSelectionUpdate,
|
onSelectionUpdate: _onSelectionUpdate,
|
||||||
style: style,
|
style: style,
|
||||||
|
startCharAmount: startCharAmount,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/plugins/inline_actions/inline_actions_command.dart';
|
|
||||||
import 'package:appflowy/plugins/inline_actions/inline_actions_menu.dart';
|
import 'package:appflowy/plugins/inline_actions/inline_actions_menu.dart';
|
||||||
import 'package:appflowy/plugins/inline_actions/inline_actions_result.dart';
|
import 'package:appflowy/plugins/inline_actions/inline_actions_result.dart';
|
||||||
import 'package:appflowy/plugins/inline_actions/inline_actions_service.dart';
|
import 'package:appflowy/plugins/inline_actions/inline_actions_service.dart';
|
||||||
@ -51,6 +50,7 @@ class InlineActionsHandler extends StatefulWidget {
|
|||||||
required this.onDismiss,
|
required this.onDismiss,
|
||||||
required this.onSelectionUpdate,
|
required this.onSelectionUpdate,
|
||||||
required this.style,
|
required this.style,
|
||||||
|
this.startCharAmount = 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
final InlineActionsService service;
|
final InlineActionsService service;
|
||||||
@ -60,6 +60,7 @@ class InlineActionsHandler extends StatefulWidget {
|
|||||||
final VoidCallback onDismiss;
|
final VoidCallback onDismiss;
|
||||||
final VoidCallback onSelectionUpdate;
|
final VoidCallback onSelectionUpdate;
|
||||||
final InlineActionsMenuStyle style;
|
final InlineActionsMenuStyle style;
|
||||||
|
final int startCharAmount;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<InlineActionsHandler> createState() => _InlineActionsHandlerState();
|
State<InlineActionsHandler> createState() => _InlineActionsHandlerState();
|
||||||
@ -99,10 +100,7 @@ class _InlineActionsHandlerState extends State<InlineActionsHandler> {
|
|||||||
_resetSelection();
|
_resetSelection();
|
||||||
|
|
||||||
newResults.sortByStartsWithKeyword(_search);
|
newResults.sortByStartsWithKeyword(_search);
|
||||||
|
setState(() => results = newResults);
|
||||||
setState(() {
|
|
||||||
results = newResults;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _resetSelection() {
|
void _resetSelection() {
|
||||||
@ -116,10 +114,9 @@ class _InlineActionsHandlerState extends State<InlineActionsHandler> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback(
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
(_) => _focusNode.requestFocus(),
|
||||||
_focusNode.requestFocus();
|
);
|
||||||
});
|
|
||||||
|
|
||||||
startOffset = widget.editorState.selection?.endIndex ?? 0;
|
startOffset = widget.editorState.selection?.endIndex ?? 0;
|
||||||
}
|
}
|
||||||
@ -163,6 +160,8 @@ class _InlineActionsHandlerState extends State<InlineActionsHandler> {
|
|||||||
isGroupSelected: _selectedGroup == index,
|
isGroupSelected: _selectedGroup == index,
|
||||||
selectedIndex: _selectedIndex,
|
selectedIndex: _selectedIndex,
|
||||||
onSelected: widget.onDismiss,
|
onSelected: widget.onDismiss,
|
||||||
|
startOffset: startOffset - widget.startCharAmount,
|
||||||
|
endOffset: _search.length + widget.startCharAmount,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
@ -200,7 +199,10 @@ class _InlineActionsHandlerState extends State<InlineActionsHandler> {
|
|||||||
context,
|
context,
|
||||||
widget.editorState,
|
widget.editorState,
|
||||||
widget.menuService,
|
widget.menuService,
|
||||||
(startOffset - 1, _search.length + 1),
|
(
|
||||||
|
startOffset - widget.startCharAmount,
|
||||||
|
_search.length + widget.startCharAmount
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
widget.onDismiss();
|
widget.onDismiss();
|
||||||
@ -212,7 +214,7 @@ class _InlineActionsHandlerState extends State<InlineActionsHandler> {
|
|||||||
} else if (event.logicalKey == LogicalKeyboardKey.backspace) {
|
} else if (event.logicalKey == LogicalKeyboardKey.backspace) {
|
||||||
if (_search.isEmpty) {
|
if (_search.isEmpty) {
|
||||||
widget.onDismiss();
|
widget.onDismiss();
|
||||||
widget.editorState.deleteBackward(); // Delete '@'
|
widget.editorState.deleteBackward();
|
||||||
} else {
|
} else {
|
||||||
widget.onSelectionUpdate();
|
widget.onSelectionUpdate();
|
||||||
widget.editorState.deleteBackward();
|
widget.editorState.deleteBackward();
|
||||||
@ -282,16 +284,12 @@ class _InlineActionsHandlerState extends State<InlineActionsHandler> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Grab index of the first character in command (right after @)
|
|
||||||
final startIndex =
|
|
||||||
delta.toPlainText().lastIndexOf(inlineActionCharacter) + 1;
|
|
||||||
|
|
||||||
search = widget.editorState
|
search = widget.editorState
|
||||||
.getTextInSelection(
|
.getTextInSelection(
|
||||||
selection.copyWith(
|
selection.copyWith(
|
||||||
start: selection.start.copyWith(offset: startIndex),
|
start: selection.start.copyWith(offset: startOffset),
|
||||||
end: selection.start
|
end: selection.start
|
||||||
.copyWith(offset: startIndex + _search.length + 1),
|
.copyWith(offset: startOffset + _search.length + 1),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.join();
|
.join();
|
||||||
@ -331,8 +329,9 @@ class _InlineActionsHandlerState extends State<InlineActionsHandler> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
search = delta
|
search = delta.toPlainText().substring(
|
||||||
.toPlainText()
|
startOffset,
|
||||||
.substring(startOffset, startOffset - 1 + _search.length);
|
startOffset - 1 + _search.length,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import 'package:appflowy/plugins/inline_actions/inline_actions_menu.dart';
|
|||||||
import 'package:appflowy/plugins/inline_actions/inline_actions_result.dart';
|
import 'package:appflowy/plugins/inline_actions/inline_actions_result.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
@ -13,6 +14,8 @@ class InlineActionsGroup extends StatelessWidget {
|
|||||||
required this.menuService,
|
required this.menuService,
|
||||||
required this.style,
|
required this.style,
|
||||||
required this.onSelected,
|
required this.onSelected,
|
||||||
|
required this.startOffset,
|
||||||
|
required this.endOffset,
|
||||||
this.isGroupSelected = false,
|
this.isGroupSelected = false,
|
||||||
this.selectedIndex = 0,
|
this.selectedIndex = 0,
|
||||||
});
|
});
|
||||||
@ -22,6 +25,8 @@ class InlineActionsGroup extends StatelessWidget {
|
|||||||
final InlineActionsMenuService menuService;
|
final InlineActionsMenuService menuService;
|
||||||
final InlineActionsMenuStyle style;
|
final InlineActionsMenuStyle style;
|
||||||
final VoidCallback onSelected;
|
final VoidCallback onSelected;
|
||||||
|
final int startOffset;
|
||||||
|
final int endOffset;
|
||||||
|
|
||||||
final bool isGroupSelected;
|
final bool isGroupSelected;
|
||||||
final int selectedIndex;
|
final int selectedIndex;
|
||||||
@ -43,6 +48,8 @@ class InlineActionsGroup extends StatelessWidget {
|
|||||||
isSelected: isGroupSelected && index == selectedIndex,
|
isSelected: isGroupSelected && index == selectedIndex,
|
||||||
style: style,
|
style: style,
|
||||||
onSelected: onSelected,
|
onSelected: onSelected,
|
||||||
|
startOffset: startOffset,
|
||||||
|
endOffset: endOffset,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -60,6 +67,8 @@ class InlineActionsWidget extends StatefulWidget {
|
|||||||
required this.isSelected,
|
required this.isSelected,
|
||||||
required this.style,
|
required this.style,
|
||||||
required this.onSelected,
|
required this.onSelected,
|
||||||
|
required this.startOffset,
|
||||||
|
required this.endOffset,
|
||||||
});
|
});
|
||||||
|
|
||||||
final InlineActionsMenuItem item;
|
final InlineActionsMenuItem item;
|
||||||
@ -68,56 +77,25 @@ class InlineActionsWidget extends StatefulWidget {
|
|||||||
final bool isSelected;
|
final bool isSelected;
|
||||||
final InlineActionsMenuStyle style;
|
final InlineActionsMenuStyle style;
|
||||||
final VoidCallback onSelected;
|
final VoidCallback onSelected;
|
||||||
|
final int startOffset;
|
||||||
|
final int endOffset;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<InlineActionsWidget> createState() => _InlineActionsWidgetState();
|
State<InlineActionsWidget> createState() => _InlineActionsWidgetState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _InlineActionsWidgetState extends State<InlineActionsWidget> {
|
class _InlineActionsWidgetState extends State<InlineActionsWidget> {
|
||||||
bool isHovering = false;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 2),
|
padding: const EdgeInsets.symmetric(vertical: 2),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: 200,
|
width: 200,
|
||||||
child: widget.item.icon != null
|
child: FlowyButton(
|
||||||
? TextButton.icon(
|
isSelected: widget.isSelected,
|
||||||
onPressed: _onPressed,
|
leftIcon: widget.item.icon?.call(widget.isSelected),
|
||||||
style: ButtonStyle(
|
text: FlowyText.regular(widget.item.label),
|
||||||
alignment: Alignment.centerLeft,
|
onTap: _onPressed,
|
||||||
backgroundColor: widget.isSelected
|
|
||||||
? MaterialStateProperty.all(
|
|
||||||
widget.style.menuItemSelectedColor,
|
|
||||||
)
|
|
||||||
: MaterialStateProperty.all(Colors.transparent),
|
|
||||||
),
|
|
||||||
icon: widget.item.icon!.call(widget.isSelected || isHovering),
|
|
||||||
label: FlowyText.regular(
|
|
||||||
widget.item.label,
|
|
||||||
color: widget.isSelected
|
|
||||||
? widget.style.menuItemSelectedTextColor
|
|
||||||
: widget.style.menuItemTextColor,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: TextButton(
|
|
||||||
onPressed: _onPressed,
|
|
||||||
style: ButtonStyle(
|
|
||||||
alignment: Alignment.centerLeft,
|
|
||||||
backgroundColor: widget.isSelected
|
|
||||||
? MaterialStateProperty.all(
|
|
||||||
widget.style.menuItemSelectedColor,
|
|
||||||
)
|
|
||||||
: MaterialStateProperty.all(Colors.transparent),
|
|
||||||
),
|
|
||||||
onHover: (value) => setState(() => isHovering = value),
|
|
||||||
child: FlowyText.regular(
|
|
||||||
widget.item.label,
|
|
||||||
color: widget.isSelected
|
|
||||||
? widget.style.menuItemSelectedTextColor
|
|
||||||
: widget.style.menuItemTextColor,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -129,7 +107,7 @@ class _InlineActionsWidgetState extends State<InlineActionsWidget> {
|
|||||||
context,
|
context,
|
||||||
widget.editorState,
|
widget.editorState,
|
||||||
widget.menuService,
|
widget.menuService,
|
||||||
(0, 0),
|
(widget.startOffset, widget.endOffset),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -579,6 +579,9 @@
|
|||||||
"calendar": {
|
"calendar": {
|
||||||
"selectACalendarToLinkTo": "Select a Calendar to link to",
|
"selectACalendarToLinkTo": "Select a Calendar to link to",
|
||||||
"createANewCalendar": "Create a new Calendar"
|
"createANewCalendar": "Create a new Calendar"
|
||||||
|
},
|
||||||
|
"document": {
|
||||||
|
"selectADocumentToLinkTo": "Select a Document to link to"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"selectionMenu": {
|
"selectionMenu": {
|
||||||
@ -589,6 +592,7 @@
|
|||||||
"referencedBoard": "Referenced Board",
|
"referencedBoard": "Referenced Board",
|
||||||
"referencedGrid": "Referenced Grid",
|
"referencedGrid": "Referenced Grid",
|
||||||
"referencedCalendar": "Referenced Calendar",
|
"referencedCalendar": "Referenced Calendar",
|
||||||
|
"referencedDocument": "Referenced Document",
|
||||||
"autoGeneratorMenuItemName": "OpenAI Writer",
|
"autoGeneratorMenuItemName": "OpenAI Writer",
|
||||||
"autoGeneratorTitleName": "OpenAI: Ask AI to write anything...",
|
"autoGeneratorTitleName": "OpenAI: Ask AI to write anything...",
|
||||||
"autoGeneratorLearnMore": "Learn more",
|
"autoGeneratorLearnMore": "Learn more",
|
||||||
|
Loading…
Reference in New Issue
Block a user