html_editor_enhanced 2.0.0 html_editor_enhanced: ^2.0.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 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:html_editor_enhanced/html_editor.dart';
import 'package:file_picker/file_picker.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'),
);
}
}
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();
@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(
child: Text(r"<\>",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
onPressed: () {
controller.toggleCodeView();
},
),
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
onButtonPressed: (ButtonType type, bool? status,
Function()? updateStatus) {
print(
"button '${describeEnum(type)}' pressed, the current selected status is $status");
return true;
},
onDropdownChanged: (DropdownType type, dynamic changed,
Function(dynamic)? updateSelectedItem) {
print(
"dropdown '${describeEnum(type)}' changed to $changed");
return true;
},
mediaLinkInsertInterceptor:
(String url, InsertFileType type) {
print(url);
return true;
},
mediaUploadInterceptor:
(PlatformFile file, InsertFileType type) async {
print(file.name); //filename
print(file.size); //size in bytes
print(file.extension); //file extension (eg jpeg or mp4)
return true;
},
),
otherOptions: OtherOptions(height: 550),
callbacks: Callbacks(onBeforeCommand: (String? currentHtml) {
print("html before change is $currentHtml");
}, onChange: (String? changed) {
print("content changed to $changed");
}, onChangeCodeview: (String? changed) {
print("code changed to $changed");
}, 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(describeEnum(error));
print(base64Str ?? "");
if (file != null) {
print(file.name);
print(file.size);
print(file.type);
}
}, onKeyDown: (int? keyCode) {
print("$keyCode key downed");
}, onKeyUp: (int? keyCode) {
print("$keyCode key released");
}, onMouseDown: () {
print("mouse downed");
}, onMouseUp: () {
print("mouse released");
}, onPaste: () {
print("pasted into editor");
}, onScroll: () {
print("editor scrolled");
}),
plugins: [
SummernoteAtMention(
getSuggestionsMobile: (String value) {
List<String> mentions = ['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.undo();
},
child:
Text("Undo", style: TextStyle(color: Colors.white)),
),
SizedBox(
width: 16,
),
TextButton(
style: TextButton.styleFrom(
backgroundColor: Colors.blueGrey),
onPressed: () {
controller.clear();
},
child:
Text("Reset", style: TextStyle(color: Colors.white)),
),
SizedBox(
width: 16,
),
TextButton(
style: TextButton.styleFrom(
backgroundColor: Theme.of(context).accentColor),
onPressed: () async {
String? txt = await controller.getText();
if (txt != null) {
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).accentColor),
onPressed: () {
controller.redo();
},
child: Text(
"Redo",
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).accentColor),
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).accentColor),
onPressed: () {
controller.insertText("Google");
},
child: Text("Insert Text",
style: TextStyle(color: Colors.white)),
),
SizedBox(
width: 16,
),
TextButton(
style: TextButton.styleFrom(
backgroundColor: Theme.of(context).accentColor),
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).accentColor),
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).accentColor),
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).accentColor),
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).accentColor),
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).accentColor),
onPressed: () async {
controller.removeNotification();
},
child: Text(
"Remove",
style: TextStyle(color: Colors.white),
),
),
],
),
),
],
),
),
),
);
}
}