Fix more Scribe mobile issues

- Add getSavedSelection to check saved selection without consuming it
- Distinguish between setText (replace all) and insertText (at cursor) operations
- Fix replace callback to use setText when no selection exists
- Clean up saved selection state after restoration in HTML utils
- Move mobile editor focus and selection restore to appropriate callbacks
This commit is contained in:
Théo Poizat
2026-01-21 16:19:04 +01:00
committed by Dat H. Pham
parent be7f28f270
commit b5606626f4
2 changed files with 71 additions and 23 deletions
+12
View File
@@ -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: '''
(() => {
@@ -34,11 +34,7 @@ extension HandleAiScribeInComposerExtension on ComposerController {
}
}
Future<void> insertTextInEditor(String text) async {
if (PlatformInfo.isMobile) {
await ensureMobileEditorFocused();
}
Future<void> insertTextInEditor(String text) async {
try {
final htmlContent = StringConvert.convertTextContentToHtmlContent(text);
@@ -57,6 +53,18 @@ extension HandleAiScribeInComposerExtension on ComposerController {
}
}
Future<void> 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<void> collapseSelection() async {
try {
if (PlatformInfo.isWeb) {
@@ -117,6 +125,28 @@ extension HandleAiScribeInComposerExtension on ComposerController {
}
}
Future<String> 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<void> clearSavedSelection() async {
try {
if (PlatformInfo.isWeb) {
@@ -158,21 +188,15 @@ extension HandleAiScribeInComposerExtension on ComposerController {
}
}
Future<void> 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<void> 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<void> 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<void> openAIAssistantModal(Offset? position, Size? size) async {