Skip to content

Commit a7f40b2

Browse files
authored
fix: paste in list (AppFlowy-IO#5621)
* fix: support pasting in list * test: add simple test * chore: remove debugPrint
1 parent c996c9c commit a7f40b2

File tree

4 files changed

+95
-38
lines changed

4 files changed

+95
-38
lines changed

frontend/appflowy_flutter/integration_test/desktop/document/document_copy_and_paste_test.dart

Lines changed: 68 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import 'dart:io';
22

3+
import 'package:flutter/services.dart';
4+
35
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
46
import 'package:appflowy/startup/startup.dart';
57
import 'package:appflowy_editor/appflowy_editor.dart';
68
import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart';
7-
import 'package:flutter/services.dart';
89
import 'package:flutter_test/flutter_test.dart';
910
import 'package:integration_test/integration_test.dart';
1011

@@ -116,6 +117,53 @@ void main() {
116117
]);
117118
});
118119
});
120+
121+
testWidgets('paste bulleted list in numbered list', (tester) async {
122+
const inAppJson =
123+
'{"document":{"type":"page","children":[{"type":"bulleted_list","children":[{"type":"bulleted_list","data":{"delta":[{"insert":"World"}]}}],"data":{"delta":[{"insert":"Hello"}]}}]}}';
124+
125+
await tester.pasteContent(
126+
inAppJson: inAppJson,
127+
beforeTest: (editorState) async {
128+
final transaction = editorState.transaction;
129+
// Insert two numbered list nodes
130+
// 1. Parent One
131+
// 2.
132+
transaction.insertNodes(
133+
[0],
134+
[
135+
Node(
136+
type: NumberedListBlockKeys.type,
137+
attributes: {
138+
'delta': [
139+
{"insert": "One"},
140+
],
141+
},
142+
),
143+
Node(
144+
type: NumberedListBlockKeys.type,
145+
attributes: {'delta': []},
146+
),
147+
],
148+
);
149+
150+
// Set the selection to the second numbered list node (which has empty delta)
151+
transaction.afterSelection = Selection.collapsed(Position(path: [1]));
152+
153+
await editorState.apply(transaction);
154+
await tester.pumpAndSettle();
155+
},
156+
(editorState) {
157+
final secondNode = editorState.getNodeAtPath([1]);
158+
expect(secondNode?.delta?.toPlainText(), 'Hello');
159+
expect(secondNode?.children.length, 1);
160+
161+
final childNode = secondNode?.children.first;
162+
expect(childNode?.delta?.toPlainText(), 'World');
163+
expect(childNode?.type, BulletedListBlockKeys.type);
164+
},
165+
);
166+
});
119167
});
120168

