chore: integrate delete line command into cut command (#907)
This commit is contained in:
parent
54edcadbb2
commit
c5de9ed84d
7 changed files with 84 additions and 125 deletions
|
|
@ -1,43 +0,0 @@
|
|||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Delete the current line
|
||||
///
|
||||
/// - support
|
||||
/// - desktop
|
||||
/// - web
|
||||
///
|
||||
final CommandShortcutEvent deleteLineCommand = CommandShortcutEvent(
|
||||
key: 'delete line',
|
||||
getDescription: () => 'Delete the current line',
|
||||
command: 'ctrl+x',
|
||||
macOSCommand: 'cmd+x',
|
||||
handler: _deleteLineCommandHandler,
|
||||
);
|
||||
|
||||
// when the selection is collapsed, press cmd+x(or ctrl+x) should delete the current line
|
||||
CommandShortcutEventHandler _deleteLineCommandHandler = (editorState) {
|
||||
final selection = editorState.selection;
|
||||
if (selection == null || !selection.isCollapsed) {
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
final node = editorState.getNodeAtPath(selection.end.path);
|
||||
if (node == null) {
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
|
||||
final nextNode = node.next;
|
||||
Selection? afterSelection;
|
||||
if (nextNode != null && nextNode.delta != null) {
|
||||
afterSelection = Selection.collapsed(
|
||||
Position(path: node.path, offset: nextNode.delta?.length ?? 0),
|
||||
);
|
||||
}
|
||||
|
||||
final transaction = editorState.transaction
|
||||
..deleteNode(node)
|
||||
..afterSelection = afterSelection;
|
||||
editorState.apply(transaction);
|
||||
|
||||
return KeyEventResult.handled;
|
||||
};
|
||||
|
|
@ -6,7 +6,6 @@ export 'base_component/block_component_action_wrapper.dart';
|
|||
export 'base_component/block_component_configuration.dart';
|
||||
// base
|
||||
export 'base_component/convert_to_paragraph_command.dart';
|
||||
export 'base_component/delete_line_command.dart';
|
||||
export 'base_component/indent_command.dart';
|
||||
export 'base_component/insert_newline_in_type_command.dart';
|
||||
export 'base_component/markdown_format_helper.dart';
|
||||
|
|
|
|||
|
|
@ -148,9 +148,6 @@ final List<CommandShortcutEvent> standardCommandShortcutEvents = [
|
|||
//
|
||||
selectAllCommand,
|
||||
|
||||
// delete line
|
||||
deleteLineCommand,
|
||||
|
||||
// copy paste and cut
|
||||
copyCommand,
|
||||
...pasteCommands,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@ class AppFlowyClipboardData {
|
|||
class AppFlowyClipboard {
|
||||
static AppFlowyClipboardData? _mockData;
|
||||
|
||||
@visibleForTesting
|
||||
static String? lastText;
|
||||
|
||||
static Future<void> setData({
|
||||
String? text,
|
||||
String? html,
|
||||
|
|
@ -21,6 +24,8 @@ class AppFlowyClipboard {
|
|||
return;
|
||||
}
|
||||
|
||||
lastText = text;
|
||||
|
||||
return Clipboard.setData(
|
||||
ClipboardData(
|
||||
text: text,
|
||||
|
|
|
|||
|
|
@ -136,22 +136,38 @@ Selection _computeSelectionAfterPasteMultipleNodes(
|
|||
|
||||
void handleCopy(EditorState editorState) async {
|
||||
final selection = editorState.selection?.normalized;
|
||||
if (selection == null || selection.isCollapsed) {
|
||||
if (selection == null) {
|
||||
return;
|
||||
}
|
||||
final text = editorState.getTextInSelection(selection).join('\n');
|
||||
String text;
|
||||
String html;
|
||||
|
||||
if (selection.isCollapsed) {
|
||||
final node = editorState.getNodeAtPath(selection.end.path);
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
text = node.delta?.toPlainText() ?? '';
|
||||
html = documentToHTML(
|
||||
Document(
|
||||
root: pageNode(children: [node.copyWith()]),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
text = editorState.getTextInSelection(selection).join('\n');
|
||||
final nodes = editorState.getSelectedNodes(selection: selection);
|
||||
if (nodes.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final html = documentToHTML(
|
||||
html = documentToHTML(
|
||||
Document(
|
||||
root: Node(
|
||||
type: 'page',
|
||||
children: nodes,
|
||||
root: pageNode(
|
||||
children: nodes.map((node) => node.copyWith()),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return AppFlowyClipboard.setData(
|
||||
text: text,
|
||||
html: html.isEmpty ? null : html,
|
||||
|
|
@ -332,13 +348,30 @@ void handleCut(EditorState editorState) {
|
|||
deleteSelectedContent(editorState);
|
||||
}
|
||||
|
||||
void deleteSelectedContent(EditorState editorState) async {
|
||||
Future<void> deleteSelectedContent(EditorState editorState) async {
|
||||
final selection = editorState.selection?.normalized;
|
||||
if (selection == null || selection.isCollapsed) {
|
||||
if (selection == null) {
|
||||
return;
|
||||
}
|
||||
final tb = editorState.transaction;
|
||||
final transaction = editorState.transaction;
|
||||
if (selection.isCollapsed) {
|
||||
// if the selection is collapsed, delete the current node
|
||||
final node = editorState.getNodeAtPath(selection.end.path);
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
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),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// if the selection is not collapsed, delete the selection
|
||||
await editorState.deleteSelection(selection);
|
||||
tb.afterSelection = Selection.collapsed(selection.start);
|
||||
editorState.apply(tb);
|
||||
transaction.afterSelection = Selection.collapsed(selection.start);
|
||||
}
|
||||
|
||||
await editorState.apply(transaction);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,46 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../../../infra/testable_editor.dart';
|
||||
import '../../../util/util.dart';
|
||||
|
||||
void main() async {
|
||||
setUpAll(() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
});
|
||||
group('cut_command_test.dart', () {
|
||||
const text = 'Welcome to AppFlowy Editor 🔥!';
|
||||
|
||||
testWidgets('update selection and execute cut command', (tester) async {
|
||||
await _testCutHandle(tester, Document.fromJson(paragraphdata));
|
||||
});
|
||||
|
||||
test('cut current line if the selection is collapsed', () async {
|
||||
final document = Document.blank().addParagraphs(
|
||||
2,
|
||||
builder: (index) => Delta()..insert('${index + 1}. $text'),
|
||||
);
|
||||
final editorState = EditorState(document: document);
|
||||
|
||||
final selection = Selection.collapsed(
|
||||
Position(path: [0], offset: text.length),
|
||||
);
|
||||
editorState.selection = selection;
|
||||
|
||||
final result = cutCommand.execute(editorState);
|
||||
expect(result, KeyEventResult.handled);
|
||||
|
||||
// check the clipboard
|
||||
final lastText = AppFlowyClipboard.lastText!;
|
||||
expect(lastText, '1. $text');
|
||||
|
||||
final after = editorState.getNodeAtPath([0])!;
|
||||
expect(after.delta!.toPlainText(), '2. $text');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,59 +0,0 @@
|
|||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../../../util/util.dart';
|
||||
|
||||
void main() async {
|
||||
group('deleteLineCommand', () {
|
||||
group('delete current line if the selection is collapsed', () {
|
||||
const text = 'Welcome to AppFlowy Editor 🔥!';
|
||||
|
||||
test('delete in collapsed selection when the index > 0', () async {
|
||||
final document = Document.blank().addParagraphs(
|
||||
2,
|
||||
builder: (index) => Delta()..insert('${index + 1}. $text'),
|
||||
);
|
||||
final editorState = EditorState(document: document);
|
||||
|
||||
final selection = Selection.collapsed(
|
||||
Position(path: [0], offset: text.length),
|
||||
);
|
||||
editorState.selection = selection;
|
||||
|
||||
final result = deleteLineCommand.execute(editorState);
|
||||
expect(result, KeyEventResult.handled);
|
||||
|
||||
final after = editorState.getNodeAtPath([0])!;
|
||||
expect(after.delta!.toPlainText(), '2. $text');
|
||||
});
|
||||
|
||||
test('do nothing if the selection is not collapsed', () async {
|
||||
final document = Document.blank().addParagraphs(
|
||||
2,
|
||||
builder: (index) => Delta()..insert('${index + 1}. $text'),
|
||||
);
|
||||
final editorState = EditorState(document: document);
|
||||
|
||||
final selection = Selection(
|
||||
start: Position(path: [0], offset: 0),
|
||||
end: Position(path: [0], offset: text.length),
|
||||
);
|
||||
editorState.selection = selection;
|
||||
|
||||
final result = deleteLineCommand.execute(editorState);
|
||||
expect(result, KeyEventResult.ignored);
|
||||
|
||||
expect(
|
||||
editorState.getNodeAtPath([0])!.delta!.toPlainText(),
|
||||
'1. $text',
|
||||
);
|
||||
expect(
|
||||
editorState.getNodeAtPath([1])!.delta!.toPlainText(),
|
||||
'2. $text',
|
||||
);
|
||||
expect(editorState.selection, selection);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
Loading…
Reference in a new issue