feat: add command shortcut keys to convert text to title (#921)
* feat: add command shortcut keys to convert text to title * fix: use const values from heading block keys * chore: add test cases for command * fix: comments and nomenclature * chore: add appropriate description
This commit is contained in:
parent
432b797914
commit
8d3f5b904b
6 changed files with 299 additions and 1 deletions
|
|
@ -178,5 +178,9 @@
|
|||
"cmdToggleStrikethrough": "toggle strikethrough",
|
||||
"cmdToggleTodoList": "toggle the todo list",
|
||||
"cmdToggleUnderline": "toggle underline",
|
||||
"cmdUndo": "undo"
|
||||
"cmdUndo": "undo",
|
||||
"cmdToggleH1": "toggle H1",
|
||||
"cmdToggleH2": "toggle H2",
|
||||
"cmdToggleH3": "toggle H3",
|
||||
"cmdToggleBody": "toggle body"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,95 @@
|
|||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
final List<CommandShortcutEvent> toggleHeadingCommands = [
|
||||
toggleH1,
|
||||
toggleH2,
|
||||
toggleH3,
|
||||
toggleBody,
|
||||
];
|
||||
|
||||
/// Markdown key event.
|
||||
///
|
||||
/// Cmd / Ctrl + Shift + T: toggle H1
|
||||
/// Cmd / Ctrl + Shift + G: toggle H2
|
||||
/// Cmd / Ctrl + Shift + J: toggle H3
|
||||
/// Cmd / Ctrl + Shift + B: toggle Body
|
||||
/// - support
|
||||
/// - desktop
|
||||
/// - web
|
||||
///
|
||||
final CommandShortcutEvent toggleH1 = CommandShortcutEvent(
|
||||
key: 'toggle H1',
|
||||
getDescription: () => AppFlowyEditorL10n.current.cmdToggleH1,
|
||||
command: 'ctrl+shift+t',
|
||||
macOSCommand: 'cmd+shift+t',
|
||||
handler: (editorState) => _toggleAttribute(
|
||||
editorState,
|
||||
1,
|
||||
),
|
||||
);
|
||||
|
||||
final CommandShortcutEvent toggleH2 = CommandShortcutEvent(
|
||||
key: 'toggle H2',
|
||||
getDescription: () => AppFlowyEditorL10n.current.cmdToggleH2,
|
||||
command: 'ctrl+shift+g',
|
||||
macOSCommand: 'cmd+shift+g',
|
||||
handler: (editorState) => _toggleAttribute(
|
||||
editorState,
|
||||
2,
|
||||
),
|
||||
);
|
||||
|
||||
final CommandShortcutEvent toggleH3 = CommandShortcutEvent(
|
||||
key: 'toggle H3',
|
||||
getDescription: () => AppFlowyEditorL10n.current.cmdToggleH3,
|
||||
command: 'ctrl+shift+j',
|
||||
macOSCommand: 'cmd+shift+j',
|
||||
handler: (editorState) => _toggleAttribute(
|
||||
editorState,
|
||||
3,
|
||||
),
|
||||
);
|
||||
|
||||
final CommandShortcutEvent toggleBody = CommandShortcutEvent(
|
||||
key: 'toggle Body',
|
||||
getDescription: () => AppFlowyEditorL10n.current.cmdToggleBody,
|
||||
command: 'ctrl+shift+b',
|
||||
macOSCommand: 'cmd+shift+b',
|
||||
handler: (editorState) => _toggleAttribute(editorState, 1, true),
|
||||
);
|
||||
|
||||
KeyEventResult _toggleAttribute(
|
||||
EditorState editorState,
|
||||
int? level, [
|
||||
bool? isBody,
|
||||
]) {
|
||||
final selection = editorState.selection;
|
||||
if (selection == null) {
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
|
||||
final node = editorState.getNodeAtPath(selection.start.path)!;
|
||||
bool isHeading = isBody ??
|
||||
node.type == HeadingBlockKeys.type &&
|
||||
node.attributes[HeadingBlockKeys.level] == level;
|
||||
|
||||
final delta = (node.delta ?? Delta()).toJson();
|
||||
|
||||
editorState.formatNode(
|
||||
selection,
|
||||
(node) => node.copyWith(
|
||||
type: isHeading ? ParagraphBlockKeys.type : HeadingBlockKeys.type,
|
||||
attributes: {
|
||||
HeadingBlockKeys.level: level,
|
||||
blockComponentBackgroundColor:
|
||||
node.attributes[blockComponentBackgroundColor],
|
||||
blockComponentTextDirection:
|
||||
node.attributes[blockComponentTextDirection],
|
||||
blockComponentDelta: delta,
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor/src/editor/block_component/heading_block_component/heading_command_shortcut.dart';
|
||||
import 'package:appflowy_editor/src/editor/util/platform_extension.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
|
@ -129,6 +130,7 @@ final List<CommandShortcutEvent> standardCommandShortcutEvents = [
|
|||
//
|
||||
toggleTodoListCommand,
|
||||
...toggleMarkdownCommands,
|
||||
...toggleHeadingCommands,
|
||||
toggleHighlightCommand,
|
||||
showLinkMenuCommand,
|
||||
openInlineLinkCommand,
|
||||
|
|
|
|||
|
|
@ -156,8 +156,12 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
"cmdTableStopAtTheBeginningOfTheCell":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Stop at the beginning of the cell"),
|
||||
"cmdToggleBody": MessageLookupByLibrary.simpleMessage("toggle body"),
|
||||
"cmdToggleBold": MessageLookupByLibrary.simpleMessage("toggle bold"),
|
||||
"cmdToggleCode": MessageLookupByLibrary.simpleMessage("toggle code"),
|
||||
"cmdToggleH1": MessageLookupByLibrary.simpleMessage("toggle H1"),
|
||||
"cmdToggleH2": MessageLookupByLibrary.simpleMessage("toggle H2"),
|
||||
"cmdToggleH3": MessageLookupByLibrary.simpleMessage("toggle H3"),
|
||||
"cmdToggleHighlight":
|
||||
MessageLookupByLibrary.simpleMessage("toggle highlight"),
|
||||
"cmdToggleItalic":
|
||||
|
|
|
|||
|
|
@ -1840,6 +1840,46 @@ class AppFlowyEditorLocalizations {
|
|||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `toggle H1`
|
||||
String get cmdToggleH1 {
|
||||
return Intl.message(
|
||||
'toggle H1',
|
||||
name: 'cmdToggleH1',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `toggle H2`
|
||||
String get cmdToggleH2 {
|
||||
return Intl.message(
|
||||
'toggle H2',
|
||||
name: 'cmdToggleH2',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `toggle H3`
|
||||
String get cmdToggleH3 {
|
||||
return Intl.message(
|
||||
'toggle H3',
|
||||
name: 'cmdToggleH3',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `toggle body`
|
||||
String get cmdToggleBody {
|
||||
return Intl.message(
|
||||
'toggle body',
|
||||
name: 'cmdToggleBody',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AppLocalizationDelegate
|
||||
|
|
|
|||
|
|
@ -0,0 +1,153 @@
|
|||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor/src/editor/block_component/heading_block_component/heading_command_shortcut.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../../util/util.dart';
|
||||
|
||||
void main() async {
|
||||
setUpAll(() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
});
|
||||
group('heading_command_shortcut_test.dart', () {
|
||||
const text = 'Welcome to AppFlowy Editor 🔥!';
|
||||
|
||||
test('toggle H1 from paragraph', () {
|
||||
final document = Document.blank()
|
||||
..addParagraph(
|
||||
initialText: text,
|
||||
);
|
||||
|
||||
final editorState = EditorState(document: document);
|
||||
|
||||
Node node = editorState.getNodeAtPath([0])!;
|
||||
expect(node.type, ParagraphBlockKeys.type);
|
||||
|
||||
final selection = Selection.collapsed(
|
||||
Position(
|
||||
path: [0],
|
||||
offset: 1,
|
||||
),
|
||||
);
|
||||
|
||||
editorState.selection = selection;
|
||||
toggleH1.execute(editorState);
|
||||
|
||||
// Before
|
||||
// Welcome to AppFlowy Editor 🔥!
|
||||
// After
|
||||
// <H1>Welcome to AppFlowy Editor 🔥! <H1>
|
||||
node = editorState.getNodeAtPath([0])!;
|
||||
expect(node.type, HeadingBlockKeys.type);
|
||||
expect(node.level, 1);
|
||||
|
||||
toggleH1.execute(editorState);
|
||||
// Before
|
||||
// <H1>Welcome to AppFlowy Editor 🔥! <H1>
|
||||
// After
|
||||
// Welcome to AppFlowy Editor 🔥!
|
||||
expect(editorState.getNodeAtPath([0])!.type, ParagraphBlockKeys.type);
|
||||
});
|
||||
|
||||
test('toggle H2 from paragraph', () {
|
||||
final document = Document.blank()
|
||||
..addParagraph(
|
||||
initialText: text,
|
||||
);
|
||||
|
||||
final editorState = EditorState(document: document);
|
||||
|
||||
Node node = editorState.getNodeAtPath([0])!;
|
||||
expect(node.type, ParagraphBlockKeys.type);
|
||||
|
||||
final selection = Selection.collapsed(
|
||||
Position(
|
||||
path: [0],
|
||||
offset: 1,
|
||||
),
|
||||
);
|
||||
|
||||
editorState.selection = selection;
|
||||
toggleH2.execute(editorState);
|
||||
|
||||
// Before
|
||||
// Welcome to AppFlowy Editor 🔥!
|
||||
// After
|
||||
// <H2>Welcome to AppFlowy Editor 🔥! <H2>
|
||||
node = editorState.getNodeAtPath([0])!;
|
||||
expect(node.type, HeadingBlockKeys.type);
|
||||
expect(node.attributes[HeadingBlockKeys.level], 2);
|
||||
|
||||
toggleH2.execute(editorState);
|
||||
// Before
|
||||
// <H2>Welcome to AppFlowy Editor 🔥! <H2>
|
||||
// After
|
||||
// Welcome to AppFlowy Editor 🔥!
|
||||
expect(editorState.getNodeAtPath([0])!.type, ParagraphBlockKeys.type);
|
||||
});
|
||||
|
||||
test('toggle H3 from paragraph', () {
|
||||
final document = Document.blank()
|
||||
..addParagraph(
|
||||
initialText: text,
|
||||
);
|
||||
|
||||
final editorState = EditorState(document: document);
|
||||
|
||||
Node node = editorState.getNodeAtPath([0])!;
|
||||
expect(node.type, ParagraphBlockKeys.type);
|
||||
|
||||
final selection = Selection.collapsed(
|
||||
Position(
|
||||
path: [0],
|
||||
offset: 1,
|
||||
),
|
||||
);
|
||||
|
||||
editorState.selection = selection;
|
||||
toggleH3.execute(editorState);
|
||||
|
||||
// Before
|
||||
// Welcome to AppFlowy Editor 🔥!
|
||||
// After
|
||||
// <H3>Welcome to AppFlowy Editor 🔥! <H3>
|
||||
node = editorState.getNodeAtPath([0])!;
|
||||
expect(node.type, HeadingBlockKeys.type);
|
||||
expect(node.attributes[HeadingBlockKeys.level], 3);
|
||||
|
||||
toggleH3.execute(editorState);
|
||||
// Before
|
||||
// <H3>Welcome to AppFlowy Editor 🔥! <H3>
|
||||
// After
|
||||
// Welcome to AppFlowy Editor 🔥!
|
||||
expect(editorState.getNodeAtPath([0])!.type, ParagraphBlockKeys.type);
|
||||
});
|
||||
|
||||
test('toggle body type from heading type', () {
|
||||
final document = Document.blank()
|
||||
..insert([0], [headingNode(level: 2, text: text)]);
|
||||
|
||||
final editorState = EditorState(document: document);
|
||||
|
||||
Node node = editorState.getNodeAtPath([0])!;
|
||||
expect(node.type, HeadingBlockKeys.type);
|
||||
expect(node.attributes[HeadingBlockKeys.level], 2);
|
||||
|
||||
final selection = Selection.collapsed(
|
||||
Position(
|
||||
path: [0],
|
||||
offset: 1,
|
||||
),
|
||||
);
|
||||
|
||||
editorState.selection = selection;
|
||||
toggleBody.execute(editorState);
|
||||
|
||||
// Before
|
||||
// <H2>Welcome to AppFlowy Editor 🔥! <H2>
|
||||
// After
|
||||
// Welcome to AppFlowy Editor 🔥!
|
||||
node = editorState.getNodeAtPath([0])!;
|
||||
expect(node.type, ParagraphBlockKeys.type);
|
||||
});
|
||||
});
|
||||
}
|
||||
Loading…
Reference in a new issue