feat: added editor specific features

This commit is contained in:
jideguru 2021-05-29 22:36:44 +01:00
parent d0faf48a9b
commit d5b8305fa3
5 changed files with 146 additions and 26 deletions

View File

@ -10,7 +10,7 @@
</head> </head>
<body> <body>
<div id="editor" contenteditable="true"></div> <div id="editor" contenteditable="true" tabindex="0"></div>
<script type="text/javascript" src="interact.min.js"></script> <script type="text/javascript" src="interact.min.js"></script>
<script type="text/javascript" src="rich_text_editor.js"></script> <script type="text/javascript" src="rich_text_editor.js"></script>
</body> </body>

View File

@ -1,3 +1,5 @@
import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:rich_editor/rich_editor.dart'; import 'package:rich_editor/rich_editor.dart';
@ -28,11 +30,72 @@ class MyHomePage extends StatefulWidget {
} }
class _MyHomePageState extends State<MyHomePage> { class _MyHomePageState extends State<MyHomePage> {
GlobalKey<RichEditorState> keyEditor = GlobalKey();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(title: Text(widget.title)), appBar: AppBar(
body: RichEditor(), title: Text(widget.title),
actions: [
PopupMenuButton(
child: IconButton(
icon: Icon(Icons.more_vert),
onPressed: null,
disabledColor: Colors.white,
),
itemBuilder: (context) {
return [
PopupMenuItem(
child: Text('Get HTML'),
value: 0,
),
PopupMenuItem(
child: Text('Clear content'),
value: 1,
),
PopupMenuItem(
child: Text('Hide keyboard'),
value: 2,
),
PopupMenuItem(
child: Text('Show Keyboard'),
value: 3,
),
];
},
onSelected: (val) async {
switch(val) {
case 0: {
String? html = await keyEditor.currentState?.getHtml();
print(html);
} break;
case 1: {
await keyEditor.currentState?.clear();
} break;
case 2: {
await keyEditor.currentState?.unFocus();
} break;
case 3: {
await keyEditor.currentState?.focus();
} break;
}
},
),
],
),
body: RichEditor(
key: keyEditor,
value: '<p> init html val </p>',
// You can return a Link (maybe you need to upload the image to your
// storage before displaying in the editor or you can also use base64
getImageUrl: (image) {
String link = 'https://avatars.githubusercontent.com/u/24323581?v=4';
String base64 = base64Encode(image.readAsBytesSync());
String base64String = 'data:image/png;base64, $base64';
return base64String;
},
),
); );
} }
} }

View File

