diff --git a/README.md b/README.md index a35681d..80e45fc 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,6 @@ Based on https://github.com/dankito/RichTextEditor, but for Flutter. ## 📸 Screenshots - ## Usage diff --git a/example/lib/basic.dart b/example/lib/basic.dart index b821535..64e6609 100644 --- a/example/lib/basic.dart +++ b/example/lib/basic.dart @@ -60,14 +60,11 @@ class BasicDemo extends StatelessWidget { ), body: RichEditor( key: keyEditor, -// value: ''' -//

Heading 1

-//

Heading 2

-//

Heading 3

-//

Heading 4

-//
Heading 5
-//
Heading 6
-// ''', // initial HTML data + value: ''' + Hello, This is a rich text Editor for Flutter. It supports most things like Bold, italics and underline. + As well as Subscript, Superscript, Colored text, Colors bg text and hyperlink. + Images and Videos are also supports + ''', // initial HTML data editorOptions: RichEditorOptions( placeholder: 'Start typing', // backgroundColor: Colors.blueGrey, // Editor's bg color diff --git a/example/pubspec.lock b/example/pubspec.lock index 063bc6c..09e7406 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -211,7 +211,7 @@ packages: path: ".." relative: true source: path - version: "0.0.3" + version: "0.0.4" sky_engine: dependency: transitive description: flutter diff --git a/lib/src/utils/javascript_executor_base.dart b/lib/src/utils/javascript_executor_base.dart index e0a6cd7..61b3112 100644 --- a/lib/src/utils/javascript_executor_base.dart +++ b/lib/src/utils/javascript_executor_base.dart @@ -1,12 +1,10 @@ -import 'dart:convert'; - import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:rich_editor/src/extensions/extensions.dart'; import 'package:rich_editor/src/models/callbacks/did_html_change_listener.dart'; import 'package:rich_editor/src/models/callbacks/html_changed_listener.dart'; import 'package:rich_editor/src/models/callbacks/loaded_listener.dart'; -import 'package:rich_editor/src/models/editor_state.dart'; import 'package:rich_editor/src/models/enum/command_name.dart'; import '../models/command_state.dart'; @@ -43,10 +41,13 @@ class JavascriptExecutorBase { List loadedListeners = []; + /// Initialise the controller so we don't have to + /// pass a controller into every Method init(InAppWebViewController? controller) { _controller = controller; } + /// Run Javascript commands in the editor using the webview controller executeJavascript(String command) async { return await _controller!.evaluateJavascript(source: 'editor.$command'); } @@ -55,14 +56,16 @@ class JavascriptExecutorBase { return htmlField!; } + /// Display HTML data in editor setHtml(String html) async { String? baseUrl; await executeJavascript("setHtml('" + encodeHtml(html) + "', '$baseUrl');"); htmlField = html; } + /// Get current HTML data from Editor getCurrentHtml() async { - String? html = await executeJavascript('getEncodedHtml()'); + String? html = await executeJavascript('getEncodedHtml();'); String? decodedHtml = decodeHtml(html!); if (decodedHtml!.startsWith('"') && decodedHtml.endsWith('"')) { decodedHtml = decodedHtml.substring(1, decodedHtml.length - 1); @@ -70,57 +73,72 @@ class JavascriptExecutorBase { return decodedHtml; } + /// Check if editor's content has been modified bool isDefaultRichTextEditorHtml(String html) { return defaultHtml == html; } // Text commands + + /// Undo last editor command/action undo() async { await executeJavascript("undo();"); } + /// Redo last editor command/action redo() async { await executeJavascript("redo();"); } + /// Make selected or subsequent text Bold setBold() async { await executeJavascript("setBold();"); } + /// Make selected or subsequent text Italic setItalic() async { await executeJavascript("setItalic();"); } + /// Make selected or subsequent text Underlined setUnderline() async { await executeJavascript("setUnderline();"); } + /// Make selected or subsequent text Subscript setSubscript() async { await executeJavascript("setSubscript();"); } + /// Make selected or subsequent text Superscript setSuperscript() async { await executeJavascript("setSuperscript();"); } + /// Strike through selected text setStrikeThrough() async { await executeJavascript("setStrikeThrough();"); } + /// Set a [Color] for the selected text setTextColor(Color? color) async { String? hex = color!.toHexColorString(); await executeJavascript("setTextColor('$hex');"); } + /// Set a [Color] for the selected text's background setTextBackgroundColor(Color? color) async { String? hex = color!.toHexColorString(); await executeJavascript("setTextBackgroundColor('$hex');"); } + /// Apply a font face to selected text setFontName(String fontName) async { await executeJavascript("setFontName('$fontName');"); } + /// Apply a font size to selected text + /// (Value can only be between 1 and 7) setFontSize(int fontSize) async { if (fontSize < 1 || fontSize > 7) { throw ("Font size should have a value between 1-7"); @@ -128,6 +146,8 @@ class JavascriptExecutorBase { await executeJavascript("setFontSize('$fontSize');"); } + /// Apply a Heading style to selected text + /// (Value can only be between 1 and 6) setHeading(int heading) async { await executeJavascript("setHeading('$heading');"); } @@ -140,47 +160,58 @@ class JavascriptExecutorBase { await executeJavascript("setPreformat();"); } + /// Create BlockQuote / make selected text a BlockQuote setBlockQuote() async { await executeJavascript("setBlockQuote();"); } + /// Remove formatting from selected text removeFormat() async { await executeJavascript("removeFormat();"); } + /// Align content left setJustifyLeft() async { await executeJavascript("setJustifyLeft();"); } + /// Align content center setJustifyCenter() async { await executeJavascript("setJustifyCenter();"); } + /// Align content right setJustifyRight() async { await executeJavascript("setJustifyRight();"); } + /// Justify content setJustifyFull() async { await executeJavascript("setJustifyFull();"); } + /// Add indentation setIndent() async { await executeJavascript("setIndent();"); } + /// Remove indentation setOutdent() async { await executeJavascript("setOutdent();"); } + /// Start an unordered list insertBulletList() async { await executeJavascript("insertBulletList();"); } + /// Start a ordered list insertNumberedList() async { await executeJavascript("insertNumberedList();"); } // Insert element + /// Insert hyper link / make selected text an hyperlink insertLink(String url, String title) async { await executeJavascript("insertLink('$url', '$title');"); } @@ -217,50 +248,63 @@ class JavascriptExecutorBase { ); } + /// Add a checkbox to the current editor insertCheckbox(String text) async { await executeJavascript("insertCheckbox('$text');"); } + /// Insert HTML code into the editor + /// (It wont display the HTML code but it'll render it) insertHtml(String html) async { String? encodedHtml = encodeHtml(html); await executeJavascript("insertHtml('$encodedHtml');"); } + /// Enable Images resizing makeImagesResizeable() async { await executeJavascript("makeImagesResizeable();"); } + /// Disable Images resizing disableImageResizing() async { await executeJavascript("disableImageResizing();"); } // Editor settings commands + /// Focus on editor and bring up keyboard focus() async { await executeJavascript("focus();"); + SystemChannels.textInput.invokeMethod('TextInput.show'); } + /// Remove focus from the editor and close the keyboard unFocus() async { await executeJavascript("blurFocus();"); } + /// Set a [Color] for the editor's background setBackgroundColor(Color? color) async { String? hex = color!.toHexColorString(); await executeJavascript("setBackgroundColor('$hex');"); } + /// Set an image for the editor's background setBackgroundImage(String image) async { await executeJavascript("setBackgroundImage('$image');"); } + /// Set a default editor text color setBaseTextColor(Color? color) async { String? hex = color!.toHexColorString(); await executeJavascript("setBaseTextColor('$hex');"); } + /// Set a default editor text font family setBaseFontFamily(String fontFamily) async { await executeJavascript("setBaseFontFamily('$fontFamily');"); } + /// Add padding to the editor's content setPadding(EdgeInsets? padding) async { String left = padding!.left.toString(); String top = padding.top.toString(); @@ -270,19 +314,23 @@ class JavascriptExecutorBase { "setPadding('${left}px', '${top}px', '${right}px', '${bottom}px');"); } - // Doesnt actually work for' now + /// Set a hint when the editor is empty + /// Doesn't actually work for now setPlaceholder(String placeholder) async { await executeJavascript("setPlaceholder('$placeholder');"); } + /// Set editor's width in pixels setEditorWidth(int px) async { await executeJavascript("setWidth('" + px.toString() + "px');"); } + /// Set editor's height in pixels setEditorHeight(int px) async { await executeJavascript("setHeight('" + px.toString() + "px');"); } + /// Enable text input on editor setInputEnabled(bool inputEnabled) async { await executeJavascript("setInputEnabled($inputEnabled);"); } @@ -295,143 +343,143 @@ 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 commandStates) { - if (this.didHtmlChange != didHtmlChange) { - this.didHtmlChange = didHtmlChange; - didHtmlChangeListeners.forEach((element) { - element.didHtmlChange(didHtmlChange); - }); - } - - handleRetrievedCommandStates(commandStates); - } - - handleRetrievedCommandStates(Map commandStates) { - determineDerivedCommandStates(commandStates); - - this.commandStates = commandStates; - commandStatesChangedListeners.forEach((element) { - element = this.commandStates; - }); - } - - determineDerivedCommandStates(Map commandStates) { - if (commandStates[CommandName.FORMATBLOCK] != null) { - var formatCommandState = commandStates[CommandName.FORMATBLOCK]; - commandStates.update( - CommandName.H1, - (val) => CommandState(formatCommandState!.executable, - isFormatActivated(formatCommandState, "h1")), - ); - commandStates.update( - CommandName.H2, - (val) => CommandState(formatCommandState!.executable, - isFormatActivated(formatCommandState, "h2"))); - commandStates.update( - CommandName.H3, - (val) => CommandState(formatCommandState!.executable, - isFormatActivated(formatCommandState, "h3")), - ); - commandStates.update( - CommandName.H4, - (val) => CommandState(formatCommandState!.executable, - isFormatActivated(formatCommandState, "h4")), - ); - commandStates.update( - CommandName.H5, - (val) => CommandState(formatCommandState!.executable, - isFormatActivated(formatCommandState, "h5")), - ); - commandStates.update( - CommandName.H6, - (val) => CommandState(formatCommandState!.executable, - isFormatActivated(formatCommandState, "h6")), - ); - commandStates.update( - CommandName.P, - (val) => CommandState(formatCommandState!.executable, - isFormatActivated(formatCommandState, "p")), - ); - commandStates.update( - CommandName.PRE, - (val) => CommandState(formatCommandState!.executable, - isFormatActivated(formatCommandState, "pre")), - ); - commandStates.update( - CommandName.BR, - (val) => CommandState(formatCommandState!.executable, - isFormatActivated(formatCommandState, "")), - ); - commandStates.update( - CommandName.BLOCKQUOTE, - (val) => CommandState(formatCommandState!.executable, - isFormatActivated(formatCommandState, "blockquote")), - ); - } - - if (commandStates[CommandName.INSERTHTML] != null) { - CommandState? insertHtmlState = commandStates[CommandName.INSERTHTML]; - commandStates.update(CommandName.INSERTLINK, (val) => insertHtmlState!); - commandStates.update(CommandName.INSERTIMAGE, (val) => insertHtmlState!); - commandStates.update( - CommandName.INSERTCHECKBOX, (val) => 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 - } - - addCommandStatesChangedListener( - Map commandStates) { - commandStatesChangedListeners.add(commandStates); - - // listener.invoke(commandStates); - } - - addDidHtmlChangeListener(DidHtmlChangeListener listener) { - didHtmlChangeListeners.add(listener); - } - - addHtmlChangedListener(HtmlChangedListener listener) { - htmlChangedListeners.add(listener); - } +// 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 commandStates) { +// if (this.didHtmlChange != didHtmlChange) { +// this.didHtmlChange = didHtmlChange; +// didHtmlChangeListeners.forEach((element) { +// element.didHtmlChange(didHtmlChange); +// }); +// } +// +// handleRetrievedCommandStates(commandStates); +// } +// +// handleRetrievedCommandStates(Map commandStates) { +// determineDerivedCommandStates(commandStates); +// +// this.commandStates = commandStates; +// commandStatesChangedListeners.forEach((element) { +// element = this.commandStates; +// }); +// } +// +// determineDerivedCommandStates(Map commandStates) { +// if (commandStates[CommandName.FORMATBLOCK] != null) { +// var formatCommandState = commandStates[CommandName.FORMATBLOCK]; +// commandStates.update( +// CommandName.H1, +// (val) => CommandState(formatCommandState!.executable, +// isFormatActivated(formatCommandState, "h1")), +// ); +// commandStates.update( +// CommandName.H2, +// (val) => CommandState(formatCommandState!.executable, +// isFormatActivated(formatCommandState, "h2"))); +// commandStates.update( +// CommandName.H3, +// (val) => CommandState(formatCommandState!.executable, +// isFormatActivated(formatCommandState, "h3")), +// ); +// commandStates.update( +// CommandName.H4, +// (val) => CommandState(formatCommandState!.executable, +// isFormatActivated(formatCommandState, "h4")), +// ); +// commandStates.update( +// CommandName.H5, +// (val) => CommandState(formatCommandState!.executable, +// isFormatActivated(formatCommandState, "h5")), +// ); +// commandStates.update( +// CommandName.H6, +// (val) => CommandState(formatCommandState!.executable, +// isFormatActivated(formatCommandState, "h6")), +// ); +// commandStates.update( +// CommandName.P, +// (val) => CommandState(formatCommandState!.executable, +// isFormatActivated(formatCommandState, "p")), +// ); +// commandStates.update( +// CommandName.PRE, +// (val) => CommandState(formatCommandState!.executable, +// isFormatActivated(formatCommandState, "pre")), +// ); +// commandStates.update( +// CommandName.BR, +// (val) => CommandState(formatCommandState!.executable, +// isFormatActivated(formatCommandState, "")), +// ); +// commandStates.update( +// CommandName.BLOCKQUOTE, +// (val) => CommandState(formatCommandState!.executable, +// isFormatActivated(formatCommandState, "blockquote")), +// ); +// } +// +// if (commandStates[CommandName.INSERTHTML] != null) { +// CommandState? insertHtmlState = commandStates[CommandName.INSERTHTML]; +// commandStates.update(CommandName.INSERTLINK, (val) => insertHtmlState!); +// commandStates.update(CommandName.INSERTIMAGE, (val) => insertHtmlState!); +// commandStates.update( +// CommandName.INSERTCHECKBOX, (val) => 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 +// } +// +// addCommandStatesChangedListener( +// Map commandStates) { +// commandStatesChangedListeners.add(commandStates); +// +// // listener.invoke(commandStates); +// } +// +// addDidHtmlChangeListener(DidHtmlChangeListener listener) { +// didHtmlChangeListeners.add(listener); +// } +// +// addHtmlChangedListener(HtmlChangedListener listener) { +// htmlChangedListeners.add(listener); +// } } diff --git a/pubspec.yaml b/pubspec.yaml index b6f30d1..c904e27 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: rich_editor description: WYSIWYG editor for Flutter with a rich set of supported formatting options. -version: 0.0.3 +version: 0.0.4 homepage: https://github.com/JideGuru/rich_editor environment: diff --git a/res/1.png b/res/1.png index a1a4f4c..4d7b220 100644 Binary files a/res/1.png and b/res/1.png differ diff --git a/res/2.png b/res/2.png deleted file mode 100644 index a72873d..0000000 Binary files a/res/2.png and /dev/null differ