html_editor_enhanced_fork_latex 3.2.0
html_editor_enhanced_fork_latex: ^3.2.0 copied to clipboard
HTML rich text editor for Android, iOS, and Web, using the Summernote library. Enhanced with highly customizable widget-based controls, bug fixes, callbacks, dark mode, and more.
import 'dart:developer';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart' as inApp;
import 'package:get/get.dart';
import 'package:html_editor_enhanced_fork_latex/html_editor.dart';
void main() => runApp(HtmlEditorExampleApp());
class HtmlEditorExampleApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(),
darkTheme: ThemeData.dark(),
home: HtmlEditorExample(title: 'Flutter HTML Editor Example'),
// home: MyApp2(title: 'Flutter HTML Editor Example'),
);
}
}
class MyApp2 extends StatefulWidget {
final String title;
MyApp2({Key? key, required this.title}) : super(key: key);
@override
State<MyApp2> createState() => _MyApp2State();
}
class _MyApp2State extends State<MyApp2> {
final HtmlEditorController controller1 = HtmlEditorController();
inApp.InAppWebViewController controller =
inApp.InAppWebViewController(0123, inApp.InAppWebView());
final controller2 = HtmlEditorController();
@override
void initState() {
super.initState();
}
Future<String> _latexToHtml(String latex, editorController) async {
const breakPoint = '<mrow><mi>break</mi></mrow>';
var mathMl =
'<math><mrow><mi>x</mi><msqrt><mi>x</mi></msqrt></mrow></math><math><mrow><mi>x</mi><msqrt><mi>x</mi></msqrt></mrow></math><math><mrow><mi>x</mi><msqrt><mi>x</mi></msqrt></mrow></math>';
var a = await controller2.mathMlToLatex(
mathMl.replaceAll('</math><math>', breakPoint), context);
for (var value in a.split('break')) {
log(value.trim(), name: 'latex');
}
log(a, name: 'MathMl');
return a;
}
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(onPressed: () {
(_latexToHtml('{x}', controller));
}),
body: Stack(
children: [
SingleChildScrollView(
child: Column(
children: [
tb(controller1),
],
),
),
],
),
appBar: AppBar(
title: Text(this.widget.title),
),
);
}
Widget tb(HtmlEditorController controller) {
var isVisible = false.obs;
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Obx(
() => isVisible.value
? ToolbarWidget(
controller: controller,
htmlToolbarOptions: HtmlToolbarOptions(
toolbarType: kIsWeb
? ToolbarType.nativeGrid
: ToolbarType.nativeScrollable,
toolbarPosition: ToolbarPosition
.custom, //required to place toolbar anywhere!
//other options
),
callbacks: null)
: IconButton(
onPressed: () {
controller.insertHtml(
r'<math><semantics><mrow><mi>y</mi><mi>y</mi></mrow></semantics></>');
isVisible.value = true;
},
icon: Icon(Icons.functions_outlined)),
),
//other widgets here
HtmlEditor(
controller: controller,
callbacks: Callbacks(
onBlur: () {
print('onBlur');
isVisible.value = false;
},
onNavigationRequestMobile: (s) async {
print('on NavReq to $s');
return NavigationActionPolicy.ALLOW;
},
onDialogShown: () => print('onDialogOpen'),
onFocus: () {
print('onFocus');
isVisible.value = true;
},
),
htmlEditorOptions: HtmlEditorOptions(
hint: 'Your text here...',
shouldEnsureVisible: true,
//initialText: "<p>text content initial, if any</p>",
),
htmlToolbarOptions: HtmlToolbarOptions(
toolbarPosition:
ToolbarPosition.custom, // required to place toolbar anywhere!
//other options
),
otherOptions: OtherOptions(height: 550),
),
]);
}
}
class HtmlEditorExample extends StatefulWidget {
HtmlEditorExample({Key? key, required this.title}) : super(key: key);
final String title;
@override
_HtmlEditorExampleState createState() => _HtmlEditorExampleState();
}
class _HtmlEditorExampleState extends State<HtmlEditorExample> {
String result = '';
final HtmlEditorController controller = HtmlEditorController();
Future<String> _latexToHtml(String latex) async {
const breakPoint = '<mrow><mi>break</mi></mrow>';
var a = await controller.mathMlToLatex(
latex.replaceAll('</math><math>', breakPoint), context);
for (var value in a.split('break')) {
log(value.trim(), name: 'latex');
}
log(a, name: 'MathMl');
return a;
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
if (!kIsWeb) {
controller.clearFocus();
}
},
child: Scaffold(
appBar: AppBar(
title: Text(widget.title),
elevation: 0,
actions: [
IconButton(
icon: Icon(Icons.refresh),
onPressed: () {
if (kIsWeb) {
controller.reloadWeb();
} else {
controller.editorController!.reload();
}
})
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
controller.toggleCodeView();
},
child: Text(r'<\>',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
),
body: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
HtmlEditor(
controller: controller,
htmlEditorOptions: HtmlEditorOptions(
hint: 'Your text here...',
shouldEnsureVisible: true,
//initialText: "<p>text content initial, if any</p>",
),
htmlToolbarOptions: HtmlToolbarOptions(
toolbarPosition: ToolbarPosition.aboveEditor,
//by default
toolbarType: ToolbarType.nativeScrollable,
//by default
),
otherOptions: OtherOptions(height: 550),
callbacks: Callbacks(onBeforeCommand: (String? currentHtml) {
print('html before change is $currentHtml');
}, onChangeContent: (String? changed) {
print('content changed to $changed');
}, onChangeCodeview: (String? changed) {
print('code changed to $changed');
}, onChangeSelection: (EditorSettings settings) {
print('parent element is ${settings.parentElement}');
print('font name is ${settings.fontName}');
}, onDialogShown: () {
print('dialog shown');
}, onEnter: () {
print('enter/return pressed');
}, onFocus: () {
print('editor focused');
}, onBlur: () {
print('editor unfocused');
}, onBlurCodeview: () {
print('codeview either focused or unfocused');
}, onInit: () {
print('init');
},
//this is commented because it overrides the default Summernote handlers
/*onImageLinkInsert: (String? url) {
print(url ?? "unknown url");
},
onImageUpload: (FileUpload file) async {
print(file.name);
print(file.size);
print(file.type);
print(file.base64);
},*/
onImageUploadError: (FileUpload? file, String? base64Str,
UploadError error) {
print((error.name));
print(base64Str ?? '');
if (file != null) {
print(file.name);
print(file.size);
print(file.type);
}
}, onKeyDown: (int? keyCode) {
print('$keyCode key downed');
print(
'current character count: ${controller.characterCount}');
}, onKeyUp: (int? keyCode) {
print('$keyCode key released');
}, onMouseDown: () {
print('mouse downed');
}, onMouseUp: () {
print('mouse released');
}, onNavigationRequestMobile: (String url) {
print(url);
return NavigationActionPolicy.ALLOW;
}, onPaste: () {
print('pasted into editor');
}, onScroll: () {
print('editor scrolled');
}),
plugins: [
SummernoteAtMention(
getSuggestionsMobile: (String value) {
var mentions = <String>['test1', 'test2', 'test3'];
return mentions
.where((element) => element.contains(value))
.toList();
},
mentionsWeb: ['test1', 'test2', 'test3'],
onSelect: (String value) {
print(value);
}),
],
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextButton(
style: TextButton.styleFrom(
backgroundColor: Colors.blueGrey),
onPressed: () {
controller.insertHtmlStringWithLatex(
r'<p><h1>Hello</h1></p><p>\(\sqrt{x} e \pi \) <br></p>');
},
child: Text('insert mathMl',
style: TextStyle(color: Colors.white)),
),
SizedBox(
width: 16,
),
TextButton(
style: TextButton.styleFrom(
backgroundColor: Colors.blueGrey),
onPressed: () async {
var text = await controller.getText();
var res = await _latexToHtml(text);
log(res, name: 'result');
},
child: Text('Get Latex',
style: TextStyle(color: Colors.white)),
),
SizedBox(
width: 16,
),
TextButton(
style: TextButton.styleFrom(
backgroundColor:
Theme.of(context).colorScheme.secondary),
onPressed: () async {
// var txt = await controller.getHtmlStringWithLatex(context);
var txt =
await controller.getHtmlStringWithLatex(context);
print(txt);
// print(res);
if (txt.contains('src=\"data:')) {
txt =
'<text removed due to base-64 data, displaying the text could cause the app to crash>';
}
setState(() {
result = txt;
});
},
child: Text(
'Submit',
style: TextStyle(color: Colors.white),
),
),
SizedBox(
width: 16,
),
TextButton(
style: TextButton.styleFrom(
backgroundColor:
Theme.of(context).colorScheme.secondary),
onPressed: () {
controller.insertLatex(r'\(\sqrt{x}\)');
},
child: Text(
'insert latex',
style: TextStyle(color: Colors.white),
),
),
],
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(result),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextButton(
style: TextButton.styleFrom(
backgroundColor: Colors.blueGrey),
onPressed: () {
controller.disable();
},
child: Text('Disable',
style: TextStyle(color: Colors.white)),
),
SizedBox(
width: 16,
),
TextButton(
style: TextButton.styleFrom(
backgroundColor:
Theme.of(context).colorScheme.secondary),
onPressed: () async {
controller.enable();
},
child: Text(
'Enable',
style: TextStyle(color: Colors.white),
),
),
],
),
),
SizedBox(height: 16),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextButton(
style: TextButton.styleFrom(
backgroundColor:
Theme.of(context).colorScheme.secondary),
onPressed: () {
controller.insertText('Google');
},
child: Text('Insert Text',
style: TextStyle(color: Colors.white)),
),
SizedBox(
width: 16,
),
TextButton(
style: TextButton.styleFrom(
backgroundColor:
Theme.of(context).colorScheme.secondary),
onPressed: () {
controller.insertHtml(
'''<p style="color: blue">Google in blue</p>''');
},
child: Text('Insert HTML',
style: TextStyle(color: Colors.white)),
),
],
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextButton(
style: TextButton.styleFrom(
backgroundColor:
Theme.of(context).colorScheme.secondary),
onPressed: () async {
controller.insertLink(
'Google linked', 'https://google.com', true);
},
child: Text(
'Insert Link',
style: TextStyle(color: Colors.white),
),
),
SizedBox(
width: 16,
),
TextButton(
style: TextButton.styleFrom(
backgroundColor:
Theme.of(context).colorScheme.secondary),
onPressed: () {
controller.insertNetworkImage(
'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_92x30dp.png',
filename: 'Google network image');
},
child: Text(
'Insert network image',
style: TextStyle(color: Colors.white),
),
),
],
),
),
SizedBox(height: 16),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextButton(
style: TextButton.styleFrom(
backgroundColor: Colors.blueGrey),
onPressed: () {
controller.addNotification(
'Info notification', NotificationType.info);
},
child:
Text('Info', style: TextStyle(color: Colors.white)),
),
SizedBox(
width: 16,
),
TextButton(
style: TextButton.styleFrom(
backgroundColor: Colors.blueGrey),
onPressed: () {
controller.addNotification(
'Warning notification', NotificationType.warning);
},
child: Text('Warning',
style: TextStyle(color: Colors.white)),
),
SizedBox(
width: 16,
),
TextButton(
style: TextButton.styleFrom(
backgroundColor:
Theme.of(context).colorScheme.secondary),
onPressed: () async {
controller.addNotification(
'Success notification', NotificationType.success);
},
child: Text(
'Success',
style: TextStyle(color: Colors.white),
),
),
SizedBox(
width: 16,
),
TextButton(
style: TextButton.styleFrom(
backgroundColor:
Theme.of(context).colorScheme.secondary),
onPressed: () {
controller.addNotification(
'Danger notification', NotificationType.danger);
},
child: Text(
'Danger',
style: TextStyle(color: Colors.white),
),
),
],
),
),
SizedBox(height: 16),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextButton(
style: TextButton.styleFrom(
backgroundColor: Colors.blueGrey),
onPressed: () {
controller.addNotification('Plaintext notification',
NotificationType.plaintext);
},
child: Text('Plaintext',
style: TextStyle(color: Colors.white)),
),
SizedBox(
width: 16,
),
TextButton(
style: TextButton.styleFrom(
backgroundColor:
Theme.of(context).colorScheme.secondary),
onPressed: () async {
controller.removeNotification();
},
child: Text(
'Remove',
style: TextStyle(color: Colors.white),
),
),
],
),
),
],
),
),
),
);
}
}