diff --git a/example/lib/main.dart b/example/lib/main.dart index 147d388..4a31452 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -105,7 +105,8 @@ class _MyHomePageState extends State { placeholder: 'Start typing', // backgroundColor: Colors.blueGrey, // baseTextColor: Colors.white, - padding: EdgeInsets.symmetric(horizontal: 50.0), + padding: EdgeInsets.symmetric(horizontal: 5.0), + baseFontFamily: 'sans-serif', // 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) { diff --git a/example/pubspec.lock b/example/pubspec.lock index da1c2d2..15051c1 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -281,7 +281,7 @@ packages: path: ".." relative: true source: path - version: "0.0.1" + version: "0.0.2" sky_engine: dependency: transitive description: flutter diff --git a/lib/src/rendering/rich_editor.dart b/lib/src/rendering/rich_editor.dart index bded914..54494d8 100644 --- a/lib/src/rendering/rich_editor.dart +++ b/lib/src/rendering/rich_editor.dart @@ -15,19 +15,21 @@ class RichEditor extends StatefulWidget { final Color? baseTextColor; final EdgeInsets? padding; final String? placeholder; + final String? baseFontFamily; final Function(File image)? getImageUrl; final Function(File video)? getVideoUrl; - RichEditor( - {Key? key, - this.value, - this.backgroundColor, - this.baseTextColor, - this.padding, - this.placeholder, - this.getImageUrl, - this.getVideoUrl}) - : super(key: key); + RichEditor({ + Key? key, + this.value, + this.backgroundColor, + this.baseTextColor, + this.padding, + this.placeholder, + this.baseFontFamily, + this.getImageUrl, + this.getVideoUrl, + }) : super(key: key); @override RichEditorState createState() => RichEditorState(); @@ -35,30 +37,29 @@ class RichEditor extends StatefulWidget { 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(); + JavascriptExecutorBase javascriptExecutor = JavascriptExecutorBase(); @override void initState() { super.initState(); if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView(); if (!Platform.isAndroid) { - initServer(); + _initServer(); } } - initServer() async { + _initServer() async { localServer = LocalServer(port); - await localServer!.start(handleRequest); + await localServer!.start(_handleRequest); } - void handleRequest(HttpRequest request) { + void _handleRequest(HttpRequest request) { try { if (request.method == 'GET' && request.uri.queryParameters['query'] == "getRawTeXHTML") { @@ -91,7 +92,7 @@ class RichEditorState extends State { EditorToolBar( controller: _controller, getImageUrl: widget.getImageUrl, - javascriptExecutorBase: javascriptExecutorBase, + javascriptExecutor: javascriptExecutor, ), Expanded( child: WebView( @@ -105,7 +106,7 @@ class RichEditorState extends State { await _controller! .loadUrl('file:///android_asset/flutter_assets/$assetPath'); } - javascriptExecutorBase.init(_controller!); + javascriptExecutor.init(_controller!); }, onPageFinished: (link) async { await _setInitialValues(); @@ -125,44 +126,70 @@ class RichEditorState extends State { } _setInitialValues() async { - if (widget.value != null) - await javascriptExecutorBase.setHtml(widget.value!); + if (widget.value != null) await javascriptExecutor.setHtml(widget.value!); if (widget.padding != null) - await javascriptExecutorBase.setPadding(widget.padding!); + await javascriptExecutor.setPadding(widget.padding!); if (widget.backgroundColor != null) - await javascriptExecutorBase.setBackgroundColor(widget.backgroundColor!); + await javascriptExecutor.setBackgroundColor(widget.backgroundColor!); if (widget.baseTextColor != null) - await javascriptExecutorBase.setBaseTextColor(widget.baseTextColor!); + await javascriptExecutor.setBaseTextColor(widget.baseTextColor!); if (widget.placeholder != null) - await javascriptExecutorBase.setPlaceholder(widget.placeholder!); + await javascriptExecutor.setPlaceholder(widget.placeholder!); + if (widget.baseFontFamily != null) + await javascriptExecutor.setBaseFontFamily(widget.baseFontFamily!); } + /// Get current HTML from editor Future getHtml() async { try { - html = await javascriptExecutorBase.getCurrentHtml(); + html = await javascriptExecutor.getCurrentHtml(); } catch (e) {} return html; } + /// Set your HTML to the editor setHtml(String html) async { - return await javascriptExecutorBase.setHtml(html); + return await javascriptExecutor.setHtml(html); } - // Hide the keyboard using JavaScript since it's being opened in a WebView - // https://stackoverflow.com/a/8263376/10835183 + /// Hide the keyboard using JavaScript since it's being opened in a WebView + /// https://stackoverflow.com/a/8263376/10835183 unFocus() { - javascriptExecutorBase.unFocus(); + javascriptExecutor.unFocus(); } - // Clear editor content using Javascript + /// 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 and Show the keyboard using JavaScript + /// https://stackoverflow.com/a/6809236/10835183 focus() { - javascriptExecutorBase.focus(); + javascriptExecutor.focus(); + } + + /// Add custom CSS code to Editor + loadCSS(String cssFile) { + var jsCSSImport = "(function() {" + + " var head = document.getElementsByTagName(\"head\")[0];" + + " var link = document.createElement(\"link\");" + + " link.rel = \"stylesheet\";" + + " link.type = \"text/css\";" + + " link.href = \"" + + cssFile + + "\";" + + " link.media = \"all\";" + + " head.appendChild(link);" + + "}) ();"; + _controller!.evaluateJavascript(jsCSSImport); + } + + /// if html is equal to html RichTextEditor sets by default at start + /// (

) so that RichTextEditor can be considered as 'empty'. + Future isEmpty() async { + html = await javascriptExecutor.getCurrentHtml(); + return html == '

'; } } diff --git a/lib/src/utils/font_list_parser.dart b/lib/src/utils/font_list_parser.dart index 8f84afd..0b98637 100644 --- a/lib/src/utils/font_list_parser.dart +++ b/lib/src/utils/font_list_parser.dart @@ -8,7 +8,7 @@ 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 +/// A simple port of FontListParser from Java to Dart /// See https://stackoverflow.com/a/29533686/10835183 class FontListParser { File androidFontsFile = File("/system/etc/fonts.xml"); @@ -39,7 +39,7 @@ class FontListParser { break; } } - if( font.t != null) { + if (font.t != null) { SystemFont systemFont = new SystemFont(family.name!, font.t!); if (fonts.contains(systemFont)) { continue; @@ -76,7 +76,7 @@ class FontListParser { return fonts; } - /// Gets font from the list defined incase the above function doesn't work + /// Gets font from the list defined in case the above function doesn't work List safelyGetSystemFonts() { try { return getSystemFonts(); @@ -85,16 +85,16 @@ class FontListParser { ["cursive", "DancingScript-Regular.ttf"], ["monospace", "DroidSansMono.ttf"], ["sans-serif", "Roboto-Regular.ttf"], - ["sans-serif-light" "Roboto-Light.ttf"], - ["sans-serif-medium", "Roboto-Medium.ttf"], - ["sans-serif-black", "Roboto-Black.ttf"], - ["sans-serif-condensed", "RobotoCondensed-Regular.ttf"], - ["sans-serif-thin", "Roboto-Thin.ttf"], + ["sans-serif-light" "Roboto-Light.ttf"], + ["sans-serif-medium", "Roboto-Medium.ttf"], + ["sans-serif-black", "Roboto-Black.ttf"], + ["sans-serif-condensed", "RobotoCondensed-Regular.ttf"], + ["sans-serif-thin", "Roboto-Thin.ttf"], ["serif", "NotoSerif-Regular.ttf"] ]; List fonts = []; for (List names in defaultSystemFonts) { - File file = new File("/system/fonts/"+ names[1]); + File file = new File("/system/fonts/" + names[1]); if (file.existsSync()) { fonts.add(new SystemFont(names[0], file.path)); } diff --git a/lib/src/utils/javascript_executor_base.dart b/lib/src/utils/javascript_executor_base.dart index a564049..e5ac1ad 100644 --- a/lib/src/utils/javascript_executor_base.dart +++ b/lib/src/utils/javascript_executor_base.dart @@ -173,48 +173,52 @@ class JavascriptExecutorBase { if (height == null) height = 300; if (alt == null) alt = ''; await executeJavascript( - "insertImage('$url', '$alt', '$width', '$height', $rotation)", + "insertImage('$url', '$alt', '$width', '$height', $rotation);", ); } insertCheckbox(String text) async { - await executeJavascript("insertCheckbox('$text')"); + await executeJavascript("insertCheckbox('$text');"); } insertHtml(String html) async { String? encodedHtml = encodeHtml(html); - await executeJavascript("insertHtml('$encodedHtml')"); + await executeJavascript("insertHtml('$encodedHtml');"); } makeImagesResizeable() async { - await executeJavascript("makeImagesResizeable()"); + await executeJavascript("makeImagesResizeable();"); } disableImageResizing() async { - await executeJavascript("disableImageResizing()"); + await executeJavascript("disableImageResizing();"); } // Editor settings commands focus() async { - await executeJavascript("focus()"); + await executeJavascript("focus();"); } unFocus() async { - await executeJavascript("blurFocus()"); + await executeJavascript("blurFocus();"); } setBackgroundColor(Color? color) async { String? hex = color!.toHexColorString(); - await executeJavascript("setBackgroundColor('$hex')"); + await executeJavascript("setBackgroundColor('$hex');"); } setBackgroundImage(String image) async { - await executeJavascript("setBackgroundImage('$image')"); + await executeJavascript("setBackgroundImage('$image');"); } setBaseTextColor(Color? color) async { String? hex = color!.toHexColorString(); - await executeJavascript("setBaseTextColor('$hex')"); + await executeJavascript("setBaseTextColor('$hex');"); + } + + setBaseFontFamily(String fontFamily) async { + await executeJavascript("setBaseFontFamily('$fontFamily');"); } setPadding(EdgeInsets? padding) async { @@ -222,11 +226,21 @@ class JavascriptExecutorBase { String top = padding.top.toString(); String right = padding.right.toString(); String bottom = padding.bottom.toString(); - await executeJavascript("setPadding('$left', '$top', '$right', '$bottom')"); + await executeJavascript( + "setPadding('${left}px', '${top}px', '${right}px', '${bottom}px');"); } + // Doesnt actually work for' now setPlaceholder(String placeholder) async { - await executeJavascript("setPlaceholder('$placeholder')"); + await executeJavascript("setPlaceholder('$placeholder');"); + } + + setEditorWidth(int px) async { + await executeJavascript("setWidth('" + px.toString() + "px');"); + } + + setEditorHeight(int px) async { + await executeJavascript("setHeight('" + px.toString() + "px');"); } static decodeHtml(String html) { diff --git a/lib/src/widgets/editor_tool_bar.dart b/lib/src/widgets/editor_tool_bar.dart index 18302fb..4dd059b 100644 --- a/lib/src/widgets/editor_tool_bar.dart +++ b/lib/src/widgets/editor_tool_bar.dart @@ -17,23 +17,18 @@ class EditorToolBar extends StatelessWidget { final WebViewController? controller; final Function(File image)? getImageUrl; final Function(File video)? getVideoUrl; - final JavascriptExecutorBase javascriptExecutorBase; + final JavascriptExecutorBase javascriptExecutor; EditorToolBar({ this.controller, this.getImageUrl, this.getVideoUrl, - required this.javascriptExecutorBase, + required this.javascriptExecutor, }); @override Widget build(BuildContext context) { - // if (controller != null) { - // javascriptExecutorBase.init(controller!); - // } - return Container( - // color: Color(0xff424242), height: 54.0, child: Column( children: [ @@ -46,21 +41,20 @@ class EditorToolBar extends StatelessWidget { tooltip: 'Bold', icon: Icons.format_bold, onTap: () async { - await javascriptExecutorBase.setBold(); + await javascriptExecutor.setBold(); }, ), TabButton( tooltip: 'Italic', icon: Icons.format_italic, onTap: () async { - await javascriptExecutorBase.setItalic(); + await javascriptExecutor.setItalic(); }, ), TabButton( tooltip: 'Insert Link', icon: Icons.link, onTap: () async { - _closeKeyboard(); var link = await showDialog( context: context, barrierDismissible: false, @@ -69,14 +63,13 @@ class EditorToolBar extends StatelessWidget { }, ); if (link != null) - await javascriptExecutorBase.insertLink(link[0], link[1]); + await javascriptExecutor.insertLink(link[0], link[1]); }, ), TabButton( tooltip: 'Insert Image', icon: Icons.image, onTap: () async { - _closeKeyboard(); var link = await showDialog( context: context, barrierDismissible: false, @@ -88,7 +81,7 @@ class EditorToolBar extends StatelessWidget { if (getImageUrl != null && link[2]) { link[0] = await getImageUrl!(File(link[0])); } - await javascriptExecutorBase.insertImage( + await javascriptExecutor.insertImage( link[0], alt: link[1], ); @@ -99,63 +92,62 @@ class EditorToolBar extends StatelessWidget { tooltip: 'Underline', icon: Icons.format_underline, onTap: () async { - await javascriptExecutorBase.setUnderline(); + await javascriptExecutor.setUnderline(); }, ), TabButton( tooltip: 'Strike through', icon: Icons.format_strikethrough, onTap: () async { - await javascriptExecutorBase.setStrikeThrough(); + await javascriptExecutor.setStrikeThrough(); }, ), TabButton( tooltip: 'Superscript', icon: Icons.superscript, onTap: () async { - await javascriptExecutorBase.setSuperscript(); + await javascriptExecutor.setSuperscript(); }, ), TabButton( tooltip: 'Subscript', icon: Icons.subscript, onTap: () async { - await javascriptExecutorBase.setSubscript(); + await javascriptExecutor.setSubscript(); }, ), TabButton( tooltip: 'Clear format', icon: Icons.format_clear, onTap: () async { - await javascriptExecutorBase.removeFormat(); + await javascriptExecutor.removeFormat(); }, ), TabButton( tooltip: 'Undo', icon: Icons.undo, onTap: () async { - await javascriptExecutorBase.undo(); + await javascriptExecutor.undo(); }, ), TabButton( tooltip: 'Redo', icon: Icons.redo, onTap: () async { - await javascriptExecutorBase.redo(); + await javascriptExecutor.redo(); }, ), TabButton( tooltip: 'Blockquote', icon: Icons.format_quote, onTap: () async { - await javascriptExecutorBase.setBlockQuote(); + await javascriptExecutor.setBlockQuote(); }, ), TabButton( tooltip: 'Font format', icon: Icons.text_format, onTap: () async { - _closeKeyboard(); var command = await showDialog( // isScrollControlled: true, context: context, @@ -165,13 +157,13 @@ class EditorToolBar extends StatelessWidget { ); if (command != null) { if (command == 'p') { - await javascriptExecutorBase.setFormattingToParagraph(); + await javascriptExecutor.setFormattingToParagraph(); } else if (command == 'pre') { - await javascriptExecutorBase.setPreformat(); + await javascriptExecutor.setPreformat(); } else if (command == 'blockquote') { - await javascriptExecutorBase.setBlockQuote(); + await javascriptExecutor.setBlockQuote(); } else { - await javascriptExecutorBase + await javascriptExecutor .setHeading(int.tryParse(command)!); } } @@ -184,9 +176,6 @@ class EditorToolBar extends StatelessWidget { tooltip: 'Font face', icon: Icons.font_download, onTap: () async { - Directory fontsDir = Directory("/system/fonts/"); - File file = File('/system/etc/fonts.xml'); - // debugPrint(await file.readAsString()); var command = await showDialog( // isScrollControlled: true, context: context, @@ -194,8 +183,9 @@ class EditorToolBar extends StatelessWidget { return FontsDialog(); }, ); + print(command); if (command != null) - await javascriptExecutorBase.setFontName(command); + await javascriptExecutor.setFontName(command); }, ), ), @@ -203,7 +193,6 @@ class EditorToolBar extends StatelessWidget { icon: Icons.format_size, tooltip: 'Font Size', onTap: () async { - _closeKeyboard(); String? command = await showDialog( // isScrollControlled: true, context: context, @@ -212,7 +201,7 @@ class EditorToolBar extends StatelessWidget { }, ); if (command != null) - await javascriptExecutorBase + await javascriptExecutor .setFontSize(int.tryParse(command)!); }, ), @@ -220,7 +209,6 @@ class EditorToolBar extends StatelessWidget { tooltip: 'Text Color', icon: Icons.format_color_text, onTap: () async { - _closeKeyboard(); var color = await showDialog( context: context, builder: (BuildContext context) { @@ -228,14 +216,13 @@ class EditorToolBar extends StatelessWidget { }, ); if (color != null) - await javascriptExecutorBase.setTextColor(color); + await javascriptExecutor.setTextColor(color); }, ), TabButton( tooltip: 'Background Color', icon: Icons.format_color_fill, onTap: () async { - _closeKeyboard(); var color = await showDialog( context: context, builder: (BuildContext context) { @@ -243,71 +230,69 @@ class EditorToolBar extends StatelessWidget { }, ); if (color != null) - await javascriptExecutorBase - .setTextBackgroundColor(color); + await javascriptExecutor.setTextBackgroundColor(color); }, ), TabButton( tooltip: 'Increase Indent', icon: Icons.format_indent_increase, onTap: () async { - await javascriptExecutorBase.setIndent(); + await javascriptExecutor.setIndent(); }, ), TabButton( tooltip: 'Decrease Indent', icon: Icons.format_indent_decrease, onTap: () async { - await javascriptExecutorBase.setOutdent(); + await javascriptExecutor.setOutdent(); }, ), TabButton( tooltip: 'Align Left', icon: Icons.format_align_left_outlined, onTap: () async { - await javascriptExecutorBase.setJustifyLeft(); + await javascriptExecutor.setJustifyLeft(); }, ), TabButton( tooltip: 'Align Center', icon: Icons.format_align_center, onTap: () async { - await javascriptExecutorBase.setJustifyCenter(); + await javascriptExecutor.setJustifyCenter(); }, ), TabButton( tooltip: 'Align Right', icon: Icons.format_align_right, onTap: () async { - await javascriptExecutorBase.setJustifyRight(); + await javascriptExecutor.setJustifyRight(); }, ), TabButton( tooltip: 'Justify', icon: Icons.format_align_justify, onTap: () async { - await javascriptExecutorBase.setJustifyFull(); + await javascriptExecutor.setJustifyFull(); }, ), TabButton( tooltip: 'Bullet List', icon: Icons.format_list_bulleted, onTap: () async { - await javascriptExecutorBase.insertBulletList(); + await javascriptExecutor.insertBulletList(); }, ), TabButton( tooltip: 'Numbered List', icon: Icons.format_list_numbered, onTap: () async { - await javascriptExecutorBase.insertNumberedList(); + await javascriptExecutor.insertNumberedList(); }, ), TabButton( tooltip: 'Checkbox', icon: Icons.check_box_outlined, onTap: () async { - _closeKeyboard(); var text = await showDialog( context: context, builder: (BuildContext context) { @@ -316,7 +301,7 @@ class EditorToolBar extends StatelessWidget { ); print(text); if (text != null) - await javascriptExecutorBase.insertCheckbox(text); + await javascriptExecutor.insertCheckbox(text); }, ), @@ -325,7 +310,7 @@ class EditorToolBar extends StatelessWidget { // tooltip: 'Search', // icon: Icons.search, // onTap: () async { - // // await javascriptExecutorBase.insertNumberedList(); + // // await javascriptExecutor.insertNumberedList(); // }, // ), ], @@ -335,10 +320,4 @@ class EditorToolBar extends StatelessWidget { ), ); } - - // Hide the keyboard using JavaScript since it's being opened in a WebView - // https://stackoverflow.com/a/8263376/10835183 - _closeKeyboard() async { - // controller!.evaluateJavascript('document.activeElement.blur();'); - } } diff --git a/lib/src/widgets/fonts_dialog.dart b/lib/src/widgets/fonts_dialog.dart index 8846127..fe6c372 100644 --- a/lib/src/widgets/fonts_dialog.dart +++ b/lib/src/widgets/fonts_dialog.dart @@ -23,19 +23,13 @@ class FontsDialog extends StatelessWidget { InkWell( child: Html(data: '

' '${basename(font.path!)}

'), - onTap: () => Navigator.pop(context, font.path), + onTap: () { + Navigator.pop(context, font.name); + }, ) ], ), ), ); } - - fontSlug(FileSystemEntity font) { - String name = basename(font.path); - String slug = name.toLowerCase(); - slug = slug.replaceAll(extension(font.path), ''); - // print(slug); - return slug; - } }