feat: added editor specific features
This commit is contained in:
parent
d0faf48a9b
commit
d5b8305fa3
@ -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>
|
||||||
|
@ -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;
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
),
|
// },
|
||||||
|
// ),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Loading…
Reference in New Issue
Block a user