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:
Mathias Mogensen 2023-11-03 21:30:24 +01:00 committed by GitHub
parent bc502c9c5b
commit b35d6131d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 428 additions and 100 deletions

View File

@ -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();
}

View File

@ -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_toggle_list_test.dart' as document_with_toggle_list_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() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
@ -35,4 +37,5 @@ void startTesting() {
document_text_direction_test.main();
document_option_action_test.main();
document_with_image_block_test.main();
document_inline_page_reference_test.main();
}

View File

@ -15,7 +15,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await insertingInlinePage(tester, ViewLayoutPB.Grid);
await insertInlinePage(tester, ViewLayoutPB.Grid);
final mentionBlock = find.byType(MentionPageBlock);
expect(mentionBlock, findsOneWidget);
@ -26,7 +26,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await insertingInlinePage(tester, ViewLayoutPB.Board);
await insertInlinePage(tester, ViewLayoutPB.Board);
final mentionBlock = find.byType(MentionPageBlock);
expect(mentionBlock, findsOneWidget);
@ -37,7 +37,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await insertingInlinePage(tester, ViewLayoutPB.Calendar);
await insertInlinePage(tester, ViewLayoutPB.Calendar);
final mentionBlock = find.byType(MentionPageBlock);
expect(mentionBlock, findsOneWidget);
@ -48,7 +48,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await insertingInlinePage(tester, ViewLayoutPB.Document);
await insertInlinePage(tester, ViewLayoutPB.Document);
final mentionBlock = find.byType(MentionPageBlock);
expect(mentionBlock, findsOneWidget);
@ -59,7 +59,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
final pageName = await insertingInlinePage(tester, ViewLayoutPB.Document);
final pageName = await insertInlinePage(tester, ViewLayoutPB.Document);
// rename
const newName = 'RenameToNewPageName';
@ -78,7 +78,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
final pageName = await insertingInlinePage(tester, ViewLayoutPB.Grid);
final pageName = await insertInlinePage(tester, ViewLayoutPB.Grid);
// rename
await tester.hoverOnPageName(
@ -98,7 +98,7 @@ void main() {
}
/// Insert a referenced database of [layout] into the document
Future<String> insertingInlinePage(
Future<String> insertInlinePage(
WidgetTester tester,
ViewLayoutPB layout,
) async {
@ -110,15 +110,19 @@ Future<String> insertingInlinePage(
layout: layout,
openAfterCreated: false,
);
// create a new document
await tester.createNewPageWithName(
name: 'insert_a_inline_page_${layout.name}',
layout: ViewLayoutPB.Document,
);
// tap the first line of the document
await tester.editor.tapLineOfEditorAt(0);
// insert a inline page
await tester.editor.showAtMenu();
await tester.editor.tapAtMenuItemWithName(name);
return name;
}

View File

@ -113,9 +113,9 @@ const _sample = r'''
[] 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

View File

@ -160,7 +160,7 @@ class EditorOperations {
await tester.ime.insertCharacter('/');
}
/// trigger the slash command (selection menu)
/// trigger the mention (@) command
Future<void> showAtMenu() async {
await tester.ime.insertCharacter('@');
}

View File

@ -1,6 +1,7 @@
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_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/mention/slash_menu_items.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
@ -139,6 +140,21 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
inlineActionsService,
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;
@ -322,6 +338,7 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
referencedBoardMenuItem,
inlineCalendarMenuItem(documentBloc),
referencedCalendarMenuItem,
referencedDocumentMenuItem,
calloutItem,
outlineItem,
mathEquationItem,

View File

@ -1,5 +1,6 @@
import 'package:appflowy/generated/locale_keys.g.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/workspace/application/view/view_service.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
@ -34,6 +35,7 @@ extension InsertDatabase on EditorState {
Future<void> insertReferencePage(
ViewPB childView,
ViewLayoutPB viewType,
) async {
final selection = this.selection;
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
final databaseId = await DatabaseViewBackendService(viewId: childView.id)
final databaseId = await DatabaseViewBackendService(viewId: view.id)
.getDatabaseId()
.then((value) => value.swap().toOption().toNullable());
if (databaseId == null) {
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(
parentViewId: childView.id,
name: "$prefix ${childView.name}",
layoutType: childView.layout,
parentViewId: view.id,
name: "$prefix ${view.name}",
layoutType: view.layout,
databaseId: databaseId,
).then((value) => value.swap().toOption().toNullable());
@ -76,18 +119,17 @@ extension InsertDatabase on EditorState {
);
}
final transaction = this.transaction;
transaction.insertNode(
selection.end.path,
return transaction
..insertNode(
path,
Node(
type: _convertPageType(childView),
type: _convertPageType(view),
attributes: {
DatabaseBlockKeys.parentID: childView.id,
DatabaseBlockKeys.parentID: view.id,
DatabaseBlockKeys.viewID: ref.id,
},
),
);
await apply(transaction);
}
String _referencedDatabasePrefix(ViewLayoutPB layout) {

View File

@ -1,4 +1,3 @@
import 'package:appflowy/generated/flowy_svgs.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/workspace/application/view/view_ext.dart';
@ -42,7 +41,7 @@ void showLinkToPageMenu(
hintText: pageType.toHintText(),
onSelected: (appPB, viewPB) async {
try {
await editorState.insertReferencePage(viewPB);
await editorState.insertReferencePage(viewPB, pageType);
linkToPageMenuEntry.remove();
} on FlowyError catch (e) {
Dialogs.show(
@ -188,6 +187,7 @@ class _LinkToPageMenuState extends State<LinkToPageMenu> {
) {
int index = 0;
return FutureBuilder<List<ViewPB>>(
future: items,
builder: (context, snapshot) {
if (snapshot.hasData &&
snapshot.connectionState == ConnectionState.done) {
@ -208,10 +208,7 @@ class _LinkToPageMenuState extends State<LinkToPageMenu> {
children.add(
FlowyButton(
isSelected: index == _selectedIndex,
leftIcon: FlowySvg(
view.iconData,
color: Theme.of(context).iconTheme.color,
),
leftIcon: view.defaultIcon(),
text: FlowyText.regular(view.name),
onTap: () => widget.onSelected(view, view),
),
@ -229,7 +226,6 @@ class _LinkToPageMenuState extends State<LinkToPageMenu> {
return const Center(child: CircularProgressIndicator());
},
future: items,
);
}
}
@ -239,13 +235,14 @@ extension on ViewLayoutPB {
switch (this) {
case ViewLayoutPB.Grid:
return LocaleKeys.document_slashMenu_grid_selectAGridToLinkTo.tr();
case ViewLayoutPB.Board:
return LocaleKeys.document_slashMenu_board_selectABoardToLinkTo.tr();
case ViewLayoutPB.Calendar:
return LocaleKeys.document_slashMenu_calendar_selectACalendarToLinkTo
.tr();
case ViewLayoutPB.Document:
return LocaleKeys.document_slashMenu_document_selectADocumentToLinkTo
.tr();
default:
throw Exception('Unknown layout type');
}

View File

@ -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;
}

View File

@ -7,6 +7,28 @@ import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.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(
name: LocaleKeys.document_plugins_referencedGrid.tr(),
icon: (editorState, onSelected, style) => SelectableSvgWidget(

View File

@ -179,7 +179,7 @@ class EditorStyleCustomizer {
backgroundColor: theme.cardColor,
groupTextColor: theme.colorScheme.onBackground.withOpacity(.8),
menuItemTextColor: theme.colorScheme.onBackground,
menuItemSelectedColor: theme.hoverColor,
menuItemSelectedColor: theme.colorScheme.secondary,
menuItemSelectedTextColor: theme.colorScheme.onSurface,
);
}

View File

@ -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_page_block.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_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart';
class InlinePageReferenceService {
InlinePageReferenceService({required this.currentViewId}) {
InlinePageReferenceService({
required this.currentViewId,
}) {
init();
}
final Completer _initCompleter = Completer<void>();
final String currentViewId;
late final ViewBackendService service;
@ -79,6 +83,7 @@ class InlinePageReferenceService {
final pageSelectionMenuItem = InlineActionsMenuItem(
keywords: [view.name.toLowerCase()],
label: view.name,
icon: (onSelected) => view.defaultIcon(),
onSelected: (context, editorState, menuService, replace) async {
final selection = editorState.selection;
if (selection == null || !selection.isCollapsed) {

View File

@ -18,6 +18,7 @@ class InlineActionsMenu extends InlineActionsMenuService {
required this.service,
required this.initialResults,
required this.style,
this.startCharAmount = 1,
});
final BuildContext context;
@ -28,6 +29,8 @@ class InlineActionsMenu extends InlineActionsMenuService {
@override
final InlineActionsMenuStyle style;
final int startCharAmount;
OverlayEntry? _menuEntry;
bool selectionChangedByMenu = false;
@ -130,6 +133,7 @@ class InlineActionsMenu extends InlineActionsMenuService {
onDismiss: dismiss,
onSelectionUpdate: _onSelectionUpdate,
style: style,
startCharAmount: startCharAmount,
),
),
),

View File

@ -1,5 +1,4 @@
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_result.dart';
import 'package:appflowy/plugins/inline_actions/inline_actions_service.dart';
@ -51,6 +50,7 @@ class InlineActionsHandler extends StatefulWidget {
required this.onDismiss,
required this.onSelectionUpdate,
required this.style,
this.startCharAmount = 1,
});
final InlineActionsService service;
@ -60,6 +60,7 @@ class InlineActionsHandler extends StatefulWidget {
final VoidCallback onDismiss;
final VoidCallback onSelectionUpdate;
final InlineActionsMenuStyle style;
final int startCharAmount;
@override
State<InlineActionsHandler> createState() => _InlineActionsHandlerState();
@ -99,10 +100,7 @@ class _InlineActionsHandlerState extends State<InlineActionsHandler> {
_resetSelection();
newResults.sortByStartsWithKeyword(_search);
setState(() {
results = newResults;
});
setState(() => results = newResults);
}
void _resetSelection() {
@ -116,10 +114,9 @@ class _InlineActionsHandlerState extends State<InlineActionsHandler> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
_focusNode.requestFocus();
});
WidgetsBinding.instance.addPostFrameCallback(
(_) => _focusNode.requestFocus(),
);
startOffset = widget.editorState.selection?.endIndex ?? 0;
}
@ -163,6 +160,8 @@ class _InlineActionsHandlerState extends State<InlineActionsHandler> {
isGroupSelected: _selectedGroup == index,
selectedIndex: _selectedIndex,
onSelected: widget.onDismiss,
startOffset: startOffset - widget.startCharAmount,
endOffset: _search.length + widget.startCharAmount,
),
)
.toList(),
@ -200,7 +199,10 @@ class _InlineActionsHandlerState extends State<InlineActionsHandler> {
context,
widget.editorState,
widget.menuService,
(startOffset - 1, _search.length + 1),
(
startOffset - widget.startCharAmount,
_search.length + widget.startCharAmount
),
);
widget.onDismiss();
@ -212,7 +214,7 @@ class _InlineActionsHandlerState extends State<InlineActionsHandler> {
} else if (event.logicalKey == LogicalKeyboardKey.backspace) {
if (_search.isEmpty) {
widget.onDismiss();
widget.editorState.deleteBackward(); // Delete '@'
widget.editorState.deleteBackward();
} else {
widget.onSelectionUpdate();
widget.editorState.deleteBackward();
@ -282,16 +284,12 @@ class _InlineActionsHandlerState extends State<InlineActionsHandler> {
return;
}
/// Grab index of the first character in command (right after @)
final startIndex =
delta.toPlainText().lastIndexOf(inlineActionCharacter) + 1;
search = widget.editorState
.getTextInSelection(
selection.copyWith(
start: selection.start.copyWith(offset: startIndex),
start: selection.start.copyWith(offset: startOffset),
end: selection.start
.copyWith(offset: startIndex + _search.length + 1),
.copyWith(offset: startOffset + _search.length + 1),
),
)
.join();
@ -331,8 +329,9 @@ class _InlineActionsHandlerState extends State<InlineActionsHandler> {
return;
}
search = delta
.toPlainText()
.substring(startOffset, startOffset - 1 + _search.length);
search = delta.toPlainText().substring(
startOffset,
startOffset - 1 + _search.length,
);
}
}

View File

@ -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_editor/appflowy_editor.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:flutter/material.dart';
@ -13,6 +14,8 @@ class InlineActionsGroup extends StatelessWidget {
required this.menuService,
required this.style,
required this.onSelected,
required this.startOffset,
required this.endOffset,
this.isGroupSelected = false,
this.selectedIndex = 0,
});
@ -22,6 +25,8 @@ class InlineActionsGroup extends StatelessWidget {
final InlineActionsMenuService menuService;
final InlineActionsMenuStyle style;
final VoidCallback onSelected;
final int startOffset;
final int endOffset;
final bool isGroupSelected;
final int selectedIndex;
@ -43,6 +48,8 @@ class InlineActionsGroup extends StatelessWidget {
isSelected: isGroupSelected && index == selectedIndex,
style: style,
onSelected: onSelected,
startOffset: startOffset,
endOffset: endOffset,
),
),
],
@ -60,6 +67,8 @@ class InlineActionsWidget extends StatefulWidget {
required this.isSelected,
required this.style,
required this.onSelected,
required this.startOffset,
required this.endOffset,
});
final InlineActionsMenuItem item;
@ -68,56 +77,25 @@ class InlineActionsWidget extends StatefulWidget {
final bool isSelected;
final InlineActionsMenuStyle style;
final VoidCallback onSelected;
final int startOffset;
final int endOffset;
@override
State<InlineActionsWidget> createState() => _InlineActionsWidgetState();
}
class _InlineActionsWidgetState extends State<InlineActionsWidget> {
bool isHovering = false;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: SizedBox(
width: 200,
child: widget.item.icon != null
? TextButton.icon(
onPressed: _onPressed,
style: ButtonStyle(
alignment: Alignment.centerLeft,
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,
),
child: FlowyButton(
isSelected: widget.isSelected,
leftIcon: widget.item.icon?.call(widget.isSelected),
text: FlowyText.regular(widget.item.label),
onTap: _onPressed,
),
),
);
@ -129,7 +107,7 @@ class _InlineActionsWidgetState extends State<InlineActionsWidget> {
context,
widget.editorState,
widget.menuService,
(0, 0),
(widget.startOffset, widget.endOffset),
);
}
}

View File

@ -579,6 +579,9 @@
"calendar": {
"selectACalendarToLinkTo": "Select a Calendar to link to",
"createANewCalendar": "Create a new Calendar"
},
"document": {
"selectADocumentToLinkTo": "Select a Document to link to"
}
},
"selectionMenu": {
@ -589,6 +592,7 @@
"referencedBoard": "Referenced Board",
"referencedGrid": "Referenced Grid",
"referencedCalendar": "Referenced Calendar",
"referencedDocument": "Referenced Document",
"autoGeneratorMenuItemName": "OpenAI Writer",
"autoGeneratorTitleName": "OpenAI: Ask AI to write anything...",
"autoGeneratorLearnMore": "Learn more",