121169
testWidgets('paste image(png) from memory', (tester) async {
@@ -210,42 +258,33 @@ void main() {
210258
testWidgets('paste the html content contains section', (tester) async {
211259
const html =
212260
'''<meta charset='utf-8'><section style="margin: 0px 8px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgba(255, 255, 255, 0.6); font-family: system-ui, -apple-system, &quot;system-ui&quot;, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(25, 25, 25); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><span style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgb(0, 160, 113);"><strong style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important;">AppFlowy</strong></span></section><section style="margin: 0px 8px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgba(255, 255, 255, 0.6); font-family: system-ui, -apple-system, &quot;system-ui&quot;, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(25, 25, 25); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><span style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgb(0, 160, 113);"><strong style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important;">Hello World</strong></span></section>''';
213-
await tester.pasteContent(
214-
html: html,
215-
(editorState) {
216-
expect(editorState.document.root.children.length, 2);
217-
final node1 = editorState.getNodeAtPath([0])!;
218-
final node2 = editorState.getNodeAtPath([1])!;
219-
expect(node1.type, ParagraphBlockKeys.type);
220-
expect(node2.type, ParagraphBlockKeys.type);
221-
},
222-
);
261+
await tester.pasteContent(html: html, (editorState) {
262+
expect(editorState.document.root.children.length, 2);
263+
final node1 = editorState.getNodeAtPath([0])!;
264+
final node2 = editorState.getNodeAtPath([1])!;
265+
expect(node1.type, ParagraphBlockKeys.type);
266+
expect(node2.type, ParagraphBlockKeys.type);
267+
});
223268
});
224269

225270
testWidgets('paste the html from google translation', (tester) async {
226271
const html =
227272
'''<meta charset='utf-8'><section style="margin: 0px 8px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgba(255, 255, 255, 0.6); font-family: system-ui, -apple-system, &quot;system-ui&quot;, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(25, 25, 25); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><span style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgb(0, 160, 113);"><strong style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important;"><font style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; vertical-align: inherit;"><font style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; vertical-align: inherit;">new force</font></font></strong></span></section><section style="margin: 0px 8px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgba(255, 255, 255, 0.6); font-family: system-ui, -apple-system, &quot;system-ui&quot;, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(25, 25, 25); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><span style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgb(0, 160, 113);"><strong style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important;"><font style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; vertical-align: inherit;"><font style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; vertical-align: inherit;">Assessment focus: potential motivations, empathy</font></font></strong></span></section><section style="margin: 0px 8px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgba(255, 255, 255, 0.6); font-family: system-ui, -apple-system, &quot;system-ui&quot;, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(25, 25, 25); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><br style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important;"></section><section style="margin: 0px 8px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgba(255, 255, 255, 0.6); font-family: system-ui, -apple-system, &quot;system-ui&quot;, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(25, 25, 25); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><font style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; vertical-align: inherit;"><font style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; vertical-align: inherit;">➢Personality characteristics and potential motivations:</font></font></section><section style="margin: 0px 8px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgba(255, 255, 255, 0.6); font-family: system-ui, -apple-system, &quot;system-ui&quot;, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(25, 25, 25); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><font style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; vertical-align: inherit;"><font style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; vertical-align: inherit;">-Reflection of self-worth</font></font></section><section style="margin: 0px 8px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgba(255, 255, 255, 0.6); font-family: system-ui, -apple-system, &quot;system-ui&quot;, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(25, 25, 25); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><font style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; vertical-align: inherit;"><font style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; vertical-align: inherit;">-Need to be respected</font></font></section><section style="margin: 0px 8px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgba(255, 255, 255, 0.6); font-family: system-ui, -apple-system, &quot;system-ui&quot;, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(25, 25, 25); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><font style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; vertical-align: inherit;"><font style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; vertical-align: inherit;">-Have a unique definition of success</font></font></section><section style="margin: 0px 8px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgba(255, 255, 255, 0.6); font-family: system-ui, -apple-system, &quot;system-ui&quot;, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(25, 25, 25); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><font style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; vertical-align: inherit;"><font style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; vertical-align: inherit;">-Be true to your own lifestyle</font></font></section>''';
228-
await tester.pasteContent(
229-
html: html,
230-
(editorState) {
231-
expect(editorState.document.root.children.length, 8);
232-
},
233-
);
273+
await tester.pasteContent(html: html, (editorState) {
274+
expect(editorState.document.root.children.length, 8);
275+
});
234276
});
235277

236278
testWidgets(
237279
'auto convert url to link preview block',
238-
(widgetTester) async {
280+
(tester) async {
239281
const url = 'https://appflowy.io';
240-
await widgetTester.pasteContent(
241-
plainText: url,
242-
(editorState) {
243-
expect(editorState.document.root.children.length, 2);
244-
final node = editorState.getNodeAtPath([0])!;
245-
expect(node.type, LinkPreviewBlockKeys.type);
246-
expect(node.attributes[LinkPreviewBlockKeys.url], url);
247-
},
248-
);
282+
await tester.pasteContent(plainText: url, (editorState) {
283+
expect(editorState.document.root.children.length, 2);
284+
final node = editorState.getNodeAtPath([0])!;
285+
expect(node.type, LinkPreviewBlockKeys.type);
286+
expect(node.attributes[LinkPreviewBlockKeys.url], url);
287+
});
249288
},
250289
);
251290
}
@@ -256,6 +295,7 @@ extension on WidgetTester {
256295
Future<void> Function(EditorState editorState)? beforeTest,
257296
String? plainText,
258297
String? html,
298+
String? inAppJson,
259299
(String, Uint8List?)? image,
260300
}) async {
261301
await initializeAppFlowy();
@@ -271,6 +311,7 @@ extension on WidgetTester {
271311
ClipboardServiceData(
272312
plainText: plainText,
273313
html: html,
314+
inAppJson: inAppJson,
274315
image: image,
275316
),
276317
);

frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/custom_copy_command.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import 'dart:convert';
22

3+
import 'package:flutter/material.dart';
4+
35
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
46
import 'package:appflowy/startup/startup.dart';
57
import 'package:appflowy_editor/appflowy_editor.dart';
6-
import 'package:flutter/material.dart';
78

89
/// Copy.
910
///

frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/custom_paste_command.dart

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import 'package:appflowy/plugins/document/application/prelude.dart';
1+
import 'package:flutter/material.dart';
2+
3+
import 'package:appflowy/plugins/document/application/document_bloc.dart';
24
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
35
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/editor_state_paste_node_extension.dart';
46
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_html.dart';
@@ -8,12 +10,9 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_p
810
import 'package:appflowy/startup/startup.dart';
911
import 'package:appflowy_editor/appflowy_editor.dart';
1012
import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart';
11-
import 'package:flutter/material.dart';
1213
import 'package:flutter_bloc/flutter_bloc.dart';
1314
import 'package:string_validator/string_validator.dart';
1415

15-
/// Paste.
16-
///
1716
/// - support
1817
/// - desktop
1918
/// - web
@@ -43,8 +42,7 @@ CommandShortcutEventHandler _pasteCommandHandler = (editorState) {
4342
final image = data.image;
4443

4544
// paste as link preview
46-
final result = await _pasteAsLinkPreview(editorState, plainText);
47-
if (result) {
45+
if (await _pasteAsLinkPreview(editorState, plainText)) {
4846
return;
4947
}
5048

@@ -57,16 +55,14 @@ CommandShortcutEventHandler _pasteCommandHandler = (editorState) {
5755
// try to paste the content in order, if any of them is failed, then try the next one
5856
if (inAppJson != null && inAppJson.isNotEmpty) {
5957
await editorState.deleteSelectionIfNeeded();
60-
final result = await editorState.pasteInAppJson(inAppJson);
61-
if (result) {
58+
if (await editorState.pasteInAppJson(inAppJson)) {
6259
return;
6360
}
6461
}
6562

6663
if (html != null && html.isNotEmpty) {
6764
await editorState.deleteSelectionIfNeeded();
68-
final result = await editorState.pasteHtml(html);
69-
if (result) {
65+
if (await editorState.pasteHtml(html)) {
7066
return;
7167
}
7268
}

0 commit comments

Comments
 (0)