From ea1e0f7c37105666090b13017db9384a164a3c71 Mon Sep 17 00:00:00 2001 From: Morn Date: Wed, 30 Apr 2025 11:27:52 +0800 Subject: [PATCH 1/2] fix: edit link will break the text styles --- .../document/document_toolbar_test.dart | 72 +++++++++++++++++++ .../desktop_toolbar/link/link_extension.dart | 53 +++++++++++--- .../desktop_toolbar/link/link_hover_menu.dart | 27 +------ 3 files changed, 116 insertions(+), 36 deletions(-) diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_toolbar_test.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_toolbar_test.dart index f455cd479d854..3a95643ee625e 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/document/document_toolbar_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_toolbar_test.dart @@ -366,5 +366,77 @@ void main() { expect(getLinkFromNode(node), link); expect(getNodeText(node), afterText); }); + + testWidgets('edit link text with style', (tester) async { + Attributes getAttribute(Node node, Selection selection) { + Attributes attributes = {}; + final ops = node.delta?.whereType() ?? []; + final startOffset = selection.start.offset; + var start = 0; + for (final op in ops) { + if (start > startOffset) break; + final length = op.length; + if (start + length > startOffset) { + attributes = op.attributes ?? {}; + break; + } + start += length; + } + + return attributes; + } + + const text = 'edit text with style', link = 'https://test.appflowy.cloud'; + await prepareForToolbar(tester, text); + final constSelection = + Selection.single(path: [0], startOffset: 0, endOffset: text.length); + + final bold = find.byFlowySvg(FlowySvgs.toolbar_bold_m), + italic = find.byFlowySvg(FlowySvgs.toolbar_inline_italic_m), + underline = find.byFlowySvg(FlowySvgs.toolbar_underline_m); + await tester.tapButton(bold); + await tester.tapButton(italic); + await tester.tapButton(underline); + + Node node = tester.editor.getNodeAtPath([0]); + Attributes attributes = getAttribute(node, constSelection); + expect(attributes, { + AppFlowyRichTextKeys.bold: true, + AppFlowyRichTextKeys.italic: true, + AppFlowyRichTextKeys.underline: true, + }); + + /// tap link button to show CreateLinkMenu + final linkButton = find.byFlowySvg(FlowySvgs.toolbar_link_m); + await tester.tapButton(linkButton); + + /// search for page and select it + final textField = find.descendant( + of: find.byType(LinkCreateMenu), + matching: find.byType(TextFormField), + ); + await tester.enterText(textField, gettingStarted); + await tester.pumpAndSettle(); + await tester.simulateKeyEvent(LogicalKeyboardKey.enter); + await tester.simulateKeyEvent(LogicalKeyboardKey.escape); + + node = tester.editor.getNodeAtPath([0]); + attributes = getAttribute(node, constSelection); + expect(isPageLink(node), true); + expect(getLinkFromNode(node) == link, false); + expect(attributes[AppFlowyRichTextKeys.bold], true); + expect(attributes[AppFlowyRichTextKeys.italic], true); + expect(attributes[AppFlowyRichTextKeys.underline], true); + + /// remove link + await tester.hoverOnWidget(find.byType(LinkHoverTrigger)); + await tester.tapButton(find.byFlowySvg(FlowySvgs.toolbar_link_unlink_m)); + node = tester.editor.getNodeAtPath([0]); + attributes = getAttribute(node, constSelection); + expect(getLinkFromNode(node) == link, false); + expect(attributes[AppFlowyRichTextKeys.bold], true); + expect(attributes[AppFlowyRichTextKeys.italic], true); + expect(attributes[AppFlowyRichTextKeys.underline], true); + }); }); } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_extension.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_extension.dart index 9232c97276c55..6a9359643a903 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_extension.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_extension.dart @@ -8,18 +8,13 @@ extension LinkExtension on EditorState { if (node == null) { return; } + final attributes = _getAttribute(node, selection) + ..remove(BuiltInAttributeKey.href) + ..remove(kIsPageLink); final index = selection.normalized.startIndex; final length = selection.length; final transaction = this.transaction - ..formatText( - node, - index, - length, - { - BuiltInAttributeKey.href: null, - kIsPageLink: null, - }, - ); + ..formatText(node, index, length, attributes); apply(transaction); } @@ -27,13 +22,51 @@ extension LinkExtension on EditorState { final node = getNodeAtPath(selection.start.path); if (node == null) return; final transaction = this.transaction; + final attributes = _getAttribute(node, selection); + attributes.addAll(info.toAttribute()); transaction.replaceText( node, selection.startIndex, selection.length, info.name, - attributes: info.toAttribute(), + attributes: attributes, ); apply(transaction); } + + void removeAndReplaceLink( + Selection selection, + String text, + ) { + final node = getNodeAtPath(selection.end.path); + if (node == null) { + return; + } + final attributes = _getAttribute(node, selection) + ..remove(BuiltInAttributeKey.href) + ..remove(kIsPageLink); + final index = selection.normalized.startIndex; + final length = selection.length; + final transaction = this.transaction + ..replaceText(node, index, length, text, attributes: attributes); + apply(transaction); + } + + Attributes _getAttribute(Node node, Selection selection) { + Attributes attributes = {}; + final ops = node.delta?.whereType() ?? []; + final startOffset = selection.start.offset; + var start = 0; + for (final op in ops) { + if (start > startOffset) break; + final length = op.length; + if (start + length > startOffset) { + attributes = op.attributes ?? {}; + break; + } + start += length; + } + + return attributes; + } } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_hover_menu.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_hover_menu.dart index b11a73daff5b1..7f74d96838390 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_hover_menu.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_hover_menu.dart @@ -187,7 +187,7 @@ class _LinkHoverTriggerState extends State { onDismiss: () => editMenuController.close(), onApply: (info) => editorState.applyLink(selection, info), onRemoveLink: (linkinfo) => - onRemoveAndReplaceLink(editorState, selection, linkinfo.name), + editorState.removeAndReplaceLink(selection, linkinfo.name), ), child: child, ); @@ -266,31 +266,6 @@ class _LinkHoverTriggerState extends State { ); } } - - void onRemoveAndReplaceLink( - EditorState editorState, - Selection selection, - String text, - ) { - final node = editorState.getNodeAtPath(selection.end.path); - if (node == null) { - return; - } - final index = selection.normalized.startIndex; - final length = selection.length; - final transaction = editorState.transaction - ..replaceText( - node, - index, - length, - text, - attributes: { - BuiltInAttributeKey.href: null, - kIsPageLink: null, - }, - ); - editorState.apply(transaction); - } } class LinkHoverMenu extends StatefulWidget { From 4651316e7583cbc0b241f30863187d60d81aeb98 Mon Sep 17 00:00:00 2001 From: Morn Date: Wed, 30 Apr 2025 13:47:12 +0800 Subject: [PATCH 2/2] fix: remove key error --- .../desktop_toolbar/link/link_extension.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_extension.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_extension.dart index 6a9359643a903..47762555d030e 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_extension.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_extension.dart @@ -8,9 +8,9 @@ extension LinkExtension on EditorState { if (node == null) { return; } - final attributes = _getAttribute(node, selection) - ..remove(BuiltInAttributeKey.href) - ..remove(kIsPageLink); + final attributes = _getAttribute(node, selection); + attributes[BuiltInAttributeKey.href] = null; + attributes[kIsPageLink] = null; final index = selection.normalized.startIndex; final length = selection.length; final transaction = this.transaction @@ -42,9 +42,9 @@ extension LinkExtension on EditorState { if (node == null) { return; } - final attributes = _getAttribute(node, selection) - ..remove(BuiltInAttributeKey.href) - ..remove(kIsPageLink); + final attributes = _getAttribute(node, selection); + attributes[BuiltInAttributeKey.href] = null; + attributes[kIsPageLink] = null; final index = selection.normalized.startIndex; final length = selection.length; final transaction = this.transaction