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;
|
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 {
|
enum _SelectionRange {
|
||||||
character,
|
character,
|
||||||
word,
|
word,
|
||||||
|
@ -253,8 +253,8 @@ void _deleteTextNodes(
|
|||||||
final last = textNodes.last;
|
final last = textNodes.last;
|
||||||
var content = textNodes.last.toPlainText();
|
var content = textNodes.last.toPlainText();
|
||||||
content = content.substring(selection.end.offset, content.length);
|
content = content.substring(selection.end.offset, content.length);
|
||||||
// Merge the fist and the last text node content,
|
// Merge the first and the last text node content,
|
||||||
// and delete the all nodes expect for the first.
|
// and delete all the nodes except for the first.
|
||||||
transaction
|
transaction
|
||||||
..deleteNodes(textNodes.sublist(1))
|
..deleteNodes(textNodes.sublist(1))
|
||||||
..mergeText(
|
..mergeText(
|
||||||
|
@ -52,13 +52,24 @@ List<ShortcutEvent> builtInShortcutEvents = [
|
|||||||
ShortcutEvent(
|
ShortcutEvent(
|
||||||
key: 'Cursor down select',
|
key: 'Cursor down select',
|
||||||
command: 'shift+alt+arrow left',
|
command: 'shift+alt+arrow left',
|
||||||
|
windowsCommand: 'shift+alt+arrow left',
|
||||||
|
linuxCommand: 'shift+alt+arrow left',
|
||||||
handler: cursorLeftWordSelect,
|
handler: cursorLeftWordSelect,
|
||||||
),
|
),
|
||||||
ShortcutEvent(
|
ShortcutEvent(
|
||||||
key: 'Cursor down select',
|
key: 'Cursor down select',
|
||||||
command: 'shift+alt+arrow right',
|
command: 'shift+alt+arrow right',
|
||||||
|
windowsCommand: 'shift+alt+arrow right',
|
||||||
|
linuxCommand: 'shift+alt+arrow right',
|
||||||
handler: cursorRightWordSelect,
|
handler: cursorRightWordSelect,
|
||||||
),
|
),
|
||||||
|
ShortcutEvent(
|
||||||
|
key: 'Cursor word delete',
|
||||||
|
command: 'meta+backspace',
|
||||||
|
windowsCommand: 'ctrl+backspace',
|
||||||
|
linuxCommand: 'ctrl+backspace',
|
||||||
|
handler: cursorLeftWordDelete,
|
||||||
|
),
|
||||||
ShortcutEvent(
|
ShortcutEvent(
|
||||||
key: 'Cursor left select',
|
key: 'Cursor left select',
|
||||||
command: 'shift+arrow left',
|
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(
|
Future<void> _testPressArrowKeyInNotCollapsedSelection(
|
||||||
|
Loading…
Reference in New Issue
Block a user