diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/commands/format_built_in_text.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/commands/format_built_in_text.dart index 30e5fcf17e..e9fe907e7d 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/commands/format_built_in_text.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/commands/format_built_in_text.dart @@ -3,29 +3,25 @@ import 'package:appflowy_editor/src/document/attributes.dart'; import 'package:appflowy_editor/src/document/built_in_attribute_keys.dart'; import 'package:appflowy_editor/src/document/node.dart'; import 'package:appflowy_editor/src/document/path.dart'; +import 'package:appflowy_editor/src/document/selection.dart'; import 'package:appflowy_editor/src/editor_state.dart'; Future formatBuiltInTextAttributes( EditorState editorState, String key, Attributes attributes, { + Selection? selection, Path? path, TextNode? textNode, }) async { + final result = getTextNodeToBeFormatted( + editorState, + path: path, + textNode: textNode, + ); if (BuiltInAttributeKey.globalStyleKeys.contains(key)) { - assert(!(path != null && textNode != null)); - assert(!(path == null && textNode == null)); - - TextNode formattedTextNode; - if (textNode != null) { - formattedTextNode = textNode; - } else if (path != null) { - formattedTextNode = editorState.document.nodeAtPath(path) as TextNode; - } else { - throw Exception('path and textNode cannot be null at the same time'); - } // remove all the existing style - final newAttributes = formattedTextNode.attributes + final newAttributes = result.attributes ..removeWhere((key, value) { if (BuiltInAttributeKey.globalStyleKeys.contains(key)) { return true; @@ -41,6 +37,13 @@ Future formatBuiltInTextAttributes( newAttributes, textNode: textNode, ); + } else if (BuiltInAttributeKey.partialStyleKeys.contains(key)) { + return updateTextNodeDeltaAttributes( + editorState, + selection, + attributes, + textNode: textNode, + ); } } @@ -60,3 +63,20 @@ Future formatTextToCheckbox( textNode: textNode, ); } + +Future formatLinkInText( + EditorState editorState, + String? link, { + Path? path, + TextNode? textNode, +}) async { + return formatBuiltInTextAttributes( + editorState, + BuiltInAttributeKey.href, + { + BuiltInAttributeKey.href: link, + }, + path: path, + textNode: textNode, + ); +} diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/commands/format_text.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/commands/format_text.dart index 41eb8c16e6..ad6a9bbfc0 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/commands/format_text.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/commands/format_text.dart @@ -1,6 +1,9 @@ +import 'dart:async'; + import 'package:appflowy_editor/src/document/attributes.dart'; import 'package:appflowy_editor/src/document/node.dart'; import 'package:appflowy_editor/src/document/path.dart'; +import 'package:appflowy_editor/src/document/selection.dart'; import 'package:appflowy_editor/src/editor_state.dart'; import 'package:appflowy_editor/src/operation/transaction_builder.dart'; import 'package:flutter/widgets.dart'; @@ -11,24 +14,90 @@ Future updateTextNodeAttributes( Path? path, TextNode? textNode, }) async { - assert(!(path != null && textNode != null)); - assert(!(path == null && textNode == null)); + final result = getTextNodeToBeFormatted( + editorState, + path: path, + textNode: textNode, + ); - TextNode formattedTextNode; - if (textNode != null) { - formattedTextNode = textNode; - } else if (path != null) { - formattedTextNode = editorState.document.nodeAtPath(path) as TextNode; - } else { - throw Exception('path and textNode cannot be null at the same time'); - } + final completer = Completer(); TransactionBuilder(editorState) - ..updateNode(formattedTextNode, attributes) + ..updateNode(result, attributes) ..commit(); WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - print('AAAAAAAAAAAAAA'); - return; + completer.complete(); }); + + return completer.future; +} + +Future updateTextNodeDeltaAttributes( + EditorState editorState, + Selection? selection, + Attributes attributes, { + Path? path, + TextNode? textNode, +}) { + final result = getTextNodeToBeFormatted( + editorState, + path: path, + textNode: textNode, + ); + final newSelection = _getSelection(editorState, selection: selection); + + final completer = Completer(); + + TransactionBuilder(editorState) + ..formatText( + result, + newSelection.startIndex, + newSelection.length, + attributes, + ) + ..commit(); + + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + completer.complete(); + }); + + return completer.future; +} + +// get formatted [TextNode] +TextNode getTextNodeToBeFormatted( + EditorState editorState, { + Path? path, + TextNode? textNode, +}) { + assert(!(path != null && textNode != null)); + assert(!(path == null && textNode == null)); + + TextNode result; + if (textNode != null) { + result = textNode; + } else if (path != null) { + result = editorState.document.nodeAtPath(path) as TextNode; + } else { + throw Exception('path and textNode cannot be null at the same time'); + } + return result; +} + +Selection _getSelection( + EditorState editorState, { + Selection? selection, +}) { + final currentSelection = + editorState.service.selectionService.currentSelection.value; + Selection result; + if (selection != null) { + result = selection; + } else if (currentSelection != null) { + result = currentSelection; + } else { + throw Exception('path and textNode cannot be null at the same time'); + } + return result; } diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/document/selection.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/document/selection.dart index ea451b46dd..99248dc167 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/document/selection.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/document/selection.dart @@ -53,6 +53,10 @@ class Selection { Selection get reversed => copyWith(start: end, end: start); + int get startIndex => normalize.start.offset; + int get endIndex => normalize.end.offset; + int get length => endIndex - startIndex; + Selection collapse({bool atStart = false}) { if (atStart) { return Selection(start: start, end: start); diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/checkbox_text.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/checkbox_text.dart index e41f3b4891..10b17d6b36 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/checkbox_text.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/checkbox_text.dart @@ -74,8 +74,8 @@ class _CheckboxNodeWidgetState extends State padding: iconPadding, name: check ? 'check' : 'uncheck', ), - onTap: () { - formatTextToCheckbox( + onTap: () async { + await formatTextToCheckbox( widget.editorState, !check, textNode: widget.textNode, diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_item.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_item.dart index 68bb5023ca..6407f81cb3 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_item.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_item.dart @@ -1,4 +1,5 @@ import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_editor/src/commands/format_built_in_text.dart'; import 'package:appflowy_editor/src/extensions/url_launcher_extension.dart'; import 'package:appflowy_editor/src/infra/flowy_svg.dart'; import 'package:appflowy_editor/src/render/link_menu/link_menu.dart'; @@ -345,11 +346,8 @@ void showLinkMenu( onOpenLink: () async { await safeLaunchUrl(linkText); }, - onSubmitted: (text) { - TransactionBuilder(editorState) - ..formatText( - textNode, index, length, {BuiltInAttributeKey.href: text}) - ..commit(); + onSubmitted: (text) async { + await formatLinkInText(editorState, text, textNode: textNode); _dismissLinkMenu(); }, onCopyLink: () { @@ -377,6 +375,7 @@ void showLinkMenu( Overlay.of(context)?.insert(_linkMenuOverlay!); editorState.service.scrollService?.disable(); + editorState.service.keyboardService?.disable(); editorState.service.selectionService.currentSelection .addListener(_dismissLinkMenu); }