mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-08-16 04:41:29 +00:00
feat: support copy/cut command when the selection is collapsed (#6429)
* feat: add delete line command * test: add delete line test * test: add delete line command test * feat: support copy command when selection is collasped
This commit is contained in:
parent
af40ff8eb1
commit
a0ee47b809
@ -0,0 +1,140 @@
|
|||||||
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:integration_test/integration_test.dart';
|
||||||
|
import 'package:universal_platform/universal_platform.dart';
|
||||||
|
|
||||||
|
import '../../shared/util.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
group('document shortcuts:', () {
|
||||||
|
testWidgets('custom cut command', (tester) async {
|
||||||
|
await tester.initializeAppFlowy();
|
||||||
|
await tester.tapAnonymousSignInButton();
|
||||||
|
|
||||||
|
const pageName = 'Test Document Shortcuts';
|
||||||
|
await tester.createNewPageWithNameUnderParent(name: pageName);
|
||||||
|
|
||||||
|
// focus on the editor
|
||||||
|
await tester.tap(find.byType(AppFlowyEditor));
|
||||||
|
|
||||||
|
// mock the data
|
||||||
|
final editorState = tester.editor.getCurrentEditorState();
|
||||||
|
final transaction = editorState.transaction;
|
||||||
|
const text1 = '1. First line';
|
||||||
|
const text2 = '2. Second line';
|
||||||
|
transaction.insertNodes([
|
||||||
|
0,
|
||||||
|
], [
|
||||||
|
paragraphNode(text: text1),
|
||||||
|
paragraphNode(text: text2),
|
||||||
|
]);
|
||||||
|
await editorState.apply(transaction);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// focus on the end of the first line
|
||||||
|
await tester.editor.updateSelection(
|
||||||
|
Selection.collapsed(
|
||||||
|
Position(path: [0], offset: text1.length),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
// press the keybinding
|
||||||
|
await tester.simulateKeyEvent(
|
||||||
|
LogicalKeyboardKey.keyX,
|
||||||
|
isControlPressed: !UniversalPlatform.isMacOS,
|
||||||
|
isMetaPressed: UniversalPlatform.isMacOS,
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// check the clipboard
|
||||||
|
final clipboard = await Clipboard.getData(Clipboard.kTextPlain);
|
||||||
|
expect(
|
||||||
|
clipboard?.text,
|
||||||
|
equals(text1),
|
||||||
|
);
|
||||||
|
|
||||||
|
final node = tester.editor.getNodeAtPath([0]);
|
||||||
|
expect(
|
||||||
|
node.delta?.toPlainText(),
|
||||||
|
equals(text2),
|
||||||
|
);
|
||||||
|
|
||||||
|
// select the whole line
|
||||||
|
await tester.editor.updateSelection(
|
||||||
|
Selection.single(
|
||||||
|
path: [0],
|
||||||
|
startOffset: 0,
|
||||||
|
endOffset: text2.length,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// press the keybinding
|
||||||
|
await tester.simulateKeyEvent(
|
||||||
|
LogicalKeyboardKey.keyX,
|
||||||
|
isControlPressed: !UniversalPlatform.isMacOS,
|
||||||
|
isMetaPressed: UniversalPlatform.isMacOS,
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// all the text should be deleted
|
||||||
|
expect(
|
||||||
|
node.delta?.toPlainText(),
|
||||||
|
equals(''),
|
||||||
|
);
|
||||||
|
|
||||||
|
final clipboard2 = await Clipboard.getData(Clipboard.kTextPlain);
|
||||||
|
expect(
|
||||||
|
clipboard2?.text,
|
||||||
|
equals(text2),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'custom copy command - copy whole line when selection is collapsed',
|
||||||
|
(tester) async {
|
||||||
|
await tester.initializeAppFlowy();
|
||||||
|
await tester.tapAnonymousSignInButton();
|
||||||
|
|
||||||
|
const pageName = 'Test Document Shortcuts';
|
||||||
|
await tester.createNewPageWithNameUnderParent(name: pageName);
|
||||||
|
|
||||||
|
// focus on the editor
|
||||||
|
await tester.tap(find.byType(AppFlowyEditor));
|
||||||
|
|
||||||
|
// mock the data
|
||||||
|
final editorState = tester.editor.getCurrentEditorState();
|
||||||
|
final transaction = editorState.transaction;
|
||||||
|
const text1 = '1. First line';
|
||||||
|
transaction.insertNodes([
|
||||||
|
0,
|
||||||
|
], [
|
||||||
|
paragraphNode(text: text1),
|
||||||
|
]);
|
||||||
|
await editorState.apply(transaction);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// focus on the end of the first line
|
||||||
|
await tester.editor.updateSelection(
|
||||||
|
Selection.collapsed(
|
||||||
|
Position(path: [0], offset: text1.length),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
// press the keybinding
|
||||||
|
await tester.simulateKeyEvent(
|
||||||
|
LogicalKeyboardKey.keyC,
|
||||||
|
isControlPressed: !UniversalPlatform.isMacOS,
|
||||||
|
isMetaPressed: UniversalPlatform.isMacOS,
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// check the clipboard
|
||||||
|
final clipboard = await Clipboard.getData(Clipboard.kTextPlain);
|
||||||
|
expect(
|
||||||
|
clipboard?.text,
|
||||||
|
equals(text1),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -5,10 +5,11 @@ import 'document_codeblock_paste_test.dart' as document_codeblock_paste_test;
|
|||||||
import 'document_copy_and_paste_test.dart' as document_copy_and_paste_test;
|
import 'document_copy_and_paste_test.dart' as document_copy_and_paste_test;
|
||||||
import 'document_create_and_delete_test.dart'
|
import 'document_create_and_delete_test.dart'
|
||||||
as document_create_and_delete_test;
|
as document_create_and_delete_test;
|
||||||
import 'document_option_action_test.dart' as document_option_action_test;
|
|
||||||
import 'document_inline_page_reference_test.dart'
|
import 'document_inline_page_reference_test.dart'
|
||||||
as document_inline_page_reference_test;
|
as document_inline_page_reference_test;
|
||||||
import 'document_more_actions_test.dart' as document_more_actions_test;
|
import 'document_more_actions_test.dart' as document_more_actions_test;
|
||||||
|
import 'document_option_action_test.dart' as document_option_action_test;
|
||||||
|
import 'document_shortcuts_test.dart' as document_shortcuts_test;
|
||||||
import 'document_text_direction_test.dart' as document_text_direction_test;
|
import 'document_text_direction_test.dart' as document_text_direction_test;
|
||||||
import 'document_with_cover_image_test.dart' as document_with_cover_image_test;
|
import 'document_with_cover_image_test.dart' as document_with_cover_image_test;
|
||||||
import 'document_with_database_test.dart' as document_with_database_test;
|
import 'document_with_database_test.dart' as document_with_database_test;
|
||||||
@ -45,4 +46,5 @@ void startTesting() {
|
|||||||
document_inline_page_reference_test.main();
|
document_inline_page_reference_test.main();
|
||||||
document_more_actions_test.main();
|
document_more_actions_test.main();
|
||||||
document_with_file_test.main();
|
document_with_file_test.main();
|
||||||
|
document_shortcuts_test.main();
|
||||||
}
|
}
|
||||||
|
@ -1,30 +1,21 @@
|
|||||||
import 'dart:ui' as ui;
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
|
||||||
import 'package:appflowy/plugins/document/application/document_bloc.dart';
|
import 'package:appflowy/plugins/document/application/document_bloc.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_configuration.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_configuration.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/align_toolbar_item/custom_text_align_command.dart';
|
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/background_color/theme_background_color.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/background_color/theme_background_color.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/format_arrow_character.dart';
|
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/page_reference_commands.dart';
|
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/callout/callout_block_shortcuts.dart';
|
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/i18n/editor_i18n.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/i18n/editor_i18n.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_style.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_style.dart';
|
||||||
import 'package:appflowy/plugins/inline_actions/handlers/date_reference.dart';
|
import 'package:appflowy/plugins/inline_actions/handlers/date_reference.dart';
|
||||||
import 'package:appflowy/plugins/inline_actions/handlers/inline_page_reference.dart';
|
import 'package:appflowy/plugins/inline_actions/handlers/inline_page_reference.dart';
|
||||||
import 'package:appflowy/plugins/inline_actions/handlers/reminder_reference.dart';
|
import 'package:appflowy/plugins/inline_actions/handlers/reminder_reference.dart';
|
||||||
import 'package:appflowy/plugins/inline_actions/inline_actions_command.dart';
|
|
||||||
import 'package:appflowy/plugins/inline_actions/inline_actions_service.dart';
|
import 'package:appflowy/plugins/inline_actions/inline_actions_service.dart';
|
||||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
||||||
import 'package:appflowy/workspace/application/settings/shortcuts/settings_shortcuts_service.dart';
|
import 'package:appflowy/workspace/application/settings/shortcuts/settings_shortcuts_service.dart';
|
||||||
import 'package:appflowy/workspace/application/view_info/view_info_bloc.dart';
|
import 'package:appflowy/workspace/application/view_info/view_info_bloc.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/af_focus_manager.dart';
|
import 'package:appflowy/workspace/presentation/home/af_focus_manager.dart';
|
||||||
import 'package:appflowy/workspace/presentation/settings/widgets/emoji_picker/emoji_picker.dart';
|
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart';
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
|
||||||
import 'package:flowy_infra/theme_extension.dart';
|
import 'package:flowy_infra/theme_extension.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -32,53 +23,6 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:universal_platform/universal_platform.dart';
|
import 'package:universal_platform/universal_platform.dart';
|
||||||
|
|
||||||
final codeBlockLocalization = CodeBlockLocalizations(
|
|
||||||
codeBlockNewParagraph:
|
|
||||||
LocaleKeys.settings_shortcutsPage_commands_codeBlockNewParagraph.tr(),
|
|
||||||
codeBlockIndentLines:
|
|
||||||
LocaleKeys.settings_shortcutsPage_commands_codeBlockIndentLines.tr(),
|
|
||||||
codeBlockOutdentLines:
|
|
||||||
LocaleKeys.settings_shortcutsPage_commands_codeBlockOutdentLines.tr(),
|
|
||||||
codeBlockSelectAll:
|
|
||||||
LocaleKeys.settings_shortcutsPage_commands_codeBlockSelectAll.tr(),
|
|
||||||
codeBlockPasteText:
|
|
||||||
LocaleKeys.settings_shortcutsPage_commands_codeBlockPasteText.tr(),
|
|
||||||
codeBlockAddTwoSpaces:
|
|
||||||
LocaleKeys.settings_shortcutsPage_commands_codeBlockAddTwoSpaces.tr(),
|
|
||||||
);
|
|
||||||
|
|
||||||
final localizedCodeBlockCommands =
|
|
||||||
codeBlockCommands(localizations: codeBlockLocalization);
|
|
||||||
|
|
||||||
final List<CommandShortcutEvent> commandShortcutEvents = [
|
|
||||||
backspaceToTitle,
|
|
||||||
arrowUpToTitle,
|
|
||||||
arrowLeftToTitle,
|
|
||||||
toggleToggleListCommand,
|
|
||||||
...localizedCodeBlockCommands,
|
|
||||||
customCopyCommand,
|
|
||||||
customPasteCommand,
|
|
||||||
customCutCommand,
|
|
||||||
...customTextAlignCommands,
|
|
||||||
|
|
||||||
// remove standard shortcuts for copy, cut, paste, todo
|
|
||||||
...standardCommandShortcutEvents
|
|
||||||
..removeWhere(
|
|
||||||
(shortcut) => [
|
|
||||||
copyCommand,
|
|
||||||
cutCommand,
|
|
||||||
pasteCommand,
|
|
||||||
toggleTodoListCommand,
|
|
||||||
].contains(shortcut),
|
|
||||||
),
|
|
||||||
|
|
||||||
emojiShortcutEvent,
|
|
||||||
];
|
|
||||||
|
|
||||||
final List<CommandShortcutEvent> defaultCommandShortcutEvents = [
|
|
||||||
...commandShortcutEvents.map((e) => e.copyWith()),
|
|
||||||
];
|
|
||||||
|
|
||||||
/// Wrapper for the appflowy editor.
|
/// Wrapper for the appflowy editor.
|
||||||
class AppFlowyEditorPage extends StatefulWidget {
|
class AppFlowyEditorPage extends StatefulWidget {
|
||||||
const AppFlowyEditorPage({
|
const AppFlowyEditorPage({
|
||||||
@ -125,7 +69,7 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
late final List<CommandShortcutEvent> cmdShortcutEvents = [
|
late final List<CommandShortcutEvent> commandShortcuts = [
|
||||||
...commandShortcutEvents,
|
...commandShortcutEvents,
|
||||||
..._buildFindAndReplaceCommands(),
|
..._buildFindAndReplaceCommands(),
|
||||||
];
|
];
|
||||||
@ -151,60 +95,15 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
|||||||
|
|
||||||
late List<SelectionMenuItem> slashMenuItems;
|
late List<SelectionMenuItem> slashMenuItems;
|
||||||
|
|
||||||
List<CharacterShortcutEvent> get characterShortcutEvents => [
|
List<CharacterShortcutEvent> get characterShortcutEvents {
|
||||||
// code block
|
return buildCharacterShortcutEvents(
|
||||||
formatBacktickToCodeBlock,
|
context,
|
||||||
...codeBlockCharacterEvents,
|
documentBloc,
|
||||||
|
styleCustomizer,
|
||||||
// callout block
|
|
||||||
insertNewLineInCalloutBlock,
|
|
||||||
|
|
||||||
// quote block
|
|
||||||
insertNewLineInQuoteBlock,
|
|
||||||
|
|
||||||
// toggle list
|
|
||||||
formatGreaterToToggleList,
|
|
||||||
insertChildNodeInsideToggleList,
|
|
||||||
|
|
||||||
// customize the slash menu command
|
|
||||||
customSlashCommand(
|
|
||||||
slashMenuItems,
|
|
||||||
style: styleCustomizer.selectionMenuStyleBuilder(),
|
|
||||||
),
|
|
||||||
|
|
||||||
customFormatGreaterEqual,
|
|
||||||
|
|
||||||
...standardCharacterShortcutEvents
|
|
||||||
..removeWhere(
|
|
||||||
(shortcut) => [
|
|
||||||
slashCommand, // Remove default slash command
|
|
||||||
formatGreaterEqual, // Overridden by customFormatGreaterEqual
|
|
||||||
].contains(shortcut),
|
|
||||||
),
|
|
||||||
|
|
||||||
/// Inline Actions
|
|
||||||
/// - Reminder
|
|
||||||
/// - Inline-page reference
|
|
||||||
inlineActionsCommand(
|
|
||||||
inlineActionsService,
|
inlineActionsService,
|
||||||
style: styleCustomizer.inlineActionsMenuStyleBuilder(),
|
slashMenuItems,
|
||||||
),
|
);
|
||||||
|
}
|
||||||
/// Inline page menu
|
|
||||||
/// - Using `[[`
|
|
||||||
pageReferenceShortcutBrackets(
|
|
||||||
context,
|
|
||||||
documentBloc.documentId,
|
|
||||||
styleCustomizer.inlineActionsMenuStyleBuilder(),
|
|
||||||
),
|
|
||||||
|
|
||||||
/// - Using `+`
|
|
||||||
pageReferenceShortcutPlusSign(
|
|
||||||
context,
|
|
||||||
documentBloc.documentId,
|
|
||||||
styleCustomizer.inlineActionsMenuStyleBuilder(),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
EditorStyleCustomizer get styleCustomizer => widget.styleCustomizer;
|
EditorStyleCustomizer get styleCustomizer => widget.styleCustomizer;
|
||||||
DocumentBloc get documentBloc => context.read<DocumentBloc>();
|
DocumentBloc get documentBloc => context.read<DocumentBloc>();
|
||||||
@ -223,12 +122,6 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
|||||||
|
|
||||||
AFFocusManager? focusManager;
|
AFFocusManager? focusManager;
|
||||||
|
|
||||||
void _loseFocus() {
|
|
||||||
if (!widget.editorState.isDisposed) {
|
|
||||||
widget.editorState.selection = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@ -363,7 +256,7 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
|||||||
),
|
),
|
||||||
// customize the shortcuts
|
// customize the shortcuts
|
||||||
characterShortcutEvents: characterShortcutEvents,
|
characterShortcutEvents: characterShortcutEvents,
|
||||||
commandShortcutEvents: cmdShortcutEvents,
|
commandShortcutEvents: commandShortcuts,
|
||||||
// customize the context menu items
|
// customize the context menu items
|
||||||
contextMenuItems: customContextMenuItems,
|
contextMenuItems: customContextMenuItems,
|
||||||
// customize the header and footer.
|
// customize the header and footer.
|
||||||
@ -469,7 +362,7 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
|||||||
final customizeShortcuts =
|
final customizeShortcuts =
|
||||||
await settingsShortcutService.getCustomizeShortcuts();
|
await settingsShortcutService.getCustomizeShortcuts();
|
||||||
await settingsShortcutService.updateCommandShortcuts(
|
await settingsShortcutService.updateCommandShortcuts(
|
||||||
cmdShortcutEvents,
|
commandShortcuts,
|
||||||
customizeShortcuts,
|
customizeShortcuts,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -542,6 +435,12 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
|||||||
}
|
}
|
||||||
await editorState.apply(transaction);
|
await editorState.apply(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _loseFocus() {
|
||||||
|
if (!widget.editorState.isDisposed) {
|
||||||
|
widget.editorState.selection = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Color? buildEditorCustomizedColor(
|
Color? buildEditorCustomizedColor(
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
|
||||||
import 'package:appflowy/startup/startup.dart';
|
import 'package:appflowy/startup/startup.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
/// Copy.
|
/// Copy.
|
||||||
///
|
///
|
||||||
@ -23,21 +22,43 @@ final CommandShortcutEvent customCopyCommand = CommandShortcutEvent(
|
|||||||
|
|
||||||
CommandShortcutEventHandler _copyCommandHandler = (editorState) {
|
CommandShortcutEventHandler _copyCommandHandler = (editorState) {
|
||||||
final selection = editorState.selection?.normalized;
|
final selection = editorState.selection?.normalized;
|
||||||
if (selection == null || selection.isCollapsed) {
|
if (selection == null) {
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
}
|
||||||
|
|
||||||
|
String? text;
|
||||||
|
String? html;
|
||||||
|
String? inAppJson;
|
||||||
|
|
||||||
|
if (selection.isCollapsed) {
|
||||||
|
// if the selection is collapsed, we will copy the text of the current line.
|
||||||
|
final node = editorState.getNodeAtPath(selection.end.path);
|
||||||
|
if (node == null) {
|
||||||
return KeyEventResult.ignored;
|
return KeyEventResult.ignored;
|
||||||
}
|
}
|
||||||
|
|
||||||
// plain text.
|
// plain text.
|
||||||
final text = editorState.getTextInSelection(selection).join('\n');
|
text = node.delta?.toPlainText();
|
||||||
|
|
||||||
|
// in app json
|
||||||
|
final document = Document.blank()..insert([0], [node.copyWith()]);
|
||||||
|
inAppJson = jsonEncode(document.toJson());
|
||||||
|
|
||||||
|
// html
|
||||||
|
html = documentToHTML(document);
|
||||||
|
} else {
|
||||||
|
// plain text.
|
||||||
|
text = editorState.getTextInSelection(selection).join('\n');
|
||||||
|
|
||||||
final nodes = editorState.getSelectedNodes(selection: selection);
|
final nodes = editorState.getSelectedNodes(selection: selection);
|
||||||
final document = Document.blank()..insert([0], nodes);
|
final document = Document.blank()..insert([0], nodes);
|
||||||
|
|
||||||
// in app json
|
// in app json
|
||||||
final inAppJson = jsonEncode(document.toJson());
|
inAppJson = jsonEncode(document.toJson());
|
||||||
|
|
||||||
// html
|
// html
|
||||||
final html = documentToHTML(document);
|
html = documentToHTML(document);
|
||||||
|
}
|
||||||
|
|
||||||
() async {
|
() async {
|
||||||
await getIt<ClipboardService>().setData(
|
await getIt<ClipboardService>().setData(
|
||||||
|
@ -18,7 +18,30 @@ final CommandShortcutEvent customCutCommand = CommandShortcutEvent(
|
|||||||
);
|
);
|
||||||
|
|
||||||
CommandShortcutEventHandler _cutCommandHandler = (editorState) {
|
CommandShortcutEventHandler _cutCommandHandler = (editorState) {
|
||||||
|
final selection = editorState.selection;
|
||||||
|
if (selection == null) {
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
}
|
||||||
|
|
||||||
customCopyCommand.execute(editorState);
|
customCopyCommand.execute(editorState);
|
||||||
|
|
||||||
|
if (!selection.isCollapsed) {
|
||||||
editorState.deleteSelectionIfNeeded();
|
editorState.deleteSelectionIfNeeded();
|
||||||
|
} else {
|
||||||
|
final node = editorState.getNodeAtPath(selection.end.path);
|
||||||
|
if (node == null) {
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
}
|
||||||
|
final transaction = editorState.transaction;
|
||||||
|
transaction.deleteNode(node);
|
||||||
|
final nextNode = node.next;
|
||||||
|
if (nextNode != null && nextNode.delta != null) {
|
||||||
|
transaction.afterSelection = Selection.collapsed(
|
||||||
|
Position(path: node.path, offset: nextNode.delta?.length ?? 0),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
editorState.apply(transaction);
|
||||||
|
}
|
||||||
|
|
||||||
return KeyEventResult.handled;
|
return KeyEventResult.handled;
|
||||||
};
|
};
|
||||||
|
@ -55,6 +55,8 @@ export 'openai/widgets/smart_edit_toolbar_item.dart';
|
|||||||
export 'outline/outline_block_component.dart';
|
export 'outline/outline_block_component.dart';
|
||||||
export 'parsers/markdown_parsers.dart';
|
export 'parsers/markdown_parsers.dart';
|
||||||
export 'quote/quote_block_shortcuts.dart';
|
export 'quote/quote_block_shortcuts.dart';
|
||||||
|
export 'shortcuts/character_shortcuts.dart';
|
||||||
|
export 'shortcuts/command_shortcuts.dart';
|
||||||
export 'slash_menu/slash_menu_items.dart';
|
export 'slash_menu/slash_menu_items.dart';
|
||||||
export 'table/table_menu.dart';
|
export 'table/table_menu.dart';
|
||||||
export 'table/table_option_action.dart';
|
export 'table/table_option_action.dart';
|
||||||
|
@ -0,0 +1,74 @@
|
|||||||
|
import 'package:appflowy/plugins/document/application/document_bloc.dart';
|
||||||
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/format_arrow_character.dart';
|
||||||
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/page_reference_commands.dart';
|
||||||
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/callout/callout_block_shortcuts.dart';
|
||||||
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||||
|
import 'package:appflowy/plugins/document/presentation/editor_style.dart';
|
||||||
|
import 'package:appflowy/plugins/inline_actions/inline_actions_command.dart';
|
||||||
|
import 'package:appflowy/plugins/inline_actions/inline_actions_service.dart';
|
||||||
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
List<CharacterShortcutEvent> buildCharacterShortcutEvents(
|
||||||
|
BuildContext context,
|
||||||
|
DocumentBloc documentBloc,
|
||||||
|
EditorStyleCustomizer styleCustomizer,
|
||||||
|
InlineActionsService inlineActionsService,
|
||||||
|
List<SelectionMenuItem> slashMenuItems,
|
||||||
|
) {
|
||||||
|
return [
|
||||||
|
// code block
|
||||||
|
formatBacktickToCodeBlock,
|
||||||
|
...codeBlockCharacterEvents,
|
||||||
|
|
||||||
|
// callout block
|
||||||
|
insertNewLineInCalloutBlock,
|
||||||
|
|
||||||
|
// quote block
|
||||||
|
insertNewLineInQuoteBlock,
|
||||||
|
|
||||||
|
// toggle list
|
||||||
|
formatGreaterToToggleList,
|
||||||
|
insertChildNodeInsideToggleList,
|
||||||
|
|
||||||
|
// customize the slash menu command
|
||||||
|
customSlashCommand(
|
||||||
|
slashMenuItems,
|
||||||
|
style: styleCustomizer.selectionMenuStyleBuilder(),
|
||||||
|
),
|
||||||
|
|
||||||
|
customFormatGreaterEqual,
|
||||||
|
|
||||||
|
...standardCharacterShortcutEvents
|
||||||
|
..removeWhere(
|
||||||
|
(shortcut) => [
|
||||||
|
slashCommand, // Remove default slash command
|
||||||
|
formatGreaterEqual, // Overridden by customFormatGreaterEqual
|
||||||
|
].contains(shortcut),
|
||||||
|
),
|
||||||
|
|
||||||
|
/// Inline Actions
|
||||||
|
/// - Reminder
|
||||||
|
/// - Inline-page reference
|
||||||
|
inlineActionsCommand(
|
||||||
|
inlineActionsService,
|
||||||
|
style: styleCustomizer.inlineActionsMenuStyleBuilder(),
|
||||||
|
),
|
||||||
|
|
||||||
|
/// Inline page menu
|
||||||
|
/// - Using `[[`
|
||||||
|
pageReferenceShortcutBrackets(
|
||||||
|
context,
|
||||||
|
documentBloc.documentId,
|
||||||
|
styleCustomizer.inlineActionsMenuStyleBuilder(),
|
||||||
|
),
|
||||||
|
|
||||||
|
/// - Using `+`
|
||||||
|
pageReferenceShortcutPlusSign(
|
||||||
|
context,
|
||||||
|
documentBloc.documentId,
|
||||||
|
styleCustomizer.inlineActionsMenuStyleBuilder(),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/align_toolbar_item/custom_text_align_command.dart';
|
||||||
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/settings/widgets/emoji_picker/emoji_picker.dart';
|
||||||
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
|
||||||
|
final List<CommandShortcutEvent> defaultCommandShortcutEvents = [
|
||||||
|
...commandShortcutEvents.map((e) => e.copyWith()),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Command shortcuts are order-sensitive. Verify order when modifying.
|
||||||
|
List<CommandShortcutEvent> commandShortcutEvents = [
|
||||||
|
backspaceToTitle,
|
||||||
|
|
||||||
|
arrowUpToTitle,
|
||||||
|
arrowLeftToTitle,
|
||||||
|
|
||||||
|
toggleToggleListCommand,
|
||||||
|
|
||||||
|
...localizedCodeBlockCommands,
|
||||||
|
|
||||||
|
customCopyCommand,
|
||||||
|
customPasteCommand,
|
||||||
|
customCutCommand,
|
||||||
|
|
||||||
|
...customTextAlignCommands,
|
||||||
|
|
||||||
|
// remove standard shortcuts for copy, cut, paste, todo
|
||||||
|
...standardCommandShortcutEvents
|
||||||
|
..removeWhere(
|
||||||
|
(shortcut) => [
|
||||||
|
copyCommand,
|
||||||
|
cutCommand,
|
||||||
|
pasteCommand,
|
||||||
|
toggleTodoListCommand,
|
||||||
|
].contains(shortcut),
|
||||||
|
),
|
||||||
|
|
||||||
|
emojiShortcutEvent,
|
||||||
|
];
|
||||||
|
|
||||||
|
final _codeBlockLocalization = CodeBlockLocalizations(
|
||||||
|
codeBlockNewParagraph:
|
||||||
|
LocaleKeys.settings_shortcutsPage_commands_codeBlockNewParagraph.tr(),
|
||||||
|
codeBlockIndentLines:
|
||||||
|
LocaleKeys.settings_shortcutsPage_commands_codeBlockIndentLines.tr(),
|
||||||
|
codeBlockOutdentLines:
|
||||||
|
LocaleKeys.settings_shortcutsPage_commands_codeBlockOutdentLines.tr(),
|
||||||
|
codeBlockSelectAll:
|
||||||
|
LocaleKeys.settings_shortcutsPage_commands_codeBlockSelectAll.tr(),
|
||||||
|
codeBlockPasteText:
|
||||||
|
LocaleKeys.settings_shortcutsPage_commands_codeBlockPasteText.tr(),
|
||||||
|
codeBlockAddTwoSpaces:
|
||||||
|
LocaleKeys.settings_shortcutsPage_commands_codeBlockAddTwoSpaces.tr(),
|
||||||
|
);
|
||||||
|
|
||||||
|
final localizedCodeBlockCommands = codeBlockCommands(
|
||||||
|
localizations: _codeBlockLocalization,
|
||||||
|
);
|
@ -1,5 +1,5 @@
|
|||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_page.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||||
import 'package:appflowy/workspace/application/settings/shortcuts/settings_shortcuts_service.dart';
|
import 'package:appflowy/workspace/application/settings/shortcuts/settings_shortcuts_service.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
@ -2,7 +2,7 @@ import 'dart:async';
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_page.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||||
import 'package:appflowy/startup/startup.dart';
|
import 'package:appflowy/startup/startup.dart';
|
||||||
import 'package:appflowy/workspace/application/settings/application_data_storage.dart';
|
import 'package:appflowy/workspace/application/settings/application_data_storage.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
@ -53,8 +53,8 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: "6da7b4e"
|
ref: c5de9ed
|
||||||
resolved-ref: "6da7b4e5dc76dd6b9d4361c744359b04ddb5b983"
|
resolved-ref: c5de9ed84dbc7461e5542ce598d803e37838a65a
|
||||||
url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
|
url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
|
||||||
source: git
|
source: git
|
||||||
version: "3.3.0"
|
version: "3.3.0"
|
||||||
@ -1559,10 +1559,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: platform
|
name: platform
|
||||||
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
|
sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.4"
|
version: "3.1.5"
|
||||||
plugin_platform_interface:
|
plugin_platform_interface:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@ -1989,10 +1989,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: string_scanner
|
name: string_scanner
|
||||||
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
|
sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0"
|
version: "1.3.0"
|
||||||
string_validator:
|
string_validator:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -2311,10 +2311,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vm_service
|
name: vm_service
|
||||||
sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
|
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "14.2.1"
|
version: "14.2.5"
|
||||||
watcher:
|
watcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -177,7 +177,7 @@ dependency_overrides:
|
|||||||
appflowy_editor:
|
appflowy_editor:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/AppFlowy-IO/appflowy-editor.git
|
url: https://github.com/AppFlowy-IO/appflowy-editor.git
|
||||||
ref: "6da7b4e"
|
ref: "c5de9ed"
|
||||||
|
|
||||||
appflowy_editor_plugins:
|
appflowy_editor_plugins:
|
||||||
git:
|
git:
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import 'dart:ffi';
|
import 'dart:ffi';
|
||||||
|
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_page.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||||
import 'package:appflowy/workspace/application/settings/shortcuts/settings_shortcuts_cubit.dart';
|
import 'package:appflowy/workspace/application/settings/shortcuts/settings_shortcuts_cubit.dart';
|
||||||
import 'package:appflowy/workspace/application/settings/shortcuts/settings_shortcuts_service.dart';
|
import 'package:appflowy/workspace/application/settings/shortcuts/settings_shortcuts_service.dart';
|
||||||
import 'package:bloc_test/bloc_test.dart';
|
import 'package:bloc_test/bloc_test.dart';
|
||||||
|
Loading…
x
Reference in New Issue
Block a user