diff --git a/lib/src/editor/block_component/base_component/selection/block_selection_area.dart b/lib/src/editor/block_component/base_component/selection/block_selection_area.dart index da7858c9d..910d73247 100644 --- a/lib/src/editor/block_component/base_component/selection/block_selection_area.dart +++ b/lib/src/editor/block_component/base_component/selection/block_selection_area.dart @@ -78,6 +78,7 @@ class _BlockSelectionAreaState extends State { @override Widget build(BuildContext context) { return ValueListenableBuilder( + key: ValueKey(widget.node.id + widget.supportTypes.toString()), valueListenable: widget.listenable, builder: ((context, value, child) { final sizedBox = child ?? const SizedBox.shrink(); 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 8c620af15..b23624ac2 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 @@ -147,6 +147,8 @@ class _BulletedListBlockComponentWidgetState placeholderTextStyle, ), textDirection: textDirection, + cursorColor: editorState.editorStyle.cursorColor, + selectionColor: editorState.editorStyle.selectionColor, ), ), ], @@ -159,6 +161,17 @@ class _BulletedListBlockComponentWidgetState child: child, ); + child = BlockSelectionContainer( + node: node, + delegate: this, + listenable: editorState.selectionNotifier, + blockColor: editorState.editorStyle.selectionColor, + supportTypes: const [ + BlockSelectionType.block, + ], + child: child, + ); + if (widget.showActions && widget.actionBuilder != null) { child = BlockComponentActionWrapper( node: node, diff --git a/lib/src/editor/block_component/divider_block_component/divider_block_component.dart b/lib/src/editor/block_component/divider_block_component/divider_block_component.dart index 71a04604b..6ecf98fa7 100644 --- a/lib/src/editor/block_component/divider_block_component/divider_block_component.dart +++ b/lib/src/editor/block_component/divider_block_component/divider_block_component.dart @@ -97,6 +97,19 @@ class _DividerBlockComponentWidgetState child: child, ); + final editorState = context.read(); + + child = BlockSelectionContainer( + node: node, + delegate: this, + listenable: editorState.selectionNotifier, + blockColor: editorState.editorStyle.selectionColor, + supportTypes: const [ + BlockSelectionType.block, + ], + child: child, + ); + if (widget.showActions && widget.actionBuilder != null) { child = BlockComponentActionWrapper( node: node, @@ -105,7 +118,6 @@ class _DividerBlockComponentWidgetState ); } - final editorState = context.read(); final selectionNotifier = editorState.selectionNotifier; return BlockSelectionContainer( node: node, @@ -163,7 +175,9 @@ class _DividerBlockComponentWidgetState final dividerBox = dividerKey.currentContext?.findRenderObject(); if (parentBox is RenderBox && dividerBox is RenderBox) { return [ - dividerBox.localToGlobal(Offset.zero, ancestor: parentBox) & + (shiftWithBaseOffset + ? dividerBox.localToGlobal(Offset.zero, ancestor: parentBox) + : Offset.zero) & dividerBox.size ]; } 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 e3b422440..38a78226c 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 @@ -159,6 +159,8 @@ class _HeadingBlockComponentWidgetState defaultTextStyle(level), ), textDirection: textDirection, + cursorColor: editorState.editorStyle.cursorColor, + selectionColor: editorState.editorStyle.selectionColor, ), ), ], @@ -171,6 +173,17 @@ class _HeadingBlockComponentWidgetState child: child, ); + child = BlockSelectionContainer( + node: node, + delegate: this, + listenable: editorState.selectionNotifier, + blockColor: editorState.editorStyle.selectionColor, + supportTypes: const [ + BlockSelectionType.block, + ], + child: child, + ); + if (widget.showActions && widget.actionBuilder != null) { child = BlockComponentActionWrapper( node: node, diff --git a/lib/src/editor/block_component/image_block_component/image_block_component.dart b/lib/src/editor/block_component/image_block_component/image_block_component.dart index fcaf61a13..5450c2b68 100644 --- a/lib/src/editor/block_component/image_block_component/image_block_component.dart +++ b/lib/src/editor/block_component/image_block_component/image_block_component.dart @@ -163,6 +163,17 @@ class ImageBlockComponentWidgetState extends State child: child, ); + child = BlockSelectionContainer( + node: node, + delegate: this, + listenable: editorState.selectionNotifier, + blockColor: editorState.editorStyle.selectionColor, + supportTypes: const [ + BlockSelectionType.block, + ], + child: child, + ); + if (widget.showActions && widget.actionBuilder != null) { child = BlockComponentActionWrapper( node: node, 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 86bae2e67..38c2378fd 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 @@ -153,6 +153,8 @@ class _NumberedListBlockComponentWidgetState placeholderTextStyle, ), textDirection: textDirection, + cursorColor: editorState.editorStyle.cursorColor, + selectionColor: editorState.editorStyle.selectionColor, ), ), ], @@ -165,6 +167,17 @@ class _NumberedListBlockComponentWidgetState child: child, ); + child = BlockSelectionContainer( + node: node, + delegate: this, + listenable: editorState.selectionNotifier, + blockColor: editorState.editorStyle.selectionColor, + supportTypes: const [ + BlockSelectionType.block, + ], + child: child, + ); + if (widget.showActions && widget.actionBuilder != null) { child = BlockComponentActionWrapper( node: node, 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 5bf4350d0..3874d94ba 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 @@ -144,6 +144,8 @@ class _QuoteBlockComponentWidgetState extends State placeholderTextStyle, ), textDirection: textDirection, + cursorColor: editorState.editorStyle.cursorColor, + selectionColor: editorState.editorStyle.selectionColor, ), ), ], @@ -157,6 +159,17 @@ class _QuoteBlockComponentWidgetState extends State child: child, ); + child = BlockSelectionContainer( + node: node, + delegate: this, + listenable: editorState.selectionNotifier, + blockColor: editorState.editorStyle.selectionColor, + supportTypes: const [ + BlockSelectionType.block, + ], + child: child, + ); + if (widget.showActions && widget.actionBuilder != null) { child = BlockComponentActionWrapper( node: node, diff --git a/lib/src/editor/block_component/rich_text/appflowy_rich_text.dart b/lib/src/editor/block_component/rich_text/appflowy_rich_text.dart index 8732f4bf8..92883fa34 100644 --- a/lib/src/editor/block_component/rich_text/appflowy_rich_text.dart +++ b/lib/src/editor/block_component/rich_text/appflowy_rich_text.dart @@ -148,10 +148,6 @@ class _AppFlowyRichTextState extends State Position position, { bool shiftWithBaseOffset = false, }) { - // TODO: the debug needs layout!!! - if (_renderParagraph?.debugNeedsLayout == true) { - return null; - } final textPosition = TextPosition(offset: position.offset); var cursorHeight = _renderParagraph?.getFullHeightForCaret(textPosition); var cursorOffset = diff --git a/lib/src/editor/block_component/table_block_component/table_block_component.dart b/lib/src/editor/block_component/table_block_component/table_block_component.dart index 4b858647d..1d40218d6 100644 --- a/lib/src/editor/block_component/table_block_component/table_block_component.dart +++ b/lib/src/editor/block_component/table_block_component/table_block_component.dart @@ -164,6 +164,17 @@ class _TableBlockComponentWidgetState extends State child: child, ); + child = BlockSelectionContainer( + node: node, + delegate: this, + listenable: editorState.selectionNotifier, + blockColor: editorState.editorStyle.selectionColor, + supportTypes: const [ + BlockSelectionType.block, + ], + child: child, + ); + if (widget.showActions && widget.actionBuilder != null) { child = BlockComponentActionWrapper( node: node, @@ -196,7 +207,10 @@ class _TableBlockComponentWidgetState extends State final tableBox = tableKey.currentContext?.findRenderObject(); if (parentBox is RenderBox && tableBox is RenderBox) { return [ - tableBox.localToGlobal(Offset.zero, ancestor: parentBox) & tableBox.size + (shiftWithBaseOffset + ? tableBox.localToGlobal(Offset.zero, ancestor: parentBox) + : Offset.zero) & + tableBox.size ]; } return [Offset.zero & _renderBox.size]; 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 bbf3132c7..0beca2c53 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 @@ -165,6 +165,8 @@ class _TodoListBlockComponentWidgetState textSpan.updateTextStyle( placeholderTextStyle, ), + cursorColor: editorState.editorStyle.cursorColor, + selectionColor: editorState.editorStyle.selectionColor, ), ), ], @@ -177,6 +179,17 @@ class _TodoListBlockComponentWidgetState child: child, ); + child = BlockSelectionContainer( + node: node, + delegate: this, + listenable: editorState.selectionNotifier, + blockColor: editorState.editorStyle.selectionColor, + supportTypes: const [ + BlockSelectionType.block, + ], + child: child, + ); + if (widget.showActions && widget.actionBuilder != null) { child = BlockComponentActionWrapper( node: node, diff --git a/lib/src/editor/editor_component/entry/page_block_component.dart b/lib/src/editor/editor_component/entry/page_block_component.dart index 506abd307..0cd7ab750 100644 --- a/lib/src/editor/editor_component/entry/page_block_component.dart +++ b/lib/src/editor/editor_component/entry/page_block_component.dart @@ -49,32 +49,30 @@ class PageBlockComponent extends BlockComponentStatelessWidget { final editorState = context.read(); final scrollController = context.read(); final items = node.children; - int extentCount = 0; - if (header != null) extentCount++; - if (footer != null) extentCount++; if (scrollController.shrinkWrap) { - return SingleChildScrollView( - controller: scrollController.scrollController, - child: Builder( - builder: (context) { - editorState.updateAutoScroller(Scrollable.of(context)); - return Padding( - padding: editorState.editorStyle.padding, - child: Column( - children: [ - if (header != null) header!, - ...items - .map((e) => editorState.renderer.build(context, e)) - .toList(), - if (footer != null) footer!, - ], - ), - ); - }, - ), + return Builder( + builder: (context) { + editorState.updateAutoScroller(Scrollable.of(context)); + return Padding( + padding: editorState.editorStyle.padding, + child: Column( + children: [ + if (header != null) header!, + ...items + .map((e) => editorState.renderer.build(context, e)) + .toList(), + if (footer != null) footer!, + ], + ), + ); + }, ); } else { + int extentCount = 0; + if (header != null) extentCount++; + if (footer != null) extentCount++; + return ScrollablePositionedList.builder( shrinkWrap: scrollController.shrinkWrap, padding: editorState.editorStyle.padding, diff --git a/lib/src/editor/editor_component/service/scroll/editor_scroll_controller.dart b/lib/src/editor/editor_component/service/scroll/editor_scroll_controller.dart index 3ddd5bccf..b113d6cea 100644 --- a/lib/src/editor/editor_component/service/scroll/editor_scroll_controller.dart +++ b/lib/src/editor/editor_component/service/scroll/editor_scroll_controller.dart @@ -12,10 +12,13 @@ import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; /// You can use [offsetNotifier] to get the current scroll offset. /// And, you can use [visibleRangeNotifier] to get the first level visible items. /// +/// If the shrinkWrap is true, the scrollController must not be null +/// and the editor should be wrapped in a SingleChildScrollView. class EditorScrollController { EditorScrollController({ required this.editorState, this.shrinkWrap = false, + ScrollController? scrollController, }) { // if shrinkWrap is true, we will render the document with Column layout. // otherwise, we will render the document with ScrollablePositionedList. @@ -30,10 +33,12 @@ class EditorScrollController { updateVisibleRange(); editorState.document.root.addListener(updateVisibleRange); + shouldDisposeScrollController = scrollController == null; + this.scrollController = scrollController ?? ScrollController(); // listen to the scroll offset - scrollController.addListener( - () => offsetNotifier.value = scrollController.offset, - ); + this.scrollController.addListener( + () => offsetNotifier.value = this.scrollController.offset, + ); } else { // listen to the scroll offset _scrollOffsetSubscription = _scrollOffsetListener.changes.listen((value) { @@ -70,7 +75,8 @@ class EditorScrollController { // these value is required by SingleChildScrollView // notes: don't use them if shrinkWrap is false // ------------ start ---------------- - final ScrollController scrollController = ScrollController(); + late final ScrollController scrollController; + bool shouldDisposeScrollController = false; // ------------ end ---------------- // these values are required by ScrollablePositionedList @@ -132,7 +138,9 @@ class EditorScrollController { // dispose the subscription void dispose() { - scrollController.dispose(); + if (shouldDisposeScrollController) { + scrollController.dispose(); + } _scrollOffsetSubscription.cancel(); _itemPositionsListener.itemPositions.removeListener(_listenItemPositions); diff --git a/lib/src/editor/editor_component/service/shortcuts/command_shortcut_events/backspace_command.dart b/lib/src/editor/editor_component/service/shortcuts/command_shortcut_events/backspace_command.dart index 04b6dd628..329abe6e9 100644 --- a/lib/src/editor/editor_component/service/shortcuts/command_shortcut_events/backspace_command.dart +++ b/lib/src/editor/editor_component/service/shortcuts/command_shortcut_events/backspace_command.dart @@ -45,10 +45,15 @@ CommandShortcutEventHandler _deleteLeftSentenceCommandHandler = (editorState) { CommandShortcutEventHandler _backspaceCommandHandler = (editorState) { final selection = editorState.selection; + final selectionType = editorState.selectionType; + if (selection == null) { return KeyEventResult.ignored; } - if (selection.isCollapsed) { + + if (selectionType == SelectionType.block) { + return _backspaceInBlockSelection(editorState); + } else if (selection.isCollapsed) { return _backspaceInCollapsedSelection(editorState); } else { return _backspaceInNotCollapsedSelection(editorState); @@ -138,3 +143,17 @@ CommandShortcutEventHandler _backspaceInNotCollapsedSelection = (editorState) { editorState.deleteSelection(selection); return KeyEventResult.handled; }; + +CommandShortcutEventHandler _backspaceInBlockSelection = (editorState) { + final selection = editorState.selection; + if (selection == null || editorState.selectionType != SelectionType.block) { + return KeyEventResult.ignored; + } + final transaction = editorState.transaction; + transaction.deleteNodesAtPath(selection.start.path); + editorState + .apply(transaction) + .then((value) => editorState.selectionType = null); + + return KeyEventResult.handled; +}; diff --git a/lib/src/editor/editor_component/service/shortcuts/command_shortcut_events/delete_command.dart b/lib/src/editor/editor_component/service/shortcuts/command_shortcut_events/delete_command.dart index 69ca64460..0e78b2b00 100644 --- a/lib/src/editor/editor_component/service/shortcuts/command_shortcut_events/delete_command.dart +++ b/lib/src/editor/editor_component/service/shortcuts/command_shortcut_events/delete_command.dart @@ -15,10 +15,13 @@ final CommandShortcutEvent deleteCommand = CommandShortcutEvent( CommandShortcutEventHandler _deleteCommandHandler = (editorState) { final selection = editorState.selection; + final selectionType = editorState.selectionType; if (selection == null) { return KeyEventResult.ignored; } - if (selection.isCollapsed) { + if (selectionType == SelectionType.block) { + return _deleteInBlockSelection(editorState); + } else if (selection.isCollapsed) { return _deleteInCollapsedSelection(editorState); } else { return _deleteInNotCollapsedSelection(editorState); @@ -83,3 +86,17 @@ CommandShortcutEventHandler _deleteInNotCollapsedSelection = (editorState) { editorState.deleteSelection(selection); return KeyEventResult.handled; }; + +CommandShortcutEventHandler _deleteInBlockSelection = (editorState) { + final selection = editorState.selection; + if (selection == null || editorState.selectionType != SelectionType.block) { + return KeyEventResult.ignored; + } + final transaction = editorState.transaction; + transaction.deleteNodesAtPath(selection.start.path); + editorState + .apply(transaction) + .then((value) => editorState.selectionType = null); + + return KeyEventResult.handled; +}; diff --git a/test/customer/custom_action_builder_test.dart b/test/customer/custom_action_builder_test.dart index 712e038e8..911ab850f 100644 --- a/test/customer/custom_action_builder_test.dart +++ b/test/customer/custom_action_builder_test.dart @@ -29,7 +29,11 @@ void main() async { await tester.pumpAndSettle(); final selectionAreaRect = tester.getTopLeft( - find.byType(BlockSelectionArea), + find.byWidgetPredicate( + (widget) => + widget is BlockSelectionArea && + widget.supportTypes.contains(BlockSelectionType.block), + ), ); expect(selectionAreaRect.dx, greaterThan(0)); }); diff --git a/test/customer/custom_toolbar_item_color_test.dart b/test/customer/custom_toolbar_item_color_test.dart index 2cc4b5887..f6c011204 100644 --- a/test/customer/custom_toolbar_item_color_test.dart +++ b/test/customer/custom_toolbar_item_color_test.dart @@ -64,13 +64,12 @@ class CustomToolbarItemColor extends StatelessWidget { toolbarActiveColor: Colors.green, ), editorState: editorState, - scrollController: scrollController, editorScrollController: EditorScrollController( editorState: editorState, + scrollController: scrollController, ), child: AppFlowyEditor( editorState: editorState, - scrollController: scrollController, ), ), ), diff --git a/test/mobile/toolbar/mobile/test_helpers/mobile_app_with_toolbar_widget.dart b/test/mobile/toolbar/mobile/test_helpers/mobile_app_with_toolbar_widget.dart index a8a5b6141..f56da01d2 100644 --- a/test/mobile/toolbar/mobile/test_helpers/mobile_app_with_toolbar_widget.dart +++ b/test/mobile/toolbar/mobile/test_helpers/mobile_app_with_toolbar_widget.dart @@ -1,5 +1,5 @@ -import 'package:flutter/material.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:flutter/material.dart'; /// Used in testing mobile app with toolbar class MobileAppWithToolbarWidget extends StatelessWidget { @@ -13,7 +13,6 @@ class MobileAppWithToolbarWidget extends StatelessWidget { @override Widget build(BuildContext context) { - final scrollController = ScrollController(); final localToolbarItems = toolbarItems ?? [ textDecorationMobileToolbarItem, @@ -31,7 +30,6 @@ class MobileAppWithToolbarWidget extends StatelessWidget { child: AppFlowyEditor( editorStyle: const EditorStyle.mobile(), editorState: editorState, - scrollController: scrollController, ), ), MobileToolbar( diff --git a/test/new/infra/testable_editor.dart b/test/new/infra/testable_editor.dart index 28d6d809e..6e987c5d5 100644 --- a/test/new/infra/testable_editor.dart +++ b/test/new/infra/testable_editor.dart @@ -50,6 +50,8 @@ class TestableEditor { final editorScrollController = EditorScrollController( editorState: editorState, + shrinkWrap: shrinkWrap, + scrollController: scrollController, ); Widget editor = Builder( @@ -59,11 +61,7 @@ class TestableEditor { editable: editable, autoFocus: autoFocus, shrinkWrap: shrinkWrap, - scrollController: scrollController, - editorScrollController: EditorScrollController( - editorState: editorState, - shrinkWrap: shrinkWrap, - ), + editorScrollController: editorScrollController, commandShortcutEvents: [ ...standardCommandShortcutEvents, ...TestableFindAndReplaceCommands(context: context) @@ -93,7 +91,6 @@ class TestableEditor { child: AppFlowyEditor( editorStyle: const EditorStyle.mobile(), editorState: editorState, - scrollController: scrollController, ), ), MobileToolbar( @@ -116,7 +113,6 @@ class TestableEditor { buildHighlightColorItem() ], editorState: editorState, - scrollController: scrollController!, editorScrollController: editorScrollController, child: editor, ); diff --git a/test/new/service/shortcuts/command_shortcut_events/show_link_menu_command_test.dart b/test/new/service/shortcuts/command_shortcut_events/show_link_menu_command_test.dart index 1e1ceab2c..17975baca 100644 --- a/test/new/service/shortcuts/command_shortcut_events/show_link_menu_command_test.dart +++ b/test/new/service/shortcuts/command_shortcut_events/show_link_menu_command_test.dart @@ -44,9 +44,9 @@ Future _testLinkMenuInSingleTextSelection(WidgetTester tester) async { buildHighlightColorItem(), ], editorState: editor.editorState, - scrollController: scrollController, editorScrollController: EditorScrollController( editorState: editor.editorState, + scrollController: scrollController, ), child: AppFlowyEditor(editorState: editor.editorState), );