Skip to content

Commit 45a120f

Browse files
committed
fix: selection is wrong after pressing arrow left / right
1 parent 28d9b8e commit 45a120f

File tree

10 files changed

+146
-43
lines changed

10 files changed

+146
-43
lines changed

frontend/app_flowy/packages/appflowy_editor/lib/src/infra/flowy_svg.dart

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,23 @@ class FlowySvg extends StatelessWidget {
55
const FlowySvg({
66
Key? key,
77
this.name,
8-
this.size = const Size(20, 20),
8+
this.width,
9+
this.height,
910
this.color,
1011
this.number,
1112
this.padding,
1213
}) : super(key: key);
1314

1415
final String? name;
15-
final Size size;
16+
final double? width;
17+
final double? height;
1618
final Color? color;
1719
final int? number;
1820
final EdgeInsets? padding;
1921

22+
final _defaultWidth = 20.0;
23+
final _defaultHeight = 20.0;
24+
2025
@override
2126
Widget build(BuildContext context) {
2227
return Padding(
@@ -27,22 +32,21 @@ class FlowySvg extends StatelessWidget {
2732

2833
Widget _buildSvg() {
2934
if (name != null) {
30-
return SizedBox.fromSize(
31-
size: size,
32-
child: SvgPicture.asset(
33-
'assets/images/$name.svg',
34-
color: color,
35-
package: 'appflowy_editor',
36-
fit: BoxFit.fill,
37-
),
35+
return SvgPicture.asset(
36+
'assets/images/$name.svg',
37+
color: color,
38+
fit: BoxFit.fill,
39+
height: height,
40+
width: width,
41+
package: 'appflowy_editor',
3842
);
3943
} else if (number != null) {
4044
final numberText =
4145
'<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg"><text x="30" y="150" fill="black" font-size="160">$number.</text></svg>';
4246
return SvgPicture.string(
4347
numberText,
44-
width: size.width,
45-
height: size.width,
48+
width: width ?? _defaultWidth,
49+
height: height ?? _defaultHeight,
4650
);
4751
}
4852
return Container();

frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/bulleted_list_text.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class _BulletedListTextNodeWidgetState extends State<BulletedListTextNodeWidget>
4747
final iconKey = GlobalKey();
4848

4949
final _richTextKey = GlobalKey(debugLabel: 'bulleted_list_text');
50-
final _iconSize = 20.0;
50+
final _iconWidth = 20.0;
5151
final _iconRightPadding = 5.0;
5252

5353
@override
@@ -67,7 +67,8 @@ class _BulletedListTextNodeWidgetState extends State<BulletedListTextNodeWidget>
6767
children: [
6868
FlowySvg(
6969
key: iconKey,
70-
size: Size.square(_iconSize),
70+
width: _iconWidth,
71+
height: _iconWidth,
7172
padding:
7273
EdgeInsets.only(top: topPadding, right: _iconRightPadding),
7374
name: 'point',

frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/checkbox_text.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class _CheckboxNodeWidgetState extends State<CheckboxNodeWidget>
4545
final iconKey = GlobalKey();
4646

4747
final _richTextKey = GlobalKey(debugLabel: 'checkbox_text');
48-
final _iconSize = 20.0;
48+
final _iconWidth = 20.0;
4949
final _iconRightPadding = 5.0;
5050

5151
@override
@@ -74,7 +74,8 @@ class _CheckboxNodeWidgetState extends State<CheckboxNodeWidget>
7474
GestureDetector(
7575
key: iconKey,
7676
child: FlowySvg(
77-
size: Size.square(_iconSize),
77+
width: _iconWidth,
78+
height: _iconWidth,
7879
padding: EdgeInsets.only(
7980
top: topPadding,
8081
right: _iconRightPadding,

frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/number_list_text.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class _NumberListTextNodeWidgetState extends State<NumberListTextNodeWidget>
4747
final iconKey = GlobalKey();
4848

4949
final _richTextKey = GlobalKey(debugLabel: 'number_list_text');
50-
final _iconSize = 20.0;
50+
final _iconWidth = 20.0;
5151
final _iconRightPadding = 5.0;
5252

5353
@override
@@ -66,7 +66,8 @@ class _NumberListTextNodeWidgetState extends State<NumberListTextNodeWidget>
6666
children: [
6767
FlowySvg(
6868
key: iconKey,
69-
size: Size.square(_iconSize),
69+
width: _iconWidth,
70+
height: _iconWidth,
7071
padding:
7172
EdgeInsets.only(top: topPadding, right: _iconRightPadding),
7273
number: widget.textNode.attributes.number,

frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/quoted_text.dart

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ class _QuotedTextNodeWidgetState extends State<QuotedTextNodeWidget>
4646
final iconKey = GlobalKey();
4747

4848
final _richTextKey = GlobalKey(debugLabel: 'quoted_text');
49-
final _iconSize = 20.0;
49+
final _iconWidth = 20.0;
5050
final _iconRightPadding = 5.0;
5151

5252
@override
@@ -60,32 +60,34 @@ class _QuotedTextNodeWidgetState extends State<QuotedTextNodeWidget>
6060
width: defaultMaxTextNodeWidth,
6161
child: Padding(
6262
padding: EdgeInsets.only(bottom: defaultLinePadding),
63-
child: Row(
64-
crossAxisAlignment: CrossAxisAlignment.start,
65-
children: [
66-
FlowySvg(
67-
key: iconKey,
68-
size: Size(_iconSize, _quoteHeight),
69-
padding:
70-
EdgeInsets.only(top: topPadding, right: _iconRightPadding),
71-
name: 'quote',
72-
),
73-
Expanded(
74-
child: FlowyRichText(
75-
key: _richTextKey,
76-
placeholderText: 'Quote',
77-
textNode: widget.textNode,
78-
editorState: widget.editorState,
63+
child: IntrinsicHeight(
64+
child: Row(
65+
crossAxisAlignment: CrossAxisAlignment.stretch,
66+
children: [
67+
FlowySvg(
68+
key: iconKey,
69+
width: _iconWidth,
70+
padding: EdgeInsets.only(
71+
top: topPadding, right: _iconRightPadding),
72+
name: 'quote',
7973
),
80-
),
81-
],
74+
Expanded(
75+
child: FlowyRichText(
76+
key: _richTextKey,
77+
placeholderText: 'Quote',
78+
textNode: widget.textNode,
79+
editorState: widget.editorState,
80+
),
81+
),
82+
],
83+
),
8284
),
8385
));
8486
}
8587

8688
double get _quoteHeight {
8789
final lines =
8890
widget.textNode.toRawString().characters.where((c) => c == '\n').length;
89-
return (lines + 1) * _iconSize;
91+
return (lines + 1) * _iconWidth;
9092
}
9193
}

frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection/toolbar_widget.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ class _ToolbarWidgetState extends State<ToolbarWidget> with ToolbarMixin {
141141
Size(toolbarHeight - (width != null ? 20 : 0), toolbarHeight),
142142
child: Center(
143143
child: FlowySvg(
144-
size: Size(width ?? 20, 20),
144+
width: width ?? 20,
145145
name: 'toolbar/$name',
146146
),
147147
),

frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/arrow_keys_handler.dart

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,9 @@ AppFlowyKeyEventHandler arrowKeysHandler = (editorState, event) {
120120
editorState.updateCursorSelection(Selection.collapsed(leftPosition));
121121
}
122122
} else {
123-
editorState
124-
.updateCursorSelection(currentSelection.collapse(atStart: true));
123+
editorState.updateCursorSelection(
124+
currentSelection.collapse(atStart: currentSelection.isBackward),
125+
);
125126
}
126127
return KeyEventResult.handled;
127128
} else if (event.logicalKey == LogicalKeyboardKey.arrowRight) {
@@ -131,7 +132,9 @@ AppFlowyKeyEventHandler arrowKeysHandler = (editorState, event) {
131132
editorState.updateCursorSelection(Selection.collapsed(rightPosition));
132133
}
133134
} else {
134-
editorState.updateCursorSelection(currentSelection.collapse());
135+
editorState.updateCursorSelection(
136+
currentSelection.collapse(atStart: !currentSelection.isBackward),
137+
);
135138
}
136139
return KeyEventResult.handled;
137140
} else if (event.logicalKey == LogicalKeyboardKey.arrowUp) {

frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/slash_handler.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -448,5 +448,6 @@ class PopupListItem {
448448
Widget _popupListIcon(String name) => FlowySvg(
449449
name: 'popup_list/$name',
450450
color: Colors.black,
451-
size: const Size.square(18.0),
451+
width: 18.0,
452+
height: 18.0,
452453
);

frontend/app_flowy/packages/appflowy_editor/test/infra/test_raw_key_event.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,12 @@ extension on LogicalKeyboardKey {
8888
if (this == LogicalKeyboardKey.delete) {
8989
return PhysicalKeyboardKey.delete;
9090
}
91+
if (this == LogicalKeyboardKey.arrowRight) {
92+
return PhysicalKeyboardKey.arrowRight;
93+
}
94+
if (this == LogicalKeyboardKey.arrowLeft) {
95+
return PhysicalKeyboardKey.arrowLeft;
96+
}
9197
if (this == LogicalKeyboardKey.pageDown) {
9298
return PhysicalKeyboardKey.pageDown;
9399
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import 'package:appflowy_editor/appflowy_editor.dart';
2+
import 'package:flutter/services.dart';
3+
import 'package:flutter_test/flutter_test.dart';
4+
import '../../infra/test_editor.dart';
5+
6+
void main() async {
7+
setUpAll(() {
8+
TestWidgetsFlutterBinding.ensureInitialized();
9+
});
10+
11+
group('arrow_keys_handler.dart', () {
12+
testWidgets('Presses arrow right key, move the cursor from left to right',
13+
(tester) async {
14+
const text = 'Welcome to Appflowy 😁';
15+
final editor = tester.editor
16+
..insertTextNode(text)
17+
..insertTextNode(text);
18+
await editor.startTesting();
19+
20+
await editor.updateSelection(
21+
Selection.single(path: [0], startOffset: 0),
22+
);
23+
24+
final textNode = editor.nodeAtPath([0]) as TextNode;
25+
for (var i = 0; i < text.length; i++) {
26+
await editor.pressLogicKey(LogicalKeyboardKey.arrowRight);
27+
28+
if (i == text.length - 1) {
29+
// Wrap to next node if the cursor is at the end of the current node.
30+
expect(
31+
editor.documentSelection,
32+
Selection.single(
33+
path: [1],
34+
startOffset: 0,
35+
),
36+
);
37+
} else {
38+
expect(
39+
editor.documentSelection,
40+
Selection.single(
41+
path: [0],
42+
startOffset: textNode.delta.nextRunePosition(i),
43+
),
44+
);
45+
}
46+
}
47+
});
48+
});
49+
50+
testWidgets(
51+
'Presses arrow left/right key since selection is not collapsed and backward',
52+
(tester) async {
53+
await _testPressArrowKeyInNotCollapsedSelection(tester, true);
54+
});
55+
56+
testWidgets(
57+
'Presses arrow left/right key since selection is not collapsed and forward',
58+
(tester) async {
59+
await _testPressArrowKeyInNotCollapsedSelection(tester, false);
60+
});
61+
}
62+
63+
Future<void> _testPressArrowKeyInNotCollapsedSelection(
64+
WidgetTester tester, bool isBackward) async {
65+
const text = 'Welcome to Appflowy 😁';
66+
final editor = tester.editor
67+
..insertTextNode(text)
68+
..insertTextNode(text);
69+
await editor.startTesting();
70+
71+
final start = Position(path: [0], offset: 5);
72+
final end = Position(path: [1], offset: 10);
73+
final selection = Selection(
74+
start: isBackward ? start : end,
75+
end: isBackward ? end : start,
76+
);
77+
await editor.updateSelection(selection);
78+
await editor.pressLogicKey(LogicalKeyboardKey.arrowLeft);
79+
expect(editor.documentSelection?.start, start);
80+
81+
await editor.updateSelection(selection);
82+
await editor.pressLogicKey(LogicalKeyboardKey.arrowRight);
83+
expect(editor.documentSelection?.end, end);
84+
}

0 commit comments

Comments
 (0)