mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Improve Editing and Navigating shortcuts with Ctrl/Meta (#1845)
* feat: handler for deleting a word * chore: typo * test: ctrl and backspace to delete word * feat: add ctrl alt arrows to select words * fix: remove print statement * fix: remove additional shortcut * fix: handle nodes empty case * test: edge cases with delete word * fix: press meta on macos
This commit is contained in:
parent
64902f763b
commit
ea9d8d03ad
@ -322,6 +322,33 @@ ShortcutEventHandler cursorRightWordSelect = (editorState, event) {
|
||||
return KeyEventResult.handled;
|
||||
};
|
||||
|
||||
ShortcutEventHandler cursorLeftWordDelete = (editorState, event) {
|
||||
final textNodes = editorState.service.selectionService.currentSelectedNodes
|
||||
.whereType<TextNode>();
|
||||
final selection = editorState.service.selectionService.currentSelection.value;
|
||||
|
||||
if (textNodes.isEmpty || selection == null) {
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
|
||||
final textNode = textNodes.first;
|
||||
|
||||
final startOfWord =
|
||||
selection.end.goLeft(editorState, selectionRange: _SelectionRange.word);
|
||||
|
||||
if (startOfWord == null) {
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
|
||||
final transaction = editorState.transaction;
|
||||
transaction.deleteText(
|
||||
textNode, startOfWord.offset, selection.end.offset - startOfWord.offset);
|
||||
|
||||
editorState.apply(transaction);
|
||||
|
||||
return KeyEventResult.handled;
|
||||
};
|
||||
|
||||
enum _SelectionRange {
|
||||
character,
|
||||
word,
|
||||
|
@ -253,8 +253,8 @@ void _deleteTextNodes(
|
||||
final last = textNodes.last;
|
||||
var content = textNodes.last.toPlainText();
|
||||
content = content.substring(selection.end.offset, content.length);
|
||||
// Merge the fist and the last text node content,
|
||||
// and delete the all nodes expect for the first.
|
||||
// Merge the first and the last text node content,
|
||||
// and delete all the nodes except for the first.
|
||||
transaction
|
||||
..deleteNodes(textNodes.sublist(1))
|
||||
..mergeText(
|
||||
|
@ -52,13 +52,24 @@ List<ShortcutEvent> builtInShortcutEvents = [
|
||||
ShortcutEvent(
|
||||
key: 'Cursor down select',
|
||||
command: 'shift+alt+arrow left',
|
||||
windowsCommand: 'shift+alt+arrow left',
|
||||
linuxCommand: 'shift+alt+arrow left',
|
||||
handler: cursorLeftWordSelect,
|
||||
),
|
||||
ShortcutEvent(
|
||||
key: 'Cursor down select',
|
||||
command: 'shift+alt+arrow right',
|
||||
windowsCommand: 'shift+alt+arrow right',
|
||||
linuxCommand: 'shift+alt+arrow right',
|
||||
handler: cursorRightWordSelect,
|
||||
),
|
||||
ShortcutEvent(
|
||||
key: 'Cursor word delete',
|
||||
command: 'meta+backspace',
|
||||
windowsCommand: 'ctrl+backspace',
|
||||
linuxCommand: 'ctrl+backspace',
|
||||
handler: cursorLeftWordDelete,
|
||||
),
|
||||
ShortcutEvent(
|
||||
key: 'Cursor left select',
|
||||
command: 'shift+arrow left',
|
||||
|
@ -466,6 +466,133 @@ void main() async {
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('Presses ctrl + backspace to delete a word', (tester) async {
|
||||
List<String> words = ["Welcome", " ", "to", " ", "Appflowy", " ", "😁"];
|
||||
final text = words.join();
|
||||
final editor = tester.editor..insertTextNode(text);
|
||||
|
||||
await editor.startTesting();
|
||||
var selection = Selection.single(path: [0], startOffset: text.length);
|
||||
await editor.updateSelection(selection);
|
||||
|
||||
if (Platform.isWindows || Platform.isLinux) {
|
||||
await editor.pressLogicKey(
|
||||
LogicalKeyboardKey.backspace,
|
||||
isControlPressed: true,
|
||||
);
|
||||
} else {
|
||||
await editor.pressLogicKey(
|
||||
LogicalKeyboardKey.backspace,
|
||||
isMetaPressed: true,
|
||||
);
|
||||
}
|
||||
|
||||
//fetching all the text that is still on the editor.
|
||||
var nodes =
|
||||
editor.editorState.service.selectionService.currentSelectedNodes;
|
||||
var textNode = nodes.whereType<TextNode>().first;
|
||||
var newText = textNode.toPlainText();
|
||||
|
||||
words.removeLast();
|
||||
expect(newText, words.join());
|
||||
|
||||
if (Platform.isWindows || Platform.isLinux) {
|
||||
await editor.pressLogicKey(
|
||||
LogicalKeyboardKey.backspace,
|
||||
isControlPressed: true,
|
||||
);
|
||||
} else {
|
||||
await editor.pressLogicKey(
|
||||
LogicalKeyboardKey.backspace,
|
||||
isMetaPressed: true,
|
||||
);
|
||||
}
|
||||
|
||||
//fetching all the text that is still on the editor.
|
||||
nodes = editor.editorState.service.selectionService.currentSelectedNodes;
|
||||
textNode = nodes.whereType<TextNode>().first;
|
||||
|
||||
newText = textNode.toPlainText();
|
||||
|
||||
words.removeLast();
|
||||
expect(newText, words.join());
|
||||
|
||||
for (var i = 0; i < words.length; i++) {
|
||||
if (Platform.isWindows || Platform.isLinux) {
|
||||
await editor.pressLogicKey(
|
||||
LogicalKeyboardKey.backspace,
|
||||
isControlPressed: true,
|
||||
);
|
||||
} else {
|
||||
await editor.pressLogicKey(
|
||||
LogicalKeyboardKey.backspace,
|
||||
isMetaPressed: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
nodes = editor.editorState.service.selectionService.currentSelectedNodes;
|
||||
textNode = nodes.whereType<TextNode>().toList(growable: false).first;
|
||||
|
||||
newText = textNode.toPlainText();
|
||||
|
||||
expect(newText, '');
|
||||
});
|
||||
|
||||
testWidgets('Testing ctrl + backspace edge cases', (tester) async {
|
||||
const text = 'Welcome to Appflowy 😁';
|
||||
final editor = tester.editor..insertTextNode(text);
|
||||
|
||||
await editor.startTesting();
|
||||
var selection = Selection.single(path: [0], startOffset: 0);
|
||||
await editor.updateSelection(selection);
|
||||
|
||||
if (Platform.isWindows || Platform.isLinux) {
|
||||
await editor.pressLogicKey(
|
||||
LogicalKeyboardKey.backspace,
|
||||
isControlPressed: true,
|
||||
);
|
||||
} else {
|
||||
await editor.pressLogicKey(
|
||||
LogicalKeyboardKey.backspace,
|
||||
isMetaPressed: true,
|
||||
);
|
||||
}
|
||||
|
||||
//fetching all the text that is still on the editor.
|
||||
var nodes =
|
||||
editor.editorState.service.selectionService.currentSelectedNodes;
|
||||
var textNode = nodes.whereType<TextNode>().first;
|
||||
var newText = textNode.toPlainText();
|
||||
|
||||
//nothing happens
|
||||
expect(newText, text);
|
||||
|
||||
selection = Selection.single(path: [0], startOffset: 14);
|
||||
await editor.updateSelection(selection);
|
||||
//Welcome to App|flowy 😁
|
||||
|
||||
if (Platform.isWindows || Platform.isLinux) {
|
||||
await editor.pressLogicKey(
|
||||
LogicalKeyboardKey.backspace,
|
||||
isControlPressed: true,
|
||||
);
|
||||
} else {
|
||||
await editor.pressLogicKey(
|
||||
LogicalKeyboardKey.backspace,
|
||||
isMetaPressed: true,
|
||||
);
|
||||
}
|
||||
|
||||
//fetching all the text that is still on the editor.
|
||||
nodes = editor.editorState.service.selectionService.currentSelectedNodes;
|
||||
textNode = nodes.whereType<TextNode>().first;
|
||||
newText = textNode.toPlainText();
|
||||
|
||||
const expectedText = 'Welcome to flowy 😁';
|
||||
expect(newText, expectedText);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _testPressArrowKeyInNotCollapsedSelection(
|
||||
|
Loading…
Reference in New Issue
Block a user