fix: remove keyword when click selection menu item

This commit is contained in:
Haoran Lin 2022-09-23 01:38:33 +08:00
parent 206b06f023
commit 5782dec45c
6 changed files with 91 additions and 52 deletions

View File

@ -29,7 +29,7 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/wakelock_macos/macos
SPEC CHECKSUMS:
FlutterMacOS: 57701585bf7de1b3fc2bb61f6378d73bbdea8424
FlutterMacOS: ae6af50a8ea7d6103d888583d46bd8328a7e9811
path_provider_macos: 3c0c3b4b0d4a76d2bf989a913c2de869c5641a19
rich_clipboard_macos: 43364b66b9dc69d203eb8dd6d758e2d12e02723c
url_launcher_macos: 597e05b8e514239626bcf4a850fcf9ef5c856ec3

View File

@ -37,7 +37,7 @@ class SelectionMenuItemWidget extends StatelessWidget {
: MaterialStateProperty.all(Colors.transparent),
),
label: Text(
item.name,
item.name(),
textAlign: TextAlign.left,
style: const TextStyle(
color: Colors.black,

View File

@ -124,7 +124,7 @@ List<SelectionMenuItem> get defaultSelectionMenuItems =>
_defaultSelectionMenuItems;
final List<SelectionMenuItem> _defaultSelectionMenuItems = [
SelectionMenuItem(
name: AppFlowyEditorLocalizations.current.text,
name: () => AppFlowyEditorLocalizations.current.text,
icon: _selectionMenuIcon('text'),
keywords: ['text'],
handler: (editorState, _, __) {
@ -132,7 +132,7 @@ final List<SelectionMenuItem> _defaultSelectionMenuItems = [
},
),
SelectionMenuItem(
name: AppFlowyEditorLocalizations.current.heading1,
name: () => AppFlowyEditorLocalizations.current.heading1,
icon: _selectionMenuIcon('h1'),
keywords: ['heading 1, h1'],
handler: (editorState, _, __) {
@ -140,7 +140,7 @@ final List<SelectionMenuItem> _defaultSelectionMenuItems = [
},
),
SelectionMenuItem(
name: AppFlowyEditorLocalizations.current.heading2,
name: () => AppFlowyEditorLocalizations.current.heading2,
icon: _selectionMenuIcon('h2'),
keywords: ['heading 2, h2'],
handler: (editorState, _, __) {
@ -148,7 +148,7 @@ final List<SelectionMenuItem> _defaultSelectionMenuItems = [
},
),
SelectionMenuItem(
name: AppFlowyEditorLocalizations.current.heading3,
name: () => AppFlowyEditorLocalizations.current.heading3,
icon: _selectionMenuIcon('h3'),
keywords: ['heading 3, h3'],
handler: (editorState, _, __) {
@ -156,13 +156,13 @@ final List<SelectionMenuItem> _defaultSelectionMenuItems = [
},
),
SelectionMenuItem(
name: AppFlowyEditorLocalizations.current.image,
name: () => AppFlowyEditorLocalizations.current.image,
icon: _selectionMenuIcon('image'),
keywords: ['image'],
handler: showImageUploadMenu,
),
SelectionMenuItem(
name: AppFlowyEditorLocalizations.current.bulletedList,
name: () => AppFlowyEditorLocalizations.current.bulletedList,
icon: _selectionMenuIcon('bulleted_list'),
keywords: ['bulleted list', 'list', 'unordered list'],
handler: (editorState, _, __) {
@ -170,7 +170,7 @@ final List<SelectionMenuItem> _defaultSelectionMenuItems = [
},
),
SelectionMenuItem(
name: AppFlowyEditorLocalizations.current.checkbox,
name: () => AppFlowyEditorLocalizations.current.checkbox,
icon: _selectionMenuIcon('checkbox'),
keywords: ['todo list', 'list', 'checkbox list'],
handler: (editorState, _, __) {
@ -178,7 +178,7 @@ final List<SelectionMenuItem> _defaultSelectionMenuItems = [
},
),
SelectionMenuItem(
name: AppFlowyEditorLocalizations.current.quote,
name: () => AppFlowyEditorLocalizations.current.quote,
icon: _selectionMenuIcon('quote'),
keywords: ['quote', 'refer'],
handler: (editorState, _, __) {

View File

@ -6,27 +6,54 @@ import 'package:appflowy_editor/src/render/selection_menu/selection_menu_service
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
typedef SelectionMenuItemHandler = void Function(
EditorState editorState,
SelectionMenuService menuService,
BuildContext context,
);
/// Selection Menu Item
class SelectionMenuItem {
SelectionMenuItem({
required this.name,
required this.icon,
required this.keywords,
required this.handler,
});
required SelectionMenuItemHandler handler,
}) {
this.handler = (editorState, menuService, context) {
_deleteToSlash(editorState);
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
handler(editorState, menuService, context);
});
};
}
final String name;
final String Function() name;
final Widget icon;
/// Customizes keywords for item.
///
/// The keywords are used to quickly retrieve items.
final List<String> keywords;
final void Function(
EditorState editorState,
SelectionMenuService menuService,
BuildContext context,
) handler;
late final SelectionMenuItemHandler handler;
void _deleteToSlash(EditorState editorState) {
final selectionService = editorState.service.selectionService;
final selection = selectionService.currentSelection.value;
final nodes = selectionService.currentSelectedNodes;
if (selection != null && nodes.length == 1) {
final node = nodes.first as TextNode;
final end = selection.start.offset;
final start = node.toRawString().substring(0, end).lastIndexOf('/');
TransactionBuilder(editorState)
..deleteText(
node,
start,
selection.start.offset - start,
)
..commit();
}
}
}
class SelectionMenuWidget extends StatefulWidget {
@ -204,11 +231,8 @@ class _SelectionMenuWidgetState extends State<SelectionMenuWidget> {
if (event.logicalKey == LogicalKeyboardKey.enter) {
if (0 <= _selectedIndex && _selectedIndex < _showingItems.length) {
_deleteLastCharacters(length: keyword.length + 1);
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
_showingItems[_selectedIndex]
.handler(widget.editorState, widget.menuService, context);
});
_showingItems[_selectedIndex]
.handler(widget.editorState, widget.menuService, context);
return KeyEventResult.handled;
}
} else if (event.logicalKey == LogicalKeyboardKey.escape) {

View File

@ -17,7 +17,7 @@ void main() async {
final menuService = _TestSelectionMenuService();
const icon = Icon(Icons.abc);
final item = SelectionMenuItem(
name: 'example',
name: () => 'example',
icon: icon,
keywords: ['example A', 'example B'],
handler: (editorState, menuService, context) {

View File

@ -13,29 +13,40 @@ void main() async {
});
group('selection_menu_widget.dart', () {
// const i = defaultSelectionMenuItems.length;
//
// Because the `defaultSelectionMenuItems` uses localization,
// and the MaterialApp has not been initialized at the time of getting the value,
// it will crash.
//
// Use const value temporarily instead.
const i = 7;
testWidgets('Selects number.$i item in selection menu', (tester) async {
final editor = await _prepare(tester);
for (var j = 0; j < i; j++) {
await editor.pressLogicKey(LogicalKeyboardKey.arrowDown);
}
for (var i = 0; i < defaultSelectionMenuItems.length; i += 1) {
testWidgets('Selects number.$i item in selection menu with enter', (
tester) async {
final editor = await _prepare(tester);
for (var j = 0; j < i; j++) {
await editor.pressLogicKey(LogicalKeyboardKey.arrowDown);
}
await editor.pressLogicKey(LogicalKeyboardKey.enter);
expect(
find.byType(SelectionMenuWidget, skipOffstage: false),
findsNothing,
);
if (defaultSelectionMenuItems[i].name != 'Image') {
await _testDefaultSelectionMenuItems(i, editor);
}
});
await editor.pressLogicKey(LogicalKeyboardKey.enter);
expect(
find.byType(SelectionMenuWidget, skipOffstage: false),
findsNothing,
);
if (defaultSelectionMenuItems[i].name() != 'Image') {
await _testDefaultSelectionMenuItems(i, editor);
}
});
testWidgets('Selects number.$i item in selection menu with click', (
tester) async {
final editor = await _prepare(tester);
await tester.tap(find.byType(SelectionMenuItemWidget).at(i));
await tester.pumpAndSettle();
expect(
find.byType(SelectionMenuWidget, skipOffstage: false),
findsNothing,
);
if (defaultSelectionMenuItems[i].name() != 'Image') {
await _testDefaultSelectionMenuItems(i, editor);
}
});
}
testWidgets('Search item in selection menu util no results',
(tester) async {
@ -138,23 +149,27 @@ Future<void> _testDefaultSelectionMenuItems(
int index, EditorWidgetTester editor) async {
expect(editor.documentLength, 4);
expect(editor.documentSelection, Selection.single(path: [2], startOffset: 0));
expect((editor.nodeAtPath([1]) as TextNode).toRawString(), 'Welcome to Appflowy 😁');
final node = editor.nodeAtPath([2]);
final item = defaultSelectionMenuItems[index];
if (item.name == 'Text') {
final itemName = item.name();
if (itemName == 'Text') {
expect(node?.subtype == null, true);
} else if (item.name == 'Heading 1') {
} else if (itemName == 'Heading 1') {
expect(node?.subtype, BuiltInAttributeKey.heading);
expect(node?.attributes.heading, BuiltInAttributeKey.h1);
} else if (item.name == 'Heading 2') {
} else if (itemName == 'Heading 2') {
expect(node?.subtype, BuiltInAttributeKey.heading);
expect(node?.attributes.heading, BuiltInAttributeKey.h2);
} else if (item.name == 'Heading 3') {
} else if (itemName == 'Heading 3') {
expect(node?.subtype, BuiltInAttributeKey.heading);
expect(node?.attributes.heading, BuiltInAttributeKey.h3);
} else if (item.name == 'Bulleted list') {
} else if (itemName == 'Bulleted list') {
expect(node?.subtype, BuiltInAttributeKey.bulletedList);
} else if (item.name == 'Checkbox') {
} else if (itemName == 'Checkbox') {
expect(node?.subtype, BuiltInAttributeKey.checkbox);
expect(node?.attributes.check, false);
} else if (itemName == 'Quote') {
expect(node?.subtype, BuiltInAttributeKey.quote);
}
}