Add text selection mixin
This mixin leverages HTML callbacks that can be set by WebView editor to send text selection event to the composer so the composer will receive text selection event necessary to display buttons related to text selection.
This commit is contained in:
@@ -48,6 +48,80 @@ class HtmlUtils {
|
||||
editor.parentNode.replaceChild(newEditor, editor);''',
|
||||
name: 'unregisterDropListener');
|
||||
|
||||
static const registerSelectionChangeListener = (
|
||||
script: '''
|
||||
let lastSelectedText = '';
|
||||
|
||||
document.addEventListener('selectionchange', function() {
|
||||
const selection = window.getSelection();
|
||||
const selectedText = selection ? selection.toString().trim() : '';
|
||||
|
||||
if (selectedText === lastSelectedText) {
|
||||
return;
|
||||
}
|
||||
lastSelectedText = selectedText;
|
||||
|
||||
if (selectedText.length > 0 && selection.rangeCount > 0) {
|
||||
const range = selection.getRangeAt(0);
|
||||
const rects = range.getClientRects();
|
||||
|
||||
if (rects.length > 0) {
|
||||
const rect = rects[rects.length - 1];
|
||||
|
||||
// When iframe
|
||||
if (window.parent) {
|
||||
window.parent.postMessage(
|
||||
JSON.stringify({
|
||||
name: 'onSelectionChange',
|
||||
hasSelection: true,
|
||||
selectedText: selectedText,
|
||||
coordinates: {
|
||||
x: rect.right,
|
||||
y: rect.bottom,
|
||||
width: rect.width,
|
||||
height: rect.height
|
||||
}
|
||||
}),
|
||||
"*"
|
||||
)
|
||||
}
|
||||
|
||||
// When WebView
|
||||
if (window.flutter_inappwebview) {
|
||||
window.flutter_inappwebview.callHandler('onSelectionChange', {
|
||||
hasSelection: true,
|
||||
selectedText: selectedText,
|
||||
coordinates: {
|
||||
x: rect.right,
|
||||
y: rect.bottom,
|
||||
width: rect.width,
|
||||
height: rect.height
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// When iframe
|
||||
if (window.parent) {
|
||||
window.parent.postMessage(
|
||||
JSON.stringify({
|
||||
name: 'onSelectionChange',
|
||||
hasSelection: false
|
||||
}),
|
||||
"*"
|
||||
)
|
||||
}
|
||||
|
||||
// When WebView
|
||||
if (window.flutter_inappwebview) {
|
||||
window.flutter_inappwebview.callHandler('onSelectionChange', {
|
||||
hasSelection: false
|
||||
});
|
||||
}
|
||||
}
|
||||
});''',
|
||||
name: 'onSelectionChange');
|
||||
|
||||
static recalculateEditorHeight({double? maxHeight}) => (
|
||||
script: '''
|
||||
const editable = document.querySelector('.note-editable');
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class TextSelectionData {
|
||||
final bool hasSelection;
|
||||
final String? selectedText;
|
||||
final TextSelectionCoordinates? coordinates;
|
||||
|
||||
const TextSelectionData({
|
||||
required this.hasSelection,
|
||||
this.selectedText,
|
||||
this.coordinates,
|
||||
});
|
||||
|
||||
factory TextSelectionData.empty() {
|
||||
return const TextSelectionData(hasSelection: false);
|
||||
}
|
||||
|
||||
factory TextSelectionData.fromMap(Map<dynamic, dynamic> data) {
|
||||
try {
|
||||
final hasSelection = data['hasSelection'] as bool? ?? false;
|
||||
|
||||
if (!hasSelection) {
|
||||
return TextSelectionData.empty();
|
||||
}
|
||||
|
||||
final selectedText = data['selectedText'] as String?;
|
||||
final coordinatesData = data['coordinates'];
|
||||
|
||||
if (coordinatesData != null && selectedText != null) {
|
||||
final coordinates = TextSelectionCoordinates.fromMap(coordinatesData);
|
||||
return TextSelectionData(
|
||||
hasSelection: true,
|
||||
selectedText: selectedText,
|
||||
coordinates: coordinates,
|
||||
);
|
||||
}
|
||||
|
||||
return TextSelectionData.empty();
|
||||
} catch (e) {
|
||||
return TextSelectionData.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TextSelectionCoordinates {
|
||||
final double x;
|
||||
final double y;
|
||||
final double width;
|
||||
final double height;
|
||||
|
||||
const TextSelectionCoordinates({
|
||||
required this.x,
|
||||
required this.y,
|
||||
required this.width,
|
||||
required this.height,
|
||||
});
|
||||
|
||||
factory TextSelectionCoordinates.fromMap(Map<dynamic, dynamic> data) {
|
||||
return TextSelectionCoordinates(
|
||||
x: (data['x'] as num?)?.toDouble() ?? 0.0,
|
||||
y: (data['y'] as num?)?.toDouble() ?? 0.0,
|
||||
width: (data['width'] as num?)?.toDouble() ?? 0.0,
|
||||
height: (data['height'] as num?)?.toDouble() ?? 0.0,
|
||||
);
|
||||
}
|
||||
|
||||
Offset get position => Offset(x, y);
|
||||
}
|
||||
|
||||
mixin TextSelectionMixin<T extends StatefulWidget> on State<T> {
|
||||
void Function(TextSelectionData?)? get onSelectionChanged => null;
|
||||
|
||||
void handleSelectionChange(Map<dynamic, dynamic> data) {
|
||||
final selectionData = TextSelectionData.fromMap(data);
|
||||
onSelectionChanged?.call(selectionData);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user