@ -1,25 +1,36 @@
import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:rich_editor/src/services/local_server.dart'; import 'package:rich_editor/src/services/local_server.dart';
import 'package:rich_editor/src/utils/javascript_executor_base.dart';
import 'package:rich_editor/src/widgets/tabs.dart'; import 'package:rich_editor/src/widgets/tabs.dart';
import 'package:webview_flutter/webview_flutter.dart'; import 'package:webview_flutter/webview_flutter.dart';
class RichEditor extends StatefulWidget { class RichEditor extends StatefulWidget {
final String? value;
final Function(File image)? getImageUrl;
final Function(File video)? getVideoUrl;
RichEditor({Key? key, this.value, this.getImageUrl, this.getVideoUrl})
: super(key: key);
@override @override
_RichEditorState createState() => _RichEditorState(); RichEditorState createState() => RichEditorState();
} }
class _RichEditorState extends State<RichEditor> { class RichEditorState extends State<RichEditor> {
WebViewController? _controller; WebViewController? _controller;
String text = ""; String text = "";
final Key _mapKey = UniqueKey(); final Key _mapKey = UniqueKey();
String assetPath = 'packages/rich_editor/assets/editor/editor.html'; String assetPath = 'packages/rich_editor/assets/editor/editor.html';
int port = 5321; int port = 5321;
String html = '';
LocalServer? localServer; LocalServer? localServer;
JavascriptExecutorBase javascriptExecutorBase = JavascriptExecutorBase();
@override @override
void initState() { void initState() {
@ -30,9 +41,9 @@ class _RichEditorState extends State<RichEditor> {
} }
} }
initServer() { initServer() async {
localServer = LocalServer(port); localServer = LocalServer(port);
localServer!.start(handleRequest); await localServer!.start(handleRequest);
} }
void handleRequest(HttpRequest request) { void handleRequest(HttpRequest request) {
@ -45,7 +56,6 @@ class _RichEditorState extends State<RichEditor> {
} }
} }
@override @override
void dispose() { void dispose() {
if (_controller != null) { if (_controller != null) {
@ -66,28 +76,36 @@ class _RichEditorState extends State<RichEditor> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return Column(
children: [ children: [
GroupedTab(controller: _controller), GroupedTab(
controller: _controller,
getImageUrl: widget.getImageUrl,
),
Expanded( Expanded(
child: WebView( child: WebView(
key: _mapKey, key: _mapKey,
// initialUrl: onWebViewCreated: (WebViewController controller) async {
// 'file:///android_asset/flutter_assets/packages/rich_editor/assets/editor/editor.html',
onWebViewCreated: (WebViewController controller) {
_controller = controller; _controller = controller;
print('WebView created'); print('WebView created');
setState(() {}); setState(() {});
if (!Platform.isAndroid) { if (!Platform.isAndroid) {
print('Loading'); print('Loading');
_loadHtmlFromAssets(); await _loadHtmlFromAssets();
} else { } else {
_controller!.loadUrl('file:///android_asset/flutter_assets/$assetPath'); await _controller!
.loadUrl('file:///android_asset/flutter_assets/$assetPath');
}
javascriptExecutorBase.init(_controller!);
if (widget.value != null) {
// wait 1 second before setting the html
Timer(Duration(seconds: 1), () async {
await javascriptExecutorBase.setHtml(widget.value!);
});
} }
}, },
javascriptMode: JavascriptMode.unrestricted, javascriptMode: JavascriptMode.unrestricted,
gestureNavigationEnabled: true, gestureNavigationEnabled: true,
gestureRecognizers: [ gestureRecognizers: [
Factory( Factory(() => VerticalDragGestureRecognizer()..onUpdate = (_) {}),
() => VerticalDragGestureRecognizer()..onUpdate = (_) {}),
].toSet(), ].toSet(),
onWebResourceError: (e) { onWebResourceError: (e) {
print("error ${e.description}"); print("error ${e.description}");
@ -100,4 +118,32 @@ class _RichEditorState extends State<RichEditor> {
], ],
); );
} }
Future<String?> getHtml() async {
try {
html = await javascriptExecutorBase.getCurrentHtml();
} catch (e) {}
return html;
}
setHtml(String html) async {
return await javascriptExecutorBase.setHtml(html);
}
// Hide the keyboard using JavaScript since it's being opened in a WebView
// https://stackoverflow.com/a/8263376/10835183
unFocus() {
_controller!.evaluateJavascript('document.activeElement.blur();');
}
// Clear editor content using Javascript
clear() {
_controller!.evaluateJavascript('document.getElementById(\'editor\').innerHTML = "";');
}
// Focus and Show the keyboard using JavaScript
// https://stackoverflow.com/a/6809236/10835183
focus() {
_controller!.evaluateJavascript('document.getElementById(\'editor\').focus();');
}
} }

View File

@ -12,6 +12,7 @@ class _InsertImageDialogState extends State<InsertImageDialog> {
TextEditingController link = TextEditingController(); TextEditingController link = TextEditingController();
TextEditingController alt = TextEditingController(); TextEditingController alt = TextEditingController();
bool picked = false;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -42,7 +43,7 @@ class _InsertImageDialogState extends State<InsertImageDialog> {
), ),
), ),
], ],
onDone: () => Navigator.pop(context, [link.text, alt.text]), onDone: () => Navigator.pop(context, [link.text, alt.text, picked]),
onCancel: () => Navigator.pop(context), onCancel: () => Navigator.pop(context),
); );
} }
@ -57,6 +58,7 @@ class _InsertImageDialogState extends State<InsertImageDialog> {
if (image != null) { if (image != null) {
link.text = image.path; link.text = image.path;
picked = true;
} }
} }
} }

View File

@ -1,3 +1,5 @@
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:rich_editor/src/utils/javascript_executor_base.dart'; import 'package:rich_editor/src/utils/javascript_executor_base.dart';
import 'package:rich_editor/src/widgets/check_dialog.dart'; import 'package:rich_editor/src/widgets/check_dialog.dart';
@ -13,8 +15,10 @@ import 'heading_dialog.dart';
class GroupedTab extends StatelessWidget { class GroupedTab extends StatelessWidget {
final WebViewController? controller; final WebViewController? controller;
final Function(File image)? getImageUrl;
final Function(File video)? getVideoUrl;
GroupedTab({this.controller}); GroupedTab({this.controller, this.getImageUrl, this.getVideoUrl});
JavascriptExecutorBase javascriptExecutorBase = JavascriptExecutorBase(); JavascriptExecutorBase javascriptExecutorBase = JavascriptExecutorBase();
@ -77,6 +81,9 @@ class GroupedTab extends StatelessWidget {
}, },
); );
if (link != null) { if (link != null) {
if (getImageUrl != null && link[2]) {
link[0] = await getImageUrl!(File(link[0]));
}
await javascriptExecutorBase.insertImage( await javascriptExecutorBase.insertImage(
link[0], link[0],
alt: link[1], alt: link[1],
@ -195,7 +202,8 @@ class GroupedTab extends StatelessWidget {
}, },
); );
if (command != null) if (command != null)
await javascriptExecutorBase.setFontSize(int.tryParse(command)!); await javascriptExecutorBase
.setFontSize(int.tryParse(command)!);
}, },
), ),
TabButton( TabButton(
@ -301,13 +309,14 @@ class GroupedTab extends StatelessWidget {
await javascriptExecutorBase.insertCheckbox(text); await javascriptExecutorBase.insertCheckbox(text);
}, },
), ),
TabButton( /// TODO: Implement Search feature
tooltip: 'Search', // TabButton(
icon: Icons.search, // tooltip: 'Search',
onTap: () async { // icon: Icons.search,
// await javascriptExecutorBase.insertNumberedList(); // onTap: () async {
}, // // await javascriptExecutorBase.insertNumberedList();
), // },
// ),
], ],
), ),
), ),