html_editor_enhanced/lib/src/widgets/html_editor_widget_mobile.dart
2022-10-30 15:25:33 +07:00

792 lines
37 KiB
Dart

import 'dart:async';
import 'dart:collection';
import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart';
import 'package:html_editor_enhanced/html_editor.dart'
hide NavigationActionPolicy, UserScript, ContextMenu;
import 'package:html_editor_enhanced/utils/utils.dart';
import 'package:visibility_detector/visibility_detector.dart';
/// The HTML Editor widget itself, for mobile (uses InAppWebView)
class HtmlEditorWidget extends StatefulWidget {
HtmlEditorWidget({
Key? key,
required this.controller,
this.callbacks,
required this.plugins,
required this.htmlEditorOptions,
required this.htmlToolbarOptions,
required this.otherOptions,
}) : super(key: key);
final HtmlEditorController controller;
final Callbacks? callbacks;
final List<Plugins> plugins;
final HtmlEditorOptions htmlEditorOptions;
final HtmlToolbarOptions htmlToolbarOptions;
final OtherOptions otherOptions;
@override
_HtmlEditorWidgetMobileState createState() => _HtmlEditorWidgetMobileState();
}
/// State for the mobile Html editor widget
///
/// A stateful widget is necessary here to allow the height to dynamically adjust.
class _HtmlEditorWidgetMobileState extends State<HtmlEditorWidget> {
/// Tracks whether the callbacks were initialized or not to prevent re-initializing them
bool callbacksInitialized = false;
/// The height of the document loaded in the editor
late double docHeight;
/// The file path to the html code
late String filePath;
/// String to use when creating the key for the widget
late String key;
/// Stream to transfer the [VisibilityInfo.visibleFraction] to the [onWindowFocus]
/// function of the webview
StreamController<double> visibleStream = StreamController<double>.broadcast();
/// Helps get the height of the toolbar to accurately adjust the height of
/// the editor when the keyboard is visible.
GlobalKey toolbarKey = GlobalKey();
/// Variable to cache the viewable size of the editor to update it in case
/// the editor is focused much after its visibility changes
double? cachedVisibleDecimal;
@override
void initState() {
docHeight = widget.otherOptions.height;
key = getRandString(10);
if (widget.htmlEditorOptions.filePath != null) {
filePath = widget.htmlEditorOptions.filePath!;
} else if (widget.plugins.isEmpty) {
filePath =
'packages/html_editor_enhanced/assets/summernote-no-plugins.html';
} else {
filePath = 'packages/html_editor_enhanced/assets/summernote.html';
}
super.initState();
}
@override
void dispose() {
visibleStream.close();
super.dispose();
}
/// resets the height of the editor to the original height
void resetHeight() async {
if (mounted) {
this.setState(() {
docHeight = widget.otherOptions.height;
});
await widget.controller.editorController!.evaluateJavascript(
source:
"\$('div.note-editable').outerHeight(${widget.otherOptions.height - (toolbarKey.currentContext?.size?.height ?? 0)});");
}
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
SystemChannels.textInput.invokeMethod('TextInput.hide');
},
child: VisibilityDetector(
key: Key(key),
onVisibilityChanged: (VisibilityInfo info) async {
if (!visibleStream.isClosed) {
cachedVisibleDecimal = info.visibleFraction == 1
? (info.size.height / widget.otherOptions.height).clamp(0, 1)
: info.visibleFraction;
visibleStream.add(info.visibleFraction == 1
? (info.size.height / widget.otherOptions.height).clamp(0, 1)
: info.visibleFraction);
}
},
child: Container(
height: docHeight + 10,
decoration: widget.otherOptions.decoration,
child: Column(
children: [
widget.htmlToolbarOptions.toolbarPosition ==
ToolbarPosition.aboveEditor
? ToolbarWidget(
key: toolbarKey,
controller: widget.controller,
htmlToolbarOptions: widget.htmlToolbarOptions,
callbacks: widget.callbacks)
: Container(height: 0, width: 0),
Expanded(
child: InAppWebView(
initialFile: filePath,
onWebViewCreated: (InAppWebViewController controller) {
widget.controller.editorController = controller;
controller.addJavaScriptHandler(
handlerName: 'FormatSettings',
callback: (e) {
var json = e[0] as Map<String, dynamic>;
print(json);
if (widget.controller.toolbar != null) {
widget.controller.toolbar!.updateToolbar(json);
}
});
},
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
javaScriptEnabled: true,
transparentBackground: true,
useShouldOverrideUrlLoading: true,
),
android: AndroidInAppWebViewOptions(
useHybridComposition: widget
.htmlEditorOptions.androidUseHybridComposition,
loadWithOverviewMode: true,
)),
initialUserScripts:
widget.htmlEditorOptions.mobileInitialScripts
as UnmodifiableListView<UserScript>?,
contextMenu: widget.htmlEditorOptions.mobileContextMenu
as ContextMenu?,
gestureRecognizers: {
Factory<VerticalDragGestureRecognizer>(
() => VerticalDragGestureRecognizer()),
Factory<LongPressGestureRecognizer>(() =>
LongPressGestureRecognizer(
duration: widget
.htmlEditorOptions.mobileLongPressDuration)),
},
shouldOverrideUrlLoading: (controller, action) async {
if (!action.request.url.toString().contains(filePath)) {
return (await widget.callbacks?.onNavigationRequestMobile
?.call(action.request.url.toString()))
as NavigationActionPolicy? ??
NavigationActionPolicy.ALLOW;
}
return NavigationActionPolicy.ALLOW;
},
onConsoleMessage: (controller, message) {
print(message.message);
},
onWindowFocus: (controller) async {
if (widget.htmlEditorOptions.shouldEnsureVisible &&
Scrollable.of(context) != null) {
await Scrollable.of(context)!.position.ensureVisible(
context.findRenderObject()!,
);
}
if (widget.htmlEditorOptions.adjustHeightForKeyboard &&
mounted &&
!visibleStream.isClosed) {
Future<void> setHeightJS() async {
await controller.evaluateJavascript(source: """
\$('div.note-editable').outerHeight(${max(docHeight - (toolbarKey.currentContext?.size?.height ?? 0), 30)});
// from https://stackoverflow.com/a/67152280
var selection = window.getSelection();
if (selection.rangeCount) {
var firstRange = selection.getRangeAt(0);
if (firstRange.commonAncestorContainer !== document) {
var tempAnchorEl = document.createElement('br');
firstRange.insertNode(tempAnchorEl);
tempAnchorEl.scrollIntoView({
block: 'end',
});
tempAnchorEl.remove();
}
}
""");
}
/// this is a workaround so jumping between focus on different
/// editable elements still resizes the editor
if ((cachedVisibleDecimal ?? 0) > 0.1) {
this.setState(() {
docHeight = widget.otherOptions.height *
cachedVisibleDecimal!;
});
await setHeightJS();
}
var visibleDecimal = await visibleStream.stream.first;
var newHeight = widget.otherOptions.height;
if (visibleDecimal > 0.1) {
this.setState(() {
docHeight = newHeight * visibleDecimal;
});
//todo add support for traditional summernote controls again?
await setHeightJS();
}
}
},
onLoadStop:
(InAppWebViewController controller, Uri? uri) async {
var url = uri.toString();
var maximumFileSize = 10485760;
if (url.contains(filePath)) {
var summernoteToolbar = '[\n';
var summernoteCallbacks = '''callbacks: {
onKeydown: function(e) {
var chars = \$(".note-editable").text();
var totalChars = chars.length;
${widget.htmlEditorOptions.characterLimit != null ? '''allowedKeys = (
e.which === 8 || /* BACKSPACE */
e.which === 35 || /* END */
e.which === 36 || /* HOME */
e.which === 37 || /* LEFT */
e.which === 38 || /* UP */
e.which === 39 || /* RIGHT*/
e.which === 40 || /* DOWN */
e.which === 46 || /* DEL*/
e.ctrlKey === true && e.which === 65 || /* CTRL + A */
e.ctrlKey === true && e.which === 88 || /* CTRL + X */
e.ctrlKey === true && e.which === 67 || /* CTRL + C */
e.ctrlKey === true && e.which === 86 || /* CTRL + V */
e.ctrlKey === true && e.which === 90 /* CTRL + Z */
);
if (!allowedKeys && \$(e.target).text().length >= ${widget.htmlEditorOptions.characterLimit}) {
e.preventDefault();
}''' : ''}
window.flutter_inappwebview.callHandler('totalChars', totalChars);
},
''';
if (widget.plugins.isNotEmpty) {
summernoteToolbar = summernoteToolbar + "['plugins', [";
for (var p in widget.plugins) {
summernoteToolbar = summernoteToolbar +
(p.getToolbarString().isNotEmpty
? "'${p.getToolbarString()}'"
: '') +
(p == widget.plugins.last
? ']]\n'
: p.getToolbarString().isNotEmpty
? ', '
: '');
if (p is SummernoteAtMention) {
summernoteCallbacks = summernoteCallbacks +
"""
\nsummernoteAtMention: {
getSuggestions: async function(value) {
var result = await window.flutter_inappwebview.callHandler('getSuggestions', value);
var resultList = result.split(',');
return resultList;
},
onSelect: (value) => {
window.flutter_inappwebview.callHandler('onSelectMention', value);
},
},
""";
controller.addJavaScriptHandler(
handlerName: 'getSuggestions',
callback: (value) {
return p.getSuggestionsMobile!
.call(value.first.toString())
.toString()
.replaceAll('[', '')
.replaceAll(']', '');
});
if (p.onSelect != null) {
controller.addJavaScriptHandler(
handlerName: 'onSelectMention',
callback: (value) {
p.onSelect!.call(value.first.toString());
});
}
}
}
}
if (widget.callbacks != null) {
if (widget.callbacks!.onImageLinkInsert != null) {
summernoteCallbacks = summernoteCallbacks +
"""
onImageLinkInsert: function(url) {
window.flutter_inappwebview.callHandler('onImageLinkInsert', url);
},
""";
}
if (widget.callbacks!.onImageUpload != null) {
summernoteCallbacks = summernoteCallbacks +
"""
onImageUpload: function(files) {
var reader = new FileReader();
var base64 = "<an error occurred>";
reader.onload = function (_) {
base64 = reader.result;
var newObject = {
'lastModified': files[0].lastModified,
'lastModifiedDate': files[0].lastModifiedDate,
'name': files[0].name,
'size': files[0].size,
'type': files[0].type,
'base64': base64
};
window.flutter_inappwebview.callHandler('onImageUpload', JSON.stringify(newObject));
};
reader.onerror = function (_) {
var newObject = {
'lastModified': files[0].lastModified,
'lastModifiedDate': files[0].lastModifiedDate,
'name': files[0].name,
'size': files[0].size,
'type': files[0].type,
'base64': base64
};
window.flutter_inappwebview.callHandler('onImageUpload', JSON.stringify(newObject));
};
reader.readAsDataURL(files[0]);
},
""";
}
if (widget.callbacks!.onImageUploadError != null) {
summernoteCallbacks = summernoteCallbacks +
"""
onImageUploadError: function(file, error) {
if (typeof file === 'string') {
window.flutter_inappwebview.callHandler('onImageUploadError', file, error);
} else {
var newObject = {
'lastModified': file.lastModified,
'lastModifiedDate': file.lastModifiedDate,
'name': file.name,
'size': file.size,
'type': file.type,
};
window.flutter_inappwebview.callHandler('onImageUploadError', JSON.stringify(newObject), error);
}
},
""";
}
}
summernoteToolbar = summernoteToolbar + '],';
summernoteCallbacks = summernoteCallbacks + '}';
await controller.evaluateJavascript(source: """
\$('#summernote-2').summernote({
placeholder: "${widget.htmlEditorOptions.hint ?? ""}",
tabsize: 2,
height: ${widget.otherOptions.height - (toolbarKey.currentContext?.size?.height ?? 0)},
toolbar: $summernoteToolbar
disableGrammar: false,
spellCheck: ${widget.htmlEditorOptions.spellCheck},
maximumFileSize: $maximumFileSize,
${widget.htmlEditorOptions.customOptions}
$summernoteCallbacks
});
\$('#summernote-2').on('summernote.change', function(_, contents, \$editable) {
window.flutter_inappwebview.callHandler('onChangeContent', contents);
});
function onSelectionChange() {
let {anchorNode, anchorOffset, focusNode, focusOffset} = document.getSelection();
var isBold = false;
var isItalic = false;
var isUnderline = false;
var isStrikethrough = false;
var isSuperscript = false;
var isSubscript = false;
var isUL = false;
var isOL = false;
var isLeft = false;
var isRight = false;
var isCenter = false;
var isFull = false;
var parent;
var fontName;
var fontSize = 16;
var foreColor = "000000";
var backColor = "FFFF00";
var focusNode2 = \$(window.getSelection().focusNode);
var parentList = focusNode2.closest("div.note-editable ol, div.note-editable ul");
var parentListType = parentList.css('list-style-type');
var lineHeight = \$(focusNode.parentNode).css('line-height');
var direction = \$(focusNode.parentNode).css('direction');
if (document.queryCommandState) {
isBold = document.queryCommandState('bold');
isItalic = document.queryCommandState('italic');
isUnderline = document.queryCommandState('underline');
isStrikethrough = document.queryCommandState('strikeThrough');
isSuperscript = document.queryCommandState('superscript');
isSubscript = document.queryCommandState('subscript');
isUL = document.queryCommandState('insertUnorderedList');
isOL = document.queryCommandState('insertOrderedList');
isLeft = document.queryCommandState('justifyLeft');
isRight = document.queryCommandState('justifyRight');
isCenter = document.queryCommandState('justifyCenter');
isFull = document.queryCommandState('justifyFull');
}
if (document.queryCommandValue) {
parent = document.queryCommandValue('formatBlock');
fontSize = document.queryCommandValue('fontSize');
foreColor = document.queryCommandValue('foreColor');
backColor = document.queryCommandValue('hiliteColor');
fontName = document.queryCommandValue('fontName');
}
var message = {
'style': parent,
'fontName': fontName,
'fontSize': fontSize,
'font': [isBold, isItalic, isUnderline],
'miscFont': [isStrikethrough, isSuperscript, isSubscript],
'color': [foreColor, backColor],
'paragraph': [isUL, isOL],
'listStyle': parentListType,
'align': [isLeft, isCenter, isRight, isFull],
'lineHeight': lineHeight,
'direction': direction,
};
window.flutter_inappwebview.callHandler('FormatSettings', message);
}
""");
await controller.evaluateJavascript(
source:
"document.onselectionchange = onSelectionChange; console.log('done');");
await controller.evaluateJavascript(
source:
"document.getElementsByClassName('note-editable')[0].setAttribute('inputmode', '${describeEnum(widget.htmlEditorOptions.inputType)}');");
if ((Theme.of(context).brightness == Brightness.dark ||
widget.htmlEditorOptions.darkMode == true) &&
widget.htmlEditorOptions.darkMode != false) {
//todo fix for iOS (https://github.com/pichillilorenzo/flutter_inappwebview/issues/695)
var darkCSS =
'<link href=\"${(widget.htmlEditorOptions.filePath != null ? "file:///android_asset/flutter_assets/packages/html_editor_enhanced/assets/" : "") + "summernote-lite-dark.css"}\" rel=\"stylesheet\">';
await controller.evaluateJavascript(
source: "\$('head').append('$darkCSS');");
}
//set the text once the editor is loaded
if (widget.htmlEditorOptions.initialText != null) {
widget.controller
.setText(widget.htmlEditorOptions.initialText!);
}
//adjusts the height of the editor when it is loaded
if (widget.htmlEditorOptions.autoAdjustHeight) {
controller.addJavaScriptHandler(
handlerName: 'setHeight',
callback: (height) {
if (height.first == 'reset') {
resetHeight();
} else {
setState(mounted, this.setState, () {
docHeight = (double.tryParse(
height.first.toString()) ??
widget.otherOptions.height) +
(toolbarKey
.currentContext?.size?.height ??
0);
});
}
});
await controller.evaluateJavascript(
source:
"var height = document.body.scrollHeight; window.flutter_inappwebview.callHandler('setHeight', height);");
}
//reset the editor's height if the keyboard disappears at any point
if (widget.htmlEditorOptions.adjustHeightForKeyboard) {
var keyboardVisibilityController =
KeyboardVisibilityController();
keyboardVisibilityController.onChange
.listen((bool visible) {
if (!visible && mounted) {
controller.clearFocus();
resetHeight();
}
});
}
widget.controller.editorController!.addJavaScriptHandler(
handlerName: 'totalChars',
callback: (keyCode) {
widget.controller.characterCount =
keyCode.first as int;
});
//disable editor if necessary
if (widget.htmlEditorOptions.disabled &&
!callbacksInitialized) {
widget.controller.disable();
}
//initialize callbacks
if (widget.callbacks != null && !callbacksInitialized) {
addJSCallbacks(widget.callbacks!);
addJSHandlers(widget.callbacks!);
callbacksInitialized = true;
}
//call onInit callback
if (widget.callbacks != null &&
widget.callbacks!.onInit != null) {
widget.callbacks!.onInit!.call();
}
//add onChange handler
controller.addJavaScriptHandler(
handlerName: 'onChangeContent',
callback: (contents) {
if (widget.htmlEditorOptions.shouldEnsureVisible &&
Scrollable.of(context) != null) {
Scrollable.of(context)!.position.ensureVisible(
context.findRenderObject()!,
);
}
if (widget.callbacks != null &&
widget.callbacks!.onChangeContent != null) {
widget.callbacks!.onChangeContent!
.call(contents.first.toString());
}
});
}
},
),
),
widget.htmlToolbarOptions.toolbarPosition ==
ToolbarPosition.belowEditor
? ToolbarWidget(
key: toolbarKey,
controller: widget.controller,
htmlToolbarOptions: widget.htmlToolbarOptions,
callbacks: widget.callbacks)
: Container(height: 0, width: 0),
],
),
),
),
);
}
/// adds the callbacks set by the user into the scripts
void addJSCallbacks(Callbacks c) {
if (c.onBeforeCommand != null) {
widget.controller.editorController!.evaluateJavascript(source: """
\$('#summernote-2').on('summernote.before.command', function(_, contents) {
window.flutter_inappwebview.callHandler('onBeforeCommand', contents);
});
""");
}
if (c.onChangeCodeview != null) {
widget.controller.editorController!.evaluateJavascript(source: """
\$('#summernote-2').on('summernote.change.codeview', function(_, contents, \$editable) {
window.flutter_inappwebview.callHandler('onChangeCodeview', contents);
});
""");
}
if (c.onDialogShown != null) {
widget.controller.editorController!.evaluateJavascript(source: """
\$('#summernote-2').on('summernote.dialog.shown', function() {
window.flutter_inappwebview.callHandler('onDialogShown', 'fired');
});
""");
}
if (c.onEnter != null) {
widget.controller.editorController!.evaluateJavascript(source: """
\$('#summernote-2').on('summernote.enter', function() {
window.flutter_inappwebview.callHandler('onEnter', 'fired');
});
""");
}
if (c.onFocus != null) {
widget.controller.editorController!.evaluateJavascript(source: """
\$('#summernote-2').on('summernote.focus', function() {
window.flutter_inappwebview.callHandler('onFocus', 'fired');
});
""");
}
if (c.onBlur != null) {
widget.controller.editorController!.evaluateJavascript(source: """
\$('#summernote-2').on('summernote.blur', function() {
window.flutter_inappwebview.callHandler('onBlur', 'fired');
});
""");
}
if (c.onBlurCodeview != null) {
widget.controller.editorController!.evaluateJavascript(source: """
\$('#summernote-2').on('summernote.blur.codeview', function() {
window.flutter_inappwebview.callHandler('onBlurCodeview', 'fired');
});
""");
}
if (c.onKeyDown != null) {
widget.controller.editorController!.evaluateJavascript(source: """
\$('#summernote-2').on('summernote.keydown', function(_, e) {
window.flutter_inappwebview.callHandler('onKeyDown', e.keyCode);
});
""");
}
if (c.onKeyUp != null) {
widget.controller.editorController!.evaluateJavascript(source: """
\$('#summernote-2').on('summernote.keyup', function(_, e) {
window.flutter_inappwebview.callHandler('onKeyUp', e.keyCode);
});
""");
}
if (c.onMouseDown != null) {
widget.controller.editorController!.evaluateJavascript(source: """
\$('#summernote-2').on('summernote.mousedown', function(_) {
window.flutter_inappwebview.callHandler('onMouseDown', 'fired');
});
""");
}
if (c.onMouseUp != null) {
widget.controller.editorController!.evaluateJavascript(source: """
\$('#summernote-2').on('summernote.mouseup', function(_) {
window.flutter_inappwebview.callHandler('onMouseUp', 'fired');
});
""");
}
if (c.onPaste != null) {
widget.controller.editorController!.evaluateJavascript(source: """
\$('#summernote-2').on('summernote.paste', function(_) {
window.flutter_inappwebview.callHandler('onPaste', 'fired');
});
""");
}
if (c.onScroll != null) {
widget.controller.editorController!.evaluateJavascript(source: """
\$('#summernote-2').on('summernote.scroll', function(_) {
window.flutter_inappwebview.callHandler('onScroll', 'fired');
});
""");
}
}
/// creates flutter_inappwebview JavaScript Handlers to handle any callbacks the
/// user has defined
void addJSHandlers(Callbacks c) {
if (c.onBeforeCommand != null) {
widget.controller.editorController!.addJavaScriptHandler(
handlerName: 'onBeforeCommand',
callback: (contents) {
c.onBeforeCommand!.call(contents.first.toString());
});
}
if (c.onChangeCodeview != null) {
widget.controller.editorController!.addJavaScriptHandler(
handlerName: 'onChangeCodeview',
callback: (contents) {
c.onChangeCodeview!.call(contents.first.toString());
});
}
if (c.onDialogShown != null) {
widget.controller.editorController!.addJavaScriptHandler(
handlerName: 'onDialogShown',
callback: (_) {
c.onDialogShown!.call();
});
}
if (c.onEnter != null) {
widget.controller.editorController!.addJavaScriptHandler(
handlerName: 'onEnter',
callback: (_) {
c.onEnter!.call();
});
}
if (c.onFocus != null) {
widget.controller.editorController!.addJavaScriptHandler(
handlerName: 'onFocus',
callback: (_) {
c.onFocus!.call();
});
}
if (c.onBlur != null) {
widget.controller.editorController!.addJavaScriptHandler(
handlerName: 'onBlur',
callback: (_) {
c.onBlur!.call();
});
}
if (c.onBlurCodeview != null) {
widget.controller.editorController!.addJavaScriptHandler(
handlerName: 'onBlurCodeview',
callback: (_) {
c.onBlurCodeview!.call();
});
}
if (c.onImageLinkInsert != null) {
widget.controller.editorController!.addJavaScriptHandler(
handlerName: 'onImageLinkInsert',
callback: (url) {
c.onImageLinkInsert!.call(url.first.toString());
});
}
if (c.onImageUpload != null) {
widget.controller.editorController!.addJavaScriptHandler(
handlerName: 'onImageUpload',
callback: (files) {
var file = fileUploadFromJson(files.first);
c.onImageUpload!.call(file);
});
}
if (c.onImageUploadError != null) {
widget.controller.editorController!.addJavaScriptHandler(
handlerName: 'onImageUploadError',
callback: (args) {
if (!args.first.toString().startsWith('{')) {
c.onImageUploadError!.call(
null,
args.first,
args.last.contains('base64')
? UploadError.jsException
: args.last.contains('unsupported')
? UploadError.unsupportedFile
: UploadError.exceededMaxSize);
} else {
var file = fileUploadFromJson(args.first.toString());
c.onImageUploadError!.call(
file,
null,
args.last.contains('base64')
? UploadError.jsException
: args.last.contains('unsupported')
? UploadError.unsupportedFile
: UploadError.exceededMaxSize);
}
});
}
if (c.onKeyDown != null) {
widget.controller.editorController!.addJavaScriptHandler(
handlerName: 'onKeyDown',
callback: (keyCode) {
c.onKeyDown!.call(keyCode.first);
});
}
if (c.onKeyUp != null) {
widget.controller.editorController!.addJavaScriptHandler(
handlerName: 'onKeyUp',
callback: (keyCode) {
c.onKeyUp!.call(keyCode.first);
});
}
if (c.onMouseDown != null) {
widget.controller.editorController!.addJavaScriptHandler(
handlerName: 'onMouseDown',
callback: (_) {
c.onMouseDown!.call();
});
}
if (c.onMouseUp != null) {
widget.controller.editorController!.addJavaScriptHandler(
handlerName: 'onMouseUp',
callback: (_) {
c.onMouseUp!.call();
});
}
if (c.onPaste != null) {
widget.controller.editorController!.addJavaScriptHandler(
handlerName: 'onPaste',
callback: (_) {
c.onPaste!.call();
});
}
if (c.onScroll != null) {
widget.controller.editorController!.addJavaScriptHandler(
handlerName: 'onScroll',
callback: (_) {
c.onScroll!.call();
});
}
}
}