From be7f28f27078a762745f7468f29d82480a97a5d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Poizat?= Date: Thu, 15 Jan 2026 17:12:07 +0100 Subject: [PATCH] Fix Scribe mobile issues - Fix Scribe replace on mobile when opened from menu bar (to be complete this fix needs this PR https://github.com/Enough-Software/enough_html_editor/pull/37) - Ensure all mobile editor call are awaited - Do not clear text if a selection has been restored (which mean we replace a selection) - Avoid collapseToEnd crash --- core/lib/utils/html/html_utils.dart | 11 ++--- .../rich_text_mobile_tablet_controller.dart | 5 ++- ...andle_ai_scribe_in_composer_extension.dart | 44 +++++++++---------- 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/core/lib/utils/html/html_utils.dart b/core/lib/utils/html/html_utils.dart index 45d9d4619..d0c04dff4 100644 --- a/core/lib/utils/html/html_utils.dart +++ b/core/lib/utils/html/html_utils.dart @@ -197,7 +197,7 @@ class HtmlUtils { script: ''' (() => { const selection = window.getSelection(); - if (selection) { + if (selection && selection.rangeCount > 0) { selection.collapseToEnd() } })();''', @@ -220,9 +220,10 @@ class HtmlUtils { const selection = window.getSelection(); if (selection && selection.rangeCount > 0) { window._savedRange = selection.getRangeAt(0).cloneRange(); - return true; + return selection.toString(); } - return false; + delete window._savedRange; + return ""; })();''', name: 'saveSelection'); @@ -234,10 +235,10 @@ class HtmlUtils { if (selection) { selection.removeAllRanges(); selection.addRange(window._savedRange); - return true; + return selection.toString(); } } - return false; + return ""; })();''', name: 'restoreSelection'); diff --git a/lib/features/composer/presentation/controller/rich_text_mobile_tablet_controller.dart b/lib/features/composer/presentation/controller/rich_text_mobile_tablet_controller.dart index 0008c233b..37d764d2c 100644 --- a/lib/features/composer/presentation/controller/rich_text_mobile_tablet_controller.dart +++ b/lib/features/composer/presentation/controller/rich_text_mobile_tablet_controller.dart @@ -21,7 +21,10 @@ class RichTextMobileTabletController extends GetxController { Future focus() async { try { - await htmlEditorApi?.webViewController.evaluateJavascript(source: "document.getElementById('editor').focus();"); + await htmlEditorApi?.webViewController.evaluateJavascript(source: ''' + (() => { + document.getElementById('editor').focus(); + })();'''); } catch (e) { logWarning('RichTextMobileTabletController::focus:Exception: $e'); } 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 27ea9a451..34a7ada6c 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 @@ -35,6 +35,10 @@ extension HandleAiScribeInComposerExtension on ComposerController { } Future insertTextInEditor(String text) async { + if (PlatformInfo.isMobile) { + await ensureMobileEditorFocused(); + } + try { final htmlContent = StringConvert.convertTextContentToHtmlContent(text); @@ -46,7 +50,7 @@ extension HandleAiScribeInComposerExtension on ComposerController { richTextWebController?.editorController.insertHtml(htmlContent); } else { - richTextMobileTabletController?.htmlEditorApi?.insertHtml(htmlContent); + await richTextMobileTabletController?.htmlEditorApi?.insertHtml(htmlContent); } } catch (e) { logWarning('$runtimeType::insertTextInEditor:Exception = $e'); @@ -71,45 +75,45 @@ extension HandleAiScribeInComposerExtension on ComposerController { } } - Future saveSelection() async { + Future saveSelection() async { try { if (PlatformInfo.isWeb) { final result = await richTextWebController?.editorController.evaluateJavascriptWeb( HtmlUtils.saveSelection.name, hasReturnValue: true, - ) ?? false; + ); return result; } else { final result = await richTextMobileTabletController?.htmlEditorApi?.webViewController .evaluateJavascript( source: HtmlUtils.saveSelection.script, - ) ?? false; + ); return result; } } catch (e) { logError('$runtimeType::saveSelection:Exception = $e'); - return false; + return ""; } } - Future restoreSelection() async { + Future restoreSelection() async { try { if (PlatformInfo.isWeb) { final result = await richTextWebController?.editorController.evaluateJavascriptWeb( HtmlUtils.restoreSelection.name, hasReturnValue: true, - ) ?? false; + ); return result; } else { final result = await richTextMobileTabletController?.htmlEditorApi?.webViewController .evaluateJavascript( source: HtmlUtils.restoreSelection.script, - ) ?? false; + ); return result; } } catch (e) { logError('$runtimeType::restoreSelection:Exception = $e'); - return false; + return ""; } } @@ -142,10 +146,8 @@ extension HandleAiScribeInComposerExtension on ComposerController { } Future saveAndUnfocusForModal() async { - final saved = await saveSelection(); - if (saved) { - await unfocusEditor(); - } + await saveSelection(); + await unfocusEditor(); } Future ensureMobileEditorFocused() async { @@ -156,12 +158,12 @@ extension HandleAiScribeInComposerExtension on ComposerController { } } - void clearTextInEditor() { + Future clearTextInEditor() async { try { if (PlatformInfo.isWeb) { richTextWebController?.editorController.setText(''); } else { - richTextMobileTabletController?.htmlEditorApi?.setText(''); + await richTextMobileTabletController?.htmlEditorApi?.setText(''); } } catch (e) { logWarning('$runtimeType::clearTextInEditor:Exception = $e'); @@ -178,12 +180,10 @@ extension HandleAiScribeInComposerExtension on ComposerController { Future onReplaceTextCallback(String text) async { final selection = editorTextSelection.value?.selectedText; - if (PlatformInfo.isMobile) { - await restoreSelection(); - } + final isSelectionRestored = PlatformInfo.isMobile ? await restoreSelection() : ""; - if (selection == null || selection.isEmpty) { - clearTextInEditor(); + if ((selection == null || selection.isEmpty) && isSelectionRestored.isEmpty) { + await clearTextInEditor(); } await insertTextInEditor(text); @@ -215,10 +215,6 @@ extension HandleAiScribeInComposerExtension on ComposerController { AiScribeSuggestionActions action, String suggestionText, ) async { - if (PlatformInfo.isMobile) { - await ensureMobileEditorFocused(); - } - switch (action) { case AiScribeSuggestionActions.replace: await onReplaceTextCallback(suggestionText);