diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_codeblock_paste_test.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_codeblock_paste_test.dart index f06a273fac..0fb8cc90e8 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/document/document_codeblock_paste_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_codeblock_paste_test.dart @@ -1,10 +1,10 @@ import 'dart:io'; -import 'package:flutter/services.dart'; - import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; @@ -25,6 +25,7 @@ void main() { const lines = 3; final text = List.generate(lines, (index) => 'line $index').join('\n'); AppFlowyClipboard.mockSetData(AppFlowyClipboardData(text: text)); + ClipboardService.mockSetData(ClipboardServiceData(plainText: text)); await insertCodeBlockInDocument(tester); @@ -51,7 +52,9 @@ Future insertCodeBlockInDocument(WidgetTester tester) async { // open the actions menu and insert the codeBlock await tester.editor.showSlashMenu(); await tester.editor.tapSlashMenuItemWithName( - LocaleKeys.document_selectionMenu_codeBlock.tr(), + LocaleKeys.document_slashMenu_name_code.tr(), + offset: 150, ); + // wait for the codeBlock to be inserted await tester.pumpAndSettle(); } diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_with_database_test.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_with_database_test.dart index f401cb1e0b..0ee7610b93 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/document/document_with_database_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_with_database_test.dart @@ -1,4 +1,3 @@ -import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database/board/presentation/board_page.dart'; import 'package:appflowy/plugins/database/calendar/presentation/calendar_page.dart'; import 'package:appflowy/plugins/database/grid/presentation/grid_page.dart'; @@ -7,7 +6,6 @@ import 'package:appflowy/plugins/inline_actions/widgets/inline_actions_handler.d import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; -import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/uuid.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; @@ -22,7 +20,7 @@ void main() { await tester.initializeAppFlowy(); await tester.tapAnonymousSignInButton(); - await insertReferenceDatabase(tester, ViewLayoutPB.Grid); + await insertLinkedDatabase(tester, ViewLayoutPB.Grid); // validate the referenced grid is inserted expect( @@ -50,7 +48,7 @@ void main() { await tester.initializeAppFlowy(); await tester.tapAnonymousSignInButton(); - await insertReferenceDatabase(tester, ViewLayoutPB.Board); + await insertLinkedDatabase(tester, ViewLayoutPB.Board); // validate the referenced board is inserted expect( @@ -66,7 +64,7 @@ void main() { await tester.initializeAppFlowy(); await tester.tapAnonymousSignInButton(); - await insertReferenceDatabase(tester, ViewLayoutPB.Calendar); + await insertLinkedDatabase(tester, ViewLayoutPB.Calendar); // validate the referenced grid is inserted expect( @@ -129,7 +127,7 @@ void main() { } /// Insert a referenced database of [layout] into the document -Future insertReferenceDatabase( +Future insertLinkedDatabase( WidgetTester tester, ViewLayoutPB layout, ) async { @@ -150,7 +148,7 @@ Future insertReferenceDatabase( // insert a referenced view await tester.editor.showSlashMenu(); await tester.editor.tapSlashMenuItemWithName( - layout.referencedMenuName, + layout.slashMenuLinkedName, ); final linkToPageMenu = find.byType(InlineActionsHandler); @@ -176,16 +174,8 @@ Future createInlineDatabase( await tester.editor.tapLineOfEditorAt(0); // insert a referenced view await tester.editor.showSlashMenu(); - final name = switch (layout) { - ViewLayoutPB.Grid => LocaleKeys.document_slashMenu_grid_createANewGrid.tr(), - ViewLayoutPB.Board => - LocaleKeys.document_slashMenu_board_createANewBoard.tr(), - ViewLayoutPB.Calendar => - LocaleKeys.document_slashMenu_calendar_createANewCalendar.tr(), - _ => '', - }; await tester.editor.tapSlashMenuItemWithName( - name, + layout.slashMenuName, ); await tester.pumpAndSettle(); diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_with_file_test.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_with_file_test.dart index b7ad7eab1a..76ad7d612f 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/document/document_with_file_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_with_file_test.dart @@ -1,7 +1,5 @@ import 'dart:io'; -import 'package:flutter/services.dart'; - import 'package:appflowy/core/config/kv.dart'; import 'package:appflowy/core/config/kv_keys.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; @@ -10,6 +8,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.da import 'package:appflowy/startup/startup.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:path/path.dart' as p; @@ -33,7 +32,9 @@ void main() { // tap the first line of the document await tester.editor.tapLineOfEditorAt(0); await tester.editor.showSlashMenu(); - await tester.editor.tapSlashMenuItemWithName('File'); + await tester.editor.tapSlashMenuItemWithName( + LocaleKeys.document_slashMenu_name_file.tr(), + ); expect(find.byType(FileBlockComponent), findsOneWidget); await tester.tap(find.byType(FileBlockComponent)); @@ -111,7 +112,9 @@ void main() { // tap the first line of the document await tester.editor.tapLineOfEditorAt(0); await tester.editor.showSlashMenu(); - await tester.editor.tapSlashMenuItemWithName('File'); + await tester.editor.tapSlashMenuItemWithName( + LocaleKeys.document_slashMenu_name_file.tr(), + ); expect(find.byType(FileBlockComponent), findsOneWidget); await tester.tap(find.byType(FileBlockComponent)); diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_with_image_block_test.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_with_image_block_test.dart index e5ff2a4dae..fb0c686824 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/document/document_with_image_block_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_with_image_block_test.dart @@ -1,8 +1,5 @@ import 'dart:io'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; - import 'package:appflowy/core/config/kv.dart'; import 'package:appflowy/core/config/kv_keys.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; @@ -17,6 +14,8 @@ import 'package:appflowy_editor/appflowy_editor.dart' hide UploadImageMenu, ResizableImage; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:path/path.dart' as p; @@ -43,7 +42,9 @@ void main() { // tap the first line of the document await tester.editor.tapLineOfEditorAt(0); await tester.editor.showSlashMenu(); - await tester.editor.tapSlashMenuItemWithName('Image'); + await tester.editor.tapSlashMenuItemWithName( + LocaleKeys.document_slashMenu_name_image.tr(), + ); expect(find.byType(CustomImageBlockComponent), findsOneWidget); expect(find.byType(ImagePlaceholder), findsOneWidget); expect( @@ -91,7 +92,9 @@ void main() { // tap the first line of the document await tester.editor.tapLineOfEditorAt(0); await tester.editor.showSlashMenu(); - await tester.editor.tapSlashMenuItemWithName('Image'); + await tester.editor.tapSlashMenuItemWithName( + LocaleKeys.document_slashMenu_name_image.tr(), + ); expect(find.byType(CustomImageBlockComponent), findsOneWidget); expect(find.byType(ImagePlaceholder), findsOneWidget); expect( @@ -144,7 +147,9 @@ void main() { // tap the first line of the document await tester.editor.tapLineOfEditorAt(0); await tester.editor.showSlashMenu(); - await tester.editor.tapSlashMenuItemWithName('Image'); + await tester.editor.tapSlashMenuItemWithName( + LocaleKeys.document_slashMenu_name_image.tr(), + ); expect(find.byType(CustomImageBlockComponent), findsOneWidget); expect(find.byType(ImagePlaceholder), findsOneWidget); expect( @@ -175,7 +180,9 @@ void main() { // tap the first line of the document await tester.editor.tapLineOfEditorAt(0); await tester.editor.showSlashMenu(); - await tester.editor.tapSlashMenuItemWithName('Image'); + await tester.editor.tapSlashMenuItemWithName( + LocaleKeys.document_slashMenu_name_image.tr(), + ); expect(find.byType(CustomImageBlockComponent), findsOneWidget); expect(find.byType(ImagePlaceholder), findsOneWidget); expect( diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_with_multi_image_block_test.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_with_multi_image_block_test.dart index e787df18f6..d85e6c631e 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/document/document_with_multi_image_block_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_with_multi_image_block_test.dart @@ -1,9 +1,5 @@ import 'dart:io'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; - import 'package:appflowy/core/config/kv.dart'; import 'package:appflowy/core/config/kv_keys.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart'; @@ -20,6 +16,9 @@ import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/workspace/presentation/widgets/image_viewer/interactive_image_toolbar.dart'; import 'package:appflowy/workspace/presentation/widgets/image_viewer/interactive_image_viewer.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:path/path.dart' as p; @@ -49,7 +48,10 @@ void main() { // tap the first line of the document await tester.editor.tapLineOfEditorAt(0); await tester.editor.showSlashMenu(); - await tester.editor.tapSlashMenuItemWithName('Photo gallery'); + await tester.editor.tapSlashMenuItemWithName( + LocaleKeys.document_slashMenu_name_photoGallery.tr(), + offset: 100, + ); expect(find.byType(MultiImageBlockComponent), findsOneWidget); expect(find.byType(MultiImagePlaceholder), findsOneWidget); @@ -144,7 +146,10 @@ void main() { // tap the first line of the document await tester.editor.tapLineOfEditorAt(0); await tester.editor.showSlashMenu(); - await tester.editor.tapSlashMenuItemWithName('Photo gallery'); + await tester.editor.tapSlashMenuItemWithName( + LocaleKeys.document_slashMenu_name_photoGallery.tr(), + offset: 100, + ); expect(find.byType(MultiImageBlockComponent), findsOneWidget); expect(find.byType(MultiImagePlaceholder), findsOneWidget); diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_with_outline_block_test.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_with_outline_block_test.dart index bfd8198295..fc6d0f86a6 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/document/document_with_outline_block_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_with_outline_block_test.dart @@ -171,7 +171,8 @@ Future insertOutlineInDocument(WidgetTester tester) async { // open the actions menu and insert the outline block await tester.editor.showSlashMenu(); await tester.editor.tapSlashMenuItemWithName( - LocaleKeys.document_selectionMenu_outline.tr(), + LocaleKeys.document_slashMenu_name_outline.tr(), + offset: 100, ); await tester.pumpAndSettle(); } diff --git a/frontend/appflowy_flutter/integration_test/shared/common_operations.dart b/frontend/appflowy_flutter/integration_test/shared/common_operations.dart index 9192ee3cf8..fad17a00c7 100644 --- a/frontend/appflowy_flutter/integration_test/shared/common_operations.dart +++ b/frontend/appflowy_flutter/integration_test/shared/common_operations.dart @@ -662,4 +662,34 @@ extension ViewLayoutPBTest on ViewLayoutPB { throw UnsupportedError('Unsupported layout: $this'); } } + + String get slashMenuName { + switch (this) { + case ViewLayoutPB.Grid: + return LocaleKeys.document_slashMenu_name_grid.tr(); + case ViewLayoutPB.Board: + return LocaleKeys.document_slashMenu_name_kanban.tr(); + case ViewLayoutPB.Document: + return LocaleKeys.document_slashMenu_name_doc.tr(); + case ViewLayoutPB.Calendar: + return LocaleKeys.document_slashMenu_name_calendar.tr(); + default: + throw UnsupportedError('Unsupported layout: $this'); + } + } + + String get slashMenuLinkedName { + switch (this) { + case ViewLayoutPB.Grid: + return LocaleKeys.document_slashMenu_name_linkedGrid.tr(); + case ViewLayoutPB.Board: + return LocaleKeys.document_slashMenu_name_linkedKanban.tr(); + case ViewLayoutPB.Document: + return LocaleKeys.document_slashMenu_name_linkedDoc.tr(); + case ViewLayoutPB.Calendar: + return LocaleKeys.document_slashMenu_name_linkedCalendar.tr(); + default: + throw UnsupportedError('Unsupported layout: $this'); + } + } } diff --git a/frontend/appflowy_flutter/integration_test/shared/editor_test_operations.dart b/frontend/appflowy_flutter/integration_test/shared/editor_test_operations.dart index 0c712f8e24..388b685bfb 100644 --- a/frontend/appflowy_flutter/integration_test/shared/editor_test_operations.dart +++ b/frontend/appflowy_flutter/integration_test/shared/editor_test_operations.dart @@ -3,7 +3,6 @@ import 'dart:ui'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/base/emoji/emoji_picker.dart'; -import 'package:appflowy/shared/icon_emoji_picker/emoji_skin_tone.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_add_button.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_option_button.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/header/cover_editor.dart'; @@ -11,6 +10,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/header/doc import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/image/upload_image_menu/widgets/embed_image_url_widget.dart'; import 'package:appflowy/plugins/inline_actions/widgets/inline_actions_handler.dart'; +import 'package:appflowy/shared/icon_emoji_picker/emoji_skin_tone.dart'; import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart'; import 'package:appflowy_editor/appflowy_editor.dart' hide Log; import 'package:easy_localization/easy_localization.dart'; @@ -170,7 +170,10 @@ class EditorOperations { /// Tap the slash menu item with [name] /// /// Must call [showSlashMenu] first. - Future tapSlashMenuItemWithName(String name) async { + Future tapSlashMenuItemWithName( + String name, { + double offset = 200, + }) async { final slashMenu = find .ancestor( of: find.byType(SelectionMenuItemWidget), @@ -180,8 +183,13 @@ class EditorOperations { ) .first; final slashMenuItem = find.text(name, findRichText: true); - await tester.scrollUntilVisible(slashMenuItem, 200, scrollable: slashMenu); - // await tester.ensureVisible(slashMenuItem); + await tester.scrollUntilVisible( + slashMenuItem, + offset, + scrollable: slashMenu, + duration: const Duration(milliseconds: 250), + ); + assert(slashMenuItem.hasFound); await tester.tapButton(slashMenuItem); } diff --git a/frontend/appflowy_flutter/ios/Podfile.lock b/frontend/appflowy_flutter/ios/Podfile.lock index 72df3b0bd7..c54ae23ed6 100644 --- a/frontend/appflowy_flutter/ios/Podfile.lock +++ b/frontend/appflowy_flutter/ios/Podfile.lock @@ -175,7 +175,7 @@ SPEC CHECKSUMS: file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655 flowy_infra_ui: 0455e1fa8c51885aa1437848e361e99419f34ebc Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - fluttertoast: 723e187574b149e68e63ca4d39b837586b903cfa + fluttertoast: e9a18c7be5413da53898f660530c56f35edfba9c image_gallery_saver: cb43cc43141711190510e92c460eb1655cd343cb image_picker_ios: 99dfe1854b4fa34d0364e74a78448a0151025425 integration_test: ce0a3ffa1de96d1a89ca0ac26fca7ea18a749ef4 @@ -197,4 +197,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: d0d9b4ff572d8695c38eb3f9b490f55cdfc57eca -COCOAPODS: 1.11.3 +COCOAPODS: 1.15.2 diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart index 822fe4d354..2fbd0a3986 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart @@ -9,7 +9,6 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/base/forma import 'package:appflowy/plugins/document/presentation/editor_plugins/base/page_reference_commands.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/callout/callout_block_shortcuts.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'; import 'package:appflowy/plugins/document/presentation/editor_style.dart'; import 'package:appflowy/plugins/inline_actions/handlers/date_reference.dart'; @@ -146,7 +145,7 @@ class _AppFlowyEditorPageState extends State { customizeFontToolbarItem, ]; - late final List slashMenuItems; + late List slashMenuItems; List get characterShortcutEvents => [ // code block @@ -282,9 +281,17 @@ class _AppFlowyEditorPageState extends State { focusManager = currFocusManager; focusManager?.loseFocusNotifier.addListener(_loseFocus); } + super.didChangeDependencies(); } + @override + void reassemble() { + super.reassemble(); + + slashMenuItems = _customSlashMenuItems(); + } + @override void dispose() { focusManager?.loseFocusNotifier.removeListener(_loseFocus); @@ -387,42 +394,45 @@ class _AppFlowyEditorPageState extends State { editorState: editorState, editorScrollController: editorScrollController, textDirection: textDirection, - tooltipBuilder: (context, id, message, child) => widget.styleCustomizer - .buildToolbarItemTooltip(context, id, message, child,), + tooltipBuilder: (context, id, message, child) => + widget.styleCustomizer.buildToolbarItemTooltip( + context, + id, + message, + child, + ), child: editor, ), ); } List _customSlashMenuItems() { - final items = [...standardSelectionMenuItems]; - final imageItem = items - .firstWhereOrNull((e) => e.name == AppFlowyEditorL10n.current.image); - if (imageItem != null) { - final imageItemIndex = items.indexOf(imageItem); - if (imageItemIndex != -1) { - items[imageItemIndex] = customImageMenuItem; - } - } return [ - ...items, - inlineGridMenuItem(documentBloc), - referencedGridMenuItem, - inlineBoardMenuItem(documentBloc), - referencedBoardMenuItem, - inlineCalendarMenuItem(documentBloc), - referencedCalendarMenuItem, - referencedDocumentMenuItem, - calloutItem, - outlineItem, - mathEquationItem, - codeBlockItem(LocaleKeys.document_selectionMenu_codeBlock.tr()), - toggleListBlockItem, - emojiMenuItem, - autoGeneratorMenuItem, - dateMenuItem, - multiImageMenuItem, - fileMenuItem, + aiWriterSlashMenuItem, + textSlashMenuItem, + heading1SlashMenuItem, + heading2SlashMenuItem, + heading3SlashMenuItem, + imageSlashMenuItem, + bulletedListSlashMenuItem, + numberedListSlashMenuItem, + quoteSlashMenuItem, + referencedDocSlashMenuItem, + gridSlashMenuItem(documentBloc), + referencedGridSlashMenuItem, + kanbanSlashMenuItem(documentBloc), + referencedKanbanSlashMenuItem, + calendarSlashMenuItem(documentBloc), + referencedCalendarSlashMenuItem, + calloutSlashMenuItem, + outlineSlashMenuItem, + mathEquationSlashMenuItem, + codeBlockSlashMenuItem, + toggleListSlashMenuItem, + emojiSlashMenuItem, + dateOrReminderSlashMenuItem, + photoGallerySlashMenuItem, + fileSlashMenuItem, ]; } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/selectable_svg_widget.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/selectable_svg_widget.dart index 8f3e4b7477..f1b082e0a7 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/selectable_svg_widget.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/selectable_svg_widget.dart @@ -1,6 +1,5 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; - import 'package:flutter/material.dart'; class SelectableSvgWidget extends StatelessWidget { @@ -9,21 +8,31 @@ class SelectableSvgWidget extends StatelessWidget { required this.data, required this.isSelected, required this.style, + this.size, + this.padding, }); final FlowySvgData data; final bool isSelected; final SelectionMenuStyle style; + final Size? size; + final EdgeInsets? padding; @override Widget build(BuildContext context) { - return FlowySvg( + final child = FlowySvg( data, - size: const Size.square(18.0), + size: size ?? const Size.square(16.0), color: isSelected ? style.selectionMenuItemSelectedIconColor : style.selectionMenuItemIconColor, ); + + if (padding != null) { + return Padding(padding: padding!, child: child); + } else { + return child; + } } } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_menu_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_menu_item.dart new file mode 100644 index 0000000000..c4e395f22f --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_menu_item.dart @@ -0,0 +1,18 @@ +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/selectable_svg_widget.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart'; +import 'package:easy_localization/easy_localization.dart'; + +final codeBlockSelectionMenuItem = SelectionMenuItem.node( + getName: () => LocaleKeys.document_selectionMenu_codeBlock.tr(), + iconBuilder: (editorState, onSelected, style) => SelectableSvgWidget( + data: FlowySvgs.icon_code_block_s, + isSelected: onSelected, + style: style, + ), + keywords: ['code', 'codeblock'], + nodeBuilder: (_, __) => codeBlockNode(), + replace: (_, node) => node.delta?.isEmpty ?? false, +); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart index e188e6b29f..436de2c601 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart @@ -40,6 +40,13 @@ class ClipboardServiceData { } class ClipboardService { + static ClipboardServiceData? _mockData; + + @visibleForTesting + static void mockSetData(ClipboardServiceData? data) { + _mockData = data; + } + Future setData(ClipboardServiceData data) async { final plainText = data.plainText; final html = data.html; @@ -81,6 +88,10 @@ class ClipboardService { } Future getData() async { + if (_mockData != null) { + return _mockData!; + } + final reader = await SystemClipboard.instance?.read(); if (reader == null) { diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/image_selection_menu.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/image_selection_menu.dart index 47c9e1fdf3..2dfc2bdaef 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/image_selection_menu.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/image_selection_menu.dart @@ -1,11 +1,12 @@ -import 'package:flutter/material.dart'; - +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/selectable_svg_widget.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/image/custom_image_block_component/custom_image_block_component.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/image/image_placeholder.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_block_component.dart'; import 'package:appflowy_editor/appflowy_editor.dart' hide Log; import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; final customImageMenuItem = SelectionMenuItem( getName: () => AppFlowyEditorL10n.current.image, @@ -28,8 +29,9 @@ final customImageMenuItem = SelectionMenuItem( final multiImageMenuItem = SelectionMenuItem( getName: () => LocaleKeys.document_plugins_photoGallery_name.tr(), - icon: (_, isSelected, style) => SelectionMenuIconWidget( - icon: Icons.photo_library_outlined, + icon: (_, isSelected, style) => SelectableSvgWidget( + data: FlowySvgs.image_s, + size: const Size.square(16.0), isSelected: isSelected, style: style, ), diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/math_equation/math_equation_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/math_equation/math_equation_block_component.dart index c7d298ff09..8fe381ca8e 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/math_equation/math_equation_block_component.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/math_equation/math_equation_block_component.dart @@ -1,5 +1,7 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/mobile_block_action_buttons.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/base/selectable_svg_widget.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; @@ -37,7 +39,11 @@ Node mathEquationNode({ // defining the callout block menu item for selection SelectionMenuItem mathEquationItem = SelectionMenuItem.node( getName: LocaleKeys.document_plugins_mathEquation_name.tr, - iconData: Icons.text_fields_rounded, + iconBuilder: (editorState, onSelected, style) => SelectableSvgWidget( + data: FlowySvgs.icon_math_eq_s, + isSelected: onSelected, + style: style, + ), keywords: ['tex, latex, katex', 'math equation', 'formula'], nodeBuilder: (editorState, _) => mathEquationNode(), replace: (_, node) => node.delta?.isEmpty ?? false, diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/slash_menu_items.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/slash_menu_items.dart index 8216ea5ab3..1a53c63ca2 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/slash_menu_items.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/slash_menu_items.dart @@ -14,10 +14,10 @@ SelectionMenuItem dateMenuItem = SelectionMenuItem( ), keywords: ['insert date', 'date', 'time'], handler: (editorState, menuService, context) => - _insertDateReference(editorState), + insertDateReference(editorState), ); -Future _insertDateReference(EditorState editorState) async { +Future insertDateReference(EditorState editorState) async { final selection = editorState.selection; if (selection == null || !selection.isCollapsed) { return; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/auto_completion_node_widget.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/auto_completion_node_widget.dart index 45875b2aa9..1484d330b9 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/auto_completion_node_widget.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/auto_completion_node_widget.dart @@ -1,5 +1,7 @@ +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/build_context_extension.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/base/selectable_svg_widget.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/base/text_robot.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/error.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/discard_dialog.dart'; @@ -41,7 +43,11 @@ Node autoCompletionNode({ SelectionMenuItem autoGeneratorMenuItem = SelectionMenuItem.node( getName: LocaleKeys.document_plugins_autoGeneratorMenuItemName.tr, - iconData: Icons.generating_tokens, + iconBuilder: (editorState, onSelected, style) => SelectableSvgWidget( + data: FlowySvgs.menu_item_ai_writer_s, + isSelected: onSelected, + style: style, + ), keywords: ['ai', 'openai', 'writer', 'ai writer', 'autogenerator'], nodeBuilder: (editorState, _) { final node = autoCompletionNode(start: editorState.selection!); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/smart_edit_toolbar_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/smart_edit_toolbar_item.dart index 434857891e..f50dcd5fbd 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/smart_edit_toolbar_item.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/smart_edit_toolbar_item.dart @@ -1,3 +1,4 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/smart_edit_action.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; @@ -8,7 +9,7 @@ import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra_ui/style_widget/icon_button.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; const _kSmartEditToolbarItemId = 'appflowy.editor.smart_edit'; @@ -65,15 +66,21 @@ class _SmartEditActionListState extends State { onClosed: () => keepEditorFocusNotifier.decrease(), buildChild: (controller) { keepEditorFocusNotifier.increase(); - final child = FlowyIconButton( - hoverColor: Colors.transparent, - preferBelow: false, - icon: const Icon( - Icons.lightbulb_outline, - size: 15, + final child = FlowyButton( + text: FlowyText.regular( + LocaleKeys.document_plugins_smartEdit.tr(), + fontSize: 13.0, + figmaLineHeight: 16.0, color: Colors.white, ), - onPressed: () { + hoverColor: Colors.transparent, + useIntrinsicWidth: true, + leftIcon: const FlowySvg( + FlowySvgs.toolbar_item_ai_s, + size: Size.square(16.0), + color: Colors.white, + ), + onTap: () { if (isAIEnabled) { controller.show(); } else { diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/plugins.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/plugins.dart index ef877d61ab..acbd3a1627 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/plugins.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/plugins.dart @@ -5,6 +5,7 @@ export 'base/toolbar_extension.dart'; export 'bulleted_list/bulleted_list_icon.dart'; export 'callout/callout_block_component.dart'; export 'code_block/code_block_language_selector.dart'; +export 'code_block/code_block_menu_item.dart'; export 'context_menu/custom_context_menu.dart'; export 'copy_and_paste/custom_copy_command.dart'; export 'copy_and_paste/custom_cut_command.dart'; @@ -50,6 +51,7 @@ export 'openai/widgets/smart_edit_node_widget.dart'; export 'openai/widgets/smart_edit_toolbar_item.dart'; export 'outline/outline_block_component.dart'; export 'parsers/markdown_parsers.dart'; +export 'slash_menu/slash_menu_items.dart'; export 'table/table_menu.dart'; export 'table/table_option_action.dart'; export 'todo_list/todo_list_icon.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items.dart new file mode 100644 index 0000000000..6b1163c79d --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items.dart @@ -0,0 +1,459 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/application/prelude.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/base/insert_page_command.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/base/link_to_page_widget.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/base/selectable_svg_widget.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/image/image_placeholder.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/workspace/application/view/view_service.dart'; +import 'package:appflowy/workspace/presentation/settings/widgets/emoji_picker/emoji_menu_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:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/style_widget/text.dart'; +import 'package:flutter/material.dart'; + +// text menu item +final textSlashMenuItem = SelectionMenuItem( + getName: () => LocaleKeys.document_slashMenu_name_text.tr(), + nameBuilder: _slashMenuItemNameBuilder, + icon: (editorState, isSelected, style) => SelectableSvgWidget( + data: FlowySvgs.slash_menu_icon_text_s, + isSelected: isSelected, + style: style, + ), + keywords: ['text', 'paragraph'], + handler: (editorState, _, __) { + insertNodeAfterSelection(editorState, paragraphNode()); + }, +); + +// heading 1 - 3 menu items +final heading1SlashMenuItem = SelectionMenuItem( + getName: () => LocaleKeys.document_slashMenu_name_heading1.tr(), + nameBuilder: _slashMenuItemNameBuilder, + icon: (editorState, isSelected, style) => SelectableSvgWidget( + data: FlowySvgs.slash_menu_icon_h1_s, + isSelected: isSelected, + style: style, + ), + keywords: ['heading 1', 'h1', 'heading1'], + handler: (editorState, _, __) { + insertHeadingAfterSelection(editorState, 1); + }, +); + +final heading2SlashMenuItem = SelectionMenuItem( + getName: () => LocaleKeys.document_slashMenu_name_heading2.tr(), + nameBuilder: _slashMenuItemNameBuilder, + icon: (editorState, isSelected, style) => SelectableSvgWidget( + data: FlowySvgs.slash_menu_icon_h2_s, + isSelected: isSelected, + style: style, + ), + keywords: ['heading 2', 'h2', 'heading2'], + handler: (editorState, _, __) { + insertHeadingAfterSelection(editorState, 2); + }, +); + +final heading3SlashMenuItem = SelectionMenuItem( + getName: () => LocaleKeys.document_slashMenu_name_heading3.tr(), + nameBuilder: _slashMenuItemNameBuilder, + icon: (editorState, isSelected, style) => SelectableSvgWidget( + data: FlowySvgs.slash_menu_icon_h3_s, + isSelected: isSelected, + style: style, + ), + keywords: ['heading 3', 'h3', 'heading3'], + handler: (editorState, _, __) { + insertHeadingAfterSelection(editorState, 3); + }, +); + +// image menu item +final imageSlashMenuItem = SelectionMenuItem( + getName: () => LocaleKeys.document_slashMenu_name_image.tr(), + nameBuilder: _slashMenuItemNameBuilder, + icon: (editorState, isSelected, style) => SelectableSvgWidget( + data: FlowySvgs.slash_menu_icon_image_s, + isSelected: isSelected, + style: style, + ), + keywords: ['image', 'photo', 'picture', 'img'], + handler: (editorState, menuService, context) async { + // use the key to retrieve the state of the image block to show the popover automatically + final imagePlaceholderKey = GlobalKey(); + await editorState.insertEmptyImageBlock(imagePlaceholderKey); + + WidgetsBinding.instance.addPostFrameCallback((_) { + imagePlaceholderKey.currentState?.controller.show(); + }); + }, +); + +// bulleted list menu item +final bulletedListSlashMenuItem = SelectionMenuItem( + getName: () => LocaleKeys.document_slashMenu_name_bulletedList.tr(), + nameBuilder: _slashMenuItemNameBuilder, + icon: (editorState, isSelected, style) => SelectableSvgWidget( + data: FlowySvgs.slash_menu_icon_bulleted_list_s, + isSelected: isSelected, + style: style, + ), + keywords: ['bulleted list', 'list', 'unordered list'], + handler: (editorState, _, __) { + insertBulletedListAfterSelection(editorState); + }, +); + +// numbered list menu item +final numberedListSlashMenuItem = SelectionMenuItem( + getName: () => LocaleKeys.document_slashMenu_name_numberedList.tr(), + nameBuilder: _slashMenuItemNameBuilder, + icon: (editorState, isSelected, style) => SelectableSvgWidget( + data: FlowySvgs.slash_menu_icon_numbered_list_s, + isSelected: isSelected, + style: style, + ), + keywords: ['numbered list', 'list', 'ordered list'], + handler: (editorState, _, __) { + insertNumberedListAfterSelection(editorState); + }, +); + +// quote menu item +final quoteSlashMenuItem = SelectionMenuItem( + getName: () => LocaleKeys.document_slashMenu_name_quote.tr(), + nameBuilder: _slashMenuItemNameBuilder, + icon: (editorState, isSelected, style) => SelectableSvgWidget( + data: FlowySvgs.slash_menu_icon_quote_s, + isSelected: isSelected, + style: style, + ), + keywords: ['quote', 'refer'], + handler: (editorState, _, __) { + insertQuoteAfterSelection(editorState); + }, +); + +// grid & board & calendar menu item +SelectionMenuItem gridSlashMenuItem(DocumentBloc documentBloc) { + return SelectionMenuItem( + getName: () => LocaleKeys.document_slashMenu_name_grid.tr(), + nameBuilder: _slashMenuItemNameBuilder, + icon: (editorState, onSelected, style) => SelectableSvgWidget( + data: FlowySvgs.slash_menu_icon_grid_s, + isSelected: onSelected, + style: style, + ), + keywords: ['grid', 'database'], + handler: (editorState, menuService, context) async { + // create the view inside current page + final parentViewId = documentBloc.documentId; + final value = await ViewBackendService.createView( + parentViewId: parentViewId, + name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(), + layoutType: ViewLayoutPB.Grid, + ); + value.map((r) => editorState.insertInlinePage(parentViewId, r)); + }, + ); +} + +SelectionMenuItem kanbanSlashMenuItem(DocumentBloc documentBloc) { + return SelectionMenuItem( + getName: () => LocaleKeys.document_slashMenu_name_kanban.tr(), + nameBuilder: _slashMenuItemNameBuilder, + icon: (editorState, onSelected, style) => SelectableSvgWidget( + data: FlowySvgs.slash_menu_icon_kanban_s, + isSelected: onSelected, + style: style, + ), + keywords: ['board', 'kanban', 'database'], + handler: (editorState, menuService, context) async { + // create the view inside current page + final parentViewId = documentBloc.documentId; + final value = await ViewBackendService.createView( + parentViewId: parentViewId, + name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(), + layoutType: ViewLayoutPB.Board, + ); + value.map((r) => editorState.insertInlinePage(parentViewId, r)); + }, + ); +} + +SelectionMenuItem calendarSlashMenuItem(DocumentBloc documentBloc) { + return SelectionMenuItem( + getName: () => LocaleKeys.document_slashMenu_name_calendar.tr(), + nameBuilder: _slashMenuItemNameBuilder, + icon: (editorState, onSelected, style) => SelectableSvgWidget( + data: FlowySvgs.slash_menu_icon_calendar_s, + isSelected: onSelected, + style: style, + ), + keywords: ['calendar', 'database'], + handler: (editorState, menuService, context) async { + // create the view inside current page + final parentViewId = documentBloc.documentId; + final value = await ViewBackendService.createView( + parentViewId: parentViewId, + name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(), + layoutType: ViewLayoutPB.Calendar, + ); + value.map((r) => editorState.insertInlinePage(parentViewId, r)); + }, + ); +} + +// linked doc menu item +final referencedDocSlashMenuItem = SelectionMenuItem( + getName: () => LocaleKeys.document_slashMenu_name_linkedDoc.tr(), + nameBuilder: _slashMenuItemNameBuilder, + icon: (editorState, isSelected, style) => SelectableSvgWidget( + data: FlowySvgs.slash_menu_icon_doc_s, + isSelected: isSelected, + style: style, + ), + keywords: ['page', 'notes', 'referenced page', 'referenced document'], + handler: (editorState, menuService, context) => showLinkToPageMenu( + editorState, + menuService, + ViewLayoutPB.Document, + ), +); + +// linked grid & board & calendar menu item +SelectionMenuItem referencedGridSlashMenuItem = SelectionMenuItem( + getName: () => LocaleKeys.document_slashMenu_name_linkedGrid.tr(), + nameBuilder: _slashMenuItemNameBuilder, + icon: (editorState, onSelected, style) => SelectableSvgWidget( + data: FlowySvgs.slash_menu_icon_grid_s, + isSelected: onSelected, + style: style, + ), + keywords: ['referenced', 'grid', 'database', 'linked'], + handler: (editorState, menuService, context) => + showLinkToPageMenu(editorState, menuService, ViewLayoutPB.Grid), +); + +SelectionMenuItem referencedKanbanSlashMenuItem = SelectionMenuItem( + getName: () => LocaleKeys.document_slashMenu_name_linkedKanban.tr(), + nameBuilder: _slashMenuItemNameBuilder, + icon: (editorState, onSelected, style) => SelectableSvgWidget( + data: FlowySvgs.slash_menu_icon_kanban_s, + isSelected: onSelected, + style: style, + ), + keywords: ['referenced', 'board', 'kanban', 'linked'], + handler: (editorState, menuService, context) => + showLinkToPageMenu(editorState, menuService, ViewLayoutPB.Board), +); + +SelectionMenuItem referencedCalendarSlashMenuItem = SelectionMenuItem( + getName: () => LocaleKeys.document_slashMenu_name_linkedCalendar.tr(), + nameBuilder: _slashMenuItemNameBuilder, + icon: (editorState, onSelected, style) => SelectableSvgWidget( + data: FlowySvgs.slash_menu_icon_calendar_s, + isSelected: onSelected, + style: style, + ), + keywords: ['referenced', 'calendar', 'database', 'linked'], + handler: (editorState, menuService, context) => + showLinkToPageMenu(editorState, menuService, ViewLayoutPB.Calendar), +); + +// callout menu item +SelectionMenuItem calloutSlashMenuItem = SelectionMenuItem.node( + getName: LocaleKeys.document_plugins_callout.tr, + nameBuilder: _slashMenuItemNameBuilder, + iconBuilder: (editorState, isSelected, style) => SelectableSvgWidget( + data: FlowySvgs.slash_menu_icon_callout_s, + isSelected: isSelected, + style: style, + ), + keywords: [CalloutBlockKeys.type], + nodeBuilder: (editorState, context) => + calloutNode(defaultColor: Colors.transparent), + replace: (_, node) => node.delta?.isEmpty ?? false, + updateSelection: (_, path, __, ___) { + return Selection.single(path: path, startOffset: 0); + }, +); + +// outline menu item +SelectionMenuItem outlineSlashMenuItem = SelectionMenuItem.node( + getName: () => LocaleKeys.document_slashMenu_name_outline.tr(), + nameBuilder: _slashMenuItemNameBuilder, + iconBuilder: (editorState, isSelected, style) => SelectableSvgWidget( + data: FlowySvgs.slash_menu_icon_outline_s, + isSelected: isSelected, + style: style, + ), + keywords: ['outline', 'table of contents'], + nodeBuilder: (editorState, _) => outlineBlockNode(), + replace: (_, node) => node.delta?.isEmpty ?? false, +); + +// math equation +SelectionMenuItem mathEquationSlashMenuItem = SelectionMenuItem.node( + getName: () => LocaleKeys.document_slashMenu_name_mathEquation.tr(), + nameBuilder: _slashMenuItemNameBuilder, + iconBuilder: (editorState, isSelected, style) => SelectableSvgWidget( + data: FlowySvgs.slash_menu_icon_math_equation_s, + isSelected: isSelected, + style: style, + ), + keywords: ['tex', 'latex', 'katex', 'math equation', 'formula'], + nodeBuilder: (editorState, _) => mathEquationNode(), + replace: (_, node) => node.delta?.isEmpty ?? false, + updateSelection: (editorState, path, __, ___) { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + final mathEquationState = + editorState.getNodeAtPath(path)?.key.currentState; + if (mathEquationState != null && + mathEquationState is MathEquationBlockComponentWidgetState) { + mathEquationState.showEditingDialog(); + } + }); + return null; + }, +); + +// code block menu item +SelectionMenuItem codeBlockSlashMenuItem = SelectionMenuItem.node( + getName: () => LocaleKeys.document_slashMenu_name_code.tr(), + nameBuilder: _slashMenuItemNameBuilder, + iconBuilder: (editorState, isSelected, style) => SelectableSvgWidget( + data: FlowySvgs.slash_menu_icon_code_block_s, + isSelected: isSelected, + style: style, + ), + keywords: ['code', 'code block'], + nodeBuilder: (_, __) => codeBlockNode(), + replace: (_, node) => node.delta?.isEmpty ?? false, +); + +// toggle menu item +SelectionMenuItem toggleListSlashMenuItem = SelectionMenuItem.node( + getName: () => LocaleKeys.document_slashMenu_name_toggleList.tr(), + nameBuilder: _slashMenuItemNameBuilder, + iconBuilder: (editorState, isSelected, style) => SelectableSvgWidget( + data: FlowySvgs.slash_menu_icon_toggle_s, + isSelected: isSelected, + style: style, + ), + keywords: ['collapsed list', 'toggle list', 'list'], + nodeBuilder: (editorState, _) => toggleListBlockNode(), + replace: (_, node) => node.delta?.isEmpty ?? false, +); + +// emoji menu item +SelectionMenuItem emojiSlashMenuItem = SelectionMenuItem( + getName: () => LocaleKeys.document_slashMenu_name_emoji.tr(), + nameBuilder: _slashMenuItemNameBuilder, + icon: (editorState, isSelected, style) => SelectableSvgWidget( + data: FlowySvgs.slash_menu_icon_emoji_picker_s, + isSelected: isSelected, + style: style, + ), + keywords: ['emoji'], + handler: (editorState, menuService, context) { + final container = Overlay.of(context); + menuService.dismiss(); + showEmojiPickerMenu( + container, + editorState, + menuService.alignment, + menuService.offset, + ); + }, +); + +// auto generate menu item +SelectionMenuItem aiWriterSlashMenuItem = SelectionMenuItem.node( + getName: () => LocaleKeys.document_slashMenu_name_aiWriter.tr(), + nameBuilder: _slashMenuItemNameBuilder, + iconBuilder: (editorState, isSelected, style) => SelectableSvgWidget( + data: FlowySvgs.slash_menu_icon_ai_writer_s, + isSelected: isSelected, + style: style, + ), + keywords: ['ai', 'openai', 'writer', 'ai writer', 'autogenerator'], + nodeBuilder: (editorState, _) { + final node = autoCompletionNode(start: editorState.selection!); + return node; + }, + replace: (_, node) => false, +); + +// date or reminder menu item +SelectionMenuItem dateOrReminderSlashMenuItem = SelectionMenuItem( + getName: () => LocaleKeys.document_slashMenu_name_dateOrReminder.tr(), + nameBuilder: _slashMenuItemNameBuilder, + icon: (editorState, isSelected, style) => SelectableSvgWidget( + data: FlowySvgs.slash_menu_icon_date_or_reminder_s, + isSelected: isSelected, + style: style, + ), + keywords: ['insert date', 'date', 'time', 'reminder'], + handler: (editorState, menuService, context) => + insertDateReference(editorState), +); + +// photo gallery menu item +SelectionMenuItem photoGallerySlashMenuItem = SelectionMenuItem( + getName: () => LocaleKeys.document_slashMenu_name_photoGallery.tr(), + nameBuilder: _slashMenuItemNameBuilder, + icon: (editorState, isSelected, style) => SelectableSvgWidget( + data: FlowySvgs.slash_menu_icon_photo_gallery_s, + isSelected: isSelected, + style: style, + ), + keywords: [ + LocaleKeys.document_plugins_photoGallery_imageKeyword.tr(), + LocaleKeys.document_plugins_photoGallery_imageGalleryKeyword.tr(), + LocaleKeys.document_plugins_photoGallery_photoKeyword.tr(), + LocaleKeys.document_plugins_photoGallery_photoBrowserKeyword.tr(), + LocaleKeys.document_plugins_photoGallery_galleryKeyword.tr(), + ], + handler: (editorState, _, __) async { + final imagePlaceholderKey = GlobalKey(); + await editorState.insertEmptyMultiImageBlock(imagePlaceholderKey); + WidgetsBinding.instance.addPostFrameCallback( + (_) => imagePlaceholderKey.currentState?.controller.show(), + ); + }, +); + +// file menu item +SelectionMenuItem fileSlashMenuItem = SelectionMenuItem( + getName: () => LocaleKeys.document_slashMenu_name_file.tr(), + nameBuilder: _slashMenuItemNameBuilder, + icon: (editorState, isSelected, style) => SelectableSvgWidget( + data: FlowySvgs.slash_menu_icon_file_s, + isSelected: isSelected, + style: style, + ), + keywords: ['file upload', 'pdf', 'zip', 'archive', 'upload'], + handler: (editorState, _, __) async => editorState.insertEmptyFileBlock(), +); + +Widget _slashMenuItemNameBuilder( + String name, + SelectionMenuStyle style, + bool isSelected, +) { + return FlowyText( + name, + fontSize: 12.0, + figmaLineHeight: 15.0, + color: isSelected + ? style.selectionMenuItemSelectedTextColor + : style.selectionMenuItemTextColor, + ); +} diff --git a/frontend/appflowy_flutter/pubspec.lock b/frontend/appflowy_flutter/pubspec.lock index 10f14ba563..ab4cb45be0 100644 --- a/frontend/appflowy_flutter/pubspec.lock +++ b/frontend/appflowy_flutter/pubspec.lock @@ -53,8 +53,8 @@ packages: dependency: "direct main" description: path: "." - ref: "4536488faf458ab45e304c1715850d4d1ae517ee" - resolved-ref: "4536488faf458ab45e304c1715850d4d1ae517ee" + ref: "0317779" + resolved-ref: "031777941ce53fe1aaa9a23177f6045b142e6e73" url: "https://github.com/AppFlowy-IO/appflowy-editor.git" source: git version: "3.1.0" diff --git a/frontend/appflowy_flutter/pubspec.yaml b/frontend/appflowy_flutter/pubspec.yaml index 928766a845..3a6064c9d6 100644 --- a/frontend/appflowy_flutter/pubspec.yaml +++ b/frontend/appflowy_flutter/pubspec.yaml @@ -199,7 +199,7 @@ dependency_overrides: appflowy_editor: git: url: https://github.com/AppFlowy-IO/appflowy-editor.git - ref: "4536488faf458ab45e304c1715850d4d1ae517ee" + ref: "0317779" appflowy_editor_plugins: git: diff --git a/frontend/resources/flowy_icons/16x/icon_code_block.svg b/frontend/resources/flowy_icons/16x/icon_code_block.svg new file mode 100644 index 0000000000..1d36321b6a --- /dev/null +++ b/frontend/resources/flowy_icons/16x/icon_code_block.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/frontend/resources/flowy_icons/16x/icon_math_eq.svg b/frontend/resources/flowy_icons/16x/icon_math_eq.svg new file mode 100644 index 0000000000..8f4a6fc4f4 --- /dev/null +++ b/frontend/resources/flowy_icons/16x/icon_math_eq.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/frontend/resources/flowy_icons/16x/menu_item_ai_writer.svg b/frontend/resources/flowy_icons/16x/menu_item_ai_writer.svg new file mode 100644 index 0000000000..744f4b9b05 --- /dev/null +++ b/frontend/resources/flowy_icons/16x/menu_item_ai_writer.svg @@ -0,0 +1 @@ +Wand Sparkles Streamline Icon: https://streamlinehq.com \ No newline at end of file diff --git a/frontend/resources/flowy_icons/16x/slash_menu_icon_ai_writer.svg b/frontend/resources/flowy_icons/16x/slash_menu_icon_ai_writer.svg new file mode 100644 index 0000000000..7f72551486 --- /dev/null +++ b/frontend/resources/flowy_icons/16x/slash_menu_icon_ai_writer.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/frontend/resources/flowy_icons/16x/slash_menu_icon_bulleted_list.svg b/frontend/resources/flowy_icons/16x/slash_menu_icon_bulleted_list.svg new file mode 100644 index 0000000000..16af99d0c7 --- /dev/null +++ b/frontend/resources/flowy_icons/16x/slash_menu_icon_bulleted_list.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/frontend/resources/flowy_icons/16x/slash_menu_icon_calendar-1.svg b/frontend/resources/flowy_icons/16x/slash_menu_icon_calendar-1.svg new file mode 100644 index 0000000000..bdabe8f5da --- /dev/null +++ b/frontend/resources/flowy_icons/16x/slash_menu_icon_calendar-1.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/frontend/resources/flowy_icons/16x/slash_menu_icon_calendar.svg b/frontend/resources/flowy_icons/16x/slash_menu_icon_calendar.svg new file mode 100644 index 0000000000..d0bf3100bc --- /dev/null +++ b/frontend/resources/flowy_icons/16x/slash_menu_icon_calendar.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/frontend/resources/flowy_icons/16x/slash_menu_icon_callout.svg b/frontend/resources/flowy_icons/16x/slash_menu_icon_callout.svg new file mode 100644 index 0000000000..9539972a37 --- /dev/null +++ b/frontend/resources/flowy_icons/16x/slash_menu_icon_callout.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/frontend/resources/flowy_icons/16x/slash_menu_icon_checkbox.svg b/frontend/resources/flowy_icons/16x/slash_menu_icon_checkbox.svg new file mode 100644 index 0000000000..abcf733ccb --- /dev/null +++ b/frontend/resources/flowy_icons/16x/slash_menu_icon_checkbox.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/frontend/resources/flowy_icons/16x/slash_menu_icon_code block.svg b/frontend/resources/flowy_icons/16x/slash_menu_icon_code block.svg new file mode 100644 index 0000000000..1d36321b6a --- /dev/null +++ b/frontend/resources/flowy_icons/16x/slash_menu_icon_code block.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/frontend/resources/flowy_icons/16x/slash_menu_icon_date_or_reminder.svg b/frontend/resources/flowy_icons/16x/slash_menu_icon_date_or_reminder.svg new file mode 100644 index 0000000000..71d0aa18b9 --- /dev/null +++ b/frontend/resources/flowy_icons/16x/slash_menu_icon_date_or_reminder.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/frontend/resources/flowy_icons/16x/slash_menu_icon_divider.svg b/frontend/resources/flowy_icons/16x/slash_menu_icon_divider.svg new file mode 100644 index 0000000000..fd53b92748 --- /dev/null +++ b/frontend/resources/flowy_icons/16x/slash_menu_icon_divider.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/flowy_icons/16x/slash_menu_icon_doc.svg b/frontend/resources/flowy_icons/16x/slash_menu_icon_doc.svg new file mode 100644 index 0000000000..71b9659027 --- /dev/null +++ b/frontend/resources/flowy_icons/16x/slash_menu_icon_doc.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/frontend/resources/flowy_icons/16x/slash_menu_icon_emoji_picker.svg b/frontend/resources/flowy_icons/16x/slash_menu_icon_emoji_picker.svg new file mode 100644 index 0000000000..f525e8cbfc --- /dev/null +++ b/frontend/resources/flowy_icons/16x/slash_menu_icon_emoji_picker.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/frontend/resources/flowy_icons/16x/slash_menu_icon_file.svg b/frontend/resources/flowy_icons/16x/slash_menu_icon_file.svg new file mode 100644 index 0000000000..32f4057a05 --- /dev/null +++ b/frontend/resources/flowy_icons/16x/slash_menu_icon_file.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/frontend/resources/flowy_icons/16x/slash_menu_icon_grid.svg b/frontend/resources/flowy_icons/16x/slash_menu_icon_grid.svg new file mode 100644 index 0000000000..9db2280443 --- /dev/null +++ b/frontend/resources/flowy_icons/16x/slash_menu_icon_grid.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/frontend/resources/flowy_icons/16x/slash_menu_icon_h1.svg b/frontend/resources/flowy_icons/16x/slash_menu_icon_h1.svg new file mode 100644 index 0000000000..a94b3fa12d --- /dev/null +++ b/frontend/resources/flowy_icons/16x/slash_menu_icon_h1.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/frontend/resources/flowy_icons/16x/slash_menu_icon_h2.svg b/frontend/resources/flowy_icons/16x/slash_menu_icon_h2.svg new file mode 100644 index 0000000000..a0580717b8 --- /dev/null +++ b/frontend/resources/flowy_icons/16x/slash_menu_icon_h2.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/frontend/resources/flowy_icons/16x/slash_menu_icon_h3.svg b/frontend/resources/flowy_icons/16x/slash_menu_icon_h3.svg new file mode 100644 index 0000000000..485caefb15 --- /dev/null +++ b/frontend/resources/flowy_icons/16x/slash_menu_icon_h3.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/frontend/resources/flowy_icons/16x/slash_menu_icon_image.svg b/frontend/resources/flowy_icons/16x/slash_menu_icon_image.svg new file mode 100644 index 0000000000..28e628ec8d --- /dev/null +++ b/frontend/resources/flowy_icons/16x/slash_menu_icon_image.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/frontend/resources/flowy_icons/16x/slash_menu_icon_kanban.svg b/frontend/resources/flowy_icons/16x/slash_menu_icon_kanban.svg new file mode 100644 index 0000000000..0ee6e28ae7 --- /dev/null +++ b/frontend/resources/flowy_icons/16x/slash_menu_icon_kanban.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/frontend/resources/flowy_icons/16x/slash_menu_icon_math_equation.svg b/frontend/resources/flowy_icons/16x/slash_menu_icon_math_equation.svg new file mode 100644 index 0000000000..602b211850 --- /dev/null +++ b/frontend/resources/flowy_icons/16x/slash_menu_icon_math_equation.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/resources/flowy_icons/16x/slash_menu_icon_numbered_list.svg b/frontend/resources/flowy_icons/16x/slash_menu_icon_numbered_list.svg new file mode 100644 index 0000000000..ffc957d59f --- /dev/null +++ b/frontend/resources/flowy_icons/16x/slash_menu_icon_numbered_list.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/frontend/resources/flowy_icons/16x/slash_menu_icon_outline.svg b/frontend/resources/flowy_icons/16x/slash_menu_icon_outline.svg new file mode 100644 index 0000000000..e6645d2168 --- /dev/null +++ b/frontend/resources/flowy_icons/16x/slash_menu_icon_outline.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/flowy_icons/16x/slash_menu_icon_photo_gallery.svg b/frontend/resources/flowy_icons/16x/slash_menu_icon_photo_gallery.svg new file mode 100644 index 0000000000..581c8c009e --- /dev/null +++ b/frontend/resources/flowy_icons/16x/slash_menu_icon_photo_gallery.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/frontend/resources/flowy_icons/16x/slash_menu_icon_quote.svg b/frontend/resources/flowy_icons/16x/slash_menu_icon_quote.svg new file mode 100644 index 0000000000..9028b658cb --- /dev/null +++ b/frontend/resources/flowy_icons/16x/slash_menu_icon_quote.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/frontend/resources/flowy_icons/16x/slash_menu_icon_simple_table.svg b/frontend/resources/flowy_icons/16x/slash_menu_icon_simple_table.svg new file mode 100644 index 0000000000..1666d7aad7 --- /dev/null +++ b/frontend/resources/flowy_icons/16x/slash_menu_icon_simple_table.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/frontend/resources/flowy_icons/16x/slash_menu_icon_text.svg b/frontend/resources/flowy_icons/16x/slash_menu_icon_text.svg new file mode 100644 index 0000000000..134e755dfd --- /dev/null +++ b/frontend/resources/flowy_icons/16x/slash_menu_icon_text.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/resources/flowy_icons/16x/slash_menu_icon_toggle.svg b/frontend/resources/flowy_icons/16x/slash_menu_icon_toggle.svg new file mode 100644 index 0000000000..ee995b4d8e --- /dev/null +++ b/frontend/resources/flowy_icons/16x/slash_menu_icon_toggle.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/flowy_icons/16x/toolbar_item_ai.svg b/frontend/resources/flowy_icons/16x/toolbar_item_ai.svg new file mode 100644 index 0000000000..e3828bddbd --- /dev/null +++ b/frontend/resources/flowy_icons/16x/toolbar_item_ai.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index a38096f9c8..9f3f38048b 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -1464,12 +1464,44 @@ }, "document": { "selectADocumentToLinkTo": "Select a Document to link to" + }, + "name": { + "text": "Text", + "heading1": "Heading 1", + "heading2": "Heading 2", + "heading3": "Heading 3", + "image": "Image", + "bulletedList": "Bulleted List", + "numberedList": "Numbered List", + "checkbox": "Checkbox", + "doc": "Doc", + "linkedDoc": "Linked Doc", + "grid": "Grid", + "linkedGrid": "Linked Grid", + "kanban": "Kanban", + "linkedKanban": "Linked Kanban", + "calendar": "Calendar", + "linkedCalendar": "Linked Calendar", + "quote": "Quote", + "divider": "Divider", + "table": "Table", + "callout": "Callout", + "outline": "Outline", + "mathEquation": "Math Equation", + "code": "Code", + "toggleList": "Toggle list", + "emoji": "Emoji", + "aiWriter": "AI Writer", + "dateOrReminder": "Date or Reminder", + "photoGallery": "Photo Gallery", + "file": "File" } }, "selectionMenu": { "outline": "Outline", "codeBlock": "Code Block" }, + "plugins": { "referencedBoard": "Referenced Board", "referencedGrid": "Referenced Grid", @@ -1482,7 +1514,7 @@ "autoGeneratorHintText": "Ask AI ...", "autoGeneratorCantGetOpenAIKey": "Can't get AI key", "autoGeneratorRewrite": "Rewrite", - "smartEdit": "AI Assistants", + "smartEdit": "Ask AI", "aI": "AI", "smartEditFixSpelling": "Fix spelling & grammar", "warning": "⚠️ AI responses can be inaccurate or misleading.",