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
This commit is contained in:
Théo Poizat
2026-01-15 17:12:07 +01:00
committed by Dat H. Pham
parent abd2453511
commit be7f28f270
3 changed files with 30 additions and 30 deletions
+6 -5
View File
@@ -197,7 +197,7 @@ class HtmlUtils {
script: ''' script: '''
(() => { (() => {
const selection = window.getSelection(); const selection = window.getSelection();
if (selection) { if (selection && selection.rangeCount > 0) {
selection.collapseToEnd() selection.collapseToEnd()
} }
})();''', })();''',
@@ -220,9 +220,10 @@ class HtmlUtils {
const selection = window.getSelection(); const selection = window.getSelection();
if (selection && selection.rangeCount > 0) { if (selection && selection.rangeCount > 0) {
window._savedRange = selection.getRangeAt(0).cloneRange(); window._savedRange = selection.getRangeAt(0).cloneRange();
return true; return selection.toString();
} }
return false; delete window._savedRange;
return "";
})();''', })();''',
name: 'saveSelection'); name: 'saveSelection');
@@ -234,10 +235,10 @@ class HtmlUtils {
if (selection) { if (selection) {
selection.removeAllRanges(); selection.removeAllRanges();
selection.addRange(window._savedRange); selection.addRange(window._savedRange);
return true; return selection.toString();
} }
} }
return false; return "";
})();''', })();''',
name: 'restoreSelection'); name: 'restoreSelection');
@@ -21,7 +21,10 @@ class RichTextMobileTabletController extends GetxController {
Future<void> focus() async { Future<void> focus() async {
try { try {
await htmlEditorApi?.webViewController.evaluateJavascript(source: "document.getElementById('editor').focus();"); await htmlEditorApi?.webViewController.evaluateJavascript(source: '''
(() => {
document.getElementById('editor').focus();
})();''');
} catch (e) { } catch (e) {
logWarning('RichTextMobileTabletController::focus:Exception: $e'); logWarning('RichTextMobileTabletController::focus:Exception: $e');
} }
@@ -35,6 +35,10 @@ extension HandleAiScribeInComposerExtension on ComposerController {
} }
Future<void> insertTextInEditor(String text) async { Future<void> insertTextInEditor(String text) async {
if (PlatformInfo.isMobile) {
await ensureMobileEditorFocused();
}
try { try {
final htmlContent = StringConvert.convertTextContentToHtmlContent(text); final htmlContent = StringConvert.convertTextContentToHtmlContent(text);
@@ -46,7 +50,7 @@ extension HandleAiScribeInComposerExtension on ComposerController {
richTextWebController?.editorController.insertHtml(htmlContent); richTextWebController?.editorController.insertHtml(htmlContent);
} else { } else {
richTextMobileTabletController?.htmlEditorApi?.insertHtml(htmlContent); await richTextMobileTabletController?.htmlEditorApi?.insertHtml(htmlContent);
} }
} catch (e) { } catch (e) {
logWarning('$runtimeType::insertTextInEditor:Exception = $e'); logWarning('$runtimeType::insertTextInEditor:Exception = $e');
@@ -71,45 +75,45 @@ extension HandleAiScribeInComposerExtension on ComposerController {
} }
} }
Future<bool> saveSelection() async { Future<String> saveSelection() async {
try { try {
if (PlatformInfo.isWeb) { if (PlatformInfo.isWeb) {
final result = await richTextWebController?.editorController.evaluateJavascriptWeb( final result = await richTextWebController?.editorController.evaluateJavascriptWeb(
HtmlUtils.saveSelection.name, HtmlUtils.saveSelection.name,
hasReturnValue: true, hasReturnValue: true,
) ?? false; );
return result; return result;
} else { } else {
final result = await richTextMobileTabletController?.htmlEditorApi?.webViewController final result = await richTextMobileTabletController?.htmlEditorApi?.webViewController
.evaluateJavascript( .evaluateJavascript(
source: HtmlUtils.saveSelection.script, source: HtmlUtils.saveSelection.script,
) ?? false; );
return result; return result;
} }
} catch (e) { } catch (e) {
logError('$runtimeType::saveSelection:Exception = $e'); logError('$runtimeType::saveSelection:Exception = $e');
return false; return "";
} }
} }
Future<bool> restoreSelection() async { Future<String> restoreSelection() async {
try { try {
if (PlatformInfo.isWeb) { if (PlatformInfo.isWeb) {
final result = await richTextWebController?.editorController.evaluateJavascriptWeb( final result = await richTextWebController?.editorController.evaluateJavascriptWeb(
HtmlUtils.restoreSelection.name, HtmlUtils.restoreSelection.name,
hasReturnValue: true, hasReturnValue: true,
) ?? false; );
return result; return result;
} else { } else {
final result = await richTextMobileTabletController?.htmlEditorApi?.webViewController final result = await richTextMobileTabletController?.htmlEditorApi?.webViewController
.evaluateJavascript( .evaluateJavascript(
source: HtmlUtils.restoreSelection.script, source: HtmlUtils.restoreSelection.script,
) ?? false; );
return result; return result;
} }
} catch (e) { } catch (e) {
logError('$runtimeType::restoreSelection:Exception = $e'); logError('$runtimeType::restoreSelection:Exception = $e');
return false; return "";
} }
} }
@@ -142,10 +146,8 @@ extension HandleAiScribeInComposerExtension on ComposerController {
} }
Future<void> saveAndUnfocusForModal() async { Future<void> saveAndUnfocusForModal() async {
final saved = await saveSelection(); await saveSelection();
if (saved) { await unfocusEditor();
await unfocusEditor();
}
} }
Future<void> ensureMobileEditorFocused() async { Future<void> ensureMobileEditorFocused() async {
@@ -156,12 +158,12 @@ extension HandleAiScribeInComposerExtension on ComposerController {
} }
} }
void clearTextInEditor() { Future<void> clearTextInEditor() async {
try { try {
if (PlatformInfo.isWeb) { if (PlatformInfo.isWeb) {
richTextWebController?.editorController.setText(''); richTextWebController?.editorController.setText('');
} else { } else {
richTextMobileTabletController?.htmlEditorApi?.setText(''); await richTextMobileTabletController?.htmlEditorApi?.setText('');
} }
} catch (e) { } catch (e) {
logWarning('$runtimeType::clearTextInEditor:Exception = $e'); logWarning('$runtimeType::clearTextInEditor:Exception = $e');
@@ -178,12 +180,10 @@ extension HandleAiScribeInComposerExtension on ComposerController {
Future<void> onReplaceTextCallback(String text) async { Future<void> onReplaceTextCallback(String text) async {
final selection = editorTextSelection.value?.selectedText; final selection = editorTextSelection.value?.selectedText;
if (PlatformInfo.isMobile) { final isSelectionRestored = PlatformInfo.isMobile ? await restoreSelection() : "";
await restoreSelection();
}
if (selection == null || selection.isEmpty) { if ((selection == null || selection.isEmpty) && isSelectionRestored.isEmpty) {
clearTextInEditor(); await clearTextInEditor();
} }
await insertTextInEditor(text); await insertTextInEditor(text);
@@ -215,10 +215,6 @@ extension HandleAiScribeInComposerExtension on ComposerController {
AiScribeSuggestionActions action, AiScribeSuggestionActions action,
String suggestionText, String suggestionText,
) async { ) async {
if (PlatformInfo.isMobile) {
await ensureMobileEditorFocused();
}
switch (action) { switch (action) {
case AiScribeSuggestionActions.replace: case AiScribeSuggestionActions.replace:
await onReplaceTextCallback(suggestionText); await onReplaceTextCallback(suggestionText);