diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/infra/flowy_svg.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/infra/flowy_svg.dart index cb60addaff..96ae89a4d5 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/infra/flowy_svg.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/infra/flowy_svg.dart @@ -5,18 +5,23 @@ class FlowySvg extends StatelessWidget { const FlowySvg({ Key? key, this.name, - this.size = const Size(20, 20), + this.width, + this.height, this.color, this.number, this.padding, }) : super(key: key); final String? name; - final Size size; + final double? width; + final double? height; final Color? color; final int? number; final EdgeInsets? padding; + final _defaultWidth = 20.0; + final _defaultHeight = 20.0; + @override Widget build(BuildContext context) { return Padding( @@ -27,22 +32,21 @@ class FlowySvg extends StatelessWidget { Widget _buildSvg() { if (name != null) { - return SizedBox.fromSize( - size: size, - child: SvgPicture.asset( - 'assets/images/$name.svg', - color: color, - package: 'appflowy_editor', - fit: BoxFit.fill, - ), + return SvgPicture.asset( + 'assets/images/$name.svg', + color: color, + fit: BoxFit.fill, + height: height, + width: width, + package: 'appflowy_editor', ); } else if (number != null) { final numberText = '$number.'; return SvgPicture.string( numberText, - width: size.width, - height: size.width, + width: width ?? _defaultWidth, + height: height ?? _defaultHeight, ); } return Container(); diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/bulleted_list_text.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/bulleted_list_text.dart index 74ee29ed44..267a5acc66 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/bulleted_list_text.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/bulleted_list_text.dart @@ -47,7 +47,7 @@ class _BulletedListTextNodeWidgetState extends State final iconKey = GlobalKey(); final _richTextKey = GlobalKey(debugLabel: 'bulleted_list_text'); - final _iconSize = 20.0; + final _iconWidth = 20.0; final _iconRightPadding = 5.0; @override @@ -67,7 +67,8 @@ class _BulletedListTextNodeWidgetState extends State children: [ FlowySvg( key: iconKey, - size: Size.square(_iconSize), + width: _iconWidth, + height: _iconWidth, padding: EdgeInsets.only(top: topPadding, right: _iconRightPadding), name: 'point', diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/checkbox_text.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/checkbox_text.dart index 646bd4d408..1607d6f649 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/checkbox_text.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/checkbox_text.dart @@ -45,7 +45,7 @@ class _CheckboxNodeWidgetState extends State final iconKey = GlobalKey(); final _richTextKey = GlobalKey(debugLabel: 'checkbox_text'); - final _iconSize = 20.0; + final _iconWidth = 20.0; final _iconRightPadding = 5.0; @override @@ -74,7 +74,8 @@ class _CheckboxNodeWidgetState extends State GestureDetector( key: iconKey, child: FlowySvg( - size: Size.square(_iconSize), + width: _iconWidth, + height: _iconWidth, padding: EdgeInsets.only( top: topPadding, right: _iconRightPadding, diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/number_list_text.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/number_list_text.dart index 836da9b69e..de3b0b55b6 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/number_list_text.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/number_list_text.dart @@ -47,7 +47,7 @@ class _NumberListTextNodeWidgetState extends State final iconKey = GlobalKey(); final _richTextKey = GlobalKey(debugLabel: 'number_list_text'); - final _iconSize = 20.0; + final _iconWidth = 20.0; final _iconRightPadding = 5.0; @override @@ -66,7 +66,8 @@ class _NumberListTextNodeWidgetState extends State children: [ FlowySvg( key: iconKey, - size: Size.square(_iconSize), + width: _iconWidth, + height: _iconWidth, padding: EdgeInsets.only(top: topPadding, right: _iconRightPadding), number: widget.textNode.attributes.number, diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/quoted_text.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/quoted_text.dart index aa43be053a..0389dfa50f 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/quoted_text.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/quoted_text.dart @@ -46,7 +46,7 @@ class _QuotedTextNodeWidgetState extends State final iconKey = GlobalKey(); final _richTextKey = GlobalKey(debugLabel: 'quoted_text'); - final _iconSize = 20.0; + final _iconWidth = 20.0; final _iconRightPadding = 5.0; @override @@ -60,25 +60,27 @@ class _QuotedTextNodeWidgetState extends State width: defaultMaxTextNodeWidth, child: Padding( padding: EdgeInsets.only(bottom: defaultLinePadding), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - FlowySvg( - key: iconKey, - size: Size(_iconSize, _quoteHeight), - padding: - EdgeInsets.only(top: topPadding, right: _iconRightPadding), - name: 'quote', - ), - Expanded( - child: FlowyRichText( - key: _richTextKey, - placeholderText: 'Quote', - textNode: widget.textNode, - editorState: widget.editorState, + child: IntrinsicHeight( + child: Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + FlowySvg( + key: iconKey, + width: _iconWidth, + padding: EdgeInsets.only( + top: topPadding, right: _iconRightPadding), + name: 'quote', ), - ), - ], + Expanded( + child: FlowyRichText( + key: _richTextKey, + placeholderText: 'Quote', + textNode: widget.textNode, + editorState: widget.editorState, + ), + ), + ], + ), ), )); } @@ -86,6 +88,6 @@ class _QuotedTextNodeWidgetState extends State double get _quoteHeight { final lines = widget.textNode.toRawString().characters.where((c) => c == '\n').length; - return (lines + 1) * _iconSize; + return (lines + 1) * _iconWidth; } } diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection/toolbar_widget.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection/toolbar_widget.dart index dfc5c5c971..4c2b621795 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection/toolbar_widget.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection/toolbar_widget.dart @@ -141,7 +141,7 @@ class _ToolbarWidgetState extends State with ToolbarMixin { Size(toolbarHeight - (width != null ? 20 : 0), toolbarHeight), child: Center( child: FlowySvg( - size: Size(width ?? 20, 20), + width: width ?? 20, name: 'toolbar/$name', ), ), diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/arrow_keys_handler.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/arrow_keys_handler.dart index e6f474668a..ecaf3325e4 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/arrow_keys_handler.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/arrow_keys_handler.dart @@ -120,8 +120,9 @@ AppFlowyKeyEventHandler arrowKeysHandler = (editorState, event) { editorState.updateCursorSelection(Selection.collapsed(leftPosition)); } } else { - editorState - .updateCursorSelection(currentSelection.collapse(atStart: true)); + editorState.updateCursorSelection( + currentSelection.collapse(atStart: currentSelection.isBackward), + ); } return KeyEventResult.handled; } else if (event.logicalKey == LogicalKeyboardKey.arrowRight) { @@ -131,7 +132,9 @@ AppFlowyKeyEventHandler arrowKeysHandler = (editorState, event) { editorState.updateCursorSelection(Selection.collapsed(rightPosition)); } } else { - editorState.updateCursorSelection(currentSelection.collapse()); + editorState.updateCursorSelection( + currentSelection.collapse(atStart: !currentSelection.isBackward), + ); } return KeyEventResult.handled; } else if (event.logicalKey == LogicalKeyboardKey.arrowUp) { diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/slash_handler.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/slash_handler.dart index a13d203450..cbb345a370 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/slash_handler.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/slash_handler.dart @@ -448,5 +448,6 @@ class PopupListItem { Widget _popupListIcon(String name) => FlowySvg( name: 'popup_list/$name', color: Colors.black, - size: const Size.square(18.0), + width: 18.0, + height: 18.0, ); diff --git a/frontend/app_flowy/packages/appflowy_editor/test/infra/test_raw_key_event.dart b/frontend/app_flowy/packages/appflowy_editor/test/infra/test_raw_key_event.dart index f54528064c..150a3e2d00 100644 --- a/frontend/app_flowy/packages/appflowy_editor/test/infra/test_raw_key_event.dart +++ b/frontend/app_flowy/packages/appflowy_editor/test/infra/test_raw_key_event.dart @@ -88,6 +88,12 @@ extension on LogicalKeyboardKey { if (this == LogicalKeyboardKey.delete) { return PhysicalKeyboardKey.delete; } + if (this == LogicalKeyboardKey.arrowRight) { + return PhysicalKeyboardKey.arrowRight; + } + if (this == LogicalKeyboardKey.arrowLeft) { + return PhysicalKeyboardKey.arrowLeft; + } if (this == LogicalKeyboardKey.pageDown) { return PhysicalKeyboardKey.pageDown; } diff --git a/frontend/app_flowy/packages/appflowy_editor/test/service/internal_key_event_handlers/arrow_keys_handler_test.dart b/frontend/app_flowy/packages/appflowy_editor/test/service/internal_key_event_handlers/arrow_keys_handler_test.dart new file mode 100644 index 0000000000..e4631b56ad --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_editor/test/service/internal_key_event_handlers/arrow_keys_handler_test.dart @@ -0,0 +1,84 @@ +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import '../../infra/test_editor.dart'; + +void main() async { + setUpAll(() { + TestWidgetsFlutterBinding.ensureInitialized(); + }); + + group('arrow_keys_handler.dart', () { + testWidgets('Presses arrow right key, move the cursor from left to right', + (tester) async { + const text = 'Welcome to Appflowy 😁'; + final editor = tester.editor + ..insertTextNode(text) + ..insertTextNode(text); + await editor.startTesting(); + + await editor.updateSelection( + Selection.single(path: [0], startOffset: 0), + ); + + final textNode = editor.nodeAtPath([0]) as TextNode; + for (var i = 0; i < text.length; i++) { + await editor.pressLogicKey(LogicalKeyboardKey.arrowRight); + + if (i == text.length - 1) { + // Wrap to next node if the cursor is at the end of the current node. + expect( + editor.documentSelection, + Selection.single( + path: [1], + startOffset: 0, + ), + ); + } else { + expect( + editor.documentSelection, + Selection.single( + path: [0], + startOffset: textNode.delta.nextRunePosition(i), + ), + ); + } + } + }); + }); + + testWidgets( + 'Presses arrow left/right key since selection is not collapsed and backward', + (tester) async { + await _testPressArrowKeyInNotCollapsedSelection(tester, true); + }); + + testWidgets( + 'Presses arrow left/right key since selection is not collapsed and forward', + (tester) async { + await _testPressArrowKeyInNotCollapsedSelection(tester, false); + }); +} + +Future _testPressArrowKeyInNotCollapsedSelection( + WidgetTester tester, bool isBackward) async { + const text = 'Welcome to Appflowy 😁'; + final editor = tester.editor + ..insertTextNode(text) + ..insertTextNode(text); + await editor.startTesting(); + + final start = Position(path: [0], offset: 5); + final end = Position(path: [1], offset: 10); + final selection = Selection( + start: isBackward ? start : end, + end: isBackward ? end : start, + ); + await editor.updateSelection(selection); + await editor.pressLogicKey(LogicalKeyboardKey.arrowLeft); + expect(editor.documentSelection?.start, start); + + await editor.updateSelection(selection); + await editor.pressLogicKey(LogicalKeyboardKey.arrowRight); + expect(editor.documentSelection?.end, end); +}