diff --git a/example/lib/pages/mobile_editor.dart b/example/lib/pages/mobile_editor.dart index e6d7dbccf..ab13c0a2e 100644 --- a/example/lib/pages/mobile_editor.dart +++ b/example/lib/pages/mobile_editor.dart @@ -142,7 +142,7 @@ class _MobileEditorState extends State { fontWeight: FontWeight.w600, ), ); - map[ParagraphBlockKeys.type] = TextBlockComponentBuilder( + map[ParagraphBlockKeys.type] = ParagraphBlockComponentBuilder( configuration: BlockComponentConfiguration( placeholderText: (node) => 'Type something...', ), diff --git a/lib/src/editor/block_component/base_component/selection/block_selection_container.dart b/lib/src/editor/block_component/base_component/selection/block_selection_container.dart index c71cdb555..9ceb3882f 100644 --- a/lib/src/editor/block_component/base_component/selection/block_selection_container.dart +++ b/lib/src/editor/block_component/base_component/selection/block_selection_container.dart @@ -50,6 +50,7 @@ class BlockSelectionContainer extends StatelessWidget { ? AlignmentDirectional.topStart : AlignmentDirectional.topEnd, children: [ + // block selection or selection area BlockSelectionArea( node: node, delegate: delegate, @@ -57,9 +58,24 @@ class BlockSelectionContainer extends StatelessWidget { cursorColor: cursorColor, selectionColor: selectionColor, blockColor: blockColor, - supportTypes: supportTypes, + supportTypes: supportTypes + .where( + (element) => element != BlockSelectionType.cursor, + ) + .toList(), ), child, + // cursor + if (supportTypes.contains(BlockSelectionType.cursor)) + BlockSelectionArea( + node: node, + delegate: delegate, + listenable: listenable, + cursorColor: cursorColor, + selectionColor: selectionColor, + blockColor: blockColor, + supportTypes: const [BlockSelectionType.cursor], + ), ], ); } diff --git a/lib/src/editor/block_component/block_component.dart b/lib/src/editor/block_component/block_component.dart index 4a881f24b..5f2f92b10 100644 --- a/lib/src/editor/block_component/block_component.dart +++ b/lib/src/editor/block_component/block_component.dart @@ -34,6 +34,7 @@ export 'image_block_component/resizable_image.dart'; // numbered list export 'numbered_list_block_component/numbered_list_block_component.dart'; export 'numbered_list_block_component/numbered_list_character_shortcut.dart'; +export 'paragraph_block_component/paragraph_block_component.dart'; // quote export 'quote_block_component/quote_block_component.dart'; export 'quote_block_component/quote_character_shortcut.dart'; @@ -47,7 +48,6 @@ export 'table_block_component/table_action.dart'; export 'table_block_component/table_block_component.dart'; export 'table_block_component/table_cell_block_component.dart'; export 'table_block_component/table_commands.dart'; -export 'text_block_component/text_block_component.dart'; // to-do list export 'todo_list_block_component/todo_list_block_component.dart'; export 'todo_list_block_component/todo_list_character_shortcut.dart'; diff --git a/lib/src/editor/block_component/bulleted_list_block_component/bulleted_list_block_component.dart b/lib/src/editor/block_component/bulleted_list_block_component/bulleted_list_block_component.dart index a1c942eaf..3806a32b0 100644 --- a/lib/src/editor/block_component/bulleted_list_block_component/bulleted_list_block_component.dart +++ b/lib/src/editor/block_component/bulleted_list_block_component/bulleted_list_block_component.dart @@ -116,7 +116,6 @@ class _BulletedListBlockComponentWidgetState ); Widget child = Container( - color: withBackgroundColor ? backgroundColor : null, width: double.infinity, alignment: alignment, child: Row( @@ -155,10 +154,13 @@ class _BulletedListBlockComponentWidgetState ), ); - child = Padding( - key: blockComponentKey, - padding: padding, - child: child, + child = Container( + color: withBackgroundColor ? backgroundColor : null, + child: Padding( + key: blockComponentKey, + padding: padding, + child: child, + ), ); child = BlockSelectionContainer( @@ -215,17 +217,14 @@ class _BulletedListIcon extends StatelessWidget { @override Widget build(BuildContext context) { - return SizedBox( - width: 22, - height: 22, - child: Padding( - padding: const EdgeInsets.only(right: 5.0), - child: Center( - child: Text( - icon, - style: textStyle, - textScaleFactor: 0.5, - ), + return Container( + constraints: const BoxConstraints(minWidth: 26, minHeight: 22), + padding: const EdgeInsets.only(right: 4.0), + child: Center( + child: Text( + icon, + style: textStyle, + textScaleFactor: 0.5, ), ), ); diff --git a/lib/src/editor/block_component/heading_block_component/heading_block_component.dart b/lib/src/editor/block_component/heading_block_component/heading_block_component.dart index 5d8f65cd4..f4c3b802b 100644 --- a/lib/src/editor/block_component/heading_block_component/heading_block_component.dart +++ b/lib/src/editor/block_component/heading_block_component/heading_block_component.dart @@ -126,7 +126,6 @@ class _HeadingBlockComponentWidgetState ); Widget child = Container( - color: backgroundColor, width: double.infinity, alignment: alignment, // Related issue: https://github.com/AppFlowy-IO/AppFlowy/issues/3175 @@ -170,10 +169,13 @@ class _HeadingBlockComponentWidgetState ), ); - child = Padding( - key: blockComponentKey, - padding: padding, - child: child, + child = Container( + color: backgroundColor, + child: Padding( + key: blockComponentKey, + padding: padding, + child: child, + ), ); child = BlockSelectionContainer( diff --git a/lib/src/editor/block_component/image_block_component/image_upload_widget.dart b/lib/src/editor/block_component/image_block_component/image_upload_widget.dart index 3895c2b97..e5aa36ae6 100644 --- a/lib/src/editor/block_component/image_block_component/image_upload_widget.dart +++ b/lib/src/editor/block_component/image_block_component/image_upload_widget.dart @@ -38,16 +38,16 @@ void showImageMenu( } menuService.dismiss(); imageMenuEntry.remove(); - keepEditorFocusNotifier.value -= 1; + keepEditorFocusNotifier.decrease(); } - keepEditorFocusNotifier.value += 1; + keepEditorFocusNotifier.increase(); imageMenuEntry = FullScreenOverlayEntry( left: left, right: right, top: top, bottom: bottom, - dismissCallback: () => keepEditorFocusNotifier.value -= 1, + dismissCallback: () => keepEditorFocusNotifier.decrease(), builder: (context) => UploadImageMenu( backgroundColor: menuService.style.selectionMenuBackgroundColor, headerColor: menuService.style.selectionMenuItemTextColor, diff --git a/lib/src/editor/block_component/numbered_list_block_component/numbered_list_block_component.dart b/lib/src/editor/block_component/numbered_list_block_component/numbered_list_block_component.dart index 1abb6bb21..e2ae9f593 100644 --- a/lib/src/editor/block_component/numbered_list_block_component/numbered_list_block_component.dart +++ b/lib/src/editor/block_component/numbered_list_block_component/numbered_list_block_component.dart @@ -121,7 +121,6 @@ class _NumberedListBlockComponentWidgetState ); Widget child = Container( - color: withBackgroundColor ? backgroundColor : null, width: double.infinity, alignment: alignment, child: Row( @@ -161,10 +160,13 @@ class _NumberedListBlockComponentWidgetState ), ); - child = Padding( - key: blockComponentKey, - padding: padding, - child: child, + child = Container( + color: withBackgroundColor ? backgroundColor : null, + child: Padding( + key: blockComponentKey, + padding: padding, + child: child, + ), ); child = BlockSelectionContainer( @@ -206,15 +208,18 @@ class _NumberedListIcon extends StatelessWidget { final editorState = context.read(); final text = editorState.editorStyle.textStyleConfiguration.text; final level = _NumberedListIconBuilder(node: node).level; - return Padding( - padding: const EdgeInsets.only(right: 5.0), - child: Text.rich( - textHeightBehavior: const TextHeightBehavior( - applyHeightToFirstAscent: false, - applyHeightToLastDescent: false, + return Container( + constraints: const BoxConstraints(minWidth: 26, minHeight: 22), + padding: const EdgeInsets.only(right: 4.0), + child: Center( + child: Text.rich( + textHeightBehavior: const TextHeightBehavior( + applyHeightToFirstAscent: false, + applyHeightToLastDescent: false, + ), + TextSpan(text: '$level.', style: text.combine(textStyle)), + textDirection: direction, ), - TextSpan(text: '$level.', style: text.combine(textStyle)), - textDirection: direction, ), ); } diff --git a/lib/src/editor/block_component/text_block_component/text_block_component.dart b/lib/src/editor/block_component/paragraph_block_component/paragraph_block_component.dart similarity index 88% rename from lib/src/editor/block_component/text_block_component/text_block_component.dart rename to lib/src/editor/block_component/paragraph_block_component/paragraph_block_component.dart index 1851b9ba6..860028ff1 100644 --- a/lib/src/editor/block_component/text_block_component/text_block_component.dart +++ b/lib/src/editor/block_component/paragraph_block_component/paragraph_block_component.dart @@ -33,15 +33,15 @@ Node paragraphNode({ ); } -class TextBlockComponentBuilder extends BlockComponentBuilder { - TextBlockComponentBuilder({ +class ParagraphBlockComponentBuilder extends BlockComponentBuilder { + ParagraphBlockComponentBuilder({ super.configuration, }); @override BlockComponentWidget build(BlockComponentContext blockComponentContext) { final node = blockComponentContext.node; - return TextBlockComponentWidget( + return ParagraphBlockComponentWidget( node: node, key: node.key, configuration: configuration, @@ -59,8 +59,8 @@ class TextBlockComponentBuilder extends BlockComponentBuilder { } } -class TextBlockComponentWidget extends BlockComponentStatefulWidget { - const TextBlockComponentWidget({ +class ParagraphBlockComponentWidget extends BlockComponentStatefulWidget { + const ParagraphBlockComponentWidget({ super.key, required super.node, super.showActions, @@ -69,11 +69,12 @@ class TextBlockComponentWidget extends BlockComponentStatefulWidget { }); @override - State createState() => - _TextBlockComponentWidgetState(); + State createState() => + _ParagraphBlockComponentWidgetState(); } -class _TextBlockComponentWidgetState extends State +class _ParagraphBlockComponentWidgetState + extends State with SelectableMixin, DefaultSelectableMixin, @@ -133,7 +134,6 @@ class _TextBlockComponentWidgetState extends State ); Widget child = Container( - color: withBackgroundColor ? backgroundColor : null, width: double.infinity, alignment: alignment, child: Column( @@ -161,10 +161,13 @@ class _TextBlockComponentWidgetState extends State ), ); - child = Padding( - key: blockComponentKey, - padding: padding, - child: child, + child = Container( + color: withBackgroundColor ? backgroundColor : null, + child: Padding( + key: blockComponentKey, + padding: padding, + child: child, + ), ); child = BlockSelectionContainer( diff --git a/lib/src/editor/block_component/quote_block_component/quote_block_component.dart b/lib/src/editor/block_component/quote_block_component/quote_block_component.dart index e7f4ef373..40a3f9b43 100644 --- a/lib/src/editor/block_component/quote_block_component/quote_block_component.dart +++ b/lib/src/editor/block_component/quote_block_component/quote_block_component.dart @@ -112,7 +112,6 @@ class _QuoteBlockComponentWidgetState extends State ); Widget child = Container( - color: backgroundColor, width: double.infinity, alignment: alignment, child: IntrinsicHeight( @@ -150,10 +149,13 @@ class _QuoteBlockComponentWidgetState extends State ), ); - child = Padding( - key: blockComponentKey, - padding: padding, - child: child, + child = Container( + color: backgroundColor, + child: Padding( + key: blockComponentKey, + padding: padding, + child: child, + ), ); child = BlockSelectionContainer( @@ -184,11 +186,17 @@ class _QuoteIcon extends StatelessWidget { @override Widget build(BuildContext context) { - return const EditorSvg( - width: 20, - height: 20, - padding: EdgeInsets.only(right: 5.0), - name: 'quote', + return Container( + constraints: const BoxConstraints(minWidth: 26, minHeight: 22), + padding: const EdgeInsets.only(right: 4.0), + child: const Center( + child: EditorSvg( + width: 20, + height: 20, + padding: EdgeInsets.only(right: 4.0), + name: 'quote', + ), + ), ); } } diff --git a/lib/src/editor/block_component/standard_block_components.dart b/lib/src/editor/block_component/standard_block_components.dart index 0fb339661..e2a5529bb 100644 --- a/lib/src/editor/block_component/standard_block_components.dart +++ b/lib/src/editor/block_component/standard_block_components.dart @@ -5,7 +5,7 @@ const standardBlockComponentConfiguration = BlockComponentConfiguration(); final Map standardBlockComponentBuilderMap = { PageBlockKeys.type: PageBlockComponentBuilder(), - ParagraphBlockKeys.type: TextBlockComponentBuilder( + ParagraphBlockKeys.type: ParagraphBlockComponentBuilder( configuration: standardBlockComponentConfiguration.copyWith( placeholderText: (_) => PlatformExtension.isDesktopOrWeb ? AppFlowyEditorLocalizations.current.slashPlaceHolder diff --git a/lib/src/editor/block_component/todo_list_block_component/todo_list_block_component.dart b/lib/src/editor/block_component/todo_list_block_component/todo_list_block_component.dart index 0e89a073c..bed30e473 100644 --- a/lib/src/editor/block_component/todo_list_block_component/todo_list_block_component.dart +++ b/lib/src/editor/block_component/todo_list_block_component/todo_list_block_component.dart @@ -132,7 +132,6 @@ class _TodoListBlockComponentWidgetState ); Widget child = Container( - color: withBackgroundColor ? backgroundColor : null, width: double.infinity, alignment: alignment, child: Row( @@ -173,10 +172,13 @@ class _TodoListBlockComponentWidgetState ), ); - child = Padding( - key: blockComponentKey, - padding: padding, - child: child, + child = Container( + color: withBackgroundColor ? backgroundColor : null, + child: Padding( + key: blockComponentKey, + padding: padding, + child: child, + ), ); child = BlockSelectionContainer( @@ -236,11 +238,14 @@ class _TodoListIcon extends StatelessWidget { child: GestureDetector( behavior: HitTestBehavior.opaque, onTap: onTap, - child: EditorSvg( - width: 22, - height: 22, - padding: const EdgeInsets.only(right: 5.0), - name: checked ? 'check' : 'uncheck', + child: Container( + constraints: const BoxConstraints(minWidth: 26, minHeight: 22), + padding: const EdgeInsets.only(right: 4.0), + child: EditorSvg( + width: 22, + height: 22, + name: checked ? 'check' : 'uncheck', + ), ), ), ); diff --git a/lib/src/editor/editor_component/service/editor.dart b/lib/src/editor/editor_component/service/editor.dart index c7d7b9745..61fbc200e 100644 --- a/lib/src/editor/editor_component/service/editor.dart +++ b/lib/src/editor/editor_component/service/editor.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor/src/flutter/overlay.dart'; import 'package:appflowy_editor/src/service/context_menu/built_in_context_menu_item.dart'; @@ -11,7 +13,7 @@ import 'package:provider/provider.dart'; // decrease the value when the popover is closed // only grab the focus when the value is 0 // the operation must be paired -ValueNotifier keepEditorFocusNotifier = ValueNotifier(0); +KeepEditorFocusNotifier keepEditorFocusNotifier = KeepEditorFocusNotifier(); class AppFlowyEditor extends StatefulWidget { AppFlowyEditor({ @@ -267,3 +269,26 @@ class _AppFlowyEditorState extends State { builders: {...widget.blockComponentBuilders}, ); } + +class KeepEditorFocusNotifier extends ValueNotifier { + KeepEditorFocusNotifier() : super(0); + + bool get shouldKeepFocus => value > 0; + + @override + set value(int v) { + super.value = max(0, v); + } + + void increase() { + value++; + } + + void decrease() { + value--; + } + + void reset() { + value = 0; + } +} diff --git a/lib/src/editor/editor_component/service/ime/non_delta_input_service.dart b/lib/src/editor/editor_component/service/ime/non_delta_input_service.dart index 7cdadbbe6..997415526 100644 --- a/lib/src/editor/editor_component/service/ime/non_delta_input_service.dart +++ b/lib/src/editor/editor_component/service/ime/non_delta_input_service.dart @@ -119,7 +119,7 @@ class NonDeltaTextInputService extends TextInputService with TextInputClient { @override void close() { - keepEditorFocusNotifier.value = 0; + keepEditorFocusNotifier.reset(); currentTextEditingValue = null; composingTextRange = null; _textInputConnection?.close(); diff --git a/lib/src/editor/editor_component/service/keyboard_service_widget.dart b/lib/src/editor/editor_component/service/keyboard_service_widget.dart index 1fd8e4190..e7e180ebb 100644 --- a/lib/src/editor/editor_component/service/keyboard_service_widget.dart +++ b/lib/src/editor/editor_component/service/keyboard_service_widget.dart @@ -265,7 +265,7 @@ class KeyboardServiceWidgetState extends State // clear the selection when the focus is lost. if (!focusNode.hasFocus) { if (PlatformExtension.isDesktopOrWeb) { - if (keepEditorFocusNotifier.value > 0) { + if (keepEditorFocusNotifier.shouldKeepFocus) { return; } } @@ -283,7 +283,7 @@ class KeyboardServiceWidgetState extends State 'keyboard service - on keep editor focus changed: ${keepEditorFocusNotifier.value}}', ); - if (keepEditorFocusNotifier.value == 0) { + if (!keepEditorFocusNotifier.shouldKeepFocus) { focusNode.requestFocus(); } } @@ -302,7 +302,10 @@ class KeyboardServiceWidgetState extends State if (renderBox != null && selectable != null) { final size = renderBox.size; final transform = renderBox.getTransformTo(null); - final rect = selectable.getCursorRectInPosition(selection.end); + final rect = selectable.getCursorRectInPosition( + selection.end, + shiftWithBaseOffset: true, + ); if (rect != null) { textInputService.updateCaretPosition(size, transform, rect); } diff --git a/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/slash_command.dart b/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/slash_command.dart index 24180aad2..21827a293 100644 --- a/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/slash_command.dart +++ b/lib/src/editor/editor_component/service/shortcuts/character_shortcut_events/slash_command.dart @@ -81,10 +81,10 @@ Future _showSlashMenu( if (shouldInsertSlash) { if (kIsWeb) { // Have no idea why the focus will lose after inserting on web. - keepEditorFocusNotifier.value += 1; + keepEditorFocusNotifier.increase(); await editorState.insertTextAtPosition('/', position: selection.start); WidgetsBinding.instance.addPostFrameCallback( - (timeStamp) => keepEditorFocusNotifier.value -= 1, + (timeStamp) => keepEditorFocusNotifier.decrease(), ); } else { await editorState.insertTextAtPosition('/', position: selection.start); diff --git a/lib/src/editor/selection_menu/selection_menu_widget.dart b/lib/src/editor/selection_menu/selection_menu_widget.dart index 8818c6457..a9fdaaee6 100644 --- a/lib/src/editor/selection_menu/selection_menu_widget.dart +++ b/lib/src/editor/selection_menu/selection_menu_widget.dart @@ -268,7 +268,7 @@ class _SelectionMenuWidgetState extends State { _showingItems = widget.items; - keepEditorFocusNotifier.value += 1; + keepEditorFocusNotifier.increase(); WidgetsBinding.instance.addPostFrameCallback((_) { _focusNode.requestFocus(); }); @@ -277,7 +277,7 @@ class _SelectionMenuWidgetState extends State { @override void dispose() { _focusNode.dispose(); - keepEditorFocusNotifier.value -= 1; + keepEditorFocusNotifier.decrease(); super.dispose(); } diff --git a/lib/src/editor/toolbar/desktop/items/color/color_menu.dart b/lib/src/editor/toolbar/desktop/items/color/color_menu.dart index c60839cd6..585b0e238 100644 --- a/lib/src/editor/toolbar/desktop/items/color/color_menu.dart +++ b/lib/src/editor/toolbar/desktop/items/color/color_menu.dart @@ -37,7 +37,7 @@ void showColorMenu( overlay = null; } - keepEditorFocusNotifier.value += 1; + keepEditorFocusNotifier.increase(); overlay = FullScreenOverlayEntry( top: top, bottom: bottom, @@ -67,7 +67,7 @@ void showColorMenu( withUpdateSelection: true, ); dismissOverlay(); - keepEditorFocusNotifier.value -= 1; + keepEditorFocusNotifier.decrease(); }, resetText: isTextColor ? AppFlowyEditorLocalizations.current.resetToDefaultColor diff --git a/lib/src/editor/toolbar/desktop/items/link/link_toolbar_item.dart b/lib/src/editor/toolbar/desktop/items/link/link_toolbar_item.dart index 2053d1bac..89edf457f 100644 --- a/lib/src/editor/toolbar/desktop/items/link/link_toolbar_item.dart +++ b/lib/src/editor/toolbar/desktop/items/link/link_toolbar_item.dart @@ -63,18 +63,18 @@ void showLinkMenu( OverlayEntry? overlay; void dismissOverlay() { - keepEditorFocusNotifier.value -= 1; + keepEditorFocusNotifier.decrease(); overlay?.remove(); overlay = null; } - keepEditorFocusNotifier.value += 1; + keepEditorFocusNotifier.increase(); overlay = FullScreenOverlayEntry( top: top, bottom: bottom, left: left, right: right, - dismissCallback: () => keepEditorFocusNotifier.value -= 1, + dismissCallback: () => keepEditorFocusNotifier.decrease(), builder: (context) { return LinkMenu( linkText: linkText, diff --git a/test/customer/custom_action_builder_test.dart b/test/customer/custom_action_builder_test.dart index 68c3b3ce7..89105d385 100644 --- a/test/customer/custom_action_builder_test.dart +++ b/test/customer/custom_action_builder_test.dart @@ -21,7 +21,7 @@ void main() async { addTearDown(gesture.removePointer); await tester.pump(); await gesture.moveTo( - tester.getCenter(find.byType(TextBlockComponentWidget)), + tester.getCenter(find.byType(ParagraphBlockComponentWidget)), ); await tester.pumpAndSettle(const Duration(milliseconds: 500)); @@ -60,7 +60,7 @@ class CustomActionBuilder extends StatelessWidget { final editorState = EditorState(document: document); - final paragraphBuilder = TextBlockComponentBuilder( + final paragraphBuilder = ParagraphBlockComponentBuilder( configuration: standardBlockComponentConfiguration, ); paragraphBuilder.showActions = (_) => true; diff --git a/test/customer/text_field_and_editor_test.dart b/test/customer/text_field_and_editor_test.dart index ccd3cc4ac..943c1d103 100644 --- a/test/customer/text_field_and_editor_test.dart +++ b/test/customer/text_field_and_editor_test.dart @@ -50,7 +50,7 @@ void main() async { expect(editorState.selection, null); await tester.tapAt( - tester.getTopLeft(find.byType(TextBlockComponentWidget)), + tester.getTopLeft(find.byType(ParagraphBlockComponentWidget)), ); await tester.pumpAndSettle(); expect(widget.focusNode.hasFocus, false);