diff --git a/core/lib/utils/html/html_utils.dart b/core/lib/utils/html/html_utils.dart index d0c04dff4..614cb97ea 100644 --- a/core/lib/utils/html/html_utils.dart +++ b/core/lib/utils/html/html_utils.dart @@ -235,6 +235,7 @@ class HtmlUtils { if (selection) { selection.removeAllRanges(); selection.addRange(window._savedRange); + delete window._savedRange; return selection.toString(); } } @@ -242,6 +243,17 @@ class HtmlUtils { })();''', name: 'restoreSelection'); + static const getSavedSelection = ( + script: ''' + (() => { + if(window._savedRange) { + return window._savedRange.toString(); + } else { + return ""; + } + })();''', + name: 'getSavedSelection'); + static const clearSavedSelection = ( script: ''' (() => { diff --git a/lib/features/composer/presentation/extensions/ai_scribe/handle_ai_scribe_in_composer_extension.dart b/lib/features/composer/presentation/extensions/ai_scribe/handle_ai_scribe_in_composer_extension.dart index 34a7ada6c..4178e8d8d 100644 --- a/lib/features/composer/presentation/extensions/ai_scribe/handle_ai_scribe_in_composer_extension.dart +++ b/lib/features/composer/presentation/extensions/ai_scribe/handle_ai_scribe_in_composer_extension.dart @@ -34,11 +34,7 @@ extension HandleAiScribeInComposerExtension on ComposerController { } } - Future insertTextInEditor(String text) async { - if (PlatformInfo.isMobile) { - await ensureMobileEditorFocused(); - } - + Future insertTextInEditor(String text) async { try { final htmlContent = StringConvert.convertTextContentToHtmlContent(text); @@ -57,6 +53,18 @@ extension HandleAiScribeInComposerExtension on ComposerController { } } + Future setTextInEditor(String text) async { + try { + if (PlatformInfo.isWeb) { + richTextWebController?.editorController.setText(text); + } else { + await richTextMobileTabletController?.htmlEditorApi?.setText(text); + } + } catch (e) { + logError('$runtimeType::insertTextInEditor:Exception = $e'); + } + } + Future collapseSelection() async { try { if (PlatformInfo.isWeb) { @@ -117,6 +125,28 @@ extension HandleAiScribeInComposerExtension on ComposerController { } } + Future getSavedSelection() async { + try { + if (PlatformInfo.isWeb) { + final result = await richTextWebController?.editorController.evaluateJavascriptWeb( + HtmlUtils.getSavedSelection.name, + hasReturnValue: true, + ); + return result; + } else { + final result = await richTextMobileTabletController?.htmlEditorApi?.webViewController + .evaluateJavascript( + source: HtmlUtils.getSavedSelection.script, + ); + return result; + } + } catch (e) { + logError('$runtimeType::getSavedSelection:Exception = $e'); + return ""; + } + } + + Future clearSavedSelection() async { try { if (PlatformInfo.isWeb) { @@ -158,21 +188,15 @@ extension HandleAiScribeInComposerExtension on ComposerController { } } - Future clearTextInEditor() async { - try { - if (PlatformInfo.isWeb) { - richTextWebController?.editorController.setText(''); - } else { - await richTextMobileTabletController?.htmlEditorApi?.setText(''); - } - } catch (e) { - logWarning('$runtimeType::clearTextInEditor:Exception = $e'); - } - } - - // Ensure we only insert at cursor position by collapsing selection before inserting Future onInsertTextCallback(String text) async { + if (PlatformInfo.isMobile) { + await ensureMobileEditorFocused(); + + await restoreSelection(); + } + await collapseSelection(); + await insertTextInEditor(text); } @@ -180,13 +204,25 @@ extension HandleAiScribeInComposerExtension on ComposerController { Future onReplaceTextCallback(String text) async { final selection = editorTextSelection.value?.selectedText; - final isSelectionRestored = PlatformInfo.isMobile ? await restoreSelection() : ""; + final savedSelection = PlatformInfo.isMobile ? await getSavedSelection() : ""; - if ((selection == null || selection.isEmpty) && isSelectionRestored.isEmpty) { - await clearTextInEditor(); + final shouldReplaceEverything = (selection == null || selection.isEmpty) && savedSelection.isEmpty; + + if (shouldReplaceEverything) { + try { + await setTextInEditor(text); + } catch (e) { + logError('$runtimeType::onReplaceTextCallback:Exception = $e'); + } + } else { + if (PlatformInfo.isMobile) { + await ensureMobileEditorFocused(); + + await restoreSelection(); + } + + await insertTextInEditor(text); } - - await insertTextInEditor(text); } Future openAIAssistantModal(Offset? position, Size? size) async {