[FR] Shortcut for toggling checkbox (#1817)

* feat: shortcut for toggling checkbox

* refactor: separate checkbox event handler

* test: chechbox event handler

* chore: remove unused imports

* refactor: command to ctrl and enter

* refactor: handler to use transactions

* test: checkbox event handler

* chore: remove unused import

* refactor: simplify handler logic
This commit is contained in:
Mayur Mahajan 2023-02-09 11:09:25 +05:30 committed by GitHub
parent 84200ddda4
commit 95ec607482
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 290 additions and 0 deletions

View File

@ -0,0 +1,38 @@
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/material.dart';
ShortcutEventHandler toggleCheckbox = (editorState, event) {
final selection = editorState.service.selectionService.currentSelection.value;
final nodes = editorState.service.selectionService.currentSelectedNodes;
final checkboxTextNodes = nodes
.where(
(element) =>
element is TextNode &&
element.subtype == BuiltInAttributeKey.checkbox,
)
.toList(growable: false);
if (selection == null || checkboxTextNodes.isEmpty) {
return KeyEventResult.ignored;
}
bool isAllCheckboxesChecked = checkboxTextNodes
.every((node) => node.attributes[BuiltInAttributeKey.checkbox] == true);
final transaction = editorState.transaction;
transaction.afterSelection = selection;
if (isAllCheckboxesChecked) {
//if all the checkboxes are checked, then make all of the checkboxes unchecked
for (final node in checkboxTextNodes) {
transaction.updateNode(node, {BuiltInAttributeKey.checkbox: false});
}
} else {
//If any one of the checkboxes is unchecked then make all checkboxes checked
for (final node in checkboxTextNodes) {
transaction.updateNode(node, {BuiltInAttributeKey.checkbox: true});
}
}
editorState.apply(transaction);
return KeyEventResult.handled;
};

View File

@ -14,6 +14,7 @@ import 'package:appflowy_editor/src/service/internal_key_event_handlers/format_s
import 'package:appflowy_editor/src/service/internal_key_event_handlers/space_on_web_handler.dart';
import 'package:appflowy_editor/src/service/internal_key_event_handlers/tab_handler.dart';
import 'package:appflowy_editor/src/service/internal_key_event_handlers/whitespace_handler.dart';
import 'package:appflowy_editor/src/service/internal_key_event_handlers/checkbox_event_handler.dart';
import 'package:appflowy_editor/src/service/shortcut_event/shortcut_event.dart';
import 'package:flutter/foundation.dart';
@ -159,6 +160,13 @@ List<ShortcutEvent> builtInShortcutEvents = [
linuxCommand: 'ctrl+u',
handler: formatUnderlineEventHandler,
),
ShortcutEvent(
key: 'Toggle Checkbox',
command: 'meta+enter',
windowsCommand: 'ctrl+enter',
linuxCommand: 'ctrl+enter',
handler: toggleCheckbox,
),
ShortcutEvent(
key: 'Format strikethrough',
command: 'meta+shift+s',

View File

@ -136,6 +136,9 @@ extension on LogicalKeyboardKey {
if (this == LogicalKeyboardKey.keyH) {
return PhysicalKeyboardKey.keyH;
}
if (this == LogicalKeyboardKey.keyQ) {
return PhysicalKeyboardKey.keyQ;
}
if (this == LogicalKeyboardKey.keyZ) {
return PhysicalKeyboardKey.keyZ;
}

View File

@ -0,0 +1,241 @@
import 'dart:io';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_editor/src/service/shortcut_event/built_in_shortcut_events.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('checkbox_event_handler_test.dart', () {
testWidgets('toggle checkbox with shortcut ctrl+enter', (tester) async {
const text = 'Checkbox1';
final editor = tester.editor
..insertTextNode(
'',
attributes: {
BuiltInAttributeKey.subtype: BuiltInAttributeKey.checkbox,
BuiltInAttributeKey.checkbox: false,
},
delta: Delta(
operations: [TextInsert(text)],
),
);
await editor.startTesting();
await editor.updateSelection(
Selection.single(path: [0], startOffset: text.length),
);
final checkboxNode = editor.nodeAtPath([0]) as TextNode;
expect(checkboxNode.subtype, BuiltInAttributeKey.checkbox);
expect(checkboxNode.attributes[BuiltInAttributeKey.checkbox], false);
for (final event in builtInShortcutEvents) {
if (event.key == 'Toggle Checkbox') {
event.updateCommand(
windowsCommand: 'ctrl+enter',
linuxCommand: 'ctrl+enter',
macOSCommand: 'meta+enter',
);
}
}
if (Platform.isWindows || Platform.isLinux) {
await editor.pressLogicKey(
LogicalKeyboardKey.enter,
isControlPressed: true,
);
} else {
await editor.pressLogicKey(
LogicalKeyboardKey.enter,
isMetaPressed: true,
);
}
expect(checkboxNode.attributes[BuiltInAttributeKey.checkbox], true);
await editor.updateSelection(
Selection.single(path: [0], startOffset: text.length),
);
if (Platform.isWindows || Platform.isLinux) {
await editor.pressLogicKey(
LogicalKeyboardKey.enter,
isControlPressed: true,
);
} else {
await editor.pressLogicKey(
LogicalKeyboardKey.enter,
isMetaPressed: true,
);
}
expect(checkboxNode.attributes[BuiltInAttributeKey.checkbox], false);
});
testWidgets(
'test if all checkboxes get unchecked after toggling them, if all of them were already checked',
(tester) async {
const text = 'Checkbox';
final editor = tester.editor
..insertTextNode(
'',
attributes: {
BuiltInAttributeKey.subtype: BuiltInAttributeKey.checkbox,
BuiltInAttributeKey.checkbox: true,
},
delta: Delta(
operations: [TextInsert(text)],
),
)
..insertTextNode(
'',
attributes: {
BuiltInAttributeKey.subtype: BuiltInAttributeKey.checkbox,
BuiltInAttributeKey.checkbox: true,
},
delta: Delta(
operations: [TextInsert(text)],
),
)
..insertTextNode(
'',
attributes: {
BuiltInAttributeKey.subtype: BuiltInAttributeKey.checkbox,
BuiltInAttributeKey.checkbox: true,
},
delta: Delta(
operations: [TextInsert(text)],
),
);
await editor.startTesting();
await editor.updateSelection(
Selection.single(path: [0], startOffset: text.length),
);
final nodes =
editor.editorState.service.selectionService.currentSelectedNodes;
final checkboxTextNodes = nodes
.where(
(element) =>
element is TextNode &&
element.subtype == BuiltInAttributeKey.checkbox,
)
.toList(growable: false);
for (final node in checkboxTextNodes) {
expect(node.attributes[BuiltInAttributeKey.checkbox], true);
}
for (final event in builtInShortcutEvents) {
if (event.key == 'Toggle Checkbox') {
event.updateCommand(
windowsCommand: 'ctrl+enter',
linuxCommand: 'ctrl+enter',
macOSCommand: 'meta+enter',
);
}
}
if (Platform.isWindows || Platform.isLinux) {
await editor.pressLogicKey(
LogicalKeyboardKey.enter,
isControlPressed: true,
);
} else {
await editor.pressLogicKey(
LogicalKeyboardKey.enter,
isMetaPressed: true,
);
}
for (final node in checkboxTextNodes) {
expect(node.attributes[BuiltInAttributeKey.checkbox], false);
}
});
testWidgets(
'test if all checkboxes get checked after toggling them, if any one of them were already checked',
(tester) async {
const text = 'Checkbox';
final editor = tester.editor
..insertTextNode(
'',
attributes: {
BuiltInAttributeKey.subtype: BuiltInAttributeKey.checkbox,
BuiltInAttributeKey.checkbox: false,
},
delta: Delta(
operations: [TextInsert(text)],
),
)
..insertTextNode(
'',
attributes: {
BuiltInAttributeKey.subtype: BuiltInAttributeKey.checkbox,
BuiltInAttributeKey.checkbox: true,
},
delta: Delta(
operations: [TextInsert(text)],
),
)
..insertTextNode(
'',
attributes: {
BuiltInAttributeKey.subtype: BuiltInAttributeKey.checkbox,
BuiltInAttributeKey.checkbox: false,
},
delta: Delta(
operations: [TextInsert(text)],
),
);
await editor.startTesting();
await editor.updateSelection(
Selection.single(path: [0], startOffset: text.length),
);
final nodes =
editor.editorState.service.selectionService.currentSelectedNodes;
final checkboxTextNodes = nodes
.where(
(element) =>
element is TextNode &&
element.subtype == BuiltInAttributeKey.checkbox,
)
.toList(growable: false);
for (final event in builtInShortcutEvents) {
if (event.key == 'Toggle Checkbox') {
event.updateCommand(
windowsCommand: 'ctrl+enter',
linuxCommand: 'ctrl+enter',
macOSCommand: 'meta+enter',
);
}
}
if (Platform.isWindows || Platform.isLinux) {
await editor.pressLogicKey(
LogicalKeyboardKey.enter,
isControlPressed: true,
);
} else {
await editor.pressLogicKey(
LogicalKeyboardKey.enter,
isMetaPressed: true,
);
}
for (final node in checkboxTextNodes) {
expect(node.attributes[BuiltInAttributeKey.checkbox], true);
}
});
});
}