From 5f4e3ecc76ef495af99aaa764cf5ab2fb9941c87 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Tue, 12 Sep 2023 15:53:39 +0800 Subject: [PATCH] fix: unable to paste texts contains section or font tag (#3379) --- .../document_copy_and_paste_test.dart | 26 +++++++++++++++++ .../editor_plugins/actions/option_action.dart | 3 +- .../callout/callout_block_component.dart | 2 +- .../copy_and_paste/custom_paste_command.dart | 28 +++++++++++++++---- .../copy_and_paste/paste_from_html.dart | 6 ++-- .../copy_and_paste/paste_from_image.dart | 6 ++-- .../paste_from_in_app_json.dart | 6 ++-- .../editor_plugins/header/cover_editor.dart | 5 ++-- .../header/document_header_node_widget.dart | 2 +- .../outline/outline_block_component.dart | 2 +- .../table/table_option_action.dart | 2 +- frontend/appflowy_flutter/pubspec.lock | 4 +-- frontend/appflowy_flutter/pubspec.yaml | 2 +- 13 files changed, 70 insertions(+), 24 deletions(-) diff --git a/frontend/appflowy_flutter/integration_test/document/document_copy_and_paste_test.dart b/frontend/appflowy_flutter/integration_test/document/document_copy_and_paste_test.dart index 6a48f6c5fa..c4c3484192 100644 --- a/frontend/appflowy_flutter/integration_test/document/document_copy_and_paste_test.dart +++ b/frontend/appflowy_flutter/integration_test/document/document_copy_and_paste_test.dart @@ -250,6 +250,32 @@ void main() { ); }, ); + + testWidgets('paste the html content contains section', (tester) async { + const html = + '''
AppFlowy
Hello World
'''; + await tester.pasteContent( + html: html, + (editorState) { + expect(editorState.document.root.children.length, 2); + final node1 = editorState.getNodeAtPath([0])!; + final node2 = editorState.getNodeAtPath([1])!; + expect(node1.type, ParagraphBlockKeys.type); + expect(node2.type, ParagraphBlockKeys.type); + }, + ); + }); + + testWidgets('paste the html from google translation', (tester) async { + const html = + '''
new force
Assessment focus: potential motivations, empathy

➢Personality characteristics and potential motivations:
-Reflection of self-worth
-Need to be respected
-Have a unique definition of success
-Be true to your own lifestyle
'''; + await tester.pasteContent( + html: html, + (editorState) { + expect(editorState.document.root.children.length, 8); + }, + ); + }); } extension on WidgetTester { diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/option_action.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/option_action.dart index 13aa48412e..71a785df12 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/option_action.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/option_action.dart @@ -5,7 +5,6 @@ import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:easy_localization/easy_localization.dart'; - import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; @@ -243,7 +242,7 @@ class ColorOptionAction extends PopoverActionCell { } final bgColor = node.attributes[blockComponentBackgroundColor] as String?; - final selectedColor = bgColor?.toColor(); + final selectedColor = bgColor?.tryToColor(); // get default background color from themeExtension final defaultColor = AFThemeExtension.of(context).calloutBGColor; final colors = [ diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/callout/callout_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/callout/callout_block_component.dart index a074681707..0d0f4c0a6f 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/callout/callout_block_component.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/callout/callout_block_component.dart @@ -139,7 +139,7 @@ class _CalloutBlockComponentWidgetState Color get backgroundColor { final colorString = node.attributes[CalloutBlockKeys.backgroundColor] as String; - return colorString.toColor() ?? Colors.transparent; + return colorString.tryToColor() ?? Colors.transparent; } // get the emoji of the note block from the node's attributes or default to '📌' diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/custom_paste_command.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/custom_paste_command.dart index 4b6d402dd0..19dc9d4486 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/custom_paste_command.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/custom_paste_command.dart @@ -43,16 +43,32 @@ CommandShortcutEventHandler _pasteCommandHandler = (editorState) { // 3. image // 4. plain text + // try to paste the content in order, if any of them is failed, then try the next one if (inAppJson != null && inAppJson.isNotEmpty) { await editorState.deleteSelectionIfNeeded(); - await editorState.pasteInAppJson(inAppJson); - } else if (html != null && html.isNotEmpty) { + final result = await editorState.pasteInAppJson(inAppJson); + if (result) { + return; + } + } + + if (html != null && html.isNotEmpty) { await editorState.deleteSelectionIfNeeded(); - await editorState.pasteHtml(html); - } else if (image != null && image.$2?.isNotEmpty == true) { + final result = await editorState.pasteHtml(html); + if (result) { + return; + } + } + + if (image != null && image.$2?.isNotEmpty == true) { await editorState.deleteSelectionIfNeeded(); - await editorState.pasteImage(image.$1, image.$2!); - } else if (plainText != null && plainText.isNotEmpty) { + final result = await editorState.pasteImage(image.$1, image.$2!); + if (result) { + return; + } + } + + if (plainText != null && plainText.isNotEmpty) { await editorState.pastePlainText(plainText); } }(); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_html.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_html.dart index cddc26b1c6..e30bcf8a5d 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_html.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_html.dart @@ -2,7 +2,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_p import 'package:appflowy_editor/appflowy_editor.dart'; extension PasteFromHtml on EditorState { - Future pasteHtml(String html) async { + Future pasteHtml(String html) async { final nodes = htmlToDocument(html).root.children.toList(); // remove the front and back empty line while (nodes.isNotEmpty && nodes.first.delta?.isEmpty == true) { @@ -11,13 +11,15 @@ extension PasteFromHtml on EditorState { while (nodes.isNotEmpty && nodes.last.delta?.isEmpty == true) { nodes.removeLast(); } + // if there's no nodes being converted successfully, return false if (nodes.isEmpty) { - return; + return false; } if (nodes.length == 1) { await pasteSingleLineNode(nodes.first); } else { await pasteMultiLineNodes(nodes.toList()); } + return true; } } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_image.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_image.dart index c926591c95..5d0919b18c 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_image.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_image.dart @@ -15,9 +15,9 @@ extension PasteFromImage on EditorState { 'gif', ]; - Future pasteImage(String format, Uint8List imageBytes) async { + Future pasteImage(String format, Uint8List imageBytes) async { if (!supportedImageFormats.contains(format)) { - return; + return false; } final path = await getIt().getPath(); @@ -37,8 +37,10 @@ extension PasteFromImage on EditorState { ); await File(copyToPath).writeAsBytes(imageBytes); await insertImageNode(copyToPath); + return true; } catch (e) { Log.error('cannot copy image file', e); } + return false; } } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_in_app_json.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_in_app_json.dart index c36114ee4b..4cc17d599b 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_in_app_json.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_in_app_json.dart @@ -5,21 +5,23 @@ import 'package:appflowy_backend/log.dart'; import 'package:appflowy_editor/appflowy_editor.dart' hide Log; extension PasteFromInAppJson on EditorState { - Future pasteInAppJson(String inAppJson) async { + Future pasteInAppJson(String inAppJson) async { try { final nodes = Document.fromJson(jsonDecode(inAppJson)).root.children; if (nodes.isEmpty) { - return; + return false; } if (nodes.length == 1) { await pasteSingleLineNode(nodes.first); } else { await pasteMultiLineNodes(nodes.toList()); } + return true; } catch (e) { Log.error( 'Failed to paste in app json: $inAppJson, error: $e', ); } + return false; } } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/cover_editor.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/cover_editor.dart index e00f8c56c7..d3d68fae9a 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/cover_editor.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/cover_editor.dart @@ -6,7 +6,6 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:easy_localization/easy_localization.dart'; - import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; @@ -536,7 +535,7 @@ class ColorItem extends StatelessWidget { dimension: 25, child: Container( decoration: BoxDecoration( - color: option.colorHex.toColor(), + color: option.colorHex.tryToColor(), shape: BoxShape.circle, ), child: isChecked @@ -548,7 +547,7 @@ class ColorItem extends StatelessWidget { color: Theme.of(context).cardColor, width: 3.0, ), - color: option.colorHex.toColor(), + color: option.colorHex.tryToColor(), shape: BoxShape.circle, ), ), diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/document_header_node_widget.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/document_header_node_widget.dart index 7d420e9b0b..adaca3c844 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/document_header_node_widget.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/document_header_node_widget.dart @@ -367,7 +367,7 @@ class DocumentCoverState extends State { fit: BoxFit.cover, ); case CoverType.color: - final color = widget.coverDetails?.toColor() ?? Colors.white; + final color = widget.coverDetails?.tryToColor() ?? Colors.white; return Container(color: color); case CoverType.none: return const SizedBox.shrink(); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/outline/outline_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/outline/outline_block_component.dart index d168e1b4a5..b1ad9e172e 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/outline/outline_block_component.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/outline/outline_block_component.dart @@ -84,7 +84,7 @@ class _OutlineBlockWidgetState extends State if (colorString == null) { return Colors.transparent; } - return colorString.toColor() ?? Colors.transparent; + return colorString.tryToColor() ?? Colors.transparent; } late EditorState editorState = context.read(); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/table/table_option_action.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/table/table_option_action.dart index 3b08a65435..e3f0d4ca4a 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/table/table_option_action.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/table/table_option_action.dart @@ -112,7 +112,7 @@ class TableColorOptionAction extends PopoverActionCell { ? TableCellBlockKeys.colBackgroundColor : TableCellBlockKeys.rowBackgroundColor; final bgColor = cell?.attributes[key] as String?; - final selectedColor = bgColor?.toColor(); + final selectedColor = bgColor?.tryToColor(); // get default background color from themeExtension final defaultColor = AFThemeExtension.of(context).tableCellBGColor; final colors = [ diff --git a/frontend/appflowy_flutter/pubspec.lock b/frontend/appflowy_flutter/pubspec.lock index cf76ec647f..b35303d219 100644 --- a/frontend/appflowy_flutter/pubspec.lock +++ b/frontend/appflowy_flutter/pubspec.lock @@ -54,8 +54,8 @@ packages: dependency: "direct main" description: path: "." - ref: "4a92c88" - resolved-ref: "4a92c88b6611af95909e4618be3970cc20f6d930" + ref: "6d68f90" + resolved-ref: "6d68f9003fa023d215dc5f20e8900f985c2cdaa1" url: "https://github.com/AppFlowy-IO/appflowy-editor.git" source: git version: "1.3.0" diff --git a/frontend/appflowy_flutter/pubspec.yaml b/frontend/appflowy_flutter/pubspec.yaml index c4e08f9df4..ce2e7e1152 100644 --- a/frontend/appflowy_flutter/pubspec.yaml +++ b/frontend/appflowy_flutter/pubspec.yaml @@ -48,7 +48,7 @@ dependencies: appflowy_editor: git: url: https://github.com/AppFlowy-IO/appflowy-editor.git - ref: 4a92c88 + ref: 6d68f90 appflowy_popover: path: packages/appflowy_popover