feat: added editor settings
This commit is contained in:
@@ -2,4 +2,4 @@ library rich_editor;
|
||||
|
||||
export 'src/rendering/rich_editor.dart';
|
||||
export 'src/widgets/editor_tool_bar.dart';
|
||||
export 'src/widgets/tab_button.dart';
|
||||
export 'src/widgets/tab_button.dart';
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// An extension that makes it easy to get Hex code
|
||||
/// from [Color] and [MaterialColor]
|
||||
extension ColorX on Color {
|
||||
String toHexColorString() => '#${value.toRadixString(16).replaceAll('ff', '')}';
|
||||
}
|
||||
String toHexColorString() {
|
||||
String hex = value.toRadixString(16).replaceAll('ff', '');
|
||||
if(hex.isEmpty) hex = '000000';
|
||||
return '#$hex';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,10 +11,22 @@ import 'package:webview_flutter/webview_flutter.dart';
|
||||
|
||||
class RichEditor extends StatefulWidget {
|
||||
final String? value;
|
||||
final Color? backgroundColor;
|
||||
final Color? baseTextColor;
|
||||
final EdgeInsets? padding;
|
||||
final String? placeholder;
|
||||
final Function(File image)? getImageUrl;
|
||||
final Function(File video)? getVideoUrl;
|
||||
|
||||
RichEditor({Key? key, this.value, this.getImageUrl, this.getVideoUrl})
|
||||
RichEditor(
|
||||
{Key? key,
|
||||
this.value,
|
||||
this.backgroundColor,
|
||||
this.baseTextColor,
|
||||
this.padding,
|
||||
this.placeholder,
|
||||
this.getImageUrl,
|
||||
this.getVideoUrl})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
@@ -86,22 +98,17 @@ class RichEditorState extends State<RichEditor> {
|
||||
key: _mapKey,
|
||||
onWebViewCreated: (WebViewController controller) async {
|
||||
_controller = controller;
|
||||
print('WebView created');
|
||||
setState(() {});
|
||||
if (!Platform.isAndroid) {
|
||||
print('Loading');
|
||||
await _loadHtmlFromAssets();
|
||||
} else {
|
||||
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!);
|
||||
});
|
||||
}
|
||||
},
|
||||
onPageFinished: (link) async {
|
||||
await _setInitialValues();
|
||||
},
|
||||
javascriptMode: JavascriptMode.unrestricted,
|
||||
gestureNavigationEnabled: true,
|
||||
@@ -117,6 +124,19 @@ class RichEditorState extends State<RichEditor> {
|
||||
);
|
||||
}
|
||||
|
||||
_setInitialValues() async {
|
||||
if (widget.value != null)
|
||||
await javascriptExecutorBase.setHtml(widget.value!);
|
||||
if (widget.padding != null)
|
||||
await javascriptExecutorBase.setPadding(widget.padding!);
|
||||
if (widget.backgroundColor != null)
|
||||
await javascriptExecutorBase.setBackgroundColor(widget.backgroundColor!);
|
||||
if (widget.baseTextColor != null)
|
||||
await javascriptExecutorBase.setBaseTextColor(widget.baseTextColor!);
|
||||
if (widget.placeholder != null)
|
||||
await javascriptExecutorBase.setPlaceholder(widget.placeholder!);
|
||||
}
|
||||
|
||||
Future<String?> getHtml() async {
|
||||
try {
|
||||
html = await javascriptExecutorBase.getCurrentHtml();
|
||||
@@ -131,17 +151,18 @@ class RichEditorState extends State<RichEditor> {
|
||||
// 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();');
|
||||
javascriptExecutorBase.unFocus();
|
||||
}
|
||||
|
||||
// Clear editor content using Javascript
|
||||
clear() {
|
||||
_controller!.evaluateJavascript('document.getElementById(\'editor\').innerHTML = "";');
|
||||
_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();');
|
||||
javascriptExecutorBase.focus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,12 +8,13 @@ import 'package:rich_editor/src/models/font.dart';
|
||||
import 'package:rich_editor/src/models/system_font.dart';
|
||||
import 'package:xml2json/xml2json.dart';
|
||||
|
||||
// A simple port of FontListParser from Java to Kotlin
|
||||
// See https://stackoverflow.com/a/29533686/10835183
|
||||
/// A simple port of FontListParser from Java to Kotlin
|
||||
/// See https://stackoverflow.com/a/29533686/10835183
|
||||
class FontListParser {
|
||||
File androidFontsFile = File("/system/etc/fonts.xml");
|
||||
File androidSystemFontsFile = File("/system/etc/system_fonts.xml");
|
||||
|
||||
/// Gets fonts from the fonts xml files in the android system
|
||||
List<SystemFont> getSystemFonts() {
|
||||
String fontsXml;
|
||||
if (androidFontsFile.existsSync()) {
|
||||
@@ -75,6 +76,7 @@ class FontListParser {
|
||||
return fonts;
|
||||
}
|
||||
|
||||
/// Gets font from the list defined incase the above function doesn't work
|
||||
List<SystemFont> safelyGetSystemFonts() {
|
||||
try {
|
||||
return getSystemFonts();
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:webview_flutter/webview_flutter.dart';
|
||||
|
||||
import '../models/command_state.dart';
|
||||
|
||||
/// A class that handles all editor-related javascript functions
|
||||
class JavascriptExecutorBase {
|
||||
WebViewController? _controller;
|
||||
|
||||
@@ -193,6 +194,41 @@ class JavascriptExecutorBase {
|
||||
await executeJavascript("disableImageResizing()");
|
||||
}
|
||||
|
||||
// Editor settings commands
|
||||
focus() async {
|
||||
await executeJavascript("focus()");
|
||||
}
|
||||
|
||||
unFocus() async {
|
||||
await executeJavascript("blurFocus()");
|
||||
}
|
||||
|
||||
setBackgroundColor(Color? color) async {
|
||||
String? hex = color!.toHexColorString();
|
||||
await executeJavascript("setBackgroundColor('$hex')");
|
||||
}
|
||||
|
||||
setBackgroundImage(String image) async {
|
||||
await executeJavascript("setBackgroundImage('$image')");
|
||||
}
|
||||
|
||||
setBaseTextColor(Color? color) async {
|
||||
String? hex = color!.toHexColorString();
|
||||
await executeJavascript("setBaseTextColor('$hex')");
|
||||
}
|
||||
|
||||
setPadding(EdgeInsets? padding) async {
|
||||
String left = padding!.left.toString();
|
||||
String top = padding.top.toString();
|
||||
String right = padding.right.toString();
|
||||
String bottom = padding.bottom.toString();
|
||||
await executeJavascript("setPadding('$left', '$top', '$right', '$bottom')");
|
||||
}
|
||||
|
||||
setPlaceholder(String placeholder) async {
|
||||
await executeJavascript("setPlaceholder('$placeholder')");
|
||||
}
|
||||
|
||||
static decodeHtml(String html) {
|
||||
return Uri.decodeFull(html);
|
||||
}
|
||||
@@ -201,87 +237,89 @@ class JavascriptExecutorBase {
|
||||
return Uri.encodeFull(html);
|
||||
}
|
||||
|
||||
// bool shouldOverrideUrlLoading(String url) {
|
||||
// String decodedUrl;
|
||||
// try {
|
||||
// decodedUrl = decodeHtml(url);
|
||||
// } catch (e) {
|
||||
// // No handling
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// if (url.indexOf(editorStateChangedCallbackScheme) == 0) {
|
||||
// editorStateChanged(
|
||||
// decodedUrl.substring(editorStateChangedCallbackScheme.length));
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// editorStateChanged(String statesString) {
|
||||
// try {
|
||||
// var editorState = EditorState.fromJson(jsonDecode(statesString));
|
||||
//
|
||||
// bool currentHtmlChanged = this.htmlField != editorState.html;
|
||||
// this.htmlField = editorState.html;
|
||||
//
|
||||
// retrievedEditorState(editorState.didHtmlChange, editorState.commandStates)
|
||||
//
|
||||
// if (currentHtmlChanged) {
|
||||
// fireHtmlChangedListenersAsync(editorState.html);
|
||||
// }
|
||||
// }
|
||||
// catch (e) {
|
||||
// throw("Could not parse command states: $statesString $e");
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// retrievedEditorState(bool didHtmlChange,
|
||||
// Map<CommandName, CommandState> commandStates) {
|
||||
// if (this.didHtmlChange != didHtmlChange) {
|
||||
// this.didHtmlChange = didHtmlChange;
|
||||
// didHtmlChangeListeners.forEach {
|
||||
// it.didHtmlChange(didHtmlChange);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// handleRetrievedCommandStates(commandStates)
|
||||
// }
|
||||
//
|
||||
// handleRetrievedCommandStates(Map<CommandName, CommandState> commandStates) {
|
||||
// determineDerivedCommandStates(commandStates)
|
||||
//
|
||||
// this.commandStates = commandStates;
|
||||
//
|
||||
// commandStatesChangedListeners.forEach {
|
||||
// it.invoke(this.commandStates)
|
||||
// }
|
||||
// }
|
||||
bool shouldOverrideUrlLoading(String url) {
|
||||
String decodedUrl;
|
||||
try {
|
||||
decodedUrl = decodeHtml(url);
|
||||
} catch (e) {
|
||||
// No handling
|
||||
return false;
|
||||
}
|
||||
|
||||
// determineDerivedCommandStates(Map<CommandName, CommandState> commandStates) {
|
||||
// commandStates[CommandName.FORMATBLOCK]?.let { formatCommandState ->
|
||||
// commandStates.put(CommandName.H1, CommandState(formatCommandState.executable, isFormatActivated(formatCommandState, "h1")))
|
||||
// commandStates.put(CommandName.H2, CommandState(formatCommandState.executable, isFormatActivated(formatCommandState, "h2")))
|
||||
// commandStates.put(CommandName.H3, CommandState(formatCommandState.executable, isFormatActivated(formatCommandState, "h3")))
|
||||
// commandStates.put(CommandName.H4, CommandState(formatCommandState.executable, isFormatActivated(formatCommandState, "h4")))
|
||||
// commandStates.put(CommandName.H5, CommandState(formatCommandState.executable, isFormatActivated(formatCommandState, "h5")))
|
||||
// commandStates.put(CommandName.H6, CommandState(formatCommandState.executable, isFormatActivated(formatCommandState, "h6")))
|
||||
// commandStates.put(CommandName.P, CommandState(formatCommandState.executable, isFormatActivated(formatCommandState, "p")))
|
||||
// commandStates.put(CommandName.PRE, CommandState(formatCommandState.executable, isFormatActivated(formatCommandState, "pre")))
|
||||
// commandStates.put(CommandName.BR, CommandState(formatCommandState.executable, isFormatActivated(formatCommandState, "")))
|
||||
// commandStates.put(CommandName.BLOCKQUOTE, CommandState(formatCommandState.executable, isFormatActivated(formatCommandState, "blockquote")))
|
||||
// }
|
||||
//
|
||||
// commandStates[CommandName.INSERTHTML]?.let { insertHtmlState ->
|
||||
// commandStates.put(CommandName.INSERTLINK, insertHtmlState)
|
||||
// commandStates.put(CommandName.INSERTIMAGE, insertHtmlState)
|
||||
// commandStates.put(CommandName.INSERTCHECKBOX, insertHtmlState)
|
||||
// }
|
||||
// }
|
||||
if (url.indexOf(editorStateChangedCallbackScheme) == 0) {
|
||||
editorStateChanged(
|
||||
decodedUrl.substring(editorStateChangedCallbackScheme.length));
|
||||
return true;
|
||||
}
|
||||
|
||||
// String isFormatActivated(CommandState formatCommandState, String format) {
|
||||
// return (formatCommandState.value == format)
|
||||
// .toString(); // rich_text_editor.js reports boolean values as string, so we also have to convert it to string
|
||||
// }
|
||||
return false;
|
||||
}
|
||||
|
||||
editorStateChanged(String statesString) {
|
||||
try {
|
||||
var editorState = EditorState.fromJson(jsonDecode(statesString));
|
||||
|
||||
bool currentHtmlChanged = this.htmlField != editorState.html;
|
||||
this.htmlField = editorState.html;
|
||||
|
||||
retrievedEditorState(
|
||||
editorState.didHtmlChange!, editorState.commandStates!);
|
||||
|
||||
if (currentHtmlChanged) {
|
||||
// fireHtmlChangedListenersAsync(editorState.html);
|
||||
}
|
||||
} catch (e) {
|
||||
throw ("Could not parse command states: $statesString $e");
|
||||
}
|
||||
}
|
||||
|
||||
retrievedEditorState(
|
||||
bool didHtmlChange, Map<CommandName, CommandState> commandStates) {
|
||||
if (this.didHtmlChange != didHtmlChange) {
|
||||
this.didHtmlChange = didHtmlChange;
|
||||
// didHtmlChangeListeners.forEach {
|
||||
// it.didHtmlChange(didHtmlChange);
|
||||
// }
|
||||
}
|
||||
|
||||
handleRetrievedCommandStates(commandStates);
|
||||
}
|
||||
|
||||
handleRetrievedCommandStates(Map<CommandName, CommandState> commandStates) {
|
||||
determineDerivedCommandStates(commandStates);
|
||||
|
||||
this.commandStates = commandStates;
|
||||
|
||||
// commandStatesChangedListeners.forEach {
|
||||
// it.invoke(this.commandStates)
|
||||
// }
|
||||
}
|
||||
|
||||
determineDerivedCommandStates(Map<CommandName, CommandState> commandStates) {
|
||||
// commandStates[CommandName.FORMATBLOCK]?.let {
|
||||
// formatCommandState
|
||||
// ->
|
||||
// commandStates.put(CommandName.H1, CommandState(formatCommandState.executable, isFormatActivated(formatCommandState, "h1")))
|
||||
// commandStates.put(CommandName.H2, CommandState(formatCommandState.executable, isFormatActivated(formatCommandState, "h2")))
|
||||
// commandStates.put(CommandName.H3, CommandState(formatCommandState.executable, isFormatActivated(formatCommandState, "h3")))
|
||||
// commandStates.put(CommandName.H4, CommandState(formatCommandState.executable, isFormatActivated(formatCommandState, "h4")))
|
||||
// commandStates.put(CommandName.H5, CommandState(formatCommandState.executable, isFormatActivated(formatCommandState, "h5")))
|
||||
// commandStates.put(CommandName.H6, CommandState(formatCommandState.executable, isFormatActivated(formatCommandState, "h6")))
|
||||
// commandStates.put(CommandName.P, CommandState(formatCommandState.executable, isFormatActivated(formatCommandState, "p")))
|
||||
// commandStates.put(CommandName.PRE, CommandState(formatCommandState.executable, isFormatActivated(formatCommandState, "pre")))
|
||||
// commandStates.put(CommandName.BR, CommandState(formatCommandState.executable, isFormatActivated(formatCommandState, "")))
|
||||
// commandStates.put(CommandName.BLOCKQUOTE, CommandState(formatCommandState.executable, isFormatActivated(formatCommandState, "blockquote")))
|
||||
// }
|
||||
//
|
||||
// commandStates[CommandName.INSERTHTML]?.let { insertHtmlState ->
|
||||
// commandStates.put(CommandName.INSERTLINK, insertHtmlState)
|
||||
// commandStates.put(CommandName.INSERTIMAGE, insertHtmlState)
|
||||
// commandStates.put(CommandName.INSERTCHECKBOX, insertHtmlState)
|
||||
// }
|
||||
}
|
||||
|
||||
String isFormatActivated(CommandState formatCommandState, String format) {
|
||||
return (formatCommandState.value == format)
|
||||
.toString(); // rich_text_editor.js reports boolean values as string, so we also have to convert it to string
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user