Skip to content

Commit ea1e0f7

Browse files
committed
fix: edit link will break the text styles
1 parent 8b1eb49 commit ea1e0f7

File tree

3 files changed

+116
-36
lines changed

3 files changed

+116
-36
lines changed

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

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,5 +366,77 @@ void main() {
366366
expect(getLinkFromNode(node), link);
367367
expect(getNodeText(node), afterText);
368368
});
369+
370+
testWidgets('edit link text with style', (tester) async {
371+
Attributes getAttribute(Node node, Selection selection) {
372+
Attributes attributes = {};
373+
final ops = node.delta?.whereType<TextInsert>() ?? [];
374+
final startOffset = selection.start.offset;
375+
var start = 0;
376+
for (final op in ops) {
377+
if (start > startOffset) break;
378+
final length = op.length;
379+
if (start + length > startOffset) {
380+
attributes = op.attributes ?? {};
381+
break;
382+
}
383+
start += length;
384+
}
385+
386+
return attributes;
387+
}
388+
389+
const text = 'edit text with style', link = 'https://test.appflowy.cloud';
390+
await prepareForToolbar(tester, text);
391+
final constSelection =
392+
Selection.single(path: [0], startOffset: 0, endOffset: text.length);
393+
394+
final bold = find.byFlowySvg(FlowySvgs.toolbar_bold_m),
395+
italic = find.byFlowySvg(FlowySvgs.toolbar_inline_italic_m),
396+
underline = find.byFlowySvg(FlowySvgs.toolbar_underline_m);
397+
await tester.tapButton(bold);
398+
await tester.tapButton(italic);
399+
await tester.tapButton(underline);
400+
401+
Node node = tester.editor.getNodeAtPath([0]);
402+
Attributes attributes = getAttribute(node, constSelection);
403+
expect(attributes, {
404+
AppFlowyRichTextKeys.bold: true,
405+
AppFlowyRichTextKeys.italic: true,
406+
AppFlowyRichTextKeys.underline: true,
407+
});
408+
409+
/// tap link button to show CreateLinkMenu
410+
final linkButton = find.byFlowySvg(FlowySvgs.toolbar_link_m);
411+
await tester.tapButton(linkButton);
412+
413+
/// search for page and select it
414+
final textField = find.descendant(
415+
of: find.byType(LinkCreateMenu),
416+
matching: find.byType(TextFormField),
417+
);
418+
await tester.enterText(textField, gettingStarted);
419+
await tester.pumpAndSettle();
420+
await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
421+
await tester.simulateKeyEvent(LogicalKeyboardKey.escape);
422+
423+
node = tester.editor.getNodeAtPath([0]);
424+
attributes = getAttribute(node, constSelection);
425+
expect(isPageLink(node), true);
426+
expect(getLinkFromNode(node) == link, false);
427+
expect(attributes[AppFlowyRichTextKeys.bold], true);
428+
expect(attributes[AppFlowyRichTextKeys.italic], true);
429+
expect(attributes[AppFlowyRichTextKeys.underline], true);
430+
431+
/// remove link
432+
await tester.hoverOnWidget(find.byType(LinkHoverTrigger));
433+
await tester.tapButton(find.byFlowySvg(FlowySvgs.toolbar_link_unlink_m));
434+
node = tester.editor.getNodeAtPath([0]);
435+
attributes = getAttribute(node, constSelection);
436+
expect(getLinkFromNode(node) == link, false);
437+
expect(attributes[AppFlowyRichTextKeys.bold], true);
438+
expect(attributes[AppFlowyRichTextKeys.italic], true);
439+
expect(attributes[AppFlowyRichTextKeys.underline], true);
440+
});
369441
});
370442
}

frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_extension.dart

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,32 +8,65 @@ extension LinkExtension on EditorState {
88
if (node == null) {
99
return;
1010
}
11+
final attributes = _getAttribute(node, selection)
12+
..remove(BuiltInAttributeKey.href)
13+
..remove(kIsPageLink);
1114
final index = selection.normalized.startIndex;
1215
final length = selection.length;
1316
final transaction = this.transaction
14-
..formatText(
15-
node,
16-
index,
17-
length,
18-
{
19-
BuiltInAttributeKey.href: null,
20-
kIsPageLink: null,
21-
},
22-
);
17+
..formatText(node, index, length, attributes);
2318
apply(transaction);
2419
}
2520

2621
void applyLink(Selection selection, LinkInfo info) {
2722
final node = getNodeAtPath(selection.start.path);
2823
if (node == null) return;
2924
final transaction = this.transaction;
25+
final attributes = _getAttribute(node, selection);
26+
attributes.addAll(info.toAttribute());
3027
transaction.replaceText(
3128
node,
3229
selection.startIndex,
3330
selection.length,
3431
info.name,
35-
attributes: info.toAttribute(),
32+
attributes: attributes,
3633
);
3734
apply(transaction);
3835
}
36+
37+
void removeAndReplaceLink(
38+
Selection selection,
39+
String text,
40+
) {
41+
final node = getNodeAtPath(selection.end.path);
42+
if (node == null) {
43+
return;
44+
}
45+
final attributes = _getAttribute(node, selection)
46+
..remove(BuiltInAttributeKey.href)
47+
..remove(kIsPageLink);
48+
final index = selection.normalized.startIndex;
49+
final length = selection.length;
50+
final transaction = this.transaction
51+
..replaceText(node, index, length, text, attributes: attributes);
52+
apply(transaction);
53+
}
54+
55+
Attributes _getAttribute(Node node, Selection selection) {
56+
Attributes attributes = {};
57+
final ops = node.delta?.whereType<TextInsert>() ?? [];
58+
final startOffset = selection.start.offset;
59+
var start = 0;
60+
for (final op in ops) {
61+
if (start > startOffset) break;
62+
final length = op.length;
63+
if (start + length > startOffset) {
64+
attributes = op.attributes ?? {};
65+
break;
66+
}
67+
start += length;
68+
}
69+
70+
return attributes;
71+
}
3972
}

frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_hover_menu.dart

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ class _LinkHoverTriggerState extends State<LinkHoverTrigger> {
187187
onDismiss: () => editMenuController.close(),
188188
onApply: (info) => editorState.applyLink(selection, info),
189189
onRemoveLink: (linkinfo) =>
190-
onRemoveAndReplaceLink(editorState, selection, linkinfo.name),
190+
editorState.removeAndReplaceLink(selection, linkinfo.name),
191191
),
192192
child: child,
193193
);
@@ -266,31 +266,6 @@ class _LinkHoverTriggerState extends State<LinkHoverTrigger> {
266266
);
267267
}
268268
}
269-
270-
void onRemoveAndReplaceLink(
271-
EditorState editorState,
272-
Selection selection,
273-
String text,
274-
) {
275-
final node = editorState.getNodeAtPath(selection.end.path);
276-
if (node == null) {
277-
return;
278-
}
279-
final index = selection.normalized.startIndex;
280-
final length = selection.length;
281-
final transaction = editorState.transaction
282-
..replaceText(
283-
node,
284-
index,
285-
length,
286-
text,
287-
attributes: {
288-
BuiltInAttributeKey.href: null,
289-
kIsPageLink: null,
290-
},
291-
);
292-
editorState.apply(transaction);
293-
}
294269
}
295270

296271
class LinkHoverMenu extends StatefulWidget {

0 commit comments

Comments
 (0)