From d5b8305fa31ff660fc61d7f15e6f98bd56bc19f2 Mon Sep 17 00:00:00 2001 From: jideguru Date: Sat, 29 May 2021 22:36:44 +0100 Subject: [PATCH] feat: added editor specific features --- assets/editor/editor.html | 2 +- example/lib/main.dart | 67 +++++++++++++++++++++- lib/src/rendering/rich_editor.dart | 72 +++++++++++++++++++----- lib/src/widgets/insert_image_dialog.dart | 4 +- lib/src/widgets/tabs.dart | 27 ++++++--- 5 files changed, 146 insertions(+), 26 deletions(-) diff --git a/assets/editor/editor.html b/assets/editor/editor.html index 553186e..f46d699 100644 --- a/assets/editor/editor.html +++ b/assets/editor/editor.html @@ -10,7 +10,7 @@ -
+
diff --git a/example/lib/main.dart b/example/lib/main.dart index 8741d99..3e7d066 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:flutter/material.dart'; import 'package:rich_editor/rich_editor.dart'; @@ -28,11 +30,72 @@ class MyHomePage extends StatefulWidget { } class _MyHomePageState extends State { + GlobalKey keyEditor = GlobalKey(); + @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: Text(widget.title)), - body: RichEditor(), + appBar: AppBar( + 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: '

init html val

', + // 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; + }, + ), ); } } diff --git a/lib/src/rendering/rich_editor.dart b/lib/src/rendering/rich_editor.dart index ab85263..f238abb 100644 --- a/lib/src/rendering/rich_editor.dart +++ b/lib/src/rendering/rich_editor.dart @@ -1,25 +1,36 @@ +import 'dart:async'; import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.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:webview_flutter/webview_flutter.dart'; 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 - _RichEditorState createState() => _RichEditorState(); + RichEditorState createState() => RichEditorState(); } -class _RichEditorState extends State { +class RichEditorState extends State { WebViewController? _controller; String text = ""; final Key _mapKey = UniqueKey(); String assetPath = 'packages/rich_editor/assets/editor/editor.html'; int port = 5321; + String html = ''; LocalServer? localServer; + JavascriptExecutorBase javascriptExecutorBase = JavascriptExecutorBase(); @override void initState() { @@ -30,9 +41,9 @@ class _RichEditorState extends State { } } - initServer() { + initServer() async { localServer = LocalServer(port); - localServer!.start(handleRequest); + await localServer!.start(handleRequest); } void handleRequest(HttpRequest request) { @@ -45,7 +56,6 @@ class _RichEditorState extends State { } } - @override void dispose() { if (_controller != null) { @@ -66,28 +76,36 @@ class _RichEditorState extends State { Widget build(BuildContext context) { return Column( children: [ - GroupedTab(controller: _controller), + GroupedTab( + controller: _controller, + getImageUrl: widget.getImageUrl, + ), Expanded( child: WebView( key: _mapKey, - // initialUrl: - // 'file:///android_asset/flutter_assets/packages/rich_editor/assets/editor/editor.html', - onWebViewCreated: (WebViewController controller) { + onWebViewCreated: (WebViewController controller) async { _controller = controller; print('WebView created'); setState(() {}); if (!Platform.isAndroid) { print('Loading'); - _loadHtmlFromAssets(); + await _loadHtmlFromAssets(); } 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, gestureNavigationEnabled: true, gestureRecognizers: [ - Factory( - () => VerticalDragGestureRecognizer()..onUpdate = (_) {}), + Factory(() => VerticalDragGestureRecognizer()..onUpdate = (_) {}), ].toSet(), onWebResourceError: (e) { print("error ${e.description}"); @@ -100,4 +118,32 @@ class _RichEditorState extends State { ], ); } + + Future 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();'); + } } diff --git a/lib/src/widgets/insert_image_dialog.dart b/lib/src/widgets/insert_image_dialog.dart index 5e47716..bfc01bc 100644 --- a/lib/src/widgets/insert_image_dialog.dart +++ b/lib/src/widgets/insert_image_dialog.dart @@ -12,6 +12,7 @@ class _InsertImageDialogState extends State { TextEditingController link = TextEditingController(); TextEditingController alt = TextEditingController(); + bool picked = false; @override Widget build(BuildContext context) { @@ -42,7 +43,7 @@ class _InsertImageDialogState extends State { ), ), ], - onDone: () => Navigator.pop(context, [link.text, alt.text]), + onDone: () => Navigator.pop(context, [link.text, alt.text, picked]), onCancel: () => Navigator.pop(context), ); } @@ -57,6 +58,7 @@ class _InsertImageDialogState extends State { if (image != null) { link.text = image.path; + picked = true; } } } diff --git a/lib/src/widgets/tabs.dart b/lib/src/widgets/tabs.dart index f0ab7f1..8e67ccd 100644 --- a/lib/src/widgets/tabs.dart +++ b/lib/src/widgets/tabs.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:rich_editor/src/utils/javascript_executor_base.dart'; import 'package:rich_editor/src/widgets/check_dialog.dart'; @@ -13,8 +15,10 @@ import 'heading_dialog.dart'; class GroupedTab extends StatelessWidget { 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(); @@ -77,6 +81,9 @@ class GroupedTab extends StatelessWidget { }, ); if (link != null) { + if (getImageUrl != null && link[2]) { + link[0] = await getImageUrl!(File(link[0])); + } await javascriptExecutorBase.insertImage( link[0], alt: link[1], @@ -195,7 +202,8 @@ class GroupedTab extends StatelessWidget { }, ); if (command != null) - await javascriptExecutorBase.setFontSize(int.tryParse(command)!); + await javascriptExecutorBase + .setFontSize(int.tryParse(command)!); }, ), TabButton( @@ -301,13 +309,14 @@ class GroupedTab extends StatelessWidget { await javascriptExecutorBase.insertCheckbox(text); }, ), - TabButton( - tooltip: 'Search', - icon: Icons.search, - onTap: () async { - // await javascriptExecutorBase.insertNumberedList(); - }, - ), + /// TODO: Implement Search feature + // TabButton( + // tooltip: 'Search', + // icon: Icons.search, + // onTap: () async { + // // await javascriptExecutorBase.insertNumberedList(); + // }, + // ), ], ), ),