mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
[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:
parent
84200ddda4
commit
95ec607482
@ -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;
|
||||||
|
};
|
@ -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/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/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/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:appflowy_editor/src/service/shortcut_event/shortcut_event.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
@ -159,6 +160,13 @@ List<ShortcutEvent> builtInShortcutEvents = [
|
|||||||
linuxCommand: 'ctrl+u',
|
linuxCommand: 'ctrl+u',
|
||||||
handler: formatUnderlineEventHandler,
|
handler: formatUnderlineEventHandler,
|
||||||
),
|
),
|
||||||
|
ShortcutEvent(
|
||||||
|
key: 'Toggle Checkbox',
|
||||||
|
command: 'meta+enter',
|
||||||
|
windowsCommand: 'ctrl+enter',
|
||||||
|
linuxCommand: 'ctrl+enter',
|
||||||
|
handler: toggleCheckbox,
|
||||||
|
),
|
||||||
ShortcutEvent(
|
ShortcutEvent(
|
||||||
key: 'Format strikethrough',
|
key: 'Format strikethrough',
|
||||||
command: 'meta+shift+s',
|
command: 'meta+shift+s',
|
||||||
|
@ -136,6 +136,9 @@ extension on LogicalKeyboardKey {
|
|||||||
if (this == LogicalKeyboardKey.keyH) {
|
if (this == LogicalKeyboardKey.keyH) {
|
||||||
return PhysicalKeyboardKey.keyH;
|
return PhysicalKeyboardKey.keyH;
|
||||||
}
|
}
|
||||||
|
if (this == LogicalKeyboardKey.keyQ) {
|
||||||
|
return PhysicalKeyboardKey.keyQ;
|
||||||
|
}
|
||||||
if (this == LogicalKeyboardKey.keyZ) {
|
if (this == LogicalKeyboardKey.keyZ) {
|
||||||
return PhysicalKeyboardKey.keyZ;
|
return PhysicalKeyboardKey.keyZ;
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user