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",
|
"cmdToggleStrikethrough": "toggle strikethrough",
|
||||||
"cmdToggleTodoList": "toggle the todo list",
|
"cmdToggleTodoList": "toggle the todo list",
|
||||||
"cmdToggleUnderline": "toggle underline",
|
"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/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:appflowy_editor/src/editor/util/platform_extension.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
@ -129,6 +130,7 @@ final List<CommandShortcutEvent> standardCommandShortcutEvents = [
|
||||||
//
|
//
|
||||||
toggleTodoListCommand,
|
toggleTodoListCommand,
|
||||||
...toggleMarkdownCommands,
|
...toggleMarkdownCommands,
|
||||||
|
...toggleHeadingCommands,
|
||||||
toggleHighlightCommand,
|
toggleHighlightCommand,
|
||||||
showLinkMenuCommand,
|
showLinkMenuCommand,
|
||||||
openInlineLinkCommand,
|
openInlineLinkCommand,
|
||||||
|
|
|
||||||
|
|
@ -156,8 +156,12 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||||
"cmdTableStopAtTheBeginningOfTheCell":
|
"cmdTableStopAtTheBeginningOfTheCell":
|
||||||
MessageLookupByLibrary.simpleMessage(
|
MessageLookupByLibrary.simpleMessage(
|
||||||
"Stop at the beginning of the cell"),
|
"Stop at the beginning of the cell"),
|
||||||
|
"cmdToggleBody": MessageLookupByLibrary.simpleMessage("toggle body"),
|
||||||
"cmdToggleBold": MessageLookupByLibrary.simpleMessage("toggle bold"),
|
"cmdToggleBold": MessageLookupByLibrary.simpleMessage("toggle bold"),
|
||||||
"cmdToggleCode": MessageLookupByLibrary.simpleMessage("toggle code"),
|
"cmdToggleCode": MessageLookupByLibrary.simpleMessage("toggle code"),
|
||||||
|
"cmdToggleH1": MessageLookupByLibrary.simpleMessage("toggle H1"),
|
||||||
|
"cmdToggleH2": MessageLookupByLibrary.simpleMessage("toggle H2"),
|
||||||
|
"cmdToggleH3": MessageLookupByLibrary.simpleMessage("toggle H3"),
|
||||||
"cmdToggleHighlight":
|
"cmdToggleHighlight":
|
||||||
MessageLookupByLibrary.simpleMessage("toggle highlight"),
|
MessageLookupByLibrary.simpleMessage("toggle highlight"),
|
||||||
"cmdToggleItalic":
|
"cmdToggleItalic":
|
||||||
|
|
|
||||||
|
|
@ -1840,6 +1840,46 @@ class AppFlowyEditorLocalizations {
|
||||||
args: [],
|
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
|
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